UNPKG

cloudcms-server

Version:
1,294 lines (1,225 loc) 113 kB
/** * 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