add intercom.js v0.1.4

This commit is contained in:
Peter Dave Hello
2014-03-05 17:07:50 +08:00
parent 70fb63fe15
commit d7d94216bc
3 changed files with 449 additions and 0 deletions
+413
View File
@@ -0,0 +1,413 @@
/*! intercom.js | https://github.com/diy/intercom.js | Apache License (v2) */
var Intercom = (function() {
// --- lib/events.js ---
var EventEmitter = function() {};
EventEmitter.createInterface = function(space) {
var methods = {};
methods.on = function(name, fn) {
if (typeof this[space] === 'undefined') {
this[space] = {};
}
if (!this[space].hasOwnProperty(name)) {
this[space][name] = [];
}
this[space][name].push(fn);
};
methods.off = function(name, fn) {
if (typeof this[space] === 'undefined') return;
if (this[space].hasOwnProperty(name)) {
util.removeItem(fn, this[space][name]);
}
};
methods.trigger = function(name) {
if (typeof this[space] !== 'undefined' && this[space].hasOwnProperty(name)) {
var args = Array.prototype.slice.call(arguments, 1);
for (var i = 0; i < this[space][name].length; i++) {
this[space][name][i].apply(this[space][name][i], args);
}
}
};
return methods;
};
var pvt = EventEmitter.createInterface('_handlers');
EventEmitter.prototype._on = pvt.on;
EventEmitter.prototype._off = pvt.off;
EventEmitter.prototype._trigger = pvt.trigger;
var pub = EventEmitter.createInterface('handlers');
EventEmitter.prototype.on = function() {
pub.on.apply(this, arguments);
Array.prototype.unshift.call(arguments, 'on');
this._trigger.apply(this, arguments);
};
EventEmitter.prototype.off = pub.off;
EventEmitter.prototype.trigger = pub.trigger;
// --- lib/localstorage.js ---
var localStorage = window.localStorage;
if (typeof localStorage === 'undefined') {
localStorage = {
getItem : function() {},
setItem : function() {},
removeItem : function() {}
};
}
// --- lib/util.js ---
var util = {};
util.guid = (function() {
var S4 = function() {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return function() {
return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
};
})();
util.throttle = function(delay, fn) {
var last = 0;
return function() {
var now = (new Date()).getTime();
if (now - last > delay) {
last = now;
fn.apply(this, arguments);
}
};
};
util.extend = function(a, b) {
if (typeof a === 'undefined' || !a) { a = {}; }
if (typeof b === 'object') {
for (var key in b) {
if (b.hasOwnProperty(key)) {
a[key] = b[key];
}
}
}
return a;
};
util.removeItem = function(item, array) {
for (var i = array.length - 1; i >= 0; i--) {
if (array[i] === item) {
array.splice(i, 1);
}
}
return array;
};
// --- lib/intercom.js ---
/**
* A cross-window broadcast service built on top
* of the HTML5 localStorage API. The interface
* mimic socket.io in design.
*
* @author Brian Reavis <brian@thirdroute.com>
* @constructor
*/
var Intercom = function() {
var self = this;
var now = (new Date()).getTime();
this.origin = util.guid();
this.lastMessage = now;
this.bindings = [];
this.receivedIDs = {};
this.previousValues = {};
var storageHandler = function() { self._onStorageEvent.apply(self, arguments); };
if (window.attachEvent) { document.attachEvent('onstorage', storageHandler); }
else { window.addEventListener('storage', storageHandler, false); };
};
Intercom.prototype._transaction = function(fn) {
var TIMEOUT = 1000;
var WAIT = 20;
var self = this;
var executed = false;
var listening = false;
var waitTimer = null;
var lock = function() {
if (executed) return;
var now = (new Date()).getTime();
var activeLock = parseInt(localStorage.getItem(INDEX_LOCK) || 0);
if (activeLock && now - activeLock < TIMEOUT) {
if (!listening) {
self._on('storage', lock);
listening = true;
}
waitTimer = window.setTimeout(lock, WAIT);
return;
}
executed = true;
localStorage.setItem(INDEX_LOCK, now);
fn();
unlock();
};
var unlock = function() {
if (listening) { self._off('storage', lock); }
if (waitTimer) { window.clearTimeout(waitTimer); }
localStorage.removeItem(INDEX_LOCK);
};
lock();
};
Intercom.prototype._cleanup_emit = util.throttle(100, function() {
var self = this;
this._transaction(function() {
var now = (new Date()).getTime();
var threshold = now - THRESHOLD_TTL_EMIT;
var changed = 0;
var messages = JSON.parse(localStorage.getItem(INDEX_EMIT) || '[]');
for (var i = messages.length - 1; i >= 0; i--) {
if (messages[i].timestamp < threshold) {
messages.splice(i, 1);
changed++;
}
}
if (changed > 0) {
localStorage.setItem(INDEX_EMIT, JSON.stringify(messages));
}
});
});
Intercom.prototype._cleanup_once = util.throttle(100, function() {
var self = this;
this._transaction(function() {
var timestamp, ttl, key;
var table = JSON.parse(localStorage.getItem(INDEX_ONCE) || '{}');
var now = (new Date()).getTime();
var changed = 0;
for (key in table) {
if (self._once_expired(key, table)) {
delete table[key];
changed++;
}
}
if (changed > 0) {
localStorage.setItem(INDEX_ONCE, JSON.stringify(table));
}
});
});
Intercom.prototype._once_expired = function(key, table) {
if (!table) return true;
if (!table.hasOwnProperty(key)) return true;
if (typeof table[key] !== 'object') return true;
var ttl = table[key].ttl || THRESHOLD_TTL_ONCE;
var now = (new Date()).getTime();
var timestamp = table[key].timestamp;
return timestamp < now - ttl;
};
Intercom.prototype._localStorageChanged = function(event, field) {
if (event && event.key) {
return event.key === field;
}
var currentValue = localStorage.getItem(field);
if (currentValue === this.previousValues[field]) {
return false;
}
this.previousValues[field] = currentValue;
return true;
};
Intercom.prototype._onStorageEvent = function(event) {
event = event || window.event;
var self = this;
if (this._localStorageChanged(event, INDEX_EMIT)) {
this._transaction(function() {
var now = (new Date()).getTime();
var data = localStorage.getItem(INDEX_EMIT);
var messages = JSON.parse(data || '[]');
for (var i = 0; i < messages.length; i++) {
if (messages[i].origin === self.origin) continue;
if (messages[i].timestamp < self.lastMessage) continue;
if (messages[i].id) {
if (self.receivedIDs.hasOwnProperty(messages[i].id)) continue;
self.receivedIDs[messages[i].id] = true;
}
self.trigger(messages[i].name, messages[i].payload);
}
self.lastMessage = now;
});
}
this._trigger('storage', event);
};
Intercom.prototype._emit = function(name, message, id) {
id = (typeof id === 'string' || typeof id === 'number') ? String(id) : null;
if (id && id.length) {
if (this.receivedIDs.hasOwnProperty(id)) return;
this.receivedIDs[id] = true;
}
var packet = {
id : id,
name : name,
origin : this.origin,
timestamp : (new Date()).getTime(),
payload : message
};
var self = this;
this._transaction(function() {
var data = localStorage.getItem(INDEX_EMIT) || '[]';
var delimiter = (data === '[]') ? '' : ',';
data = [data.substring(0, data.length - 1), delimiter, JSON.stringify(packet), ']'].join('');
localStorage.setItem(INDEX_EMIT, data);
self.trigger(name, message);
window.setTimeout(function() { self._cleanup_emit(); }, 50);
});
};
Intercom.prototype.bind = function(object, options) {
for (var i = 0; i < Intercom.bindings.length; i++) {
var binding = Intercom.bindings[i].factory(object, options || null, this);
if (binding) { this.bindings.push(binding); }
}
};
Intercom.prototype.emit = function(name, message) {
this._emit.apply(this, arguments);
this._trigger('emit', name, message);
};
Intercom.prototype.once = function(key, fn, ttl) {
if (!Intercom.supported) return;
var self = this;
this._transaction(function() {
var data = JSON.parse(localStorage.getItem(INDEX_ONCE) || '{}');
if (!self._once_expired(key, data)) return;
data[key] = {};
data[key].timestamp = (new Date()).getTime();
if (typeof ttl === 'number') {
data[key].ttl = ttl * 1000;
}
localStorage.setItem(INDEX_ONCE, JSON.stringify(data));
fn();
window.setTimeout(function() { self._cleanup_once(); }, 50);
});
};
util.extend(Intercom.prototype, EventEmitter.prototype);
Intercom.bindings = [];
Intercom.supported = (typeof localStorage !== 'undefined');
var INDEX_EMIT = 'intercom';
var INDEX_ONCE = 'intercom_once';
var INDEX_LOCK = 'intercom_lock';
var THRESHOLD_TTL_EMIT = 50000;
var THRESHOLD_TTL_ONCE = 1000 * 3600;
Intercom.destroy = function() {
localStorage.removeItem(INDEX_LOCK);
localStorage.removeItem(INDEX_EMIT);
localStorage.removeItem(INDEX_ONCE);
};
Intercom.getInstance = (function() {
var intercom = null;
return function() {
if (!intercom) {
intercom = new Intercom();
}
return intercom;
};
})();
// --- lib/bindings/socket.js ---
/**
* Socket.io binding for intercom.js.
*
* - When a message is received on the socket, it's emitted on intercom.
* - When a message is emitted via intercom, it's sent over the socket.
*
* @author Brian Reavis <brian@thirdroute.com>
*/
var SocketBinding = function(socket, options, intercom) {
options = util.extend({
id : null,
send : true,
receive : true
}, options);
if (options.receive) {
var watchedEvents = [];
var onEventAdded = function(name, fn) {
if (watchedEvents.indexOf(name) === -1) {
watchedEvents.push(name);
socket.on(name, function(data) {
var id = (typeof options.id === 'function') ? options.id(name, data) : null;
var emit = (typeof options.receive === 'function') ? options.receive(name, data) : true;
if (emit || typeof emit !== 'boolean') {
intercom._emit(name, data, id);
}
});
}
};
for (var name in intercom.handlers) {
for (var i = 0; i < intercom.handlers[name].length; i++) {
onEventAdded(name, intercom.handlers[name][i]);
}
}
intercom._on('on', onEventAdded);
}
if (options.send) {
intercom._on('emit', function(name, message) {
var emit = (typeof options.send === 'function') ? options.send(name, message) : true;
if (emit || typeof emit !== 'boolean') {
socket.emit(name, message);
}
});
}
};
SocketBinding.factory = function(object, options, intercom) {
if (typeof object.socket === 'undefined') { return false };
return new SocketBinding(object, options, intercom);
};
Intercom.bindings.push(SocketBinding);
return Intercom;
})();
+12
View File
@@ -0,0 +1,12 @@
/*! intercom.js | https://github.com/diy/intercom.js | Apache License (v2) */
var Intercom=function(){var g=function(){};g.createInterface=function(b){return{on:function(a,c){"undefined"===typeof this[b]&&(this[b]={});this[b].hasOwnProperty(a)||(this[b][a]=[]);this[b][a].push(c)},off:function(a,c){"undefined"!==typeof this[b]&&this[b].hasOwnProperty(a)&&i.removeItem(c,this[b][a])},trigger:function(a){if("undefined"!==typeof this[b]&&this[b].hasOwnProperty(a))for(var c=Array.prototype.slice.call(arguments,1),e=0;e<this[b][a].length;e++)this[b][a][e].apply(this[b][a][e],c)}}};
var m=g.createInterface("_handlers");g.prototype._on=m.on;g.prototype._off=m.off;g.prototype._trigger=m.trigger;var n=g.createInterface("handlers");g.prototype.on=function(){n.on.apply(this,arguments);Array.prototype.unshift.call(arguments,"on");this._trigger.apply(this,arguments)};g.prototype.off=n.off;g.prototype.trigger=n.trigger;var f=window.localStorage;"undefined"===typeof f&&(f={getItem:function(){},setItem:function(){},removeItem:function(){}});var i={},h=function(){return(65536*(1+Math.random())|
0).toString(16).substring(1)};i.guid=function(){return h()+h()+"-"+h()+"-"+h()+"-"+h()+"-"+h()+h()+h()};i.throttle=function(b,a){var c=0;return function(){var e=(new Date).getTime();e-c>b&&(c=e,a.apply(this,arguments))}};i.extend=function(b,a){if("undefined"===typeof b||!b)b={};if("object"===typeof a)for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b};i.removeItem=function(b,a){for(var c=a.length-1;0<=c;c--)a[c]===b&&a.splice(c,1);return a};var d=function(){var b=this,a=(new Date).getTime();
this.origin=i.guid();this.lastMessage=a;this.bindings=[];this.receivedIDs={};this.previousValues={};a=function(){b._onStorageEvent.apply(b,arguments)};window.attachEvent?document.attachEvent("onstorage",a):window.addEventListener("storage",a,!1)};d.prototype._transaction=function(b){var a=this,c=!1,e=!1,p=null,d=function(){if(!c){var g=(new Date).getTime(),s=parseInt(f.getItem(l)||0);s&&1E3>g-s?(e||(a._on("storage",d),e=!0),p=window.setTimeout(d,20)):(c=!0,f.setItem(l,g),b(),e&&a._off("storage",d),
p&&window.clearTimeout(p),f.removeItem(l))}};d()};d.prototype._cleanup_emit=i.throttle(100,function(){this._transaction(function(){for(var b=(new Date).getTime()-t,a=0,c=JSON.parse(f.getItem(j)||"[]"),e=c.length-1;0<=e;e--)c[e].timestamp<b&&(c.splice(e,1),a++);0<a&&f.setItem(j,JSON.stringify(c))})});d.prototype._cleanup_once=i.throttle(100,function(){var b=this;this._transaction(function(){var a,c=JSON.parse(f.getItem(k)||"{}");(new Date).getTime();var e=0;for(a in c)b._once_expired(a,c)&&(delete c[a],
e++);0<e&&f.setItem(k,JSON.stringify(c))})});d.prototype._once_expired=function(b,a){if(!a||!a.hasOwnProperty(b)||"object"!==typeof a[b])return!0;var c=a[b].ttl||u,e=(new Date).getTime();return a[b].timestamp<e-c};d.prototype._localStorageChanged=function(b,a){if(b&&b.key)return b.key===a;var c=f.getItem(a);if(c===this.previousValues[a])return!1;this.previousValues[a]=c;return!0};d.prototype._onStorageEvent=function(b){var b=b||window.event,a=this;this._localStorageChanged(b,j)&&this._transaction(function(){for(var b=
(new Date).getTime(),e=f.getItem(j),e=JSON.parse(e||"[]"),d=0;d<e.length;d++)if(e[d].origin!==a.origin&&!(e[d].timestamp<a.lastMessage)){if(e[d].id){if(a.receivedIDs.hasOwnProperty(e[d].id))continue;a.receivedIDs[e[d].id]=!0}a.trigger(e[d].name,e[d].payload)}a.lastMessage=b});this._trigger("storage",b)};d.prototype._emit=function(b,a,c){if((c="string"===typeof c||"number"===typeof c?String(c):null)&&c.length){if(this.receivedIDs.hasOwnProperty(c))return;this.receivedIDs[c]=!0}var e={id:c,name:b,origin:this.origin,
timestamp:(new Date).getTime(),payload:a},d=this;this._transaction(function(){var c=f.getItem(j)||"[]",c=[c.substring(0,c.length-1),"[]"===c?"":",",JSON.stringify(e),"]"].join("");f.setItem(j,c);d.trigger(b,a);window.setTimeout(function(){d._cleanup_emit()},50)})};d.prototype.bind=function(b,a){for(var c=0;c<d.bindings.length;c++){var e=d.bindings[c].factory(b,a||null,this);e&&this.bindings.push(e)}};d.prototype.emit=function(b,a){this._emit.apply(this,arguments);this._trigger("emit",b,a)};d.prototype.once=
function(b,a,c){if(d.supported){var e=this;this._transaction(function(){var d=JSON.parse(f.getItem(k)||"{}");e._once_expired(b,d)&&(d[b]={},d[b].timestamp=(new Date).getTime(),"number"===typeof c&&(d[b].ttl=1E3*c),f.setItem(k,JSON.stringify(d)),a(),window.setTimeout(function(){e._cleanup_once()},50))})}};i.extend(d.prototype,g.prototype);d.bindings=[];d.supported="undefined"!==typeof f;var j="intercom",k="intercom_once",l="intercom_lock",t=5E4,u=36E5;d.destroy=function(){f.removeItem(l);f.removeItem(j);
f.removeItem(k)};var q=null;d.getInstance=function(){q||(q=new d);return q};var r=function(b,a,c){a=i.extend({id:null,send:!0,receive:!0},a);if(a.receive){var d=[],f=function(f){-1===d.indexOf(f)&&(d.push(f),b.on(f,function(b){var d="function"===typeof a.id?a.id(f,b):null,e="function"===typeof a.receive?a.receive(f,b):!0;(e||"boolean"!==typeof e)&&c._emit(f,b,d)}))},g;for(g in c.handlers)for(var h=0;h<c.handlers[g].length;h++)f(g,c.handlers[g][h]);c._on("on",f)}a.send&&c._on("emit",function(c,d){var e=
"function"===typeof a.send?a.send(c,d):!0;(e||"boolean"!==typeof e)&&b.emit(c,d)})};r.factory=function(b,a,c){return"undefined"===typeof b.socket?!1:new r(b,a,c)};d.bindings.push(r);return d}();
+24
View File
@@ -0,0 +1,24 @@
{
"name": "intercom.js",
"title": "Intercom",
"filename": "intercom.min.js",
"version": "0.1.4",
"description": "A client-side cross-window message broadcast library built on top of the HTML5 localStorage API.",
"homepage": "https://github.com/diy/intercom.js",
"author": {
"name": "Brian Reavis",
"email": "brian@thirdroute.com",
"url": "http://thirdroute.com"
},
"repositories": [{
"type": "git",
"url": "https://github.com/diy/intercom.js.git"
}],
"licenses": [
{
"type": "Apache",
"url": "http://www.apache.org/licenses/LICENSE-2.0"
}
],
"keywords": ["intercom", "js"]
}