MAJOR: Updates for Authenticated Web UI and CLI #30
|
@ -338,9 +338,12 @@ controllers.relay = function (req, res, opts) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('POST /api/relay:');
|
||||||
|
console.log(opts.body);
|
||||||
|
console.log();
|
||||||
return urequestAsync(opts.body).then(function (resp) {
|
return urequestAsync(opts.body).then(function (resp) {
|
||||||
res.setHeader('Content-Type', 'application/json');
|
res.setHeader('Content-Type', 'application/json');
|
||||||
var resp = resp.toJSON();
|
resp = resp.toJSON();
|
||||||
res.end(JSON.stringify(resp));
|
res.end(JSON.stringify(resp));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -104,6 +104,7 @@
|
||||||
|
|
||||||
<script src="/js/vue.js"></script>
|
<script src="/js/vue.js"></script>
|
||||||
<script src="/js/telebit.js"></script>
|
<script src="/js/telebit.js"></script>
|
||||||
|
<script src="/js/telebit-token.js"></script>
|
||||||
<script src="/js/app.js"></script>
|
<script src="/js/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -5,7 +5,7 @@ var Vue = window.Vue;
|
||||||
var Telebit = window.TELEBIT;
|
var Telebit = window.TELEBIT;
|
||||||
var api = {};
|
var api = {};
|
||||||
|
|
||||||
/*globals AbortController*/
|
/*
|
||||||
function safeFetch(url, opts) {
|
function safeFetch(url, opts) {
|
||||||
var controller = new AbortController();
|
var controller = new AbortController();
|
||||||
var tok = setTimeout(function () {
|
var tok = setTimeout(function () {
|
||||||
|
@ -19,28 +19,29 @@ function safeFetch(url, opts) {
|
||||||
clearTimeout(tok);
|
clearTimeout(tok);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
api.config = function apiConfig() {
|
api.config = function apiConfig() {
|
||||||
return safeFetch("/api/config", {
|
return Telebit.reqLocalAsync({
|
||||||
method: "GET"
|
url: "/api/config"
|
||||||
|
, method: "GET"
|
||||||
}).then(function (resp) {
|
}).then(function (resp) {
|
||||||
return resp.json().then(function (json) {
|
var json = resp.body;
|
||||||
appData.config = json;
|
appData.config = json;
|
||||||
return json;
|
return json;
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
api.status = function apiStatus() {
|
api.status = function apiStatus() {
|
||||||
return safeFetch("/api/status", { method: "GET" }).then(function (resp) {
|
return Telebit.reqLocalAsync({ url: "/api/status", method: "GET" }).then(function (resp) {
|
||||||
return resp.json().then(function (json) {
|
var json = resp.body;
|
||||||
appData.status = json;
|
appData.status = json;
|
||||||
return json;
|
return json;
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
api.initialize = function apiInitialize() {
|
api.initialize = function apiInitialize() {
|
||||||
var opts = {
|
var opts = {
|
||||||
method: "POST"
|
url: "/api/init"
|
||||||
|
, method: "POST"
|
||||||
, headers: {
|
, headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
|
@ -48,15 +49,14 @@ api.initialize = function apiInitialize() {
|
||||||
foo: 'bar'
|
foo: 'bar'
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
return safeFetch("/api/init", opts).then(function (resp) {
|
return Telebit.reqLocalAsync(opts).then(function (resp) {
|
||||||
return resp.json().then(function (json) {
|
var json = resp.body;
|
||||||
appData.initResult = json;
|
appData.initResult = json;
|
||||||
window.alert("Error: [success] " + JSON.stringify(json, null, 2));
|
window.alert("Error: [success] " + JSON.stringify(json, null, 2));
|
||||||
return json;
|
return json;
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
window.alert("Error: [init] " + (err.message || JSON.stringify(err, null, 2)));
|
window.alert("Error: [init] " + (err.message || JSON.stringify(err, null, 2)));
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO test for internet connectivity (and telebit connectivity)
|
// TODO test for internet connectivity (and telebit connectivity)
|
||||||
|
@ -104,15 +104,27 @@ var appMethods = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (-1 !== TELEBIT_RELAYS.indexOf(appData.init.relay)) {
|
if (-1 !== TELEBIT_RELAYS.indexOf(appData.init.relay)) {
|
||||||
|
if (!telebitState.config) { telebitState.config = {}; }
|
||||||
|
if (!telebitState.config.relay) { telebitState.config.relay = telebitState.relay; }
|
||||||
|
telebitState.config.email = appData.init.email;
|
||||||
|
telebitState.config._otp = Telebit.otp();
|
||||||
|
return Telebit.authorize(telebitState).then(function () {
|
||||||
|
console.log('1 api.init...');
|
||||||
return api.initialize();
|
return api.initialize();
|
||||||
|
}).catch(function (err) {
|
||||||
|
console.error(err);
|
||||||
|
window.alert("Error: [authorize] " + (err.message || JSON.stringify(err, null, 2)));
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
changeState('advanced');
|
changeState('advanced');
|
||||||
}
|
}
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
|
console.error(err);
|
||||||
window.alert("Error: [directory] " + (err.message || JSON.stringify(err, null, 2)));
|
window.alert("Error: [directory] " + (err.message || JSON.stringify(err, null, 2)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
, advance: function () {
|
, advance: function () {
|
||||||
|
console.log('2 api.init...');
|
||||||
return api.initialize();
|
return api.initialize();
|
||||||
}
|
}
|
||||||
, productionAcme: function () {
|
, productionAcme: function () {
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
;(function (exports) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* global Promise */
|
||||||
|
var PromiseA;
|
||||||
|
if ('undefined' !== typeof Promise) {
|
||||||
|
PromiseA = Promise;
|
||||||
|
} else {
|
||||||
|
throw new Error("no Promise implementation defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
var common = exports.TELEBIT || require('./lib/common.js');
|
||||||
|
|
||||||
|
common.authorize = common.getToken = function getToken(state) {
|
||||||
|
state.relay = state.config.relay;
|
||||||
|
|
||||||
|
// { _otp, config: {} }
|
||||||
|
return common.api.token(state, {
|
||||||
|
error: function (err) {
|
||||||
|
console.error("[Error] common.api.token handlers.error:");
|
||||||
|
console.error(err);
|
||||||
|
return PromiseA.reject(err);
|
||||||
|
}
|
||||||
|
, directory: function (dir) {
|
||||||
|
//console.log('[directory] Telebit Relay Discovered:');
|
||||||
|
//console.log(dir);
|
||||||
|
state._apiDirectory = dir;
|
||||||
|
return PromiseA.resolve();
|
||||||
|
}
|
||||||
|
, tunnelUrl: function (tunnelUrl) {
|
||||||
|
//console.log('[tunnelUrl] Telebit Relay Tunnel Socket:', tunnelUrl);
|
||||||
|
state.wss = tunnelUrl;
|
||||||
|
return PromiseA.resolve();
|
||||||
|
}
|
||||||
|
, requested: function (authReq) {
|
||||||
|
console.log("[requested] Pairing Requested");
|
||||||
|
state.config._otp = state.config._otp = authReq.otp;
|
||||||
|
|
||||||
|
if (!state.config.token && state._can_pair) {
|
||||||
|
console.info("0000".replace(/0000/g, state.config._otp));
|
||||||
|
}
|
||||||
|
|
||||||
|
return PromiseA.resolve();
|
||||||
|
}
|
||||||
|
, connect: function (pretoken) {
|
||||||
|
console.log("[connect] Enabling Pairing Locally...");
|
||||||
|
state.config.pretoken = pretoken;
|
||||||
|
state._connecting = true;
|
||||||
|
|
||||||
|
return common.reqLocalAsync({ url: '/api/config', method: 'POST', data: state.config || {} }).then(function () {
|
||||||
|
console.info("waiting...");
|
||||||
|
return PromiseA.resolve();
|
||||||
|
}).catch(function (err) {
|
||||||
|
state._error = err;
|
||||||
|
console.error("Error while initializing config [connect]:");
|
||||||
|
console.error(err);
|
||||||
|
return PromiseA.reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
, offer: function (token) {
|
||||||
|
//console.log("[offer] Pairing Enabled by Relay");
|
||||||
|
state.config.token = token;
|
||||||
|
if (state._error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state._connecting = true;
|
||||||
|
try {
|
||||||
|
//require('jsonwebtoken').decode(token);
|
||||||
|
token = token.split('.');
|
||||||
|
token[0] = token[0].replace(/_/g, '/').replace(/-/g, '+');
|
||||||
|
while (token[0].length % 4) { token[0] += '='; }
|
||||||
|
btoa(token[0]);
|
||||||
|
token[1] = token[1].replace(/_/g, '/').replace(/-/g, '+');
|
||||||
|
while (token[1].length % 4) { token[1] += '='; }
|
||||||
|
btoa(token[1]);
|
||||||
|
//console.log(require('jsonwebtoken').decode(token));
|
||||||
|
} catch(e) {
|
||||||
|
console.warn("[warning] could not decode token");
|
||||||
|
}
|
||||||
|
return common.reqLocalAsync({ url: '/api/config', method: 'POST', data: state.config }).then(function () {
|
||||||
|
//console.log("Pairing Enabled Locally");
|
||||||
|
return PromiseA.resolve();
|
||||||
|
}).catch(function (err) {
|
||||||
|
state._error = err;
|
||||||
|
console.error("Error while initializing config [offer]:");
|
||||||
|
console.error(err);
|
||||||
|
return PromiseA.reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
, granted: function (/*_*/) {
|
||||||
|
//console.log("[grant] Pairing complete!");
|
||||||
|
return PromiseA.resolve();
|
||||||
|
}
|
||||||
|
, end: function () {
|
||||||
|
return common.reqLocalAsync({ url: '/api/enable', method: 'POST', data: [] }).then(function () {
|
||||||
|
console.info("Success");
|
||||||
|
|
||||||
|
// workaround for https://github.com/nodejs/node/issues/21319
|
||||||
|
if (state._useTty) {
|
||||||
|
setTimeout(function () {
|
||||||
|
console.info("Some fun things to try first:\n");
|
||||||
|
console.info(" ~/telebit http ~/public");
|
||||||
|
console.info(" ~/telebit tcp 5050");
|
||||||
|
console.info(" ~/telebit ssh auto");
|
||||||
|
console.info();
|
||||||
|
console.info("Press any key to continue...");
|
||||||
|
console.info();
|
||||||
|
process.exit(0);
|
||||||
|
}, 0.5 * 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// end workaround
|
||||||
|
|
||||||
|
//parseCli(state);
|
||||||
|
}).catch(function (err) {
|
||||||
|
console.error('[end] [error]', err);
|
||||||
|
return PromiseA.reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
}('undefined' === typeof module ? window : module.exports));
|
|
@ -2,6 +2,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var common = exports.TELEBIT = {};
|
var common = exports.TELEBIT = {};
|
||||||
|
common.debug = true;
|
||||||
|
|
||||||
/* global Promise */
|
/* global Promise */
|
||||||
var PromiseA;
|
var PromiseA;
|
||||||
|
@ -14,19 +15,6 @@ if ('undefined' !== typeof Promise) {
|
||||||
/*globals AbortController*/
|
/*globals AbortController*/
|
||||||
if ('undefined' !== typeof fetch) {
|
if ('undefined' !== typeof fetch) {
|
||||||
common.requestAsync = function (opts) {
|
common.requestAsync = function (opts) {
|
||||||
/*
|
|
||||||
if (opts.json && true !== opts.json) {
|
|
||||||
opts.body = opts.json;
|
|
||||||
}
|
|
||||||
if (opts.json) {
|
|
||||||
if (!opts.headers) { opts.headers = {}; }
|
|
||||||
if (opts.body) {
|
|
||||||
opts.headers['Content-Type'] = 'application/json';
|
|
||||||
} else {
|
|
||||||
opts.headers.Accepts = 'application/json';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
// funnel requests through the local server
|
// funnel requests through the local server
|
||||||
// (avoid CORS, for now)
|
// (avoid CORS, for now)
|
||||||
var relayOpts = {
|
var relayOpts = {
|
||||||
|
@ -49,13 +37,6 @@ if ('undefined' !== typeof fetch) {
|
||||||
return window.fetch(relayOpts.url, relayOpts).then(function (resp) {
|
return window.fetch(relayOpts.url, relayOpts).then(function (resp) {
|
||||||
clearTimeout(tok);
|
clearTimeout(tok);
|
||||||
return resp.json().then(function (json) {
|
return resp.json().then(function (json) {
|
||||||
/*
|
|
||||||
var headers = {};
|
|
||||||
resp.headers.forEach(function (k, v) {
|
|
||||||
headers[k] = v;
|
|
||||||
});
|
|
||||||
return { statusCode: resp.status, headers: headers, body: json };
|
|
||||||
*/
|
|
||||||
if (json.error) {
|
if (json.error) {
|
||||||
return PromiseA.reject(new Error(json.error && json.error.message || JSON.stringify(json.error)));
|
return PromiseA.reject(new Error(json.error && json.error.message || JSON.stringify(json.error)));
|
||||||
}
|
}
|
||||||
|
@ -63,8 +44,38 @@ if ('undefined' !== typeof fetch) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
common.reqLocalAsync = function (opts) {
|
||||||
|
if (!opts) { opts = {}; }
|
||||||
|
if (opts.json && true !== opts.json) {
|
||||||
|
opts.body = opts.json;
|
||||||
|
}
|
||||||
|
if (opts.json) {
|
||||||
|
if (!opts.headers) { opts.headers = {}; }
|
||||||
|
if (opts.body) {
|
||||||
|
opts.headers['Content-Type'] = 'application/json';
|
||||||
|
} else {
|
||||||
|
opts.headers.Accepts = 'application/json';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var controller = new AbortController();
|
||||||
|
var tok = setTimeout(function () {
|
||||||
|
controller.abort();
|
||||||
|
}, 4000);
|
||||||
|
opts.signal = controller.signal;
|
||||||
|
return window.fetch(opts.url, opts).then(function (resp) {
|
||||||
|
clearTimeout(tok);
|
||||||
|
return resp.json().then(function (json) {
|
||||||
|
var headers = {};
|
||||||
|
resp.headers.forEach(function (k, v) {
|
||||||
|
headers[k] = v;
|
||||||
|
});
|
||||||
|
return { statusCode: resp.status, headers: headers, body: json };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
common.requestAsync = require('util').promisify(require('@coolaj86/urequest'));
|
common.requestAsync = require('util').promisify(require('@coolaj86/urequest'));
|
||||||
|
common.reqLocalAsync = require('util').promisify(require('@coolaj86/urequest'));
|
||||||
}
|
}
|
||||||
|
|
||||||
common.parseUrl = function (hostname) {
|
common.parseUrl = function (hostname) {
|
||||||
|
@ -78,7 +89,12 @@ common.parseUrl = function (hostname) {
|
||||||
return hostname;
|
return hostname;
|
||||||
};
|
};
|
||||||
common.parseHostname = function (hostname) {
|
common.parseHostname = function (hostname) {
|
||||||
var location = new URL(hostname);
|
var location = {};
|
||||||
|
try {
|
||||||
|
location = new URL(hostname);
|
||||||
|
} catch(e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
if (!location.protocol || /\./.test(location.protocol)) {
|
if (!location.protocol || /\./.test(location.protocol)) {
|
||||||
hostname = 'https://' + hostname;
|
hostname = 'https://' + hostname;
|
||||||
location = new URL(hostname);
|
location = new URL(hostname);
|
||||||
|
@ -109,6 +125,17 @@ common.signToken = function (state) {
|
||||||
|
|
||||||
return jwt.sign(tokenData, state.config.secret);
|
return jwt.sign(tokenData, state.config.secret);
|
||||||
};
|
};
|
||||||
|
common.promiseTimeout = function (ms) {
|
||||||
|
var x = new PromiseA(function (resolve) {
|
||||||
|
x._tok = setTimeout(function () {
|
||||||
|
resolve();
|
||||||
|
}, ms);
|
||||||
|
});
|
||||||
|
x.cancel = function () {
|
||||||
|
clearTimeout(x._tok);
|
||||||
|
};
|
||||||
|
return x;
|
||||||
|
};
|
||||||
common.api = {};
|
common.api = {};
|
||||||
common.api.directory = function (state) {
|
common.api.directory = function (state) {
|
||||||
console.log('[DEBUG] state:');
|
console.log('[DEBUG] state:');
|
||||||
|
@ -118,11 +145,14 @@ common.api.directory = function (state) {
|
||||||
if (state._relays[state._relayUrl]) {
|
if (state._relays[state._relayUrl]) {
|
||||||
return PromiseA.resolve(state._relays[state._relayUrl]);
|
return PromiseA.resolve(state._relays[state._relayUrl]);
|
||||||
}
|
}
|
||||||
|
console.error('aaaaaaaaabsnthsnth');
|
||||||
return common.requestAsync({ url: state._relayUrl + common.apiDirectory, json: true }).then(function (resp) {
|
return common.requestAsync({ url: state._relayUrl + common.apiDirectory, json: true }).then(function (resp) {
|
||||||
|
console.error('123aaaaaaaaabsnthsnth');
|
||||||
var dir = resp.body;
|
var dir = resp.body;
|
||||||
state._relays[state._relayUrl] = dir;
|
state._relays[state._relayUrl] = dir;
|
||||||
return dir;
|
return dir;
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
|
console.error('bsnthsnth');
|
||||||
return PromiseA.reject(err);
|
return PromiseA.reject(err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -133,18 +163,18 @@ common.api._parseWss = function (state, dir) {
|
||||||
state._relayHostname = common.parseHostname(state.relay);
|
state._relayHostname = common.parseHostname(state.relay);
|
||||||
return dir.tunnel.method + '://' + dir.api_host.replace(/:hostname/g, state._relayHostname) + dir.tunnel.pathname;
|
return dir.tunnel.method + '://' + dir.api_host.replace(/:hostname/g, state._relayHostname) + dir.tunnel.pathname;
|
||||||
};
|
};
|
||||||
common.api.wss = function (state, cb) {
|
common.api.wss = function (state) {
|
||||||
common.api.directory(state).then(function (dir) {
|
return common.api.directory(state).then(function (dir) {
|
||||||
cb(null, common.api._parseWss(state, dir));
|
return common.api._parseWss(state, dir);
|
||||||
}).catch(cb);
|
});
|
||||||
};
|
};
|
||||||
common.api.token = function (state, handlers) {
|
common.api.token = function (state, handlers) {
|
||||||
// directory, requested, connect, tunnelUrl, offer, granted, end
|
// directory, requested, connect, tunnelUrl, offer, granted, end
|
||||||
function afterDir(err, dir) {
|
function afterDir(dir) {
|
||||||
if (common.debug) { console.log('[debug] after dir'); }
|
if (common.debug) { console.log('[debug] after dir'); }
|
||||||
state.wss = common.api._parseWss(state, dir);
|
state.wss = common.api._parseWss(state, dir);
|
||||||
|
|
||||||
handlers.tunnelUrl(state.wss, function () {
|
return PromiseA.resolve(handlers.tunnelUrl(state.wss)).then(function () {
|
||||||
if (common.debug) { console.log('[debug] after tunnelUrl'); }
|
if (common.debug) { console.log('[debug] after tunnelUrl'); }
|
||||||
if (state.config.secret /* && !state.config.token */) {
|
if (state.config.secret /* && !state.config.token */) {
|
||||||
state.config._token = common.signToken(state);
|
state.config._token = common.signToken(state);
|
||||||
|
@ -153,21 +183,19 @@ common.api.token = function (state, handlers) {
|
||||||
if (state.token) {
|
if (state.token) {
|
||||||
if (common.debug) { console.log('[debug] token via token or secret'); }
|
if (common.debug) { console.log('[debug] token via token or secret'); }
|
||||||
// { token, pretoken }
|
// { token, pretoken }
|
||||||
handlers.connect(state.token, function () {
|
return PromiseA.resolve(handlers.connect(state.token)).then(function () {
|
||||||
handlers.end(null, function () {});
|
return PromiseA.resolve(handlers.end(null));
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// backwards compat (TODO remove)
|
if (!dir.pair_request) {
|
||||||
if (err || !dir || !dir.pair_request) {
|
|
||||||
if (common.debug) { console.log('[debug] no dir, connect'); }
|
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 PromiseA.resolve(handlers.error(err || 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
|
// TODO sign token with own private key, including public key and thumbprint
|
||||||
// (much like ACME JOSE account)
|
// (much like ACME JOSE account)
|
||||||
|
// TODO handle agree
|
||||||
var otp = state.config._otp; // common.otp();
|
var otp = state.config._otp; // common.otp();
|
||||||
var authReq = {
|
var authReq = {
|
||||||
subject: state.config.email
|
subject: state.config.email
|
||||||
|
@ -187,8 +215,21 @@ common.api.token = function (state, handlers) {
|
||||||
*/
|
*/
|
||||||
};
|
};
|
||||||
var pairRequestUrl = new URL(dir.pair_request.pathname, 'https://' + dir.api_host.replace(/:hostname/g, state._relayHostname));
|
var pairRequestUrl = new URL(dir.pair_request.pathname, 'https://' + dir.api_host.replace(/:hostname/g, state._relayHostname));
|
||||||
|
console.log('pairRequestUrl:', pairRequestUrl);
|
||||||
|
//console.log('pairRequestUrl:', JSON.stringify(pairRequestUrl.toJSON()));
|
||||||
var req = {
|
var req = {
|
||||||
url: pairRequestUrl
|
// WHATWG URL defines .toJSON() but, of course, it's not implemented
|
||||||
|
// because... why would we implement JavaScript objects in the DOM
|
||||||
|
// when we can have perfectly incompatible non-JS objects?
|
||||||
|
url: {
|
||||||
|
host: pairRequestUrl.host
|
||||||
|
, hostname: pairRequestUrl.hostname
|
||||||
|
, href: pairRequestUrl.href
|
||||||
|
, pathname: pairRequestUrl.pathname
|
||||||
|
, port: pairRequestUrl.port
|
||||||
|
, protocol: pairRequestUrl.protocol
|
||||||
|
, search: pairRequestUrl.search
|
||||||
|
}
|
||||||
, method: dir.pair_request.method
|
, method: dir.pair_request.method
|
||||||
, json: authReq
|
, json: authReq
|
||||||
};
|
};
|
||||||
|
@ -198,7 +239,7 @@ common.api.token = function (state, handlers) {
|
||||||
function gotoNext(req) {
|
function gotoNext(req) {
|
||||||
if (common.debug) { console.log('[debug] gotoNext called'); }
|
if (common.debug) { console.log('[debug] gotoNext called'); }
|
||||||
if (common.debug) { console.log(req); }
|
if (common.debug) { console.log(req); }
|
||||||
common.requestAsync(req).then(function (resp) {
|
return common.requestAsync(req).then(function (resp) {
|
||||||
var body = resp.body;
|
var body = resp.body;
|
||||||
|
|
||||||
function checkLocation() {
|
function checkLocation() {
|
||||||
|
@ -207,86 +248,88 @@ common.api.token = function (state, handlers) {
|
||||||
// pending, try again
|
// pending, try again
|
||||||
if ('pending' === body.status && resp.headers.location) {
|
if ('pending' === body.status && resp.headers.location) {
|
||||||
if (common.debug) { console.log('[debug] pending'); }
|
if (common.debug) { console.log('[debug] pending'); }
|
||||||
setTimeout(gotoNext, 2 * 1000, { url: resp.headers.location, json: true });
|
return common.promiseTimeout(2 * 1000).then(function () {
|
||||||
return;
|
return gotoNext({ url: resp.headers.location, json: true });
|
||||||
}
|
});
|
||||||
|
} else if ('ready' === body.status) {
|
||||||
if ('ready' === body.status) {
|
|
||||||
if (common.debug) { console.log('[debug] ready'); }
|
if (common.debug) { console.log('[debug] ready'); }
|
||||||
if (firstReady) {
|
if (firstReady) {
|
||||||
if (common.debug) { console.log('[debug] first ready'); }
|
if (common.debug) { console.log('[debug] first ready'); }
|
||||||
firstReady = false;
|
firstReady = false;
|
||||||
state.token = body.access_token;
|
state.token = body.access_token;
|
||||||
state.config.token = state.token;
|
state.config.token = state.token;
|
||||||
handlers.offer(body.access_token, function () {
|
// falls through on purpose
|
||||||
|
PromiseA.resolve(handlers.offer(body.access_token)).then(function () {
|
||||||
/*ignore*/
|
/*ignore*/
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setTimeout(gotoNext, 2 * 1000, req);
|
return common.promiseTimeout(2 * 1000).then(function () {
|
||||||
return;
|
return gotoNext(req);
|
||||||
}
|
|
||||||
|
|
||||||
if ('complete' === body.status) {
|
|
||||||
if (common.debug) { console.log('[debug] complete'); }
|
|
||||||
handlers.granted(null, function () {
|
|
||||||
handlers.end(null, function () {});
|
|
||||||
});
|
});
|
||||||
return;
|
} else if ('complete' === body.status) {
|
||||||
}
|
if (common.debug) { console.log('[debug] complete'); }
|
||||||
|
return PromiseA.resolve(handlers.granted(null)).then(function () {
|
||||||
|
return PromiseA.resolve(handlers.end(null)).then(function () {});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
if (common.debug) { console.log('[debug] bad status'); }
|
if (common.debug) { console.log('[debug] bad status'); }
|
||||||
var err = new Error("Bad State:" + body.status);
|
var err = new Error("Bad State:" + body.status);
|
||||||
err._request = req;
|
err._request = req;
|
||||||
handlers.error(err, function () {});
|
return PromiseA.resolve(handlers.error(err));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (firstReq) {
|
if (firstReq) {
|
||||||
if (common.debug) { console.log('[debug] first req'); }
|
if (common.debug) { console.log('[debug] first req'); }
|
||||||
handlers.requested(authReq, function () {
|
if (!body.access_token && !body.jwt) {
|
||||||
handlers.connect(body.access_token || body.jwt, function () {
|
return PromiseA.reject(new Error("something wrong with pre-authorization request"));
|
||||||
|
}
|
||||||
|
firstReq = false;
|
||||||
|
return PromiseA.resolve(handlers.requested(authReq)).then(function () {
|
||||||
|
return PromiseA.resolve(handlers.connect(body.access_token || body.jwt)).then(function () {
|
||||||
var err;
|
var err;
|
||||||
if (!resp.headers.location) {
|
if (!resp.headers.location) {
|
||||||
err = new Error("bad authentication request response");
|
err = new Error("bad authentication request response");
|
||||||
err._resp = resp.toJSON && resp.toJSON();
|
err._resp = resp.toJSON && resp.toJSON();
|
||||||
handlers.error(err, function () {});
|
return PromiseA.resolve(handlers.error(err)).then(function () {});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
setTimeout(gotoNext, 2 * 1000, { url: resp.headers.location, json: true });
|
return common.promiseTimeout(2 * 1000).then(function () {
|
||||||
|
return gotoNext({ url: resp.headers.location, json: true });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
firstReq = false;
|
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
if (common.debug) { console.log('[debug] other req'); }
|
if (common.debug) { console.log('[debug] other req'); }
|
||||||
checkLocation();
|
return checkLocation();
|
||||||
}
|
}
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
if (common.debug) { console.log('[debug] gotoNext error'); }
|
if (common.debug) { console.log('[debug] gotoNext error'); }
|
||||||
err._request = req;
|
err._request = req;
|
||||||
err._hint = '[telebitd.js] pair request';
|
err._hint = '[telebitd.js] pair request';
|
||||||
handlers.error(err, function () {});
|
return PromiseA.resolve(handlers.error(err)).then(function () {});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
gotoNext(req);
|
return gotoNext(req);
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// backwards compat (TODO verify we can remove this)
|
// backwards compat (TODO verify we can remove this)
|
||||||
var failoverDir = '{ "api_host": ":hostname", "tunnel": { "method": "wss", "pathname": "" } }';
|
var failoverDir = '{ "api_host": ":hostname", "tunnel": { "method": "wss", "pathname": "" } }';
|
||||||
common.api.directory(state).then(function (dir) {
|
return common.api.directory(state).then(function (dir) {
|
||||||
if (!dir.api_host) {
|
console.log('[debug] [directory]', dir);
|
||||||
dir = JSON.parse(failoverDir);
|
if (!dir.api_host) { dir = JSON.parse(failoverDir); }
|
||||||
return afterDir(null, dir);
|
return dir;
|
||||||
}
|
|
||||||
handlers.directory(dir).then(function (dir) {
|
|
||||||
return afterDir(null, dir);
|
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
return PromiseA.reject(err);
|
console.warn('[warn] [directory] fetch fail, using failover');
|
||||||
|
console.warn(err);
|
||||||
|
return JSON.parse(failoverDir);
|
||||||
|
}).then(function (dir) {
|
||||||
|
return PromiseA.resolve(handlers.directory(dir)).then(function () {
|
||||||
|
console.log('[debug] [directory]', dir);
|
||||||
|
return afterDir(dir);
|
||||||
});
|
});
|
||||||
}).catch(function (err) {
|
|
||||||
return afterDir(err, JSON.parse(failoverDir));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue