jsforce2
Version:
Salesforce API Library for JavaScript
271 lines (245 loc) • 7.93 kB
JavaScript
/**
* @file Manages Streaming APIs
* @author Shinichi Tomita <shinichi.tomita@gmail.com>
*/
'use strict';
var events = require('events'),
inherits = require('inherits'),
_ = require('lodash/core'),
Faye = require('faye'),
StreamingExtension = require('./streaming-extension'),
jsforce = require('../core');
/**
* Streaming API topic class
*
* @class Streaming~Topic
* @param {Streaming} steaming - Streaming API object
* @param {String} name - Topic name
*/
var Topic = function(streaming, name) {
this._streaming = streaming;
this.name = name;
};
/**
* @typedef {Object} Streaming~StreamingMessage
* @prop {Object} event
* @prop {Object} event.type - Event type
* @prop {Record} sobject - Record information
*/
/**
* Subscribe listener to topic
*
* @method Streaming~Topic#subscribe
* @param {Callback.<Streaming~StreamingMesasge>} listener - Streaming message listener
* @returns {Subscription} - Faye subscription object
*/
Topic.prototype.subscribe = function(listener) {
return this._streaming.subscribe(this.name, listener);
};
/**
* Unsubscribe listener from topic
*
* @method Streaming~Topic#unsubscribe
* @param {Callback.<Streaming~StreamingMesasge>} listener - Streaming message listener
* @returns {Streaming~Topic}
*/
Topic.prototype.unsubscribe = function(listener) {
this._streaming.unsubscribe(this.name, listener);
return this;
};
/*--------------------------------------------*/
/**
* Streaming API Generic Streaming Channel
*
* @class Streaming~Channel
* @param {Streaming} steaming - Streaming API object
* @param {String} name - Channel name (starts with "/u/")
*/
var Channel = function(streaming, name) {
this._streaming = streaming;
this._name = name;
};
/**
* Subscribe to channel
*
* @param {Callback.<Streaming~StreamingMessage>} listener - Streaming message listener
* @returns {Subscription} - Faye subscription object
*/
Channel.prototype.subscribe = function(listener) {
return this._streaming.subscribe(this._name, listener);
};
Channel.prototype.unsubscribe = function(listener) {
this._streaming.unsubscribe(this._name, listener);
return this;
};
Channel.prototype.push = function(events, callback) {
var isArray = _.isArray(events);
events = isArray ? events : [ events ];
var conn = this._streaming._conn;
if (!this._id) {
this._id = conn.sobject('StreamingChannel').findOne({ Name: this._name }, 'Id')
.then(function(rec) { return rec.Id });
}
return this._id.then(function(id) {
var channelUrl = '/sobjects/StreamingChannel/' + id + '/push';
return conn.requestPost(channelUrl, { pushEvents: events });
}).then(function(rets) {
return isArray ? rets : rets[0];
}).thenCall(callback);
};
/*--------------------------------------------*/
/**
* Streaming API class
*
* @class
* @extends events.EventEmitter
* @param {Connection} conn - Connection object
*/
var Streaming = function(conn) {
this._conn = conn;
};
inherits(Streaming, events.EventEmitter);
/** @private **/
Streaming.prototype._createClient = function(forChannelName, extensions) {
// forChannelName is advisory, for an API workaround. It does not restrict or select the channel.
var needsReplayFix = typeof forChannelName === 'string' && forChannelName.indexOf('/u/') === 0;
var endpointUrl = [
this._conn.instanceUrl,
// special endpoint "/cometd/replay/xx.x" is only available in 36.0.
// See https://releasenotes.docs.salesforce.com/en-us/summer16/release-notes/rn_api_streaming_classic_replay.htm
"cometd" + (needsReplayFix === true && this._conn.version === "36.0" ? "/replay" : ""),
this._conn.version
].join('/');
var fayeClient = new Faye.Client(endpointUrl, {});
fayeClient.setHeader('Authorization', 'OAuth '+this._conn.accessToken);
if (extensions instanceof Array) {
extensions.forEach(function(extension) {
fayeClient.addExtension(extension);
});
}
if (fayeClient._dispatcher.getConnectionTypes().indexOf('callback-polling') === -1) {
// prevent streaming API server error
fayeClient._dispatcher.selectTransport('long-polling');
fayeClient._dispatcher._transport.batching = false;
}
return fayeClient;
};
/** @private **/
Streaming.prototype._getFayeClient = function(channelName) {
var isGeneric = channelName.indexOf('/u/') === 0;
var clientType = isGeneric ? 'generic' : 'pushTopic';
if (!this._fayeClients || !this._fayeClients[clientType]) {
this._fayeClients = this._fayeClients || {};
this._fayeClients[clientType] = this._createClient(channelName);
}
return this._fayeClients[clientType];
};
/**
* Get named topic
*
* @param {String} name - Topic name
* @returns {Streaming~Topic}
*/
Streaming.prototype.topic = function(name) {
this._topics = this._topics || {};
var topic = this._topics[name] =
this._topics[name] || new Topic(this, name);
return topic;
};
/**
* Get Channel for Id
* @param {String} channelId - Id of StreamingChannel object
* @returns {Streaming~Channel}
*/
Streaming.prototype.channel = function(channelId) {
return new Channel(this, channelId);
};
/**
* Subscribe topic/channel
*
* @param {String} name - Topic name
* @param {Callback.<Streaming~StreamingMessage>} listener - Streaming message listener
* @returns {Subscription} - Faye subscription object
*/
Streaming.prototype.subscribe = function(name, listener) {
var channelName = name.indexOf('/') === 0 ? name : '/topic/' + name;
var fayeClient = this._getFayeClient(channelName);
return fayeClient.subscribe(channelName, listener);
};
/**
* Unsubscribe topic
*
* @param {String} name - Topic name
* @param {Callback.<Streaming~StreamingMessage>} listener - Streaming message listener
* @returns {Streaming}
*/
Streaming.prototype.unsubscribe = function(name, listener) {
var channelName = name.indexOf('/') === 0 ? name : '/topic/' + name;
var fayeClient = this._getFayeClient(channelName);
fayeClient.unsubscribe(channelName, listener);
return this;
};
/**
* Create a Streaming client, optionally with extensions
*
* See Faye docs for implementation details: https://faye.jcoglan.com/browser/extensions.html
*
* Example usage:
*
* ```javascript
* // Establish a Salesforce connection. (Details elided)
* const conn = new jsforce.Connection({ … });
*
* const fayeClient = conn.streaming.createClient();
*
* const subscription = fayeClient.subscribe(channel, data => {
* console.log('topic received data', data);
* });
*
* subscription.cancel();
* ```
*
* Example with extensions, using Replay & Auth Failure extensions in a server-side Node.js app:
*
* ```javascript
* // Establish a Salesforce connection. (Details elided)
* const conn = new jsforce.Connection({ … });
*
* const channel = "/event/My_Event__e";
* const replayId = -2; // -2 is all retained events
*
* const exitCallback = () => process.exit(1);
* const authFailureExt = new jsforce.StreamingExtension.AuthFailure(exitCallback);
*
* const replayExt = new jsforce.StreamingExtension.Replay(channel, replayId);
*
* const fayeClient = conn.streaming.createClient([
* authFailureExt,
* replayExt
* ]);
*
* const subscription = fayeClient.subscribe(channel, data => {
* console.log('topic received data', data);
* });
*
* subscription.cancel();
* ```
*
* @param {Array} Extensions - Optional, extensions to apply to the Faye client
* @returns {FayeClient} - Faye client object
*/
Streaming.prototype.createClient = function(extensions) {
return this._createClient(null, extensions);
};
/*--------------------------------------------*/
/*
* Register hook in connection instantiation for dynamically adding this API module features
*/
jsforce.on('connection:new', function(conn) {
conn.streaming = new Streaming(conn);
});
/*
*
*/
jsforce.StreamingExtension = StreamingExtension;
module.exports = Streaming;