Comparing version 0.5.1 to 0.6.0
@@ -6,5 +6,5 @@ var HelpEsb = require('../help-esb'); | ||
client.on('group.asdf', function(data) { | ||
client.on('group.asdf', function(message) { | ||
console.log('Received data:'); | ||
console.log(data); | ||
console.log(message.toJSON()); | ||
}); | ||
@@ -11,0 +11,0 @@ |
@@ -6,4 +6,4 @@ var HelpEsb = require('../help-esb'); | ||
client.rpcReceive('rpc-test', function(data) { | ||
return {greeting: 'Hello ' + data.name}; | ||
client.rpcReceive('rpc-test', function(message) { | ||
return {greeting: 'Hello ' + message.get('name')}; | ||
}); | ||
@@ -10,0 +10,0 @@ |
@@ -5,7 +5,11 @@ var HelpEsb = require('../help-esb'); | ||
client.rpcSend('rpc-test', {name: 'nubs'}) | ||
var util = require('util'); | ||
client.rpcSend('org-settings-set', {routingStrategy: 'helpdotcom', organizationId: '2b9ad7bd-3aba-407c-83ce-9e95581a11bf'}) | ||
//client.rpcSend('chat-transcript-get', {roomId: 'd6e6ca50-e87d-4231-827b-799f7a665ffb', organizationId: '2b9ad7bd-3aba-407c-83ce-9e95581a11bf'}) | ||
.timeout(5000) | ||
.then(function(response) { | ||
.then(function(message) { | ||
console.log('Received response:'); | ||
console.log(response); | ||
console.log(util.inspect(message.toJSON(), {depth: null, colors: true})); | ||
console.log(message.get()); | ||
}).catch(function(error) { | ||
@@ -12,0 +16,0 @@ console.warn('Received error:'); |
367
help-esb.js
@@ -14,5 +14,16 @@ (function(root, factory) { | ||
require('uuid'), | ||
require('lodash') | ||
require('lodash'), | ||
require('object-path') | ||
); | ||
}(this, function(HelpEsb, net, EventEmitter, util, url, Promise, uuid, _) { | ||
}(this, function( | ||
HelpEsb, | ||
net, | ||
EventEmitter, | ||
util, | ||
url, | ||
Promise, | ||
uuid, | ||
_, | ||
objectPath | ||
) { | ||
'use strict'; | ||
@@ -31,4 +42,4 @@ | ||
// client.on('type.error', console.error); | ||
// client.on('group.subscriptionChannel1', function(data) { | ||
// // Process data | ||
// client.on('group.subscriptionChannel1', function(message) { | ||
// // Process message | ||
// }); | ||
@@ -40,4 +51,4 @@ // | ||
// client.login('clientName'); | ||
// client.rpcReceive('subscriptionChannel1', function(data) { | ||
// // Process data | ||
// client.rpcReceive('subscriptionChannel1', function(message) { | ||
// // Process message | ||
// return result; | ||
@@ -56,2 +67,4 @@ // }); | ||
this._options = _.extend({debug: false}, options); | ||
this.mb = new HelpEsb.MessageBuilder(this); | ||
}; | ||
@@ -71,6 +84,4 @@ | ||
return this._authentication = this._rpcSend({ | ||
meta: {type: 'login'}, | ||
data: {name: name, subscriptions: []} | ||
}).timeout(10000); | ||
return this._authentication = this._rpcSend(this.mb.login(name)) | ||
.timeout(10000); | ||
}; | ||
@@ -89,6 +100,3 @@ | ||
this._subscriptions[group] = this._authPromise().then(function() { | ||
return this._rpcSend({ | ||
meta: {type: 'subscribe'}, | ||
data: {channel: group} | ||
}).timeout(10000); | ||
return this._rpcSend(this.mb.subscribe(group)).timeout(10000); | ||
}.bind(this)); | ||
@@ -101,13 +109,25 @@ } | ||
// ### HelpEsb.Client.send | ||
// Sends a payload message to the ESB with the given data. Returns a promise | ||
// that, like the [subscribe](#helpesb-client-subscribe) call, is fulfilled | ||
// when the message is sent, but does not indicate whether the message was | ||
// received by the ESB or by any subscribers. For RPC-esque behavior, use | ||
// [rpcSend](#helpesb-client-rpcsend). | ||
// Sends a payload message to the ESB with the given message. Returns a | ||
// promise that, like the [subscribe](#helpesb-client-subscribe) call, is | ||
// fulfilled when the message is sent, but does not indicate whether the | ||
// message was received by the ESB or by any subscribers. For RPC-esque | ||
// behavior, use [rpcSend](#helpesb-client-rpcsend). | ||
// | ||
// Optionally, you can also pass a second message instance that indicates | ||
// which request message this message is in regards to. This allows for | ||
// following full request cycle reporting with the ESB. We can track the | ||
// requests down through multiple layers using this `inre` flag, and then the | ||
// responses can come back up using `replyTo` and we can reconstruct the | ||
// topography of the calls after the fact. | ||
// | ||
// client.send('target', {id: 1234, message: 'Hello!'}); | ||
HelpEsb.Client.prototype.send = function(group, data, replyCallback) { | ||
HelpEsb.Client.prototype.send = function( | ||
group, | ||
message, | ||
inre, | ||
replyCallback | ||
) { | ||
return this._authPromise().then(function() { | ||
return this._send( | ||
{meta: {type: 'sendMessage', group: group}, data: data}, | ||
this.mb.send(group, this.mb.coerce(message), inre), | ||
replyCallback | ||
@@ -127,11 +147,12 @@ ); | ||
// | ||
// client.rpcSend('foo', {name: 'John'}).then(function(response) { | ||
// console.log(response); | ||
// }).catch(function(error) { | ||
// console.error(error); | ||
// }); | ||
HelpEsb.Client.prototype.rpcSend = function(group, data) { | ||
// client.rpcSend('foo', {name: 'John'}) | ||
// .then(function(response) { | ||
// console.log(response.toJSON()); | ||
// }).catch(function(error) { | ||
// console.error(error); | ||
// }); | ||
HelpEsb.Client.prototype.rpcSend = function(group, message, inre) { | ||
var send = Promise.promisify(HelpEsb.Client.prototype.send).bind(this); | ||
return this.subscribe(group + '-result').then(function() { | ||
return send(group, data).spread(this._checkRpcResult); | ||
return send(group, message, inre).then(this._checkRpcResult); | ||
}.bind(this)); | ||
@@ -147,4 +168,4 @@ }; | ||
// | ||
// client.rpcReceive('foo', function(data) { | ||
// return {greeting: 'Hello ' + (data.name || 'Stranger')}; | ||
// client.rpcReceive('foo', function(message) { | ||
// return {greeting: 'Hello ' + message.get('name', 'Stranger')}; | ||
// }); | ||
@@ -154,3 +175,3 @@ // | ||
// | ||
// client.rpcReceive('foo', function(data) { | ||
// client.rpcReceive('foo', function(message) { | ||
// return request.getAsync('http://www.google.com'); | ||
@@ -162,3 +183,3 @@ // }); | ||
// | ||
// client.rpcReceive('foo', function(data) { | ||
// client.rpcReceive('foo', function(message) { | ||
// throw new Error('Not implemented!'); | ||
@@ -168,4 +189,4 @@ // }); | ||
this.subscribe(group); | ||
this.on('group.' + group, function(data, incomingMeta) { | ||
var meta = {type: 'sendMessage', replyTo: incomingMeta.id}; | ||
this.on('group.' + group, function(message) { | ||
var meta = {type: 'sendMessage', replyTo: message.getMeta('id')}; | ||
@@ -176,27 +197,29 @@ // Link up our reply to the incoming request but on the "result" group. | ||
// CC the groups in the incoming messages CC list. | ||
if (incomingMeta.cc && typeof incomingMeta.cc.group !== 'undefined') { | ||
groups = groups.concat(incomingMeta.cc.group); | ||
if (message.hasMeta('cc.group')) { | ||
groups = groups.concat(message.getMeta('cc.group')); | ||
} | ||
if (typeof incomingMeta.session !== 'undefined') { | ||
meta.session = incomingMeta.session; | ||
if (message.hasMeta('session')) { | ||
meta.session = message.getMeta('session'); | ||
} | ||
var sendToGroup = function(meta, data, group) { | ||
return this._send({meta: _.extend({group: group}, meta), data: data}); | ||
var sendToGroup = function(message, group) { | ||
return this._send(this.mb.send(group, message)); | ||
}.bind(this); | ||
var sendToAll = function(meta, data) { | ||
return Promise.all(groups.map(_.partial(sendToGroup, meta, data))); | ||
var sendToAll = function(message) { | ||
return Promise.all(groups.map(_.partial(sendToGroup, message))); | ||
}; | ||
Promise.try(cb.bind({}, data, incomingMeta)).then(function(data) { | ||
return sendToAll(_.extend({result: 'SUCCESS'}, meta), data); | ||
Promise.try(cb.bind({}, message)).then(function(message) { | ||
return sendToAll( | ||
this.mb.success( | ||
this.mb.extend({meta: meta}, this.mb.coerce(message)) | ||
) | ||
); | ||
}.bind(this)).catch(function(error) { | ||
var reason = error instanceof Error ? error.toString() : error; | ||
var errorMeta = _.extend({reason: reason}, meta); | ||
return sendToAll( | ||
_.extend({result: 'FAILURE', reason: reason}, meta), | ||
data | ||
); | ||
return sendToAll(this.mb.failure({meta: errorMeta})); | ||
}.bind(this)); | ||
@@ -214,2 +237,21 @@ }.bind(this)); | ||
// ### HelpEsb.Client.decorateMessage | ||
// Formats the message with client-specific values needed by the ESB. This | ||
// includes the `from` key in the meta to reference the logged in client | ||
// channel id. | ||
// | ||
// You probably don't need to call this yourself as it is called by the | ||
// `MessageBuilder`. | ||
HelpEsb.Client.prototype.decorateMessage = function(message) { | ||
if ( | ||
this._authentication !== null && | ||
this._authentication.isFulfilled() && | ||
this._authentication.value().has('channelId') | ||
) { | ||
message.meta.from = this._authentication.value().get('channelId'); | ||
} | ||
return message; | ||
}; | ||
// --- | ||
@@ -272,21 +314,22 @@ // ### Private Methods | ||
// Format the packet for the ESB and send it over the socket. JSON encodes | ||
// Format the message for the ESB and send it over the socket. JSON encodes | ||
// the message and appends a newline as the delimiter between messages. | ||
HelpEsb.Client.prototype._send = function(packet, replyCallback) { | ||
packet = this._massageOutboundPacket(packet); | ||
HelpEsb.Client.prototype._send = function(message, replyCallback) { | ||
// Register a callback for replies to this message if a callback is given. | ||
if (replyCallback) { | ||
this.once('replyTo.' + packet.meta.id, _.partial(replyCallback, null)); | ||
this.once( | ||
'replyTo.' + message.getMeta('id'), | ||
_.partial(replyCallback, null) | ||
); | ||
} | ||
return this._sendRaw(JSON.stringify(packet) + '\n'); | ||
return this._sendRaw(JSON.stringify(message) + '\n'); | ||
}; | ||
// Sends the packet like **_send**, but returns a promise for a response from | ||
// some other service. This uses the autogen message id and relies on the | ||
// other service properly publishing a message with a proper replyTo. | ||
HelpEsb.Client.prototype._rpcSend = function(packet) { | ||
// Sends the message like **_send**, but returns a promise for a response | ||
// from some other service. This uses the autogen message id and relies on | ||
// the other service properly publishing a message with a proper replyTo. | ||
HelpEsb.Client.prototype._rpcSend = function(message) { | ||
var send = Promise.promisify(HelpEsb.Client.prototype._send).bind(this); | ||
return send(packet).spread(this._checkRpcResult); | ||
return send(message).then(this._checkRpcResult); | ||
}; | ||
@@ -296,19 +339,19 @@ | ||
// successful. | ||
HelpEsb.Client.prototype._checkRpcResult = function(data, meta) { | ||
if (meta.result !== 'SUCCESS') { | ||
return Promise.reject(meta.reason); | ||
HelpEsb.Client.prototype._checkRpcResult = function(message) { | ||
if (message.getMeta('result') !== 'SUCCESS') { | ||
return Promise.reject(message.getMeta('reason')); | ||
} | ||
return Promise.resolve(data); | ||
return Promise.resolve(message); | ||
}; | ||
// Wait on the socket connection and once it is avaialable send the given | ||
// string data returning a promise of the data being sent. | ||
HelpEsb.Client.prototype._sendRaw = function(data) { | ||
// string packet returning a promise of the packet being sent. | ||
HelpEsb.Client.prototype._sendRaw = function(packet) { | ||
if (this._options.debug) { | ||
console.log('help-esb SENDING', data); | ||
console.log('help-esb SENDING', packet); | ||
} | ||
return this._socketConnection.then(function() { | ||
return this._socket.writeAsync(data); | ||
return this._socket.writeAsync(packet); | ||
}.bind(this)); | ||
@@ -336,5 +379,5 @@ }; | ||
// Handles a single packet of data. The data is expected to be JSON, and if | ||
// it isn't, a `type.error` event will be emitted. Otherwise, an event for | ||
// each of the meta fields (e.g., `type.error`, `group.someGroup`, | ||
// Handles a single packet of data. The packet is expected to be JSON, and | ||
// if it isn't, a `type.error` event will be emitted. Otherwise, an event | ||
// for each of the meta fields (e.g., `type.error`, `group.someGroup`, | ||
// `replyTo.SOME_ID`) will be emitted. | ||
@@ -353,4 +396,9 @@ // | ||
var message; | ||
try { | ||
packet = JSON.parse(packet); | ||
message = new HelpEsb.Message(JSON.parse(packet)); | ||
if (!message.hasMeta('type')) { | ||
throw new Error('Invalid format detected for packet'); | ||
} | ||
} catch (e) { | ||
@@ -361,12 +409,3 @@ this.emit('type.error', e); | ||
if ( | ||
typeof packet.meta !== 'object' || | ||
typeof packet.meta.type !== 'string' || | ||
typeof packet.data === 'undefined' | ||
) { | ||
this.emit('type.error', 'Invalid format detected for packet', packet); | ||
return; | ||
} | ||
// Emits key.value events with the data and meta. If the value is an | ||
// Emits key.value events with the message. If the value is an | ||
// array, it iterates over the array and emits events on each value in the | ||
@@ -381,27 +420,11 @@ // array. Returns true if any of the events were handled. | ||
return this.emit(key + '.' + value, packet.data, packet.meta); | ||
return this.emit(key + '.' + value, message); | ||
}.bind(this); | ||
this.emit('*', packet.data, packet.meta); | ||
if (!_.any(_.map(packet.meta, emitKeyValue))) { | ||
this.emit('*.unhandled', packet.data, packet.meta); | ||
this.emit('*', message); | ||
if (!_.any(_.map(message.getMeta(), emitKeyValue))) { | ||
this.emit('*.unhandled', message); | ||
} | ||
}; | ||
// Process the packet to ensure it conforms to the ESB requirements. Sets | ||
// the message id in the metadata for the packet if it wasn't already set. | ||
HelpEsb.Client.prototype._massageOutboundPacket = function(packet) { | ||
packet.meta.id = packet.meta.id || uuid.v4(); | ||
if ( | ||
this._authentication !== null && | ||
this._authentication.isFulfilled() && | ||
typeof this._authentication.value().channelId !== 'undefined' | ||
) { | ||
packet.meta.from = this._authentication.value().channelId; | ||
} | ||
return packet; | ||
}; | ||
// This will return a failed promise if authentication hasn't been attempted | ||
@@ -414,3 +437,145 @@ // yet. | ||
// ## HelpEsb.MessageBuilder | ||
// The `MessageBuilder` is a helper object that can build a `HelpEsb.Message` | ||
// according to standard message types. | ||
// ### HelpEsb.MessageBuilder *constructor* | ||
// Initializes the object with access to the `HelpEsb.Client` instance used | ||
// to decorate messages further. | ||
HelpEsb.MessageBuilder = function(client) { | ||
this._client = client; | ||
}; | ||
// ### HelpEsb.MessageBuilder.login | ||
// Creates a standard login message for the given client name. | ||
HelpEsb.MessageBuilder.prototype.login = function(name) { | ||
return new HelpEsb.Message({ | ||
meta: {type: 'login'}, | ||
data: {name: name, subscriptions: []} | ||
}); | ||
}; | ||
// ### HelpEsb.MessageBuilder.subscribe | ||
// Creates a standard subscribe message for the given group name. | ||
HelpEsb.MessageBuilder.prototype.subscribe = function(group) { | ||
return this.create({meta: {type: 'subscribe'}, data: {channel: group}}); | ||
}; | ||
// ### HelpEsb.MessageBuilder.send | ||
// Creates a standard `sendMessage` message, extending off of the given | ||
// message. | ||
HelpEsb.MessageBuilder.prototype.send = function(group, message, inre) { | ||
return this.extend( | ||
{ | ||
meta: { | ||
type: 'sendMessage', | ||
group: group, | ||
inre: inre && inre.getMeta('id') | ||
} | ||
}, | ||
message | ||
); | ||
}; | ||
// ### HelpEsb.MessageBuilder.success | ||
// Creates a standard success message which has a result status, extending | ||
// off of the given message. | ||
HelpEsb.MessageBuilder.prototype.success = function(message) { | ||
return this.extend({meta: {result: 'SUCCESS'}}, message); | ||
}; | ||
// ### HelpEsb.MessageBuilder.failure | ||
// Creates a standard failure message which has a result status, extending | ||
// off of the given message. | ||
HelpEsb.MessageBuilder.prototype.failure = function(message) { | ||
return this.extend({meta: {result: 'FAILURE'}}, message); | ||
}; | ||
// ### HelpEsb.MessageBuilder.create | ||
// Creates a `HelpEsb.Message` object that has been decorated by the client. | ||
HelpEsb.MessageBuilder.prototype.create = function(message) { | ||
return new HelpEsb.Message(this._client.decorateMessage(message)); | ||
}; | ||
// ### HelpEsb.MessageBuilder.build | ||
// Creates a `HelpEsb.Message` object that has been decorated by the client | ||
// from its constituent data and meta parameters. | ||
HelpEsb.MessageBuilder.prototype.build = function(data, meta) { | ||
return this.create({meta: meta || {}, data: data || {}}); | ||
}; | ||
// ### HelpEsb.MessageBuilder.coerce | ||
// Coerces the passed argument into a message, returning it as is if it is a | ||
// `HelpEsb.Message` object, or building it from its `data` (and optional | ||
// `meta`) otherwise. | ||
HelpEsb.MessageBuilder.prototype.coerce = function(data, meta) { | ||
return data instanceof HelpEsb.Message ? data : this.build(data, meta); | ||
}; | ||
// ### HelpEsb.MessageBuilder.extend | ||
// Extends a message (`Message` object or POJO) with other message(s). | ||
// Returns a new message that is the combined data/meta parts from all of the | ||
// passed message arguments. | ||
HelpEsb.MessageBuilder.prototype.extend = function(/* object, extension */) { | ||
var params = _.map(arguments, function(arg) { | ||
return _.clone(arg instanceof HelpEsb.Message ? arg.toJSON() : arg); | ||
}); | ||
return this.build( | ||
_.extend.apply({}, [{}].concat(_.pluck(params, 'data'))), | ||
_.extend.apply({}, [{}].concat(_.pluck(params, 'meta'))) | ||
); | ||
}; | ||
// ## HelpEsb.Message | ||
// A data object representing an ESB message. Also provides some convenience | ||
// methods. | ||
// ### HelpEsb.Message *constructor* | ||
// Initiates the message based on the given message object. Initializes the | ||
// meta and data fields appropriately, including adding a message id if one | ||
// does not exist. | ||
HelpEsb.Message = function(message) { | ||
this._data = _.has(message, 'data') ? message.data : {}; | ||
this._meta = _.extend({id: uuid.v4()}, message.meta); | ||
}; | ||
// ### HelpEsb.Message.get | ||
// Get the data property with the given dot-delimited path. For example, | ||
// | ||
// message = new HelpEsb.Message({foo: {bar: 'baz'}}); | ||
// message.get('foo.bar') === 'baz'; | ||
// | ||
// You can also provide a default value to return instead of `undefined` for | ||
// values that don't exist. | ||
HelpEsb.Message.prototype.get = function(path, def) { | ||
return objectPath.get(this._data, path, def); | ||
}; | ||
// ### HelpEsb.Message.getMeta | ||
// Like [get](#helpesb-message-get), but for the meta fields. | ||
HelpEsb.Message.prototype.getMeta = function(path, def) { | ||
return objectPath.get(this._meta, path, def); | ||
}; | ||
// ### HelpEsb.Message.has | ||
// Checks for the existence of the data proper with the given dot-delimited | ||
// path. | ||
HelpEsb.Message.prototype.has = function(path) { | ||
return objectPath.has(this._data, path); | ||
}; | ||
// ### HelpEsb.Message.hasMeta | ||
// Like [has](#helpesb-message-has), but for the meta fields. | ||
HelpEsb.Message.prototype.hasMeta = function(path) { | ||
return objectPath.has(this._meta, path); | ||
}; | ||
// ### HelpEsb.Message.toJSON | ||
// Converts the message into its canonical form for JSON serialization. | ||
HelpEsb.Message.prototype.toJSON = function() { | ||
return {meta: this._meta, data: this._data}; | ||
}; | ||
return HelpEsb; | ||
})); |
{ | ||
"name": "help-esb", | ||
"version": "0.5.1", | ||
"version": "0.6.0", | ||
"description": "A client for the Help.com team's ESB.", | ||
@@ -13,4 +13,5 @@ "main": "help-esb.js", | ||
"dependencies": { | ||
"bluebird": "~2.3", | ||
"bluebird": "~2.9", | ||
"lodash": "~2.4", | ||
"object-path": "~0.9.0", | ||
"uuid": "~2.0" | ||
@@ -17,0 +18,0 @@ }, |
29722
538
4
+ Addedobject-path@~0.9.0
+ Addedbluebird@2.9.34(transitive)
+ Addedobject-path@0.9.2(transitive)
- Removedbluebird@2.3.11(transitive)
Updatedbluebird@~2.9