Comparing version 0.2.5 to 0.2.6
{ | ||
"name": "crosstab", | ||
"version": "v0.2.5", | ||
"version": "v0.2.6", | ||
"homepage": "https://github.com/tejacques/crosstab", | ||
@@ -5,0 +5,0 @@ "authors": [ |
{ | ||
"name": "crosstab", | ||
"version": "0.2.5", | ||
"version": "0.2.6", | ||
"description": "A utility library for cross-tab communication using localStorage.", | ||
@@ -5,0 +5,0 @@ "author": "Tom Jacques <tejacques@gmail.com>", |
@@ -57,3 +57,3 @@ # crosstab [![NPM version][npm-img]][npm-url] [![Build Status][travis-img]][travis-url] | ||
crosstab registers event handlers like this: | ||
crosstab registers and unregisters event handlers like this: | ||
@@ -70,8 +70,8 @@ ```JavaScript | ||
var message = { | ||
id: util.generateId(), // The unique ID of this message | ||
event: event, // The name of the event | ||
data: data, // The data to pass | ||
destination: destination, // The destination tab | ||
origin: crosstab.id, // The origin tab | ||
timestamp: util.now() // The time the message was created | ||
id: string, // The unique ID of this message | ||
event: string, // The name of the event | ||
data: any, // The data passed | ||
destination: string, // The destination tab | ||
origin: string, // The origin tab | ||
timestamp: number // The time the message was created | ||
}; | ||
@@ -86,13 +86,59 @@ ``` | ||
I wanted to be able to have robust cross tab communication for the purpose of resource sharing (such as websockets). Though there are some libraries which have a similar goal, they all had subtle issues. This library aims to be the most correct it can be for supported browsers. This library was created with inspiration from the excellent [intercom.js](https://github.com/diy/intercom.js/) library, and addresses several of it's shortcomings: | ||
I wanted to be able to have robust cross tab communication for the purpose of resource sharing (such as websockets). Though there are some libraries which have a similar goal, they all had subtle issues. This library aims to be the most correct it can be for supported browsers. This library was created with inspiration from the [intercom.js](https://github.com/diy/intercom.js/) library, and addresses several of it's shortcomings by dropping support for IE8 and using a lockless system that is entirely event driven. IE8 can still be used with crosstab by using the [tejacques/IE8-EventListener](https://github.com/tejacques/IE8-EventListener) EventListener polyfill | ||
* intercom.js doesn't implement proper locking. | ||
* does not guarantee that one tab holds the lock at a time (in fact this is impossible to guarantee flat out, but can be guaranteed within defined execution times). | ||
* locking on functions that throw will break. | ||
* Updates to any localStorage item will cause the locks to attempt to be acquired instead of only removals of the lock. | ||
* in trying to support IE8 message broadcasting in intercom.js has a race condition where messages can be dropped. | ||
* intercom.js leaks memory by maintaining a state of every message id received (also in an attempt to support IE8) | ||
Contributing | ||
------------ | ||
crosstab solves these issues by dropping support for IE8 and using a lockless system that is entirely event driven (IE8 cannot pass messages via localStorage events, which is why intercom.js requires locking, because it supports IE8). | ||
Contributions are welcome and encouraged, you can contribute in several different ways, by filing issues, commenting on discussions, or contributing code. | ||
### Filing issues ### | ||
Please use the issue tracker for discussions and bug reports. For bug reports, please include as much detail as possible. | ||
These will help determine/resolve your issue: | ||
* Clear description of the problem or unexpexted behavior | ||
* Clear description of the expected result or output | ||
* Steps to reproduce | ||
* Steps you have taken to debug it yourself | ||
* Minimal reproducible example with self contained and runnable JS code | ||
### Contributing Code ### | ||
#### Project Workflow #### | ||
We use roughly the [Github Workflow](https://guides.github.com/introduction/flow/). You should: | ||
* Create an issue for the feature/bug | ||
* Fork the project | ||
* Create a new branch | ||
* Commit changes | ||
* Submit a pull request to the master branch | ||
#### Tests #### | ||
Tests can be run with the following command: | ||
```.sh | ||
grunt test | ||
``` | ||
* Tests must pass | ||
* Tests should be added for each bug/feature that is added | ||
* All tests should be self-contained | ||
* If test is determined to be too difficult to create for an issue, there does not need to be a test for it | ||
#### Getting Started #### | ||
```.sh | ||
git clone https://github.com/tejacques/crosstab | ||
cd crosstab | ||
npm install | ||
grunt | ||
``` | ||
You can now access: | ||
* Examples: [http://localhost:9000](http://localhost:9000) | ||
* Tests: [http://localhost:9000/test/mocha_test.html](http://localhost:9000/test/mocha_test.html) | ||
[downloads-img]: https://img.shields.io/npm/dm/crosstab.svg | ||
@@ -99,0 +145,0 @@ [npm-url]: https://npmjs.org/package/crosstab |
/*! | ||
* crosstab JavaScript Library v0.2.5 | ||
* crosstab JavaScript Library v0.2.6 | ||
* https://github.com/tejacques/crosstab | ||
@@ -79,6 +79,20 @@ * | ||
util.forEachObj = function (thing, fn) { | ||
for (var key in thing) { | ||
if (thing.hasOwnProperty(key)) { | ||
fn.call(thing, thing[key], key); | ||
var toString = Object.prototype.toString; | ||
util.isArray = Array.isArray || function (arr) { | ||
return arr instanceof Array; | ||
}; | ||
util.isNumber = function (num) { | ||
return typeof num === 'number'; | ||
}; | ||
util.isFunction = function (fn) { | ||
return typeof fn === 'function'; | ||
}; | ||
util.forEachObj = function (obj, fn) { | ||
for (var key in obj) { | ||
if (obj.hasOwnProperty(key)) { | ||
fn.call(obj, obj[key], key); | ||
} | ||
@@ -88,5 +102,6 @@ } | ||
util.forEachArr = function (thing, fn) { | ||
for (var i = 0; i < thing.length; i++) { | ||
fn.call(thing, thing[i], i); | ||
util.forEachArr = function (arr, fn) { | ||
var len = arr.length | ||
for (var i = 0; i < len; i++) { | ||
fn.call(arr, arr[i], i, arr); | ||
} | ||
@@ -96,3 +111,3 @@ }; | ||
util.forEach = function (thing, fn) { | ||
if (Object.prototype.toString.call(thing) === '[object Array]') { | ||
if (util.isArray(thing)) { | ||
util.forEachArr(thing, fn); | ||
@@ -114,3 +129,3 @@ } else { | ||
util.filter = function (thing, fn) { | ||
var isArr = Object.prototype.toString.call(thing) === '[object Array]'; | ||
var isArr = util.isArray(thing); | ||
var res = isArr ? [] : {}; | ||
@@ -149,19 +164,67 @@ | ||
// --- Events --- | ||
// node.js style events, with the main difference being object based | ||
// rather than array based, as well as being able to add/remove | ||
// events by key. | ||
// node.js style events, with the main difference being able | ||
// to add/remove events by key. | ||
util.createEventHandler = function () { | ||
var events = {}; | ||
var subscribeKeyToListener = {}; | ||
var findHandlerByKey = function(event, key) { | ||
var handler; | ||
if (subscribeKeyToListener[event]) { | ||
handler = subscribeKeyToListener[event][key]; | ||
} | ||
return handler; | ||
}; | ||
var findHandlerIndex = function (event, listener) { | ||
var listenerIndex = -1; | ||
var eventList = events[event]; | ||
if (eventList && listener) { | ||
var len = eventList.length || 0; | ||
for(var i = 0; i < len; i++) { | ||
if (eventList[i] === listener) { | ||
listenerIndex = i; | ||
break; | ||
} | ||
} | ||
} | ||
return listenerIndex; | ||
}; | ||
var addListener = function (event, listener, key) { | ||
key = key || listener; | ||
var handlers = listeners(event); | ||
handlers[key] = listener; | ||
return key; | ||
var storedHandler = findHandlerByKey(event, key); | ||
var listenerIndex; | ||
if (storedHandler === undefined) { | ||
listenerIndex = handlers.length; | ||
handlers[listenerIndex] = listener; | ||
if (!subscribeKeyToListener[event]) { | ||
(subscribeKeyToListener[event] = {}); | ||
} | ||
if (key) { | ||
subscribeKeyToListener[event][key] = listener; | ||
} | ||
} else { | ||
listenerIndex = findHandlerIndex(event, storedHandler); | ||
handlers[listenerIndex] = listener; | ||
} | ||
return key || listener; | ||
}; | ||
var removeListener = function (event, key) { | ||
if (events[event] && events[event][key]) { | ||
delete events[event][key]; | ||
var handler = util.isFunction(key) | ||
? key | ||
: findHandlerByKey(event, key); | ||
var listenerIndex = findHandlerIndex(event, handler); | ||
if (listenerIndex === -1) return false; | ||
if (events[event] && events[event][listenerIndex]) { | ||
events[event].splice(listenerIndex, 1); | ||
delete subscribeKeyToListener[event][key]; | ||
return true; | ||
@@ -173,9 +236,18 @@ } | ||
var removeAllListeners = function (event) { | ||
var successful = false; | ||
if (event) { | ||
if (events[event]) { | ||
delete events[event]; | ||
successful = true; | ||
} | ||
if (subscribeKeyToListener[event]) { | ||
delete subscribeKeyToListener[event]; | ||
successful = successful && true; | ||
} | ||
} else { | ||
events = {}; | ||
subscribeKeyToListener = {}; | ||
successful = true; | ||
} | ||
return successful; | ||
}; | ||
@@ -188,3 +260,3 @@ | ||
util.forEach(handlers, function (listener) { | ||
if (typeof (listener) === 'function') { | ||
if (util.isFunction(listener)) { | ||
listener.apply(this, args); | ||
@@ -197,4 +269,3 @@ } | ||
// Generate a unique id for this listener | ||
var handlers = listeners(event); | ||
while (!key || handlers[key]) { | ||
while (!key || (findHandlerByKey(event, key) !== undefined)) { | ||
key = util.generateId(); | ||
@@ -213,10 +284,24 @@ } | ||
var listeners = function (event) { | ||
var handlers = events[event] = events[event] || {}; | ||
var handlers = events[event] = events[event] || []; | ||
return handlers; | ||
}; | ||
var destructor = function() { | ||
removeAllListeners(); | ||
}; | ||
return { | ||
addListener: addListener, | ||
destructor: destructor, | ||
on: addListener, | ||
off: removeListener, | ||
off: function(event, key) { | ||
var argsLen = arguments.length; | ||
if (!argsLen) { | ||
return removeAllListeners(); | ||
} else if (argsLen === 1) { | ||
return removeAllListeners(event); | ||
} else { | ||
return removeListener(event, key); | ||
} | ||
}, | ||
once: once, | ||
@@ -243,3 +328,4 @@ emit: emit, | ||
removeListener: eventHandler.removeListener, | ||
removeAllListeners: eventHandler.removeAllListeners | ||
removeAllListeners: eventHandler.removeAllListeners, | ||
destructor: eventHandler.destructor | ||
}; | ||
@@ -304,3 +390,4 @@ | ||
function beforeUnload() { | ||
function unload() { | ||
crosstab.stopKeepalive = true; | ||
var numTabs = 0; | ||
@@ -319,2 +406,4 @@ util.forEach(util.tabs, function (tab, key) { | ||
} | ||
util.events.destructor(); | ||
} | ||
@@ -483,2 +572,3 @@ | ||
crosstab.once = util.events.once; | ||
crosstab.off = util.events.off; | ||
@@ -620,3 +710,3 @@ // --- Crosstab supported --- | ||
window.addEventListener('storage', onStorageEvent, false); | ||
window.addEventListener('beforeunload', beforeUnload, false); | ||
window.addEventListener('unload', unload, false); | ||
@@ -623,0 +713,0 @@ util.events.on('PING', function (message) { |
@@ -80,3 +80,3 @@ // =========== Iframe Tools ============== | ||
it ('should be a function', function () { | ||
it('should be a function', function () { | ||
expect(window.crosstab).to.be.a('function'); | ||
@@ -102,2 +102,86 @@ }); | ||
it('should invoke event listeners in the order they added', function () { | ||
var msg = "TestMessage"; | ||
var order = []; | ||
// http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop | ||
crosstab.on('test', function(message) { | ||
expect((message || {}).data).to.be(msg); | ||
order.push(2); | ||
}, 'second'); | ||
crosstab.on('test', function(message) { | ||
order.push(3); | ||
}, '3'); | ||
crosstab.on('test', function(message) { | ||
order.push(1); | ||
}, 'first'); | ||
crosstab.on('test', function(message) { | ||
expect((message || {}).data).to.be(msg); | ||
order.push(101); | ||
}); | ||
crosstab.on('test', function(message) { | ||
order.push(102); | ||
}); | ||
crosstab.broadcast('test', msg); | ||
expect(order).to.eql([2, 3, 1, 101, 102]); | ||
}); | ||
it('should not invoke event listener after unsubscribing', function() { | ||
var msg = "test"; | ||
var received; | ||
var offSuccessful; | ||
var listeners = crosstab.util.events.listeners; | ||
// ------------------------------- | ||
// unsubscribe with event + key | ||
// ------------------------------- | ||
var evt1 = 'test-1'; | ||
crosstab.on(evt1, function(message) { | ||
received = message.data + '-1'; | ||
}, 'first'); | ||
crosstab.broadcast(evt1, msg); | ||
expect(received).to.eql(msg + '-1'); | ||
// unsubscribe with event + non-exist key | ||
received = undefined; | ||
offSuccessful = crosstab.off(evt1, 'non-exist'); | ||
expect(offSuccessful).to.eql(false); | ||
crosstab.broadcast(evt1, msg); | ||
expect(received).to.eql(msg + '-1'); | ||
// unsubscribe with event + key | ||
var listenersLen = listeners(evt1).length; | ||
received = undefined; | ||
offSuccessful = crosstab.off(evt1, 'first'); | ||
expect(offSuccessful).to.eql(true); | ||
crosstab.broadcast(evt1, msg); | ||
expect(received).to.be(undefined); | ||
expect(listeners(evt1).length).to.be(listenersLen-1); | ||
// ------------------------------- | ||
// unsubscribe with event | ||
// ------------------------------- | ||
var evt2 = 'test-2'; | ||
crosstab.on(evt2, function(message) { | ||
received = message.data + '-1'; | ||
}, 'first'); | ||
crosstab.broadcast(evt2, msg); | ||
expect(received).to.eql(msg + '-1'); | ||
// unsubcribe with non-exist event | ||
received = undefined; | ||
offSuccessful = crosstab.off('non-exist'); | ||
expect(offSuccessful).to.eql(false); | ||
crosstab.broadcast(evt2, msg); | ||
expect(received).to.eql(msg + '-1'); | ||
// unscribe with event | ||
received = undefined; | ||
offSuccessful = crosstab.off(evt2); | ||
expect(offSuccessful).to.eql(true); | ||
crosstab.broadcast(evt2, msg); | ||
expect(received).to.be(undefined); | ||
}); | ||
it('should only trigger callback once for crosstab.once', function () { | ||
@@ -104,0 +188,0 @@ var msg = "TestOnce"; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
72157
1150
148