Skip to content

Instantly share code, notes, and snippets.

@davehewy
Last active December 20, 2015 13:29
Show Gist options
  • Save davehewy/6138730 to your computer and use it in GitHub Desktop.
Save davehewy/6138730 to your computer and use it in GitHub Desktop.
This snippet has a dependency on easyXDM (http://easyxdm.net/wp/category/examples/). Decided to wrap it in a library which has a little bit nicer handling of cross domain scripting and less home baked code to try and speed things up.
/**
* 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 (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({
* &nbsp; local: "name.html",
* &nbsp; onReady: function(){
* &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the socket
* &nbsp; &nbsp; socket.postMessage("foo-message");
* &nbsp; },
* &nbsp; onMessage: function(message, origin) {
* &nbsp;&nbsp; alert("received " + message + " from " + origin);
* &nbsp; }
* });
* </code></pre>
* Setting up the consumer:
* <pre><code>
* var socket = new easyXDM.Socket({
* &nbsp; remote: "http:&#47;&#47;remotedomain/page.html",
* &nbsp; remoteHelper: "http:&#47;&#47;remotedomain/name.html",
* &nbsp; onReady: function(){
* &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the socket
* &nbsp; &nbsp; socket.postMessage("foo-message");
* &nbsp; },
* &nbsp; onMessage: function(message, origin) {
* &nbsp;&nbsp; alert("received " + message + " from " + origin);
* &nbsp; }
* });
* </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({
* &nbsp; &#47;&#47; this configuration is equal to that used by the Socket.
* &nbsp; remote: "http:&#47;&#47;remotedomain/...",
* &nbsp; onReady: function(){
* &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the proxy
* &nbsp; &nbsp; rpc.foo(...
* &nbsp; }
* },{
* &nbsp; local: {..},
* &nbsp; remote: {..}
* });
* </code></pre>
*
* <h2>Exposing functions (procedures)</h2>
* <pre><code>
* var rpc = new easyXDM.Rpc({
* &nbsp; ...
* },{
* &nbsp; local: {
* &nbsp; &nbsp; nameOfMethod: {
* &nbsp; &nbsp; &nbsp; method: function(arg1, arg2, success, error){
* &nbsp; &nbsp; &nbsp; &nbsp; ...
* &nbsp; &nbsp; &nbsp; }
* &nbsp; &nbsp; },
* &nbsp; &nbsp; &#47;&#47; with shorthand notation
* &nbsp; &nbsp; nameOfAnotherMethod: function(arg1, arg2, success, error){
* &nbsp; &nbsp; }
* &nbsp; },
* &nbsp; 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({
* &nbsp; ...
* },{
* &nbsp; local: {...},
* &nbsp; remote: {
* &nbsp; &nbsp; nameOfMethod: {}
* &nbsp; }
* });
* </code></pre>
* To call a remote method use
* <pre><code>
* rpc.nameOfMethod("arg1", "arg2", function(value) {
* &nbsp; alert("success: " + value);
* }, function(message) {
* &nbsp; 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({
* &nbsp; ...
* },{
* &nbsp; local: {...},
* &nbsp; remote: {...},
* &nbsp; serializer : {
* &nbsp; &nbsp; parse: function(string){ ... },
* &nbsp; &nbsp; stringify: function(object) {...}
* &nbsp; }
* });
* </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);
/**
* 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(N,d,p,K,k,H){var b=this;var n=Math.floor(Math.random()*10000);var q=Function.prototype;var Q=/^((http.?:)\/\/([^:\/\s]+)(:\d+)*)/;var R=/[\-\w]+\/\.\.\//;var F=/([^:])\/\//g;var I="";var o={};var M=N.easyXDM;var U="easyXDM_";var E;var y=false;var i;var h;function C(X,Z){var Y=typeof X[Z];return Y=="function"||(!!(Y=="object"&&X[Z]))||Y=="unknown"}function u(X,Y){return !!(typeof(X[Y])=="object"&&X[Y])}function r(X){return Object.prototype.toString.call(X)==="[object Array]"}function c(){var Z="Shockwave Flash",ad="application/x-shockwave-flash";if(!t(navigator.plugins)&&typeof navigator.plugins[Z]=="object"){var ab=navigator.plugins[Z].description;if(ab&&!t(navigator.mimeTypes)&&navigator.mimeTypes[ad]&&navigator.mimeTypes[ad].enabledPlugin){i=ab.match(/\d+/g)}}if(!i){var Y;try{Y=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");i=Array.prototype.slice.call(Y.GetVariable("$version").match(/(\d+),(\d+),(\d+),(\d+)/),1);Y=null}catch(ac){}}if(!i){return false}var X=parseInt(i[0],10),aa=parseInt(i[1],10);h=X>9&&aa>0;return true}var v,x;if(C(N,"addEventListener")){v=function(Z,X,Y){Z.addEventListener(X,Y,false)};x=function(Z,X,Y){Z.removeEventListener(X,Y,false)}}else{if(C(N,"attachEvent")){v=function(X,Z,Y){X.attachEvent("on"+Z,Y)};x=function(X,Z,Y){X.detachEvent("on"+Z,Y)}}else{throw new Error("Browser not supported")}}var W=false,J=[],L;if("readyState" in d){L=d.readyState;W=L=="complete"||(~navigator.userAgent.indexOf("AppleWebKit/")&&(L=="loaded"||L=="interactive"))}else{W=!!d.body}function s(){if(W){return}W=true;for(var X=0;X<J.length;X++){J[X]()}J.length=0}if(!W){if(C(N,"addEventListener")){v(d,"DOMContentLoaded",s)}else{v(d,"readystatechange",function(){if(d.readyState=="complete"){s()}});if(d.documentElement.doScroll&&N===top){var g=function(){if(W){return}try{d.documentElement.doScroll("left")}catch(X){K(g,1);return}s()};g()}}v(N,"load",s)}function G(Y,X){if(W){Y.call(X);return}J.push(function(){Y.call(X)})}function m(){var Z=parent;if(I!==""){for(var X=0,Y=I.split(".");X<Y.length;X++){Z=Z[Y[X]]}}return Z.easyXDM}function e(X){N.easyXDM=M;I=X;if(I){U="easyXDM_"+I.replace(".","_")+"_"}return o}function z(X){return X.match(Q)[3]}function f(X){return X.match(Q)[4]||""}function j(Z){var X=Z.toLowerCase().match(Q);var aa=X[2],ab=X[3],Y=X[4]||"";if((aa=="http:"&&Y==":80")||(aa=="https:"&&Y==":443")){Y=""}return aa+"//"+ab+Y}function B(X){X=X.replace(F,"$1/");if(!X.match(/^(http||https):\/\//)){var Y=(X.substring(0,1)==="/")?"":p.pathname;if(Y.substring(Y.length-1)!=="/"){Y=Y.substring(0,Y.lastIndexOf("/")+1)}X=p.protocol+"//"+p.host+Y+X}while(R.test(X)){X=X.replace(R,"")}return X}function P(X,aa){var ac="",Z=X.indexOf("#");if(Z!==-1){ac=X.substring(Z);X=X.substring(0,Z)}var ab=[];for(var Y in aa){if(aa.hasOwnProperty(Y)){ab.push(Y+"="+H(aa[Y]))}}return X+(y?"#":(X.indexOf("?")==-1?"?":"&"))+ab.join("&")+ac}var S=(function(X){X=X.substring(1).split("&");var Z={},aa,Y=X.length;while(Y--){aa=X[Y].split("=");Z[aa[0]]=k(aa[1])}return Z}(/xdm_e=/.test(p.search)?p.search:p.hash));function t(X){return typeof X==="undefined"}var O=function(){var Y={};var Z={a:[1,2,3]},X='{"a":[1,2,3]}';if(typeof JSON!="undefined"&&typeof JSON.stringify==="function"&&JSON.stringify(Z).replace((/\s/g),"")===X){return JSON}if(Object.toJSON){if(Object.toJSON(Z).replace((/\s/g),"")===X){Y.stringify=Object.toJSON}}if(typeof String.prototype.evalJSON==="function"){Z=X.evalJSON();if(Z.a&&Z.a.length===3&&Z.a[2]===3){Y.parse=function(aa){return aa.evalJSON()}}}if(Y.stringify&&Y.parse){O=function(){return Y};return Y}return null};function T(X,Y,Z){var ab;for(var aa in Y){if(Y.hasOwnProperty(aa)){if(aa in X){ab=Y[aa];if(typeof ab==="object"){T(X[aa],ab,Z)}else{if(!Z){X[aa]=Y[aa]}}}else{X[aa]=Y[aa]}}}return X}function a(){var Y=d.body.appendChild(d.createElement("form")),X=Y.appendChild(d.createElement("input"));X.name=U+"TEST"+n;E=X!==Y.elements[X.name];d.body.removeChild(Y)}function A(Y){if(t(E)){a()}var ac;if(E){ac=d.createElement('<iframe name="'+Y.props.name+'"/>')}else{ac=d.createElement("IFRAME");ac.name=Y.props.name}ac.id=ac.name=Y.props.name;delete Y.props.name;if(typeof Y.container=="string"){Y.container=d.getElementById(Y.container)}if(!Y.container){T(ac.style,{position:"absolute",top:"-2000px",left:"0px"});Y.container=d.body}var ab=Y.props.src;Y.props.src="javascript:false";T(ac,Y.props);ac.border=ac.frameBorder=0;ac.allowTransparency=true;Y.container.appendChild(ac);if(Y.onLoad){v(ac,"load",Y.onLoad)}if(Y.usePost){var aa=Y.container.appendChild(d.createElement("form")),X;aa.target=ac.name;aa.action=ab;aa.method="POST";if(typeof(Y.usePost)==="object"){for(var Z in Y.usePost){if(Y.usePost.hasOwnProperty(Z)){if(E){X=d.createElement('<input name="'+Z+'"/>')}else{X=d.createElement("INPUT");X.name=Z}X.value=Y.usePost[Z];aa.appendChild(X)}}}aa.submit();aa.parentNode.removeChild(aa)}else{ac.src=ab}Y.props.src=ab;return ac}function V(aa,Z){if(typeof aa=="string"){aa=[aa]}var Y,X=aa.length;while(X--){Y=aa[X];Y=new RegExp(Y.substr(0,1)=="^"?Y:("^"+Y.replace(/(\*)/g,".$1").replace(/\?/g,".")+"$"));if(Y.test(Z)){return true}}return false}function l(Z){var ae=Z.protocol,Y;Z.isHost=Z.isHost||t(S.xdm_p);y=Z.hash||false;if(!Z.props){Z.props={}}if(!Z.isHost){Z.channel=S.xdm_c.replace(/["'<>\\]/g,"");Z.secret=S.xdm_s;Z.remote=S.xdm_e.replace(/["'<>\\]/g,"");ae=S.xdm_p;if(Z.acl&&!V(Z.acl,Z.remote)){throw new Error("Access denied for "+Z.remote)}}else{Z.remote=B(Z.remote);Z.channel=Z.channel||"default"+n++;Z.secret=Math.random().toString(16).substring(2);if(t(ae)){if(j(p.href)==j(Z.remote)){ae="4"}else{if(C(N,"postMessage")||C(d,"postMessage")){ae="1"}else{if(Z.swf&&C(N,"ActiveXObject")&&c()){ae="6"}else{if(navigator.product==="Gecko"&&"frameElement" in N&&navigator.userAgent.indexOf("WebKit")==-1){ae="5"}else{if(Z.remoteHelper){ae="2"}else{ae="0"}}}}}}}Z.protocol=ae;switch(ae){case"0":T(Z,{interval:100,delay:2000,useResize:true,useParent:false,usePolling:false},true);if(Z.isHost){if(!Z.local){var ac=p.protocol+"//"+p.host,X=d.body.getElementsByTagName("img"),ad;var aa=X.length;while(aa--){ad=X[aa];if(ad.src.substring(0,ac.length)===ac){Z.local=ad.src;break}}if(!Z.local){Z.local=N}}var ab={xdm_c:Z.channel,xdm_p:0};if(Z.local===N){Z.usePolling=true;Z.useParent=true;Z.local=p.protocol+"//"+p.host+p.pathname+p.search;ab.xdm_e=Z.local;ab.xdm_pa=1}else{ab.xdm_e=B(Z.local)}if(Z.container){Z.useResize=false;ab.xdm_po=1}Z.remote=P(Z.remote,ab)}else{T(Z,{channel:S.xdm_c,remote:S.xdm_e,useParent:!t(S.xdm_pa),usePolling:!t(S.xdm_po),useResize:Z.useParent?false:Z.useResize})}Y=[new o.stack.HashTransport(Z),new o.stack.ReliableBehavior({}),new o.stack.QueueBehavior({encode:true,maxLength:4000-Z.remote.length}),new o.stack.VerifyBehavior({initiate:Z.isHost})];break;case"1":Y=[new o.stack.PostMessageTransport(Z)];break;case"2":Z.remoteHelper=B(Z.remoteHelper);Y=[new o.stack.NameTransport(Z),new o.stack.QueueBehavior(),new o.stack.VerifyBehavior({initiate:Z.isHost})];break;case"3":Y=[new o.stack.NixTransport(Z)];break;case"4":Y=[new o.stack.SameOriginTransport(Z)];break;case"5":Y=[new o.stack.FrameElementTransport(Z)];break;case"6":if(!i){c()}Y=[new o.stack.FlashTransport(Z)];break}Y.push(new o.stack.QueueBehavior({lazy:Z.lazy,remove:true}));return Y}function D(aa){var ab,Z={incoming:function(ad,ac){this.up.incoming(ad,ac)},outgoing:function(ac,ad){this.down.outgoing(ac,ad)},callback:function(ac){this.up.callback(ac)},init:function(){this.down.init()},destroy:function(){this.down.destroy()}};for(var Y=0,X=aa.length;Y<X;Y++){ab=aa[Y];T(ab,Z,true);if(Y!==0){ab.down=aa[Y-1]}if(Y!==X-1){ab.up=aa[Y+1]}}return ab}function w(X){X.up.down=X.down;X.down.up=X.up;X.up=X.down=null}T(o,{version:"2.4.17.1",query:S,stack:{},apply:T,getJSONObject:O,whenReady:G,noConflict:e});o.DomHelper={on:v,un:x,requiresJSON:function(X){if(!u(N,"JSON")){d.write('<script type="text/javascript" src="'+X+'"><\/script>')}}};(function(){var X={};o.Fn={set:function(Y,Z){X[Y]=Z},get:function(Z,Y){var aa=X[Z];if(Y){delete X[Z]}return aa}}}());o.Socket=function(Y){var X=D(l(Y).concat([{incoming:function(ab,aa){Y.onMessage(ab,aa)},callback:function(aa){if(Y.onReady){Y.onReady(aa)}}}])),Z=j(Y.remote);this.origin=j(Y.remote);this.destroy=function(){X.destroy()};this.postMessage=function(aa){X.outgoing(aa,Z)};X.init()};o.Rpc=function(Z,Y){if(Y.local){for(var ab in Y.local){if(Y.local.hasOwnProperty(ab)){var aa=Y.local[ab];if(typeof aa==="function"){Y.local[ab]={method:aa}}}}}var X=D(l(Z).concat([new o.stack.RpcBehavior(this,Y),{callback:function(ac){if(Z.onReady){Z.onReady(ac)}}}]));this.origin=j(Z.remote);this.destroy=function(){X.destroy()};X.init()};o.stack.SameOriginTransport=function(Y){var Z,ab,aa,X;return(Z={outgoing:function(ad,ae,ac){aa(ad);if(ac){ac()}},destroy:function(){if(ab){ab.parentNode.removeChild(ab);ab=null}},onDOMReady:function(){X=j(Y.remote);if(Y.isHost){T(Y.props,{src:P(Y.remote,{xdm_e:p.protocol+"//"+p.host+p.pathname,xdm_c:Y.channel,xdm_p:4}),name:U+Y.channel+"_provider"});ab=A(Y);o.Fn.set(Y.channel,function(ac){aa=ac;K(function(){Z.up.callback(true)},0);return function(ad){Z.up.incoming(ad,X)}})}else{aa=m().Fn.get(Y.channel,true)(function(ac){Z.up.incoming(ac,X)});K(function(){Z.up.callback(true)},0)}},init:function(){G(Z.onDOMReady,Z)}})};o.stack.FlashTransport=function(aa){var ac,X,ab,ad,Y,ae;function af(ah,ag){K(function(){ac.up.incoming(ah,ad)},0)}function Z(ah){var ag=aa.swf+"?host="+aa.isHost;var aj="easyXDM_swf_"+Math.floor(Math.random()*10000);o.Fn.set("flash_loaded"+ah.replace(/[\-.]/g,"_"),function(){o.stack.FlashTransport[ah].swf=Y=ae.firstChild;var ak=o.stack.FlashTransport[ah].queue;for(var al=0;al<ak.length;al++){ak[al]()}ak.length=0});if(aa.swfContainer){ae=(typeof aa.swfContainer=="string")?d.getElementById(aa.swfContainer):aa.swfContainer}else{ae=d.createElement("div");T(ae.style,h&&aa.swfNoThrottle?{height:"20px",width:"20px",position:"fixed",right:0,top:0}:{height:"1px",width:"1px",position:"absolute",overflow:"hidden",right:0,top:0});d.body.appendChild(ae)}var ai="callback=flash_loaded"+ah.replace(/[\-.]/g,"_")+"&proto="+b.location.protocol+"&domain="+z(b.location.href)+"&port="+f(b.location.href)+"&ns="+I;ae.innerHTML="<object height='20' width='20' type='application/x-shockwave-flash' id='"+aj+"' data='"+ag+"'><param name='allowScriptAccess' value='always'></param><param name='wmode' value='transparent'><param name='movie' value='"+ag+"'></param><param name='flashvars' value='"+ai+"'></param><embed type='application/x-shockwave-flash' FlashVars='"+ai+"' allowScriptAccess='always' wmode='transparent' src='"+ag+"' height='1' width='1'></embed></object>"}return(ac={outgoing:function(ah,ai,ag){Y.postMessage(aa.channel,ah.toString());if(ag){ag()}},destroy:function(){try{Y.destroyChannel(aa.channel)}catch(ag){}Y=null;if(X){X.parentNode.removeChild(X);X=null}},onDOMReady:function(){ad=aa.remote;o.Fn.set("flash_"+aa.channel+"_init",function(){K(function(){ac.up.callback(true)})});o.Fn.set("flash_"+aa.channel+"_onMessage",af);aa.swf=B(aa.swf);var ah=z(aa.swf);var ag=function(){o.stack.FlashTransport[ah].init=true;Y=o.stack.FlashTransport[ah].swf;Y.createChannel(aa.channel,aa.secret,j(aa.remote),aa.isHost);if(aa.isHost){if(h&&aa.swfNoThrottle){T(aa.props,{position:"fixed",right:0,top:0,height:"20px",width:"20px"})}T(aa.props,{src:P(aa.remote,{xdm_e:j(p.href),xdm_c:aa.channel,xdm_p:6,xdm_s:aa.secret}),name:U+aa.channel+"_provider"});X=A(aa)}};if(o.stack.FlashTransport[ah]&&o.stack.FlashTransport[ah].init){ag()}else{if(!o.stack.FlashTransport[ah]){o.stack.FlashTransport[ah]={queue:[ag]};Z(ah)}else{o.stack.FlashTransport[ah].queue.push(ag)}}},init:function(){G(ac.onDOMReady,ac)}})};o.stack.PostMessageTransport=function(aa){var ac,ad,Y,Z;function X(ae){if(ae.origin){return j(ae.origin)}if(ae.uri){return j(ae.uri)}if(ae.domain){return p.protocol+"//"+ae.domain}throw"Unable to retrieve the origin of the event"}function ab(af){var ae=X(af);if(ae==Z&&af.data.substring(0,aa.channel.length+1)==aa.channel+" "){ac.up.incoming(af.data.substring(aa.channel.length+1),ae)}}return(ac={outgoing:function(af,ag,ae){Y.postMessage(aa.channel+" "+af,ag||Z);if(ae){ae()}},destroy:function(){x(N,"message",ab);if(ad){Y=null;ad.parentNode.removeChild(ad);ad=null}},onDOMReady:function(){Z=j(aa.remote);if(aa.isHost){var ae=function(af){if(af.data==aa.channel+"-ready"){Y=("postMessage" in ad.contentWindow)?ad.contentWindow:ad.contentWindow.document;x(N,"message",ae);v(N,"message",ab);K(function(){ac.up.callback(true)},0)}};v(N,"message",ae);T(aa.props,{src:P(aa.remote,{xdm_e:j(p.href),xdm_c:aa.channel,xdm_p:1}),name:U+aa.channel+"_provider"});ad=A(aa)}else{v(N,"message",ab);Y=("postMessage" in N.parent)?N.parent:N.parent.document;Y.postMessage(aa.channel+"-ready",Z);K(function(){ac.up.callback(true)},0)}},init:function(){G(ac.onDOMReady,ac)}})};o.stack.FrameElementTransport=function(Y){var Z,ab,aa,X;return(Z={outgoing:function(ad,ae,ac){aa.call(this,ad);if(ac){ac()}},destroy:function(){if(ab){ab.parentNode.removeChild(ab);ab=null}},onDOMReady:function(){X=j(Y.remote);if(Y.isHost){T(Y.props,{src:P(Y.remote,{xdm_e:j(p.href),xdm_c:Y.channel,xdm_p:5}),name:U+Y.channel+"_provider"});ab=A(Y);ab.fn=function(ac){delete ab.fn;aa=ac;K(function(){Z.up.callback(true)},0);return function(ad){Z.up.incoming(ad,X)}}}else{if(d.referrer&&j(d.referrer)!=S.xdm_e){N.top.location=S.xdm_e}aa=N.frameElement.fn(function(ac){Z.up.incoming(ac,X)});Z.up.callback(true)}},init:function(){G(Z.onDOMReady,Z)}})};o.stack.NameTransport=function(ab){var ac;var ae,ai,aa,ag,ah,Y,X;function af(al){var ak=ab.remoteHelper+(ae?"#_3":"#_2")+ab.channel;ai.contentWindow.sendMessage(al,ak)}function ad(){if(ae){if(++ag===2||!ae){ac.up.callback(true)}}else{af("ready");ac.up.callback(true)}}function aj(ak){ac.up.incoming(ak,Y)}function Z(){if(ah){K(function(){ah(true)},0)}}return(ac={outgoing:function(al,am,ak){ah=ak;af(al)},destroy:function(){ai.parentNode.removeChild(ai);ai=null;if(ae){aa.parentNode.removeChild(aa);aa=null}},onDOMReady:function(){ae=ab.isHost;ag=0;Y=j(ab.remote);ab.local=B(ab.local);if(ae){o.Fn.set(ab.channel,function(al){if(ae&&al==="ready"){o.Fn.set(ab.channel,aj);ad()}});X=P(ab.remote,{xdm_e:ab.local,xdm_c:ab.channel,xdm_p:2});T(ab.props,{src:X+"#"+ab.channel,name:U+ab.channel+"_provider"});aa=A(ab)}else{ab.remoteHelper=ab.remote;o.Fn.set(ab.channel,aj)}var ak=function(){var al=ai||this;x(al,"load",ak);o.Fn.set(ab.channel+"_load",Z);(function am(){if(typeof al.contentWindow.sendMessage=="function"){ad()}else{K(am,50)}}())};ai=A({props:{src:ab.local+"#_4"+ab.channel},onLoad:ak})},init:function(){G(ac.onDOMReady,ac)}})};o.stack.HashTransport=function(Z){var ac;var ah=this,af,aa,X,ad,am,ab,al;var ag,Y;function ak(ao){if(!al){return}var an=Z.remote+"#"+(am++)+"_"+ao;((af||!ag)?al.contentWindow:al).location=an}function ae(an){ad=an;ac.up.incoming(ad.substring(ad.indexOf("_")+1),Y)}function aj(){if(!ab){return}var an=ab.location.href,ap="",ao=an.indexOf("#");if(ao!=-1){ap=an.substring(ao)}if(ap&&ap!=ad){ae(ap)}}function ai(){aa=setInterval(aj,X)}return(ac={outgoing:function(an,ao){ak(an)},destroy:function(){N.clearInterval(aa);if(af||!ag){al.parentNode.removeChild(al)}al=null},onDOMReady:function(){af=Z.isHost;X=Z.interval;ad="#"+Z.channel;am=0;ag=Z.useParent;Y=j(Z.remote);if(af){T(Z.props,{src:Z.remote,name:U+Z.channel+"_provider"});if(ag){Z.onLoad=function(){ab=N;ai();ac.up.callback(true)}}else{var ap=0,an=Z.delay/50;(function ao(){if(++ap>an){throw new Error("Unable to reference listenerwindow")}try{ab=al.contentWindow.frames[U+Z.channel+"_consumer"]}catch(aq){}if(ab){ai();ac.up.callback(true)}else{K(ao,50)}}())}al=A(Z)}else{ab=N;ai();if(ag){al=parent;ac.up.callback(true)}else{T(Z,{props:{src:Z.remote+"#"+Z.channel+new Date(),name:U+Z.channel+"_consumer"},onLoad:function(){ac.up.callback(true)}});al=A(Z)}}},init:function(){G(ac.onDOMReady,ac)}})};o.stack.ReliableBehavior=function(Y){var aa,ac;var ab=0,X=0,Z="";return(aa={incoming:function(af,ad){var ae=af.indexOf("_"),ag=af.substring(0,ae).split(",");af=af.substring(ae+1);if(ag[0]==ab){Z="";if(ac){ac(true);ac=null}}if(af.length>0){aa.down.outgoing(ag[1]+","+ab+"_"+Z,ad);if(X!=ag[1]){X=ag[1];aa.up.incoming(af,ad)}}},outgoing:function(af,ad,ae){Z=af;ac=ae;aa.down.outgoing(X+","+(++ab)+"_"+af,ad)}})};o.stack.QueueBehavior=function(Z){var ac,ad=[],ag=true,aa="",af,X=0,Y=false,ab=false;function ae(){if(Z.remove&&ad.length===0){w(ac);return}if(ag||ad.length===0||af){return}ag=true;var ah=ad.shift();ac.down.outgoing(ah.data,ah.origin,function(ai){ag=false;if(ah.callback){K(function(){ah.callback(ai)},0)}ae()})}return(ac={init:function(){if(t(Z)){Z={}}if(Z.maxLength){X=Z.maxLength;ab=true}if(Z.lazy){Y=true}else{ac.down.init()}},callback:function(ai){ag=false;var ah=ac.up;ae();ah.callback(ai)},incoming:function(ak,ai){if(ab){var aj=ak.indexOf("_"),ah=parseInt(ak.substring(0,aj),10);aa+=ak.substring(aj+1);if(ah===0){if(Z.encode){aa=k(aa)}ac.up.incoming(aa,ai);aa=""}}else{ac.up.incoming(ak,ai)}},outgoing:function(al,ai,ak){if(Z.encode){al=H(al)}var ah=[],aj;if(ab){while(al.length!==0){aj=al.substring(0,X);al=al.substring(aj.length);ah.push(aj)}while((aj=ah.shift())){ad.push({data:ah.length+"_"+aj,origin:ai,callback:ah.length===0?ak:null})}}else{ad.push({data:al,origin:ai,callback:ak})}if(Y){ac.down.init()}else{ae()}},destroy:function(){af=true;ac.down.destroy()}})};o.stack.VerifyBehavior=function(ab){var ac,aa,Y,Z=false;function X(){aa=Math.random().toString(16).substring(2);ac.down.outgoing(aa)}return(ac={incoming:function(af,ad){var ae=af.indexOf("_");if(ae===-1){if(af===aa){ac.up.callback(true)}else{if(!Y){Y=af;if(!ab.initiate){X()}ac.down.outgoing(af)}}}else{if(af.substring(0,ae)===Y){ac.up.incoming(af.substring(ae+1),ad)}}},outgoing:function(af,ad,ae){ac.down.outgoing(aa+"_"+af,ad,ae)},callback:function(ad){if(ab.initiate){X()}}})};o.stack.RpcBehavior=function(ad,Y){var aa,af=Y.serializer||O();var ae=0,ac={};function X(ag){ag.jsonrpc="2.0";aa.down.outgoing(af.stringify(ag))}function ab(ag,ai){var ah=Array.prototype.slice;return function(){var aj=arguments.length,al,ak={method:ai};if(aj>0&&typeof arguments[aj-1]==="function"){if(aj>1&&typeof arguments[aj-2]==="function"){al={success:arguments[aj-2],error:arguments[aj-1]};ak.params=ah.call(arguments,0,aj-2)}else{al={success:arguments[aj-1]};ak.params=ah.call(arguments,0,aj-1)}ac[""+(++ae)]=al;ak.id=ae}else{ak.params=ah.call(arguments,0)}if(ag.namedParams&&ak.params.length===1){ak.params=ak.params[0]}X(ak)}}function Z(an,am,ai,al){if(!ai){if(am){X({id:am,error:{code:-32601,message:"Procedure not found."}})}return}var ak,ah;if(am){ak=function(ao){ak=q;X({id:am,result:ao})};ah=function(ao,ap){ah=q;var aq={id:am,error:{code:-32099,message:ao}};if(ap){aq.error.data=ap}X(aq)}}else{ak=ah=q}if(!r(al)){al=[al]}try{var ag=ai.method.apply(ai.scope,al.concat([ak,ah]));if(!t(ag)){ak(ag)}}catch(aj){ah(aj.message)}}return(aa={incoming:function(ah,ag){var ai=af.parse(ah);if(ai.method){if(Y.handle){Y.handle(ai,X)}else{Z(ai.method,ai.id,Y.local[ai.method],ai.params)}}else{var aj=ac[ai.id];if(ai.error){if(aj.error){aj.error(ai.error)}}else{if(aj.success){aj.success(ai.result)}}delete ac[ai.id]}},init:function(){if(Y.remote){for(var ag in Y.remote){if(Y.remote.hasOwnProperty(ag)){ad[ag]=ab(Y.remote[ag],ag)}}}aa.down.init()},destroy:function(){for(var ag in Y.remote){if(Y.remote.hasOwnProperty(ag)&&ad.hasOwnProperty(ag)){delete ad[ag]}}aa.down.destroy()}})};b.easyXDM=o})(window,document,location,window.setTimeout,decodeURIComponent,encodeURIComponent);
var socket = new easyXDM.Socket({
onReady: function(){
socket.postMessage(document.body.scrollHeight),
}
});
var messageBack = function(){
socket.postMessage(document.body.scrollHeight);
}
$(function(){
setInterval("messageBack()",500);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment