MAJOR: Updates for Authenticated Web UI and CLI #30
|
@ -338,9 +338,12 @@ controllers.relay = function (req, res, opts) {
|
|||
return;
|
||||
}
|
||||
|
||||
console.log('POST /api/relay:');
|
||||
console.log(opts.body);
|
||||
console.log();
|
||||
return urequestAsync(opts.body).then(function (resp) {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
var resp = resp.toJSON();
|
||||
resp = resp.toJSON();
|
||||
res.end(JSON.stringify(resp));
|
||||
});
|
||||
};
|
||||
|
|
|
@ -104,6 +104,7 @@
|
|||
|
||||
<script src="/js/vue.js"></script>
|
||||
<script src="/js/telebit.js"></script>
|
||||
<script src="/js/telebit-token.js"></script>
|
||||
<script src="/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -5,7 +5,7 @@ var Vue = window.Vue;
|
|||
var Telebit = window.TELEBIT;
|
||||
var api = {};
|
||||
|
||||
/*globals AbortController*/
|
||||
/*
|
||||
function safeFetch(url, opts) {
|
||||
var controller = new AbortController();
|
||||
var tok = setTimeout(function () {
|
||||
|
@ -19,28 +19,29 @@ function safeFetch(url, opts) {
|
|||
clearTimeout(tok);
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
api.config = function apiConfig() {
|
||||
return safeFetch("/api/config", {
|
||||
method: "GET"
|
||||
return Telebit.reqLocalAsync({
|
||||
url: "/api/config"
|
||||
, method: "GET"
|
||||
}).then(function (resp) {
|
||||
return resp.json().then(function (json) {
|
||||
appData.config = json;
|
||||
return json;
|
||||
});
|
||||
var json = resp.body;
|
||||
appData.config = json;
|
||||
return json;
|
||||
});
|
||||
};
|
||||
api.status = function apiStatus() {
|
||||
return safeFetch("/api/status", { method: "GET" }).then(function (resp) {
|
||||
return resp.json().then(function (json) {
|
||||
appData.status = json;
|
||||
return json;
|
||||
});
|
||||
return Telebit.reqLocalAsync({ url: "/api/status", method: "GET" }).then(function (resp) {
|
||||
var json = resp.body;
|
||||
appData.status = json;
|
||||
return json;
|
||||
});
|
||||
};
|
||||
api.initialize = function apiInitialize() {
|
||||
var opts = {
|
||||
method: "POST"
|
||||
url: "/api/init"
|
||||
, method: "POST"
|
||||
, headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
@ -48,14 +49,13 @@ api.initialize = function apiInitialize() {
|
|||
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)));
|
||||
});
|
||||
return Telebit.reqLocalAsync(opts).then(function (resp) {
|
||||
var json = resp.body;
|
||||
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)));
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -104,15 +104,27 @@ var appMethods = {
|
|||
return;
|
||||
}
|
||||
if (-1 !== TELEBIT_RELAYS.indexOf(appData.init.relay)) {
|
||||
return api.initialize();
|
||||
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();
|
||||
}).catch(function (err) {
|
||||
console.error(err);
|
||||
window.alert("Error: [authorize] " + (err.message || JSON.stringify(err, null, 2)));
|
||||
});
|
||||
} else {
|
||||
changeState('advanced');
|
||||
}
|
||||
}).catch(function (err) {
|
||||
console.error(err);
|
||||
window.alert("Error: [directory] " + (err.message || JSON.stringify(err, null, 2)));
|
||||
});
|
||||
}
|
||||
, advance: function () {
|
||||
console.log('2 api.init...');
|
||||
return api.initialize();
|
||||
}
|
||||
, 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';
|
||||
|
||||
var common = exports.TELEBIT = {};
|
||||
common.debug = true;
|
||||
|
||||
/* global Promise */
|
||||
var PromiseA;
|
||||
|
@ -14,19 +15,6 @@ if ('undefined' !== typeof Promise) {
|
|||
/*globals AbortController*/
|
||||
if ('undefined' !== typeof fetch) {
|
||||
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
|
||||
// (avoid CORS, for now)
|
||||
var relayOpts = {
|
||||
|
@ -49,13 +37,6 @@ if ('undefined' !== typeof fetch) {
|
|||
return window.fetch(relayOpts.url, relayOpts).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 };
|
||||
*/
|
||||
if (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 {
|
||||
common.requestAsync = require('util').promisify(require('@coolaj86/urequest'));
|
||||
common.reqLocalAsync = require('util').promisify(require('@coolaj86/urequest'));
|
||||
}
|
||||
|
||||
common.parseUrl = function (hostname) {
|
||||
|
@ -78,7 +89,12 @@ common.parseUrl = function (hostname) {
|
|||
return 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)) {
|
||||
hostname = 'https://' + hostname;
|
||||
location = new URL(hostname);
|
||||
|
@ -109,6 +125,17 @@ common.signToken = function (state) {
|
|||
|
||||
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.directory = function (state) {
|
||||
console.log('[DEBUG] state:');
|
||||
|
@ -118,11 +145,14 @@ common.api.directory = function (state) {
|
|||
if (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) {
|
||||
console.error('123aaaaaaaaabsnthsnth');
|
||||
var dir = resp.body;
|
||||
state._relays[state._relayUrl] = dir;
|
||||
return dir;
|
||||
}).catch(function (err) {
|
||||
console.error('bsnthsnth');
|
||||
return PromiseA.reject(err);
|
||||
});
|
||||
};
|
||||
|
@ -133,18 +163,18 @@ common.api._parseWss = function (state, dir) {
|
|||
state._relayHostname = common.parseHostname(state.relay);
|
||||
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).then(function (dir) {
|
||||
cb(null, common.api._parseWss(state, dir));
|
||||
}).catch(cb);
|
||||
common.api.wss = function (state) {
|
||||
return common.api.directory(state).then(function (dir) {
|
||||
return common.api._parseWss(state, dir);
|
||||
});
|
||||
};
|
||||
common.api.token = function (state, handlers) {
|
||||
// directory, requested, connect, tunnelUrl, offer, granted, end
|
||||
function afterDir(err, dir) {
|
||||
function afterDir(dir) {
|
||||
if (common.debug) { console.log('[debug] after 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 (state.config.secret /* && !state.config.token */) {
|
||||
state.config._token = common.signToken(state);
|
||||
|
@ -153,21 +183,19 @@ common.api.token = function (state, handlers) {
|
|||
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 PromiseA.resolve(handlers.connect(state.token)).then(function () {
|
||||
return PromiseA.resolve(handlers.end(null));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// backwards compat (TODO remove)
|
||||
if (err || !dir || !dir.pair_request) {
|
||||
if (!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;
|
||||
return PromiseA.resolve(handlers.error(err || new Error("No token found or generated, and no pair_request api found.")));
|
||||
}
|
||||
|
||||
// TODO sign token with own private key, including public key and thumbprint
|
||||
// (much like ACME JOSE account)
|
||||
// TODO handle agree
|
||||
var otp = state.config._otp; // common.otp();
|
||||
var authReq = {
|
||||
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));
|
||||
console.log('pairRequestUrl:', pairRequestUrl);
|
||||
//console.log('pairRequestUrl:', JSON.stringify(pairRequestUrl.toJSON()));
|
||||
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
|
||||
, json: authReq
|
||||
};
|
||||
|
@ -198,7 +239,7 @@ common.api.token = function (state, handlers) {
|
|||
function gotoNext(req) {
|
||||
if (common.debug) { console.log('[debug] gotoNext called'); }
|
||||
if (common.debug) { console.log(req); }
|
||||
common.requestAsync(req).then(function (resp) {
|
||||
return common.requestAsync(req).then(function (resp) {
|
||||
var body = resp.body;
|
||||
|
||||
function checkLocation() {
|
||||
|
@ -207,86 +248,88 @@ common.api.token = function (state, handlers) {
|
|||
// 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) {
|
||||
return common.promiseTimeout(2 * 1000).then(function () {
|
||||
return gotoNext({ url: resp.headers.location, json: true });
|
||||
});
|
||||
} else 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 () {
|
||||
// falls through on purpose
|
||||
PromiseA.resolve(handlers.offer(body.access_token)).then(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 common.promiseTimeout(2 * 1000).then(function () {
|
||||
return gotoNext(req);
|
||||
});
|
||||
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'); }
|
||||
var err = new Error("Bad State:" + body.status);
|
||||
err._request = req;
|
||||
return PromiseA.resolve(handlers.error(err));
|
||||
}
|
||||
|
||||
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 () {
|
||||
if (!body.access_token && !body.jwt) {
|
||||
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;
|
||||
if (!resp.headers.location) {
|
||||
err = new Error("bad authentication request response");
|
||||
err._resp = resp.toJSON && resp.toJSON();
|
||||
handlers.error(err, function () {});
|
||||
return;
|
||||
return PromiseA.resolve(handlers.error(err)).then(function () {});
|
||||
}
|
||||
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 {
|
||||
if (common.debug) { console.log('[debug] other req'); }
|
||||
checkLocation();
|
||||
return 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 () {});
|
||||
return PromiseA.resolve(handlers.error(err)).then(function () {});
|
||||
});
|
||||
}
|
||||
|
||||
gotoNext(req);
|
||||
return 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);
|
||||
});
|
||||
return common.api.directory(state).then(function (dir) {
|
||||
console.log('[debug] [directory]', dir);
|
||||
if (!dir.api_host) { dir = JSON.parse(failoverDir); }
|
||||
return dir;
|
||||
}).catch(function (err) {
|
||||
return afterDir(err, JSON.parse(failoverDir));
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue