(function (window, document, location, setTimeout, decodeURIComponent, encodeURIComponent) { | |
/*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ | |
/*global JSON, XMLHttpRequest, window, escape, unescape, ActiveXObject */ | |
// | |
// easyXDM | |
// http://easyxdm.net/ | |
// Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
var global = this; | |
var channelId = Math.floor(Math.random() * 10000); // randomize the initial id in case of multiple closures loaded | |
var emptyFn = Function.prototype; | |
var reURI = /^((http.?:)\/\/([^:\/\s]+)(:\d+)*)/; // returns groups for protocol (2), domain (3) and port (4) | |
var reParent = /[\-\w]+\/\.\.\//; // matches a foo/../ expression | |
var reDoubleSlash = /([^:])\/\//g; // matches // anywhere but in the protocol | |
var namespace = ""; // stores namespace under which easyXDM object is stored on the page (empty if object is global) | |
var easyXDM = {}; | |
var _easyXDM = window.easyXDM; // map over global easyXDM in case of overwrite | |
var IFRAME_PREFIX = "easyXDM_"; | |
var HAS_NAME_PROPERTY_BUG; | |
var useHash = false; // whether to use the hash over the query | |
var flashVersion; // will be set if using flash | |
var HAS_FLASH_THROTTLED_BUG; | |
var _trace = emptyFn; | |
// http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting | |
function isHostMethod(object, property){ | |
var t = typeof object[property]; | |
return t == 'function' || | |
(!!(t == 'object' && object[property])) || | |
t == 'unknown'; | |
} | |
function isHostObject(object, property){ | |
return !!(typeof(object[property]) == 'object' && object[property]); | |
} | |
// end | |
// http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/ | |
function isArray(o){ | |
return Object.prototype.toString.call(o) === '[object Array]'; | |
} | |
// end | |
function hasFlash(){ | |
var name = "Shockwave Flash", mimeType = "application/x-shockwave-flash"; | |
if (!undef(navigator.plugins) && typeof navigator.plugins[name] == "object") { | |
// adapted from the swfobject code | |
var description = navigator.plugins[name].description; | |
if (description && !undef(navigator.mimeTypes) && navigator.mimeTypes[mimeType] && navigator.mimeTypes[mimeType].enabledPlugin) { | |
flashVersion = description.match(/\d+/g); | |
} | |
} | |
if (!flashVersion) { | |
var flash; | |
try { | |
flash = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); | |
flashVersion = Array.prototype.slice.call(flash.GetVariable("$version").match(/(\d+),(\d+),(\d+),(\d+)/), 1); | |
flash = null; | |
} | |
catch (notSupportedException) { | |
} | |
} | |
if (!flashVersion) { | |
return false; | |
} | |
var major = parseInt(flashVersion[0], 10), minor = parseInt(flashVersion[1], 10); | |
HAS_FLASH_THROTTLED_BUG = major > 9 && minor > 0; | |
return true; | |
} | |
/* | |
* Cross Browser implementation for adding and removing event listeners. | |
*/ | |
var on, un; | |
if (isHostMethod(window, "addEventListener")) { | |
on = function(target, type, listener){ | |
_trace("adding listener " + type); | |
target.addEventListener(type, listener, false); | |
}; | |
un = function(target, type, listener){ | |
_trace("removing listener " + type); | |
target.removeEventListener(type, listener, false); | |
}; | |
} | |
else if (isHostMethod(window, "attachEvent")) { | |
on = function(object, sEvent, fpNotify){ | |
_trace("adding listener " + sEvent); | |
object.attachEvent("on" + sEvent, fpNotify); | |
}; | |
un = function(object, sEvent, fpNotify){ | |
_trace("removing listener " + sEvent); | |
object.detachEvent("on" + sEvent, fpNotify); | |
}; | |
} | |
else { | |
throw new Error("Browser not supported"); | |
} | |
/* | |
* Cross Browser implementation of DOMContentLoaded. | |
*/ | |
var domIsReady = false, domReadyQueue = [], readyState; | |
if ("readyState" in document) { | |
// If browser is WebKit-powered, check for both 'loaded' (legacy browsers) and | |
// 'interactive' (HTML5 specs, recent WebKit builds) states. | |
// https://bugs.webkit.org/show_bug.cgi?id=45119 | |
readyState = document.readyState; | |
domIsReady = readyState == "complete" || (~ navigator.userAgent.indexOf('AppleWebKit/') && (readyState == "loaded" || readyState == "interactive")); | |
} | |
else { | |
// If readyState is not supported in the browser, then in order to be able to fire whenReady functions apropriately | |
// when added dynamically _after_ DOM load, we have to deduce wether the DOM is ready or not. | |
// We only need a body to add elements to, so the existence of document.body is enough for us. | |
domIsReady = !!document.body; | |
} | |
function dom_onReady(){ | |
if (domIsReady) { | |
return; | |
} | |
domIsReady = true; | |
_trace("firing dom_onReady"); | |
for (var i = 0; i < domReadyQueue.length; i++) { | |
domReadyQueue[i](); | |
} | |
domReadyQueue.length = 0; | |
} | |
if (!domIsReady) { | |
if (isHostMethod(window, "addEventListener")) { | |
on(document, "DOMContentLoaded", dom_onReady); | |
} | |
else { | |
on(document, "readystatechange", function(){ | |
if (document.readyState == "complete") { | |
dom_onReady(); | |
} | |
}); | |
if (document.documentElement.doScroll && window === top) { | |
var doScrollCheck = function(){ | |
if (domIsReady) { | |
return; | |
} | |
// http://javascript.nwbox.com/IEContentLoaded/ | |
try { | |
document.documentElement.doScroll("left"); | |
} | |
catch (e) { | |
setTimeout(doScrollCheck, 1); | |
return; | |
} | |
dom_onReady(); | |
}; | |
doScrollCheck(); | |
} | |
} | |
// A fallback to window.onload, that will always work | |
on(window, "load", dom_onReady); | |
} | |
/** | |
* This will add a function to the queue of functions to be run once the DOM reaches a ready state. | |
* If functions are added after this event then they will be executed immediately. | |
* @param {function} fn The function to add | |
* @param {Object} scope An optional scope for the function to be called with. | |
*/ | |
function whenReady(fn, scope){ | |
if (domIsReady) { | |
fn.call(scope); | |
return; | |
} | |
domReadyQueue.push(function(){ | |
fn.call(scope); | |
}); | |
} | |
/** | |
* Returns an instance of easyXDM from the parent window with | |
* respect to the namespace. | |
* | |
* @return An instance of easyXDM (in the parent window) | |
*/ | |
function getParentObject(){ | |
var obj = parent; | |
if (namespace !== "") { | |
for (var i = 0, ii = namespace.split("."); i < ii.length; i++) { | |
if (!obj) { | |
throw new Error(ii.slice(0, i + 1).join('.') + ' is not an object'); | |
} | |
obj = obj[ii[i]]; | |
} | |
} | |
if (!obj || !obj.easyXDM) { | |
throw new Error('Could not find easyXDM in parent.' + namespace); | |
} | |
return obj.easyXDM; | |
} | |
/** | |
* Removes easyXDM variable from the global scope. It also returns control | |
* of the easyXDM variable to whatever code used it before. | |
* | |
* @param {String} ns A string representation of an object that will hold | |
* an instance of easyXDM. | |
* @return An instance of easyXDM | |
*/ | |
function noConflict(ns){ | |
if (typeof ns != "string" || !ns) { | |
throw new Error('namespace must be a non-empty string'); | |
} | |
_trace("Settings namespace to '" + ns + "'"); | |
window.easyXDM = _easyXDM; | |
namespace = ns; | |
if (namespace) { | |
IFRAME_PREFIX = "easyXDM_" + namespace.replace(".", "_") + "_"; | |
} | |
return easyXDM; | |
} | |
/* | |
* Methods for working with URLs | |
*/ | |
/** | |
* Get the domain name from a url. | |
* @param {String} url The url to extract the domain from. | |
* @return The domain part of the url. | |
* @type {String} | |
*/ | |
function getDomainName(url){ | |
if (!url) { | |
throw new Error("url is undefined or empty"); | |
} | |
return url.match(reURI)[3]; | |
} | |
/** | |
* Get the port for a given URL, or "" if none | |
* @param {String} url The url to extract the port from. | |
* @return The port part of the url. | |
* @type {String} | |
*/ | |
function getPort(url){ | |
if (!url) { | |
throw new Error("url is undefined or empty"); | |
} | |
return url.match(reURI)[4] || ""; | |
} | |
/** | |
* Returns a string containing the schema, domain and if present the port | |
* @param {String} url The url to extract the location from | |
* @return {String} The location part of the url | |
*/ | |
function getLocation(url){ | |
if (!url) { | |
throw new Error("url is undefined or empty"); | |
} | |
if (/^file/.test(url)) { | |
throw new Error("The file:// protocol is not supported"); | |
} | |
var m = url.toLowerCase().match(reURI); | |
var proto = m[2], domain = m[3], port = m[4] || ""; | |
if ((proto == "http:" && port == ":80") || (proto == "https:" && port == ":443")) { | |
port = ""; | |
} | |
return proto + "//" + domain + port; | |
} | |
/** | |
* Resolves a relative url into an absolute one. | |
* @param {String} url The path to resolve. | |
* @return {String} The resolved url. | |
*/ | |
function resolveUrl(url){ | |
if (!url) { | |
throw new Error("url is undefined or empty"); | |
} | |
// replace all // except the one in proto with / | |
url = url.replace(reDoubleSlash, "$1/"); | |
// If the url is a valid url we do nothing | |
if (!url.match(/^(http||https):\/\//)) { | |
// If this is a relative path | |
var path = (url.substring(0, 1) === "/") ? "" : location.pathname; | |
if (path.substring(path.length - 1) !== "/") { | |
path = path.substring(0, path.lastIndexOf("/") + 1); | |
} | |
url = location.protocol + "//" + location.host + path + url; | |
} | |
// reduce all 'xyz/../' to just '' | |
while (reParent.test(url)) { | |
url = url.replace(reParent, ""); | |
} | |
_trace("resolved url '" + url + "'"); | |
return url; | |
} | |
/** | |
* Appends the parameters to the given url.<br/> | |
* The base url can contain existing query parameters. | |
* @param {String} url The base url. | |
* @param {Object} parameters The parameters to add. | |
* @return {String} A new valid url with the parameters appended. | |
*/ | |
function appendQueryParameters(url, parameters){ | |
if (!parameters) { | |
throw new Error("parameters is undefined or null"); | |
} | |
var hash = "", indexOf = url.indexOf("#"); | |
if (indexOf !== -1) { | |
hash = url.substring(indexOf); | |
url = url.substring(0, indexOf); | |
} | |
var q = []; | |
for (var key in parameters) { | |
if (parameters.hasOwnProperty(key)) { | |
q.push(key + "=" + encodeURIComponent(parameters[key])); | |
} | |
} | |
return url + (useHash ? "#" : (url.indexOf("?") == -1 ? "?" : "&")) + q.join("&") + hash; | |
} | |
// build the query object either from location.query, if it contains the xdm_e argument, or from location.hash | |
var query = (function(input){ | |
input = input.substring(1).split("&"); | |
var data = {}, pair, i = input.length; | |
while (i--) { | |
pair = input[i].split("="); | |
data[pair[0]] = decodeURIComponent(pair[1]); | |
} | |
return data; | |
}(/xdm_e=/.test(location.search) ? location.search : location.hash)); | |
/* | |
* Helper methods | |
*/ | |
/** | |
* Helper for checking if a variable/property is undefined | |
* @param {Object} v The variable to test | |
* @return {Boolean} True if the passed variable is undefined | |
*/ | |
function undef(v){ | |
return typeof v === "undefined"; | |
} | |
/** | |
* A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works. | |
* @return {JSON} A valid JSON conforming object, or null if not found. | |
*/ | |
var getJSON = function(){ | |
var cached = {}; | |
var obj = { | |
a: [1, 2, 3] | |
}, json = "{\"a\":[1,2,3]}"; | |
if (typeof JSON != "undefined" && typeof JSON.stringify === "function" && JSON.stringify(obj).replace((/\s/g), "") === json) { | |
// this is a working JSON instance | |
return JSON; | |
} | |
if (Object.toJSON) { | |
if (Object.toJSON(obj).replace((/\s/g), "") === json) { | |
// this is a working stringify method | |
cached.stringify = Object.toJSON; | |
} | |
} | |
if (typeof String.prototype.evalJSON === "function") { | |
obj = json.evalJSON(); | |
if (obj.a && obj.a.length === 3 && obj.a[2] === 3) { | |
// this is a working parse method | |
cached.parse = function(str){ | |
return str.evalJSON(); | |
}; | |
} | |
} | |
if (cached.stringify && cached.parse) { | |
// Only memoize the result if we have valid instance | |
getJSON = function(){ | |
return cached; | |
}; | |
return cached; | |
} | |
return null; | |
}; | |
/** | |
* Applies properties from the source object to the target object.<br/> | |
* @param {Object} target The target of the properties. | |
* @param {Object} source The source of the properties. | |
* @param {Boolean} noOverwrite Set to True to only set non-existing properties. | |
*/ | |
function apply(destination, source, noOverwrite){ | |
var member; | |
for (var prop in source) { | |
if (source.hasOwnProperty(prop)) { | |
if (prop in destination) { | |
member = source[prop]; | |
if (typeof member === "object") { | |
apply(destination[prop], member, noOverwrite); | |
} | |
else if (!noOverwrite) { | |
destination[prop] = source[prop]; | |
} | |
} | |
else { | |
destination[prop] = source[prop]; | |
} | |
} | |
} | |
return destination; | |
} | |
// This tests for the bug in IE where setting the [name] property using javascript causes the value to be redirected into [submitName]. | |
function testForNamePropertyBug(){ | |
var form = document.body.appendChild(document.createElement("form")), input = form.appendChild(document.createElement("input")); | |
input.name = IFRAME_PREFIX + "TEST" + channelId; // append channelId in order to avoid caching issues | |
HAS_NAME_PROPERTY_BUG = input !== form.elements[input.name]; | |
document.body.removeChild(form); | |
_trace("HAS_NAME_PROPERTY_BUG: " + HAS_NAME_PROPERTY_BUG); | |
} | |
/** | |
* Creates a frame and appends it to the DOM. | |
* @param config {object} This object can have the following properties | |
* <ul> | |
* <li> {object} prop The properties that should be set on the frame. This should include the 'src' property.</li> | |
* <li> {object} attr The attributes that should be set on the frame.</li> | |
* <li> {DOMElement} container Its parent element (Optional).</li> | |
* <li> {function} onLoad A method that should be called with the frames contentWindow as argument when the frame is fully loaded. (Optional)</li> | |
* </ul> | |
* @return The frames DOMElement | |
* @type DOMElement | |
*/ | |
function createFrame(config){ | |
_trace("creating frame: " + config.props.src); | |
if (undef(HAS_NAME_PROPERTY_BUG)) { | |
testForNamePropertyBug(); | |
} | |
var frame; | |
// This is to work around the problems in IE6/7 with setting the name property. | |
// Internally this is set as 'submitName' instead when using 'iframe.name = ...' | |
// This is not required by easyXDM itself, but is to facilitate other use cases | |
if (HAS_NAME_PROPERTY_BUG) { | |
frame = document.createElement("<iframe name=\"" + config.props.name + "\"/>"); | |
} | |
else { | |
frame = document.createElement("IFRAME"); | |
frame.name = config.props.name; | |
} | |
frame.id = frame.name = config.props.name; | |
delete config.props.name; | |
if (typeof config.container == "string") { | |
config.container = document.getElementById(config.container); | |
} | |
if (!config.container) { | |
// This needs to be hidden like this, simply setting display:none and the like will cause failures in some browsers. | |
apply(frame.style, { | |
position: "absolute", | |
top: "-2000px", | |
// Avoid potential horizontal scrollbar | |
left: "0px" | |
}); | |
config.container = document.body; | |
} | |
// HACK: IE cannot have the src attribute set when the frame is appended | |
// into the container, so we set it to "javascript:false" as a | |
// placeholder for now. If we left the src undefined, it would | |
// instead default to "about:blank", which causes SSL mixed-content | |
// warnings in IE6 when on an SSL parent page. | |
var src = config.props.src; | |
config.props.src = "javascript:false"; | |
// transfer properties to the frame | |
apply(frame, config.props); | |
frame.border = frame.frameBorder = 0; | |
frame.allowTransparency = true; | |
config.container.appendChild(frame); | |
if (config.onLoad) { | |
on(frame, "load", config.onLoad); | |
} | |
// set the frame URL to the proper value (we previously set it to | |
// "javascript:false" to work around the IE issue mentioned above) | |
if(config.usePost) { | |
var form = config.container.appendChild(document.createElement('form')), input; | |
form.target = frame.name; | |
form.action = src; | |
form.method = 'POST'; | |
if (typeof(config.usePost) === 'object') { | |
for (var i in config.usePost) { | |
if (config.usePost.hasOwnProperty(i)) { | |
if (HAS_NAME_PROPERTY_BUG) { | |
input = document.createElement('<input name="' + i + '"/>'); | |
} else { | |
input = document.createElement("INPUT"); | |
input.name = i; | |
} | |
input.value = config.usePost[i]; | |
form.appendChild(input); | |
} | |
} | |
} | |
form.submit(); | |
form.parentNode.removeChild(form); | |
} else { | |
frame.src = src; | |
} | |
config.props.src = src; | |
return frame; | |
} | |
/** | |
* Check whether a domain is allowed using an Access Control List. | |
* The ACL can contain * and ? as wildcards, or can be regular expressions. | |
* If regular expressions they need to begin with ^ and end with $. | |
* @param {Array/String} acl The list of allowed domains | |
* @param {String} domain The domain to test. | |
* @return {Boolean} True if the domain is allowed, false if not. | |
*/ | |
function checkAcl(acl, domain){ | |
// normalize into an array | |
if (typeof acl == "string") { | |
acl = [acl]; | |
} | |
var re, i = acl.length; | |
while (i--) { | |
re = acl[i]; | |
re = new RegExp(re.substr(0, 1) == "^" ? re : ("^" + re.replace(/(\*)/g, ".$1").replace(/\?/g, ".") + "$")); | |
if (re.test(domain)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/* | |
* Functions related to stacks | |
*/ | |
/** | |
* Prepares an array of stack-elements suitable for the current configuration | |
* @param {Object} config The Transports configuration. See easyXDM.Socket for more. | |
* @return {Array} An array of stack-elements with the TransportElement at index 0. | |
*/ | |
function prepareTransportStack(config){ | |
var protocol = config.protocol, stackEls; | |
config.isHost = config.isHost || undef(query.xdm_p); | |
useHash = config.hash || false; | |
_trace("preparing transport stack"); | |
if (!config.props) { | |
config.props = {}; | |
} | |
if (!config.isHost) { | |
_trace("using parameters from query"); | |
config.channel = query.xdm_c.replace(/["'<>\\]/g, ""); | |
config.secret = query.xdm_s; | |
config.remote = query.xdm_e.replace(/["'<>\\]/g, ""); | |
; | |
protocol = query.xdm_p; | |
if (config.acl && !checkAcl(config.acl, config.remote)) { | |
throw new Error("Access denied for " + config.remote); | |
} | |
} | |
else { | |
config.remote = resolveUrl(config.remote); | |
config.channel = config.channel || "default" + channelId++; | |
config.secret = Math.random().toString(16).substring(2); | |
if (undef(protocol)) { | |
if (getLocation(location.href) == getLocation(config.remote)) { | |
/* | |
* Both documents has the same origin, lets use direct access. | |
*/ | |
protocol = "4"; | |
} | |
else if (isHostMethod(window, "postMessage") || isHostMethod(document, "postMessage")) { | |
/* | |
* This is supported in IE8+, Firefox 3+, Opera 9+, Chrome 2+ and Safari 4+ | |
*/ | |
protocol = "1"; | |
} | |
else if (config.swf && isHostMethod(window, "ActiveXObject") && hasFlash()) { | |
/* | |
* The Flash transport superseedes the NixTransport as the NixTransport has been blocked by MS | |
*/ | |
protocol = "6"; | |
} | |
else if (navigator.product === "Gecko" && "frameElement" in window && navigator.userAgent.indexOf('WebKit') == -1) { | |
/* | |
* This is supported in Gecko (Firefox 1+) | |
*/ | |
protocol = "5"; | |
} | |
else if (config.remoteHelper) { | |
/* | |
* This is supported in all browsers that retains the value of window.name when | |
* navigating from one domain to another, and where parent.frames[foo] can be used | |
* to get access to a frame from the same domain | |
*/ | |
protocol = "2"; | |
} | |
else { | |
/* | |
* This is supported in all browsers where [window].location is writable for all | |
* The resize event will be used if resize is supported and the iframe is not put | |
* into a container, else polling will be used. | |
*/ | |
protocol = "0"; | |
} | |
_trace("selecting protocol: " + protocol); | |
} | |
else { | |
_trace("using protocol: " + protocol); | |
} | |
} | |
config.protocol = protocol; // for conditional branching | |
switch (protocol) { | |
case "0":// 0 = HashTransport | |
apply(config, { | |
interval: 100, | |
delay: 2000, | |
useResize: true, | |
useParent: false, | |
usePolling: false | |
}, true); | |
if (config.isHost) { | |
if (!config.local) { | |
_trace("looking for image to use as local"); | |
// If no local is set then we need to find an image hosted on the current domain | |
var domain = location.protocol + "//" + location.host, images = document.body.getElementsByTagName("img"), image; | |
var i = images.length; | |
while (i--) { | |
image = images[i]; | |
if (image.src.substring(0, domain.length) === domain) { | |
config.local = image.src; | |
break; | |
} | |
} | |
if (!config.local) { | |
_trace("no image found, defaulting to using the window"); | |
// If no local was set, and we are unable to find a suitable file, then we resort to using the current window | |
config.local = window; | |
} | |
} | |
var parameters = { | |
xdm_c: config.channel, | |
xdm_p: 0 | |
}; | |
if (config.local === window) { | |
// We are using the current window to listen to | |
config.usePolling = true; | |
config.useParent = true; | |
config.local = location.protocol + "//" + location.host + location.pathname + location.search; | |
parameters.xdm_e = config.local; | |
parameters.xdm_pa = 1; // use parent | |
} | |
else { | |
parameters.xdm_e = resolveUrl(config.local); | |
} | |
if (config.container) { | |
config.useResize = false; | |
parameters.xdm_po = 1; // use polling | |
} | |
config.remote = appendQueryParameters(config.remote, parameters); | |
} | |
else { | |
apply(config, { | |
channel: query.xdm_c, | |
remote: query.xdm_e, | |
useParent: !undef(query.xdm_pa), | |
usePolling: !undef(query.xdm_po), | |
useResize: config.useParent ? false : config.useResize | |
}); | |
} | |
stackEls = [new easyXDM.stack.HashTransport(config), new easyXDM.stack.ReliableBehavior({}), new easyXDM.stack.QueueBehavior({ | |
encode: true, | |
maxLength: 4000 - config.remote.length | |
}), new easyXDM.stack.VerifyBehavior({ | |
initiate: config.isHost | |
})]; | |
break; | |
case "1": | |
stackEls = [new easyXDM.stack.PostMessageTransport(config)]; | |
break; | |
case "2": | |
config.remoteHelper = resolveUrl(config.remoteHelper); | |
stackEls = [new easyXDM.stack.NameTransport(config), new easyXDM.stack.QueueBehavior(), new easyXDM.stack.VerifyBehavior({ | |
initiate: config.isHost | |
})]; | |
break; | |
case "3": | |
stackEls = [new easyXDM.stack.NixTransport(config)]; | |
break; | |
case "4": | |
stackEls = [new easyXDM.stack.SameOriginTransport(config)]; | |
break; | |
case "5": | |
stackEls = [new easyXDM.stack.FrameElementTransport(config)]; | |
break; | |
case "6": | |
if (!flashVersion) { | |
hasFlash(); | |
} | |
stackEls = [new easyXDM.stack.FlashTransport(config)]; | |
break; | |
} | |
// this behavior is responsible for buffering outgoing messages, and for performing lazy initialization | |
stackEls.push(new easyXDM.stack.QueueBehavior({ | |
lazy: config.lazy, | |
remove: true | |
})); | |
return stackEls; | |
} | |
/** | |
* Chains all the separate stack elements into a single usable stack.<br/> | |
* If an element is missing a necessary method then it will have a pass-through method applied. | |
* @param {Array} stackElements An array of stack elements to be linked. | |
* @return {easyXDM.stack.StackElement} The last element in the chain. | |
*/ | |
function chainStack(stackElements){ | |
var stackEl, defaults = { | |
incoming: function(message, origin){ | |
this.up.incoming(message, origin); | |
}, | |
outgoing: function(message, recipient){ | |
this.down.outgoing(message, recipient); | |
}, | |
callback: function(success){ | |
this.up.callback(success); | |
}, | |
init: function(){ | |
this.down.init(); | |
}, | |
destroy: function(){ | |
this.down.destroy(); | |
} | |
}; | |
for (var i = 0, len = stackElements.length; i < len; i++) { | |
stackEl = stackElements[i]; | |
apply(stackEl, defaults, true); | |
if (i !== 0) { | |
stackEl.down = stackElements[i - 1]; | |
} | |
if (i !== len - 1) { | |
stackEl.up = stackElements[i + 1]; | |
} | |
} | |
return stackEl; | |
} | |
/** | |
* This will remove a stackelement from its stack while leaving the stack functional. | |
* @param {Object} element The elment to remove from the stack. | |
*/ | |
function removeFromStack(element){ | |
element.up.down = element.down; | |
element.down.up = element.up; | |
element.up = element.down = null; | |
} | |
/* | |
* Export the main object and any other methods applicable | |
*/ | |
/** | |
* @class easyXDM | |
* A javascript library providing cross-browser, cross-domain messaging/RPC. | |
* @version 2.4.17.1 | |
* @singleton | |
*/ | |
apply(easyXDM, { | |
/** | |
* The version of the library | |
* @type {string} | |
*/ | |
version: "2.4.17.1", | |
/** | |
* This is a map containing all the query parameters passed to the document. | |
* All the values has been decoded using decodeURIComponent. | |
* @type {object} | |
*/ | |
query: query, | |
/** | |
* @private | |
*/ | |
stack: {}, | |
/** | |
* Applies properties from the source object to the target object.<br/> | |
* @param {object} target The target of the properties. | |
* @param {object} source The source of the properties. | |
* @param {boolean} noOverwrite Set to True to only set non-existing properties. | |
*/ | |
apply: apply, | |
/** | |
* A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works. | |
* @return {JSON} A valid JSON conforming object, or null if not found. | |
*/ | |
getJSONObject: getJSON, | |
/** | |
* This will add a function to the queue of functions to be run once the DOM reaches a ready state. | |
* If functions are added after this event then they will be executed immediately. | |
* @param {function} fn The function to add | |
* @param {object} scope An optional scope for the function to be called with. | |
*/ | |
whenReady: whenReady, | |
/** | |
* Removes easyXDM variable from the global scope. It also returns control | |
* of the easyXDM variable to whatever code used it before. | |
* | |
* @param {String} ns A string representation of an object that will hold | |
* an instance of easyXDM. | |
* @return An instance of easyXDM | |
*/ | |
noConflict: noConflict | |
}); | |
// Expose helper functions so we can test them | |
apply(easyXDM, { | |
checkAcl: checkAcl, | |
getDomainName: getDomainName, | |
getLocation: getLocation, | |
appendQueryParameters: appendQueryParameters | |
}); | |
/*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ | |
/*global console, _FirebugCommandLine, easyXDM, window, escape, unescape, isHostObject, undef, _trace, domIsReady, emptyFn, namespace */ | |
// | |
// easyXDM | |
// http://easyxdm.net/ | |
// Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
var debug = { | |
_deferred: [], | |
flush: function(){ | |
this.trace("... deferred messages ..."); | |
for (var i = 0, len = this._deferred.length; i < len; i++) { | |
this.trace(this._deferred[i]); | |
} | |
this._deferred.length = 0; | |
this.trace("... end of deferred messages ..."); | |
}, | |
getTime: function(){ | |
var d = new Date(), h = d.getHours() + "", m = d.getMinutes() + "", s = d.getSeconds() + "", ms = d.getMilliseconds() + "", zeros = "000"; | |
if (h.length == 1) { | |
h = "0" + h; | |
} | |
if (m.length == 1) { | |
m = "0" + m; | |
} | |
if (s.length == 1) { | |
s = "0" + s; | |
} | |
ms = zeros.substring(ms.length) + ms; | |
return h + ":" + m + ":" + s + "." + ms; | |
}, | |
/** | |
* Logs the message to console.log if available | |
* @param {String} msg The message to log | |
*/ | |
log: function(msg){ | |
// Uses memoizing to cache the implementation | |
if (!isHostObject(window, "console") || undef(console.log)) { | |
/** | |
* Sets log to be an empty function since we have no output available | |
* @ignore | |
*/ | |
this.log = emptyFn; | |
} | |
else { | |
/** | |
* Sets log to be a wrapper around console.log | |
* @ignore | |
* @param {String} msg | |
*/ | |
this.log = function(msg){ | |
console.log(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ": " + msg); | |
}; | |
} | |
this.log(msg); | |
}, | |
/** | |
* Will try to trace the given message either to a DOMElement with the id "log", | |
* or by using console.info. | |
* @param {String} msg The message to trace | |
*/ | |
trace: function(msg){ | |
// Uses memoizing to cache the implementation | |
if (!domIsReady) { | |
if (this._deferred.length === 0) { | |
easyXDM.whenReady(debug.flush, debug); | |
} | |
this._deferred.push(msg); | |
this.log(msg); | |
} | |
else { | |
var el = document.getElementById("log"); | |
// is there a log element present? | |
if (el) { | |
/** | |
* Sets trace to be a function that outputs the messages to the DOMElement with id "log" | |
* @ignore | |
* @param {String} msg | |
*/ | |
this.trace = function(msg){ | |
try { | |
el.appendChild(document.createElement("div")).appendChild(document.createTextNode(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ":" + msg)); | |
el.scrollTop = el.scrollHeight; | |
} | |
catch (e) { | |
//In case we are unloading | |
} | |
}; | |
} | |
else if (isHostObject(window, "console") && !undef(console.info)) { | |
/** | |
* Sets trace to be a wrapper around console.info | |
* @ignore | |
* @param {String} msg | |
*/ | |
this.trace = function(msg){ | |
console.info(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ":" + msg); | |
}; | |
} | |
else { | |
/** | |
* Create log window | |
* @ignore | |
*/ | |
var domain = location.host, windowname = domain.replace(/[\-.:]/g, "") + "easyxdm_log", logWin; | |
try { | |
logWin = window.open("", windowname, "width=800,height=200,status=0,navigation=0,scrollbars=1"); | |
} | |
catch (e) { | |
} | |
if (logWin) { | |
var doc = logWin.document; | |
el = doc.getElementById("log"); | |
if (!el) { | |
doc.write("<html><head><title>easyXDM log " + domain + "</title></head>"); | |
doc.write("<body><div id=\"log\"></div></body></html>"); | |
doc.close(); | |
el = doc.getElementById("log"); | |
} | |
this.trace = function(msg){ | |
try { | |
el.appendChild(doc.createElement("div")).appendChild(doc.createTextNode(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ":" + msg)); | |
el.scrollTop = el.scrollHeight; | |
} | |
catch (e) { | |
//In case we are unloading | |
} | |
}; | |
this.trace("---- new logger at " + location.href); | |
} | |
if (!el) { | |
// We are unable to use any logging | |
this.trace = emptyFn; | |
} | |
} | |
this.trace(msg); | |
} | |
}, | |
/** | |
* Creates a method usable for tracing. | |
* @param {String} name The name the messages should be marked with | |
* @return {Function} A function that accepts a single string as argument. | |
*/ | |
getTracer: function(name){ | |
return function(msg){ | |
debug.trace(name + ": " + msg); | |
}; | |
} | |
}; | |
debug.log("easyXDM present on '" + location.href); | |
easyXDM.Debug = debug; | |
_trace = debug.getTracer("{Private}"); | |
/*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ | |
/*global easyXDM, window, escape, unescape, isHostObject, isHostMethod, un, on, createFrame, debug */ | |
// | |
// easyXDM | |
// http://easyxdm.net/ | |
// Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
/** | |
* @class easyXDM.DomHelper | |
* Contains methods for dealing with the DOM | |
* @singleton | |
*/ | |
easyXDM.DomHelper = { | |
/** | |
* Provides a consistent interface for adding eventhandlers | |
* @param {Object} target The target to add the event to | |
* @param {String} type The name of the event | |
* @param {Function} listener The listener | |
*/ | |
on: on, | |
/** | |
* Provides a consistent interface for removing eventhandlers | |
* @param {Object} target The target to remove the event from | |
* @param {String} type The name of the event | |
* @param {Function} listener The listener | |
*/ | |
un: un, | |
/** | |
* Checks for the presence of the JSON object. | |
* If it is not present it will use the supplied path to load the JSON2 library. | |
* This should be called in the documents head right after the easyXDM script tag. | |
* http://json.org/json2.js | |
* @param {String} path A valid path to json2.js | |
*/ | |
requiresJSON: function(path){ | |
if (!isHostObject(window, "JSON")) { | |
debug.log("loading external JSON"); | |
// we need to encode the < in order to avoid an illegal token error | |
// when the script is inlined in a document. | |
document.write('<' + 'script type="text/javascript" src="' + path + '"><' + '/script>'); | |
} | |
else { | |
debug.log("native JSON found"); | |
} | |
} | |
}; | |
/*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ | |
/*global easyXDM, window, escape, unescape, debug */ | |
// | |
// easyXDM | |
// http://easyxdm.net/ | |
// Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
(function(){ | |
// The map containing the stored functions | |
var _map = {}; | |
/** | |
* @class easyXDM.Fn | |
* This contains methods related to function handling, such as storing callbacks. | |
* @singleton | |
* @namespace easyXDM | |
*/ | |
easyXDM.Fn = { | |
/** | |
* Stores a function using the given name for reference | |
* @param {String} name The name that the function should be referred by | |
* @param {Function} fn The function to store | |
* @namespace easyXDM.fn | |
*/ | |
set: function(name, fn){ | |
this._trace("storing function " + name); | |
_map[name] = fn; | |
}, | |
/** | |
* Retrieves the function referred to by the given name | |
* @param {String} name The name of the function to retrieve | |
* @param {Boolean} del If the function should be deleted after retrieval | |
* @return {Function} The stored function | |
* @namespace easyXDM.fn | |
*/ | |
get: function(name, del){ | |
this._trace("retrieving function " + name); | |
var fn = _map[name]; | |
if (!fn) { | |
this._trace(name + " not found"); | |
} | |
if (del) { | |
delete _map[name]; | |
} | |
return fn; | |
} | |
}; | |
easyXDM.Fn._trace = debug.getTracer("easyXDM.Fn"); | |
}()); | |
/*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ | |
/*global easyXDM, window, escape, unescape, chainStack, prepareTransportStack, getLocation, debug */ | |
// | |
// easyXDM | |
// http://easyxdm.net/ | |
// Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
/** | |
* @class easyXDM.Socket | |
* This class creates a transport channel between two domains that is usable for sending and receiving string-based messages.<br/> | |
* The channel is reliable, supports queueing, and ensures that the message originates from the expected domain.<br/> | |
* Internally different stacks will be used depending on the browsers features and the available parameters. | |
* <h2>How to set up</h2> | |
* Setting up the provider: | |
* <pre><code> | |
* var socket = new easyXDM.Socket({ | |
* local: "name.html", | |
* onReady: function(){ | |
* // you need to wait for the onReady callback before using the socket | |
* socket.postMessage("foo-message"); | |
* }, | |
* onMessage: function(message, origin) { | |
* alert("received " + message + " from " + origin); | |
* } | |
* }); | |
* </code></pre> | |
* Setting up the consumer: | |
* <pre><code> | |
* var socket = new easyXDM.Socket({ | |
* remote: "http://remotedomain/page.html", | |
* remoteHelper: "http://remotedomain/name.html", | |
* onReady: function(){ | |
* // you need to wait for the onReady callback before using the socket | |
* socket.postMessage("foo-message"); | |
* }, | |
* onMessage: function(message, origin) { | |
* alert("received " + message + " from " + origin); | |
* } | |
* }); | |
* </code></pre> | |
* If you are unable to upload the <code>name.html</code> file to the consumers domain then remove the <code>remoteHelper</code> property | |
* and easyXDM will fall back to using the HashTransport instead of the NameTransport when not able to use any of the primary transports. | |
* @namespace easyXDM | |
* @constructor | |
* @cfg {String/Window} local The url to the local name.html document, a local static file, or a reference to the local window. | |
* @cfg {Boolean} lazy (Consumer only) Set this to true if you want easyXDM to defer creating the transport until really needed. | |
* @cfg {String} remote (Consumer only) The url to the providers document. | |
* @cfg {String} remoteHelper (Consumer only) The url to the remote name.html file. This is to support NameTransport as a fallback. Optional. | |
* @cfg {Number} delay The number of milliseconds easyXDM should try to get a reference to the local window. Optional, defaults to 2000. | |
* @cfg {Number} interval The interval used when polling for messages. Optional, defaults to 300. | |
* @cfg {String} channel (Consumer only) The name of the channel to use. Can be used to set consistent iframe names. Must be unique. Optional. | |
* @cfg {Function} onMessage The method that should handle incoming messages.<br/> This method should accept two arguments, the message as a string, and the origin as a string. Optional. | |
* @cfg {Function} onReady A method that should be called when the transport is ready. Optional. | |
* @cfg {DOMElement|String} container (Consumer only) The element, or the id of the element that the primary iframe should be inserted into. If not set then the iframe will be positioned off-screen. Optional. | |
* @cfg {Array/String} acl (Provider only) Here you can specify which '[protocol]://[domain]' patterns that should be allowed to act as the consumer towards this provider.<br/> | |
* This can contain the wildcards ? and *. Examples are 'http://example.com', '*.foo.com' and '*dom?.com'. If you want to use reqular expressions then you pattern needs to start with ^ and end with $. | |
* If none of the patterns match an Error will be thrown. | |
* @cfg {Object} props (Consumer only) Additional properties that should be applied to the iframe. This can also contain nested objects e.g: <code>{style:{width:"100px", height:"100px"}}</code>. | |
* Properties such as 'name' and 'src' will be overrided. Optional. | |
*/ | |
easyXDM.Socket = function(config){ | |
var trace = debug.getTracer("easyXDM.Socket"); | |
trace("constructor"); | |
// create the stack | |
var stack = chainStack(prepareTransportStack(config).concat([{ | |
incoming: function(message, origin){ | |
config.onMessage(message, origin); | |
}, | |
callback: function(success){ | |
if (config.onReady) { | |
config.onReady(success); | |
} | |
} | |
}])), recipient = getLocation(config.remote); | |
// set the origin | |
this.origin = getLocation(config.remote); | |
/** | |
* Initiates the destruction of the stack. | |
*/ | |
this.destroy = function(){ | |
stack.destroy(); | |
}; | |
/** | |
* Posts a message to the remote end of the channel | |
* @param {String} message The message to send | |
*/ | |
this.postMessage = function(message){ | |
stack.outgoing(message, recipient); | |
}; | |
stack.init(); | |
}; | |
/*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ | |
/*global easyXDM, window, escape, unescape, undef,, chainStack, prepareTransportStack, debug, getLocation */ | |
// | |
// easyXDM | |
// http://easyxdm.net/ | |
// Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
/** | |
* @class easyXDM.Rpc | |
* Creates a proxy object that can be used to call methods implemented on the remote end of the channel, and also to provide the implementation | |
* of methods to be called from the remote end.<br/> | |
* The instantiated object will have methods matching those specified in <code>config.remote</code>.<br/> | |
* This requires the JSON object present in the document, either natively, using json.org's json2 or as a wrapper around library spesific methods. | |
* <h2>How to set up</h2> | |
* <pre><code> | |
* var rpc = new easyXDM.Rpc({ | |
* // this configuration is equal to that used by the Socket. | |
* remote: "http://remotedomain/...", | |
* onReady: function(){ | |
* // you need to wait for the onReady callback before using the proxy | |
* rpc.foo(... | |
* } | |
* },{ | |
* local: {..}, | |
* remote: {..} | |
* }); | |
* </code></pre> | |
* | |
* <h2>Exposing functions (procedures)</h2> | |
* <pre><code> | |
* var rpc = new easyXDM.Rpc({ | |
* ... | |
* },{ | |
* local: { | |
* nameOfMethod: { | |
* method: function(arg1, arg2, success, error){ | |
* ... | |
* } | |
* }, | |
* // with shorthand notation | |
* nameOfAnotherMethod: function(arg1, arg2, success, error){ | |
* } | |
* }, | |
* remote: {...} | |
* }); | |
* </code></pre> | |
* The function referenced by [method] will receive the passed arguments followed by the callback functions <code>success</code> and <code>error</code>.<br/> | |
* To send a successfull result back you can use | |
* <pre><code> | |
* return foo; | |
* </pre></code> | |
* or | |
* <pre><code> | |
* success(foo); | |
* </pre></code> | |
* To return an error you can use | |
* <pre><code> | |
* throw new Error("foo error"); | |
* </code></pre> | |
* or | |
* <pre><code> | |
* error("foo error"); | |
* </code></pre> | |
* | |
* <h2>Defining remotely exposed methods (procedures/notifications)</h2> | |
* The definition of the remote end is quite similar: | |
* <pre><code> | |
* var rpc = new easyXDM.Rpc({ | |
* ... | |
* },{ | |
* local: {...}, | |
* remote: { | |
* nameOfMethod: {} | |
* } | |
* }); | |
* </code></pre> | |
* To call a remote method use | |
* <pre><code> | |
* rpc.nameOfMethod("arg1", "arg2", function(value) { | |
* alert("success: " + value); | |
* }, function(message) { | |
* alert("error: " + message + ); | |
* }); | |
* </code></pre> | |
* Both the <code>success</code> and <code>errror</code> callbacks are optional.<br/> | |
* When called with no callback a JSON-RPC 2.0 notification will be executed. | |
* Be aware that you will not be notified of any errors with this method. | |
* <br/> | |
* <h2>Specifying a custom serializer</h2> | |
* If you do not want to use the JSON2 library for non-native JSON support, but instead capabilities provided by some other library | |
* then you can specify a custom serializer using <code>serializer: foo</code> | |
* <pre><code> | |
* var rpc = new easyXDM.Rpc({ | |
* ... | |
* },{ | |
* local: {...}, | |
* remote: {...}, | |
* serializer : { | |
* parse: function(string){ ... }, | |
* stringify: function(object) {...} | |
* } | |
* }); | |
* </code></pre> | |
* If <code>serializer</code> is set then the class will not attempt to use the native implementation. | |
* @namespace easyXDM | |
* @constructor | |
* @param {Object} config The underlying transports configuration. See easyXDM.Socket for available parameters. | |
* @param {Object} jsonRpcConfig The description of the interface to implement. | |
*/ | |
easyXDM.Rpc = function(config, jsonRpcConfig){ | |
var trace = debug.getTracer("easyXDM.Rpc"); | |
trace("constructor"); | |
// expand shorthand notation | |
if (jsonRpcConfig.local) { | |
for (var method in jsonRpcConfig.local) { | |
if (jsonRpcConfig.local.hasOwnProperty(method)) { | |
var member = jsonRpcConfig.local[method]; | |
if (typeof member === "function") { | |
jsonRpcConfig.local[method] = { | |
method: member | |
}; | |
} | |
} | |
} | |
} | |
// create the stack | |
var stack = chainStack(prepareTransportStack(config).concat([new easyXDM.stack.RpcBehavior(this, jsonRpcConfig), { | |
callback: function(success){ | |
if (config.onReady) { | |
config.onReady(success); | |
} | |
} | |
}])); | |
// set the origin | |
this.origin = getLocation(config.remote); | |
/** | |
* Initiates the destruction of the stack. | |
*/ | |
this.destroy = function(){ | |
stack.destroy(); | |
}; | |
stack.init(); | |
}; | |
/*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ | |
/*global easyXDM, window, escape, unescape, getLocation, appendQueryParameters, createFrame, debug, un, on, apply, whenReady, getParentObject, IFRAME_PREFIX*/ | |
// | |
// easyXDM | |
// http://easyxdm.net/ | |
// Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
/** | |
* @class easyXDM.stack.SameOriginTransport | |
* SameOriginTransport is a transport class that can be used when both domains have the same origin.<br/> | |
* This can be useful for testing and for when the main application supports both internal and external sources. | |
* @namespace easyXDM.stack | |
* @constructor | |
* @param {Object} config The transports configuration. | |
* @cfg {String} remote The remote document to communicate with. | |
*/ | |
easyXDM.stack.SameOriginTransport = function(config){ | |
var trace = debug.getTracer("easyXDM.stack.SameOriginTransport"); | |
trace("constructor"); | |
var pub, frame, send, targetOrigin; | |
return (pub = { | |
outgoing: function(message, domain, fn){ | |
send(message); | |
if (fn) { | |
fn(); | |
} | |
}, | |
destroy: function(){ | |
trace("destroy"); | |
if (frame) { | |
frame.parentNode.removeChild(frame); | |
frame = null; | |
} | |
}, | |
onDOMReady: function(){ | |
trace("init"); | |
targetOrigin = getLocation(config.remote); | |
if (config.isHost) { | |
// set up the iframe | |
apply(config.props, { | |
src: appendQueryParameters(config.remote, { | |
xdm_e: location.protocol + "//" + location.host + location.pathname, | |
xdm_c: config.channel, | |
xdm_p: 4 // 4 = SameOriginTransport | |
}), | |
name: IFRAME_PREFIX + config.channel + "_provider" | |
}); | |
frame = createFrame(config); | |
easyXDM.Fn.set(config.channel, function(sendFn){ | |
send = sendFn; | |
setTimeout(function(){ | |
pub.up.callback(true); | |
}, 0); | |
return function(msg){ | |
pub.up.incoming(msg, targetOrigin); | |
}; | |
}); | |
} | |
else { | |
send = getParentObject().Fn.get(config.channel, true)(function(msg){ | |
pub.up.incoming(msg, targetOrigin); | |
}); | |
setTimeout(function(){ | |
pub.up.callback(true); | |
}, 0); | |
} | |
}, | |
init: function(){ | |
whenReady(pub.onDOMReady, pub); | |
} | |
}); | |
}; | |
/*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ | |
/*global global, easyXDM, window, getLocation, appendQueryParameters, createFrame, debug, apply, whenReady, IFRAME_PREFIX, namespace, resolveUrl, getDomainName, HAS_FLASH_THROTTLED_BUG, getPort, query*/ | |
// | |
// easyXDM | |
// http://easyxdm.net/ | |
// Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
/** | |
* @class easyXDM.stack.FlashTransport | |
* FlashTransport is a transport class that uses an SWF with LocalConnection to pass messages back and forth. | |
* @namespace easyXDM.stack | |
* @constructor | |
* @param {Object} config The transports configuration. | |
* @cfg {String} remote The remote domain to communicate with. | |
* @cfg {String} secret the pre-shared secret used to secure the communication. | |
* @cfg {String} swf The path to the swf file | |
* @cfg {Boolean} swfNoThrottle Set this to true if you want to take steps to avoid beeing throttled when hidden. | |
* @cfg {String || DOMElement} swfContainer Set this if you want to control where the swf is placed | |
*/ | |
easyXDM.stack.FlashTransport = function(config){ | |
var trace = debug.getTracer("easyXDM.stack.FlashTransport"); | |
trace("constructor"); | |
if (!config.swf) { | |
throw new Error("Path to easyxdm.swf is missing"); | |
} | |
var pub, // the public interface | |
frame, send, targetOrigin, swf, swfContainer; | |
function onMessage(message, origin){ | |
setTimeout(function(){ | |
trace("received message"); | |
pub.up.incoming(message, targetOrigin); | |
}, 0); | |
} | |
/** | |
* This method adds the SWF to the DOM and prepares the initialization of the channel | |
*/ | |
function addSwf(domain){ | |
trace("creating factory with SWF from " + domain); | |
// the differentiating query argument is needed in Flash9 to avoid a caching issue where LocalConnection would throw an error. | |
var url = config.swf + "?host=" + config.isHost; | |
var id = "easyXDM_swf_" + Math.floor(Math.random() * 10000); | |
// prepare the init function that will fire once the swf is ready | |
easyXDM.Fn.set("flash_loaded" + domain.replace(/[\-.]/g, "_"), function(){ | |
easyXDM.stack.FlashTransport[domain].swf = swf = swfContainer.firstChild; | |
var queue = easyXDM.stack.FlashTransport[domain].queue; | |
for (var i = 0; i < queue.length; i++) { | |
queue[i](); | |
} | |
queue.length = 0; | |
}); | |
if (config.swfContainer) { | |
swfContainer = (typeof config.swfContainer == "string") ? document.getElementById(config.swfContainer) : config.swfContainer; | |
} | |
else { | |
// create the container that will hold the swf | |
swfContainer = document.createElement('div'); | |
// http://bugs.adobe.com/jira/browse/FP-4796 | |
// http://tech.groups.yahoo.com/group/flexcoders/message/162365 | |
// https://groups.google.com/forum/#!topic/easyxdm/mJZJhWagoLc | |
apply(swfContainer.style, HAS_FLASH_THROTTLED_BUG && config.swfNoThrottle ? { | |
height: "20px", | |
width: "20px", | |
position: "fixed", | |
right: 0, | |
top: 0 | |
} : { | |
height: "1px", | |
width: "1px", | |
position: "absolute", | |
overflow: "hidden", | |
right: 0, | |
top: 0 | |
}); | |
document.body.appendChild(swfContainer); | |
} | |
// create the object/embed | |
var flashVars = "callback=flash_loaded" + domain.replace(/[\-.]/g, "_") + "&proto=" + global.location.protocol + "&domain=" + getDomainName(global.location.href) + "&port=" + getPort(global.location.href) + "&ns=" + namespace; | |
flashVars += "&log=true"; | |
swfContainer.innerHTML = "<object height='20' width='20' type='application/x-shockwave-flash' id='" + id + "' data='" + url + "'>" + | |
"<param name='allowScriptAccess' value='always'></param>" + | |
"<param name='wmode' value='transparent'>" + | |
"<param name='movie' value='" + | |
url + | |
"'></param>" + | |
"<param name='flashvars' value='" + | |
flashVars + | |
"'></param>" + | |
"<embed type='application/x-shockwave-flash' FlashVars='" + | |
flashVars + | |
"' allowScriptAccess='always' wmode='transparent' src='" + | |
url + | |
"' height='1' width='1'></embed>" + | |
"</object>"; | |
} | |
return (pub = { | |
outgoing: function(message, domain, fn){ | |
swf.postMessage(config.channel, message.toString()); | |
if (fn) { | |
fn(); | |
} | |
}, | |
destroy: function(){ | |
trace("destroy"); | |
try { | |
swf.destroyChannel(config.channel); | |
} | |
catch (e) { | |
} | |
swf = null; | |
if (frame) { | |
frame.parentNode.removeChild(frame); | |
frame = null; | |
} | |
}, | |
onDOMReady: function(){ | |
trace("init"); | |
targetOrigin = config.remote; | |
// Prepare the code that will be run after the swf has been intialized | |
easyXDM.Fn.set("flash_" + config.channel + "_init", function(){ | |
setTimeout(function(){ | |
trace("firing onReady"); | |
pub.up.callback(true); | |
}); | |
}); | |
// set up the omMessage handler | |
easyXDM.Fn.set("flash_" + config.channel + "_onMessage", onMessage); | |
config.swf = resolveUrl(config.swf); // reports have been made of requests gone rogue when using relative paths | |
var swfdomain = getDomainName(config.swf); | |
var fn = function(){ | |
// set init to true in case the fn was called was invoked from a separate instance | |
easyXDM.stack.FlashTransport[swfdomain].init = true; | |
swf = easyXDM.stack.FlashTransport[swfdomain].swf; | |
// create the channel | |
swf.createChannel(config.channel, config.secret, getLocation(config.remote), config.isHost); | |
if (config.isHost) { | |
// if Flash is going to be throttled and we want to avoid this | |
if (HAS_FLASH_THROTTLED_BUG && config.swfNoThrottle) { | |
apply(config.props, { | |
position: "fixed", | |
right: 0, | |
top: 0, | |
height: "20px", | |
width: "20px" | |
}); | |
} | |
// set up the iframe | |
apply(config.props, { | |
src: appendQueryParameters(config.remote, { | |
xdm_e: getLocation(location.href), | |
xdm_c: config.channel, | |
xdm_p: 6, // 6 = FlashTransport | |
xdm_s: config.secret | |
}), | |
name: IFRAME_PREFIX + config.channel + "_provider" | |
}); | |
frame = createFrame(config); | |
} | |
}; | |
if (easyXDM.stack.FlashTransport[swfdomain] && easyXDM.stack.FlashTransport[swfdomain].init) { | |
// if the swf is in place and we are the consumer | |
fn(); | |
} | |
else { | |
// if the swf does not yet exist | |
if (!easyXDM.stack.FlashTransport[swfdomain]) { | |
// add the queue to hold the init fn's | |
easyXDM.stack.FlashTransport[swfdomain] = { | |
queue: [fn] | |
}; | |
addSwf(swfdomain); | |
} | |
else { | |
easyXDM.stack.FlashTransport[swfdomain].queue.push(fn); | |
} | |
} | |
}, | |
init: function(){ | |
whenReady(pub.onDOMReady, pub); | |
} | |
}); | |
}; | |
/*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ | |
/*global easyXDM, window, escape, unescape, getLocation, appendQueryParameters, createFrame, debug, un, on, apply, whenReady, IFRAME_PREFIX*/ | |
// | |
// easyXDM | |
// http://easyxdm.net/ | |
// Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
/** | |
* @class easyXDM.stack.PostMessageTransport | |
* PostMessageTransport is a transport class that uses HTML5 postMessage for communication.<br/> | |
* <a href="http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx">http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx</a><br/> | |
* <a href="https://developer.mozilla.org/en/DOM/window.postMessage">https://developer.mozilla.org/en/DOM/window.postMessage</a> | |
* @namespace easyXDM.stack | |
* @constructor | |
* @param {Object} config The transports configuration. | |
* @cfg {String} remote The remote domain to communicate with. | |
*/ | |
easyXDM.stack.PostMessageTransport = function(config){ | |
var trace = debug.getTracer("easyXDM.stack.PostMessageTransport"); | |
trace("constructor"); | |
var pub, // the public interface | |
frame, // the remote frame, if any | |
callerWindow, // the window that we will call with | |
targetOrigin; // the domain to communicate with | |
/** | |
* Resolves the origin from the event object | |
* @private | |
* @param {Object} event The messageevent | |
* @return {String} The scheme, host and port of the origin | |
*/ | |
function _getOrigin(event){ | |
if (event.origin) { | |
// This is the HTML5 property | |
return getLocation(event.origin); | |
} | |
if (event.uri) { | |
// From earlier implementations | |
return getLocation(event.uri); | |
} | |
if (event.domain) { | |
// This is the last option and will fail if the | |
// origin is not using the same schema as we are | |
return location.protocol + "//" + event.domain; | |
} | |
throw "Unable to retrieve the origin of the event"; | |
} | |
/** | |
* This is the main implementation for the onMessage event.<br/> | |
* It checks the validity of the origin and passes the message on if appropriate. | |
* @private | |
* @param {Object} event The messageevent | |
*/ | |
function _window_onMessage(event){ | |
var origin = _getOrigin(event); | |
trace("received message '" + event.data + "' from " + origin); | |
if (origin == targetOrigin && event.data.substring(0, config.channel.length + 1) == config.channel + " ") { | |
pub.up.incoming(event.data.substring(config.channel.length + 1), origin); | |
} | |
} | |
return (pub = { | |
outgoing: function(message, domain, fn){ | |
callerWindow.postMessage(config.channel + " " + message, domain || targetOrigin); | |
if (fn) { | |
fn(); | |
} | |
}, | |
destroy: function(){ | |
trace("destroy"); | |
un(window, "message", _window_onMessage); | |
if (frame) { | |
callerWindow = null; | |
frame.parentNode.removeChild(frame); | |
frame = null; | |
} | |
}, | |
onDOMReady: function(){ | |
trace("init"); | |
targetOrigin = getLocation(config.remote); | |
if (config.isHost) { | |
// add the event handler for listening | |
var waitForReady = function(event){ | |
if (event.data == config.channel + "-ready") { | |
trace("firing onReady"); | |
// replace the eventlistener | |
callerWindow = ("postMessage" in frame.contentWindow) ? frame.contentWindow : frame.contentWindow.document; | |
un(window, "message", waitForReady); | |
on(window, "message", _window_onMessage); | |
setTimeout(function(){ | |
pub.up.callback(true); | |
}, 0); | |
} | |
}; | |
on(window, "message", waitForReady); | |
// set up the iframe | |
apply(config.props, { | |
src: appendQueryParameters(config.remote, { | |
xdm_e: getLocation(location.href), | |
xdm_c: config.channel, | |
xdm_p: 1 // 1 = PostMessage | |
}), | |
name: IFRAME_PREFIX + config.channel + "_provider" | |
}); | |
frame = createFrame(config); | |
} | |
else { | |
// add the event handler for listening | |
on(window, "message", _window_onMessage); | |
callerWindow = ("postMessage" in window.parent) ? window.parent : window.parent.document; | |
callerWindow.postMessage(config.channel + "-ready", targetOrigin); | |
setTimeout(function(){ | |
pub.up.callback(true); | |
}, 0); | |
} | |
}, | |
init: function(){ | |
whenReady(pub.onDOMReady, pub); | |
} | |
}); | |
}; | |
/*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ | |
/*global easyXDM, window, escape, unescape, getLocation, appendQueryParameters, createFrame, debug, apply, query, whenReady, IFRAME_PREFIX*/ | |
// | |
// easyXDM | |
// http://easyxdm.net/ | |
// Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
/** | |
* @class easyXDM.stack.FrameElementTransport | |
* FrameElementTransport is a transport class that can be used with Gecko-browser as these allow passing variables using the frameElement property.<br/> | |
* Security is maintained as Gecho uses Lexical Authorization to determine under which scope a function is running. | |
* @namespace easyXDM.stack | |
* @constructor | |
* @param {Object} config The transports configuration. | |
* @cfg {String} remote The remote document to communicate with. | |
*/ | |
easyXDM.stack.FrameElementTransport = function(config){ | |
var trace = debug.getTracer("easyXDM.stack.FrameElementTransport"); | |
trace("constructor"); | |
var pub, frame, send, targetOrigin; | |
return (pub = { | |
outgoing: function(message, domain, fn){ | |
send.call(this, message); | |
if (fn) { | |
fn(); | |
} | |
}, | |
destroy: function(){ | |
trace("destroy"); | |
if (frame) { | |
frame.parentNode.removeChild(frame); | |
frame = null; | |
} | |
}, | |
onDOMReady: function(){ | |
trace("init"); | |
targetOrigin = getLocation(config.remote); | |
if (config.isHost) { | |
// set up the iframe | |
apply(config.props, { | |
src: appendQueryParameters(config.remote, { | |
xdm_e: getLocation(location.href), | |
xdm_c: config.channel, | |
xdm_p: 5 // 5 = FrameElementTransport | |
}), | |
name: IFRAME_PREFIX + config.channel + "_provider" | |
}); | |
frame = createFrame(config); | |
frame.fn = function(sendFn){ | |
delete frame.fn; | |
send = sendFn; | |
setTimeout(function(){ | |
pub.up.callback(true); | |
}, 0); | |
// remove the function so that it cannot be used to overwrite the send function later on | |
return function(msg){ | |
pub.up.incoming(msg, targetOrigin); | |
}; | |
}; | |
} | |
else { | |
// This is to mitigate origin-spoofing | |
if (document.referrer && getLocation(document.referrer) != query.xdm_e) { | |
window.top.location = query.xdm_e; | |
} | |
send = window.frameElement.fn(function(msg){ | |
pub.up.incoming(msg, targetOrigin); | |
}); | |
pub.up.callback(true); | |
} | |
}, | |
init: function(){ | |
whenReady(pub.onDOMReady, pub); | |
} | |
}); | |
}; | |
/*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ | |
/*global easyXDM, window, escape, unescape, undef, getLocation, appendQueryParameters, resolveUrl, createFrame, debug, un, apply, whenReady, IFRAME_PREFIX*/ | |
// | |
// easyXDM | |
// http://easyxdm.net/ | |
// Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
/** | |
* @class easyXDM.stack.NameTransport | |
* NameTransport uses the window.name property to relay data. | |
* The <code>local</code> parameter needs to be set on both the consumer and provider,<br/> | |
* and the <code>remoteHelper</code> parameter needs to be set on the consumer. | |
* @constructor | |
* @param {Object} config The transports configuration. | |
* @cfg {String} remoteHelper The url to the remote instance of hash.html - this is only needed for the host. | |
* @namespace easyXDM.stack | |
*/ | |
easyXDM.stack.NameTransport = function(config){ | |
var trace = debug.getTracer("easyXDM.stack.NameTransport"); | |
trace("constructor"); | |
if (config.isHost && undef(config.remoteHelper)) { | |
trace("missing remoteHelper"); | |
throw new Error("missing remoteHelper"); | |
} | |
var pub; // the public interface | |
var isHost, callerWindow, remoteWindow, readyCount, callback, remoteOrigin, remoteUrl; | |
function _sendMessage(message){ | |
var url = config.remoteHelper + (isHost ? "#_3" : "#_2") + config.channel; | |
trace("sending message " + message); | |
trace("navigating to '" + url + "'"); | |
callerWindow.contentWindow.sendMessage(message, url); | |
} | |
function _onReady(){ | |
if (isHost) { | |
if (++readyCount === 2 || !isHost) { | |
pub.up.callback(true); | |
} | |
} | |
else { | |
_sendMessage("ready"); | |
trace("calling onReady"); | |
pub.up.callback(true); | |
} | |
} | |
function _onMessage(message){ | |
trace("received message " + message); | |
pub.up.incoming(message, remoteOrigin); | |
} | |
function _onLoad(){ | |
if (callback) { | |
setTimeout(function(){ | |
callback(true); | |
}, 0); | |
} | |
} | |
return (pub = { | |
outgoing: function(message, domain, fn){ | |
callback = fn; | |
_sendMessage(message); | |
}, | |
destroy: function(){ | |
trace("destroy"); | |
callerWindow.parentNode.removeChild(callerWindow); | |
callerWindow = null; | |
if (isHost) { | |
remoteWindow.parentNode.removeChild(remoteWindow); | |
remoteWindow = null; | |
} | |
}, | |
onDOMReady: function(){ | |
trace("init"); | |
isHost = config.isHost; | |
readyCount = 0; | |
remoteOrigin = getLocation(config.remote); | |
config.local = resolveUrl(config.local); | |
if (isHost) { | |
// Register the callback | |
easyXDM.Fn.set(config.channel, function(message){ | |
trace("received initial message " + message); | |
if (isHost && message === "ready") { | |
// Replace the handler | |
easyXDM.Fn.set(config.channel, _onMessage); | |
_onReady(); | |
} | |
}); | |
// Set up the frame that points to the remote instance | |
remoteUrl = appendQueryParameters(config.remote, { | |
xdm_e: config.local, | |
xdm_c: config.channel, | |
xdm_p: 2 | |
}); | |
apply(config.props, { | |
src: remoteUrl + '#' + config.channel, | |
name: IFRAME_PREFIX + config.channel + "_provider" | |
}); | |
remoteWindow = createFrame(config); | |
} | |
else { | |
config.remoteHelper = config.remote; | |
easyXDM.Fn.set(config.channel, _onMessage); | |
} | |
// Set up the iframe that will be used for the transport | |
var onLoad = function(){ | |
// Remove the handler | |
var w = callerWindow || this; | |
un(w, "load", onLoad); | |
easyXDM.Fn.set(config.channel + "_load", _onLoad); | |
(function test(){ | |
if (typeof w.contentWindow.sendMessage == "function") { | |
_onReady(); | |
} | |
else { | |
setTimeout(test, 50); | |
} | |
}()); | |
}; | |
callerWindow = createFrame({ | |
props: { | |
src: config.local + "#_4" + config.channel | |
}, | |
onLoad: onLoad | |
}); | |
}, | |
init: function(){ | |
whenReady(pub.onDOMReady, pub); | |
} | |
}); | |
}; | |
/*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ | |
/*global easyXDM, window, escape, unescape, getLocation, createFrame, debug, un, on, apply, whenReady, IFRAME_PREFIX*/ | |
// | |
// easyXDM | |
// http://easyxdm.net/ | |
// Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
/** | |
* @class easyXDM.stack.HashTransport | |
* HashTransport is a transport class that uses the IFrame URL Technique for communication.<br/> | |
* <a href="http://msdn.microsoft.com/en-us/library/bb735305.aspx">http://msdn.microsoft.com/en-us/library/bb735305.aspx</a><br/> | |
* @namespace easyXDM.stack | |
* @constructor | |
* @param {Object} config The transports configuration. | |
* @cfg {String/Window} local The url to the local file used for proxying messages, or the local window. | |
* @cfg {Number} delay The number of milliseconds easyXDM should try to get a reference to the local window. | |
* @cfg {Number} interval The interval used when polling for messages. | |
*/ | |
easyXDM.stack.HashTransport = function(config){ | |
var trace = debug.getTracer("easyXDM.stack.HashTransport"); | |
trace("constructor"); | |
var pub; | |
var me = this, isHost, _timer, pollInterval, _lastMsg, _msgNr, _listenerWindow, _callerWindow; | |
var useParent, _remoteOrigin; | |
function _sendMessage(message){ | |
trace("sending message '" + (_msgNr + 1) + " " + message + "' to " + _remoteOrigin); | |
if (!_callerWindow) { | |
trace("no caller window"); | |
return; | |
} | |
var url = config.remote + "#" + (_msgNr++) + "_" + message; | |
((isHost || !useParent) ? _callerWindow.contentWindow : _callerWindow).location = url; | |
} | |
function _handleHash(hash){ | |
_lastMsg = hash; | |
trace("received message '" + _lastMsg + "' from " + _remoteOrigin); | |
pub.up.incoming(_lastMsg.substring(_lastMsg.indexOf("_") + 1), _remoteOrigin); | |
} | |
/** | |
* Checks location.hash for a new message and relays this to the receiver. | |
* @private | |
*/ | |
function _pollHash(){ | |
if (!_listenerWindow) { | |
return; | |
} | |
var href = _listenerWindow.location.href, hash = "", indexOf = href.indexOf("#"); | |
if (indexOf != -1) { | |
hash = href.substring(indexOf); | |
} | |
if (hash && hash != _lastMsg) { | |
trace("poll: new message"); | |
_handleHash(hash); | |
} | |
} | |
function _attachListeners(){ | |
trace("starting polling"); | |
_timer = setInterval(_pollHash, pollInterval); | |
} | |
return (pub = { | |
outgoing: function(message, domain){ | |
_sendMessage(message); | |
}, | |
destroy: function(){ | |
window.clearInterval(_timer); | |
if (isHost || !useParent) { | |
_callerWindow.parentNode.removeChild(_callerWindow); | |
} | |
_callerWindow = null; | |
}, | |
onDOMReady: function(){ | |
isHost = config.isHost; | |
pollInterval = config.interval; | |
_lastMsg = "#" + config.channel; | |
_msgNr = 0; | |
useParent = config.useParent; | |
_remoteOrigin = getLocation(config.remote); | |
if (isHost) { | |
apply(config.props, { | |
src: config.remote, | |
name: IFRAME_PREFIX + config.channel + "_provider" | |
}); | |
if (useParent) { | |
config.onLoad = function(){ | |
_listenerWindow = window; | |
_attachListeners(); | |
pub.up.callback(true); | |
}; | |
} | |
else { | |
var tries = 0, max = config.delay / 50; | |
(function getRef(){ | |
if (++tries > max) { | |
trace("unable to get reference to _listenerWindow, giving up"); | |
throw new Error("Unable to reference listenerwindow"); | |
} | |
try { | |
_listenerWindow = _callerWindow.contentWindow.frames[IFRAME_PREFIX + config.channel + "_consumer"]; | |
} | |
catch (ex) { | |
} | |
if (_listenerWindow) { | |
_attachListeners(); | |
trace("got a reference to _listenerWindow"); | |
pub.up.callback(true); | |
} | |
else { | |
setTimeout(getRef, 50); | |
} | |
}()); | |
} | |
_callerWindow = createFrame(config); | |
} | |
else { | |
_listenerWindow = window; | |
_attachListeners(); | |
if (useParent) { | |
_callerWindow = parent; | |
pub.up.callback(true); | |
} | |
else { | |
apply(config, { | |
props: { | |
src: config.remote + "#" + config.channel + new Date(), | |
name: IFRAME_PREFIX + config.channel + "_consumer" | |
}, | |
onLoad: function(){ | |
pub.up.callback(true); | |
} | |
}); | |
_callerWindow = createFrame(config); | |
} | |
} | |
}, | |
init: function(){ | |
whenReady(pub.onDOMReady, pub); | |
} | |
}); | |
}; | |
/*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ | |
/*global easyXDM, window, escape, unescape, debug */ | |
// | |
// easyXDM | |
// http://easyxdm.net/ | |
// Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
/** | |
* @class easyXDM.stack.ReliableBehavior | |
* This is a behavior that tries to make the underlying transport reliable by using acknowledgements. | |
* @namespace easyXDM.stack | |
* @constructor | |
* @param {Object} config The behaviors configuration. | |
*/ | |
easyXDM.stack.ReliableBehavior = function(config){ | |
var trace = debug.getTracer("easyXDM.stack.ReliableBehavior"); | |
trace("constructor"); | |
var pub, // the public interface | |
callback; // the callback to execute when we have a confirmed success/failure | |
var idOut = 0, idIn = 0, currentMessage = ""; | |
return (pub = { | |
incoming: function(message, origin){ | |
trace("incoming: " + message); | |
var indexOf = message.indexOf("_"), ack = message.substring(0, indexOf).split(","); | |
message = message.substring(indexOf + 1); | |
if (ack[0] == idOut) { | |
trace("message delivered"); | |
currentMessage = ""; | |
if (callback) { | |
callback(true); | |
callback = null; | |
} | |
} | |
if (message.length > 0) { | |
trace("sending ack, and passing on " + message); | |
pub.down.outgoing(ack[1] + "," + idOut + "_" + currentMessage, origin); | |
if (idIn != ack[1]) { | |
idIn = ack[1]; | |
pub.up.incoming(message, origin); | |
} | |
} | |
}, | |
outgoing: function(message, origin, fn){ | |
currentMessage = message; | |
callback = fn; | |
pub.down.outgoing(idIn + "," + (++idOut) + "_" + message, origin); | |
} | |
}); | |
}; | |
/*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ | |
/*global easyXDM, window, escape, unescape, debug, undef, removeFromStack*/ | |
// | |
// easyXDM | |
// http://easyxdm.net/ | |
// Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
/** | |
* @class easyXDM.stack.QueueBehavior | |
* This is a behavior that enables queueing of messages. <br/> | |
* It will buffer incoming messages and dispach these as fast as the underlying transport allows. | |
* This will also fragment/defragment messages so that the outgoing message is never bigger than the | |
* set length. | |
* @namespace easyXDM.stack | |
* @constructor | |
* @param {Object} config The behaviors configuration. Optional. | |
* @cfg {Number} maxLength The maximum length of each outgoing message. Set this to enable fragmentation. | |
*/ | |
easyXDM.stack.QueueBehavior = function(config){ | |
var trace = debug.getTracer("easyXDM.stack.QueueBehavior"); | |
trace("constructor"); | |
var pub, queue = [], waiting = true, incoming = "", destroying, maxLength = 0, lazy = false, doFragment = false; | |
function dispatch(){ | |
if (config.remove && queue.length === 0) { | |
trace("removing myself from the stack"); | |
removeFromStack(pub); | |
return; | |
} | |
if (waiting || queue.length === 0 || destroying) { | |
return; | |
} | |
trace("dispatching from queue"); | |
waiting = true; | |
var message = queue.shift(); | |
pub.down.outgoing(message.data, message.origin, function(success){ | |
waiting = false; | |
if (message.callback) { | |
setTimeout(function(){ | |
message.callback(success); | |
}, 0); | |
} | |
dispatch(); | |
}); | |
} | |
return (pub = { | |
init: function(){ | |
if (undef(config)) { | |
config = {}; | |
} | |
if (config.maxLength) { | |
maxLength = config.maxLength; | |
doFragment = true; | |
} | |
if (config.lazy) { | |
lazy = true; | |
} | |
else { | |
pub.down.init(); | |
} | |
}, | |
callback: function(success){ | |
waiting = false; | |
var up = pub.up; // in case dispatch calls removeFromStack | |
dispatch(); | |
up.callback(success); | |
}, | |
incoming: function(message, origin){ | |
if (doFragment) { | |
var indexOf = message.indexOf("_"), seq = parseInt(message.substring(0, indexOf), 10); | |
incoming += message.substring(indexOf + 1); | |
if (seq === 0) { | |
trace("received the last fragment"); | |
if (config.encode) { | |
incoming = decodeURIComponent(incoming); | |
} | |
pub.up.incoming(incoming, origin); | |
incoming = ""; | |
} | |
else { | |
trace("waiting for more fragments, seq=" + message); | |
} | |
} | |
else { | |
pub.up.incoming(message, origin); | |
} | |
}, | |
outgoing: function(message, origin, fn){ | |
if (config.encode) { | |
message = encodeURIComponent(message); | |
} | |
var fragments = [], fragment; | |
if (doFragment) { | |
// fragment into chunks | |
while (message.length !== 0) { | |
fragment = message.substring(0, maxLength); | |
message = message.substring(fragment.length); | |
fragments.push(fragment); | |
} | |
// enqueue the chunks | |
while ((fragment = fragments.shift())) { | |
trace("enqueuing"); | |
queue.push({ | |
data: fragments.length + "_" + fragment, | |
origin: origin, | |
callback: fragments.length === 0 ? fn : null | |
}); | |
} | |
} | |
else { | |
queue.push({ | |
data: message, | |
origin: origin, | |
callback: fn | |
}); | |
} | |
if (lazy) { | |
pub.down.init(); | |
} | |
else { | |
dispatch(); | |
} | |
}, | |
destroy: function(){ | |
trace("destroy"); | |
destroying = true; | |
pub.down.destroy(); | |
} | |
}); | |
}; | |
/*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ | |
/*global easyXDM, window, escape, unescape, undef, debug */ | |
// | |
// easyXDM | |
// http://easyxdm.net/ | |
// Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
/** | |
* @class easyXDM.stack.VerifyBehavior | |
* This behavior will verify that communication with the remote end is possible, and will also sign all outgoing, | |
* and verify all incoming messages. This removes the risk of someone hijacking the iframe to send malicious messages. | |
* @namespace easyXDM.stack | |
* @constructor | |
* @param {Object} config The behaviors configuration. | |
* @cfg {Boolean} initiate If the verification should be initiated from this end. | |
*/ | |
easyXDM.stack.VerifyBehavior = function(config){ | |
var trace = debug.getTracer("easyXDM.stack.VerifyBehavior"); | |
trace("constructor"); | |
if (undef(config.initiate)) { | |
throw new Error("settings.initiate is not set"); | |
} | |
var pub, mySecret, theirSecret, verified = false; | |
function startVerification(){ | |
trace("requesting verification"); | |
mySecret = Math.random().toString(16).substring(2); | |
pub.down.outgoing(mySecret); | |
} | |
return (pub = { | |
incoming: function(message, origin){ | |
var indexOf = message.indexOf("_"); | |
if (indexOf === -1) { | |
if (message === mySecret) { | |
trace("verified, calling callback"); | |
pub.up.callback(true); | |
} | |
else if (!theirSecret) { | |
trace("returning secret"); | |
theirSecret = message; | |
if (!config.initiate) { | |
startVerification(); | |
} | |
pub.down.outgoing(message); | |
} | |
} | |
else { | |
if (message.substring(0, indexOf) === theirSecret) { | |
pub.up.incoming(message.substring(indexOf + 1), origin); | |
} | |
} | |
}, | |
outgoing: function(message, origin, fn){ | |
pub.down.outgoing(mySecret + "_" + message, origin, fn); | |
}, | |
callback: function(success){ | |
if (config.initiate) { | |
startVerification(); | |
} | |
} | |
}); | |
}; | |
/*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ | |
/*global easyXDM, window, escape, unescape, undef, getJSON, debug, emptyFn, isArray */ | |
// | |
// easyXDM | |
// http://easyxdm.net/ | |
// Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
/** | |
* @class easyXDM.stack.RpcBehavior | |
* This uses JSON-RPC 2.0 to expose local methods and to invoke remote methods and have responses returned over the the string based transport stack.<br/> | |
* Exposed methods can return values synchronous, asyncronous, or bet set up to not return anything. | |
* @namespace easyXDM.stack | |
* @constructor | |
* @param {Object} proxy The object to apply the methods to. | |
* @param {Object} config The definition of the local and remote interface to implement. | |
* @cfg {Object} local The local interface to expose. | |
* @cfg {Object} remote The remote methods to expose through the proxy. | |
* @cfg {Object} serializer The serializer to use for serializing and deserializing the JSON. Should be compatible with the HTML5 JSON object. Optional, will default to JSON. | |
*/ | |
easyXDM.stack.RpcBehavior = function(proxy, config){ | |
var trace = debug.getTracer("easyXDM.stack.RpcBehavior"); | |
var pub, serializer = config.serializer || getJSON(); | |
var _callbackCounter = 0, _callbacks = {}; | |
/** | |
* Serializes and sends the message | |
* @private | |
* @param {Object} data The JSON-RPC message to be sent. The jsonrpc property will be added. | |
*/ | |
function _send(data){ | |
data.jsonrpc = "2.0"; | |
pub.down.outgoing(serializer.stringify(data)); | |
} | |
/** | |
* Creates a method that implements the given definition | |
* @private | |
* @param {Object} The method configuration | |
* @param {String} method The name of the method | |
* @return {Function} A stub capable of proxying the requested method call | |
*/ | |
function _createMethod(definition, method){ | |
var slice = Array.prototype.slice; | |
trace("creating method " + method); | |
return function(){ | |
trace("executing method " + method); | |
var l = arguments.length, callback, message = { | |
method: method | |
}; | |
if (l > 0 && typeof arguments[l - 1] === "function") { | |
//with callback, procedure | |
if (l > 1 && typeof arguments[l - 2] === "function") { | |
// two callbacks, success and error | |
callback = { | |
success: arguments[l - 2], | |
error: arguments[l - 1] | |
}; | |
message.params = slice.call(arguments, 0, l - 2); | |
} | |
else { | |
// single callback, success | |
callback = { | |
success: arguments[l - 1] | |
}; | |
message.params = slice.call(arguments, 0, l - 1); | |
} | |
_callbacks["" + (++_callbackCounter)] = callback; | |
message.id = _callbackCounter; | |
} | |
else { | |
// no callbacks, a notification | |
message.params = slice.call(arguments, 0); | |
} | |
if (definition.namedParams && message.params.length === 1) { | |
message.params = message.params[0]; | |
} | |
// Send the method request | |
_send(message); | |
}; | |
} | |
/** | |
* Executes the exposed method | |
* @private | |
* @param {String} method The name of the method | |
* @param {Number} id The callback id to use | |
* @param {Function} method The exposed implementation | |
* @param {Array} params The parameters supplied by the remote end | |
*/ | |
function _executeMethod(method, id, fn, params){ | |
if (!fn) { | |
trace("requested to execute non-existent procedure " + method); | |
if (id) { | |
_send({ | |
id: id, | |
error: { | |
code: -32601, | |
message: "Procedure not found." | |
} | |
}); | |
} | |
return; | |
} | |
trace("requested to execute procedure " + method); | |
var success, error; | |
if (id) { | |
success = function(result){ | |
success = emptyFn; | |
_send({ | |
id: id, | |
result: result | |
}); | |
}; | |
error = function(message, data){ | |
error = emptyFn; | |
var msg = { | |
id: id, | |
error: { | |
code: -32099, | |
message: message | |
} | |
}; | |
if (data) { | |
msg.error.data = data; | |
} | |
_send(msg); | |
}; | |
} | |
else { | |
success = error = emptyFn; | |
} | |
// Call local method | |
if (!isArray(params)) { | |
params = [params]; | |
} | |
try { | |
var result = fn.method.apply(fn.scope, params.concat([success, error])); | |
if (!undef(result)) { | |
success(result); | |
} | |
} | |
catch (ex1) { | |
error(ex1.message); | |
} | |
} | |
return (pub = { | |
incoming: function(message, origin){ | |
var data = serializer.parse(message); | |
if (data.method) { | |
trace("received request to execute method " + data.method + (data.id ? (" using callback id " + data.id) : "")); | |
// A method call from the remote end | |
if (config.handle) { | |
config.handle(data, _send); | |
} | |
else { | |
_executeMethod(data.method, data.id, config.local[data.method], data.params); | |
} | |
} | |
else { | |
trace("received return value destined to callback with id " + data.id); | |
// A method response from the other end | |
var callback = _callbacks[data.id]; | |
if (data.error) { | |
if (callback.error) { | |
callback.error(data.error); | |
} | |
else { | |
trace("unhandled error returned."); | |
} | |
} | |
else if (callback.success) { | |
callback.success(data.result); | |
} | |
delete _callbacks[data.id]; | |
} | |
}, | |
init: function(){ | |
trace("init"); | |
if (config.remote) { | |
trace("creating stubs"); | |
// Implement the remote sides exposed methods | |
for (var method in config.remote) { | |
if (config.remote.hasOwnProperty(method)) { | |
proxy[method] = _createMethod(config.remote[method], method); | |
} | |
} | |
} | |
pub.down.init(); | |
}, | |
destroy: function(){ | |
trace("destroy"); | |
for (var method in config.remote) { | |
if (config.remote.hasOwnProperty(method) && proxy.hasOwnProperty(method)) { | |
delete proxy[method]; | |
} | |
} | |
pub.down.destroy(); | |
} | |
}); | |
}; | |
global.easyXDM = easyXDM; | |
})(window, document, location, window.setTimeout, decodeURIComponent, encodeURIComponent); |