1.4.3 • Published 5 years ago

node-red-contrib-salesforce-connection-emitter v1.4.3

Weekly downloads
9
License
MIT
Repository
github
Last release
5 years ago

Overview

A set of Node Red commands that allows nodes to better connect to salesforce (using environment variables, connection pools and config nodes).

Many of the connections we have seen for Salesforce rely on creating a configuration node to store the credentials. This is then used by the various other nodes to connect to salesforce - each with their own connection.

We would instead like to ensure the following:

  • Credentials are secured using best practices
    • While explicitly stating credentials to the config / resulting JSON is sill allowed, environment variables are now also supported. (Providing greater reusability, support for heroku and security)
  • Connections are established at the Connection Credential and emitted to those dependent.
    • Child nodes can get notified of the current connection, when it is disconnected or request it be reset through events.
  • Support for ES6 Classes and subclassing
    • By providing classes that can be extended, listening for the events can become quite simple.

Nodes


connection.SfConnectionEmitter

Configuration Node - used by most (if not all of the other Salesforce commands.

When other nodes specify their type as sf-connection-emitter, then a drop-down dialog allows them to choose which configuration to use.

Each configuration manages the connection to salesforce, and emits events to those listening when:

  • (newConnection) - a connection is established
  • (connectionLost) - the connection has been disconnected
  • (refresh) - the connection should be restarted (logout and re-established)
  • (logout) - request the connection be severed

For nodes that subclass the connection.SfConnectionReceiver - this is all handled for you... For more information, please see that class

Screenshot of ConnectionEmitter

Configuration

Events

The SfConnectionEmitter dispatches four types of events, automatically handled by the

Each configuration manages the connection to salesforce, and emits events to those listening when:

  • (newConnection) - a connection is established
  • (connectionLost) - the connection has been disconnected
  • (refresh) - the connection should be restarted (logout and re-established)
  • (logout) - request the connection be severed

For nodes that subclass the connection.SfConnectionReceiver - this is all handled for you... For more information, please see that class


platformEvents.SfPlatformEventSubscriber

Use this to listen to Salesforce Platform Events

More on Platform Events can also be found on Trailhead.Salesforce.com

Screenshot of subscription

As mentioned above, the replay Id captured is preserved for you automatically.

Note that this is preserved to be only visible to that same node, as opposed to the flow or within the whole project. Please see Node Red's documentation on Node Context for more

While this is a decent stop-gap, future work will allow it to be stored to external services (such as a Redis store).

To force the replay Id, configure it with an exclaimation mark / bang at the end: For example: 12!


platformEvents.SfPlatformEventPublisher

Use this to publish Salesforce Platform Events

Simply apply the object you want to publish as the msg.payload and it will handle the rest.

More on Platform Events can also be found on Trailhead.Salesforce.com

Screenshot of Publisher


query.SfUniversalQuery

Use this to do a SOQL or Tooling API query within Salesforce.

Supports selection of the API, queries that can be (environment variables, global settings, property within a message, etc) and you can specify where the results go.

Example Flow

[{"id":"d2048d63.a9936","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"802b9dc4.7cebc","type":"inject","z":"d2048d63.a9936","name":"","topic":"","payload":"{\"query\":\"select id from Apexclass\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":140,"wires":[["3251ec56.5aef64"]]},{"id":"3251ec56.5aef64","type":"sf-universal-query","z":"d2048d63.a9936","name":"","sfconn":"73000fb4.ffb8e","api":"soql","query":"payload.query","queryType":"msg","target":"payload.some.result.somewhere","limit":100,"x":360,"y":140,"wires":[["a7b76b3.b6f7c98"]]},{"id":"a7b76b3.b6f7c98","type":"debug","z":"d2048d63.a9936","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":590,"y":140,"wires":[]},{"id":"73000fb4.ffb8e","type":"sf-connection-emitter","z":"","host":"SF_HOST","hostType":"env","username":"SF_USERNAME","usernameType":"env","password":"SF_PASSWORD","passwordType":"env","token":"","tokenType":"env"}]

Screenshot of universal query

Properties


describe.SfUniversalDescribe

Use this node to describe all objects or just a particular object using the Metadata API, Tooling API or SOAP API.

The name of the object to describe can also be defined either as a message property, or as a literal string.

Please note there is a bug with Node Red where the sobject appears required when it is not. The sobject is only required when not performing a describe all

Example Flow

[{"id":"7061841b.7dc20c","type":"tab","label":"Describe","disabled":false,"info":""},{"id":"ba6d47c3.0396f8","type":"inject","z":"7061841b.7dc20c","name":"Start","topic":"","payload":"{}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":80,"wires":[["806d933.686d77"]]},{"id":"806d933.686d77","type":"sf-universal-describe","z":"7061841b.7dc20c","name":"","sfconn":"73000fb4.ffb8e","api":"soap","describeAll":true,"objectName":"","objectNameType":"msg","target":"payload.describe","x":380,"y":80,"wires":[["221f1521.c786aa"]]},{"id":"221f1521.c786aa","type":"debug","z":"7061841b.7dc20c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":600,"y":80,"wires":[]},{"id":"ea764edf.67e0c","type":"comment","z":"7061841b.7dc20c","name":"Describe Everything","info":"","x":170,"y":40,"wires":[]},{"id":"6c23a421.ca6f3c","type":"comment","z":"7061841b.7dc20c","name":"","info":"","x":140,"y":160,"wires":[]},{"id":"5c26c6e1.7c68d8","type":"inject","z":"7061841b.7dc20c","name":"{\"sobject\":\"Account\"}","topic":"","payload":"{\"sobject\":\"Account\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":180,"y":220,"wires":[["722eb5e5.bc948c"]]},{"id":"722eb5e5.bc948c","type":"sf-universal-describe","z":"7061841b.7dc20c","name":"","sfconn":"73000fb4.ffb8e","api":"soap","describeAll":false,"objectName":"payload.sobject","objectNameType":"msg","target":"payload.describe","x":420,"y":220,"wires":[["c941c43f.b7d5b8"]]},{"id":"c941c43f.b7d5b8","type":"debug","z":"7061841b.7dc20c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":640,"y":220,"wires":[]},{"id":"73000fb4.ffb8e","type":"sf-connection-emitter","z":"","host":"SF_HOST","hostType":"env","username":"SF_USERNAME","usernameType":"env","password":"SF_PASSWORD","passwordType":"env","token":"","tokenType":"env"}]

Screenshot of universal describe


http.SfUniversalHttp

Use this to perform an HTTP Get request to salesforce.

This is quite often used with the Universal Describe to get further information.

(Note: A common example is to use node-red-contrib-literal-utils to pick the urls from a set of describes, and then use node-red-contrib-serial-iterator to then iterate through each of those values and get the results)

NOTE: Currently, we are only supporting GET. If others factors are needed, please submit an issue and it can be discussed.

Example Flow

[{"id":"d2048d63.a9936","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"b684172.c49dfe8","type":"debug","z":"d2048d63.a9936","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":510,"y":200,"wires":[]},{"id":"67520728.547d28","type":"inject","z":"d2048d63.a9936","name":"{\"url\":\"/services/data/v42.0/sobjects/Account/describe\"}","topic":"","payload":"{\"url\":\"/services/data/v42.0/sobjects/Account/describe\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":270,"y":120,"wires":[["75031448.d6721c"]]},{"id":"75031448.d6721c","type":"sf-universal-http","z":"d2048d63.a9936","name":"","sfconn":"73000fb4.ffb8e","url":"payload.url","urlType":"msg","target":"payload","x":280,"y":200,"wires":[["b684172.c49dfe8"]]},{"id":"73000fb4.ffb8e","type":"sf-connection-emitter","z":"","host":"SF_HOST","hostType":"env","username":"SF_USERNAME","usernameType":"env","password":"SF_PASSWORD","passwordType":"env","token":"","tokenType":"env"}]

Screenshot of universal http

Example Flow with Repeater

A great example of using the http callout is if you have a list of URLs - such as templated from a previous describe...

[{"id":"c9dd3425.d23d88","type":"tab","label":"Flow 2","disabled":false,"info":""},{"id":"f9b2421e.23d19","type":"inject","z":"c9dd3425.d23d88","name":"{\"urls\":[...]}","topic":"","payload":"{\"urls\":[\"/services/data/v42.0/sobjects/Account/describe/compactLayouts\",\"/services/data/v42.0/sobjects/Account/describe/approvalLayouts\",\"/services/data/v42.0/sobjects/Account/listviews\",\"/services/data/v42.0/sobjects/Account/describe\",\"https://speed-inspiration-3102-dev-ed.cs69.my.salesforce.com/001/e\",\"/services/data/v42.0/sobjects/Account/quickActions\",\"/services/data/v42.0/sobjects/Account/describe/layouts\",\"/services/data/v42.0/sobjects/Account\"]}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":120,"y":140,"wires":[["f0240db8.37fa9"]]},{"id":"f0240db8.37fa9","type":"Serial Iterator","z":"c9dd3425.d23d88","name":"Serial Iterator","property":"payload.urls","inputFlow":"feedback","saveOutput":1,"recursive":0,"storeId":0,"x":320,"y":140,"wires":[["c545ae04.d3d54"],["36d218df.df27e8"]]},{"id":"c545ae04.d3d54","type":"sf-universal-http","z":"c9dd3425.d23d88","name":"","sfconn":"73000fb4.ffb8e","url":"payload","urlType":"msg","target":"payload","x":320,"y":220,"wires":[["f0240db8.37fa9"]]},{"id":"36d218df.df27e8","type":"debug","z":"c9dd3425.d23d88","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":550,"y":140,"wires":[]},{"id":"73000fb4.ffb8e","type":"sf-connection-emitter","z":"","host":"SF_HOST","hostType":"env","username":"SF_USERNAME","usernameType":"env","password":"SF_PASSWORD","passwordType":"env","token":"","tokenType":"env"}]

Screenshot of http with serial

Example Flow with Describe and Repeater

(Note: A common example is to use node-red-contrib-literal-utils to pick the urls from a set of describes, and then use node-red-contrib-serial-iterator to then iterate through each of those values and get the results)

[{"id":"833f466c.8e4288","type":"tab","label":"Simple Flow","disabled":false,"info":""},{"id":"9694019f.d3cb7","type":"inject","z":"833f466c.8e4288","name":"Blank Payload","topic":"","payload":"{}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":80,"wires":[["abe1fcd2.d03e5"]]},{"id":"abe1fcd2.d03e5","type":"sf-universal-describe","z":"833f466c.8e4288","name":"","sfconn":"73000fb4.ffb8e","api":"soap","describeAll":true,"objectName":"","objectNameType":"msg","target":"describe","x":340,"y":80,"wires":[["740e1493.83e07c","3de2ebf4.276014"]]},{"id":"740e1493.83e07c","type":"debug","z":"833f466c.8e4288","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":550,"y":40,"wires":[]},{"id":"55cc2fc3.d9035","type":"debug","z":"833f466c.8e4288","name":"Complete","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":360,"y":420,"wires":[]},{"id":"c73609ff.5022c8","type":"Serial Iterator","z":"833f466c.8e4288","name":"","property":"describe.urls","inputFlow":"feedback","saveOutput":1,"recursive":0,"storeId":0,"x":130,"y":300,"wires":[["7bcef9d2.ed5e48"],["55cc2fc3.d9035"]]},{"id":"7bcef9d2.ed5e48","type":"sf-universal-http","z":"833f466c.8e4288","name":"","sfconn":"73000fb4.ffb8e","url":"payload","urlType":"msg","target":"payload","x":370,"y":300,"wires":[["c73609ff.5022c8"]]},{"id":"e5f2e6e5.3be2c8","type":"comment","z":"833f466c.8e4288","name":"Describe the list of objects...","info":"","x":160,"y":40,"wires":[]},{"id":"de2df7bd.d94808","type":"function","z":"833f466c.8e4288","name":"Only describe the first 3 urls","func":"msg.describe.urls = msg.describe.urls.slice(0,3);\nreturn msg;","outputs":1,"noerr":0,"x":380,"y":200,"wires":[["6db56361.e766dc","c73609ff.5022c8"]]},{"id":"3de2ebf4.276014","type":"pick-array-value","z":"833f466c.8e4288","name":"","arrayPath":"describe.sobjects","valuePath":"urls.sobject","targetPath":"describe.urls","x":150,"y":200,"wires":[["de2df7bd.d94808"]]},{"id":"6db56361.e766dc","type":"debug","z":"833f466c.8e4288","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":590,"y":160,"wires":[]},{"id":"5fcec93a.536a38","type":"comment","z":"833f466c.8e4288","name":"Pick the URLs from the array of objects...","info":"","x":200,"y":160,"wires":[]},{"id":"a758b017.2a287","type":"comment","z":"833f466c.8e4288","name":"Loop through the URLs one at a time...","info":"","x":210,"y":260,"wires":[]},{"id":"73000fb4.ffb8e","type":"sf-connection-emitter","z":"","host":"SF_HOST","hostType":"env","username":"SF_USERNAME","usernameType":"env","password":"SF_PASSWORD","passwordType":"env","token":"","tokenType":"env"}]

Screenshot of describe and repeater

Properties




Extending

While the request to use ES6 classes is currently underway within Node-Red modules, the following is the current structure for the nodes:

All nodes can be found by importing the module:

Note that Node-Red gets access to the setupNodeRed function, using require(...) directly gives access to the es6 class.

Also note, although NodeRed does not support TypeScript, care has been taken to support jsdoc / intellisense - to make extending these modules easier...

Screenshot of intellisense

connection.SfConnectionReceiver

Base Class for many of the other commands.

Note that this provides a couple convenience functions, such as setting the status and base methods for listening to newConnection and connectionLost events.

Simply override the handleNewConnection(JsForceConnection) and handleConnectionLost(JsForceConnection) methods - respectively...

Properties

initialize

Intitialize the node

Returns the instance, to support chaining...

listenToConnection

Starts listening to a single salesforce connection emitter...

Just give it the name of the property on the connection that holds the value, it will figure out the rest.

Returns void

setStatus

Sets the status on the node

Sets the status on the node so it appears connected or disconnected...

Screenshot of connected node

handleNewConnection

Overwrite this method to get notified when a connection is established.

(note that existing connections can be compared and so can also be disconnected, or see handleConnectionLost method below)

handleConnectionLost

Overwrite this method to get notified when the connection is lost.

(This will always get called before handleNewConnection on a connectionEmitter#refresh event)


Subclassing

For example: to access the ConnectionReceiver (for subclassing), use the following:

const connectionEmitter = require('node-red-contrib-salesforce-connection-emitter');
const SfConnectionReceiver = connectionEmitter.connection.SfConnectionReceiver;
//-- or directly through destructuring
const {connection: {SfConnectionReceiver}} = require('node-red-contrib-salesforce-connection-emitter');

class MyClass extends SfConnectionReceiver {...}

one further example - changing the name of the class:

const {connection: {SfConnectionReceiver:ConnectionReceiver}} = require('node-red-contrib-salesforce-connection-emitter');

class MyClass extends ConnectionReceiver {...}

Running Tests

  • To test the project run npm run test or npm run test:watch to continuously test.

Running Linter

  • To run linters on the project, run npm run lint or npm run lint:watch to continously lint.

Further

1.4.3

5 years ago

1.4.2

5 years ago

1.4.1

5 years ago

1.4.0

5 years ago

1.3.3

5 years ago

1.3.2

5 years ago

1.3.1

5 years ago

1.3.0

5 years ago

1.2.2

5 years ago

1.2.1

5 years ago

1.2.0

5 years ago

1.1.0

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago