Author: johnh
Date: Tue Oct 27 17:14:32 2009
New Revision: 830254
URL: http://svn.apache.org/viewvc?rev=830254&view=rev
Log:
Use of the gadgets.rpc library has slowly changed over time due to the
transports that underlie it. This has led to a scenario in which it's
unnecessarily confusing and error-prone to set up and use the library.
This is particularly so now that no transports require a custom relay to be
served and used. All a gadget/child needs is its parent's URL and an rpctoken.
All the container needs is the child's IFRAME URL and the token it created
itself.
Further, many people are using this library in a generic child IFRAME context.
The primary complication with this is how the in-IFRAME initialization routine
works, namely that it uses gadgets.config, which is confusing to mock out.
This CL attempts to remedy all of the above. It sets up a dead-simple way to use
the library. Any container wishing to create and communicate with an
alternate-domain IFRAME need only follow this protocol:
* Include rpc.js lib.
* Create IFRAME with id="<ID>" and a src URL with parameters "parent=<MY_URL>"
and "rpctoken=<RANDOM_STRING>" and add it (anywhere) to the DOM.
- For optimal caching, these params may be placed on the fragment/hash.
* Call gadgets.rpc.setupReceiver("<ID>");
Assuming the child IFRAME includes rpc.js as well, all communication setup
happens automatically from here. Variations on this are supported if absolutely
necessary, but are not recommended.
This implementation is fully backward-compatible w/ the existing
gadget-initialized mechanism. It's not exactly a drop-in cross-domain solution
in that gadgets.util.getUrlParameters() is required (meaning you need to source
gadget.com/gadgets/js/rpc.js?c=1 on both sides, not the raw rpc.js files), but
is nevertheless much cleaner.
Modified:
incubator/shindig/trunk/features/src/main/javascript/features/core/util.js
incubator/shindig/trunk/features/src/main/javascript/features/rpc/rpc.js
incubator/shindig/trunk/javascript/container/rpctest_container.html
Modified:
incubator/shindig/trunk/features/src/main/javascript/features/core/util.js
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/features/src/main/javascript/features/core/util.js?rev=830254&r1=830253&r2=830254&view=diff
==============================================================================
--- incubator/shindig/trunk/features/src/main/javascript/features/core/util.js
(original)
+++ incubator/shindig/trunk/features/src/main/javascript/features/core/util.js
Tue Oct 27 17:14:32 2009
@@ -34,11 +34,11 @@
* Parses URL parameters into an object.
* @return {Array.<String>} The parameters
*/
- function parseUrlParams() {
+ function parseUrlParams(url) {
// Get settings from url, 'hash' takes precedence over 'search' component
// don't use document.location.hash due to browser differences.
var query;
- var l = document.location.href;
+ var l = url;
var queryIdx = l.indexOf("?");
var hashIdx = l.indexOf("#");
if (hashIdx === -1) {
@@ -108,16 +108,20 @@
/**
* Gets the URL parameters.
*
+ * @param {String} opt_url Optional URL whose parameters to parse.
+ * Defaults to window's current URL.
* @return {Object} Parameters passed into the query string
* @member gadgets.util
* @private Implementation detail.
*/
- getUrlParameters : function () {
- if (parameters !== null) {
+ getUrlParameters : function (opt_url) {
+ if (parameters !== null && typeof opt_url === "undefined") {
+ // "parameters" is a cache of current window params only.
return parameters;
}
+ var parsed = {};
parameters = {};
- var pairs = parseUrlParams();
+ var pairs = parseUrlParams(opt_url || document.location.href);
var unesc = window.decodeURIComponent ? decodeURIComponent : unescape;
for (var i = 0, j = pairs.length; i < j; ++i) {
var pos = pairs[i].indexOf('=');
@@ -130,9 +134,13 @@
// argname. Unclear on if it should do:
// argname = argname.replace(/\+/g, " ");
value = value.replace(/\+/g, " ");
- parameters[argName] = unesc(value);
+ parsed[argName] = unesc(value);
}
- return parameters;
+ if (typeof opt_url === "undefined") {
+ // Cache current-window params in parameters var.
+ parameters = parsed;
+ }
+ return parsed;
},
/**
Modified:
incubator/shindig/trunk/features/src/main/javascript/features/rpc/rpc.js
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/features/src/main/javascript/features/rpc/rpc.js?rev=830254&r1=830253&r2=830254&view=diff
==============================================================================
--- incubator/shindig/trunk/features/src/main/javascript/features/rpc/rpc.js
(original)
+++ incubator/shindig/trunk/features/src/main/javascript/features/rpc/rpc.js
Tue Oct 27 17:14:32 2009
@@ -66,8 +66,7 @@
// Special service name for acknowledgements.
var ACK = '__ack';
- // Timeout and number of setup attempts between each
- // for when setAuthToken is called before the target it specifies exists.
+ // Timeout and number of attempts made to setup a transport receiver.
var SETUP_FRAME_TIMEOUT = 500;
var SETUP_FRAME_MAX_TRIES = 10;
@@ -84,7 +83,7 @@
var earlyRpcQueue = {};
// isGadget =~ isChild for the purposes of rpc (used only in setup).
- var isGadget = (window.top !== window.self);
+ var isChild = (window.top !== window.self);
// Set the current rpc ID from window.name immediately, to prevent
// shadowing of window.name by a "var name" declaration, or similar.
@@ -99,7 +98,7 @@
gadgets.log("gadgets.rpc." + name + "(" +
gadgets.json.stringify(Array.prototype.slice.call(arguments)) +
"): call ignored. [caller: " + document.location +
- ", isGadget: " + isGadget + "]");
+ ", isChild: " + isChild + "]");
}
}
return {
@@ -121,11 +120,9 @@
params = gadgets.util.getUrlParameters();
}
- authToken['..'] = params.rpctoken || params.ifpctok || "";
-
// Indicates whether to support early-message queueing, which is designed
// to ensure that all messages sent by gadgets.rpc.call, irrespective
- // when they were made (before/after setAuthToken, before/after transport
+ // when they were made (before/after setupReceiver, before/after transport
// setup complete), are sent. Hiding behind a query param to allow opt-in
// for a time while this technique is proven.
var useEarlyQueueing = (params['rpc_earlyq'] === "1");
@@ -172,7 +169,7 @@
for (var i = 0; i < earlyQueue.length; ++i) {
var rpc = earlyQueue[i];
// There was no auth/rpc token set before, so set it now.
- rpc.t = gadgets.rpc.getAuthToken(receiverId);
+ rpc.t = getAuthToken(receiverId);
tx.call(receiverId, rpc.f, rpc);
}
@@ -200,7 +197,7 @@
// Validate auth token.
if (authToken[rpc.f]) {
// We don't do type coercion here because all entries in the authToken
- // object are strings, as are all url params. See setAuthToken(...).
+ // object are strings, as are all url params. See setupReceiver(...).
if (authToken[rpc.f] !== rpc.t) {
throw new Error("Invalid auth token. " +
authToken[rpc.f] + " vs " + rpc.t);
@@ -398,8 +395,27 @@
return false;
}
- // gadgets.config might not be available, such as when serving container js.
- if (isGadget && gadgets.config) {
+ function setRelayUrl(targetId, url, opt_useLegacy) {
+ relayUrl[targetId] = url;
+ useLegacyProtocol[targetId] = !!opt_useLegacy;
+ }
+
+ function getAuthToken(targetId) {
+ return authToken[targetId];
+ }
+
+ function setAuthToken(targetId, token) {
+ token = token || "";
+
+ // Coerce token to a String, ensuring that all authToken values
+ // are strings. This ensures correct comparison with URL params
+ // in the process(rpc) method.
+ authToken[targetId] = String(token);
+
+ setupFrame(targetId, token);
+ }
+
+ function setupContainerGadgetContext(rpctoken) {
/**
* Initializes gadget to container RPC params from the provided
configuration.
*/
@@ -429,21 +445,17 @@
}
}
}
- relayUrl['..'] = parentRelayUrl;
var useLegacy = !!configRpc.useLegacyProtocol;
- useLegacyProtocol['..'] = useLegacy;
+ setRelayUrl('..', parentRelayUrl, useLegacy);
+
if (useLegacy) {
transport = gadgets.rpctx.ifpc;
transport.init(process, transportReady);
}
- // Here, we add a hook for the transport to actively set up
- // gadget -> container communication. Running here ensures
- // that relayUri info will be available.
- if (transport.setup('..') === false) {
- receiverTx['..'] = fallbackTransport;
- }
+ // Sets the auth token and signals transport to setup connection to
container.
+ setAuthToken('..', rpctoken);
}
var requiredConfig = {
@@ -452,6 +464,59 @@
gadgets.config.register("rpc", requiredConfig, init);
}
+ function setupContainerGenericIframe(rpctoken, opt_parent) {
+ // Generic child IFRAME setting up connection w/ its container.
+ // Use the opt_parent param if provided, or the "parent" query param
+ // if found -- otherwise, do nothing since this call might be initiated
+ // automatically at first, then actively later in IFRAME code.
+ var parent = opt_parent || params.parent;
+ if (parent) {
+ setRelayUrl('..', parent);
+ setAuthToken('..', rpctoken);
+ }
+ }
+
+ function setupChildIframe(gadgetId, opt_frameurl, opt_authtoken) {
+ if (!gadgets.util) {
+ return;
+ }
+ var childIframe = document.getElementById(gadgetId);
+ if (!childIframe) {
+ throw new Error("Cannot set up gadgets.rpc receiver with ID: " +
gadgetId +
+ ", element not found.");
+ }
+
+ // The "relay URL" can either be explicitly specified or is set as
+ // the child IFRAME URL verbatim.
+ var relayUrl = opt_frameurl || childIframe.src;
+ setRelayUrl(gadgetId, relayUrl);
+
+ // The auth token is parsed from child params (rpctoken) or overridden.
+ var childParams = gadgets.util.getUrlParameters(childIframe.src);
+ var rpctoken = opt_authtoken || childParams.rpctoken;
+ setAuthToken(gadgetId, rpctoken);
+ }
+
+ function setupReceiver(targetId, opt_receiverurl, opt_authtoken) {
+ if (targetId === '..') {
+ // Gadget/IFRAME to container.
+ var rpctoken = opt_authtoken || params.rpctoken || params.ifpctok || "";
+ if (gadgets.config) {
+ setupContainerGadgetContext(rpctoken);
+ } else {
+ setupContainerGenericIframe(rpctoken, opt_receiverurl);
+ }
+ } else {
+ // Container to child.
+ setupChildIframe(targetId, opt_receiverurl, opt_authtoken);
+ }
+ }
+
+ // gadgets.config might not be available, such as when serving container js.
+ if (isChild) {
+ setupReceiver('..');
+ }
+
return /** @scope gadgets.rpc */ {
/**
* Registers an RPC service.
@@ -617,11 +682,9 @@
* wire format.
*
* @member gadgets.rpc
+ * @deprecated
*/
- setRelayUrl: function(targetId, url, opt_useLegacy) {
- relayUrl[targetId] = url;
- useLegacyProtocol[targetId] = !!opt_useLegacy;
- },
+ setRelayUrl: setRelayUrl,
/**
* Sets the auth token of a target frame.
@@ -630,24 +693,49 @@
* calls to or from this target id.
*
* @member gadgets.rpc
+ * @deprecated
*/
- setAuthToken: function(targetId, token) {
- token = token || "";
-
- // Coerce token to a String, ensuring that all authToken values
- // are strings. This ensures correct comparison with URL params
- // in the process(rpc) method.
- authToken[targetId] = String(token);
+ setAuthToken: setAuthToken,
- setupFrame(targetId, token);
- },
+ /**
+ * Sets up the gadgets.rpc library to communicate with the receiver.
+ * This method replaces setRelayUrl(...) and setAuthToken(...)
+ *
+ * Simplified instructions - highly recommended:
+ * 1. Generate <iframe id="<ID>"
url="...#parent=<PARENTURL>&rpctoken=<RANDOM>"/>
+ * and add to DOM.
+ * 2. Call gadgets.rpc.setupReceiver("<ID>");
+ * --> All parent/child communication initializes automatically from here.
+ * Naturally, both sides need to include the library.
+ *
+ * Detailed container/parent instructions:
+ * 1. Create the target IFRAME (eg. gadget) with a given <ID> and params
+ * rpctoken=<token> (eg. #rpctoken=1234), which is a random/unguessbable
+ * string, and parent=<url>, where <url> is the URL of the container.
+ * 2. Append IFRAME to the document.
+ * 3. Call gadgets.rpc.setupReceiver(<ID>)
+ * [Optional]. Strictly speaking, you may omit rpctoken and parent. This
+ * practice earns little but is occasionally useful for
testing.
+ * If you omit parent, you MUST pass your container URL as the
2nd
+ * parameter to this method.
+ *
+ * Detailed gadget/child IFRAME instructions:
+ * 0. If your container/parent passed parent and rpctoken params (query
string
+ * or fragment are both OK), you needn't do anything. The library will
self-
+ * initialize.
+ * 1. If "parent" is omitted, you MUST call this method with targetId '..'
+ * and the second param set to the parent URL.
+ * 2. If "rpctoken" is omitted, but the container set an authToken manually
+ * for this frame, you MUST pass that ID (however acquired) as the 2nd
param
+ * to this method.
+ */
+ setupReceiver: setupReceiver,
/**
* Helper method to retrieve the authToken for a given gadget.
+ * Not to be used directly.
*/
- getAuthToken: function(targetId) {
- return authToken[targetId];
- },
+ getAuthToken: getAuthToken,
/**
* Gets the RPC relay mechanism.
@@ -669,10 +757,10 @@
* JSON-encoded and URI escaped packet data.
*
* @member gadgets.rpc
+ * @deprecated
*/
receive: function(fragment) {
if (fragment.length > 4) {
- // TODO parse fragment[1..3] to merge multi-fragment messages
process(gadgets.json.parse(
decodeURIComponent(fragment[fragment.length - 1])));
}
Modified: incubator/shindig/trunk/javascript/container/rpctest_container.html
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/container/rpctest_container.html?rev=830254&r1=830253&r2=830254&view=diff
==============================================================================
--- incubator/shindig/trunk/javascript/container/rpctest_container.html
(original)
+++ incubator/shindig/trunk/javascript/container/rpctest_container.html Tue Oct
27 17:14:32 2009
@@ -112,8 +112,7 @@
// "correct" way.
container.innerHTML = iframeHtml;
document.getElementById('gadget').src = renderUrl;
- gadgets.rpc.setRelayUrl('gadget', gadgetrelay);
- gadgets.rpc.setAuthToken('gadget', secret);
+ gadgets.rpc.setupReceiver('gadget'); // use the new init API, which
parses out all needed variables.
}
}