diff --git a/example/rp/index.html b/example/rp/index.html index d7221a471106f09f097ccfb6ae4fc9f00cae49c0..99fc8351b0a09efdc6d982ee900ea8b2ca151b48 100644 --- a/example/rp/index.html +++ b/example/rp/index.html @@ -17,7 +17,7 @@ a:link, a:visited { font-style: italic; text-decoration: none; color: #008; } a:hover { border-bottom: 2px solid black ; } .title { font-size: 2em; font-weight: bold; text-align: center; margin: 1.5em auto 1.5em auto; } .intro { font-size: 1.2em; } -.specify { font-size: 1.1em; padding-top: 2em; } +.specify, .session { font-size: 1.1em; padding-top: 2em; } body div { width: 600px; margin: auto; } pre { @@ -57,7 +57,7 @@ pre { </div> <div class="specify"> - <p>What flavor of assertion would you like?</p> + <p><b>What flavor of assertion would you like?</b></p> <ul> <li> <input type="checkbox" id="privacy"> @@ -74,22 +74,31 @@ pre { <button class="logout">logout</button> </div> +<div class="session"> + <p><b>Care to simulate a session?</b></p> + <p>If you enter an email address or 'null' here, upon reload this value will + be passed to .watch() as the first parameter. This lets you test things like + assertion generation suppression when the site and browser agree on who is logged in. + </p> + <input type="text" id="loggedInUser" width="80"> + <button class="update_session">Update "session"</button> +</div> + <div class="loginEvents"> - <h2>login events</h2> - <pre> ... login events ... </pre> + <h2>logins</h2> + <pre> ... </pre> </div> <div class="logoutEvents"> - <h2>logout events</h2> - <pre> ... logout events ... </pre> + <h2>logouts</h2> + <pre> ... </pre> </div> -<div class="loginCanceledEvents"> - <h2>loginCanceled events</h2> - <pre> ... loginCanceled events ... </pre> +<div class="readiness"> + <h2>readiness</h2> + <pre> ... </pre> </div> - </body> <script src="jquery-min.js"></script> @@ -102,6 +111,8 @@ function loggit() { } catch(e) {} } +var serial = 1; + // a function to check an assertion against the server function checkAssertion(assertion) { $.ajax({ @@ -113,7 +124,8 @@ function checkAssertion(assertion) { audience: window.location.protocol + "//" + window.location.host }, success: function(data, textStatus, jqXHR) { - $(".loginEvents > pre").text(JSON.stringify(data, null, 4)); + var old = $(".loginEvents > pre").text() + "\n"; + $(".loginEvents > pre").text(old + JSON.stringify(data, null, 4)); }, error: function(jqXHR, textStatus, errorThrown) { var resp = jqXHR.responseText ? JSON.parse(jqXHR.responseText) : errorThrown; @@ -122,21 +134,26 @@ function checkAssertion(assertion) { }); }; -navigator.id.addEventListener('login', function(event) { - loggit("login event"); - checkAssertion(event.assertion); -}); - -navigator.id.addEventListener('logout', function(event) { - loggit("logout event"); - var txt = 'got event at ' + (new Date).toString(); - $(".logoutEvents > pre").text(txt); -}); - -navigator.id.addEventListener('loginCanceled', function(event) { - loggit("loginCanceled"); - var txt = 'got event at ' + (new Date).toString(); - $(".loginCanceledEvents > pre").text(txt); +navigator.id.experimental.watch({ + email: (localStorage.loggedInUser === 'null') ? null : localStorage.loggedInUser, + onready: function () { + loggit("onready"); + var txt = serial++ + ' navigator.id ready at ' + (new Date).toString(); + $(".readiness > pre").text(txt); + + }, + onlogin: function (assertion) { + loggit("onlogin"); + var txt = serial++ + ' got assertion at ' + (new Date).toString(); + $(".loginEvents > pre").text(txt); + + checkAssertion(assertion); + }, + onlogout: function () { + loggit("onlogout"); + var txt = serial++ + ' logout callback invoked at ' + (new Date).toString(); + $(".logoutEvents > pre").text(txt); + } }); $(document).ready(function() { @@ -146,14 +163,26 @@ $(document).ready(function() { var requiredEmail = $.trim($('#requiredEmail').val()); if (!requiredEmail.length) requiredEmail = undefined; - navigator.id.request({ + $(".specify button.assertion").attr('disabled', 'true'); + + navigator.id.experimental.request({ privacyURL: $('#privacy').attr('checked') ? "/privacy.html" : undefined, tosURL: $('#tos').attr('checked') ? "/TOS.html" : undefined, - requiredEmail: requiredEmail + requiredEmail: requiredEmail, + onclose: function() { + loggit("onclose"); + $(".specify button.assertion").removeAttr('disabled'); + } }); }); $(".specify button.logout").click(navigator.id.logout); + + $(".session button.update_session").click(function() { + localStorage.loggedInUser = $.trim($('#loggedInUser').val()); + $(".session input").fadeOut(100).fadeIn(350); + }); + $('#loggedInUser').val(localStorage.loggedInUser ? localStorage.loggedInUser : ""); }); </script> diff --git a/resources/static/communication_iframe/start.js b/resources/static/communication_iframe/start.js index ec70a5b08b97e023640a6b26a45f3233645976be..57ce36dcaca52ba1d78fc3db41208e89371444ec 100644 --- a/resources/static/communication_iframe/start.js +++ b/resources/static/communication_iframe/start.js @@ -70,9 +70,12 @@ }); chan.bind("loaded", function(trans, params) { + trans.delayReturn(true); setRemoteOrigin(trans.origin); - checkAndEmit(watchState); - trans.complete(); + checkAndEmit(function() { + watchState(); + trans.complete(); + }); }); chan.bind("logout", function(trans, params) { diff --git a/resources/static/include_js/include.js b/resources/static/include_js/include.js index 01a67fe39fd6e13c5c74aa6ea2997deb292bd5ab..e8255e426f2a38475ab2f319faea771a172a2608 100644 --- a/resources/static/include_js/include.js +++ b/resources/static/include_js/include.js @@ -770,12 +770,13 @@ try { var d = JSON.parse(e.data); if (d.a === 'ready') messageTarget.postMessage(req, origin); - else if (d.a === 'error') cb(d.d); - else if (d.a === 'response') { + else if (d.a === 'error') { + if (cb) { cb(d.d); cb = null; } + } else if (d.a === 'response') { removeListener(window, 'message', onMessage); removeListener(window, 'unload', cleanup); cleanup(); - cb(null, d.d); + if (cb) { cb(null, d.d); cb = null; } } } catch(e) { } }; @@ -936,11 +937,11 @@ var w; - // table of registered event listeners - var listeners = { - login: [ ], - logout: [ ], - loginCanceled: [ ] + // table of registered observers + var observers = { + login: null, + logout: null, + ready: null }; var compatMode = undefined; @@ -951,30 +952,8 @@ if (compatMode === undefined) compatMode = requiredMode; else if (compatMode != requiredMode) { - throw "you cannot combine browserid event APIs with navigator.id.getVerifiedEmail() or navigator.id.get()" + - "this site should instead use navigator.id.request() and the browserid event API"; - } - } - - function emitEvent(type, params) { - if (listeners[type]) { - var evt = document.createEvent('Event'); - evt.initEvent(type, true, true); - // XXX: we should probably implement .stopImmediatePropagation() - if (params) { - for (var k in params) { - if (params.hasOwnProperty(k)) { - evt[k] = params[k]; - } - } - } - for (var i = 0; i < listeners[type].length; i++) { - try { - listeners[type][i](evt); - } catch(e) { - // XXX: what shall we do when an exception is raised by an event handler? - } - } + throw "you cannot combine the navigator.id.watch() API with navigator.id.getVerifiedEmail() or navigator.id.get()" + + "this site should instead use navigator.id.request() and navigator.id.watch()"; } } @@ -996,102 +975,50 @@ // once the channel is set up, we'll fire a loaded message. this is the // cutoff point where we'll say if 'setLoggedInUser' was not called before // this point, then it wont be called (XXX: optimize and improve me) - commChan.call({ method: 'loaded', success: function(){}, error: function() {} }); + commChan.call({ + method: 'loaded', + success: function(){ + if (observers.ready) observers.ready(); + }, error: function() { + } + }); } }); commChan.bind('logout', function(trans, params) { - emitEvent('logout'); + if (observers.logout) observers.logout(); }); commChan.bind('login', function(trans, params) { - emitEvent('login', { assertion: params }); + if (observers.login) observers.login(params); }); } } - function internalAddEventListener(type, listener) { - // add event to listeners table if it's not there already - if (!listeners[type]) throw "unsupported event type: '" + type + "'"; + function internalWatch(options) { + if (typeof options !== 'object') return; - // is the function already registered? - for (var i = 0; i < listeners[type].length; i++) { - if (listeners[type][i] === listener) return; + if (options.onlogin && typeof options.onlogin !== 'function' || + options.onlogout && typeof options.onlogout !== 'function' || + options.onready && typeof options.onready !== 'function') + { + throw "non-function where function expected in parameters to navigator.id.watch()"; } - listeners[type].push(listener); - } - navigator.id.addEventListener = function(type, listener) { - checkCompat(false); + observers.login = options.onlogin || null; + observers.logout = options.onlogout || null; + observers.ready = options.onready || null; - // allocate iframe if it is not allocated _open_hidden_iframe(); - internalAddEventListener(type,listener); - }; - function internalRemoveEventListener(type, listener ) { - // remove event from listeners table - var i; - for (i = 0; i < listeners[type].length; i++) { - if (listeners[type][i] === listener) break; - } - if (i < listeners[type][i].length) { - listeners[type].splice(i, 1); + if (typeof options.email !== 'undefined') { + commChan.notify({ + method: 'loggedInUser', + params: options.email + }); } } - navigator.id.removeEventListener = function(type, listener/*, useCapture */) { - checkCompat(false); - internalRemoveEventListener(type, listener); - }; - - navigator.id.logout = function() { - checkCompat(false); - - // allocate iframe if it is not allocated - _open_hidden_iframe(); - - // send logout message - commChan.notify({ method: 'logout' }); - }; - - navigator.id.setLoggedInUser = function(email) { - checkCompat(false); - - // 1. allocate iframe if it is not allocated - _open_hidden_iframe(); - - // 2. send a "loggedInUser" message to iframe - commChan.notify({ method: 'loggedInUser', params: email }); - }; - - // backwards compatibility function - navigator.id.get = function(callback, options) { - checkCompat(true); - - if (options && options.silent) { - if (callback) setTimeout(function() { callback(null); }, 0); - } else { - function handleEvent(e) { - internalRemoveEventListener('login', handleEvent); - callback((e && e.assertion) ? e.assertion : null); - } - internalAddEventListener('login', handleEvent); - internalRequest(options); - } - }; - - // backwards compatibility function - navigator.id.getVerifiedEmail = function(callback) { - checkCompat(true); - navigator.id.get(callback); - }; - - navigator.id.request = function(options) { - checkCompat(false); - return internalRequest(options); - }; - function internalRequest(options) { // focus an existing window if (w) { @@ -1147,12 +1074,71 @@ // clear the window handle w = undefined; - if (!err && r && r.assertion) emitEvent('login', { assertion: r.assertion }); - else emitEvent('loginCanceled'); + if (!err && r && r.assertion) { + try { + if (observers.login) observers.login(r.assertion); + } catch(e) { + // client's observer threw an exception + } + } + + // complete + if (options && options.onclose) options.onclose(); + delete options.onclose; }); }; - navigator.id._shimmed = true; + navigator.id = { + // The experimental API, not yet final + experimental: { + request: function(options) { + checkCompat(false); + return internalRequest(options); + }, + watch: function(options) { + checkCompat(false); + internalWatch(options); + } + }, + // logout from the current website + logout: function() { + // allocate iframe if it is not allocated + _open_hidden_iframe(); + // send logout message + commChan.notify({ method: 'logout' }); + }, + // get an assertion + get: function(callback, options) { + checkCompat(true); + internalWatch({ + onlogin: function(assertion) { + if (callback) { + callback(assertion); + callback = null + } + } + }); + options.oncomplete = function() { + if (callback) { + callback(null); + callback = null; + } + internalWatch({}); + }; + if (options && options.silent) { + if (callback) setTimeout(function() { callback(null); }, 0); + } else { + internalRequest(options); + } + }, + // backwards compatibility with old API + getVerifiedEmail: function(callback) { + checkCompat(true); + navigator.id.get(callback); + }, + // required for forwards compatibility with native implementations + _shimmed: true + }; } }()); diff --git a/resources/static/test/cases/include.js b/resources/static/test/cases/include.js index 42aa6c6562488a4dfba94cba7b438641c8d0ba02..2273cc25c93da6a3116c33a716eff262be9d8be4 100644 --- a/resources/static/test/cases/include.js +++ b/resources/static/test/cases/include.js @@ -15,15 +15,21 @@ test("expected public API functions available", function() { _.each([ "get", - "request", - "setLoggedInUser", - "logout", - "addEventListener", - "removeEventListener" + "getVerifiedEmail", + "logout" ], function(item, index) { equal(typeof navigator.id[ item ], "function", "navigator.id." + item + " is available"); }); }); + test("expected experimental API function is available", function() { + _.each([ + "request", + "watch" + ], function(item, index) { + equal(typeof navigator.id.experimental[ item ], "function", "navigator.id." + item + " is available"); + }); + }); + }());