Created Replay panel, refactored bus/postal, re-organized file structure, etc.

This commit is contained in:
Jim Cowart 2011-08-14 04:29:12 -04:00
parent f96f4eb485
commit e3bd1d64fe
30 changed files with 1156 additions and 697 deletions

View file

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

View file

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

View file

@ -1,5 +1,5 @@
var postal = global.postal = new Postal();
global.postal = postal;
postal.DEFAULT_EXCHANGE = DEFAULT_EXCHANGE;
postal.SYSTEM_EXCHANGE = SYSTEM_EXCHANGE;

View file

@ -1,6 +1,4 @@
var postal = new Postal();
postal.DEFAULT_EXCHANGE = DEFAULT_EXCHANGE;
postal.SYSTEM_EXCHANGE = SYSTEM_EXCHANGE;
postal.NORMAL_MODE = NORMAL_MODE;

View file

@ -0,0 +1,2 @@
})(window);

View file

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

View file

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

View file

@ -0,0 +1,14 @@
#!/bin/sh
OutFile='output/browser/postal.replay.js'
cp version-header.js $OutFile
cat ./boilerplate/replay_header.txt >> $OutFile
# Combine the source files
while read line; do
cat ../$line >> $OutFile
done < source-browser-replay.txt
cat ./boilerplate/replay_footer.txt >> $OutFile

View file

@ -9,6 +9,6 @@ cat ./boilerplate/browser_header.txt >> $OutFile
# Combine the source files
while read line; do
cat ../$line >> $OutFile
done < SourceManifest-browser.txt
done < source-browser-postal.txt
cat ./boilerplate/browser_footer.txt >> $OutFile

View file

@ -35,104 +35,7 @@ var isArray = function(value) {
}
};
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 Bus = function() {
var _regexify = function(topic) {
if(!this.cache[topic]) {
this.cache[topic] = topic.replace(".", "\.").replace("*", ".*");
@ -146,42 +49,69 @@ var Postal = function() {
(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()
});
});
}.bind(this);
_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.context = undefined;
this.cache = {};
this.getMode = function() { return _mode; };
this.wireTaps = [];
this.subscriptions = {};
this.subscriptions[DEFAULT_EXCHANGE] = {};
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.apply(sub.context, [data]);
sub.onFired();
}
});
}
});
};
this.mode = NORMAL_MODE;
this[NORMAL_MODE] = {
setup: function() {
this.mode = NORMAL_MODE;
this.context = undefined;
},
teardown: function() {
// no-op
}
};
this.init = function() {
this[NORMAL_MODE]();
var systemEx = this.subscriptions[SYSTEM_EXCHANGE] || {};
this.subscriptions = {};
this.subscriptions[DEFAULT_EXCHANGE] = {};
this.subscriptions[SYSTEM_EXCHANGE] = systemEx;
this.cache = {};
this.wireTaps = [];
};
};
var bus = new Bus();
var Postal = function() {
this.getMode = function() { return bus.mode; };
/*
options object has the following optional members:
{
@ -196,10 +126,10 @@ var Postal = function() {
_topicList, // we allow multiple topics to be subscribed in one call.,
_once = false,
_subData = {
callback: function() { /* placeholder noop */ },
callback: function() { /* placeholder no-op */ },
priority: 50,
context: null,
onFired: function() { /* noop */ }
onFired: function() { /* placeholder no-op */ }
},
_idx,
_found;
@ -237,26 +167,26 @@ var Postal = function() {
}.bind(this);
}
if(!this.subscriptions[_exchange]) {
this.subscriptions[_exchange] = {};
if(!bus.subscriptions[_exchange]) {
bus.subscriptions[_exchange] = {};
}
_topicList.forEach(function(tpc) {
if(!this.subscriptions[_exchange][tpc]) {
this.subscriptions[_exchange][tpc] = [_subData];
if(!bus.subscriptions[_exchange][tpc]) {
bus.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) {
_idx = bus.subscriptions[_exchange][tpc].length - 1;
if(bus.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);
if(bus.subscriptions[_exchange][tpc][_idx].priority <= _subData.priority) {
bus.subscriptions[_exchange][tpc].splice(_idx + 1, 0, _subData);
_found = true;
break;
}
}
if(!_found) {
this.subscriptions[_exchange][tpc].unshift(_subData);
bus.subscriptions[_exchange][tpc].unshift(_subData);
}
}
}
@ -286,12 +216,12 @@ var Postal = function() {
}
_topicList.forEach(function(tpc) {
if(this.subscriptions[_exchange][tpc]) {
var _len = this.subscriptions[_exchange][tpc].length,
if(bus.subscriptions[_exchange][tpc]) {
var _len = bus.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 );
if (bus.subscriptions[_exchange][tpc][_idx].callback === callback) {
bus.subscriptions[_exchange][tpc].splice( _idx, 1 );
break;
}
}
@ -324,46 +254,54 @@ var Postal = function() {
_topicList = topic.split(/\s/);
_data = data || {};
}
if(_mode !== REPLAY_MODE || (_mode === REPLAY_MODE && _exchange === SYSTEM_EXCHANGE)) {
if(bus.mode !== REPLAY_MODE || (bus.mode === REPLAY_MODE && _exchange === SYSTEM_EXCHANGE)) {
_topicList.forEach(function(tpc){
_publish(_exchange, tpc, _data);
bus.publish(_exchange, tpc, _data);
});
}
};
this.reset = function() {
bus.init();
};
this.addBusBehavior = function(behaviorName, setup, teardown) {
if(!bus[behaviorName]) {
bus[behaviorName] = {};
}
bus[behaviorName].setup = function() {
bus.mode = behaviorName;
setup(bus);
};
if(teardown) {
bus[behaviorName].teardown = function() { teardown(bus); }
}
else {
bus[behaviorName].teardown = function() { /* no-op */ }
}
};
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;
}
if(data.mode && bus[data.mode]) {
bus[bus.mode].teardown();
bus[data.mode].setup(data);
}
}.bind(this));
this.addWireTap = function(callback) {
bus.wireTaps.push(callback);
return function() {
var idx = bus.wireTaps.indexOf(callback);
if(idx !== -1) {
bus.wireTaps.splice(idx,1);
}
};
};
};
var postal = global.postal = new Postal();
var postal = new Postal();
global.postal = postal;
postal.DEFAULT_EXCHANGE = DEFAULT_EXCHANGE;
postal.SYSTEM_EXCHANGE = SYSTEM_EXCHANGE;

View file

@ -0,0 +1,238 @@
/*
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 _forEachKeyValue = function(object, callback) {
for(var x in object) {
if(object.hasOwnProperty(x)) {
callback(x, object[x]);
}
}
},
_subscriptions = [];
var ReplayContext = function (bus) {
var _batch,
_continue = true,
_loadMessages = function(batchId) {
var msgStore = amplify.store(postal.POSTAL_MSG_STORE_KEY),
targetBatch;
if(msgStore[window.location.pathname] && msgStore[window.location.pathname][batchId]) {
targetBatch = msgStore[window.location.pathname][batchId];
targetBatch.messages.forEach(function(msg) {
msg.timeStamp = new Date(msg.timeStamp);
});
_batch = msgStore[window.location.pathname][batchId];
postal.publish(postal.SYSTEM_EXCHANGE, "replay.store.batchLoaded", { batchId: batchId,
description: targetBatch.description,
msgCount: targetBatch.messages.length });
}
},
_batchListCache = [],
_remoteConfigured = false,
_replayImmediate = function() {
if(_batch) {
while(_batch.messages.length > 0) {
if(_continue) {
_advanceNext();
}
else {
break;
}
}
}
},
_advanceNext = function() {
if(_batch && _batch.messages.length > 0) {
var msg = _batch.messages.shift();
bus.publish(msg.exchange, msg.topic, msg.data);
}
},
_replayRealTime = function() {
if(_continue && _batch && _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();
}
}
};
this.init = function() {
_subscriptions.push(postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.immediate", function() {
_continue = true;
_replayImmediate();
}));
_subscriptions.push(postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.advanceNext", function() {
_continue = true;
_advanceNext();
}));
_subscriptions.push(postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.realTime", function() {
_continue = true;
_replayRealTime();
}));
_subscriptions.push(postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.stop", function() {
_continue = false;
}));
_subscriptions.push(postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.store.loadBatch", function(data) {
if(data.batchId) {
_continue = false;
_loadMessages(data.batchId);
}
}));
_subscriptions.push(postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.store.refreshLocal", function(data){
var local = amplify.store(postal.POSTAL_MSG_STORE_KEY) || {},
batches = [];
if(local && local[window.location.pathname]) {
_forEachKeyValue(local[window.location.pathname], function(k, v) {
batches.push({ batchId: v.batchId,
description: v.description,
messageCount: v.messages.length,
source: "local"
});
});
}
_batchListCache = _batchListCache.filter(function(x) { return x.source !== "local"; })
.concat(batches);
postal.publish(postal.SYSTEM_EXCHANGE, "replay.store.batchList", _batchListCache);
}));
_subscriptions.push(postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.store.refreshRemote", function(data) {
if(_remoteConfigured) {
amplify.request("getRemoteCaptures", function(data) {
if(data.batches) {
_batchListCache = _batchListCache.filter(function(x) { return x.source !== 'remote'; });
_batchListCache = _batchListCache.concat(data.batches.map(function(x) {
x.source = "remote";
return x;
}));
postal.publish(postal.SYSTEM_EXCHANGE, "replay.store.batchList", _batchListCache);
}
});
}
}));
_subscriptions.push(postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.store.remote.config", function(data) {
if(amplify) {
amplify.request.define("getRemoteCaptures", "ajax", {
"url": data.url,
"dataType": "json",
"type": data.method,
"contentType" : "application/json"
});
_remoteConfigured = true;
}
else {
throw "Amplify.js is required in order to access remote captured batches."
}
}));
postal.publish(postal.SYSTEM_EXCHANGE, "replay.store.refreshLocal");
postal.publish(postal.SYSTEM_EXCHANGE, "replay.store.refreshRemote");
};
this.init();
};
// Adding replay functionality to the bus.....
postal.addBusBehavior(postal.REPLAY_MODE,
function(bus) {
postal.replay.render();
return new ReplayContext(bus);
},
function(bus) {
postal.replay.hide();
_subscriptions.forEach(function(remove) { remove(); });
});
var ReplayPanel = function() {
var _rendered = false,
_style = '.postal-replay-wrapper { font-family: Tahoma, Arial; font-size: 10pt; float: left; vertical-align: middle; margin: 0px; padding: 0px; position: fixed; left: 0px; top: 0px; width: 100%; background-color: steelblue; color: white; } .postal-replay-title { float: left; margin-top: 4px; margin-left: 5px; margin-right: 15px; font-weight: bold; font-size: 11pt; height: 100%; } .postal-replay-button { float: left; } .postal-replay-dropdown { width: 150px; } .postal-replay-load { float: right; } .postal-replay-load select { float: left; margin-right: 10px; } #currentBatch { margin-top: 4px; margin-left: 20px; font-size: 10pt; font-weight: bold; float: left; } .postal-replay-exit { margin-left:35px; }',
_html = '<div class="postal-replay-title">Postal Replay</div> <input class="postal-replay-button" type="button" id="btnRealTime" value="Play" alt="Real Time Playback" onclick="postal.replay.replayRealTime()"> <input class="postal-replay-button" type="button" id="btnStop" value="Stop" onclick="postal.replay.replayStop()"> <input class="postal-replay-button" type="button" id="btnAdvance" value="Step" alt="Advances to Next Msg (manual progression)" onclick="postal.replay.replayAdvance()"> <input class="postal-replay-button" type="button" id="btnImmediate" value="Immediate" alt="Replays all Messages Immediately" onclick="postal.replay.replayImmediate()"> <div id="currentBatch"></div> <div class="postal-replay-load"> <select class="postal-replay-dropdown" id="drpBatches"></select> <input class="postal-replay-button" type="button" id="btnLoad" value="Load Batch" onclick="postal.replay.loadBatch()"> <input class="postal-replay-button postal-replay-exit" type="button" id="btnExitReplay" value="Exit Replay Mode" onclick="postal.replay.exitReplay()"></div>';
this.exitReplay = function() {
postal.publish(postal.SYSTEM_EXCHANGE, "mode.set", { mode: postal.NORMAL_MODE });
};
this.replayRealTime = function() {
postal.publish(postal.SYSTEM_EXCHANGE, "replay.realTime");
};
this.replayImmediate = function() {
postal.publish(postal.SYSTEM_EXCHANGE, "replay.immediate");
};
this.replayAdvance = function() {
postal.publish(postal.SYSTEM_EXCHANGE, "replay.advanceNext");
};
this.replayStop = function() {
postal.publish(postal.SYSTEM_EXCHANGE, "replay.stop");
};
this.loadBatch = function() {
var batchId = document.getElementById("drpBatches").value;
postal.publish(postal.SYSTEM_EXCHANGE, "replay.store.loadBatch", { batchId: batchId });
};
postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.store.batchLoaded", function(data) {
var text = "Replaying: " + data.batchId + " (" + data.description + ") " + data.msgCount + " message(s)";
document.getElementById("currentBatch").innerText = text;
});
postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.store.batchList", function(data) {
var dropDown = document.getElementById("drpBatches"),
optElem;
dropDown.options.remove();
if(data) {
data.forEach(function(item) {
optElem = document.createElement("option");
optElem.value = item.batchId;
optElem.text = item.batchId
dropDown.options.add(optElem);
});
}
});
this.render = function() {
if(!_rendered){
var style = document.createElement("style");
style.innerText = _style;
document.getElementsByTagName("head")[0].appendChild(style);
var wrapper = document.createElement("div");
wrapper.setAttribute("class", "postal-replay-wrapper");
wrapper.setAttribute("id", "replay-wrapper");
wrapper.innerHTML = _html;
document.body.appendChild(wrapper);
_rendered = true;
}
else {
document.getElementById("replay-wrapper").hidden = false;
}
};
this.hide = function() {
document.getElementById("replay-wrapper").hidden = true;
};
};
postal.replay = new ReplayPanel();
})(window);

View file

@ -33,104 +33,7 @@ var isArray = function(value) {
}
};
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 Bus = function() {
var _regexify = function(topic) {
if(!this.cache[topic]) {
this.cache[topic] = topic.replace(".", "\.").replace("*", ".*");
@ -144,42 +47,69 @@ var Postal = function() {
(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()
});
});
}.bind(this);
_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.context = undefined;
this.cache = {};
this.getMode = function() { return _mode; };
this.wireTaps = [];
this.subscriptions = {};
this.subscriptions[DEFAULT_EXCHANGE] = {};
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.apply(sub.context, [data]);
sub.onFired();
}
});
}
});
};
this.mode = NORMAL_MODE;
this[NORMAL_MODE] = {
setup: function() {
this.mode = NORMAL_MODE;
this.context = undefined;
},
teardown: function() {
// no-op
}
};
this.init = function() {
this[NORMAL_MODE]();
var systemEx = this.subscriptions[SYSTEM_EXCHANGE] || {};
this.subscriptions = {};
this.subscriptions[DEFAULT_EXCHANGE] = {};
this.subscriptions[SYSTEM_EXCHANGE] = systemEx;
this.cache = {};
this.wireTaps = [];
};
};
var bus = new Bus();
var Postal = function() {
this.getMode = function() { return bus.mode; };
/*
options object has the following optional members:
{
@ -194,10 +124,10 @@ var Postal = function() {
_topicList, // we allow multiple topics to be subscribed in one call.,
_once = false,
_subData = {
callback: function() { /* placeholder noop */ },
callback: function() { /* placeholder no-op */ },
priority: 50,
context: null,
onFired: function() { /* noop */ }
onFired: function() { /* placeholder no-op */ }
},
_idx,
_found;
@ -235,26 +165,26 @@ var Postal = function() {
}.bind(this);
}
if(!this.subscriptions[_exchange]) {
this.subscriptions[_exchange] = {};
if(!bus.subscriptions[_exchange]) {
bus.subscriptions[_exchange] = {};
}
_topicList.forEach(function(tpc) {
if(!this.subscriptions[_exchange][tpc]) {
this.subscriptions[_exchange][tpc] = [_subData];
if(!bus.subscriptions[_exchange][tpc]) {
bus.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) {
_idx = bus.subscriptions[_exchange][tpc].length - 1;
if(bus.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);
if(bus.subscriptions[_exchange][tpc][_idx].priority <= _subData.priority) {
bus.subscriptions[_exchange][tpc].splice(_idx + 1, 0, _subData);
_found = true;
break;
}
}
if(!_found) {
this.subscriptions[_exchange][tpc].unshift(_subData);
bus.subscriptions[_exchange][tpc].unshift(_subData);
}
}
}
@ -284,12 +214,12 @@ var Postal = function() {
}
_topicList.forEach(function(tpc) {
if(this.subscriptions[_exchange][tpc]) {
var _len = this.subscriptions[_exchange][tpc].length,
if(bus.subscriptions[_exchange][tpc]) {
var _len = bus.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 );
if (bus.subscriptions[_exchange][tpc][_idx].callback === callback) {
bus.subscriptions[_exchange][tpc].splice( _idx, 1 );
break;
}
}
@ -322,47 +252,53 @@ var Postal = function() {
_topicList = topic.split(/\s/);
_data = data || {};
}
if(_mode !== REPLAY_MODE || (_mode === REPLAY_MODE && _exchange === SYSTEM_EXCHANGE)) {
if(bus.mode !== REPLAY_MODE || (bus.mode === REPLAY_MODE && _exchange === SYSTEM_EXCHANGE)) {
_topicList.forEach(function(tpc){
_publish(_exchange, tpc, _data);
bus.publish(_exchange, tpc, _data);
});
}
};
this.reset = function() {
bus.init();
};
this.addBusBehavior = function(behaviorName, setup, teardown) {
if(!bus[behaviorName]) {
bus[behaviorName] = {};
}
bus[behaviorName].setup = function() {
bus.mode = behaviorName;
setup(bus);
};
if(teardown) {
bus[behaviorName].teardown = function() { teardown(bus); }
}
else {
bus[behaviorName].teardown = function() { /* no-op */ }
}
};
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;
}
if(data.mode && bus[data.mode]) {
bus[bus.mode].teardown();
bus[data.mode].setup(data);
}
}.bind(this));
this.addWireTap = function(callback) {
bus.wireTaps.push(callback);
return function() {
var idx = bus.wireTaps.indexOf(callback);
if(idx !== -1) {
bus.wireTaps.splice(idx,1);
}
};
};
};
var postal = new Postal();
postal.DEFAULT_EXCHANGE = DEFAULT_EXCHANGE;
postal.SYSTEM_EXCHANGE = SYSTEM_EXCHANGE;
postal.NORMAL_MODE = NORMAL_MODE;

View file

@ -0,0 +1,3 @@
src/misc.js
src/Bus.js
src/Postal.js

View file

@ -0,0 +1,2 @@
src/replay/ReplayContext.js
src/replay/ReplayPanel.js

View file

@ -4,7 +4,7 @@ QUnit.specify("postal.js", function(){
describe("broker", function(){
describe("When publishing a message to a specific one level topic", function() {
describe("with one recipient", function() {
postal = new Postal();
postal.reset();
var objA = {
messageReceived: false
};
@ -17,7 +17,7 @@ QUnit.specify("postal.js", function(){
});
});
describe("with two recipients", function() {
postal = new Postal();
postal.reset();
var ObjA = function() {
this.messageReceived = false;
@ -48,7 +48,7 @@ QUnit.specify("postal.js", function(){
});
describe("When publishing a message to a specific multi-level topic", function() {
describe("with one recipient", function() {
postal = new Postal();
postal.reset();
var objA = {
messageReceived: false
};
@ -61,7 +61,7 @@ QUnit.specify("postal.js", function(){
});
});
describe("with two recipients", function() {
postal = new Postal();
postal.reset();
var ObjA = function() {
this.messageReceived = false;
@ -92,7 +92,7 @@ QUnit.specify("postal.js", function(){
});
describe("When publishing a wildcard message to a multi-level topic", function() {
describe("with one recipient", function() {
postal = new Postal();
postal.reset();
var objA = {
messageReceived: false
};
@ -105,7 +105,7 @@ QUnit.specify("postal.js", function(){
});
});
describe("with two recipients", function() {
postal = new Postal();
postal.reset();
var ObjA = function() {
this.messageReceived = false;
@ -136,7 +136,7 @@ QUnit.specify("postal.js", function(){
});
describe("When unsubscribing using provided callback", function() {
describe("with one callback", function() {
postal = new Postal();
postal.reset();
var objA = {
messageCount: 0
};
@ -151,7 +151,7 @@ QUnit.specify("postal.js", function(){
});
});
describe("with two callbacks", function() {
postal = new Postal();
postal.reset();
var ObjA = function() {
var _unsubscribe;
@ -197,10 +197,9 @@ QUnit.specify("postal.js", function(){
});
});
});
describe("When publishing a message on a specific exchange", function(){
describe("With a valid exchange", function() {
postal = new Postal();
postal.reset();
var objA = {
messageCount: 0
};
@ -215,7 +214,7 @@ QUnit.specify("postal.js", function(){
});
});
describe("With an invalid exchange", function() {
postal = new Postal();
postal.reset();
var objA = {
messageCount: 0
};
@ -231,7 +230,7 @@ QUnit.specify("postal.js", function(){
});
describe("With multiple active exchanges", function() {
describe("Publishing only to one exchange", function(){
postal = new Postal();
postal.reset();
var objA = {
messageCount: 0
};
@ -255,7 +254,7 @@ QUnit.specify("postal.js", function(){
});
});
describe("Publishing to multiple exchanges", function(){
postal = new Postal();
postal.reset();
var objA = {
messageCount: 0
};
@ -283,154 +282,5 @@ QUnit.specify("postal.js", function(){
});
});
});
describe("With Mode Change Messages", function(){
describe("Change To Replay", function() {
postal = new Postal();
var objA = {
messageCount: 0
},
mode;
var unsubscribeA = postal.subscribe("MyExchangeA", "Test.*", function() { objA.messageCount++; });
postal.publish("MyExchangeA", "Test.Topic", {});
postal.publish(SYSTEM_EXCHANGE, "mode.set", { mode: REPLAY_MODE });
mode = postal.getMode();
postal.publish("MyExchangeA", "Test.Topic", {});
unsubscribeA();
it("the subscription callback for objA should be invoked only once", function(){
assert(objA.messageCount).equals(1);
});
it("broker should report replay mode", function() {
assert(mode).equals(REPLAY_MODE);
});
});
describe("Change To Replay & Back to Normal", function() {
postal = new Postal();
var mode,
objA = {
messageCount: 0
},
mode2;
var unsubscribeA = postal.subscribe("MyExchangeA", "Test.*", function() { objA.messageCount++; });
postal.publish("MyExchangeA", "Test.Topic", {});
postal.publish(SYSTEM_EXCHANGE, "mode.set", { mode: REPLAY_MODE });
postal.publish("MyExchangeA", "Test.Topic", {});
mode = postal.getMode();
postal.publish(SYSTEM_EXCHANGE, "mode.set", { mode: NORMAL_MODE });
mode2 = postal.getMode();
postal.publish("MyExchangeA", "Test.Topic", {});
unsubscribeA();
it("the subscription callback for objA should be invoked only twice", function(){
assert(objA.messageCount).equals(2);
});
it("broker should report replay mode", function() {
assert(mode).equals(REPLAY_MODE);
});
it("broker should report normal mode", function() {
assert(mode2).equals(NORMAL_MODE);
});
});
describe("Change To Replay & Then to Capture", function() {
postal = new Postal();
var mode,
objA = {
messageCount: 0
},
mode2;
var unsubscribeA = postal.subscribe("MyExchangeA", "Test.*", function() { objA.messageCount++; });
postal.publish("MyExchangeA", "Test.Topic", {});
postal.publish(SYSTEM_EXCHANGE, "mode.set", { mode: REPLAY_MODE });
postal.publish("MyExchangeA", "Test.Topic", {});
mode = postal.getMode();
postal.publish(SYSTEM_EXCHANGE, "mode.set", { mode: CAPTURE_MODE });
mode2 = postal.getMode();
postal.publish("MyExchangeA", "Test.Topic", {});
unsubscribeA();
it("the subscription callback for objA should be invoked only twice", function(){
assert(objA.messageCount).equals(2);
});
it("broker should report replay mode", function() {
assert(mode).equals(REPLAY_MODE);
});
it("broker should report capture mode", function() {
assert(mode2).equals(CAPTURE_MODE);
});
});
describe("Change To Capture & Then to Normal", function() {
postal = new Postal();
var mode,
mode2,
objA = {
messageCount: 0
};
var unsubscribeA = postal.subscribe("MyExchangeA", "Test.*", function() { objA.messageCount++; });
postal.publish("MyExchangeA", "Test.Topic", {});
postal.publish(SYSTEM_EXCHANGE, "mode.set", { mode: CAPTURE_MODE });
postal.publish("MyExchangeA", "Test.Topic", {});
mode = postal.getMode();
postal.publish(SYSTEM_EXCHANGE, "mode.set", { mode: NORMAL_MODE });
mode2 = postal.getMode();
postal.publish("MyExchangeA", "Test.Topic", {});
unsubscribeA();
it("the subscription callback for objA should be invoked only 3x", function(){
assert(objA.messageCount).equals(3);
});
it("broker should report replay mode", function() {
assert(mode).equals(CAPTURE_MODE);
});
it("broker should report capture mode", function() {
assert(mode2).equals(NORMAL_MODE);
});
});
describe("Change To Capture", function() {
postal = new Postal();
var mode,
savedBatch,
objA = {
messageCount: 0
},
objB = {
messageCount: 0
};
var unsubscribeA = postal.subscribe("MyExchangeA", "Test.*", function() { objA.messageCount++; });
var unsubscribeB = postal.subscribe("MyExchangeB", "Test.*", function() { objB.messageCount++; });
postal.publish(SYSTEM_EXCHANGE, "mode.set", { mode: CAPTURE_MODE });
mode = postal.getMode();
postal.publish("MyExchangeA", "Test.Topic", {});
postal.publish("MyExchangeA", "Test.Topic", {});
postal.publish("MyExchangeB", "Test.Topic", {});
postal.publish("MyExchangeB", "Test.Topic", {});
unsubscribeA();
unsubscribeB();
postal.publish(SYSTEM_EXCHANGE, "captor.save", { batchId: "MyMsgBatch", description: "Just a Test" });
savedBatch = amplify.store(POSTAL_MSG_STORE_KEY)["MyMsgBatch"];
it("the subscription callback for objA should be invoked only twice", function(){
assert(objA.messageCount).equals(2);
});
it("broker should report replay mode", function() {
assert(mode).equals(CAPTURE_MODE);
});
it("captured message batch should exist", function() {
assert(savedBatch !== undefined).isTrue();
})
it("captured message batch should have 4 messages", function() {
});
});
});
});
});

View file

@ -0,0 +1,74 @@
QUnit.specify("postal.js", function(){
describe("broker", function(){
describe("With Mode Change Messages", function(){
describe("Change To Capture & Then to Normal", function() {
postal = new Postal();
var mode,
mode2,
objA = {
messageCount: 0
};
var unsubscribeA = postal.subscribe("MyExchangeA", "Test.*", function() { objA.messageCount++; });
postal.publish("MyExchangeA", "Test.Topic", {});
postal.publish(SYSTEM_EXCHANGE, "mode.set", { mode: CAPTURE_MODE });
postal.publish("MyExchangeA", "Test.Topic", {});
mode = postal.getMode();
postal.publish(SYSTEM_EXCHANGE, "mode.set", { mode: NORMAL_MODE });
mode2 = postal.getMode();
postal.publish("MyExchangeA", "Test.Topic", {});
unsubscribeA();
it("the subscription callback for objA should be invoked only 3x", function(){
assert(objA.messageCount).equals(3);
});
it("broker should report replay mode", function() {
assert(mode).equals(CAPTURE_MODE);
});
it("broker should report capture mode", function() {
assert(mode2).equals(NORMAL_MODE);
});
});
describe("Change To Capture", function() {
postal = new Postal();
var mode,
savedBatch,
objA = {
messageCount: 0
},
objB = {
messageCount: 0
};
var unsubscribeA = postal.subscribe("MyExchangeA", "Test.*", function() { objA.messageCount++; });
var unsubscribeB = postal.subscribe("MyExchangeB", "Test.*", function() { objB.messageCount++; });
postal.publish(SYSTEM_EXCHANGE, "mode.set", { mode: CAPTURE_MODE });
mode = postal.getMode();
postal.publish("MyExchangeA", "Test.Topic", {});
postal.publish("MyExchangeA", "Test.Topic", {});
postal.publish("MyExchangeB", "Test.Topic", {});
postal.publish("MyExchangeB", "Test.Topic", {});
unsubscribeA();
unsubscribeB();
postal.publish(SYSTEM_EXCHANGE, "captor.save", { batchId: "MyMsgBatch", description: "Just a Test" });
savedBatch = amplify.store(POSTAL_MSG_STORE_KEY)["MyMsgBatch"];
it("the subscription callback for objA should be invoked only twice", function(){
assert(objA.messageCount).equals(2);
});
it("broker should report replay mode", function() {
assert(mode).equals(CAPTURE_MODE);
});
it("captured message batch should exist", function() {
assert(savedBatch !== undefined).isTrue();
})
it("captured message batch should have 4 messages", function() {
});
});
});
});
});

View file

@ -0,0 +1,56 @@
QUnit.specify("postal.js", function(){
describe("broker", function(){
describe("With Mode Change Messages", function(){
describe("Change To Replay", function() {
postal.reset();
var objA = {
messageCount: 0
},
mode;
var unsubscribeA = postal.subscribe("MyExchangeA", "Test.*", function() { objA.messageCount++; });
postal.publish("MyExchangeA", "Test.Topic", {});
postal.publish(SYSTEM_EXCHANGE, "mode.set", { mode: REPLAY_MODE });
mode = postal.getMode();
postal.publish("MyExchangeA", "Test.Topic", {});
unsubscribeA();
it("the subscription callback for objA should be invoked only once", function(){
assert(objA.messageCount).equals(1);
});
it("broker should report replay mode", function() {
assert(mode).equals(REPLAY_MODE);
});
});
describe("Change To Replay & Back to Normal", function() {
postal.reset();
var mode,
objA = {
messageCount: 0
},
mode2;
var unsubscribeA = postal.subscribe("MyExchangeA", "Test.*", function() { objA.messageCount++; });
postal.publish("MyExchangeA", "Test.Topic", {});
postal.publish(SYSTEM_EXCHANGE, "mode.set", { mode: REPLAY_MODE });
postal.publish("MyExchangeA", "Test.Topic", {});
mode = postal.getMode();
postal.publish(SYSTEM_EXCHANGE, "mode.set", { mode: NORMAL_MODE });
mode2 = postal.getMode();
postal.publish("MyExchangeA", "Test.Topic", {});
unsubscribeA();
it("the subscription callback for objA should be invoked only twice", function(){
assert(objA.messageCount).equals(2);
});
it("broker should report replay mode", function() {
assert(mode).equals(REPLAY_MODE);
});
it("broker should report normal mode", function() {
assert(mode2).equals(NORMAL_MODE);
});
});
});
});
});

22
spec/postal.core.html Normal file
View file

@ -0,0 +1,22 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script type="text/javascript" src="../lib/jquery-1.5.2.js"></script>
<script type="text/javascript" src="../lib/qunit.js"></script>
<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/misc.js"></script>
<script type="text/javascript" src="../src/Bus.js"></script>
<script type="text/javascript" src="../src/Postal.js"></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>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
</body>
</html>

View file

@ -0,0 +1,24 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script type="text/javascript" src="../lib/jquery-1.5.2.js"></script>
<script type="text/javascript" src="../lib/qunit.js"></script>
<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/misc.js"></script>
<script type="text/javascript" src="../src/Bus.js"></script>
<script type="text/javascript" src="../src/Postal.js"></script>
<script type="text/javascript" src="../src/MessageCaptor.js"></script>
<script type="text/javascript" src="broker.withCapture.spec.js"></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>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
</body>
</html>

View file

@ -0,0 +1,26 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script type="text/javascript" src="../lib/jquery-1.5.2.js"></script>
<script type="text/javascript" src="../lib/qunit.js"></script>
<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/misc.js"></script>
<script type="text/javascript" src="../src/Bus.js"></script>
<script type="text/javascript" src="../src/Postal.js"></script>
<script type="text/javascript" src="../src/replay/ReplayContext.js"></script>
<script type="text/javascript" src="../src/MessageCaptor.js"></script>
<script type="text/javascript" src="broker.withCapture.spec.js"></script>
<script type="text/javascript" src="broker.withReplay.spec.js"></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>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
</body>
</html>

View file

@ -0,0 +1,24 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script type="text/javascript" src="../lib/jquery-1.5.2.js"></script>
<script type="text/javascript" src="../lib/qunit.js"></script>
<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/misc.js"></script>
<script type="text/javascript" src="../src/Bus.js"></script>
<script type="text/javascript" src="../src/Postal.js"></script>
<script type="text/javascript" src="../src/replay/ReplayContext.js"></script>
<script type="text/javascript" src="broker.withReplay.spec.js"></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>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
</body>
</html>

View file

@ -1,26 +1,19 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script type="text/javascript" src="../lib/jquery-1.5.2.js"></script>
<script type="text/javascript" src="../lib/qunit.js"></script>
<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/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/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" />
<script type="text/javascript">
var core = window.open("postal.core.html","core");
core.focus();
var wCapture = window.open("postal.withCapture.html","wCapture");
wCapture.focus();
var wReplay = window.open("postal.withReplay.html","wReplay");
wReplay.focus();
var wCaptureReplay = window.open("postal.withCapture_Replay.html","wCaptureReplay");
wCaptureReplay.focus();
</script>
</head>
<body>
<h1 id="qunit-header"></h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
</body>
</html>

73
src/Bus.js Normal file
View file

@ -0,0 +1,73 @@
var Bus = 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);
this.context = undefined;
this.cache = {};
this.wireTaps = [];
this.subscriptions = {};
this.subscriptions[DEFAULT_EXCHANGE] = {};
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.apply(sub.context, [data]);
sub.onFired();
}
});
}
});
};
this.mode = NORMAL_MODE;
this[NORMAL_MODE] = {
setup: function() {
this.mode = NORMAL_MODE;
this.context = undefined;
},
teardown: function() {
// no-op
}
};
this.init = function() {
this[NORMAL_MODE]();
var systemEx = this.subscriptions[SYSTEM_EXCHANGE] || {};
this.subscriptions = {};
this.subscriptions[DEFAULT_EXCHANGE] = {};
this.subscriptions[SYSTEM_EXCHANGE] = systemEx;
this.cache = {};
this.wireTaps = [];
};
};
var bus = new Bus();

8
src/Diagnostics.js Normal file
View file

@ -0,0 +1,8 @@
postal.addWireTap(function(data) {
try {
console.log(JSON.stringify(data || {}));
}
catch(exception) {
console.log("(Unable to show JSON data)");
}
});

View file

@ -1,32 +1,74 @@
var MessageCaptor = function(plugUp, unPlug) {
var MessageCaptor = function(bus) {
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);
}.bind(this),
_remoteConfigured = false;
plugUp(_grabMsg);
this.plugUp = function() {
bus.wireTaps.push(_grabMsg);
};
this.unPlug = function(callback) {
var idx = bus.wireTaps.indexOf(callback);
if(idx !== -1) {
bus.wireTaps.splice(idx,1);
}
};
this.messages = [];
this.save = function(batchId, description) {
unPlug(_grabMsg);
var captureStore = amplify.store(POSTAL_MSG_STORE_KEY);
if(!captureStore) {
captureStore = {};
this.save = function(location, batchId, description) {
this.unPlug(_grabMsg);
var batch = {
batchId: batchId,
description: description,
messages: this.messages
};
if(location === 'remote') {
amplify.request("saveRemoteCapture", batch, function(data) {
postal.publish(SYSTEM_EXCHANGE, "replay.store.refreshRemote");
});
}
else {
var captureStore = amplify.store(POSTAL_MSG_STORE_KEY);
if(!captureStore) {
captureStore = {};
}
if(!captureStore[window.location.pathname]) {
captureStore[window.location.pathname] = {};
}
captureStore[window.location.pathname][batchId] = batch;
amplify.store(POSTAL_MSG_STORE_KEY, captureStore);
postal.publish(SYSTEM_EXCHANGE, "replay.store.refreshLocal");
}
captureStore[batchId] = {
batchId: batchId,
description: description,
messages: this.messages
};
amplify.store(POSTAL_MSG_STORE_KEY, captureStore);
};
this.plugUp();
postal.subscribe(SYSTEM_EXCHANGE, "captor.save", function(data) {
this.save(data.batchId || new Date().toString(),
this.save(data.location || "local",
data.batchId || new Date().toString(),
data.description || "Captured Message Batch");
}.bind(this));
postal.subscribe(SYSTEM_EXCHANGE, "captor.remote.config", function(data) {
if(amplify) {
amplify.request.define("saveRemoteCapture", "ajax", {
"url": data.url,
"dataType": "json",
"type": data.method,
"contentType" : "application/json"
});
_remoteConfigured = true;
}
else {
throw "Amplify.js is required in order to save captured batches to a remote location."
}
});
};
// Adding replay functionality to the bus.....
postal.addBusBehavior(CAPTURE_MODE, function(bus) { return new MessageCaptor(bus); });

View file

@ -1,52 +1,6 @@
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] = {};
this.getMode = function() { return bus.mode; };
/*
options object has the following optional members:
@ -62,10 +16,10 @@ var Postal = function() {
_topicList, // we allow multiple topics to be subscribed in one call.,
_once = false,
_subData = {
callback: function() { /* placeholder noop */ },
callback: function() { /* placeholder no-op */ },
priority: 50,
context: null,
onFired: function() { /* noop */ }
onFired: function() { /* placeholder no-op */ }
},
_idx,
_found;
@ -103,26 +57,26 @@ var Postal = function() {
}.bind(this);
}
if(!this.subscriptions[_exchange]) {
this.subscriptions[_exchange] = {};
if(!bus.subscriptions[_exchange]) {
bus.subscriptions[_exchange] = {};
}
_topicList.forEach(function(tpc) {
if(!this.subscriptions[_exchange][tpc]) {
this.subscriptions[_exchange][tpc] = [_subData];
if(!bus.subscriptions[_exchange][tpc]) {
bus.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) {
_idx = bus.subscriptions[_exchange][tpc].length - 1;
if(bus.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);
if(bus.subscriptions[_exchange][tpc][_idx].priority <= _subData.priority) {
bus.subscriptions[_exchange][tpc].splice(_idx + 1, 0, _subData);
_found = true;
break;
}
}
if(!_found) {
this.subscriptions[_exchange][tpc].unshift(_subData);
bus.subscriptions[_exchange][tpc].unshift(_subData);
}
}
}
@ -152,12 +106,12 @@ var Postal = function() {
}
_topicList.forEach(function(tpc) {
if(this.subscriptions[_exchange][tpc]) {
var _len = this.subscriptions[_exchange][tpc].length,
if(bus.subscriptions[_exchange][tpc]) {
var _len = bus.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 );
if (bus.subscriptions[_exchange][tpc][_idx].callback === callback) {
bus.subscriptions[_exchange][tpc].splice( _idx, 1 );
break;
}
}
@ -190,41 +144,50 @@ var Postal = function() {
_topicList = topic.split(/\s/);
_data = data || {};
}
if(_mode !== REPLAY_MODE || (_mode === REPLAY_MODE && _exchange === SYSTEM_EXCHANGE)) {
if(bus.mode !== REPLAY_MODE || (bus.mode === REPLAY_MODE && _exchange === SYSTEM_EXCHANGE)) {
_topicList.forEach(function(tpc){
_publish(_exchange, tpc, _data);
bus.publish(_exchange, tpc, _data);
});
}
};
this.reset = function() {
bus.init();
};
this.addBusBehavior = function(behaviorName, setup, teardown) {
if(!bus[behaviorName]) {
bus[behaviorName] = {};
}
bus[behaviorName].setup = function() {
bus.mode = behaviorName;
setup(bus);
};
if(teardown) {
bus[behaviorName].teardown = function() { teardown(bus); }
}
else {
bus[behaviorName].teardown = function() { /* no-op */ }
}
};
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;
}
if(data.mode && bus[data.mode]) {
bus[bus.mode].teardown();
bus[data.mode].setup(data);
}
}.bind(this));
this.addWireTap = function(callback) {
bus.wireTaps.push(callback);
return function() {
var idx = bus.wireTaps.indexOf(callback);
if(idx !== -1) {
bus.wireTaps.splice(idx,1);
}
};
};
};
var postal = new Postal();

View file

@ -1,65 +0,0 @@
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;
});
};

153
src/replay/ReplayContext.js Normal file
View file

@ -0,0 +1,153 @@
var _forEachKeyValue = function(object, callback) {
for(var x in object) {
if(object.hasOwnProperty(x)) {
callback(x, object[x]);
}
}
},
_subscriptions = [];
var ReplayContext = function (bus) {
var _batch,
_continue = true,
_loadMessages = function(batchId) {
var msgStore = amplify.store(postal.POSTAL_MSG_STORE_KEY),
targetBatch;
if(msgStore[window.location.pathname] && msgStore[window.location.pathname][batchId]) {
targetBatch = msgStore[window.location.pathname][batchId];
targetBatch.messages.forEach(function(msg) {
msg.timeStamp = new Date(msg.timeStamp);
});
_batch = msgStore[window.location.pathname][batchId];
postal.publish(postal.SYSTEM_EXCHANGE, "replay.store.batchLoaded", { batchId: batchId,
description: targetBatch.description,
msgCount: targetBatch.messages.length });
}
},
_batchListCache = [],
_remoteConfigured = false,
_replayImmediate = function() {
if(_batch) {
while(_batch.messages.length > 0) {
if(_continue) {
_advanceNext();
}
else {
break;
}
}
}
},
_advanceNext = function() {
if(_batch && _batch.messages.length > 0) {
var msg = _batch.messages.shift();
bus.publish(msg.exchange, msg.topic, msg.data);
}
},
_replayRealTime = function() {
if(_continue && _batch && _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();
}
}
};
this.init = function() {
_subscriptions.push(postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.immediate", function() {
_continue = true;
_replayImmediate();
}));
_subscriptions.push(postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.advanceNext", function() {
_continue = true;
_advanceNext();
}));
_subscriptions.push(postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.realTime", function() {
_continue = true;
_replayRealTime();
}));
_subscriptions.push(postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.stop", function() {
_continue = false;
}));
_subscriptions.push(postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.store.loadBatch", function(data) {
if(data.batchId) {
_continue = false;
_loadMessages(data.batchId);
}
}));
_subscriptions.push(postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.store.refreshLocal", function(data){
var local = amplify.store(postal.POSTAL_MSG_STORE_KEY) || {},
batches = [];
if(local && local[window.location.pathname]) {
_forEachKeyValue(local[window.location.pathname], function(k, v) {
batches.push({ batchId: v.batchId,
description: v.description,
messageCount: v.messages.length,
source: "local"
});
});
}
_batchListCache = _batchListCache.filter(function(x) { return x.source !== "local"; })
.concat(batches);
postal.publish(postal.SYSTEM_EXCHANGE, "replay.store.batchList", _batchListCache);
}));
_subscriptions.push(postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.store.refreshRemote", function(data) {
if(_remoteConfigured) {
amplify.request("getRemoteCaptures", function(data) {
if(data.batches) {
_batchListCache = _batchListCache.filter(function(x) { return x.source !== 'remote'; });
_batchListCache = _batchListCache.concat(data.batches.map(function(x) {
x.source = "remote";
return x;
}));
postal.publish(postal.SYSTEM_EXCHANGE, "replay.store.batchList", _batchListCache);
}
});
}
}));
_subscriptions.push(postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.store.remote.config", function(data) {
if(amplify) {
amplify.request.define("getRemoteCaptures", "ajax", {
"url": data.url,
"dataType": "json",
"type": data.method,
"contentType" : "application/json"
});
_remoteConfigured = true;
}
else {
throw "Amplify.js is required in order to access remote captured batches."
}
}));
postal.publish(postal.SYSTEM_EXCHANGE, "replay.store.refreshLocal");
postal.publish(postal.SYSTEM_EXCHANGE, "replay.store.refreshRemote");
};
this.init();
};
// Adding replay functionality to the bus.....
postal.addBusBehavior(postal.REPLAY_MODE,
function(bus) {
postal.replay.render();
return new ReplayContext(bus);
},
function(bus) {
postal.replay.hide();
_subscriptions.forEach(function(remove) { remove(); });
});

74
src/replay/ReplayPanel.js Normal file
View file

@ -0,0 +1,74 @@
var ReplayPanel = function() {
var _rendered = false,
_style = '.postal-replay-wrapper { font-family: Tahoma, Arial; font-size: 10pt; float: left; vertical-align: middle; margin: 0px; padding: 0px; position: fixed; left: 0px; top: 0px; width: 100%; background-color: steelblue; color: white; } .postal-replay-title { float: left; margin-top: 4px; margin-left: 5px; margin-right: 15px; font-weight: bold; font-size: 11pt; height: 100%; } .postal-replay-button { float: left; } .postal-replay-dropdown { width: 150px; } .postal-replay-load { float: right; } .postal-replay-load select { float: left; margin-right: 10px; } #currentBatch { margin-top: 4px; margin-left: 20px; font-size: 10pt; font-weight: bold; float: left; } .postal-replay-exit { margin-left:35px; }',
_html = '<div class="postal-replay-title">Postal Replay</div> <input class="postal-replay-button" type="button" id="btnRealTime" value="Play" alt="Real Time Playback" onclick="postal.replay.replayRealTime()"> <input class="postal-replay-button" type="button" id="btnStop" value="Stop" onclick="postal.replay.replayStop()"> <input class="postal-replay-button" type="button" id="btnAdvance" value="Step" alt="Advances to Next Msg (manual progression)" onclick="postal.replay.replayAdvance()"> <input class="postal-replay-button" type="button" id="btnImmediate" value="Immediate" alt="Replays all Messages Immediately" onclick="postal.replay.replayImmediate()"> <div id="currentBatch"></div> <div class="postal-replay-load"> <select class="postal-replay-dropdown" id="drpBatches"></select> <input class="postal-replay-button" type="button" id="btnLoad" value="Load Batch" onclick="postal.replay.loadBatch()"> <input class="postal-replay-button postal-replay-exit" type="button" id="btnExitReplay" value="Exit Replay Mode" onclick="postal.replay.exitReplay()"></div>';
this.exitReplay = function() {
postal.publish(postal.SYSTEM_EXCHANGE, "mode.set", { mode: postal.NORMAL_MODE });
};
this.replayRealTime = function() {
postal.publish(postal.SYSTEM_EXCHANGE, "replay.realTime");
};
this.replayImmediate = function() {
postal.publish(postal.SYSTEM_EXCHANGE, "replay.immediate");
};
this.replayAdvance = function() {
postal.publish(postal.SYSTEM_EXCHANGE, "replay.advanceNext");
};
this.replayStop = function() {
postal.publish(postal.SYSTEM_EXCHANGE, "replay.stop");
};
this.loadBatch = function() {
var batchId = document.getElementById("drpBatches").value;
postal.publish(postal.SYSTEM_EXCHANGE, "replay.store.loadBatch", { batchId: batchId });
};
postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.store.batchLoaded", function(data) {
var text = "Replaying: " + data.batchId + " (" + data.description + ") " + data.msgCount + " message(s)";
document.getElementById("currentBatch").innerText = text;
});
postal.subscribe(postal.SYSTEM_EXCHANGE, "replay.store.batchList", function(data) {
var dropDown = document.getElementById("drpBatches"),
optElem;
dropDown.options.remove();
if(data) {
data.forEach(function(item) {
optElem = document.createElement("option");
optElem.value = item.batchId;
optElem.text = item.batchId
dropDown.options.add(optElem);
});
}
});
this.render = function() {
if(!_rendered){
var style = document.createElement("style");
style.innerText = _style;
document.getElementsByTagName("head")[0].appendChild(style);
var wrapper = document.createElement("div");
wrapper.setAttribute("class", "postal-replay-wrapper");
wrapper.setAttribute("id", "replay-wrapper");
wrapper.innerHTML = _html;
document.body.appendChild(wrapper);
_rendered = true;
}
else {
document.getElementById("replay-wrapper").hidden = false;
}
};
this.hide = function() {
document.getElementById("replay-wrapper").hidden = true;
};
};
postal.replay = new ReplayPanel();

13
src/replay/panel.html Normal file
View file

@ -0,0 +1,13 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title></title>
<script type="text/javascript" src="../../build/output/browser/postal.js"></script>
<script type="text/javascript" src="../Diagnostics.js"></script>
<script type="text/javascript" src="ReplayPanel.js"></script>
</head>
<body onload="postal.replay.render();">
<span>Test</span>
</body>
</html>