diff options
| -rw-r--r-- | auth.go | 7 | ||||
| -rw-r--r-- | auth_persona.go | 69 | ||||
| -rw-r--r-- | bommom.go | 1 | ||||
| -rw-r--r-- | serve.go | 67 | ||||
| -rw-r--r-- | templates/account.html | 29 | ||||
| -rw-r--r-- | templates/base.html | 20 | ||||
| -rw-r--r-- | templates/bom_upload.html | 4 | ||||
| -rw-r--r-- | templates/bom_view.html | 4 | ||||
| -rw-r--r-- | templates/home.html | 4 | ||||
| -rw-r--r-- | templates/static/include.js | 1255 | ||||
| -rw-r--r-- | templates/user.html | 4 | 
11 files changed, 1449 insertions, 15 deletions
@@ -12,6 +12,7 @@ type AuthService interface {  	NewAccount(name, pw, email string) error  	ChangePassword(name, oldPw, newPw string) error  	GetEmail(name string) (string, error) +	GetUserName(email string) (string, error)  }  // DummyAuth is a "wide-open" implementation of AuthService for development and @@ -32,5 +33,9 @@ func (da DummyAuth) ChangePassword(name, oldPw, newPw string) error {  }  func (da DummyAuth) GetEmail(name string) (string, error) { -	return "example@bommom.com", nil +	return "example@localhost", nil +} + +func (da DummyAuth) GetUserName(name string) (string, error) { +	return "common", nil  } diff --git a/auth_persona.go b/auth_persona.go new file mode 100644 index 0000000..7895c2d --- /dev/null +++ b/auth_persona.go @@ -0,0 +1,69 @@ +package main + +import ( +    "encoding/json" +    "io" +    "io/ioutil" +    "log" +    "net/http" +    "net/url" +) + +type PersonaResponse struct { +    Status, Email, Reason string +} + +func (b PersonaResponse) Okay() bool { +    return b.Status == "okay" +} + +func VerifyPersonaAssertion(assertion, audience string) PersonaResponse { +    resp, _ := http.PostForm( +        "https://browserid.org/verify", +        url.Values{ +            "assertion": {assertion}, +            "audience": {audience}, +    }) +    response := personaResponseFromJson(resp.Body) +    resp.Body.Close() + +    return response +} + +func personaResponseFromJson(r io.Reader) (resp PersonaResponse) { +    body, err := ioutil.ReadAll(r) + +    if err != nil { +        log.Fatal(err) +    } + +    err = json.Unmarshal(body, &resp) + +    if err != nil { +        log.Fatal(err) +    } + +    return resp +} + +type PersonaAuth bool  + +func (pa PersonaAuth) CheckLogin(name, pw string) error { +	return nil +} + +func (pa PersonaAuth) NewAccount(name, pw, email string) error { +	return nil +} + +func (pa PersonaAuth) ChangePassword(name, oldPw, newPw string) error { +	return nil +} + +func (pa PersonaAuth) GetEmail(name string) (string, error) { +	return "example@localhost", nil +} + +func (pa PersonaAuth) GetUserName(name string) (string, error) { +	return "common", nil +} @@ -27,6 +27,7 @@ var (  	inFormat      = flag.String("informat", "", "command output format (for 'load' etc)")  	listenPort    = flag.Uint("port", 7070, "port to listen on (HTTP serve)")  	listenHost    = flag.String("host", "", "hostname to listen on (HTTP serve)") +	sessionSecret = flag.String("sessionSecret", "12345", "cookie session secret")  )  func main() { @@ -8,12 +8,15 @@ import (  	"regexp"      "path/filepath"      "time" +    "code.google.com/p/gorilla/sessions"  )  var ( -	tmplHome, tmplView, tmplUser, tmplBomView, tmplBomUpload *template.Template +	tmplHome, tmplView, tmplAccount, tmplUser, tmplBomView, tmplBomUpload *template.Template  ) +var store = sessions.NewCookieStore([]byte(*sessionSecret)) +  func baseHandler(w http.ResponseWriter, r *http.Request) {  	var err error  	log.Printf("serving %s\n", r.URL.Path) @@ -25,6 +28,12 @@ func baseHandler(w http.ResponseWriter, r *http.Request) {  	switch {  	case r.URL.Path == "/":  		err = homeController(w, r) +	case r.URL.Path == "/account/login/": +		err = loginController(w, r) +	case r.URL.Path == "/account/logout/": +		err = logoutController(w, r) +	//case r.URL.Path == "/account/newuser/": +	//	err = newUserController(w, r)  	case bomUploadUrlPattern.MatchString(r.URL.Path):  		match := bomUploadUrlPattern.FindStringSubmatch(r.URL.Path)  		err = bomUploadController(w, r, match[1], match[2]) @@ -48,7 +57,10 @@ func baseHandler(w http.ResponseWriter, r *http.Request) {  }  func homeController(w http.ResponseWriter, r *http.Request) (err error) { +    session, _ := store.Get(r, "bommom")  	context := make(map[string]interface{}) +	context["Session"] = session.Values +    log.Printf("%s\n", session.Values["UserName"])  	context["BomList"], err = bomstore.ListBoms("")  	if err != nil {  		return @@ -57,13 +69,54 @@ func homeController(w http.ResponseWriter, r *http.Request) (err error) {  	return  } +func loginController(w http.ResponseWriter, r *http.Request) (err error) { +    session, _ := store.Get(r, "bommom") +    context := make(map[string]interface{}) +    context["ActionLogin"] = true +    context["Session"] = session.Values +    if r.Method == "POST" { +        if isShortName(r.FormValue("UserName")) != true { +            context["Problem"] = "Ugh, need to use a SHORTNAME!" +            err = tmplAccount.Execute(w, context) +            return +        } +        audience := "http://localhost:7070" +        vResponse := VerifyPersonaAssertion(r.FormValue("assertion"), audience) +        if vResponse.Okay() { +            session.Values["UserName"] = r.FormValue("UserName") +            session.Values["Email"] = vResponse.Email +            session.Save(r, w) +            context["Session"] = session.Values +            http.Redirect(w, r, "/", 302) +            return +        } else { +            context["Problem"] = vResponse.Reason +            err = tmplAccount.Execute(w, context) +            return +        } +    } +	err = tmplAccount.Execute(w, context) +	return +} + +func logoutController(w http.ResponseWriter, r *http.Request) (err error) { +    session, _ := store.Get(r, "bommom") +    context := make(map[string]interface{}) +    delete(session.Values, "UserName") +    delete(session.Values, "Email") +    session.Save(r, w) +    context["Session"] = session.Values +    context["ActionLogout"] = true +	err = tmplAccount.Execute(w, context) +	return +} +  func userController(w http.ResponseWriter, r *http.Request, user, extra string) (err error) { +    session, _ := store.Get(r, "bommom")  	if !isShortName(user) {  		http.Error(w, "invalid username: "+user, 400)  		return  	} -	var email string -	email, err = auth.GetEmail(user)  	if err != nil {  		// no such user  		http.NotFound(w, r) @@ -72,6 +125,7 @@ func userController(w http.ResponseWriter, r *http.Request, user, extra string)  	context := make(map[string]interface{})  	context["BomList"], err = bomstore.ListBoms(ShortName(user))  	context["UserName"] = user +	context["Session"] = session.Values  	if err != nil {  		return  	} @@ -80,6 +134,7 @@ func userController(w http.ResponseWriter, r *http.Request, user, extra string)  }  func bomController(w http.ResponseWriter, r *http.Request, user, name string) (err error) { +    session, _ := store.Get(r, "bommom")  	if !isShortName(user) {  		http.Error(w, "invalid username: "+user, 400)  		return @@ -90,6 +145,7 @@ func bomController(w http.ResponseWriter, r *http.Request, user, name string) (e  	}  	context := make(map[string]interface{})  	context["BomMeta"], context["Bom"], err = bomstore.GetHead(ShortName(user), ShortName(name)) +	context["Session"] = session.Values  	if err != nil {  		http.Error(w, "404 couldn't open bom: " + user + "/" + name, 404)  		return nil @@ -99,6 +155,7 @@ func bomController(w http.ResponseWriter, r *http.Request, user, name string) (e  }  func bomUploadController(w http.ResponseWriter, r *http.Request, user, name string) (err error) { +    session, _ := store.Get(r, "bommom")  	if !isShortName(user) {  		http.Error(w, "invalid username: "+user, 400) @@ -109,6 +166,7 @@ func bomUploadController(w http.ResponseWriter, r *http.Request, user, name stri  		return  	}  	context := make(map[string]interface{}) +	context["Session"] = session.Values      context["user"] = ShortName(user)      context["name"] = ShortName(name)  	context["BomMeta"], context["Bom"], err = bomstore.GetHead(ShortName(user), ShortName(name)) @@ -202,6 +260,9 @@ func serveCmd() {  	// load and parse templates  	baseTmplPath := *templatePath + "/base.html"  	tmplHome = template.Must(template.ParseFiles(*templatePath+"/home.html", baseTmplPath)) +	tmplAccount = template.Must(template.ParseFiles(*templatePath+"/account.html", baseTmplPath)) +	//tmplLogout = template.Must(template.ParseFiles(*templatePath+"/logout.html", baseTmplPath)) +	//tmplNewUser = template.Must(template.ParseFiles(*templatePath+"/newuser.html", baseTmplPath))  	tmplUser = template.Must(template.ParseFiles(*templatePath+"/user.html", baseTmplPath))  	tmplBomView = template.Must(template.ParseFiles(*templatePath+"/bom_view.html", baseTmplPath))  	tmplBomUpload = template.Must(template.ParseFiles(*templatePath+"/bom_upload.html", baseTmplPath)) diff --git a/templates/account.html b/templates/account.html new file mode 100644 index 0000000..9b7e3b1 --- /dev/null +++ b/templates/account.html @@ -0,0 +1,29 @@ +{{ template "HEADER" . }} +{{ if .ActionLogin }} + +<br><br><br><br> +<div class="container"> +	<div class="row"> +		<div class="span6 offset3 well"> +			<legend>Please Sign In</legend> +            {{ if .Problem }} +            <div class="alert alert-error"> +                <strong>Problem!</strong> {{ .Problem }} +            </div> +            {{ end }} +			<form method="POST" action="/account/login/" id="login-form"> +              <input id="assertion-field" type="hidden" name="assertion" value=""> +              <input type="text" id="UserName" class="span6" name="UserName" placeholder="Username"> +			</form>     +            <a href="javascript:login()"><button class="btn btn-info btn-block">Sign In With Mozilla Persona</button></a> +		</div> +	</div> +</div> + +{{ else }} +{{if .ActionLogout }} +<h1>logged out, goodbye!</h1> +{{ else }}{{if .ActionNewUser }} +<h1>hello new human!</h1> +{{end}}{{end}}{{end}} +{{ template "FOOTER" . }} diff --git a/templates/base.html b/templates/base.html index 8066063..4fe66ad 100644 --- a/templates/base.html +++ b/templates/base.html @@ -2,6 +2,19 @@  <head>  <title>bommom</title>  <link rel="stylesheet" type="text/css" href="/static/default.css"></link> +<script src="/static/include.js"></script> +<script> +function login() { +    navigator.id.get(function(assertion) { +        if (assertion) { +            var assertion_field = document.getElementById("assertion-field"); +            assertion_field.value = assertion; +            var login_form = document.getElementById("login-form"); +            login_form.submit(); +        } +    }); +} +</script>  </head>  <body> @@ -12,10 +25,11 @@        {{ range .BreadCrumbs }} / {{.}} {{end}}        <div class="nav-collapse collapse">          <p class="navbar-text pull-right"> -        {{ if .User }} -            {{ .User.Email }} +        {{ if .Session.UserName }} +            {{ .Session.UserName }} +            <a href="/account/logout/"><button class="btn btn-mini" type="button" style="margin-left: 5px;">Log out!</button></a>          {{ else }} -            <button class="btn btn-mini disabled" type="button" style="margin-top: 9px;">Log in!</button> +            <a href="/account/login/"><button class="btn btn-mini" type="button" style="margin-top: 9px;">Log in!</button></a>          {{ end }}          </p>        </div><!--/.nav-collapse --> diff --git a/templates/bom_upload.html b/templates/bom_upload.html index 30e2ff6..8cc15c8 100644 --- a/templates/bom_upload.html +++ b/templates/bom_upload.html @@ -1,4 +1,4 @@ -{{ template "HEADER" }} +{{ template "HEADER" . }}  {{ if .BomMeta }}  <h1>{{ .BomMeta.Name }} is a bom.</h1> @@ -21,4 +21,4 @@  <input type="submit" value="Upload"></input>  </form> -{{ template "FOOTER" }} +{{ template "FOOTER" . }} diff --git a/templates/bom_view.html b/templates/bom_view.html index 2e66bf1..7eca3e3 100644 --- a/templates/bom_view.html +++ b/templates/bom_view.html @@ -1,4 +1,4 @@ -{{ template "HEADER" }} +{{ template "HEADER" . }}  <h1>{{ .BomMeta.Name }} is a bom.</h1>  <a href="./_upload/"><button class="btn btn-mini">upload new</button></a>  <br> @@ -32,4 +32,4 @@  </tr>  {{ end }}  </table> -{{ template "FOOTER" }} +{{ template "FOOTER" . }} diff --git a/templates/home.html b/templates/home.html index 8749c58..40e8f6c 100644 --- a/templates/home.html +++ b/templates/home.html @@ -1,4 +1,4 @@ -{{ template "HEADER" }} +{{ template "HEADER" . }}  <h1>this is bomom.</h1>  <table class="table">  {{ range .BomList }} @@ -8,4 +8,4 @@  No Boms found!  {{ end }}  </table> -{{ template "FOOTER" }} +{{ template "FOOTER" . }} diff --git a/templates/static/include.js b/templates/static/include.js new file mode 100644 index 0000000..1df41c0 --- /dev/null +++ b/templates/static/include.js @@ -0,0 +1,1255 @@ +/** + * Uncompressed source can be found at https://login.persona.org/include.orig.js + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +(function() { +  // this is the file that the RP includes to shim in the +  // navigator.id.getVerifiedEmail() function +  //  "use strict"; + +  // local embedded copy of jschannel: http://github.com/mozilla/jschannel +  /** +   * js_channel is a very lightweight abstraction on top of +   * postMessage which defines message formats and semantics +   * to support interactions more rich than just message passing +   * js_channel supports: +   *  + query/response - traditional rpc +   *  + query/update/response - incremental async return of results +   *    to a query +   *  + notifications - fire and forget +   *  + error handling +   * +   * js_channel is based heavily on json-rpc, but is focused at the +   * problem of inter-iframe RPC. +   * +   * Message types: +   *  There are 5 types of messages that can flow over this channel, +   *  and you may determine what type of message an object is by +   *  examining its parameters: +   *  1. Requests +   *    + integer id +   *    + string method +   *    + (optional) any params +   *  2. Callback Invocations (or just "Callbacks") +   *    + integer id +   *    + string callback +   *    + (optional) params +   *  3. Error Responses (or just "Errors) +   *    + integer id +   *    + string error +   *    + (optional) string message +   *  4. Responses +   *    + integer id +   *    + (optional) any result +   *  5. Notifications +   *    + string method +   *    + (optional) any params +   */ +  var Channel = (function() { +    "use strict"; + +    // 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, and window. +    // futher if two bound channels have the same window and 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(win, origin, scope, handler) { +      function hasWin(arr) { +        for (var i = 0; i < arr.length; i++) if (arr[i].win === win) return true; +        return false; +      } + +      // 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 = hasWin(s_boundChans[k][scope]); +            if (exists) break; +          } +        } +      } else { +        // we must check only '*' +        if ((s_boundChans['*'] && s_boundChans['*'][scope])) { +          exists = hasWin(s_boundChans['*'][scope]); +        } +        if (!exists && s_boundChans[origin] && s_boundChans[origin][scope]) +        { +          exists = hasWin(s_boundChans[origin][scope]); +        } +      } +      if (exists) throw "A channel is already bound to the same window which overlaps with origin '"+ origin +"' and has scope '"+scope+"'"; + +      if (typeof s_boundChans[origin] != 'object') s_boundChans[origin] = { }; +      if (typeof s_boundChans[origin][scope] != 'object') s_boundChans[origin][scope] = [ ]; +      s_boundChans[origin][scope].push({win: win, handler: handler}); +    } + +    function s_removeBoundChan(win, origin, scope) { +      var arr = s_boundChans[origin][scope]; +      for (var i = 0; i < arr.length; i++) { +        if (arr[i].win === win) { +          arr.splice(i,1); +        } +      } +      if (s_boundChans[origin][scope].length === 0) { +        delete s_boundChans[origin][scope]; +      } +    } + +    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) { +      try { +        var m = JSON.parse(e.data); +        if (typeof m !== 'object' || m === null) throw "malformed"; +      } catch(e) { +        // just ignore any posted messages that do not consist of valid JSON +        return; +      } + +      var w = e.source; +      var o = e.origin; +      var s, i, meth; + +      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; + +      // w is message source window +      // o is message origin +      // m is parsed message +      // s is message scope +      // i is message id (or undefined) +      // 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') { +        var delivered = false; +        if (s_boundChans[o] && s_boundChans[o][s]) { +          for (var j = 0; j < s_boundChans[o][s].length; j++) { +            if (s_boundChans[o][s][j].win === w) { +              s_boundChans[o][s][j].handler(o, meth, m); +              delivered = true; +              break; +            } +          } +        } + +        if (!delivered && s_boundChans['*'] && s_boundChans['*'][s]) { +          for (var j = 0; j < s_boundChans['*'][s].length; j++) { +            if (s_boundChans['*'][s][j].win === w) { +              s_boundChans['*'][s][j].handler(o, meth, m); +              break; +            } +          } +        } +      } +      // 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); +      } +    }; + +    // 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 communicate +     *   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); +          } +        }; + +        /* 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"); +        } + +        /* 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].toLowerCase(); +            validOrigin = true; +          } +        } + +        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: '::'"; +        } + +        /* 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 nonexistent 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 nonexistent 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 nonexistent 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 setTransactionTimeout = function(transId, timeout, method) { +          return window.setTimeout(function() { +            if (outTbl[transId]) { +              // XXX: what if client code raises an exception here? +              var msg = "timeout (" + timeout + "ms) exceeded on method '" + method + "'"; +              (1,outTbl[transId].error)("timeout_error", msg); +              delete outTbl[transId]; +              delete s_transIds[transId]; +            } +          }, timeout); +        }; + +        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 it's 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 it's 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 it's 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); +                    /* On MSIE8, this can result in 'out of memory', which +                     * leaves message undefined. */ +                    if (typeof(message) == 'undefined') +                      message = e.toString(); +                  } 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 +            } +          } +        }; + +        // now register our bound channel for msg routing +        s_addBoundChan(cfg.window, 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; +        }; + +        // 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); +          } +        }; + +        var onReady = function(trans, type) { +          debug('ready msg received'); +          if (ready) throw "received ready message while in ready state.  help!"; + +          if (type === 'ping') { +            chanId += '-R'; +          } else { +            chanId += '-L'; +          } + +          obj.unbind('__ready'); // now this handler isn't needed any more. +          ready = true; +          debug('ready msg accepted.'); + +          if (type === 'ping') { +            obj.notify({ method: '__ready', params: 'pong' }); +          } + +          // flush queue +          while (pendingQueue.length) { +            postMessage(pendingQueue.pop()); +          } + +          // 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; +            return this; +          }, +          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; + +            if (m.timeout) +              // XXX: This function returns a timeout ID, but we don't do anything with it. +              // We might want to keep track of it so we can cancel it using clearTimeout() +              // when the transaction completes. +              setTransactionTimeout(s_curTranId, m.timeout, scopeMethod(m.method)); + +            // 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.window, 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 = ""; +          } +        }; + +        obj.bind('__ready', onReady); +        setTimeout(function() { +//          postMessage({ method: scopeMethod('__ready'), params: "ping" }, true); +        }, 0); + +        return obj; +      } +    }; +  })(); + +  // local embedded copy of winchan: http://github.com/lloyd/winchan +  // BEGIN WINCHAN + +  ;WinChan = (function() { +    var RELAY_FRAME_NAME = "__winchan_relay_frame"; +    var CLOSE_CMD = "die"; + +    // a portable addListener implementation +    function addListener(w, event, cb) { +      if(w.attachEvent) w.attachEvent('on' + event, cb); +      else if (w.addEventListener) w.addEventListener(event, cb, false); +    } + +    // a portable removeListener implementation +    function removeListener(w, event, cb) { +      if(w.detachEvent) w.detachEvent('on' + event, cb); +      else if (w.removeEventListener) w.removeEventListener(event, cb, false); +    } + +    // checking for IE8 or above +    function isInternetExplorer() { +      var rv = -1; // Return value assumes failure. +      if (navigator.appName === 'Microsoft Internet Explorer') { +        var ua = navigator.userAgent; +        var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); +        if (re.exec(ua) != null) +          rv = parseFloat(RegExp.$1); +      } +      return rv >= 8; +    } + +    // checking Mobile Firefox (Fennec) +    function isFennec() { +      try { +        // We must check for both XUL and Java versions of Fennec.  Both have +        // distinct UA strings. +        var userAgent = navigator.userAgent; +        return (userAgent.indexOf('Fennec/') != -1) ||  // XUL +               (userAgent.indexOf('Firefox/') != -1 && userAgent.indexOf('Android') != -1);   // Java +      } catch(e) {}; +      return false; +    } + +    // feature checking to see if this platform is supported at all +    function isSupported() { +      return (window.JSON && window.JSON.stringify && +              window.JSON.parse && window.postMessage); +    } + +    // given a URL, extract the origin +    function extractOrigin(url) { +      if (!/^https?:\/\//.test(url)) url = window.location.href; +      var m = /^(https?:\/\/[\-_a-zA-Z\.0-9:]+)/.exec(url); +      if (m) return m[1]; +      return url; +    } + +    // find the relay iframe in the opener +    function findRelay() { +      var loc = window.location; +      var frames = window.opener.frames; +      var origin = loc.protocol + '//' + loc.host; +      for (var i = frames.length - 1; i >= 0; i--) { +        try { +          if (frames[i].location.href.indexOf(origin) === 0 && +              frames[i].name === RELAY_FRAME_NAME) +          { +            return frames[i]; +          } +        } catch(e) { } +      } +      return; +    } + +    var isIE = isInternetExplorer(); + +    if (isSupported()) { +      /*  General flow: +       *                  0. user clicks +       *  (IE SPECIFIC)   1. caller adds relay iframe (served from trusted domain) to DOM +       *                  2. caller opens window (with content from trusted domain) +       *                  3. window on opening adds a listener to 'message' +       *  (IE SPECIFIC)   4. window on opening finds iframe +       *                  5. window checks if iframe is "loaded" - has a 'doPost' function yet +       *  (IE SPECIFIC5)  5a. if iframe.doPost exists, window uses it to send ready event to caller +       *  (IE SPECIFIC5)  5b. if iframe.doPost doesn't exist, window waits for frame ready +       *  (IE SPECIFIC5)  5bi. once ready, window calls iframe.doPost to send ready event +       *                  6. caller upon reciept of 'ready', sends args +       */ +      return { +        open: function(opts, cb) { +          if (!cb) throw "missing required callback argument"; + +          // test required options +          var err; +          if (!opts.url) err = "missing required 'url' parameter"; +          if (!opts.relay_url) err = "missing required 'relay_url' parameter"; +          if (err) setTimeout(function() { cb(err); }, 0); + +          // supply default options +          if (!opts.window_name) opts.window_name = null; +          if (!opts.window_features || isFennec()) opts.window_features = undefined; + +          // opts.params may be undefined + +          var iframe; + +          // sanity check, are url and relay_url the same origin? +          var origin = extractOrigin(opts.url); +          if (origin !== extractOrigin(opts.relay_url)) { +            return setTimeout(function() { +              cb('invalid arguments: origin of url and relay_url must match'); +            }, 0); +          } + +          var messageTarget; + +          if (isIE) { +            // first we need to add a "relay" iframe to the document that's served +            // from the target domain.  We can postmessage into a iframe, but not a +            // window +            iframe = document.createElement("iframe"); +            // iframe.setAttribute('name', framename); +            iframe.setAttribute('src', opts.relay_url); +            iframe.style.display = "none"; +            iframe.setAttribute('name', RELAY_FRAME_NAME); +            document.body.appendChild(iframe); +            messageTarget = iframe.contentWindow; +          } + +          var w = window.open(opts.url, opts.window_name, opts.window_features); + +          if (!messageTarget) messageTarget = w; + +          var req = JSON.stringify({a: 'request', d: opts.params}); + +          // cleanup on unload +          function cleanup() { +            if (iframe) document.body.removeChild(iframe); +            iframe = undefined; +            if (w) { +              try { +                w.close(); +              } catch (securityViolation) { +                // This happens in Opera 12 sometimes +                // see https://github.com/mozilla/browserid/issues/1844 +                messageTarget.postMessage(CLOSE_CMD, origin); +              } +            } +            w = messageTarget = undefined; +          } + +          addListener(window, 'unload', cleanup); + +          function onMessage(e) { +            try { +              var d = JSON.parse(e.data); +              if (d.a === 'ready') messageTarget.postMessage(req, origin); +              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(); +                if (cb) { +                  cb(null, d.d); +                  cb = null; +                } +              } +            } catch(err) { } +          } + +          addListener(window, 'message', onMessage); + +          return { +            close: cleanup, +            focus: function() { +              if (w) { +                try { +                  w.focus(); +                } catch (e) { +                  // IE7 blows up here, do nothing +                } +              } +            } +          }; +        } +      }; +    } else { +      return { +        open: function(url, winopts, arg, cb) { +          setTimeout(function() { cb("unsupported browser"); }, 0); +        } +      }; +    } +  })(); + + + +  // END WINCHAN + +  var BrowserSupport = (function() { +    var win = window, +        nav = navigator, +        reason; + +    // For unit testing +    function setTestEnv(newNav, newWindow) { +      nav = newNav; +      win = newWindow; +    } + +    function getInternetExplorerVersion() { +      var rv = -1; // Return value assumes failure. +      if (nav.appName == 'Microsoft Internet Explorer') { +        var ua = nav.userAgent; +        var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); +        if (re.exec(ua) != null) +          rv = parseFloat(RegExp.$1); +      } + +      return rv; +    } + +    function checkIE() { +      var ieVersion = getInternetExplorerVersion(), +          ieNosupport = ieVersion > -1 && ieVersion < 8; + +      if(ieNosupport) { +        return "BAD_IE_VERSION"; +      } +    } + +    function explicitNosupport() { +      return checkIE(); +    } + +    function checkLocalStorage() { +      // Firefox/Fennec/Chrome blow up when trying to access or +      // write to localStorage. We must do two explicit checks, first +      // whether the browser has localStorage.  Second, we must check +      // whether the localStorage can be written to.  Firefox (at v11) +      // throws an exception when querying win['localStorage'] +      // when cookies are disabled. Chrome (v17) excepts when trying to +      // write to localStorage when cookies are disabled. If an +      // exception is thrown, then localStorage is disabled. If no +      // exception is thrown, hasLocalStorage will be true if the +      // browser supports localStorage and it can be written to. +      try { +        var hasLocalStorage = 'localStorage' in win +                        // Firefox will except here if cookies are disabled. +                        && win['localStorage'] !== null; + +        if(hasLocalStorage) { +          // browser has localStorage, check if it can be written to. If +          // cookies are disabled, some browsers (Chrome) will except here. +          win['localStorage'].setItem("test", "true"); +          win['localStorage'].removeItem("test"); +        } +        else { +          // Browser does not have local storage. +          return "LOCALSTORAGE_NOT_SUPPORTED"; +        } +      } catch(e) { +          return "LOCALSTORAGE_DISABLED"; +      } +    } + +    function checkPostMessage() { +      if(!win.postMessage) { +        return "POSTMESSAGE_NOT_SUPPORTED"; +      } +    } + +    function checkJSON() { +      if(!(window.JSON && window.JSON.stringify && window.JSON.parse)) { +        return "JSON_NOT_SUPPORTED"; +      } +    } + +    function isSupported() { +      reason = explicitNosupport() || checkLocalStorage() || checkPostMessage() || checkJSON(); + +      return !reason; +    } + + +    function getNoSupportReason() { +      return reason; +    } + +    return { +      /** +       * Set the test environment. +       * @method setTestEnv +       */ +      setTestEnv: setTestEnv, +      /** +       * Check whether the current browser is supported +       * @method isSupported +       * @returns {boolean} +       */ +      isSupported: isSupported, +      /** +       * Called after isSupported, if isSupported returns false.  Gets the reason +       * why browser is not supported. +       * @method getNoSupportReason +       * @returns {string} +       */ +      getNoSupportReason: getNoSupportReason +    }; +  }()); + +  if (!navigator.id) { +    navigator.id = {}; +  } + +  if (!navigator.id.request || navigator.id._shimmed) { +    var ipServer = "https://login.persona.org"; +    var userAgent = navigator.userAgent; +    // We must check for both XUL and Java versions of Fennec.  Both have +    // distinct UA strings. +    var isFennec = (userAgent.indexOf('Fennec/') != -1) ||  // XUL +                     (userAgent.indexOf('Firefox/') != -1 && userAgent.indexOf('Android') != -1);   // Java + +    var windowOpenOpts = +      (isFennec ? undefined : +       "menubar=0,location=1,resizable=1,scrollbars=1,status=0,dialog=1,minimizable=1,width=700,height=375"); + +    var w; + +    // table of registered observers +    var observers = { +      login: null, +      logout: null, +      ready: null +    }; + +    var compatMode = undefined; +    function checkCompat(requiredMode) { +      if (requiredMode === true) { +        // this deprecation warning should be re-enabled when the .watch and .request APIs become final. +        // try { console.log("this site uses deprecated APIs (see documentation for navigator.id.request())"); } catch(e) { } +      } + +      if (compatMode === undefined) compatMode = requiredMode; +      else if (compatMode != requiredMode) { +        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()"; +      } +    } + +    var commChan, +        browserSupported = BrowserSupport.isSupported(); + +    // this is for calls that are non-interactive +    function _open_hidden_iframe() { +      // If this is an unsupported browser, do not even attempt to add the +      // IFRAME as doing so will cause an exception to be thrown in IE6 and IE7 +      // from within the communication_iframe. +      if(!browserSupported) return; + +      try { +        if (!commChan) { +          var doc = window.document; +          var iframe = doc.createElement("iframe"); +          iframe.style.display = "none"; +          doc.body.appendChild(iframe); +          iframe.src = ipServer + "/communication_iframe"; +          commChan = Channel.build({ +            window: iframe.contentWindow, +            origin: ipServer, +            scope: "mozid_ni", +            onReady: function() { +              // 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(){ +                  if (observers.ready) observers.ready(); +                }, error: function() { +                } +              }); +            } +          }); + +          commChan.bind('logout', function(trans, params) { +            if (observers.logout) observers.logout(); +          }); + +          commChan.bind('login', function(trans, params) { +            if (observers.login) observers.login(params); +          }); +        } +      } catch(e) { +        // channel building failed!  let's ignore the error and allow higher +        // level code to handle user messaging. +        commChan = undefined; +      } +    } + +    function defined(item) { +      return typeof item !== "undefined"; +    } + +    function warn(message) { +      try { +        console.warn(message); +      } catch(e) { +        /* ignore error */ +      } +    } + +    function checkDeprecated(options, field) { +      if(defined(options[field])) { +        warn(field + " has been deprecated"); +        return true; +      } +    } + +    function checkRenamed(options, oldName, newName) { +      if (defined(options[oldName]) && +          defined(options[newName])) { +        throw "you cannot supply *both* " + oldName + " and " + newName; +      } +      else if(checkDeprecated(options, oldName)) { +        options[newName] = options[oldName]; +        delete options[oldName]; +      } +    } + +    function internalWatch(options) { +      if (typeof options !== 'object') 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()"; +      } + +      if (!options.onlogin) throw "'onlogin' is a required argument to navigator.id.watch()"; +      if (!options.onlogout) throw "'onlogout' is a required argument to navigator.id.watch()"; + +      observers.login = options.onlogin || null; +      observers.logout = options.onlogout || null; +      observers.ready = options.onready || null; + +      _open_hidden_iframe(); + +      // back compat support for loggedInEmail +      checkRenamed(options, "loggedInEmail", "loggedInUser"); + +      // check that the commChan was properly initialized before interacting with it. +      // on unsupported browsers commChan might still be undefined, in which case +      // we let the dialog display the "unsupported browser" message upon spawning. +      if (typeof options.loggedInUser !== 'undefined' && commChan) { +        commChan.notify({ +          method: 'loggedInUser', +          params: options.loggedInUser +        }); +      } +    } + +    function internalRequest(options) { +      checkDeprecated(options, "requiredEmail"); +      checkRenamed(options, "tosURL", "termsOfService"); +      checkRenamed(options, "privacyURL", "privacyPolicy"); + +      if (options.termsOfService && !options.privacyPolicy) { +        warn("termsOfService ignored unless privacyPolicy also defined"); +      } + +      if (options.privacyPolicy && !options.termsOfService) { +        warn("privacyPolicy ignored unless termsOfService also defined"); +      } + +      // focus an existing window +      if (w) { +        try { +          w.focus(); +        } +        catch(e) { +          /* IE7 blows up here, do nothing */ +        } +        return; +      } + +      if (!BrowserSupport.isSupported()) { +        var reason = BrowserSupport.getNoSupportReason(), +        url = "unsupported_dialog"; + +        if(reason === "LOCALSTORAGE_DISABLED") { +          url = "cookies_disabled"; +        } + +        w = window.open( +          ipServer + "/" + url, +          null, +          windowOpenOpts); +        return; +      } + +      // notify the iframe that the dialog is running so we +      // don't do duplicative work +      if (commChan) commChan.notify({ method: 'dialog_running' }); + +      w = WinChan.open({ +        url: ipServer + '/sign_in', +        relay_url: ipServer + '/relay', +        window_features: windowOpenOpts, +        window_name: '__persona_dialog', +        params: { +          method: "get", +          params: options +        } +      }, function(err, r) { +        // unpause the iframe to detect future changes in login state +        if (commChan) { +          // update the loggedInUser in the case that an assertion was generated, as +          // this will prevent the comm iframe from thinking that state has changed +          // and generating a new assertion.  IF, however, this request is not a success, +          // then we do not change the loggedInUser - and we will let the comm frame determine +          // if generating a logout event is the right thing to do +          if (!err && r && r.email) { +            commChan.notify({ method: 'loggedInUser', params: r.email }); +          } +          commChan.notify({ method: 'dialog_complete' }); +        } + +        // clear the window handle +        w = undefined; +        if (!err && r && r.assertion) { +          try { +            if (observers.login) observers.login(r.assertion); +          } catch(e) { +            // client's observer threw an exception +          } +        } + +        // if either err indicates the user canceled the signin (expected) or a +        // null response was sent (unexpected), invoke the .oncancel() handler. +        if (err === 'client closed window' || !r) { +          if (options && options.oncancel) options.oncancel(); +          delete options.oncancel; +        } +      }); +    }; + +    navigator.id = { +      request: function(options) { +        if (this != navigator.id) +          throw new Error("all navigator.id calls must be made on the navigator.id object"); +        options = options || {}; +        checkCompat(false); +        // returnTo is used for post-email-verification redirect +        if (!options.returnTo) options.returnTo = document.location.pathname; +        return internalRequest(options); +      }, +      watch: function(options) { +        if (this != navigator.id) +          throw new Error("all navigator.id calls must be made on the navigator.id object"); +        checkCompat(false); +        internalWatch(options); +      }, +      // logout from the current website +      // The callback parameter is DEPRECATED, instead you should use the +      // the .onlogout observer of the .watch() api. +      logout: function(callback) { +        if (this != navigator.id) +          throw new Error("all navigator.id calls must be made on the navigator.id object"); +        // allocate iframe if it is not allocated +        _open_hidden_iframe(); +        // send logout message if the commChan exists +        if (commChan) commChan.notify({ method: 'logout' }); +        if (typeof callback === 'function') setTimeout(callback, 0); +      }, +      // get an assertion +      get: function(callback, passedOptions) { +        var opts = {}; +        passedOptions = passedOptions || {}; +        opts.privacyPolicy =  passedOptions.privacyPolicy || undefined; +        opts.termsOfService = passedOptions.termsOfService || undefined; +        opts.privacyURL = passedOptions.privacyURL || undefined; +        opts.tosURL = passedOptions.tosURL || undefined; + +        if (checkDeprecated(passedOptions, "silent")) { +          // Silent has been deprecated, do nothing.  Placing the check here +          // prevents the callback from being called twice, once with null and +          // once after internalWatch has been called.  See issue #1532 +          if (callback) setTimeout(function() { callback(null); }, 0); +          return; +        } + +        checkCompat(true); +        internalWatch({ +          onlogin: function(assertion) { +            if (callback) { +              callback(assertion); +              callback = null; +            } +          }, +          onlogout: function() {} +        }); +        opts.oncancel = function() { +          if (callback) { +            callback(null); +            callback = null; +          } +          observers.login = observers.logout = observers.ready = null; +        }; +        internalRequest(opts); +      }, +      // backwards compatibility with old API +      getVerifiedEmail: function(callback) { +        warn("navigator.id.getVerifiedEmail has been deprecated"); +        checkCompat(true); +        navigator.id.get(callback); +      }, +      // required for forwards compatibility with native implementations +      _shimmed: true +    }; +  } +}()); diff --git a/templates/user.html b/templates/user.html index 296db8c..995819c 100644 --- a/templates/user.html +++ b/templates/user.html @@ -1,4 +1,4 @@ -{{ template "HEADER" }} +{{ template "HEADER" . }}  <h1>{{ .UserName }} is a bommom user.</h1>  <table class="table">  {{ range .BomList }} @@ -8,4 +8,4 @@  No Boms found!  {{ end }}  </table> -{{ template "FOOTER" }} +{{ template "FOOTER" . }}  | 
