web ui updates
This commit is contained in:
		
							parent
							
								
									b81f0ecede
								
							
						
					
					
						commit
						9e1c9c00ca
					
				@ -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