diff --git a/authority/jsapi/include.js b/authority/jsapi/include.js index a6c04a97f10856f38920d4bd87c46842b5a962a7..4f6847205daac3164f864305b24c76d595a744af 100644 --- a/authority/jsapi/include.js +++ b/authority/jsapi/include.js @@ -1,539 +1,539 @@ // this is the file that the RP includes to shim in the // navigator.id.getVerifiedEmail() function -if (!navigator.id) { navigator.id = {} } + if (!navigator.id) { navigator.id = {} } if (!navigator.id.getVerifiedEmail) { - // local embedded copy of jschannel: http://github.com/mozilla/jschannel - var Channel = (function() { - // current transaction id, start out at a random *odd* number between 1 and a million - // There is one current transaction counter id per page, and it's shared between - // channel instances. That means of all messages posted from a single javascript - // evaluation context, we'll never have two with the same id. - var s_curTranId = Math.floor(Math.random()*1000001); - - // no two bound channels in the same javascript evaluation context may have the same origin & scope. - // futher if two bound channels have the same scope, they may not have *overlapping* origins - // (either one or both support '*'). This restriction allows a single onMessage handler to efficiently - // route messages based on origin and scope. The s_boundChans maps origins to scopes, to message - // handlers. Request and Notification messages are routed using this table. - // Finally, channels are inserted into this table when built, and removed when destroyed. - var s_boundChans = { }; - - // add a channel to s_boundChans, throwing if a dup exists - function s_addBoundChan(origin, scope, handler) { - // does she exist? - var exists = false; - if (origin === '*') { - // we must check all other origins, sadly. - for (var k in s_boundChans) { - if (!s_boundChans.hasOwnProperty(k)) continue; - if (k === '*') continue; - if (typeof s_boundChans[k][scope] === 'object') { - exists = true; - } - } - } else { - // we must check only '*' - if ((s_boundChans['*'] && s_boundChans['*'][scope]) || - (s_boundChans[origin] && s_boundChans[origin][scope])) - { - exists = true; - } - } - if (exists) throw "A channel already exists which overlaps with origin '"+ origin +"' and has scope '"+scope+"'"; + // local embedded copy of jschannel: http://github.com/mozilla/jschannel + var Channel = (function() { + // current transaction id, start out at a random *odd* number between 1 and a million + // There is one current transaction counter id per page, and it's shared between + // channel instances. That means of all messages posted from a single javascript + // evaluation context, we'll never have two with the same id. + var s_curTranId = Math.floor(Math.random()*1000001); + + // no two bound channels in the same javascript evaluation context may have the same origin & scope. + // futher if two bound channels have the same scope, they may not have *overlapping* origins + // (either one or both support '*'). This restriction allows a single onMessage handler to efficiently + // route messages based on origin and scope. The s_boundChans maps origins to scopes, to message + // handlers. Request and Notification messages are routed using this table. + // Finally, channels are inserted into this table when built, and removed when destroyed. + var s_boundChans = { }; + + // add a channel to s_boundChans, throwing if a dup exists + function s_addBoundChan(origin, scope, handler) { + // does she exist? + var exists = false; + if (origin === '*') { + // we must check all other origins, sadly. + for (var k in s_boundChans) { + if (!s_boundChans.hasOwnProperty(k)) continue; + if (k === '*') continue; + if (typeof s_boundChans[k][scope] === 'object') { + exists = true; + } + } + } else { + // we must check only '*' + if ((s_boundChans['*'] && s_boundChans['*'][scope]) || + (s_boundChans[origin] && s_boundChans[origin][scope])) + { + exists = true; + } + } + if (exists) throw "A channel already exists which overlaps with origin '"+ origin +"' and has scope '"+scope+"'"; + + if (typeof s_boundChans[origin] != 'object') s_boundChans[origin] = { }; + s_boundChans[origin][scope] = handler; + } + + function s_removeBoundChan(origin, scope) { + delete s_boundChans[origin][scope]; + // possibly leave a empty object around. whatevs. + } + + function s_isArray(obj) { + if (Array.isArray) return Array.isArray(obj); + else { + return (obj.constructor.toString().indexOf("Array") != -1); + } + } + + // No two outstanding outbound messages may have the same id, period. Given that, a single table + // mapping "transaction ids" to message handlers, allows efficient routing of Callback, Error, and + // Response messages. Entries are added to this table when requests are sent, and removed when + // responses are received. + var s_transIds = { }; + + // class singleton onMessage handler + // this function is registered once and all incoming messages route through here. This + // arrangement allows certain efficiencies, message data is only parsed once and dispatch + // is more efficient, especially for large numbers of simultaneous channels. + var s_onMessage = function(e) { + var m = JSON.parse(e.data); + if (typeof m !== 'object') return; + + var o = e.origin; + var s = null; + var i = null; + var meth = null; + + if (typeof m.method === 'string') { + var ar = m.method.split('::'); + if (ar.length == 2) { + s = ar[0]; + meth = ar[1]; + } else { + meth = m.method; + } + } + + if (typeof m.id !== 'undefined') i = m.id; + + // o is message origin + // m is parsed message + // s is message scope + // i is message id (or null) + // meth is unscoped method name + // ^^ based on these factors we can route the message + + // if it has a method it's either a notification or a request, + // route using s_boundChans + if (typeof meth === 'string') { + if (s_boundChans[o] && s_boundChans[o][s]) { + s_boundChans[o][s](o, meth, m); + } else if (s_boundChans['*'] && s_boundChans['*'][s]) { + s_boundChans['*'][s](o, meth, m); + } + } + // otherwise it must have an id (or be poorly formed + else if (typeof i != 'undefined') { + if (s_transIds[i]) s_transIds[i](o, meth, m); + } + }; - if (typeof s_boundChans[origin] != 'object') s_boundChans[origin] = { }; - s_boundChans[origin][scope] = handler; + // Setup postMessage event listeners + if (window.addEventListener) window.addEventListener('message', s_onMessage, false); + else if(window.attachEvent) window.attachEvent('onmessage', s_onMessage); + + /* a messaging channel is constructed from a window and an origin. + * the channel will assert that all messages received over the + * channel match the origin + * + * Arguments to Channel.build(cfg): + * + * cfg.window - the remote window with which we'll communication + * cfg.origin - the expected origin of the remote window, may be '*' + * which matches any origin + * cfg.scope - the 'scope' of messages. a scope string that is + * prepended to message names. local and remote endpoints + * of a single channel must agree upon scope. Scope may + * not contain double colons ('::'). + * cfg.debugOutput - A boolean value. If true and window.console.log is + * a function, then debug strings will be emitted to that + * function. + * cfg.debugOutput - A boolean value. If true and window.console.log is + * a function, then debug strings will be emitted to that + * function. + * cfg.postMessageObserver - A function that will be passed two arguments, + * an origin and a message. It will be passed these immediately + * before messages are posted. + * cfg.gotMessageObserver - A function that will be passed two arguments, + * an origin and a message. It will be passed these arguments + * immediately after they pass scope and origin checks, but before + * they are processed. + * cfg.onReady - A function that will be invoked when a channel becomes "ready", + * this occurs once both sides of the channel have been + * instantiated and an application level handshake is exchanged. + * the onReady function will be passed a single argument which is + * the channel object that was returned from build(). + */ + return { + build: function(cfg) { + var debug = function(m) { + if (cfg.debugOutput && window.console && window.console.log) { + // try to stringify, if it doesn't work we'll let javascript's built in toString do its magic + try { if (typeof m !== 'string') m = JSON.stringify(m); } catch(e) { } + console.log("["+chanId+"] " + m); + } } - function s_removeBoundChan(origin, scope) { - delete s_boundChans[origin][scope]; - // possibly leave a empty object around. whatevs. + /* browser capabilities check */ + if (!window.postMessage) throw("jschannel cannot run this browser, no postMessage"); + if (!window.JSON || !window.JSON.stringify || ! window.JSON.parse) { + throw("jschannel cannot run this browser, no JSON parsing/serialization"); } - function s_isArray(obj) { - if (Array.isArray) return Array.isArray(obj); - else { - return (obj.constructor.toString().indexOf("Array") != -1); - } + /* basic argument validation */ + if (typeof cfg != 'object') throw("Channel build invoked without a proper object argument"); + + if (!cfg.window || !cfg.window.postMessage) throw("Channel.build() called without a valid window argument"); + + /* we'd have to do a little more work to be able to run multiple channels that intercommunicate the same + * window... Not sure if we care to support that */ + if (window === cfg.window) throw("target window is same as present window -- not allowed"); + + // let's require that the client specify an origin. if we just assume '*' we'll be + // propagating unsafe practices. that would be lame. + var validOrigin = false; + if (typeof cfg.origin === 'string') { + var oMatch; + if (cfg.origin === "*") validOrigin = true; + // allow valid domains under http and https. Also, trim paths off otherwise valid origins. + else if (null !== (oMatch = cfg.origin.match(/^https?:\/\/(?:[-a-zA-Z0-9\.])+(?::\d+)?/))) { + cfg.origin = oMatch[0]; + validOrigin = true; + } } - // No two outstanding outbound messages may have the same id, period. Given that, a single table - // mapping "transaction ids" to message handlers, allows efficient routing of Callback, Error, and - // Response messages. Entries are added to this table when requests are sent, and removed when - // responses are received. - var s_transIds = { }; - - // class singleton onMessage handler - // this function is registered once and all incoming messages route through here. This - // arrangement allows certain efficiencies, message data is only parsed once and dispatch - // is more efficient, especially for large numbers of simultaneous channels. - var s_onMessage = function(e) { - var m = JSON.parse(e.data); - if (typeof m !== 'object') return; - - var o = e.origin; - var s = null; - var i = null; - var meth = null; - - if (typeof m.method === 'string') { - var ar = m.method.split('::'); - if (ar.length == 2) { - s = ar[0]; - meth = ar[1]; - } else { - meth = m.method; - } - } + if (!validOrigin) throw ("Channel.build() called with an invalid origin"); - if (typeof m.id !== 'undefined') i = m.id; - - // o is message origin - // m is parsed message - // s is message scope - // i is message id (or null) - // meth is unscoped method name - // ^^ based on these factors we can route the message - - // if it has a method it's either a notification or a request, - // route using s_boundChans - if (typeof meth === 'string') { - if (s_boundChans[o] && s_boundChans[o][s]) { - s_boundChans[o][s](o, meth, m); - } else if (s_boundChans['*'] && s_boundChans['*'][s]) { - s_boundChans['*'][s](o, meth, m); - } - } - // otherwise it must have an id (or be poorly formed - else if (typeof i != 'undefined') { - if (s_transIds[i]) s_transIds[i](o, meth, m); - } - }; + if (typeof cfg.scope !== 'undefined') { + if (typeof cfg.scope !== 'string') throw 'scope, when specified, must be a string'; + if (cfg.scope.split('::').length > 1) throw "scope may not contain double colons: '::'" + } - // Setup postMessage event listeners - if (window.addEventListener) window.addEventListener('message', s_onMessage, false); - else if(window.attachEvent) window.attachEvent('onmessage', s_onMessage); - - /* a messaging channel is constructed from a window and an origin. - * the channel will assert that all messages received over the - * channel match the origin - * - * Arguments to Channel.build(cfg): - * - * cfg.window - the remote window with which we'll communication - * cfg.origin - the expected origin of the remote window, may be '*' - * which matches any origin - * cfg.scope - the 'scope' of messages. a scope string that is - * prepended to message names. local and remote endpoints - * of a single channel must agree upon scope. Scope may - * not contain double colons ('::'). - * cfg.debugOutput - A boolean value. If true and window.console.log is - * a function, then debug strings will be emitted to that - * function. - * cfg.debugOutput - A boolean value. If true and window.console.log is - * a function, then debug strings will be emitted to that - * function. - * cfg.postMessageObserver - A function that will be passed two arguments, - * an origin and a message. It will be passed these immediately - * before messages are posted. - * cfg.gotMessageObserver - A function that will be passed two arguments, - * an origin and a message. It will be passed these arguments - * immediately after they pass scope and origin checks, but before - * they are processed. - * cfg.onReady - A function that will be invoked when a channel becomes "ready", - * this occurs once both sides of the channel have been - * instantiated and an application level handshake is exchanged. - * the onReady function will be passed a single argument which is - * the channel object that was returned from build(). - */ - return { - build: function(cfg) { - var debug = function(m) { - if (cfg.debugOutput && window.console && window.console.log) { - // try to stringify, if it doesn't work we'll let javascript's built in toString do its magic - try { if (typeof m !== 'string') m = JSON.stringify(m); } catch(e) { } - console.log("["+chanId+"] " + m); - } - } + /* private variables */ + // generate a random and psuedo unique id for this channel + var chanId = (function () { + var text = ""; + var alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + for(var i=0; i < 5; i++) text += alpha.charAt(Math.floor(Math.random() * alpha.length)); + return text; + })(); + + // registrations: mapping method names to call objects + var regTbl = { }; + // current oustanding sent requests + var outTbl = { }; + // current oustanding received requests + var inTbl = { }; + // are we ready yet? when false we will block outbound messages. + var ready = false; + var pendingQueue = [ ]; + + var createTransaction = function(id,origin,callbacks) { + var shouldDelayReturn = false; + var completed = false; + + return { + origin: origin, + invoke: function(cbName, v) { + // verify in table + if (!inTbl[id]) throw "attempting to invoke a callback of a non-existant transaction: " + id; + // verify that the callback name is valid + var valid = false; + for (var i = 0; i < callbacks.length; i++) if (cbName === callbacks[i]) { valid = true; break; } + if (!valid) throw "request supports no such callback '" + cbName + "'"; + + // send callback invocation + postMessage({ id: id, callback: cbName, params: v}); + }, + error: function(error, message) { + completed = true; + // verify in table + if (!inTbl[id]) throw "error called for non-existant message: " + id; - /* browser capabilities check */ - if (!window.postMessage) throw("jschannel cannot run this browser, no postMessage"); - if (!window.JSON || !window.JSON.stringify || ! window.JSON.parse) { - throw("jschannel cannot run this browser, no JSON parsing/serialization"); - } + // remove transaction from table + delete inTbl[id]; - /* basic argument validation */ - if (typeof cfg != 'object') throw("Channel build invoked without a proper object argument"); - - if (!cfg.window || !cfg.window.postMessage) throw("Channel.build() called without a valid window argument"); - - /* we'd have to do a little more work to be able to run multiple channels that intercommunicate the same - * window... Not sure if we care to support that */ - if (window === cfg.window) throw("target window is same as present window -- not allowed"); - - // let's require that the client specify an origin. if we just assume '*' we'll be - // propagating unsafe practices. that would be lame. - var validOrigin = false; - if (typeof cfg.origin === 'string') { - var oMatch; - if (cfg.origin === "*") validOrigin = true; - // allow valid domains under http and https. Also, trim paths off otherwise valid origins. - else if (null !== (oMatch = cfg.origin.match(/^https?:\/\/(?:[-a-zA-Z0-9\.])+(?::\d+)?/))) { - cfg.origin = oMatch[0]; - validOrigin = true; + // send error + postMessage({ id: id, error: error, message: message }); + }, + complete: function(v) { + completed = true; + // verify in table + if (!inTbl[id]) throw "complete called for non-existant message: " + id; + // remove transaction from table + delete inTbl[id]; + // send complete + postMessage({ id: id, result: v }); + }, + delayReturn: function(delay) { + if (typeof delay === 'boolean') { + shouldDelayReturn = (delay === true); + } + return shouldDelayReturn; + }, + completed: function() { + return completed; + } + }; + } + + var onMessage = function(origin, method, m) { + // if an observer was specified at allocation time, invoke it + if (typeof cfg.gotMessageObserver === 'function') { + // pass observer a clone of the object so that our + // manipulations are not visible (i.e. method unscoping). + // This is not particularly efficient, but then we expect + // that message observers are primarily for debugging anyway. + try { + cfg.gotMessageObserver(origin, m); + } catch (e) { + debug("gotMessageObserver() raised an exception: " + e.toString()); + } + } + + // now, what type of message is this? + if (m.id && method) { + // a request! do we have a registered handler for this request? + if (regTbl[method]) { + var trans = createTransaction(m.id, origin, m.callbacks ? m.callbacks : [ ]); + inTbl[m.id] = { }; + try { + // callback handling. we'll magically create functions inside the parameter list for each + // callback + if (m.callbacks && s_isArray(m.callbacks) && m.callbacks.length > 0) { + for (var i = 0; i < m.callbacks.length; i++) { + var path = m.callbacks[i]; + var obj = m.params; + var pathItems = path.split('/'); + for (var j = 0; j < pathItems.length - 1; j++) { + var cp = pathItems[j]; + if (typeof obj[cp] !== 'object') obj[cp] = { }; + obj = obj[cp]; } + obj[pathItems[pathItems.length - 1]] = (function() { + var cbName = path; + return function(params) { + return trans.invoke(cbName, params); + } + })(); + } } - - if (!validOrigin) throw ("Channel.build() called with an invalid origin"); - - if (typeof cfg.scope !== 'undefined') { - if (typeof cfg.scope !== 'string') throw 'scope, when specified, must be a string'; - if (cfg.scope.split('::').length > 1) throw "scope may not contain double colons: '::'" + var resp = regTbl[method](trans, m.params); + if (!trans.delayReturn() && !trans.completed()) trans.complete(resp); + } catch(e) { + // automagic handling of exceptions: + var error = "runtime_error"; + var message = null; + // * if its a string then it gets an error code of 'runtime_error' and string is the message + if (typeof e === 'string') { + message = e; + } else if (typeof e === 'object') { + // either an array or an object + // * if its an array of length two, then array[0] is the code, array[1] is the error message + if (e && s_isArray(e) && e.length == 2) { + error = e[0]; + message = e[1]; + } + // * if its an object then we'll look form error and message parameters + else if (typeof e.error === 'string') { + error = e.error; + if (!e.message) message = ""; + else if (typeof e.message === 'string') message = e.message; + else e = e.message; // let the stringify/toString message give us a reasonable verbose error string + } } - /* private variables */ - // generate a random and psuedo unique id for this channel - var chanId = (function () { - var text = ""; - var alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - for(var i=0; i < 5; i++) text += alpha.charAt(Math.floor(Math.random() * alpha.length)); - return text; - })(); - - // registrations: mapping method names to call objects - var regTbl = { }; - // current oustanding sent requests - var outTbl = { }; - // current oustanding received requests - var inTbl = { }; - // are we ready yet? when false we will block outbound messages. - var ready = false; - var pendingQueue = [ ]; - - var createTransaction = function(id,origin,callbacks) { - var shouldDelayReturn = false; - var completed = false; - - return { - origin: origin, - invoke: function(cbName, v) { - // verify in table - if (!inTbl[id]) throw "attempting to invoke a callback of a non-existant transaction: " + id; - // verify that the callback name is valid - var valid = false; - for (var i = 0; i < callbacks.length; i++) if (cbName === callbacks[i]) { valid = true; break; } - if (!valid) throw "request supports no such callback '" + cbName + "'"; - - // send callback invocation - postMessage({ id: id, callback: cbName, params: v}); - }, - error: function(error, message) { - completed = true; - // verify in table - if (!inTbl[id]) throw "error called for non-existant message: " + id; - - // remove transaction from table - delete inTbl[id]; - - // send error - postMessage({ id: id, error: error, message: message }); - }, - complete: function(v) { - completed = true; - // verify in table - if (!inTbl[id]) throw "complete called for non-existant message: " + id; - // remove transaction from table - delete inTbl[id]; - // send complete - postMessage({ id: id, result: v }); - }, - delayReturn: function(delay) { - if (typeof delay === 'boolean') { - shouldDelayReturn = (delay === true); - } - return shouldDelayReturn; - }, - completed: function() { - return completed; - } - }; + // message is *still* null, let's try harder + if (message === null) { + try { + message = JSON.stringify(e); + } catch (e2) { + message = e.toString(); + } } - var onMessage = function(origin, method, m) { - // if an observer was specified at allocation time, invoke it - if (typeof cfg.gotMessageObserver === 'function') { - // pass observer a clone of the object so that our - // manipulations are not visible (i.e. method unscoping). - // This is not particularly efficient, but then we expect - // that message observers are primarily for debugging anyway. - try { - cfg.gotMessageObserver(origin, m); - } catch (e) { - debug("gotMessageObserver() raised an exception: " + e.toString()); - } - } - - // now, what type of message is this? - if (m.id && method) { - // a request! do we have a registered handler for this request? - if (regTbl[method]) { - var trans = createTransaction(m.id, origin, m.callbacks ? m.callbacks : [ ]); - inTbl[m.id] = { }; - try { - // callback handling. we'll magically create functions inside the parameter list for each - // callback - if (m.callbacks && s_isArray(m.callbacks) && m.callbacks.length > 0) { - for (var i = 0; i < m.callbacks.length; i++) { - var path = m.callbacks[i]; - var obj = m.params; - var pathItems = path.split('/'); - for (var j = 0; j < pathItems.length - 1; j++) { - var cp = pathItems[j]; - if (typeof obj[cp] !== 'object') obj[cp] = { }; - obj = obj[cp]; - } - obj[pathItems[pathItems.length - 1]] = (function() { - var cbName = path; - return function(params) { - return trans.invoke(cbName, params); - } - })(); - } - } - var resp = regTbl[method](trans, m.params); - if (!trans.delayReturn() && !trans.completed()) trans.complete(resp); - } catch(e) { - // automagic handling of exceptions: - var error = "runtime_error"; - var message = null; - // * if its a string then it gets an error code of 'runtime_error' and string is the message - if (typeof e === 'string') { - message = e; - } else if (typeof e === 'object') { - // either an array or an object - // * if its an array of length two, then array[0] is the code, array[1] is the error message - if (e && s_isArray(e) && e.length == 2) { - error = e[0]; - message = e[1]; - } - // * if its an object then we'll look form error and message parameters - else if (typeof e.error === 'string') { - error = e.error; - if (!e.message) message = ""; - else if (typeof e.message === 'string') message = e.message; - else e = e.message; // let the stringify/toString message give us a reasonable verbose error string - } - } - - // message is *still* null, let's try harder - if (message === null) { - try { - message = JSON.stringify(e); - } catch (e2) { - message = e.toString(); - } - } - - trans.error(error,message); - } - } - } else if (m.id && m.callback) { - if (!outTbl[m.id] ||!outTbl[m.id].callbacks || !outTbl[m.id].callbacks[m.callback]) - { - debug("ignoring invalid callback, id:"+m.id+ " (" + m.callback +")"); - } else { - // XXX: what if client code raises an exception here? - outTbl[m.id].callbacks[m.callback](m.params); - } - } else if (m.id) { - if (!outTbl[m.id]) { - debug("ignoring invalid response: " + m.id); - } else { - // XXX: what if client code raises an exception here? - if (m.error) { - (1,outTbl[m.id].error)(m.error, m.message); - } else { - if (m.result !== undefined) (1,outTbl[m.id].success)(m.result); - else (1,outTbl[m.id].success)(); - } - delete outTbl[m.id]; - delete s_transIds[m.id]; - } - } else if (method) { - // tis a notification. - if (regTbl[method]) { - // yep, there's a handler for that. - // transaction is null for notifications. - regTbl[method](null, m.params); - // if the client throws, we'll just let it bubble out - // what can we do? Also, here we'll ignore return values - } - } - } + trans.error(error,message); + } + } + } else if (m.id && m.callback) { + if (!outTbl[m.id] ||!outTbl[m.id].callbacks || !outTbl[m.id].callbacks[m.callback]) + { + debug("ignoring invalid callback, id:"+m.id+ " (" + m.callback +")"); + } else { + // XXX: what if client code raises an exception here? + outTbl[m.id].callbacks[m.callback](m.params); + } + } else if (m.id) { + if (!outTbl[m.id]) { + debug("ignoring invalid response: " + m.id); + } else { + // XXX: what if client code raises an exception here? + if (m.error) { + (1,outTbl[m.id].error)(m.error, m.message); + } else { + if (m.result !== undefined) (1,outTbl[m.id].success)(m.result); + else (1,outTbl[m.id].success)(); + } + delete outTbl[m.id]; + delete s_transIds[m.id]; + } + } else if (method) { + // tis a notification. + if (regTbl[method]) { + // yep, there's a handler for that. + // transaction is null for notifications. + regTbl[method](null, m.params); + // if the client throws, we'll just let it bubble out + // what can we do? Also, here we'll ignore return values + } + } + } - // now register our bound channel for msg routing - s_addBoundChan(cfg.origin, ((typeof cfg.scope === 'string') ? cfg.scope : ''), onMessage); + // now register our bound channel for msg routing + s_addBoundChan(cfg.origin, ((typeof cfg.scope === 'string') ? cfg.scope : ''), onMessage); - // scope method names based on cfg.scope specified when the Channel was instantiated - var scopeMethod = function(m) { - if (typeof cfg.scope === 'string' && cfg.scope.length) m = [cfg.scope, m].join("::"); - return m; - } + // scope method names based on cfg.scope specified when the Channel was instantiated + var scopeMethod = function(m) { + if (typeof cfg.scope === 'string' && cfg.scope.length) m = [cfg.scope, m].join("::"); + return m; + } - // a small wrapper around postmessage whose primary function is to handle the - // case that clients start sending messages before the other end is "ready" - var postMessage = function(msg, force) { - if (!msg) throw "postMessage called with null message"; - - // delay posting if we're not ready yet. - var verb = (ready ? "post " : "queue "); - debug(verb + " message: " + JSON.stringify(msg)); - if (!force && !ready) { - pendingQueue.push(msg); - } else { - if (typeof cfg.postMessageObserver === 'function') { - try { - cfg.postMessageObserver(cfg.origin, msg); - } catch (e) { - debug("postMessageObserver() raised an exception: " + e.toString()); - } - } - - cfg.window.postMessage(JSON.stringify(msg), cfg.origin); - } - } + // a small wrapper around postmessage whose primary function is to handle the + // case that clients start sending messages before the other end is "ready" + var postMessage = function(msg, force) { + if (!msg) throw "postMessage called with null message"; + + // delay posting if we're not ready yet. + var verb = (ready ? "post " : "queue "); + debug(verb + " message: " + JSON.stringify(msg)); + if (!force && !ready) { + pendingQueue.push(msg); + } else { + if (typeof cfg.postMessageObserver === 'function') { + try { + cfg.postMessageObserver(cfg.origin, msg); + } catch (e) { + debug("postMessageObserver() raised an exception: " + e.toString()); + } + } - var onReady = function(trans, type) { - debug('ready msg received'); - if (ready) throw "received ready message while in ready state. help!"; + cfg.window.postMessage(JSON.stringify(msg), cfg.origin); + } + } - if (type === 'ping') { - chanId += '-R'; - } else { - chanId += '-L'; - } + var onReady = function(trans, type) { + debug('ready msg received'); + if (ready) throw "received ready message while in ready state. help!"; - obj.unbind('__ready'); // now this handler isn't needed any more. - ready = true; - debug('ready msg accepted.'); + if (type === 'ping') { + chanId += '-R'; + } else { + chanId += '-L'; + } - if (type === 'ping') { - obj.notify({ method: '__ready', params: 'pong' }); - } + obj.unbind('__ready'); // now this handler isn't needed any more. + ready = true; + debug('ready msg accepted.'); - // flush queue - while (pendingQueue.length) { - postMessage(pendingQueue.pop()); - } + if (type === 'ping') { + obj.notify({ method: '__ready', params: 'pong' }); + } - // invoke onReady observer if provided - if (typeof cfg.onReady === 'function') cfg.onReady(obj); - }; - - var obj = { - // tries to unbind a bound message handler. returns false if not possible - unbind: function (method) { - if (regTbl[method]) { - if (!(delete regTbl[method])) throw ("can't delete method: " + method); - return true; - } - return false; - }, - bind: function (method, cb) { - if (!method || typeof method !== 'string') throw "'method' argument to bind must be string"; - if (!cb || typeof cb !== 'function') throw "callback missing from bind params"; - - if (regTbl[method]) throw "method '"+method+"' is already bound!"; - regTbl[method] = cb; - }, - call: function(m) { - if (!m) throw 'missing arguments to call function'; - if (!m.method || typeof m.method !== 'string') throw "'method' argument to call must be string"; - if (!m.success || typeof m.success !== 'function') throw "'success' callback missing from call"; - - // now it's time to support the 'callback' feature of jschannel. We'll traverse the argument - // object and pick out all of the functions that were passed as arguments. - var callbacks = { }; - var callbackNames = [ ]; - - var pruneFunctions = function (path, obj) { - if (typeof obj === 'object') { - for (var k in obj) { - if (!obj.hasOwnProperty(k)) continue; - var np = path + (path.length ? '/' : '') + k; - if (typeof obj[k] === 'function') { - callbacks[np] = obj[k]; - callbackNames.push(np); - delete obj[k]; - } else if (typeof obj[k] === 'object') { - pruneFunctions(np, obj[k]); - } - } - } - }; - pruneFunctions("", m.params); - - // build a 'request' message and send it - var msg = { id: s_curTranId, method: scopeMethod(m.method), params: m.params }; - if (callbackNames.length) msg.callbacks = callbackNames; - - // insert into the transaction table - outTbl[s_curTranId] = { callbacks: callbacks, error: m.error, success: m.success }; - s_transIds[s_curTranId] = onMessage; - - // increment current id - s_curTranId++; - - postMessage(msg); - }, - notify: function(m) { - if (!m) throw 'missing arguments to notify function'; - if (!m.method || typeof m.method !== 'string') throw "'method' argument to notify must be string"; - - // no need to go into any transaction table - postMessage({ method: scopeMethod(m.method), params: m.params }); - }, - destroy: function () { - s_removeBoundChan(cfg.origin, ((typeof cfg.scope === 'string') ? cfg.scope : '')); - if (window.removeEventListener) window.removeEventListener('message', onMessage, false); - else if(window.detachEvent) window.detachEvent('onmessage', onMessage); - ready = false; - regTbl = { }; - inTbl = { }; - outTbl = { }; - cfg.origin = null; - pendingQueue = [ ]; - debug("channel destroyed"); - chanId = ""; - } - }; + // flush queue + while (pendingQueue.length) { + postMessage(pendingQueue.pop()); + } - obj.bind('__ready', onReady); - setTimeout(function() { - postMessage({ method: scopeMethod('__ready'), params: "ping" }, true); - }, 0); + // invoke onReady observer if provided + if (typeof cfg.onReady === 'function') cfg.onReady(obj); + }; - return obj; + var obj = { + // tries to unbind a bound message handler. returns false if not possible + unbind: function (method) { + if (regTbl[method]) { + if (!(delete regTbl[method])) throw ("can't delete method: " + method); + return true; } + return false; + }, + bind: function (method, cb) { + if (!method || typeof method !== 'string') throw "'method' argument to bind must be string"; + if (!cb || typeof cb !== 'function') throw "callback missing from bind params"; + + if (regTbl[method]) throw "method '"+method+"' is already bound!"; + regTbl[method] = cb; + }, + call: function(m) { + if (!m) throw 'missing arguments to call function'; + if (!m.method || typeof m.method !== 'string') throw "'method' argument to call must be string"; + if (!m.success || typeof m.success !== 'function') throw "'success' callback missing from call"; + + // now it's time to support the 'callback' feature of jschannel. We'll traverse the argument + // object and pick out all of the functions that were passed as arguments. + var callbacks = { }; + var callbackNames = [ ]; + + var pruneFunctions = function (path, obj) { + if (typeof obj === 'object') { + for (var k in obj) { + if (!obj.hasOwnProperty(k)) continue; + var np = path + (path.length ? '/' : '') + k; + if (typeof obj[k] === 'function') { + callbacks[np] = obj[k]; + callbackNames.push(np); + delete obj[k]; + } else if (typeof obj[k] === 'object') { + pruneFunctions(np, obj[k]); + } + } + } + }; + pruneFunctions("", m.params); + + // build a 'request' message and send it + var msg = { id: s_curTranId, method: scopeMethod(m.method), params: m.params }; + if (callbackNames.length) msg.callbacks = callbackNames; + + // insert into the transaction table + outTbl[s_curTranId] = { callbacks: callbacks, error: m.error, success: m.success }; + s_transIds[s_curTranId] = onMessage; + + // increment current id + s_curTranId++; + + postMessage(msg); + }, + notify: function(m) { + if (!m) throw 'missing arguments to notify function'; + if (!m.method || typeof m.method !== 'string') throw "'method' argument to notify must be string"; + + // no need to go into any transaction table + postMessage({ method: scopeMethod(m.method), params: m.params }); + }, + destroy: function () { + s_removeBoundChan(cfg.origin, ((typeof cfg.scope === 'string') ? cfg.scope : '')); + if (window.removeEventListener) window.removeEventListener('message', onMessage, false); + else if(window.detachEvent) window.detachEvent('onmessage', onMessage); + ready = false; + regTbl = { }; + inTbl = { }; + outTbl = { }; + cfg.origin = null; + pendingQueue = [ ]; + debug("channel destroyed"); + chanId = ""; + } }; - })(); - - navigator.id.getVerifiedEmail = function() { - var w = window.open("http://authority.mozilla.org/jsapi/picker.html", "_mozid_picker", - "menubar=no,location=no,resizable=no,scrollbars=no,status=no,dialog=yes,width=420,height=230"); - - // XXX: this origin check needs to tighten up - var chan = Channel.build({window: w, origin: "*", scope: "mozid"}); - chan.call({ - method: "getVerifiedEmail", - success: function(rv) { - alert("successfully got verified email: " + JSON.stringify(rv)); - }, - error: function(e) { - alert("ACK! failed to get verified email: " + JSON.stringify(e)); - } - }); + + obj.bind('__ready', onReady); + setTimeout(function() { + postMessage({ method: scopeMethod('__ready'), params: "ping" }, true); + }, 0); + + return obj; + } }; + })(); + + navigator.id.getVerifiedEmail = function(onsuccess, onerror) { + var w = window.open("http://authority.mozilla.org/jsapi/picker.html", "_mozid_picker", + "menubar=no,location=no,resizable=no,scrollbars=no,status=no,dialog=yes,width=420,height=230"); + + // XXX: this origin check needs to tighten up + var chan = Channel.build({window: w, origin: "*", scope: "mozid"}); + chan.call({ + method: "getVerifiedEmail", + success: function(rv) { + if (onsuccess) onsuccess(rv); + }, + error: function(code, msg) { + if (onerror) onerror(code, msg); + } + }); + }; } diff --git a/rp/index.html b/rp/index.html index 59a1cc3b0eecc9c3436072d107dbd43f8a72d8cf..26523881c6902097f59664f4527c9728a77e2b21 100644 --- a/rp/index.html +++ b/rp/index.html @@ -14,31 +14,33 @@ computer usage for little ones. <script src="jquery-min.js"></script> <script src="http://authority.mozilla.org/jsapi/include.js"></script> <script> -$(document).ready(function() { + $(document).ready(function() { $("#login").click(function() { - navigator.id.getVerifiedEmail(function(assertion) { - // assertion at this point is a blob that looks like this: - // - // { - // audience: "mywords.com", - // valid-until: <format TBD>, - // email: "sophia@hilaiel.coom" - // } - // - // Now we'll send this assertion up to the mywords server for - // validation + navigator.id.getVerifiedEmail(function(assertion) { + // assertion at this point is a blob that looks like this: + // + // { + // audience: "mywords.com", + // valid-until: <format TBD>, + // email: "sophia@hilaiel.coom" + // } + // + // Now we'll send this assertion up to the mywords server for + // validation - $.post( - '/login', - JSON.stringify(assertion), - function(data, textStatus) { - // we've got a response from the server half of the mywords - // application which has validated the assertion - console.log(data); - }); - }); + $.post( + '/login', + JSON.stringify(assertion), + function(data, textStatus) { + // we've got a response from the server half of the mywords + // application which has validated the assertion + console.log(data); + }); + }, function(code, msg) { + alert("something very bad happened! ("+code+"): " + msg); + }); }); -}); + }); </script> </html>