Skip to content

Instantly share code, notes, and snippets.

@Athorcis
Created September 29, 2015 11:44
Show Gist options
  • Save Athorcis/812be3c1a253a65390ee to your computer and use it in GitHub Desktop.
Save Athorcis/812be3c1a253a65390ee to your computer and use it in GitHub Desktop.
/*jslint browser: true */
/*global jQuery */
var Bridge = (function (window, jQuery) {
"use strict";
var Bridge;
Bridge = {
callbackId: 0,
callbacks: []
};
function postMessage(targetWindow, object, targetOrigin) {
if (targetOrigin === 'null') {
targetOrigin = '*';
}
targetWindow.postMessage(JSON.stringify(object), targetOrigin);
}
function Plugin() {
this.events = {};
}
Plugin.prototype = {
callbackContext: null,
events: null,
addEventListener: function (type) {
if (this.events[type]) {
this.events[type].addEventListener(this.callbackContext);
} else {
this.callbackContext.error('Bridge.INVALID_EVENT');
}
},
removeEventListener: function (type, callbackId) {
if (this.events[type]) {
this.events[type].removeEventListener(type, callbackId);
} else {
this.callbackContext.error('Bridge.INVALID_EVENT');
}
},
execute: function (action, args, callbackContext) {
var result;
this.callbackContext = callbackContext;
if (jQuery.isFunction(this[action]) && action !== 'execute') {
result = this[action].apply(this, args);
} else {
callbackContext.error('Bridge.INVALID_ACTION');
}
this.callbackContext = null;
return result;
}
};
function PluginResult(status, message) {
this.status = status;
this.message = message;
}
PluginResult.Status = {
NO_RESULT: 0,
OK: 1,
ERROR: 2
};
PluginResult.prototype = {
status: 0,
message: undefined,
keepCallback: false,
setKeepCallback: function (b) {
this.keepCallback = b;
},
getStatus: function () {
return this.status;
},
getKeepCallback: function () {
return this.keepCallback;
}
};
function PluginEvent() {
this.callbackContexts = [];
}
PluginEvent.prototype = {
counter: 0,
callbackContexts: null,
addEventListener: function (callbackContext) {
this.callbackContexts[callbackContext.getCallbackId()] = callbackContext;
++this.counter;
if (this.counter === 1) {
this.start();
}
},
removeEventListener: function (callbackId) {
if (this.callbackContexts[callbackId]) {
this.callbackContexts[callbackId].sendPluginResult(new PluginResult(PluginResult.Status.NO_RESULT));
delete this.callbackContexts[callbackId];
--this.counter;
if (!this.counter) {
this.stop();
}
}
},
start: function () {},
stop: function () {},
fireEvent: function (message) {
var result, callbackId,
callbackContexts = this.callbackContexts;
result = new PluginResult(PluginResult.Status.OK, message);
result.setKeepCallback(true);
for (callbackId in callbackContexts) {
if (callbackContexts.hasOwnProperty(callbackId)) {
callbackContexts[callbackId].sendPluginResult(result);
}
}
}
};
function addEvent(plugin, type, prototype) {
var eventClass = function () {};
eventClass.prototype = jQuery.extend(new PluginEvent(), prototype);
plugin.events[type] = new eventClass(); // jshint ignore: line
}
function CallbackContext(callbackId, targetWindow, targetOrigin) {
this.callbackId = callbackId;
this.targetWindow = targetWindow;
this.targetOrigin = targetOrigin;
}
CallbackContext.prototype = {
callbackId: '',
targetWindow: null,
targetOrigin: '',
finished: false,
isFinished: function () {
return this.finished;
},
getCallbackId: function () {
return this.callbackId;
},
sendPluginResult: function (pluginResult) {
if (this.finished) {
return;
}
this.finished = !pluginResult.getKeepCallback();
var object = {
callbackId: this.callbackId,
keepCallback: !this.finished,
status: pluginResult.status,
message: pluginResult.message
};
postMessage(this.targetWindow, object, this.targetOrigin);
},
success: function (message) {
this.sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
},
error: function (message) {
this.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message));
}
};
function exec(targetWindow, targetOrigin, success, fail, service, action, args) {
var callbackId = service + Bridge.callbackId++;
if (success || fail) {
Bridge.callbacks[callbackId] = {success: success, fail: fail};
}
postMessage(targetWindow, {callbackId: callbackId, service: service, action: action, args: Array.prototype.slice.call(args, 0)}, targetOrigin);
return callbackId;
}
jQuery(window).on('message', function (jqEvent) {
var event, object, callbackContext, service, result, callbackId, callback;
event = jqEvent.originalEvent;
try {
object = jQuery.parseJSON(event.data);
} catch (exception) {
console.error('Bridge.INVALID_MESSAGE');
}
if (object.service) {
callbackContext = new CallbackContext(object.callbackId, event.source, event.origin);
service = Bridge.services[object.service];
if (service) {
try {
result = service.execute(object.action, object.args, callbackContext);
} catch (exception) {
callbackContext.error('Bridge.ACTION_ERROR: ' + exception.message);
}
if (result !== undefined) {
callbackContext.success(result);
}
} else {
callbackContext.error('Bridge.INVALID_SERVICE');
}
} else {
callbackId = object.callbackId;
if (Bridge.callbacks[callbackId]) {
callback = Bridge.callbacks[callbackId];
if (object.status === PluginResult.Status.OK) {
if (jQuery.isFunction(callback.success)) {
try {
callback.success(object.message);
} catch (exception) {
console.error('Bridge.SUCCESS_CALLBACK_ERROR: ' + exception.message);
}
}
} else if (object.status === PluginResult.Status.ERROR) {
if (jQuery.isFunction(callback.error)) {
try {
callback.fail(object.message);
} catch (exception) {
console.error('Bridge.FAIL_CALLBACK_ERROR: ' + exception.message);
}
}
}
if (!object.keepCallback) {
delete Bridge.callbacks[callbackId];
}
}
}
});
function Iframe(options) {
this.$element = options.$element || jQuery('<iframe src="bridge.html"></iframe>');
this.element = this.$element.get(0);
this.execQueue = [];
if (options) {
if (options.updateHeight) {
this.addEventListener('heightUpdate', jQuery.proxy(this.onHeightUpdate, this));
}
if (options.mirror) {
var title = document.title,
js = 'document.title = \'' + title + '\';';
if (jQuery.isFunction(history.pushState)) {
js += 'history.pushState(\'\', \'' + title + '\', \'' + location.href + '\');';
} else {
js += 'location.replace(\'' + location.hash + '\');';
}
this.eval(js);
}
}
if (!options.loaded) {
this.$element.load(jQuery.proxy(this.onLoad, this));
} else {
this.loaded = true;
}
}
Iframe.prototype = {
$element: null,
element: null,
execQueue: null,
loaded: false,
exec: function (service, action, args, success, fail) {
if (this.loaded) {
exec(this.element.contentWindow, '*', success, fail, service, action, args);
} else {
this.execQueue.push(arguments);
}
},
addEventListener: function (type, listener) {
return this.exec('core', 'addEventListener', [type], listener);
},
removeEventListener: function (type, callbackId) {
this.exec('core', 'removeEventListener', [type, callbackId]);
},
addStyle: function (css) {
this.exec('core', 'addStyle', [css]);
},
append: function (html) {
this.exec('core', 'append', [html]);
},
eval: function (source, success, fail) {
if (jQuery.isFunction(source)) {
source = '(' + source.toString() + '())';
}
this.exec('core', 'eval', [source], success, fail);
},
onLoad: function () {
var i,
queue = this.execQueue;
this.loaded = true;
for (i = queue.length - 1; i >= 0; --i) {
this.exec.apply(this, queue[i]);
}
},
onHeightUpdate: function (height) {
this.$element.height(height);
}
};
function PluginCore() {
addEvent(this, 'heightUpdate', {
intervalId: -1,
previousHeight: 0,
start: function () {
this.intervalId = setInterval(jQuery.proxy(this.intervalCallback, this), 500);
},
intervalCallback: function () {
var height = jQuery('body').height();
if (height !== this.previousHeight) {
this.previousHeight = height;
this.fireEvent(height);
}
},
stop: function () {
clearInterval(this.intervalId);
}
});
}
PluginCore.prototype = jQuery.extend(new Plugin(), {
addStyle: function (css) {
jQuery('head').append('<style type="text/css">' + css + '</style>');
},
append: function (html) {
jQuery('body').append(html);
},
eval: function (source) {
return eval(source); // jshint ignore: line
}
});
return jQuery.extend(Bridge, {
services: {
core: new PluginCore()
},
exec: exec,
Iframe: Iframe
});
}(this, jQuery));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment