Set up node and browser build scripts. Node output has NOT been tested. Added unit tests, all kinds of refactoring to core engine.

This commit is contained in:
Jim Cowart 2011-08-10 22:32:08 -04:00
parent e860d8700b
commit 8a963ab322
21 changed files with 887 additions and 284 deletions

View file

@ -1,2 +0,0 @@
exports.Postal = MessageBroker;

View file

@ -1,5 +1,4 @@
src/languageExtensions.js
src/CapturedMessageBatch.js
src/misc.js
src/MessageCaptor.js
src/ReplayContext.js
src/Postal.js

View file

@ -0,0 +1,4 @@
src/misc.js
src/MessageCaptor.js
src/ReplayContext.js
src/Postal.js

View file

@ -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);

View file

@ -0,0 +1,2 @@
(function(global, undefined) {

View file

@ -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;

4
build/build-all.sh Executable file
View file

@ -0,0 +1,4 @@
#!/bin/sh
./linux-build-node.sh
./linux-build-browser.sh

14
build/linux-build-browser.sh Executable file
View file

@ -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

View file

@ -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
cat ./boilerplate/node_footer.txt >> $OutFile

View file

@ -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);

View file

@ -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;
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;

View file

@ -1,4 +0,0 @@
simplifyDebugCallback([
'src/languageExtensions.js',
'src/broker.js',
]);

View file

@ -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
*/

View file

@ -6,16 +6,17 @@
<script type="text/javascript" src="../lib/pavlov.js"></script>
<script type="text/javascript" src="../lib/amplify.core.js"></script>
<script type="text/javascript" src="../lib/amplify.store.js"></script>
<script type="text/javascript" src="../src/languageExtensions.js"></script>
<script type="text/javascript" src="../src/misc.js"></script>
<script type="text/javascript" src="../src/Postal.js"></script>
<script type="text/javascript" src="../src/ReplayContext.js"></script>
<script type="text/javascript" src="../src/CapturedMessageBatch.js"></script>
<script type="text/javascript" src="../src/MessageCaptor.js"></script>
<script type="text/javascript">
var postal = new Postal();
</script>
<script type="text/javascript" src="broker.spec.js"></script>
<link rel="stylesheet" href="../lib/qunit.css" type="text/css" media="screen" />
</head>
<body>
<h1 id="qunit-header"></h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>

View file

@ -1,5 +0,0 @@
var CapturedMessageBatch = function(batchId, description, messages) {
this.batchId = "";
this.description = description || "Captured Message Batch";
this.messages = messages || [];
};

View file

@ -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));
};
};

View file

@ -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();

View file

@ -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;
});
};
};

View file

@ -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;
}
}
};
};*/

View file

@ -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;

28
src/misc.js Normal file
View file

@ -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]);
}
}
};