vis-postmonger
Version:
An ultra-lightweight Javascript utility for simplified cross-domain messaging.
332 lines (273 loc) • 10.3 kB
JavaScript
/*
* Postmonger.js version 0.0.13
* https://github.com/kevinparkerson/postmonger
*
* Copyright (c) 2012-2014 Kevin Parkerson
* Available via the MIT or new BSD license.
* Further details and documentation:
* http://kevinparkerson.github.com/postmonger/
*
*///
(function(root, factory){
if(typeof define === 'function' && define.amd) {
define('postmonger', [], function(){ return factory(root); });
}else {
root.Postmonger = factory(root);
}
}(this, function(root){
root = root || window;
var exports = exports || undefined;
var Postmonger;
var previous = root.Postmonger;
var _window = (root.addEventListener || root.attachEvent) ? root : window;
var Connection, Events, Session;
//Set up Postmonger namespace, provide noConflict support, and version
if (typeof(exports) !== 'undefined') {
Postmonger = exports;
} else {
Postmonger = {};
}
Postmonger.noConflict = function(){
root.Postmonger = previous;
return this;
};
Postmonger.version = '0.0.13';
//Create a new Postmonger Connection
Connection = Postmonger.Connection = function(options){
options = (typeof(options)==='object') ? options : {};
var connect = options.connect || _window.parent;
var from = options.from || '*';
var to = options.to || '*';
var self = this;
//If string, grab based on id
if(typeof(connect)==='string'){
connect = document.getElementById(connect);
}
//If no connection, check for jquery object
if(connect && !connect.postMessage && connect.jquery){
connect = connect.get(0);
}
//If still no connection, check for iframe
if(connect && !connect.postMessage && (connect.contentWindow || connect.contentDocument)){
connect = connect.contentWindow || connect.contentDocument;
}
//Throw warning if connection could not be made
if(!(connect && connect.postMessage)){
if(_window.console && _window.console.warn){
_window.console.warn(' Warning: Postmonger could not establish connection with ', options.connect);
}
return false;
}
self.connect = connect;
self.to = to;
self.from = from;
return self;
};
//Postmonger.Events - Hacked together from Backbone.Events and two Underscore functions.
Events = Postmonger.Events = function(){
var eventSplitter = /\s+/;
var self = this;
self._callbacks = {};
self._has = function(obj, key){
return Object.prototype.hasOwnProperty.call(obj, key);
};
self._keys = function(obj){
if(Object.keys){
return Object.keys(obj);
}
if (typeof(obj)!=='object'){
throw new TypeError('Invalid object');
}
var keys = [];
for(var key in obj){
if (self._has(obj, key)) {
keys[keys.length] = key;
}
}
return keys;
};
self.on = function(events, callback, context){
var calls, event, node, tail, list;
if(!callback){
return self;
}
events = events.split(eventSplitter);
self._callbacks = self._callbacks || {};
calls = self._callbacks;
while (event = events.shift()) {
list = calls[event];
node = (list) ? list.tail : {};
tail = {};
node.next = tail;
node.context = context;
node.callback = callback;
calls[event] = {
tail: tail,
next: (list) ? list.next : node
};
}
return self;
};
self.off = function(events, callback, context){
var calls = self._callbacks;
var event, node, tail, cb, ctx;
if(!calls){
return;
}
if(!(events || callback || context)){
delete self._callbacks;
return self;
}
events = (events) ? events.split(eventSplitter) : self._keys(calls);
while (event = events.shift()) {
node = calls[event];
delete calls[event];
if (!node || !(callback || context)) {
continue;
}
tail = node.tail;
while ((node = node.next) !== tail) {
cb = node.callback;
ctx = node.context;
if (((callback && cb) !== callback) || ((context && ctx) !== context)) {
self.on(event, cb, ctx);
}
}
}
return self;
};
self.trigger = function(events){
var event, node, calls, tail, args, all, rest;
if (!(calls = self._callbacks)) {
return self;
}
all = calls.all;
events = events.split(eventSplitter);
rest = Array.prototype.slice.call(arguments, 1);
while (event = events.shift()) {
if (node = calls[event]) {
tail = node.tail;
while ((node = node.next) !== tail) {
node.callback.apply(node.context || self, rest);
}
}
if (node = all) {
tail = node.tail;
args = [event].concat(rest);
while ((node = node.next) !== tail) {
node.callback.apply(node.context || self, args);
}
}
}
return self;
};
return self;
};
//Create a new Postmonger Session
Session = Postmonger.Session = function(){
var args = (arguments.length>0) ? Array.prototype.slice.call(arguments, 0) : [{}];
var connections = [];
var incoming = new Events();
var outgoing = new Events();
var self = this;
var connection, i, j, l, ln, postMessageListener;
//Session API hooks
self.on = incoming.on;
self.off = incoming.off;
self.trigger = outgoing.trigger;
self.end = function(){
incoming.off();
outgoing.off();
if(_window.removeEventListener){
_window.removeEventListener('message', postMessageListener, false);
}else if(_window.detachEvent){
_window.detachEvent('onmessage', postMessageListener);
}
return self;
};
//Establishing connections
for(i=0, l=args.length; i<l; i++){
connection = new Connection(args[i]);
if(connection){
for(j=0, ln=connections.length; j<ln; j++){
if(
connections[j].connect===connection.connect &&
connections[j].from===connection.from &&
connections[j].to===connection.to
){
connection = null;
break;
}
}
if(connection){
connections.push(connection);
}
}
}
//Listener for incoming messages
postMessageListener = function(event){
var conn = null;
var message = [];
var data;
var k, len;
//Attempt to find the connection we're dealing with
for(k=0, len=connections.length; k<len; k++){
if(connections[k].connect===event.source){
conn = connections[k];
break;
}
}
//Check if we've found the connection
if(!conn){
return false;
}
//Check if the message is from the expected origin
if(conn.from!=='*' && conn.from!==event.origin){
return false;
}
//Check the data that's been passed
try{
data = JSON.parse(event.data);
if(!data.e){
return false;
}
}catch(e){
return false;
}
//Format the passed in data
message.push(data.e);
delete data.e;
for(k in data){
message.push(data[k]);
}
//Send the message
incoming['trigger'].apply(root, message);
};
//Add the listener
if(_window.addEventListener){
_window.addEventListener('message', postMessageListener, false);
}else if(_window.attachEvent){
_window.attachEvent('onmessage', postMessageListener);
}else{
if(_window.console && _window.console.warn){
_window.console.warn('WARNING: Postmonger could not listen for messages on window %o', _window);
}
return false;
}
//Sending outgoing messages
outgoing.on('all', function(){
var args = Array.prototype.slice.call(arguments, 0);
var message = {};
var k, len;
message.e = args[0];
for(k=1, len=args.length; k<len; k++){
message['a' + k] = args[k];
}
for(k=0, len=connections.length; k<len; k++){
connections[k].connect.postMessage(JSON.stringify(message), connections[k].to);
}
});
return self;
};
return Postmonger;
}));