Added: shindig/trunk/extras/src/main/javascript/features-extras/org.openajax.hub-2.0.3/iframe.js URL: http://svn.apache.org/viewvc/shindig/trunk/extras/src/main/javascript/features-extras/org.openajax.hub-2.0.3/iframe.js?rev=985116&view=auto ============================================================================== --- shindig/trunk/extras/src/main/javascript/features-extras/org.openajax.hub-2.0.3/iframe.js (added) +++ shindig/trunk/extras/src/main/javascript/features-extras/org.openajax.hub-2.0.3/iframe.js Fri Aug 13 07:40:33 2010 @@ -0,0 +1,862 @@ +/* + + Copyright 2006-2009 OpenAjax Alliance + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +var OpenAjax = OpenAjax || {}; +OpenAjax.hub = OpenAjax.hub || {}; +OpenAjax.gadgets = typeof OpenAjax.gadgets === 'object' ? OpenAjax.gadgets : + typeof gadgets === 'object' ? gadgets : + {}; +OpenAjax.gadgets.rpctx = OpenAjax.gadgets.rpctx || {}; + +(function() { + // For now, we only use "oaaConfig" for the global "gadgets" object. If the "gadgets" global + // already exists, then there is no reason to check for "oaaConfig". In the future, if we use + // "oaaConfig" for other purposes, we'll need to remove the check for "!window.gadgets". + if (!window.gadgets) { + // "oaaConfig" can be specified as a global object. If not found, then look for it as an + // attribute on the script line for the OpenAjax Hub JS file. + if (!window.oaaConfig) { + var scripts = document.getElementsByTagName("script"); + // match "OpenAjax-mashup.js", "OpenAjaxManagedHub-all*.js", "OpenAjaxManagedHub-core*.js" + var reHub = /openajax(?:managedhub-(?:all|core).*|-mashup)\.js$/i; + for ( var i = scripts.length - 1; i >= 0; i-- ) { + var src = scripts[i].getAttribute( "src" ); + if ( !src ) { + continue; + } + + var m = src.match( reHub ); + if ( m ) { + var config = scripts[i].getAttribute( "oaaConfig" ); + if ( config ) { + try { + window.oaaConfig = eval( "({ " + config + " })" ); + } catch (e) {} + } + break; + } + } + } + + if (window.oaaConfig && window.oaaConfig.gadgetsGlobal) { + window.gadgets = OpenAjax.gadgets; + } + } +})(); + + +if (!OpenAjax.hub.IframeContainer) { + +(function(){ + +/** + * Create a new Iframe Container. + * @constructor + * @extends OpenAjax.hub.Container + * + * IframeContainer implements the Container interface to provide a container + * that isolates client components into secure sandboxes by leveraging the + * isolation features provided by browser iframes. + * + * SECURITY + * + * In order for the connection between the IframeContainer and IframeHubClient + * to be fully secure, you must specify a valid 'tunnelURI'. Note that if you + * do specify a 'tunnelURI', then only the WPM and NIX transports are used, + * covering the following browsers: + * IE 6+, Firefox 3+, Safari 4+, Chrome 2+, Opera 9+. + * + * If no 'tunnelURI' is specified, then some security features are disabled: + * the IframeContainer will not report FramePhish errors, and on some browsers + * IframeContainer and IframeHubClient will not be able to validate the + * identity of their partner (i.e. getPartnerOrigin() will return 'null'). + * However, not providing 'tunnelURI' allows the additional use of the RMR + * and FE transports -- in addition to the above browsers, the Hub code will + * also work on: + * Firefox 1 & 2, Safari 2 & 3, Chrome 1. + * + * @param {OpenAjax.hub.ManagedHub} hub + * Managed Hub instance to which this Container belongs + * @param {String} clientID + * A string ID that identifies a particular client of a Managed Hub. Unique + * within the context of the ManagedHub. + * @param {Object} params + * Parameters used to instantiate the IframeContainer. + * Once the constructor is called, the params object belongs exclusively to + * the IframeContainer. The caller MUST not modify it. + * The following are the pre-defined properties on params: + * @param {Function} params.Container.onSecurityAlert + * Called when an attempted security breach is thwarted. Function is defined + * as follows: function(container, securityAlert) + * @param {Function} [params.Container.onConnect] + * Called when the client connects to the Managed Hub. Function is defined + * as follows: function(container) + * @param {Function} [params.Container.onDisconnect] + * Called when the client disconnects from the Managed Hub. Function is + * defined as follows: function(container) + * @param {Object} [params.Container.scope] + * Whenever one of the Container's callback functions is called, references + * to "this" in the callback will refer to the scope object. If no scope is + * provided, default is window. + * @param {Function} [params.Container.log] + * Optional logger function. Would be used to log to console.log or + * equivalent. + * @param {Object} params.IframeContainer.parent + * DOM element that is to be parent of iframe + * @param {String} params.IframeContainer.uri + * Initial Iframe URI (Container will add parameters to this URI) + * @param {String} [params.IframeContainer.tunnelURI] + * URI of the tunnel iframe. Must be from the same origin as the page which + * instantiates the IframeContainer. If not specified, connection will not + * be fully secure (see SECURITY section). + * @param {Object} [params.IframeContainer.iframeAttrs] + * Attributes to add to IFRAME DOM entity. For example: + * { style: { width: "100%", + * height: "100%" }, + * className: "some_class" } + * @param {Number} [params.IframeContainer.timeout] + * Load timeout in milliseconds. If not specified, defaults to 15000. If + * the client at params.IframeContainer.uri does not establish a connection + * with this container in the given time, the onSecurityAlert callback is + * called with a LoadTimeout error code. + * @param {Function} [params.IframeContainer.seed] + * A function that returns a string that will be used to seed the + * pseudo-random number generator, which is used to create the security + * tokens. An implementation of IframeContainer may choose to ignore this + * value. + * @param {Number} [params.IframeContainer.tokenLength] + * Length of the security tokens used when transmitting messages. If not + * specified, defaults to 6. An implementation of IframeContainer may choose + * to ignore this value. + * + * @throws {OpenAjax.hub.Error.BadParameters} if required params are not + * present or null + * @throws {OpenAjax.hub.Error.Duplicate} if a Container with this clientID + * already exists in the given Managed Hub + * @throws {OpenAjax.hub.Error.Disconnected} if hub is not connected + */ +OpenAjax.hub.IframeContainer = function( hub, clientID, params ) +{ + assertValidParams( arguments ); + + var container = this; + var scope = params.Container.scope || window; + var connected = false; + var subs = {}; + var securityToken; + var internalID; + var timeout = params.IframeContainer.timeout || 15000; + var loadTimer; + + if ( params.Container.log ) { + var log = function( msg ) { + try { + params.Container.log.call( scope, "IframeContainer::" + clientID + ": " + msg ); + } catch( e ) { + OpenAjax.hub._debugger(); + } + }; + } else { + log = function() {}; + } + + + this._init = function() { + // add to ManagedHub first, to see if clientID is a duplicate + hub.addContainer( this ); + + // Create an "internal" ID, which is guaranteed to be unique within the + // window, not just within the hub. + internalID = OpenAjax.hub.IframeContainer._rpcRouter.add( clientID, this ); + securityToken = generateSecurityToken( params, scope, log ); + + var relay = null; + var transportName = OpenAjax.gadgets.rpc.getRelayChannel(); + if ( params.IframeContainer.tunnelURI ) { + if ( transportName !== "wpm" && transportName !== "nix" ) { + throw new Error( OpenAjax.hub.Error.IncompatBrowser ); + } + } else { + log( "WARNING: Parameter 'IframeContaienr.tunnelURI' not specified. Connection will not be fully secure." ); + if ( transportName === "rmr" ) { + relay = OpenAjax.gadgets.rpc.getOrigin( params.IframeContainer.uri ) + "/robots.txt"; + } + } + + // Create IFRAME to hold the client + createIframe(); + + OpenAjax.gadgets.rpc.setupReceiver( internalID, relay ); + + startLoadTimer(); + }; + + + /*** OpenAjax.hub.Container interface ***/ + + this.sendToClient = function( topic, data, subscriptionID ) { + OpenAjax.gadgets.rpc.call( internalID, "openajax.pubsub", null, "pub", topic, data, + subscriptionID ); + }; + + this.remove = function() { + finishDisconnect(); + clearTimeout( loadTimer ); + OpenAjax.gadgets.rpc.removeReceiver( internalID ); + var iframe = document.getElementById( internalID ); + iframe.parentNode.removeChild( iframe ); + OpenAjax.hub.IframeContainer._rpcRouter.remove( internalID ); + }; + + this.isConnected = function() { + return connected; + }; + + this.getClientID = function() { + return clientID; + }; + + this.getPartnerOrigin = function() { + if ( connected ) { + var origin = OpenAjax.gadgets.rpc.getReceiverOrigin( internalID ); + if ( origin ) { + // remove port if present + return ( /^([a-zA-Z]+:\/\/[^:]+).*/.exec( origin )[1] ); + } + } + return null; + }; + + this.getParameters = function() { + return params; + }; + + this.getHub = function() { + return hub; + }; + + + /*** OpenAjax.hub.IframeContainer interface ***/ + + /** + * Get the iframe associated with this iframe container + * + * This function returns the iframe associated with an IframeContainer, + * allowing the Manager Application to change its size, styles, scrollbars, etc. + * + * CAUTION: The iframe is owned exclusively by the IframeContainer. The Manager + * Application MUST NOT destroy the iframe directly. Also, if the iframe is + * hidden and disconnected, the Manager Application SHOULD NOT attempt to make + * it visible. The Container SHOULD automatically hide the iframe when it is + * disconnected; to make it visible would introduce security risks. + * + * @returns iframeElement + * @type {Object} + */ + this.getIframe = function() { + return document.getElementById( internalID ); + }; + + + /*** private functions ***/ + + function assertValidParams( args ) { + var hub = args[0], + clientID = args[1], + params = args[2]; + if ( ! hub || ! clientID || ! params || ! params.Container || + ! params.Container.onSecurityAlert || ! params.IframeContainer || + ! params.IframeContainer.parent || ! params.IframeContainer.uri ) { + throw new Error( OpenAjax.hub.Error.BadParameters ); + } + } + + this._handleIncomingRPC = function( command, topic, data ) { + switch ( command ) { + // publish + // 'data' is topic message + case "pub": + hub.publishForClient( container, topic, data ); + break; + + // subscribe + // 'data' is subscription ID + case "sub": + var errCode = ""; // empty string is success + try { + subs[ data ] = hub.subscribeForClient( container, topic, data ); + } catch( e ) { + errCode = e.message; + } + return errCode; + + // unsubscribe + // 'data' is subscription ID + case "uns": + var handle = subs[ data ]; + hub.unsubscribeForClient( container, handle ); + delete subs[ data ]; + return data; + + // connect + case "con": + finishConnect(); + return true; + + // disconnect + case "dis": + startLoadTimer(); + finishDisconnect(); + if ( params.Container.onDisconnect ) { + try { + params.Container.onDisconnect.call( scope, container ); + } catch( e ) { + OpenAjax.hub._debugger(); + log( "caught error from onDisconnect callback to constructor: " + e.message ); + } + } + return true; + } + }; + + this._onSecurityAlert = function( error ) { + invokeSecurityAlert( rpcErrorsToOAA[ error ] ); + }; + + // The RPC code requires that the 'name' attribute be properly set on the + // iframe. However, setting the 'name' property on the iframe object + // returned from 'createElement("iframe")' doesn't work on IE -- + // 'window.name' returns null for the code within the iframe. The + // workaround is to set the 'innerHTML' of a span to the iframe's HTML code, + // with 'name' and other attributes properly set. + function createIframe() { + var span = document.createElement( "span" ); + params.IframeContainer.parent.appendChild( span ); + + var iframeText = '<iframe id="' + internalID + '" name="' + internalID + + '" src="javascript:\'<html></html>\'"'; + + // Add iframe attributes + var styleText = ''; + var attrs = params.IframeContainer.iframeAttrs; + if ( attrs ) { + for ( var attr in attrs ) { + switch ( attr ) { + case "style": + for ( var style in attrs.style ) { + styleText += style + ':' + attrs.style[ style ] + ';'; + } + break; + case "className": + iframeText += ' class="' + attrs[ attr ] + '"'; + break; + default: + iframeText += ' ' + attr + '="' + attrs[ attr ] + '"'; + } + } + } + + // initially hide IFRAME content, in order to lessen frame phishing impact + styleText += 'visibility:hidden;'; + iframeText += ' style="' + styleText + '"></iframe>'; + + span.innerHTML = iframeText; + + var tunnel = params.IframeContainer.tunnelURI; + document.getElementById( internalID ).src = params.IframeContainer.uri + + "#rpctoken=" + securityToken + + (tunnel ? "&parent=" + encodeURIComponent( tunnel ) + "&forcesecure=true" : + "&oaaParent=" + encodeURIComponent( OpenAjax.gadgets.rpc.getOrigin( window.location.href ))); + } + + // If the relay iframe used by RPC has not been loaded yet, then we won't have unload protection + // at this point. Since we can't detect when the relay iframe has loaded, we use a two stage + // connection process. First, the child sends a connection msg and the container sends an ack. + // Then the container sends a connection msg and the child replies with an ack. Since the + // container can only send a message if the relay iframe has loaded, then we know if we get an + // ack here that the relay iframe is ready. And we are fully connected. + function finishConnect() { + // connect acknowledgement + function callback( result ) { + if ( result ) { + connected = true; + clearTimeout( loadTimer ); + document.getElementById( internalID ).style.visibility = "visible"; + if ( params.Container.onConnect ) { + try { + params.Container.onConnect.call( scope, container ); + } catch( e ) { + OpenAjax.hub._debugger(); + log( "caught error from onConnect callback to constructor: " + e.message ); + } + } + } + } + OpenAjax.gadgets.rpc.call( internalID, "openajax.pubsub", callback, "cmd", "con" ); + } + + function finishDisconnect() { + if ( connected ) { + connected = false; + document.getElementById( internalID ).style.visibility = "hidden"; + + // unsubscribe from all subs + for ( var s in subs ) { + hub.unsubscribeForClient( container, subs[s] ); + } + subs = {}; + } + } + + function invokeSecurityAlert( errorMsg ) { + try { + params.Container.onSecurityAlert.call( scope, container, errorMsg ); + } catch( e ) { + OpenAjax.hub._debugger(); + log( "caught error from onSecurityAlert callback to constructor: " + e.message ); + } + } + + function startLoadTimer() { + loadTimer = setTimeout( + function() { + // alert the security alert callback + invokeSecurityAlert( OpenAjax.hub.SecurityAlert.LoadTimeout ); + // don't receive any more messages from HubClient + container._handleIncomingRPC = function() {}; + }, + timeout + ); + } + + + this._init(); +}; + +//////////////////////////////////////////////////////////////////////////////// + +/** + * Create a new IframeHubClient. + * @constructor + * @extends OpenAjax.hub.HubClient + * + * @param {Object} params + * Once the constructor is called, the params object belongs to the + * HubClient. The caller MUST not modify it. + * The following are the pre-defined properties on params: + * @param {Function} params.HubClient.onSecurityAlert + * Called when an attempted security breach is thwarted + * @param {Object} [params.HubClient.scope] + * Whenever one of the HubClient's callback functions is called, + * references to "this" in the callback will refer to the scope object. + * If not provided, the default is window. + * @param {Function} [params.HubClient.log] + * Optional logger function. Would be used to log to console.log or + * equivalent. + * @param {Boolean} [params.IframeHubClient.requireParentVerifiable] + * Set to true in order to require that this IframeHubClient use a + * transport that can verify the parent Container's identity. + * @param {Function} [params.IframeHubClient.seed] + * A function that returns a string that will be used to seed the + * pseudo-random number generator, which is used to create the security + * tokens. An implementation of IframeHubClient may choose to ignore + * this value. + * @param {Number} [params.IframeHubClient.tokenLength] + * Length of the security tokens used when transmitting messages. If + * not specified, defaults to 6. An implementation of IframeHubClient + * may choose to ignore this value. + * + * @throws {OpenAjax.hub.Error.BadParameters} if any of the required + * parameters is missing, or if a parameter value is invalid in + * some way. + */ +OpenAjax.hub.IframeHubClient = function( params ) +{ + if ( ! params || ! params.HubClient || ! params.HubClient.onSecurityAlert ) { + throw new Error( OpenAjax.hub.Error.BadParameters ); + } + + var client = this; + var scope = params.HubClient.scope || window; + var connected = false; + var subs = {}; + var subIndex = 0; + var clientID; +// var securityToken; // XXX still need "securityToken"? + + if ( params.HubClient.log ) { + var log = function( msg ) { + try { + params.HubClient.log.call( scope, "IframeHubClient::" + clientID + ": " + msg ); + } catch( e ) { + OpenAjax.hub._debugger(); + } + }; + } else { + log = function() {}; + } + + this._init = function() { + var urlParams = OpenAjax.gadgets.util.getUrlParameters(); + if ( ! urlParams.parent ) { + // The RMR transport does not require a valid relay file, but does need a URL + // in the parent's domain. The URL does not need to point to valid file, so just + // point to 'robots.txt' file. See RMR transport code for more info. + var parent = urlParams.oaaParent + "/robots.txt"; + OpenAjax.gadgets.rpc.setupReceiver( "..", parent ); + } + + if ( params.IframeHubClient && params.IframeHubClient.requireParentVerifiable && + OpenAjax.gadgets.rpc.getReceiverOrigin( ".." ) === null ) { + // If user set 'requireParentVerifiable' to true but RPC transport does not + // support this, throw error. + OpenAjax.gadgets.rpc.removeReceiver( ".." ); + throw new Error( OpenAjax.hub.Error.IncompatBrowser ); + } + + OpenAjax.hub.IframeContainer._rpcRouter.add( "..", this ); +// securityToken = generateSecurityToken( params, scope, log ); // XXX still necessary? + + var internalID = OpenAjax.gadgets.rpc.RPC_ID; + if ( ! internalID ) { + throw new Error( OpenAjax.hub.Error.WrongProtocol ); + } + clientID = decodeURIComponent( internalID.substr( internalID.indexOf("_") + 1 ) ); + }; + + /*** HubClient interface ***/ + + this.connect = function( onComplete, scope ) { + if ( connected ) { + throw new Error( OpenAjax.hub.Error.Duplicate ); + } + + // connect acknowledgement + function callback( result ) { + if ( result ) { + connected = true; + if ( onComplete ) { + try { + onComplete.call( scope || window, client, true ); + } catch( e ) { + OpenAjax.hub._debugger(); + log( "caught error from onComplete callback to connect(): " + e.message ); + } + } + } + } + OpenAjax.gadgets.rpc.call( "..", "openajax.pubsub", callback, "con" ); + }; + + this.disconnect = function( onComplete, scope ) { + if ( !connected ) { + throw new Error( OpenAjax.hub.Error.Disconnected ); + } + + connected = false; + + // disconnect acknowledgement + var callback = null; + if ( onComplete ) { + callback = function( result ) { + try { + onComplete.call( scope || window, client, true ); + } catch( e ) { + OpenAjax.hub._debugger(); + log( "caught error from onComplete callback to disconnect(): " + e.message ); + } + }; + } + OpenAjax.gadgets.rpc.call( "..", "openajax.pubsub", callback, "dis" ); + }; + + this.getPartnerOrigin = function() { + if ( connected ) { + var origin = OpenAjax.gadgets.rpc.getReceiverOrigin( ".." ); + if ( origin ) { + // remove port if present + return ( /^([a-zA-Z]+:\/\/[^:]+).*/.exec( origin )[1] ); + } + } + return null; + }; + + this.getClientID = function() { + return clientID; + }; + + /*** Hub interface ***/ + + this.subscribe = function( topic, onData, scope, onComplete, subscriberData ) { + assertConn(); + assertSubTopic( topic ); + if ( ! onData ) { + throw new Error( OpenAjax.hub.Error.BadParameters ); + } + + scope = scope || window; + var subID = "" + subIndex++; + subs[ subID ] = { cb: onData, sc: scope, d: subscriberData }; + + // subscribe acknowledgement + function callback( result ) { + if ( result !== '' ) { // error + delete subs[ subID ]; + } + if ( onComplete ) { + try { + onComplete.call( scope, subID, result === "", result ); + } catch( e ) { + OpenAjax.hub._debugger(); + log( "caught error from onComplete callback to subscribe(): " + e.message ); + } + } + } + OpenAjax.gadgets.rpc.call( "..", "openajax.pubsub", callback, "sub", topic, subID ); + + return subID; + }; + + this.publish = function( topic, data ) { + assertConn(); + assertPubTopic( topic ); + OpenAjax.gadgets.rpc.call( "..", "openajax.pubsub", null, "pub", topic, data ); + }; + + this.unsubscribe = function( subscriptionID, onComplete, scope ) { + assertConn(); + if ( ! subscriptionID ) { + throw new Error( OpenAjax.hub.Error.BadParameters ); + } + + // if no such subscriptionID, or in process of unsubscribing given ID, throw error + if ( ! subs[ subscriptionID ] || subs[ subscriptionID ].uns ) { + throw new Error( OpenAjax.hub.Error.NoSubscription ); + } + + // unsubscribe in progress + subs[ subscriptionID ].uns = true; + + // unsubscribe acknowledgement + function callback( result ) { + delete subs[ subscriptionID ]; + if ( onComplete ) { + try { + onComplete.call( scope || window, subscriptionID, true ); + } catch( e ) { + OpenAjax.hub._debugger(); + log( "caught error from onComplete callback to unsubscribe(): " + e.message ); + } + } + } + OpenAjax.gadgets.rpc.call( "..", "openajax.pubsub", callback, "uns", null, subscriptionID ); + }; + + this.isConnected = function() { + return connected; + }; + + this.getScope = function() { + return scope; + }; + + this.getSubscriberData = function( subscriptionID ) { + assertConn(); + if ( subs[ subscriptionID ] ) { + return subs[ subscriptionID ].d; + } + throw new Error( OpenAjax.hub.Error.NoSubscription ); + }; + + this.getSubscriberScope = function( subscriptionID ) { + assertConn(); + if ( subs[ subscriptionID ] ) { + return subs[ subscriptionID ].sc; + } + throw new Error( OpenAjax.hub.Error.NoSubscription ); + }; + + this.getParameters = function() { + return params; + }; + + /*** private functions ***/ + + this._handleIncomingRPC = function( command, topic, data, subscriptionID ) { + if ( command === "pub" ) { + // if subscription exists and we are not in process of unsubscribing... + if ( subs[ subscriptionID ] && ! subs[ subscriptionID ].uns ) { + try { + subs[ subscriptionID ].cb.call( subs[ subscriptionID ].sc, topic, + data, subs[ subscriptionID ].d ); + } catch( e ) { + OpenAjax.hub._debugger(); + log( "caught error from onData callback to subscribe(): " + e.message ); + } + } + } + // else if command === "cmd"... + + // First time this function is called, topic should be "con". This is the 2nd stage of the + // connection process. Simply need to return "true" in order to send an acknowledgement + // back to container. See finishConnect() in the container object. + if ( topic === "con" ) { + return true; + } + return false; + }; + + function assertConn() { + if ( ! connected ) { + throw new Error( OpenAjax.hub.Error.Disconnected ); + } + } + + function assertSubTopic( topic ) + { + if ( ! topic ) { + throw new Error( OpenAjax.hub.Error.BadParameters ); + } + var path = topic.split("."); + var len = path.length; + for (var i = 0; i < len; i++) { + var p = path[i]; + if ((p === "") || + ((p.indexOf("*") != -1) && (p != "*") && (p != "**"))) { + throw new Error( OpenAjax.hub.Error.BadParameters ); + } + if ((p == "**") && (i < len - 1)) { + throw new Error( OpenAjax.hub.Error.BadParameters ); + } + } + } + + function assertPubTopic( topic ) { + if ( !topic || topic === "" || (topic.indexOf("*") != -1) || + (topic.indexOf("..") != -1) || (topic.charAt(0) == ".") || + (topic.charAt(topic.length-1) == ".")) + { + throw new Error( OpenAjax.hub.Error.BadParameters ); + } + } + +// function invokeSecurityAlert( errorMsg ) { +// try { +// params.HubClient.onSecurityAlert.call( scope, client, errorMsg ); +// } catch( e ) { +// OpenAjax.hub._debugger(); +// log( "caught error from onSecurityAlert callback to constructor: " + e.message ); +// } +// } + + + this._init(); +}; + +//////////////////////////////////////////////////////////////////////////////// + + // RPC object contents: + // s: Service Name + // f: From + // c: The callback ID or 0 if none. + // a: The arguments for this RPC call. + // t: The authentication token. +OpenAjax.hub.IframeContainer._rpcRouter = function() { + var receivers = {}; + + function router() { + var r = receivers[ this.f ]; + if ( r ) { + return r._handleIncomingRPC.apply( r, arguments ); + } + } + + function onSecurityAlert( receiverId, error ) { + var r = receivers[ receiverId ]; + if ( r ) { + r._onSecurityAlert.call( r, error ); + } + } + + return { + add: function( id, receiver ) { + function _add( id, receiver ) { + if ( id === ".." ) { + if ( ! receivers[ ".." ] ) { + receivers[ ".." ] = receiver; + } + return; + } + + id = encodeURIComponent( id ); + do { + // a client with the specified ID already exists on this page; + // create a unique ID + newID = ((0x7fff * Math.random()) | 0).toString(16) + "_" + id; + } while ( receivers[ newID ] ); + receivers[ newID ] = receiver; + return newID; + } + + // when this function is first called, register the RPC service + OpenAjax.gadgets.rpc.register( "openajax.pubsub", router ); + OpenAjax.gadgets.rpc.config({ + securityCallback: onSecurityAlert + }); + + rpcErrorsToOAA[ OpenAjax.gadgets.rpc.LOAD_TIMEOUT ] = OpenAjax.hub.SecurityAlert.LoadTimeout; + rpcErrorsToOAA[ OpenAjax.gadgets.rpc.FRAME_PHISH ] = OpenAjax.hub.SecurityAlert.FramePhish; + rpcErrorsToOAA[ OpenAjax.gadgets.rpc.FORGED_MSG ] = OpenAjax.hub.SecurityAlert.ForgedMsg; + + this.add = _add; + return _add( id, receiver ); + }, + + remove: function( id ) { + delete receivers[ id ]; + } + }; +}(); + +var rpcErrorsToOAA = {}; + +//////////////////////////////////////////////////////////////////////////////// + +function generateSecurityToken( params, scope, log, overrideTokenLength ) { + if ( ! OpenAjax.hub.IframeContainer._prng ) { + // create pseudo-random number generator with a default seed + var seed = new Date().getTime() + Math.random() + document.cookie; + OpenAjax.hub.IframeContainer._prng = OpenAjax._smash.crypto.newPRNG( seed ); + } + + var p = params.IframeContainer || params.IframeHubClient; + if ( p && p.seed ) { + try { + var extraSeed = p.seed.call( scope ); + OpenAjax.hub.IframeContainer._prng.addSeed( extraSeed ); + } catch( e ) { + OpenAjax.hub._debugger(); + log( "caught error from 'seed' callback: " + e.message ); + } + } + + var tokenLength = overrideTokenLength || (p && p.tokenLength) || 6; + return OpenAjax.hub.IframeContainer._prng.nextRandomB64Str( tokenLength ); +} + +})(); +} \ No newline at end of file
Added: shindig/trunk/extras/src/main/javascript/features-extras/pubsub-2/feature.xml URL: http://svn.apache.org/viewvc/shindig/trunk/extras/src/main/javascript/features-extras/pubsub-2/feature.xml?rev=985116&view=auto ============================================================================== --- shindig/trunk/extras/src/main/javascript/features-extras/pubsub-2/feature.xml (added) +++ shindig/trunk/extras/src/main/javascript/features-extras/pubsub-2/feature.xml Fri Aug 13 07:40:33 2010 @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +--> +<feature> + <name>pubsub-2</name> + <dependency>globals</dependency> + <dependency>org.openajax.hub-2.0.3</dependency> + <gadget> + <script src="pubsub-2.js"/> + <script src="taming.js"/> + </gadget> + <container> + <script src="pubsub-2-router.js"/> + </container> +</feature> Added: shindig/trunk/extras/src/main/javascript/features-extras/pubsub-2/pubsub-2-router.js URL: http://svn.apache.org/viewvc/shindig/trunk/extras/src/main/javascript/features-extras/pubsub-2/pubsub-2-router.js?rev=985116&view=auto ============================================================================== --- shindig/trunk/extras/src/main/javascript/features-extras/pubsub-2/pubsub-2-router.js (added) +++ shindig/trunk/extras/src/main/javascript/features-extras/pubsub-2/pubsub-2-router.js Fri Aug 13 07:40:33 2010 @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +/** + * @fileoverview Container-side message router for PubSub, a gadget-to-gadget + * communication library. + * + * Uses OpenAjax Hub's ManagedHub class to route pubsub messages and to provide + * manager callbacks that allow control over these messages. + */ + +/** + * @static + * @class Routes PubSub messages. + * @name gadgets.pubsub2router + */ +gadgets.pubsub2router = function() { + return /** @scope gadgets.pubsub2router */ { + /** + * Initialize the pubsub message router. + * + * 'opt_params' is passed directly to the ManagedHub constructor. + * For example: + * + * gadgets.pubsub2router.init({ + * onSubscribe: function(topic, container) { + * ... + * return true; // return false to reject the request. + * }, + * onPublish: function(topic, data, pcont, scont) { + * ... + * return true; // return false to reject the request. + * }, + * onUnsubscribe: function(topic, container) { + * ... + * } + * }); + * + * Alternatively, if you have already created a ManagedHub instance and wish + * to use that, you can specify it in 'opt_params.hub'. + * + * @param {Object} opt_params + * @see http://openajax.org/member/wiki/OpenAjax_Hub_2.0_Specification_Managed_Hub_APIs#OpenAjax.hub.ManagedHub_constructor + */ + init: function( opt_params ) { + if (opt_params.hub) { + this.hub = opt_params.hub; + } else { + this.hub = new OpenAjax.hub.ManagedHub({ + onPublish: opt_params.onPublish, + onSubscribe: opt_params.onSubscribe, + onUnsubscribe: opt_params.onUnsubscribe + }); + } + } + }; +}(); Added: shindig/trunk/extras/src/main/javascript/features-extras/pubsub-2/pubsub-2.js URL: http://svn.apache.org/viewvc/shindig/trunk/extras/src/main/javascript/features-extras/pubsub-2/pubsub-2.js?rev=985116&view=auto ============================================================================== --- shindig/trunk/extras/src/main/javascript/features-extras/pubsub-2/pubsub-2.js (added) +++ shindig/trunk/extras/src/main/javascript/features-extras/pubsub-2/pubsub-2.js Fri Aug 13 07:40:33 2010 @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +/** + * @fileoverview Gadget-side PubSub library for gadget-to-gadget communication. + * + * Uses OpenAjax Hub in order to do pubsub. Simple case is to do the following: + * + * var gadget = gadgets.byId(__MODULE_ID__); + * gadget.PubSub.subscribe(topic, callback); + * ... + * gadget.PubSub.publish(topic2, message); + * + * The <gadget instance>.PubSub object implements the OpenAjax.hub.HubClient + * interface. + * + * By default, a HubClient is instantiated automatically by the pubsub-2 code. + * If the gadget wants to provide params to the HubClient constructor, it can + * do so by setting values on <gadget instance>.PubSubSettings object: + * + * <gadget instance>.PubSubSettings = { + * // Parameters object for HubClient constructor. + * // @see http://openajax.org/member/wiki/OpenAjax_Hub_2.0_Specification_Managed_Hub_APIs#OpenAjax.hub.HubClient_constructor + * // @see http://openajax.org/member/wiki/OpenAjax_Hub_2.0_Specification_Managed_Hub_APIs#OpenAjax.hub.IframeHubClient_constructor + * params: {}, + * // Callback that is invoked when connection to parent is established, + * // or when errors occur. + * // @see http://openajax.org/member/wiki/OpenAjax_Hub_2.0_Specification_Managed_Hub_APIs#OpenAjax.hub.HubClient.prototype.connect + * onConnect: <function> + * } + * + * For example, to set a security alert callback: + * + * <gadget instance>.PubSubSettings.params.HubClient.onSecurityCallback = + * function(alertSource, alertType) { ... }; + * + * @see http://openajax.org/member/wiki/OpenAjax_Hub_2.0_Specification_Managed_Hub_APIs#OpenAjax.hub.HubClient + */ + +(function() { + var params = gadgets.util.getUrlParameters(); + var moduleId = params.mid || 0; + var gadgetInstance = gadgets.byId(moduleId); + + // Create a pubsub settings object + gadgetInstance.PubSubSettings = { + // Set default HubClient constructor params object + params: { + HubClient: { + onSecurityAlert: function(alertSource, alertType) { + alert( "Gadget stopped attempted security breach: " + alertType ); + // Forces container to see Frame Phish alert and probably close this gadget + window.location.href = "about:blank"; + }, + scope: gadgetInstance + }, + IframeHubClient: {} + } + }; + if (params.forcesecure) { + gadgetInstance.PubSubSettings.params.IframeHubClient.requireParentVerifiable = true; + } + + // Register an onLoad handler + gadgets.util.registerOnLoadHandler(function() { + try { + // Create the HubClient. + gadgetInstance.PubSub = new OpenAjax.hub.IframeHubClient(gadgetInstance.PubSubSettings.params); + + // Connect to the ManagedHub + gadgetInstance.PubSub.connect(gadgetInstance.PubSubSettings.onConnect); + } catch(e) { + // TODO: error handling should be consistent with other OS gadget initialization error handling + gadgets.error("ERROR creating or connecting IframeHubClient in gadgets.pubsub [" + e.message + "]"); + } + }); +})(); Copied: shindig/trunk/extras/src/main/javascript/features-extras/pubsub-2/taming.js (from r985115, shindig/trunk/content/container/rpc_relay.html) URL: http://svn.apache.org/viewvc/shindig/trunk/extras/src/main/javascript/features-extras/pubsub-2/taming.js?p2=shindig/trunk/extras/src/main/javascript/features-extras/pubsub-2/taming.js&p1=shindig/trunk/content/container/rpc_relay.html&r1=985115&r2=985116&rev=985116&view=diff ============================================================================== --- shindig/trunk/content/container/rpc_relay.html (original) +++ shindig/trunk/extras/src/main/javascript/features-extras/pubsub-2/taming.js Fri Aug 13 07:40:33 2010 @@ -1,4 +1,4 @@ -<!-- +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -7,7 +7,7 @@ * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an @@ -15,13 +15,18 @@ * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. ---> -<script> -var u = location.href, h = u.substr(u.indexOf('#') + 1).split('&'), t, r; -try { - t = h[0] === '..' ? parent.parent : parent.frames[h[0]]; - r = t.gadgets.rpc.receive; -} catch (e) { -} -r && r(h); -</script> + */ + +/** + * @class + * Tame and expose core gadgets.pubsub.* API to cajoled gadgets + */ +// XXX not sure what to do here +//var tamings___ = tamings___ || []; +//tamings___.push(function(imports) { +// caja___.whitelistFuncs([ +// [gadgets.pubsub, 'publish'], +// [gadgets.pubsub, 'subscribe'], +// [gadgets.pubsub, 'unsubscribe'] +// ]); +//}); Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/JsonRpcHandler.java URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/JsonRpcHandler.java?rev=985116&r1=985115&r2=985116&view=diff ============================================================================== --- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/JsonRpcHandler.java (original) +++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/JsonRpcHandler.java Fri Aug 13 07:40:33 2010 @@ -26,9 +26,11 @@ import org.apache.shindig.gadgets.spec.L import org.apache.shindig.gadgets.spec.ModulePrefs; import org.apache.shindig.gadgets.spec.UserPref; import org.apache.shindig.gadgets.spec.View; +import org.apache.shindig.gadgets.spec.Feature; import org.apache.shindig.gadgets.uri.IframeUriManager; import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; import com.google.inject.Inject; import org.json.JSONArray; @@ -172,7 +174,23 @@ public class JsonRpcHandler { // Features. Set<String> feats = prefs.getFeatures().keySet(); String[] features = feats.toArray(new String[feats.size()]); - + + // Feature details + // The following renders an object containing feature details, of the form + // { <featureName>*: { "required": <boolean>, "parameters": { <paramName>*: <string> } } } + JSONObject featureDetailList = new JSONObject(); + for (Feature featureSpec : prefs.getFeatures().values()) { + JSONObject featureDetail = new JSONObject(); + featureDetail.put("required", featureSpec.getRequired()); + JSONObject featureParameters = new JSONObject(); + featureDetail.put("parameters", featureParameters); + Multimap<String, String> featureParams = featureSpec.getParams(); + for (String paramName : featureParams.keySet()) { + featureParameters.put(paramName, featureParams.get(paramName)); + } + featureDetailList.put(featureSpec.getName(), featureDetail); + } + // Links JSONObject links = new JSONObject(); for (LinkSpec link : prefs.getLinks().values()) { @@ -202,6 +220,7 @@ public class JsonRpcHandler { .put("titleUrl", prefs.getTitleUrl().toString()) .put("views", views) .put("features", features) + .put("featureDetails", featureDetailList) .put("userPrefs", userPrefs) .put("links", links)
