MAJOR: Updates for Authenticated Web UI and CLI #30
| @ -7,7 +7,7 @@ | ||||
|   <div class="v-app"> | ||||
|     <h1>Telebit (Remote) Setup</h1> | ||||
| 
 | ||||
|     <section v-if="views.section.create"> | ||||
|     <section v-if="views.section.setup"> | ||||
|       <h2>Create Account</h2> | ||||
|       <form v-on:submit.stop.prevent="initialize"> | ||||
| 
 | ||||
| @ -43,12 +43,23 @@ | ||||
|         </small> | ||||
| 
 | ||||
|         <details><summary><small>Advanced</small></summary> | ||||
|         <label for="-relay">Relay:</label><input id="-relay" v-model="init.relay" type="text" placeholder="telebit.cloud"> | ||||
| 
 | ||||
|         <label for="-relay">Relay:</label> | ||||
|           <input id="-relay" v-model="init.relay" type="text" placeholder="telebit.cloud"> | ||||
|         <br> | ||||
|         <button type="button" v-on:click="defaultRelay">Use Default</button> | ||||
|         <button type="button" v-on:click="betaRelay">Use Beta</button> | ||||
|         <br> | ||||
|         <br> | ||||
| 
 | ||||
|         <label for="-acme-server">ACME (Let's Encrypt) Server:</label> | ||||
|           <input id="-acme-server" v-model="init.acmeServer" type="text" placeholder="https://acme-v02.api.letsencrypt.org/directory"> | ||||
|         <br> | ||||
|         <button type="button" v-on:click="productionAcme">Use Production</button> | ||||
|         <button type="button" v-on:click="stagingAcme">Use Staging</button> | ||||
|         <br> | ||||
|         <br> | ||||
| 
 | ||||
|         </details> | ||||
| 
 | ||||
|         <button type="submit">Accept & Continue</button> | ||||
| @ -58,22 +69,32 @@ | ||||
|     </section> | ||||
| 
 | ||||
|     <section v-if="views.section.advanced"> | ||||
|       <h2>Advanced Setup</h2> | ||||
|       <form v-on:submit.stop.prevent="initialize"> | ||||
|       <h2>Advanced Setup for {{ init.relay }}</h2> | ||||
|       <form v-on:submit.stop.prevent="advance"> | ||||
| 
 | ||||
|         <label for="-secret">Relay Secret:</label> | ||||
|         <strong><label for="-secret">Relay Shared Secret:</label></strong> | ||||
|         <input id="-secret" v-model="init.secret" type="text" placeholder="ex: xxxxxxxxxxxx"> | ||||
|         <br> | ||||
| 
 | ||||
|         <strong><label for="-domains">Domains:</label></strong> | ||||
|         <br> | ||||
|         <small>(comma separated list of domains to use for http, tls, https, etc)</small> | ||||
|         <br> | ||||
|         <input id="-domains" v-model="init.domains" type="text" placeholder="ex: whatever.com, example.com"> | ||||
|         <br> | ||||
| 
 | ||||
|         <strong><label for="-ports">TCP Ports:</label></strong> | ||||
|         <br> | ||||
|         <small>(comman separated list of ports, excluding 80 and 443, typically port over 1024)</small> | ||||
|         <br> | ||||
|         <input id="-ports" v-model="init.ports" type="text" placeholder="ex: 5050, 3000, 8080"> | ||||
|         <br> | ||||
| 
 | ||||
|         <label for="-telemetry"><input id="-telemetry" v-model="init.telemetry" type="checkbox"> | ||||
|           Contribute to Telebit by sharing telemetry</label> | ||||
|         <br> | ||||
| 
 | ||||
|         <label for="-relay">[Advanced] Relay:</label> | ||||
|         <input id="-relay" v-model="init.relay" type="text" placeholder="telebit.cloud"> | ||||
|         <br> | ||||
| 
 | ||||
|         <button type="submit">Accept & Continue</button> | ||||
|         <button type="submit">Finish</button> | ||||
| 
 | ||||
|       </form> | ||||
|       <pre><code>{{ init }}</code></pre> | ||||
|  | ||||
| @ -1,14 +1,29 @@ | ||||
| ;(function () { | ||||
| 'use strict'; | ||||
| 
 | ||||
| console.log("hello"); | ||||
| 
 | ||||
| var Vue = window.Vue; | ||||
| var Telebit = window.TELEBIT; | ||||
| var api = {}; | ||||
| 
 | ||||
| /*globals AbortController*/ | ||||
| function safeFetch(url, opts) { | ||||
|   var controller = new AbortController(); | ||||
|   var tok = setTimeout(function () { | ||||
|     controller.abort(); | ||||
|   }, 4000); | ||||
|   if (!opts) { | ||||
|     opts = {}; | ||||
|   } | ||||
|   opts.signal = controller.signal; | ||||
|   return window.fetch(url, opts).finally(function () { | ||||
|     clearTimeout(tok); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| api.config = function apiConfig() { | ||||
|   return window.fetch("/api/config", { method: "GET" }).then(function (resp) { | ||||
|   return safeFetch("/api/config", { | ||||
|     method: "GET" | ||||
|   }).then(function (resp) { | ||||
|     return resp.json().then(function (json) { | ||||
|       appData.config = json; | ||||
|       return json; | ||||
| @ -16,17 +31,43 @@ api.config = function apiConfig() { | ||||
|   }); | ||||
| }; | ||||
| api.status = function apiStatus() { | ||||
|   return window.fetch("/api/status", { method: "GET" }).then(function (resp) { | ||||
|   return safeFetch("/api/status", { method: "GET" }).then(function (resp) { | ||||
|     return resp.json().then(function (json) { | ||||
|       appData.status = json; | ||||
|       return json; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| api.initialize = function apiInitialize() { | ||||
|   var opts = { | ||||
|     method: "POST" | ||||
|   , headers: { | ||||
|       'Content-Type': 'application/json' | ||||
|     } | ||||
|   , body: JSON.stringify({ | ||||
|       foo: 'bar' | ||||
|     }) | ||||
|   }; | ||||
|   return safeFetch("/api/init", opts).then(function (resp) { | ||||
|     return resp.json().then(function (json) { | ||||
|       appData.initResult = json; | ||||
|       window.alert("Error: [success] " + JSON.stringify(json, null, 2)); | ||||
|       return json; | ||||
|     }).catch(function (err) { | ||||
|       window.alert("Error: [init] " + (err.message || JSON.stringify(err, null, 2))); | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // TODO test for internet connectivity (and telebit connectivity)
 | ||||
| var DEFAULT_RELAY = 'telebit.cloud'; | ||||
| var BETA_RELAY = 'telebit.ppl.family'; | ||||
| var TELEBIT_RELAYS = [ | ||||
|   DEFAULT_RELAY | ||||
| , BETA_RELAY | ||||
| ]; | ||||
| var PRODUCTION_ACME = 'https://acme-v02.api.letsencrypt.org/directory'; | ||||
| var STAGING_ACME = 'https://acme-staging-v02.api.letsencrypt.org/directory'; | ||||
| var appData = { | ||||
|   config: null | ||||
| , status: null | ||||
| @ -35,40 +76,93 @@ var appData = { | ||||
|   , letos: true | ||||
|   , notifications: "important" | ||||
|   , relay: DEFAULT_RELAY | ||||
|   , telemetry: true | ||||
|   , acmeServer: PRODUCTION_ACME | ||||
|   } | ||||
| , http: null | ||||
| , tcp: null | ||||
| , ssh: null | ||||
| , views: { | ||||
|     section: { | ||||
|       create: true | ||||
|       setup: false | ||||
|     , advanced: false | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| var telebitState = {}; | ||||
| var appMethods = { | ||||
|   initialize: function () { | ||||
|     console.log("call initialize"); | ||||
|     if (!appData.init.relay) { | ||||
|       appData.init.relay = DEFAULT_RELAY; | ||||
|     } | ||||
|     if (DEFAULT_RELAY !== appData.init.relay) { | ||||
|       window.alert("TODO: Custom Relay Not Implemented Yet"); | ||||
|     } | ||||
|     Telebit.api.directory({ relay: appData.init.relay }, function (err, dir) { | ||||
|       if (err) { | ||||
|         window.alert("Error:" + (err.message || JSON.stringify(err, null, 2))); | ||||
|     appData.init.relay = appData.init.relay.toLowerCase(); | ||||
|     telebitState = { relay: appData.init.relay }; | ||||
|     return Telebit.api.directory(telebitState).then(function (dir) { | ||||
|       if (!dir.api_host) { | ||||
|         window.alert("Error: '" + telebitState.relay + "' does not appear to be a valid telebit service"); | ||||
|         return; | ||||
|       } | ||||
|       window.alert("Success:" + JSON.stringify(dir, null, 2)); | ||||
|       if (-1 !== TELEBIT_RELAYS.indexOf(appData.init.relay)) { | ||||
|         return api.initialize(); | ||||
|       } else { | ||||
|         changeState('advanced'); | ||||
|       } | ||||
|     }).catch(function (err) { | ||||
|       window.alert("Error: [directory] " + (err.message || JSON.stringify(err, null, 2))); | ||||
|     }); | ||||
|   } | ||||
| , advance: function () { | ||||
|     return api.initialize(); | ||||
|   } | ||||
| , productionAcme: function () { | ||||
|     console.log("prod acme:"); | ||||
|     appData.init.acmeServer = PRODUCTION_ACME; | ||||
|     console.log(appData.init.acmeServer); | ||||
|   } | ||||
| , stagingAcme: function () { | ||||
|     console.log("staging acme:"); | ||||
|     appData.init.acmeServer = STAGING_ACME; | ||||
|     console.log(appData.init.acmeServer); | ||||
|   } | ||||
| , defaultRelay: function () { | ||||
|     appData.init.relay = DEFAULT_RELAY; | ||||
|   } | ||||
| , betaRelay: function () { | ||||
|     appData.init.relay = BETA_RELAY; | ||||
|   } | ||||
| , defaultRhubarb: function () { | ||||
|     appData.init.rhubarb = DEFAULT_RELAY; | ||||
|   } | ||||
| , betaRhubarb: function () { | ||||
|     appData.init.rhubarb = BETA_RELAY; | ||||
|   } | ||||
| }; | ||||
| var appStates = { | ||||
|   setup: function () { | ||||
|     appData.views.section = { setup: true }; | ||||
|   } | ||||
| , advanced: function () { | ||||
|     appData.views.section = { advanced: true }; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| function changeState(newstate) { | ||||
|   location.hash = '#/' + newstate + '/'; | ||||
| } | ||||
| window.addEventListener('hashchange', setState, false); | ||||
| function setState(/*ev*/) { | ||||
|   //ev.oldURL
 | ||||
|   //ev.newURL
 | ||||
|   var parts = location.hash.substr(1).replace(/^\//, '').replace(/\/$/, '').split('/'); | ||||
|   var fn = appStates; | ||||
|   parts.forEach(function (s) { | ||||
|     console.log("state:", s); | ||||
|     fn = fn[s]; | ||||
|   }); | ||||
|   fn(); | ||||
|   //appMethods.states[newstate]();
 | ||||
| } | ||||
| 
 | ||||
| new Vue({ | ||||
|   el: ".v-app" | ||||
| @ -76,8 +170,12 @@ new Vue({ | ||||
| , methods: appMethods | ||||
| }); | ||||
| 
 | ||||
| 
 | ||||
| api.config(); | ||||
| api.status(); | ||||
| api.status().then(function () { | ||||
|   changeState('setup'); | ||||
|   setState(); | ||||
| }); | ||||
| 
 | ||||
| window.api = api; | ||||
| }()); | ||||
|  | ||||
| @ -11,6 +11,7 @@ if ('undefined' !== typeof Promise) { | ||||
|   throw new Error("no Promise implementation defined"); | ||||
| } | ||||
| 
 | ||||
| /*globals AbortController*/ | ||||
| if ('undefined' !== typeof fetch) { | ||||
|   common.requestAsync = function (opts) { | ||||
|     /* | ||||
| @ -37,7 +38,16 @@ if ('undefined' !== typeof fetch) { | ||||
|       } | ||||
|     , body: JSON.stringify(opts) | ||||
|     }; | ||||
|     var controller = new AbortController(); | ||||
|     var tok = setTimeout(function () { | ||||
|       controller.abort(); | ||||
|     }, 4000); | ||||
|     if (!relayOpts) { | ||||
|       relayOpts = {}; | ||||
|     } | ||||
|     relayOpts.signal = controller.signal; | ||||
|     return window.fetch(relayOpts.url, relayOpts).then(function (resp) { | ||||
|       clearTimeout(tok); | ||||
|       return resp.json().then(function (json) { | ||||
|         /* | ||||
|         var headers = {}; | ||||
| @ -100,17 +110,20 @@ common.signToken = function (state) { | ||||
|   return jwt.sign(tokenData, state.config.secret); | ||||
| }; | ||||
| common.api = {}; | ||||
| common.api.directory = function (state, next) { | ||||
|   console.log('state:'); | ||||
| common.api.directory = function (state) { | ||||
|   console.log('[DEBUG] state:'); | ||||
|   console.log(state); | ||||
|   state._relayUrl = common.parseUrl(state.relay); | ||||
|   common.requestAsync({ url: state._relayUrl + common.apiDirectory, json: true }).then(function (resp) { | ||||
|   if (!state._relays) { state._relays = {}; } | ||||
|   if (state._relays[state._relayUrl]) { | ||||
|     return PromiseA.resolve(state._relays[state._relayUrl]); | ||||
|   } | ||||
|   return common.requestAsync({ url: state._relayUrl + common.apiDirectory, json: true }).then(function (resp) { | ||||
|     var dir = resp.body; | ||||
|     if (!dir) { dir = { api_host: ':hostname', tunnel: { method: "wss", pathname: "" } }; } | ||||
|     state._apiDirectory = dir; | ||||
|     next(null, dir); | ||||
|     state._relays[state._relayUrl] = dir; | ||||
|     return dir; | ||||
|   }).catch(function (err) { | ||||
|     next(err); | ||||
|     return PromiseA.reject(err); | ||||
|   }); | ||||
| }; | ||||
| common.api._parseWss = function (state, dir) { | ||||
| @ -121,153 +134,159 @@ common.api._parseWss = function (state, dir) { | ||||
|   return dir.tunnel.method + '://' + dir.api_host.replace(/:hostname/g, state._relayHostname) + dir.tunnel.pathname; | ||||
| }; | ||||
| common.api.wss = function (state, cb) { | ||||
|   common.api.directory(state, function (err, dir) { | ||||
|     cb(err, common.api._parseWss(state, dir)); | ||||
|   }); | ||||
|   common.api.directory(state).then(function (dir) { | ||||
|     cb(null, common.api._parseWss(state, dir)); | ||||
|   }).catch(cb); | ||||
| }; | ||||
| common.api.token = function (state, handlers) { | ||||
|   common.api.directory(state, function (err, dir) { | ||||
|     // directory, requested, connect, tunnelUrl, offer, granted, end
 | ||||
|     function afterDir() { | ||||
|       if (common.debug) { console.log('[debug] after dir'); } | ||||
|       state.wss = common.api._parseWss(state, dir); | ||||
|   // directory, requested, connect, tunnelUrl, offer, granted, end
 | ||||
|   function afterDir(err, dir) { | ||||
|     if (common.debug) { console.log('[debug] after dir'); } | ||||
|     state.wss = common.api._parseWss(state, dir); | ||||
| 
 | ||||
|       handlers.tunnelUrl(state.wss, function () { | ||||
|         if (common.debug) { console.log('[debug] after tunnelUrl'); } | ||||
|         if (state.config.secret /* && !state.config.token */) { | ||||
|           state.config._token = common.signToken(state); | ||||
|         } | ||||
|         state.token = state.token || state.config.token || state.config._token; | ||||
|         if (state.token) { | ||||
|           if (common.debug) { console.log('[debug] token via token or secret'); } | ||||
|           // { token, pretoken }
 | ||||
|           handlers.connect(state.token, function () { | ||||
|             handlers.end(null, function () {}); | ||||
|           }); | ||||
|           return; | ||||
|         } | ||||
|     handlers.tunnelUrl(state.wss, function () { | ||||
|       if (common.debug) { console.log('[debug] after tunnelUrl'); } | ||||
|       if (state.config.secret /* && !state.config.token */) { | ||||
|         state.config._token = common.signToken(state); | ||||
|       } | ||||
|       state.token = state.token || state.config.token || state.config._token; | ||||
|       if (state.token) { | ||||
|         if (common.debug) { console.log('[debug] token via token or secret'); } | ||||
|         // { token, pretoken }
 | ||||
|         handlers.connect(state.token, function () { | ||||
|           handlers.end(null, function () {}); | ||||
|         }); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|         // backwards compat (TODO remove)
 | ||||
|         if (err || !dir || !dir.pair_request) { | ||||
|           if (common.debug) { console.log('[debug] no dir, connect'); } | ||||
|           handlers.error(new Error("No token found or generated, and no pair_request api found.")); | ||||
|           return; | ||||
|         } | ||||
|       // backwards compat (TODO remove)
 | ||||
|       if (err || !dir || !dir.pair_request) { | ||||
|         if (common.debug) { console.log('[debug] no dir, connect'); } | ||||
|         handlers.error(new Error("No token found or generated, and no pair_request api found.")); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|         // TODO sign token with own private key, including public key and thumbprint
 | ||||
|         //      (much like ACME JOSE account)
 | ||||
|         var otp = state.config._otp; // common.otp();
 | ||||
|         var authReq = { | ||||
|           subject: state.config.email | ||||
|         , subject_scheme: 'mailto' | ||||
|           // TODO create domains list earlier
 | ||||
|         , scope: (state.config._servernames || Object.keys(state.config.servernames || {})) | ||||
|             .concat(state.config._ports || Object.keys(state.config.ports || {})).join(',') | ||||
|         , otp: otp | ||||
|         // TODO make call to daemon for this info beforehand
 | ||||
|         /* | ||||
|         , hostname: os.hostname() | ||||
|           // Used for User-Agent
 | ||||
|         , os_type: os.type() | ||||
|         , os_platform: os.platform() | ||||
|         , os_release: os.release() | ||||
|         , os_arch: os.arch() | ||||
|         */ | ||||
|         }; | ||||
|         var pairRequestUrl = new URL(dir.pair_request.pathname, 'https://' + dir.api_host.replace(/:hostname/g, state._relayHostname)); | ||||
|         var req = { | ||||
|           url: pairRequestUrl | ||||
|         , method: dir.pair_request.method | ||||
|         , json: authReq | ||||
|         }; | ||||
|         var firstReq = true; | ||||
|         var firstReady = true; | ||||
|       // TODO sign token with own private key, including public key and thumbprint
 | ||||
|       //      (much like ACME JOSE account)
 | ||||
|       var otp = state.config._otp; // common.otp();
 | ||||
|       var authReq = { | ||||
|         subject: state.config.email | ||||
|       , subject_scheme: 'mailto' | ||||
|         // TODO create domains list earlier
 | ||||
|       , scope: (state.config._servernames || Object.keys(state.config.servernames || {})) | ||||
|           .concat(state.config._ports || Object.keys(state.config.ports || {})).join(',') | ||||
|       , otp: otp | ||||
|       // TODO make call to daemon for this info beforehand
 | ||||
|       /* | ||||
|       , hostname: os.hostname() | ||||
|         // Used for User-Agent
 | ||||
|       , os_type: os.type() | ||||
|       , os_platform: os.platform() | ||||
|       , os_release: os.release() | ||||
|       , os_arch: os.arch() | ||||
|       */ | ||||
|       }; | ||||
|       var pairRequestUrl = new URL(dir.pair_request.pathname, 'https://' + dir.api_host.replace(/:hostname/g, state._relayHostname)); | ||||
|       var req = { | ||||
|         url: pairRequestUrl | ||||
|       , method: dir.pair_request.method | ||||
|       , json: authReq | ||||
|       }; | ||||
|       var firstReq = true; | ||||
|       var firstReady = true; | ||||
| 
 | ||||
|         function gotoNext(req) { | ||||
|           if (common.debug) { console.log('[debug] gotoNext called'); } | ||||
|           if (common.debug) { console.log(req); } | ||||
|           common.requestAsync(req).then(function (resp) { | ||||
|             var body = resp.body; | ||||
|       function gotoNext(req) { | ||||
|         if (common.debug) { console.log('[debug] gotoNext called'); } | ||||
|         if (common.debug) { console.log(req); } | ||||
|         common.requestAsync(req).then(function (resp) { | ||||
|           var body = resp.body; | ||||
| 
 | ||||
|             function checkLocation() { | ||||
|               if (common.debug) { console.log('[debug] checkLocation'); } | ||||
|               if (common.debug) { console.log(body); } | ||||
|               // pending, try again
 | ||||
|               if ('pending' === body.status && resp.headers.location) { | ||||
|                 if (common.debug) { console.log('[debug] pending'); } | ||||
|                 setTimeout(gotoNext, 2 * 1000, { url: resp.headers.location, json: true }); | ||||
|                 return; | ||||
|               } | ||||
| 
 | ||||
|               if ('ready' === body.status) { | ||||
|                 if (common.debug) { console.log('[debug] ready'); } | ||||
|                 if (firstReady) { | ||||
|                   if (common.debug) { console.log('[debug] first ready'); } | ||||
|                   firstReady = false; | ||||
|                   state.token = body.access_token; | ||||
|                   state.config.token = state.token; | ||||
|                   handlers.offer(body.access_token, function () { | ||||
|                     /*ignore*/ | ||||
|                   }); | ||||
|                 } | ||||
|                 setTimeout(gotoNext, 2 * 1000, req); | ||||
|                 return; | ||||
|               } | ||||
| 
 | ||||
|               if ('complete' === body.status) { | ||||
|                 if (common.debug) { console.log('[debug] complete'); } | ||||
|                 handlers.granted(null, function () { | ||||
|                   handlers.end(null, function () {}); | ||||
|                 }); | ||||
|                 return; | ||||
|               } | ||||
| 
 | ||||
|               if (common.debug) { console.log('[debug] bad status'); } | ||||
|               var err = new Error("Bad State:" + body.status); | ||||
|               err._request = req; | ||||
|               handlers.error(err, function () {}); | ||||
|             } | ||||
| 
 | ||||
|             if (firstReq) { | ||||
|               if (common.debug) { console.log('[debug] first req'); } | ||||
|               handlers.requested(authReq, function () { | ||||
|                 handlers.connect(body.access_token || body.jwt, function () { | ||||
|                   var err; | ||||
|                   if (!resp.headers.location) { | ||||
|                     err = new Error("bad authentication request response"); | ||||
|                     err._resp = resp.toJSON && resp.toJSON(); | ||||
|                     handlers.error(err, function () {}); | ||||
|                     return; | ||||
|                   } | ||||
|                   setTimeout(gotoNext, 2 * 1000, { url: resp.headers.location, json: true }); | ||||
|                 }); | ||||
|               }); | ||||
|               firstReq = false; | ||||
|           function checkLocation() { | ||||
|             if (common.debug) { console.log('[debug] checkLocation'); } | ||||
|             if (common.debug) { console.log(body); } | ||||
|             // pending, try again
 | ||||
|             if ('pending' === body.status && resp.headers.location) { | ||||
|               if (common.debug) { console.log('[debug] pending'); } | ||||
|               setTimeout(gotoNext, 2 * 1000, { url: resp.headers.location, json: true }); | ||||
|               return; | ||||
|             } else { | ||||
|               if (common.debug) { console.log('[debug] other req'); } | ||||
|               checkLocation(); | ||||
|             } | ||||
|           }).catch(function (err) { | ||||
|             if (common.debug) { console.log('[debug] gotoNext error'); } | ||||
| 
 | ||||
|             if ('ready' === body.status) { | ||||
|               if (common.debug) { console.log('[debug] ready'); } | ||||
|               if (firstReady) { | ||||
|                 if (common.debug) { console.log('[debug] first ready'); } | ||||
|                 firstReady = false; | ||||
|                 state.token = body.access_token; | ||||
|                 state.config.token = state.token; | ||||
|                 handlers.offer(body.access_token, function () { | ||||
|                   /*ignore*/ | ||||
|                 }); | ||||
|               } | ||||
|               setTimeout(gotoNext, 2 * 1000, req); | ||||
|               return; | ||||
|             } | ||||
| 
 | ||||
|             if ('complete' === body.status) { | ||||
|               if (common.debug) { console.log('[debug] complete'); } | ||||
|               handlers.granted(null, function () { | ||||
|                 handlers.end(null, function () {}); | ||||
|               }); | ||||
|               return; | ||||
|             } | ||||
| 
 | ||||
|             if (common.debug) { console.log('[debug] bad status'); } | ||||
|             var err = new Error("Bad State:" + body.status); | ||||
|             err._request = req; | ||||
|             err._hint = '[telebitd.js] pair request'; | ||||
|             handlers.error(err, function () {}); | ||||
|           }); | ||||
|         } | ||||
|           } | ||||
| 
 | ||||
|         gotoNext(req); | ||||
|           if (firstReq) { | ||||
|             if (common.debug) { console.log('[debug] first req'); } | ||||
|             handlers.requested(authReq, function () { | ||||
|               handlers.connect(body.access_token || body.jwt, function () { | ||||
|                 var err; | ||||
|                 if (!resp.headers.location) { | ||||
|                   err = new Error("bad authentication request response"); | ||||
|                   err._resp = resp.toJSON && resp.toJSON(); | ||||
|                   handlers.error(err, function () {}); | ||||
|                   return; | ||||
|                 } | ||||
|                 setTimeout(gotoNext, 2 * 1000, { url: resp.headers.location, json: true }); | ||||
|               }); | ||||
|             }); | ||||
|             firstReq = false; | ||||
|             return; | ||||
|           } else { | ||||
|             if (common.debug) { console.log('[debug] other req'); } | ||||
|             checkLocation(); | ||||
|           } | ||||
|         }).catch(function (err) { | ||||
|           if (common.debug) { console.log('[debug] gotoNext error'); } | ||||
|           err._request = req; | ||||
|           err._hint = '[telebitd.js] pair request'; | ||||
|           handlers.error(err, function () {}); | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     if (dir && dir.api_host) { | ||||
|       handlers.directory(dir, afterDir); | ||||
|     } else { | ||||
|       // backwards compat
 | ||||
|       dir = { api_host: ':hostname', tunnel: { method: "wss", pathname: "" } }; | ||||
|       afterDir(); | ||||
|       gotoNext(req); | ||||
| 
 | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // backwards compat (TODO verify we can remove this)
 | ||||
|   var failoverDir = '{ "api_host": ":hostname", "tunnel": { "method": "wss", "pathname": "" } }'; | ||||
|   common.api.directory(state).then(function (dir) { | ||||
|     if (!dir.api_host) { | ||||
|       dir = JSON.parse(failoverDir); | ||||
|       return afterDir(null, dir); | ||||
|     } | ||||
|     handlers.directory(dir).then(function (dir) { | ||||
|       return afterDir(null, dir); | ||||
|     }).catch(function (err) { | ||||
|       return PromiseA.reject(err); | ||||
|     }); | ||||
|   }).catch(function (err) { | ||||
|     return afterDir(err, JSON.parse(failoverDir)); | ||||
|   }); | ||||
| 
 | ||||
| }; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user