mirror of
				https://git.coolaj86.com/coolaj86/telebit.js.git
				synced 2025-10-30 22:32:46 +00:00 
			
		
		
		
	WIP: use API to find tunnel
This commit is contained in:
		
							parent
							
								
									13326a89a6
								
							
						
					
					
						commit
						8fbd49f0e6
					
				| @ -135,42 +135,31 @@ function askForConfig(answers, mainCb) { | |||||||
|       console.info(""); |       console.info(""); | ||||||
|       console.info("What relay will you be using? (press enter for default)"); |       console.info("What relay will you be using? (press enter for default)"); | ||||||
|       console.info(""); |       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) { |       rl.question('relay [default: telebit.cloud]: ', function (relay) { | ||||||
|         // TODO parse and check https://{{relay}}/.well-known/telebit.cloud/directives.json
 |         // TODO parse and check https://{{relay}}/.well-known/telebit.cloud/directives.json
 | ||||||
|         if (!relay) { relay = 'telebit.cloud'; } |         if (!relay) { relay = 'telebit.cloud'; } | ||||||
|         answers.relay = relay.trim(); |         answers.relay = relay.trim(); | ||||||
|         var urlstr = parseUrl(answers.relay) + '_apis/telebit.cloud/index.json'; |         var urlstr = common.parseUrl(answers.relay) + common.apiDirectory; | ||||||
|         https.get(urlstr, function (resp) { |         common.urequest({ url: urlstr, json: true }, function (err, resp, body) { | ||||||
|           var body = ''; |           if (err) { | ||||||
|  |             console.error("[Network Error] Failed to retrieve '" + urlstr + "'"); | ||||||
|  |             console.error(e); | ||||||
|  |             askRelay(cb); | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|           if (200 !== resp.statusCode) { |           if (200 !== resp.statusCode) { | ||||||
|             console.error("[" + resp.statusCode + " Error] Failed to retrieve '" + urlstr + "'"); |             console.error("[" + resp.statusCode + " Error] Failed to retrieve '" + urlstr + "'"); | ||||||
|             askRelay(cb); |             askRelay(cb); | ||||||
|             return; |             return; | ||||||
|           } |           } | ||||||
|           resp.on('data', function (chunk) { |           if (Buffer.isBuffer(body) || 'object' !== typeof body) { | ||||||
|             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("[Parse Error] Failed to retrieve '" + urlstr + "'"); | ||||||
|               console.error(e); |             console.error(body); | ||||||
|             askRelay(cb); |             askRelay(cb); | ||||||
|             return; |             return; | ||||||
|           } |           } | ||||||
|             if (!(body.api_host)) { |           if (!body.api_host) { | ||||||
|             console.error("[API Error] API Index '" + urlstr + "' does not describe a known version of telebit.cloud"); |             console.error("[API Error] API Index '" + urlstr + "' does not describe a known version of telebit.cloud"); | ||||||
|             console.error(e); |             console.error(e); | ||||||
|             askRelay(cb); |             askRelay(cb); | ||||||
| @ -181,11 +170,6 @@ function askForConfig(answers, mainCb) { | |||||||
|           } |           } | ||||||
|           cb(); |           cb(); | ||||||
|         }); |         }); | ||||||
|         }).on('error', function (e) { |  | ||||||
|           console.error("[Network Error] Failed to retrieve '" + urlstr + "'"); |  | ||||||
|           console.error(e); |  | ||||||
|           askRelay(cb); |  | ||||||
|         }); |  | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   , function askAgree(cb) { |   , function askAgree(cb) { | ||||||
|  | |||||||
| @ -75,12 +75,8 @@ try { | |||||||
| var controlServer; | var controlServer; | ||||||
| 
 | 
 | ||||||
| var tun; | var tun; | ||||||
| function serveControls() { | 
 | ||||||
|   if (!state.config.disable) { | function serveControlsHelper() { | ||||||
|     if (state.config.relay && (state.config.token || state.config.agreeTos)) { |  | ||||||
|       tun = rawTunnel(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   controlServer = http.createServer(function (req, res) { |   controlServer = http.createServer(function (req, res) { | ||||||
|     var opts = url.parse(req.url, true); |     var opts = url.parse(req.url, true); | ||||||
|     if (opts.query._body) { |     if (opts.query._body) { | ||||||
| @ -101,13 +97,13 @@ function serveControls() { | |||||||
|       , code: 'CONFIG' |       , 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.code = "AWAIT_AUTH"; | ||||||
|         dumpy.message = [ |         dumpy.message = [ | ||||||
|           "Check your email." |           "Check your email." | ||||||
|         , "You must verify your email address to activate this device." |         , "You must verify your email address to activate this device." | ||||||
|         , "" |         , "" | ||||||
|         , "    Login Code (if needed): " + state.otp |         , "    Device Pairing Code: " + state.otp | ||||||
|         ].join('\n'); |         ].join('\n'); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
| @ -204,16 +200,21 @@ function serveControls() { | |||||||
| 
 | 
 | ||||||
|       if (tun) { |       if (tun) { | ||||||
|         tun.end(function () { |         tun.end(function () { | ||||||
|           tun = rawTunnel(); |           rawTunnel(saveAndReport); | ||||||
|         }); |         }); | ||||||
|         tun = null; |         tun = null; | ||||||
|         setTimeout(function () { |         setTimeout(function () { | ||||||
|           if (!tun) { tun = rawTunnel(); } |           if (!tun) { | ||||||
|  |             rawTunnel(saveAndReport); | ||||||
|  |           } | ||||||
|         }, 3000); |         }, 3000); | ||||||
|       } else { |       } else { | ||||||
|         tun = rawTunnel(); |         rawTunnel(saveAndReport); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  |       function saveAndReport(err, _tun) { | ||||||
|  |         if (err) { throw err; } | ||||||
|  |         tun = _tun; | ||||||
|         fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { |         fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { | ||||||
|           if (err) { |           if (err) { | ||||||
|             res.statusCode = 500; |             res.statusCode = 500; | ||||||
| @ -224,6 +225,7 @@ function serveControls() { | |||||||
| 
 | 
 | ||||||
|           listSuccess(); |           listSuccess(); | ||||||
|         }); |         }); | ||||||
|  |       } | ||||||
| 
 | 
 | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| @ -350,7 +352,9 @@ function serveControls() { | |||||||
|         listSuccess(); |         listSuccess(); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       tun = rawTunnel(); |       rawTunnel(function (err, _tun) { | ||||||
|  |         if (err) { throw err; } | ||||||
|  |         tun = _tun; | ||||||
|         fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { |         fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { | ||||||
|           if (err) { |           if (err) { | ||||||
|             res.statusCode = 500; |             res.statusCode = 500; | ||||||
| @ -359,6 +363,7 @@ function serveControls() { | |||||||
|           } |           } | ||||||
|           listSuccess(); |           listSuccess(); | ||||||
|         }); |         }); | ||||||
|  |       }); | ||||||
|       return; |       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 parseConfig(err, text) { | ||||||
| 
 | 
 | ||||||
|   function run() { |   function run() { | ||||||
| @ -603,6 +622,7 @@ function connectTunnel() { | |||||||
| 
 | 
 | ||||||
|   var tun = remote.connect({ |   var tun = remote.connect({ | ||||||
|     relay: state.relay |     relay: state.relay | ||||||
|  |   , wss: state.wss | ||||||
|   , config: state.config |   , config: state.config | ||||||
|   , otp: state.otp |   , otp: state.otp | ||||||
|   , sortingHat: state.sortingHat |   , sortingHat: state.sortingHat | ||||||
| @ -618,31 +638,20 @@ function connectTunnel() { | |||||||
|   return tun; |   return tun; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function rawTunnel() { | function rawTunnel(cb) { | ||||||
|   if (state.config.disable || !state.config.relay || !(state.config.token || state.config.agreeTos)) { |   if (state.config.disable || !state.config.relay || !(state.config.token || state.config.agreeTos)) { | ||||||
|  |     cb(null, null); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   state.relay = state.config.relay; |   state.relay = state.config.relay; | ||||||
|   if (!state.relay) { |   if (!state.relay) { | ||||||
|     throw new Error("'" + state._confpath + "' is missing 'relay'"); |     cb(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); |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   */ |  | ||||||
| 
 | 
 | ||||||
|   var location = url.parse(state.relay); |   state.relayUrl = common.parseUrl(state.relay); | ||||||
|   if (!location.protocol || /\./.test(location.protocol)) { |   state.relayHostname = common.parseHostname(state.relay); | ||||||
|     state.relay = 'wss://' + state.relay; |  | ||||||
|     location = url.parse(state.relay); |  | ||||||
|   } |  | ||||||
|   var aud = location.hostname + (location.port ? ':' + location.port : ''); |  | ||||||
|   state.relay = location.protocol + '//' + aud; |  | ||||||
| 
 | 
 | ||||||
|   if (!state.config.token && state.config.secret) { |   if (!state.config.token && state.config.secret) { | ||||||
|     var jwt = require('jsonwebtoken'); |     var jwt = require('jsonwebtoken'); | ||||||
| @ -662,10 +671,22 @@ function rawTunnel() { | |||||||
|   } |   } | ||||||
|   state.token = state.token || state.config.token; |   state.token = state.token || state.config.token; | ||||||
| 
 | 
 | ||||||
|  |   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 | ||||||
|  | 
 | ||||||
|  |     if (token) { | ||||||
|  |       cb(null, connectTunnel()); | ||||||
|  |       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)
 | ||||||
| 
 | 
 | ||||||
|   return connectTunnel(); |     // TODO do auth stuff
 | ||||||
|  | 
 | ||||||
|  |     cb(null, connectTunnel()); | ||||||
|  |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| require('fs').readFile(confpath, 'utf8', parseConfig); | 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.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 { | try { | ||||||
|   mkdirp.sync(path.join(__dirname, '..', 'var', 'log')); |   mkdirp.sync(path.join(__dirname, '..', 'var', 'log')); | ||||||
|   mkdirp.sync(path.join(__dirname, '..', 'var', 'run')); |   mkdirp.sync(path.join(__dirname, '..', 'var', 'run')); | ||||||
|  | |||||||
| @ -399,7 +399,7 @@ function _connect(state) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   , onOpen: function () { |   , onOpen: function () { | ||||||
|       console.info("[open] connected to '" + state.relay + "'"); |       console.info("[open] connected to '" + (state.wss || state.relay) + "'"); | ||||||
|       wsHandlers.refreshTimeout(); |       wsHandlers.refreshTimeout(); | ||||||
|       timeoutId = setTimeout(wsHandlers.checkTimeout, activityTimeout); |       timeoutId = setTimeout(wsHandlers.checkTimeout, activityTimeout); | ||||||
| 
 | 
 | ||||||
| @ -498,8 +498,8 @@ function _connect(state) { | |||||||
|     timeoutId = null; |     timeoutId = null; | ||||||
|     var machine = Packer.create(packerHandlers); |     var machine = Packer.create(packerHandlers); | ||||||
| 
 | 
 | ||||||
|     console.info("[connect] '" + state.relay + "'"); |     console.info("[connect] '" + (state.wss || state.relay) + "'"); | ||||||
|     var tunnelUrl = state.relay.replace(/\/$/, '') + '/'; // + auth;
 |     var tunnelUrl = (state.wss || state.relay).replace(/\/$/, '') + '/'; // + auth;
 | ||||||
|     wstunneler = new WebSocket(tunnelUrl, { rejectUnauthorized: !state.insecure }); |     wstunneler = new WebSocket(tunnelUrl, { rejectUnauthorized: !state.insecure }); | ||||||
|     wstunneler.on('open', wsHandlers.onOpen); |     wstunneler.on('open', wsHandlers.onOpen); | ||||||
|     wstunneler.on('close', wsHandlers.onClose); |     wstunneler.on('close', wsHandlers.onClose); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user