Add intercom.js functionality for inter-window event broadcast
This commit is contained in:
parent
bb9c34e5b3
commit
799a7ae4b1
|
@ -0,0 +1,66 @@
|
|||
define(function(require) {
|
||||
|
||||
// Based on https://github.com/diy/intercom.js/blob/master/lib/events.js
|
||||
// Copyright 2012 DIY Co Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
function removeItem(item, array) {
|
||||
for (var i = array.length - 1; i >= 0; i--) {
|
||||
if (array[i] === item) {
|
||||
array.splice(i, 1);
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
function EventEmitter() {}
|
||||
|
||||
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)) {
|
||||
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;
|
||||
|
||||
return EventEmitter;
|
||||
|
||||
});
|
|
@ -0,0 +1,314 @@
|
|||
define(function(require) {
|
||||
|
||||
// Based on https://github.com/diy/intercom.js/blob/master/lib/intercom.js
|
||||
// Copyright 2012 DIY Co Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
var EventEmitter = require('eventemitter');
|
||||
var guid = require('src/shared').guid;
|
||||
|
||||
function throttle(delay, fn) {
|
||||
var last = 0;
|
||||
return function() {
|
||||
var now = Date.now();
|
||||
if (now - last > delay) {
|
||||
last = now;
|
||||
fn.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function extend(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;
|
||||
}
|
||||
|
||||
var localStorage = (function(window) {
|
||||
if (typeof window.localStorage === 'undefined') {
|
||||
return {
|
||||
getItem : function() {},
|
||||
setItem : function() {},
|
||||
removeItem : function() {}
|
||||
};
|
||||
}
|
||||
return window.localStorage;
|
||||
}(this));
|
||||
|
||||
function Intercom() {
|
||||
var self = this;
|
||||
var now = Date.now();
|
||||
|
||||
this.origin = guid();
|
||||
this.lastMessage = now;
|
||||
this.receivedIDs = {};
|
||||
this.previousValues = {};
|
||||
|
||||
var storageHandler = function() {
|
||||
self._onStorageEvent.apply(self, arguments);
|
||||
};
|
||||
if (document.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;
|
||||
|
||||
function lock() {
|
||||
if (executed) {
|
||||
return;
|
||||
}
|
||||
|
||||
var now = Date.now();
|
||||
var activeLock = 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();
|
||||
}
|
||||
|
||||
function unlock() {
|
||||
if (listening) {
|
||||
self._off('storage', lock);
|
||||
}
|
||||
if (waitTimer) {
|
||||
window.clearTimeout(waitTimer);
|
||||
}
|
||||
localStorage.removeItem(INDEX_LOCK);
|
||||
}
|
||||
|
||||
lock();
|
||||
};
|
||||
|
||||
Intercom.prototype._cleanup_emit = throttle(100, function() {
|
||||
var self = this;
|
||||
|
||||
self._transaction(function() {
|
||||
var now = Date.now();
|
||||
var threshold = now - THRESHOLD_TTL_EMIT;
|
||||
var changed = 0;
|
||||
var messages;
|
||||
|
||||
try {
|
||||
messages = JSON.parse(localStorage.getItem(INDEX_EMIT) || '[]');
|
||||
} catch(e) {
|
||||
messages = [];
|
||||
}
|
||||
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 = throttle(100, function() {
|
||||
var self = this;
|
||||
|
||||
self._transaction(function() {
|
||||
var timestamp, ttl, key;
|
||||
var table;
|
||||
var now = Date.now();
|
||||
var changed = 0;
|
||||
|
||||
try {
|
||||
table = JSON.parse(localStorage.getItem(INDEX_ONCE) || '{}');
|
||||
} catch(e) {
|
||||
table = {};
|
||||
}
|
||||
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 = Date.now();
|
||||
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 = Date.now();
|
||||
var data = localStorage.getItem(INDEX_EMIT);
|
||||
var messages;
|
||||
|
||||
try {
|
||||
messages = JSON.parse(data || '[]');
|
||||
} catch(e) {
|
||||
messages = [];
|
||||
}
|
||||
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 : Date.now(),
|
||||
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.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;
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(INDEX_ONCE) || '{}');
|
||||
} catch(e) {
|
||||
data = {};
|
||||
}
|
||||
if (!self._once_expired(key, data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
data[key] = {};
|
||||
data[key].timestamp = Date.now();
|
||||
if (typeof ttl === 'number') {
|
||||
data[key].ttl = ttl * 1000;
|
||||
}
|
||||
|
||||
localStorage.setItem(INDEX_ONCE, JSON.stringify(data));
|
||||
fn();
|
||||
|
||||
window.setTimeout(function() {
|
||||
self._cleanup_once();
|
||||
}, 50);
|
||||
});
|
||||
};
|
||||
|
||||
extend(Intercom.prototype, EventEmitter.prototype);
|
||||
|
||||
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;
|
||||
return function() {
|
||||
if (!intercom) {
|
||||
intercom = new Intercom();
|
||||
}
|
||||
return intercom;
|
||||
};
|
||||
})();
|
||||
|
||||
return Intercom;
|
||||
});
|
18
src/fs.js
18
src/fs.js
|
@ -58,6 +58,7 @@ define(function(require) {
|
|||
var providers = require('src/providers/providers');
|
||||
var adapters = require('src/adapters/adapters');
|
||||
var Shell = require('src/shell');
|
||||
var Intercom = require('intercom');
|
||||
|
||||
/*
|
||||
* DirectoryEntry
|
||||
|
@ -164,10 +165,16 @@ define(function(require) {
|
|||
update = true;
|
||||
}
|
||||
|
||||
function complete(error) {
|
||||
// Broadcast this change to all fs instances, in all windows on this origin
|
||||
context.intercom.emit('change', path);
|
||||
callback(error);
|
||||
}
|
||||
|
||||
if(update) {
|
||||
context.put(node.id, node, callback);
|
||||
context.put(node.id, node, complete);
|
||||
} else {
|
||||
callback();
|
||||
complete();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1624,16 +1631,21 @@ define(function(require) {
|
|||
// Open file system storage provider
|
||||
provider.open(function(err, needsFormatting) {
|
||||
function complete(error) {
|
||||
// Wrap the provider so we can extend the context with fs flags.
|
||||
// FileSystem watch events are broadcast between windows via intercom
|
||||
var intercom = Intercom.getInstance();
|
||||
|
||||
// Wrap the provider so we can extend the context with fs flags, intercom.
|
||||
// From this point forward we won't call open again, so drop it.
|
||||
fs.provider = {
|
||||
getReadWriteContext: function() {
|
||||
var context = provider.getReadWriteContext();
|
||||
context.flags = flags;
|
||||
context.intercom = intercom;
|
||||
return context;
|
||||
},
|
||||
getReadOnlyContext: function() {
|
||||
var context = provider.getReadOnlyContext();
|
||||
context.intercom = intercom;
|
||||
context.flags = flags;
|
||||
return context;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue