diff --git a/build/NodeExports.js b/build/NodeExports.js deleted file mode 100644 index 8eedd9f..0000000 --- a/build/NodeExports.js +++ /dev/null @@ -1,2 +0,0 @@ - -exports.Postal = MessageBroker; \ No newline at end of file diff --git a/build/SourceManifest.txt b/build/SourceManifest-browser.txt similarity index 50% rename from build/SourceManifest.txt rename to build/SourceManifest-browser.txt index 3c7d47d..ea0b356 100644 --- a/build/SourceManifest.txt +++ b/build/SourceManifest-browser.txt @@ -1,5 +1,4 @@ -src/languageExtensions.js -src/CapturedMessageBatch.js +src/misc.js src/MessageCaptor.js src/ReplayContext.js src/Postal.js diff --git a/build/SourceManifest-node.txt b/build/SourceManifest-node.txt new file mode 100644 index 0000000..ea0b356 --- /dev/null +++ b/build/SourceManifest-node.txt @@ -0,0 +1,4 @@ +src/misc.js +src/MessageCaptor.js +src/ReplayContext.js +src/Postal.js diff --git a/build/boilerplate/browser_footer.txt b/build/boilerplate/browser_footer.txt new file mode 100644 index 0000000..7a086ad --- /dev/null +++ b/build/boilerplate/browser_footer.txt @@ -0,0 +1,11 @@ + +var postal = global.postal = new Postal(); + +postal.DEFAULT_EXCHANGE = DEFAULT_EXCHANGE; +postal.SYSTEM_EXCHANGE = SYSTEM_EXCHANGE; +postal.NORMAL_MODE = NORMAL_MODE; +postal.CAPTURE_MODE = CAPTURE_MODE; +postal.REPLAY_MODE = REPLAY_MODE; +postal.POSTAL_MSG_STORE_KEY = POSTAL_MSG_STORE_KEY; + +})(window); \ No newline at end of file diff --git a/build/boilerplate/browser_header.txt b/build/boilerplate/browser_header.txt new file mode 100644 index 0000000..da1b5f5 --- /dev/null +++ b/build/boilerplate/browser_header.txt @@ -0,0 +1,2 @@ +(function(global, undefined) { + diff --git a/build/boilerplate/node_footer.txt b/build/boilerplate/node_footer.txt new file mode 100644 index 0000000..2a7f149 --- /dev/null +++ b/build/boilerplate/node_footer.txt @@ -0,0 +1,11 @@ + +var postal = new Postal(); + +postal.DEFAULT_EXCHANGE = DEFAULT_EXCHANGE; +postal.SYSTEM_EXCHANGE = SYSTEM_EXCHANGE; +postal.NORMAL_MODE = NORMAL_MODE; +postal.CAPTURE_MODE = CAPTURE_MODE; +postal.REPLAY_MODE = REPLAY_MODE; +postal.POSTAL_MSG_STORE_KEY = POSTAL_MSG_STORE_KEY; + +exports.postal = postal; \ No newline at end of file diff --git a/build/build-all.sh b/build/build-all.sh new file mode 100755 index 0000000..efe70f0 --- /dev/null +++ b/build/build-all.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +./linux-build-node.sh +./linux-build-browser.sh \ No newline at end of file diff --git a/build/linux-build-browser.sh b/build/linux-build-browser.sh new file mode 100755 index 0000000..bc162c2 --- /dev/null +++ b/build/linux-build-browser.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +OutFile='output/browser/postal.js' + +cp version-header.js $OutFile + +cat ./boilerplate/browser_header.txt >> $OutFile + +# Combine the source files +while read line; do + cat ../$line >> $OutFile +done < SourceManifest-browser.txt + +cat ./boilerplate/browser_footer.txt >> $OutFile \ No newline at end of file diff --git a/build/linux-build-node.sh b/build/linux-build-node.sh index 765758c..5bc6b47 100755 --- a/build/linux-build-node.sh +++ b/build/linux-build-node.sh @@ -7,6 +7,6 @@ cp version-header.js $OutFile # Combine the source files while read line; do cat ../$line >> $OutFile -done < SourceManifest.txt +done < SourceManifest-node.txt -cat NodeExports.js >> $OutFile \ No newline at end of file +cat ./boilerplate/node_footer.txt >> $OutFile \ No newline at end of file diff --git a/build/output/browser/postal.js b/build/output/browser/postal.js new file mode 100644 index 0000000..1403570 --- /dev/null +++ b/build/output/browser/postal.js @@ -0,0 +1,375 @@ +/* + postal.js + Author: Jim Cowart + License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license) + Version 0.0.1 +*/ + +(function(global, undefined) { + +var isArray = function(value) { + var s = typeof value; + if (s === 'object') { + if (value) { + if (typeof value.length === 'number' && + !(value.propertyIsEnumerable('length')) && + typeof value.splice === 'function') { + s = 'array'; + } + } + } + return s === 'array'; + }, + slice = [].slice, + DEFAULT_EXCHANGE = "/", + SYSTEM_EXCHANGE = "postal", + NORMAL_MODE = "Normal", + CAPTURE_MODE = "Capture", + REPLAY_MODE = "Replay", + POSTAL_MSG_STORE_KEY = "postal.captured", + _forEachKeyValue = function(object, callback) { + for(var x in object) { + if(object.hasOwnProperty(x)) { + callback(x, object[x]); + } + } + }; + +var MessageCaptor = function(plugUp, unPlug) { + var _grabMsg = function(data) { + // We need to ignore system messages, since they could involve captures, replays, etc. + if(data.exchange !== SYSTEM_EXCHANGE) { + this.messages.push(data); + } + }.bind(this); + + plugUp(_grabMsg); + + this.messages = []; + + this.save = function(batchId, description) { + unPlug(_grabMsg); + var captureStore = amplify.store(POSTAL_MSG_STORE_KEY); + if(!captureStore) { + captureStore = {}; + } + captureStore[batchId] = { + batchId: batchId, + description: description, + messages: this.messages + }; + amplify.store(POSTAL_MSG_STORE_KEY, captureStore); + }; + + postal.subscribe(SYSTEM_EXCHANGE, "captor.save", function(data) { + this.save(data.batchId || new Date().toString(), + data.description || "Captured Message Batch"); + }.bind(this)); +}; + +var ReplayContext = function (publish, subscribe) { + var _batch, + _continue = true, + _loadMessages = function(batchId) { + var msgStore = amplify.store(POSTAL_MSG_STORE_KEY), + targetBatch = msgStore[batchId]; + if(targetBatch) { + targetBatch.messages.forEach(function(msg) { + msg.timeStamp = new Date(msg.timeStamp); + }); + _batch = targetBatch; + } + }, + _replayImmediate = function() { + while(_batch.messages.length > 0) { + if(_continue) { + _advanceNext(); + } + else { + break; + } + } + }, + _advanceNext = function() { + var msg = _batch.messages.shift(); + publish(msg.exchange, msg.topic, msg.data); + }, + _replayRealTime = function() { + if(_continue && _batch.messages.length > 0) { + if(_batch.messages.length > 1) { + var span = _batch.messages[1].timeStamp - _batch.messages[0].timeStamp; + _advanceNext(); + setTimeout(_replayRealTime, span); + } + else { + _advanceNext(); + } + } + }; + + postal.subscribe(SYSTEM_EXCHANGE, "replay.load", function(data) { + _continue = false; + _loadMessages(data); + }); + + postal.subscribe(SYSTEM_EXCHANGE, "replay.immediate", function() { + _continue = true; + _replayImmediate(); + }); + + postal.subscribe(SYSTEM_EXCHANGE, "replay.advanceNext", function() { + _continue = true; + _advanceNext(); + }); + + postal.subscribe(SYSTEM_EXCHANGE, "replay.realTime", function() { + _continue = true; + _replayRealTime(); + }); + + postal.subscribe(SYSTEM_EXCHANGE, "replay.stop", function() { + _continue = false; + }); +}; + +var Postal = function() { + var _regexify = function(topic) { + if(!this.cache[topic]) { + this.cache[topic] = topic.replace(".", "\.").replace("*", ".*"); + } + return this.cache[topic]; + }.bind(this), + _isTopicMatch = function(topic, comparison) { + if(!this.cache[topic + '_' + comparison]) { + this.cache[topic + '_' + comparison] = topic === comparison || + (comparison.indexOf("*") !== -1 && topic.search(_regexify(comparison)) !== -1) || + (topic.indexOf("*") !== -1 && comparison.search(_regexify(topic)) !== -1); + } + return this.cache[topic + '_' + comparison]; + }.bind(this), + _publish = function(exchange, topic, data) { + this.wireTaps.forEach(function(tap) { + tap({ + exchange: exchange, + topic: topic, + data: data, + timeStamp: new Date() + }); + }); + + _forEachKeyValue(this.subscriptions[exchange],function(subTpc, subs) { + if(_isTopicMatch(topic, subTpc)) { + subs.forEach(function(sub) { + if(typeof sub.callback === 'function') { + sub.callback(data); + sub.onFired(); + } + }); + } + }); + }.bind(this), + _mode = NORMAL_MODE, + _replayContext, + _captor; + + this.cache = {}; + + this.getMode = function() { return _mode; }; + + this.wireTaps = []; + + this.subscriptions = {}; + + this.subscriptions[DEFAULT_EXCHANGE] = {}; + + /* + options object has the following optional members: + { + once: {true || false (true indicates a fire-only-once subscription}, + priority: {integer value - lower value == higher priority}, + context: {the "this" context for the callback invocation} + } + */ + this.subscribe = function(exchange, topic, callback, options) { + var _args = slice.call(arguments, 0), + _exchange, + _topicList, // we allow multiple topics to be subscribed in one call., + _once = false, + _subData = { + callback: function() { /* placeholder noop */ }, + priority: 50, + context: null, + onFired: function() { /* noop */ } + }, + _idx, + _found; + + if(_args.length === 2) { // expecting topic and callback + _exchange = DEFAULT_EXCHANGE; + _topicList = _args[0].split(/\s/); + _subData.callback = _args[1]; + } + else if(_args.length === 3 && typeof _args[2] === 'function') { // expecting exchange, topic, callback + _exchange = exchange; + _topicList = _args[1].split(/\s/); + _subData.callback = _args[2]; + } + else if(_args.length === 3 && typeof _args[2] === 'object') { // expecting topic, callback and options + _exchange = DEFAULT_EXCHANGE; + _topicList = _args[0].split(/\s/); + _subData.callback = _args[1]; + _subData.priority = _args[2].priority ? _args[2].priority : 50; + _subData.context = _args[2].context ? _args[2].context : null; + _once = _args[2].once ? _args[2].once : false; + } + else { + _exchange = exchange; + _topicList = topic.split(/\s/); + _subData.callback = callback; + _subData.priority = options.priority ? options.priority : 50; + _subData.context = options.context ? options.context : null; + _once = options.once ? options.once : false; + } + + if(_once) { + _subData.onFired = function() { + this.unsubscribe.apply(this,[_exchange, _topicList.join(' '), _subData.callback]); + }.bind(this); + } + + if(!this.subscriptions[_exchange]) { + this.subscriptions[_exchange] = {}; + } + + _topicList.forEach(function(tpc) { + if(!this.subscriptions[_exchange][tpc]) { + this.subscriptions[_exchange][tpc] = [_subData]; + } + else { + _idx = this.subscriptions[_exchange][tpc].length - 1; + if(this.subscriptions[_exchange][tpc].filter(function(sub) { return sub === callback; }).length === 0) { + for(; _idx >= 0; _idx--) { + if(this.subscriptions[_exchange][tpc][_idx].priority <= _subData.priority) { + this.subscriptions[_exchange][tpc].splice(_idx + 1, 0, _subData); + _found = true; + break; + } + } + if(!_found) { + this.subscriptions[_exchange][tpc].unshift(_subData); + } + } + } + }, this); + + // return callback for un-subscribing... + return function() { + this.unsubscribe(_exchange, _topicList.join(' '), _subData.callback); + }.bind(this); + }; + + this.unsubscribe = function(exchange, topic, callback) { + var _args = slice.call(arguments,0), + _exchange, + _topicList, // we allow multiple topics to be unsubscribed in one call. + _callback; + + if(_args.length === 2) { + _exchange = DEFAULT_EXCHANGE; + _topicList = _args[0].split(/\s/); + _callback = _args[1]; + } + else if(_args.length === 3) { + _exchange = exchange; + _topicList = topic.split(/\s/); + _callback = callback; + } + + _topicList.forEach(function(tpc) { + if(this.subscriptions[_exchange][tpc]) { + var _len = this.subscriptions[_exchange][tpc].length, + _idx = 0; + for ( ; _idx < _len; _idx++ ) { + if (this.subscriptions[_exchange][tpc][_idx].callback === callback) { + this.subscriptions[_exchange][tpc].splice( _idx, 1 ); + break; + } + } + } + },this); + }; + + this.publish = function(exchange, topic, data) { + var _args = slice.call(arguments,0), + _exchange, + _topicList, + _data; + if(_args.length === 1) { + _exchange = DEFAULT_EXCHANGE; + _topicList = _args[0].split(/\s/); + _data = {}; + } + else if(_args.length === 2 && typeof _args[1] === 'object') { + _exchange = DEFAULT_EXCHANGE; + _topicList = _args[0].split(/\s/); + _data = _args[1] || {}; + } + else if(_args.length === 2) { + _exchange = _args[0]; + _topicList = _args[1].split(/\s/); + _data = {}; + } + else { + _exchange = exchange; + _topicList = topic.split(/\s/); + _data = data || {}; + } + if(_mode !== REPLAY_MODE || (_mode === REPLAY_MODE && _exchange === SYSTEM_EXCHANGE)) { + + _topicList.forEach(function(tpc){ + _publish(_exchange, tpc, _data); + }); + } + }; + + this.subscribe(SYSTEM_EXCHANGE, "mode.set", function(data) { + if(data.mode) { + switch(data.mode) { + case REPLAY_MODE: + _mode = REPLAY_MODE; + _replayContext = new ReplayContext(_publish.bind(this), this.subscribe.bind(this)); + _captor = undefined; + break; + case CAPTURE_MODE: + _mode = CAPTURE_MODE; + _captor = new MessageCaptor(function(callback){ + this.wireTaps.push(callback); + }.bind(this), + function(callback) { + var idx = this.wireTaps.indexOf(callback); + if(idx !== -1) { + this.wireTaps.splice(idx,1); + } + }.bind(this)); + break; + default: + _mode = NORMAL_MODE; + _replayContext = undefined; + _captor = undefined; + break; + } + } + }.bind(this)); +}; + + +var postal = global.postal = new Postal(); + +postal.DEFAULT_EXCHANGE = DEFAULT_EXCHANGE; +postal.SYSTEM_EXCHANGE = SYSTEM_EXCHANGE; +postal.NORMAL_MODE = NORMAL_MODE; +postal.CAPTURE_MODE = CAPTURE_MODE; +postal.REPLAY_MODE = REPLAY_MODE; +postal.POSTAL_MSG_STORE_KEY = POSTAL_MSG_STORE_KEY; + +})(window); \ No newline at end of file diff --git a/build/output/nodejs/postal.js b/build/output/nodejs/postal.js index bc35c0b..c2ec960 100644 --- a/build/output/nodejs/postal.js +++ b/build/output/nodejs/postal.js @@ -1,111 +1,373 @@ -// Postal.js -// Author: Jim Cowart -// License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license) -// Version 0.0.1 +/* + postal.js + Author: Jim Cowart + License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license) + Version 0.0.1 +*/ -if(!Object.prototype.forEach) { - Object.prototype.forEach = function (callback) { - var self = this; - for(var x in self) { - if(self.hasOwnProperty(x)) { - callback(self[x]); - } - } - }; -}; - -if(!Object.prototype.forEachKeyValue) { - Object.prototype.forEachKeyValue = function (callback) { - var self = this; - for(var x in self) { - if(self.hasOwnProperty(x)) { - callback(x, self[x]); - } - } - }; -}; - -function isArray(value) { - var s = typeof value; - if (s === 'object') { - if (value) { - if (typeof value.length === 'number' && - !(value.propertyIsEnumerable('length')) && - typeof value.splice === 'function') { - s = 'array'; - } - } - } - return s === 'array'; -} - -var slice = [].slice;var MessageBroker = function() { - var subscriptions = {}, - regexify = function(topic) { - if(!this[topic]) { - this[topic] = topic.replace(".", "\.").replace("*", ".*"); - } - return this[topic]; - }.bind(this), - isTopicMatch = function(topic, comparison) { - if(!this[topic + '_' + comparison]) { - this[topic + '_' + comparison] = topic === comparison || - (comparison.indexOf("*") !== -1 && topic.search(regexify(comparison)) !== -1) || - (topic.indexOf("*") !== -1 && comparison.search(regexify(topic)) !== -1); - } - return this[topic + '_' + comparison]; - }.bind(this); - - this.subscribe = function(topic, callback) { - var topicList = topic.split(/\s/), // we allow multiple topics to be subscribed in one call. - subIdx = 0, - exists; - topicList.forEach(function(topic) { - exists = false; - if(!subscriptions[topic]) { - subscriptions[topic] = [callback]; - } - else { - subscriptions[topic].forEach(function(sub) { - if(subscriptions[topic][subIdx] === callback) { - exists = true; - } - }); - if(!exists) { - subscriptions[topic].push(callback); +var isArray = function(value) { + var s = typeof value; + if (s === 'object') { + if (value) { + if (typeof value.length === 'number' && + !(value.propertyIsEnumerable('length')) && + typeof value.splice === 'function') { + s = 'array'; } } - }); + } + return s === 'array'; + }, + slice = [].slice, + DEFAULT_EXCHANGE = "/", + SYSTEM_EXCHANGE = "postal", + NORMAL_MODE = "Normal", + CAPTURE_MODE = "Capture", + REPLAY_MODE = "Replay", + POSTAL_MSG_STORE_KEY = "postal.captured", + _forEachKeyValue = function(object, callback) { + for(var x in object) { + if(object.hasOwnProperty(x)) { + callback(x, object[x]); + } + } + }; + +var MessageCaptor = function(plugUp, unPlug) { + var _grabMsg = function(data) { + // We need to ignore system messages, since they could involve captures, replays, etc. + if(data.exchange !== SYSTEM_EXCHANGE) { + this.messages.push(data); + } + }.bind(this); + + plugUp(_grabMsg); + + this.messages = []; + + this.save = function(batchId, description) { + unPlug(_grabMsg); + var captureStore = amplify.store(POSTAL_MSG_STORE_KEY); + if(!captureStore) { + captureStore = {}; + } + captureStore[batchId] = { + batchId: batchId, + description: description, + messages: this.messages + }; + amplify.store(POSTAL_MSG_STORE_KEY, captureStore); + }; + + postal.subscribe(SYSTEM_EXCHANGE, "captor.save", function(data) { + this.save(data.batchId || new Date().toString(), + data.description || "Captured Message Batch"); + }.bind(this)); +}; + +var ReplayContext = function (publish, subscribe) { + var _batch, + _continue = true, + _loadMessages = function(batchId) { + var msgStore = amplify.store(POSTAL_MSG_STORE_KEY), + targetBatch = msgStore[batchId]; + if(targetBatch) { + targetBatch.messages.forEach(function(msg) { + msg.timeStamp = new Date(msg.timeStamp); + }); + _batch = targetBatch; + } + }, + _replayImmediate = function() { + while(_batch.messages.length > 0) { + if(_continue) { + _advanceNext(); + } + else { + break; + } + } + }, + _advanceNext = function() { + var msg = _batch.messages.shift(); + publish(msg.exchange, msg.topic, msg.data); + }, + _replayRealTime = function() { + if(_continue && _batch.messages.length > 0) { + if(_batch.messages.length > 1) { + var span = _batch.messages[1].timeStamp - _batch.messages[0].timeStamp; + _advanceNext(); + setTimeout(_replayRealTime, span); + } + else { + _advanceNext(); + } + } + }; + + postal.subscribe(SYSTEM_EXCHANGE, "replay.load", function(data) { + _continue = false; + _loadMessages(data); + }); + + postal.subscribe(SYSTEM_EXCHANGE, "replay.immediate", function() { + _continue = true; + _replayImmediate(); + }); + + postal.subscribe(SYSTEM_EXCHANGE, "replay.advanceNext", function() { + _continue = true; + _advanceNext(); + }); + + postal.subscribe(SYSTEM_EXCHANGE, "replay.realTime", function() { + _continue = true; + _replayRealTime(); + }); + + postal.subscribe(SYSTEM_EXCHANGE, "replay.stop", function() { + _continue = false; + }); +}; + +var Postal = function() { + var _regexify = function(topic) { + if(!this.cache[topic]) { + this.cache[topic] = topic.replace(".", "\.").replace("*", ".*"); + } + return this.cache[topic]; + }.bind(this), + _isTopicMatch = function(topic, comparison) { + if(!this.cache[topic + '_' + comparison]) { + this.cache[topic + '_' + comparison] = topic === comparison || + (comparison.indexOf("*") !== -1 && topic.search(_regexify(comparison)) !== -1) || + (topic.indexOf("*") !== -1 && comparison.search(_regexify(topic)) !== -1); + } + return this.cache[topic + '_' + comparison]; + }.bind(this), + _publish = function(exchange, topic, data) { + this.wireTaps.forEach(function(tap) { + tap({ + exchange: exchange, + topic: topic, + data: data, + timeStamp: new Date() + }); + }); + + _forEachKeyValue(this.subscriptions[exchange],function(subTpc, subs) { + if(_isTopicMatch(topic, subTpc)) { + subs.forEach(function(sub) { + if(typeof sub.callback === 'function') { + sub.callback(data); + sub.onFired(); + } + }); + } + }); + }.bind(this), + _mode = NORMAL_MODE, + _replayContext, + _captor; + + this.cache = {}; + + this.getMode = function() { return _mode; }; + + this.wireTaps = []; + + this.subscriptions = {}; + + this.subscriptions[DEFAULT_EXCHANGE] = {}; + + /* + options object has the following optional members: + { + once: {true || false (true indicates a fire-only-once subscription}, + priority: {integer value - lower value == higher priority}, + context: {the "this" context for the callback invocation} + } + */ + this.subscribe = function(exchange, topic, callback, options) { + var _args = slice.call(arguments, 0), + _exchange, + _topicList, // we allow multiple topics to be subscribed in one call., + _once = false, + _subData = { + callback: function() { /* placeholder noop */ }, + priority: 50, + context: null, + onFired: function() { /* noop */ } + }, + _idx, + _found; + + if(_args.length === 2) { // expecting topic and callback + _exchange = DEFAULT_EXCHANGE; + _topicList = _args[0].split(/\s/); + _subData.callback = _args[1]; + } + else if(_args.length === 3 && typeof _args[2] === 'function') { // expecting exchange, topic, callback + _exchange = exchange; + _topicList = _args[1].split(/\s/); + _subData.callback = _args[2]; + } + else if(_args.length === 3 && typeof _args[2] === 'object') { // expecting topic, callback and options + _exchange = DEFAULT_EXCHANGE; + _topicList = _args[0].split(/\s/); + _subData.callback = _args[1]; + _subData.priority = _args[2].priority ? _args[2].priority : 50; + _subData.context = _args[2].context ? _args[2].context : null; + _once = _args[2].once ? _args[2].once : false; + } + else { + _exchange = exchange; + _topicList = topic.split(/\s/); + _subData.callback = callback; + _subData.priority = options.priority ? options.priority : 50; + _subData.context = options.context ? options.context : null; + _once = options.once ? options.once : false; + } + + if(_once) { + _subData.onFired = function() { + this.unsubscribe.apply(this,[_exchange, _topicList.join(' '), _subData.callback]); + }.bind(this); + } + + if(!this.subscriptions[_exchange]) { + this.subscriptions[_exchange] = {}; + } + + _topicList.forEach(function(tpc) { + if(!this.subscriptions[_exchange][tpc]) { + this.subscriptions[_exchange][tpc] = [_subData]; + } + else { + _idx = this.subscriptions[_exchange][tpc].length - 1; + if(this.subscriptions[_exchange][tpc].filter(function(sub) { return sub === callback; }).length === 0) { + for(; _idx >= 0; _idx--) { + if(this.subscriptions[_exchange][tpc][_idx].priority <= _subData.priority) { + this.subscriptions[_exchange][tpc].splice(_idx + 1, 0, _subData); + _found = true; + break; + } + } + if(!_found) { + this.subscriptions[_exchange][tpc].unshift(_subData); + } + } + } + }, this); + // return callback for un-subscribing... return function() { - this.unsubscribe(topic, callback); + this.unsubscribe(_exchange, _topicList.join(' '), _subData.callback); }.bind(this); }; - this.publish = function(topic, data) { - subscriptions.forEachKeyValue(function(subNm, subs) { - if(isTopicMatch(topic, subNm)) { - subs.forEach(function(callback) { - if(typeof callback === 'function') { - callback(data); + this.unsubscribe = function(exchange, topic, callback) { + var _args = slice.call(arguments,0), + _exchange, + _topicList, // we allow multiple topics to be unsubscribed in one call. + _callback; + + if(_args.length === 2) { + _exchange = DEFAULT_EXCHANGE; + _topicList = _args[0].split(/\s/); + _callback = _args[1]; + } + else if(_args.length === 3) { + _exchange = exchange; + _topicList = topic.split(/\s/); + _callback = callback; + } + + _topicList.forEach(function(tpc) { + if(this.subscriptions[_exchange][tpc]) { + var _len = this.subscriptions[_exchange][tpc].length, + _idx = 0; + for ( ; _idx < _len; _idx++ ) { + if (this.subscriptions[_exchange][tpc][_idx].callback === callback) { + this.subscriptions[_exchange][tpc].splice( _idx, 1 ); + break; } - }); + } } - }); + },this); }; - this.unsubscribe = function(topic, callback) { - if ( !subscriptions[ topic ] ) { - return; + this.publish = function(exchange, topic, data) { + var _args = slice.call(arguments,0), + _exchange, + _topicList, + _data; + if(_args.length === 1) { + _exchange = DEFAULT_EXCHANGE; + _topicList = _args[0].split(/\s/); + _data = {}; } - var length = subscriptions[ topic ].length, - idx = 0; - for ( ; idx < length; idx++ ) { - if (subscriptions[topic][idx] === callback) { - subscriptions[topic].splice( idx, 1 ); + else if(_args.length === 2 && typeof _args[1] === 'object') { + _exchange = DEFAULT_EXCHANGE; + _topicList = _args[0].split(/\s/); + _data = _args[1] || {}; + } + else if(_args.length === 2) { + _exchange = _args[0]; + _topicList = _args[1].split(/\s/); + _data = {}; + } + else { + _exchange = exchange; + _topicList = topic.split(/\s/); + _data = data || {}; + } + if(_mode !== REPLAY_MODE || (_mode === REPLAY_MODE && _exchange === SYSTEM_EXCHANGE)) { + + _topicList.forEach(function(tpc){ + _publish(_exchange, tpc, _data); + }); + } + }; + + this.subscribe(SYSTEM_EXCHANGE, "mode.set", function(data) { + if(data.mode) { + switch(data.mode) { + case REPLAY_MODE: + _mode = REPLAY_MODE; + _replayContext = new ReplayContext(_publish.bind(this), this.subscribe.bind(this)); + _captor = undefined; + break; + case CAPTURE_MODE: + _mode = CAPTURE_MODE; + _captor = new MessageCaptor(function(callback){ + this.wireTaps.push(callback); + }.bind(this), + function(callback) { + var idx = this.wireTaps.indexOf(callback); + if(idx !== -1) { + this.wireTaps.splice(idx,1); + } + }.bind(this)); + break; + default: + _mode = NORMAL_MODE; + _replayContext = undefined; + _captor = undefined; break; } } - }; + }.bind(this)); }; -exports.Postal = MessageBroker; \ No newline at end of file + + +var postal = new Postal(); + +postal.DEFAULT_EXCHANGE = DEFAULT_EXCHANGE; +postal.SYSTEM_EXCHANGE = SYSTEM_EXCHANGE; +postal.NORMAL_MODE = NORMAL_MODE; +postal.CAPTURE_MODE = CAPTURE_MODE; +postal.REPLAY_MODE = REPLAY_MODE; +postal.POSTAL_MSG_STORE_KEY = POSTAL_MSG_STORE_KEY; + +exports.postal = postal; \ No newline at end of file diff --git a/build/source-references.js b/build/source-references.js deleted file mode 100644 index ff1775e..0000000 --- a/build/source-references.js +++ /dev/null @@ -1,4 +0,0 @@ -simplifyDebugCallback([ - 'src/languageExtensions.js', - 'src/broker.js', -]); \ No newline at end of file diff --git a/build/version-header.js b/build/version-header.js index 11394fa..5986aad 100644 --- a/build/version-header.js +++ b/build/version-header.js @@ -1,5 +1,7 @@ -// Postal.js -// Author: Jim Cowart -// License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license) -// Version 0.0.1 +/* + postal.js + Author: Jim Cowart + License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license) + Version 0.0.1 +*/ diff --git a/spec/runner.html b/spec/runner.html index a796d94..844a8dd 100644 --- a/spec/runner.html +++ b/spec/runner.html @@ -6,16 +6,17 @@ - + - +
- diff --git a/src/CapturedMessageBatch.js b/src/CapturedMessageBatch.js deleted file mode 100644 index ed1b693..0000000 --- a/src/CapturedMessageBatch.js +++ /dev/null @@ -1,5 +0,0 @@ -var CapturedMessageBatch = function(batchId, description, messages) { - this.batchId = ""; - this.description = description || "Captured Message Batch"; - this.messages = messages || []; -}; \ No newline at end of file diff --git a/src/MessageCaptor.js b/src/MessageCaptor.js index 152c9a7..6f9553a 100644 --- a/src/MessageCaptor.js +++ b/src/MessageCaptor.js @@ -16,7 +16,11 @@ var MessageCaptor = function(plugUp, unPlug) { if(!captureStore) { captureStore = {}; } - captureStore[batchId] = new CapturedMessageBatch(batchId, description, this.messages); + captureStore[batchId] = { + batchId: batchId, + description: description, + messages: this.messages + }; amplify.store(POSTAL_MSG_STORE_KEY, captureStore); }; @@ -24,4 +28,5 @@ var MessageCaptor = function(plugUp, unPlug) { this.save(data.batchId || new Date().toString(), data.description || "Captured Message Batch"); }.bind(this)); -}; \ No newline at end of file +}; + diff --git a/src/Postal.js b/src/Postal.js index 93bd325..031fc64 100644 --- a/src/Postal.js +++ b/src/Postal.js @@ -1,36 +1,45 @@ -var DEFAULT_EXCHANGE = "/", - SYSTEM_EXCHANGE = "postal", - NORMAL_MODE = "Normal", - CAPTURE_MODE = "Capture", - REPLAY_MODE = "Replay", - POSTAL_MSG_STORE_KEY = "postal.captured", - _forEachKeyValue = function(object, callback) { - for(var x in object) { - if(object.hasOwnProperty(x)) { - callback(x, object[x]); - } - } - }; - var Postal = function() { var _regexify = function(topic) { - if(!this[topic]) { - this[topic] = topic.replace(".", "\.").replace("*", ".*"); + if(!this.cache[topic]) { + this.cache[topic] = topic.replace(".", "\.").replace("*", ".*"); } - return this[topic]; + return this.cache[topic]; }.bind(this), _isTopicMatch = function(topic, comparison) { - if(!this[topic + '_' + comparison]) { - this[topic + '_' + comparison] = topic === comparison || + if(!this.cache[topic + '_' + comparison]) { + this.cache[topic + '_' + comparison] = topic === comparison || (comparison.indexOf("*") !== -1 && topic.search(_regexify(comparison)) !== -1) || (topic.indexOf("*") !== -1 && comparison.search(_regexify(topic)) !== -1); } - return this[topic + '_' + comparison]; + return this.cache[topic + '_' + comparison]; + }.bind(this), + _publish = function(exchange, topic, data) { + this.wireTaps.forEach(function(tap) { + tap({ + exchange: exchange, + topic: topic, + data: data, + timeStamp: new Date() + }); + }); + + _forEachKeyValue(this.subscriptions[exchange],function(subTpc, subs) { + if(_isTopicMatch(topic, subTpc)) { + subs.forEach(function(sub) { + if(typeof sub.callback === 'function') { + sub.callback(data); + sub.onFired(); + } + }); + } + }); }.bind(this), _mode = NORMAL_MODE, _replayContext, _captor; + this.cache = {}; + this.getMode = function() { return _mode; }; this.wireTaps = []; @@ -161,11 +170,21 @@ var Postal = function() { _exchange, _topicList, _data; - if(_args.length == 2) { + if(_args.length === 1) { + _exchange = DEFAULT_EXCHANGE; + _topicList = _args[0].split(/\s/); + _data = {}; + } + else if(_args.length === 2 && typeof _args[1] === 'object') { _exchange = DEFAULT_EXCHANGE; _topicList = _args[0].split(/\s/); _data = _args[1] || {}; } + else if(_args.length === 2) { + _exchange = _args[0]; + _topicList = _args[1].split(/\s/); + _data = {}; + } else { _exchange = exchange; _topicList = topic.split(/\s/); @@ -174,26 +193,8 @@ var Postal = function() { if(_mode !== REPLAY_MODE || (_mode === REPLAY_MODE && _exchange === SYSTEM_EXCHANGE)) { _topicList.forEach(function(tpc){ - this.wireTaps.forEach(function(tap) { - tap({ - exchange: _exchange, - topic: tpc, - data: _data, - timeStamp: new Date() - }); - }); - - _forEachKeyValue(this.subscriptions[_exchange],function(subTpc, subs) { - if(_isTopicMatch(tpc, subTpc)) { - subs.map(function(sub) { return sub.callback; }) - .forEach(function(callback) { - if(typeof callback === 'function') { - callback(_data); - } - }); - } - }); - }, this); + _publish(_exchange, tpc, _data); + }); } }; @@ -202,7 +203,7 @@ var Postal = function() { switch(data.mode) { case REPLAY_MODE: _mode = REPLAY_MODE; - _replayContext = new ReplayContext(this.publish.bind(this), this.subscribe.bind(this)); + _replayContext = new ReplayContext(_publish.bind(this), this.subscribe.bind(this)); _captor = undefined; break; case CAPTURE_MODE: @@ -227,5 +228,3 @@ var Postal = function() { }.bind(this)); }; -var postal = new Postal(); - diff --git a/src/ReplayContext.js b/src/ReplayContext.js index da36940..5265159 100644 --- a/src/ReplayContext.js +++ b/src/ReplayContext.js @@ -5,6 +5,9 @@ var ReplayContext = function (publish, subscribe) { var msgStore = amplify.store(POSTAL_MSG_STORE_KEY), targetBatch = msgStore[batchId]; if(targetBatch) { + targetBatch.messages.forEach(function(msg) { + msg.timeStamp = new Date(msg.timeStamp); + }); _batch = targetBatch; } }, @@ -23,39 +26,40 @@ var ReplayContext = function (publish, subscribe) { publish(msg.exchange, msg.topic, msg.data); }, _replayRealTime = function() { - var lastTime = _batch.messages[0].timeStamp; - while(_batch.messages.length > 0) { - if(_continue) { - _advanceNext(); - setTimeout(publish(SYSTEM_EXCHANGE, "replay.realTime"), _batch.messages[0].timeStamp - lastTime); - } - else { - break; - } + if(_continue && _batch.messages.length > 0) { + if(_batch.messages.length > 1) { + var span = _batch.messages[1].timeStamp - _batch.messages[0].timeStamp; + _advanceNext(); + setTimeout(_replayRealTime, span); + } + else { + _advanceNext(); + } } }; - subscribe(SYSTEM_EXCHANGE, "replay.load", function(data) { + postal.subscribe(SYSTEM_EXCHANGE, "replay.load", function(data) { _continue = false; _loadMessages(data); }); - subscribe(SYSTEM_EXCHANGE, "replay.immediate", function() { + postal.subscribe(SYSTEM_EXCHANGE, "replay.immediate", function() { _continue = true; _replayImmediate(); }); - subscribe(SYSTEM_EXCHANGE, "replay.advanceNext", function() { + postal.subscribe(SYSTEM_EXCHANGE, "replay.advanceNext", function() { _continue = true; _advanceNext(); }); - subscribe(SYSTEM_EXCHANGE, "replay.realTime", function() { + postal.subscribe(SYSTEM_EXCHANGE, "replay.realTime", function() { _continue = true; _replayRealTime(); }); - subscribe(SYSTEM_EXCHANGE, "replay.stop", function() { + postal.subscribe(SYSTEM_EXCHANGE, "replay.stop", function() { _continue = false; }); -}; \ No newline at end of file +}; + diff --git a/src/broker.js b/src/broker.js deleted file mode 100644 index 29cdf12..0000000 --- a/src/broker.js +++ /dev/null @@ -1,70 +0,0 @@ -/* -var MessageBroker = function() { - var subscriptions = {}, - regexify = function(topic) { - if(!this[topic]) { - this[topic] = topic.replace(".", "\.").replace("*", ".*"); - } - return this[topic]; - }.bind(this), - isTopicMatch = function(topic, comparison) { - if(!this[topic + '_' + comparison]) { - this[topic + '_' + comparison] = topic === comparison || - (comparison.indexOf("*") !== -1 && topic.search(regexify(comparison)) !== -1) || - (topic.indexOf("*") !== -1 && comparison.search(regexify(topic)) !== -1); - } - return this[topic + '_' + comparison]; - }.bind(this); - - this.subscribe = function(topic, callback) { - var topicList = topic.split(/\s/), // we allow multiple topics to be subscribed in one call. - subIdx = 0, - exists; - topicList.forEach(function(topic) { - exists = false; - if(!subscriptions[topic]) { - subscriptions[topic] = [callback]; - } - else { - subscriptions[topic].forEach(function(sub) { - if(subscriptions[topic][subIdx] === callback) { - exists = true; - } - }); - if(!exists) { - subscriptions[topic].push(callback); - } - } - }); - // return callback for un-subscribing... - return function() { - this.unsubscribe(topic, callback); - }.bind(this); - }; - - this.publish = function(topic, data) { - subscriptions.forEachKeyValue(function(subNm, subs) { - if(isTopicMatch(topic, subNm)) { - subs.forEach(function(callback) { - if(typeof callback === 'function') { - callback(data); - } - }); - } - }); - }; - - this.unsubscribe = function(topic, callback) { - if ( !subscriptions[ topic ] ) { - return; - } - var length = subscriptions[ topic ].length, - idx = 0; - for ( ; idx < length; idx++ ) { - if (subscriptions[topic][idx] === callback) { - subscriptions[topic].splice( idx, 1 ); - break; - } - } - }; -};*/ diff --git a/src/languageExtensions.js b/src/languageExtensions.js deleted file mode 100644 index ec73f3a..0000000 --- a/src/languageExtensions.js +++ /dev/null @@ -1,37 +0,0 @@ -/*if(!Object.prototype.forEach) { - Object.prototype.forEach = function (callback) { - var self = this; - for(var x in self) { - if(self.hasOwnProperty(x)) { - callback(self[x]); - } - } - }; -}; - -if(!Object.prototype.forEachKeyValue) { - Object.prototype.forEachKeyValue = function (callback) { - var self = this; - for(var x in self) { - if(self.hasOwnProperty(x)) { - callback(x, self[x]); - } - } - }; -};*/ - -var isArray = function(value) { - var s = typeof value; - if (s === 'object') { - if (value) { - if (typeof value.length === 'number' && - !(value.propertyIsEnumerable('length')) && - typeof value.splice === 'function') { - s = 'array'; - } - } - } - return s === 'array'; -}; - -var slice = [].slice; \ No newline at end of file diff --git a/src/misc.js b/src/misc.js new file mode 100644 index 0000000..9821b67 --- /dev/null +++ b/src/misc.js @@ -0,0 +1,28 @@ +var isArray = function(value) { + var s = typeof value; + if (s === 'object') { + if (value) { + if (typeof value.length === 'number' && + !(value.propertyIsEnumerable('length')) && + typeof value.splice === 'function') { + s = 'array'; + } + } + } + return s === 'array'; + }, + slice = [].slice, + DEFAULT_EXCHANGE = "/", + SYSTEM_EXCHANGE = "postal", + NORMAL_MODE = "Normal", + CAPTURE_MODE = "Capture", + REPLAY_MODE = "Replay", + POSTAL_MSG_STORE_KEY = "postal.captured", + _forEachKeyValue = function(object, callback) { + for(var x in object) { + if(object.hasOwnProperty(x)) { + callback(x, object[x]); + } + } + }; +