WIP: use API to find tunnel
This commit is contained in:
parent
13326a89a6
commit
8fbd49f0e6
|
@ -135,56 +135,40 @@ function askForConfig(answers, mainCb) {
|
|||
console.info("");
|
||||
console.info("What relay will you be using? (press enter for default)");
|
||||
console.info("");
|
||||
function parseUrl(hostname) {
|
||||
var url = require('url');
|
||||
var location = url.parse(hostname);
|
||||
if (!location.protocol || /\./.test(location.protocol)) {
|
||||
hostname = 'https://' + hostname;
|
||||
location = url.parse(hostname);
|
||||
}
|
||||
hostname = location.hostname + (location.port ? ':' + location.port : '');
|
||||
hostname = location.protocol.replace(/https?/, 'https') + '//' + hostname + location.pathname;
|
||||
return hostname;
|
||||
}
|
||||
|
||||
rl.question('relay [default: telebit.cloud]: ', function (relay) {
|
||||
// TODO parse and check https://{{relay}}/.well-known/telebit.cloud/directives.json
|
||||
if (!relay) { relay = 'telebit.cloud'; }
|
||||
answers.relay = relay.trim();
|
||||
var urlstr = parseUrl(answers.relay) + '_apis/telebit.cloud/index.json';
|
||||
https.get(urlstr, function (resp) {
|
||||
var body = '';
|
||||
var urlstr = common.parseUrl(answers.relay) + common.apiDirectory;
|
||||
common.urequest({ url: urlstr, json: true }, function (err, resp, body) {
|
||||
if (err) {
|
||||
console.error("[Network Error] Failed to retrieve '" + urlstr + "'");
|
||||
console.error(e);
|
||||
askRelay(cb);
|
||||
return;
|
||||
}
|
||||
if (200 !== resp.statusCode) {
|
||||
console.error("[" + resp.statusCode + " Error] Failed to retrieve '" + urlstr + "'");
|
||||
askRelay(cb);
|
||||
return;
|
||||
}
|
||||
resp.on('data', function (chunk) {
|
||||
body += chunk.toString('utf8');
|
||||
});
|
||||
resp.on('end', function () {
|
||||
try {
|
||||
body = JSON.parse(body);
|
||||
} catch(e) {
|
||||
console.error("[Parse Error] Failed to retrieve '" + urlstr + "'");
|
||||
console.error(e);
|
||||
askRelay(cb);
|
||||
return;
|
||||
}
|
||||
if (!(body.api_host)) {
|
||||
console.error("[API Error] API Index '" + urlstr + "' does not describe a known version of telebit.cloud");
|
||||
console.error(e);
|
||||
askRelay(cb);
|
||||
return;
|
||||
}
|
||||
if (body.pair_request) {
|
||||
answers._can_pair = true;
|
||||
}
|
||||
cb();
|
||||
});
|
||||
}).on('error', function (e) {
|
||||
console.error("[Network Error] Failed to retrieve '" + urlstr + "'");
|
||||
console.error(e);
|
||||
askRelay(cb);
|
||||
if (Buffer.isBuffer(body) || 'object' !== typeof body) {
|
||||
console.error("[Parse Error] Failed to retrieve '" + urlstr + "'");
|
||||
console.error(body);
|
||||
askRelay(cb);
|
||||
return;
|
||||
}
|
||||
if (!body.api_host) {
|
||||
console.error("[API Error] API Index '" + urlstr + "' does not describe a known version of telebit.cloud");
|
||||
console.error(e);
|
||||
askRelay(cb);
|
||||
return;
|
||||
}
|
||||
if (body.pair_request) {
|
||||
answers._can_pair = true;
|
||||
}
|
||||
cb();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
115
bin/telebitd.js
115
bin/telebitd.js
|
@ -75,12 +75,8 @@ try {
|
|||
var controlServer;
|
||||
|
||||
var tun;
|
||||
function serveControls() {
|
||||
if (!state.config.disable) {
|
||||
if (state.config.relay && (state.config.token || state.config.agreeTos)) {
|
||||
tun = rawTunnel();
|
||||
}
|
||||
}
|
||||
|
||||
function serveControlsHelper() {
|
||||
controlServer = http.createServer(function (req, res) {
|
||||
var opts = url.parse(req.url, true);
|
||||
if (opts.query._body) {
|
||||
|
@ -101,13 +97,13 @@ function serveControls() {
|
|||
, code: 'CONFIG'
|
||||
};
|
||||
|
||||
if (/\btelebit\.cloud\b/i.test(state.config.relay) && state.config.email && !state.token) {
|
||||
if (state._can_pair && state.config.email && !state.token) {
|
||||
dumpy.code = "AWAIT_AUTH";
|
||||
dumpy.message = [
|
||||
"Check your email."
|
||||
, "You must verify your email address to activate this device."
|
||||
, ""
|
||||
, " Login Code (if needed): " + state.otp
|
||||
, " Device Pairing Code: " + state.otp
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
|
@ -204,26 +200,32 @@ function serveControls() {
|
|||
|
||||
if (tun) {
|
||||
tun.end(function () {
|
||||
tun = rawTunnel();
|
||||
rawTunnel(saveAndReport);
|
||||
});
|
||||
tun = null;
|
||||
setTimeout(function () {
|
||||
if (!tun) { tun = rawTunnel(); }
|
||||
if (!tun) {
|
||||
rawTunnel(saveAndReport);
|
||||
}
|
||||
}, 3000);
|
||||
} else {
|
||||
tun = rawTunnel();
|
||||
rawTunnel(saveAndReport);
|
||||
}
|
||||
|
||||
fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) {
|
||||
if (err) {
|
||||
res.statusCode = 500;
|
||||
res.end('{"error":{"message":"Could not save config file after init: ' + err.message.replace(/"/g, "'")
|
||||
+ '.\nPerhaps check that the file exists and your user has permissions to write it?"}}');
|
||||
return;
|
||||
}
|
||||
function saveAndReport(err, _tun) {
|
||||
if (err) { throw err; }
|
||||
tun = _tun;
|
||||
fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) {
|
||||
if (err) {
|
||||
res.statusCode = 500;
|
||||
res.end('{"error":{"message":"Could not save config file after init: ' + err.message.replace(/"/g, "'")
|
||||
+ '.\nPerhaps check that the file exists and your user has permissions to write it?"}}');
|
||||
return;
|
||||
}
|
||||
|
||||
listSuccess();
|
||||
});
|
||||
listSuccess();
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -350,14 +352,17 @@ function serveControls() {
|
|||
listSuccess();
|
||||
return;
|
||||
}
|
||||
tun = rawTunnel();
|
||||
fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) {
|
||||
if (err) {
|
||||
res.statusCode = 500;
|
||||
res.end('{"error":{"message":"Could not save config file. Perhaps you\'re user doesn\'t have permission?"}}');
|
||||
return;
|
||||
}
|
||||
listSuccess();
|
||||
rawTunnel(function (err, _tun) {
|
||||
if (err) { throw err; }
|
||||
tun = _tun;
|
||||
fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) {
|
||||
if (err) {
|
||||
res.statusCode = 500;
|
||||
res.end('{"error":{"message":"Could not save config file. Perhaps you\'re user doesn\'t have permission?"}}');
|
||||
return;
|
||||
}
|
||||
listSuccess();
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -409,6 +414,20 @@ function serveControls() {
|
|||
});
|
||||
}
|
||||
|
||||
function serveControls() {
|
||||
if (!state.config.disable) {
|
||||
if (state.config.relay && (state.config.token || state.config.agreeTos)) {
|
||||
rawTunnel(function (err, _tun) {
|
||||
if (err) { throw err; }
|
||||
tun = _tun;
|
||||
serveControlsHelper();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
serveControlsHelper();
|
||||
}
|
||||
|
||||
function parseConfig(err, text) {
|
||||
|
||||
function run() {
|
||||
|
@ -603,6 +622,7 @@ function connectTunnel() {
|
|||
|
||||
var tun = remote.connect({
|
||||
relay: state.relay
|
||||
, wss: state.wss
|
||||
, config: state.config
|
||||
, otp: state.otp
|
||||
, sortingHat: state.sortingHat
|
||||
|
@ -618,31 +638,20 @@ function connectTunnel() {
|
|||
return tun;
|
||||
}
|
||||
|
||||
function rawTunnel() {
|
||||
function rawTunnel(cb) {
|
||||
if (state.config.disable || !state.config.relay || !(state.config.token || state.config.agreeTos)) {
|
||||
cb(null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
state.relay = state.config.relay;
|
||||
if (!state.relay) {
|
||||
throw new Error("'" + state._confpath + "' is missing 'relay'");
|
||||
}
|
||||
|
||||
/*
|
||||
if (!(state.config.secret || state.config.token)) {
|
||||
console.error("You must use --secret or --token with --relay");
|
||||
process.exit(1);
|
||||
cb(new Error("'" + state._confpath + "' is missing 'relay'"));
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
var location = url.parse(state.relay);
|
||||
if (!location.protocol || /\./.test(location.protocol)) {
|
||||
state.relay = 'wss://' + state.relay;
|
||||
location = url.parse(state.relay);
|
||||
}
|
||||
var aud = location.hostname + (location.port ? ':' + location.port : '');
|
||||
state.relay = location.protocol + '//' + aud;
|
||||
state.relayUrl = common.parseUrl(state.relay);
|
||||
state.relayHostname = common.parseHostname(state.relay);
|
||||
|
||||
if (!state.config.token && state.config.secret) {
|
||||
var jwt = require('jsonwebtoken');
|
||||
|
@ -662,10 +671,22 @@ function rawTunnel() {
|
|||
}
|
||||
state.token = state.token || state.config.token;
|
||||
|
||||
// TODO sign token with own private key, including public key and thumbprint
|
||||
// (much like ACME JOSE account)
|
||||
common.urequest({ url: state.relayUrl + common.apiDirectory, json: true }, function (err, resp, body) {
|
||||
state._apiDirectory = body;
|
||||
state.wss = body.tunnel.method + '://' + body.api_host.replace(/:hostname/g, state.relayHostname) + body.tunnel.pathname
|
||||
|
||||
return connectTunnel();
|
||||
if (token) {
|
||||
cb(null, connectTunnel());
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO sign token with own private key, including public key and thumbprint
|
||||
// (much like ACME JOSE account)
|
||||
|
||||
// TODO do auth stuff
|
||||
|
||||
cb(null, connectTunnel());
|
||||
});
|
||||
}
|
||||
|
||||
require('fs').readFile(confpath, 'utf8', parseConfig);
|
||||
|
|
|
@ -26,6 +26,69 @@ common.pipename = function (config, newApi) {
|
|||
};
|
||||
common.DEFAULT_SOCK_NAME = path.join(homedir, localshare, 'var', 'run', 'telebit.sock');
|
||||
|
||||
common.parseUrl = function (hostname) {
|
||||
var url = require('url');
|
||||
var location = url.parse(hostname);
|
||||
if (!location.protocol || /\./.test(location.protocol)) {
|
||||
hostname = 'https://' + hostname;
|
||||
location = url.parse(hostname);
|
||||
}
|
||||
hostname = location.hostname + (location.port ? ':' + location.port : '');
|
||||
hostname = location.protocol.replace(/https?/, 'https') + '//' + hostname + location.pathname;
|
||||
return hostname;
|
||||
};
|
||||
|
||||
common.apiDirectory = '_apis/telebit.cloud/index.json';
|
||||
common.urequest = function (opts, cb) {
|
||||
// request.js behavior:
|
||||
// encoding: null + json ? unknown
|
||||
// json => attempt to parse, fail silently
|
||||
// encoding => buffer.toString(encoding)
|
||||
// null === encoding => Buffer.concat(buffers)
|
||||
https.get(opts, function (resp) {
|
||||
var encoding = opts.encoding;
|
||||
if (null === encoding) {
|
||||
resp._body = [];
|
||||
} else {
|
||||
resp._body = '';
|
||||
}
|
||||
if (!resp.headers['content-length'] || 0 === parseInt(resp.headers['content-length'], 10)) {
|
||||
cb(resp);
|
||||
}
|
||||
resp._bodyLength = 0;
|
||||
resp.on('data', function (chunk) {
|
||||
if ('string' === typeof resp.body) {
|
||||
resp.body += chunk.toString(encoding);
|
||||
} else {
|
||||
resp._body.push(chunk);
|
||||
resp._bodyLength += chunk.length;
|
||||
}
|
||||
});
|
||||
resp.on('end', function () {
|
||||
if ('string' !== typeof resp.body) {
|
||||
if (1 === resp._body.length) {
|
||||
resp.body = resp._body[0];
|
||||
} else {
|
||||
resp.body = Buffer.concat(resp._body, resp._bodyLength);
|
||||
}
|
||||
resp._body = null;
|
||||
}
|
||||
if (opts.json && 'string' === typeof resp.body) {
|
||||
// TODO I would parse based on Content-Type
|
||||
// but request.js doesn't do that.
|
||||
try {
|
||||
resp.body = JSON.parse(resp.body);
|
||||
} catch(e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
cb(null, resp, resp.body);
|
||||
});
|
||||
}).on('error', function (e) {
|
||||
cb(e);
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
mkdirp.sync(path.join(__dirname, '..', 'var', 'log'));
|
||||
mkdirp.sync(path.join(__dirname, '..', 'var', 'run'));
|
||||
|
|
|
@ -399,7 +399,7 @@ function _connect(state) {
|
|||
}
|
||||
|
||||
, onOpen: function () {
|
||||
console.info("[open] connected to '" + state.relay + "'");
|
||||
console.info("[open] connected to '" + (state.wss || state.relay) + "'");
|
||||
wsHandlers.refreshTimeout();
|
||||
timeoutId = setTimeout(wsHandlers.checkTimeout, activityTimeout);
|
||||
|
||||
|
@ -498,8 +498,8 @@ function _connect(state) {
|
|||
timeoutId = null;
|
||||
var machine = Packer.create(packerHandlers);
|
||||
|
||||
console.info("[connect] '" + state.relay + "'");
|
||||
var tunnelUrl = state.relay.replace(/\/$/, '') + '/'; // + auth;
|
||||
console.info("[connect] '" + (state.wss || state.relay) + "'");
|
||||
var tunnelUrl = (state.wss || state.relay).replace(/\/$/, '') + '/'; // + auth;
|
||||
wstunneler = new WebSocket(tunnelUrl, { rejectUnauthorized: !state.insecure });
|
||||
wstunneler.on('open', wsHandlers.onOpen);
|
||||
wstunneler.on('close', wsHandlers.onClose);
|
||||
|
|
Loading…
Reference in New Issue