MAJOR: Updates for Authenticated Web UI and CLI #30
| @ -8,11 +8,11 @@ var os = require('os'); | |||||||
| //var url = require('url');
 | //var url = require('url');
 | ||||||
| var fs = require('fs'); | var fs = require('fs'); | ||||||
| var path = require('path'); | var path = require('path'); | ||||||
| var http = require('http'); |  | ||||||
| //var https = require('https');
 | //var https = require('https');
 | ||||||
| var YAML = require('js-yaml'); | var YAML = require('js-yaml'); | ||||||
| var TOML = require('toml'); | var TOML = require('toml'); | ||||||
| var TPLS = TOML.parse(fs.readFileSync(path.join(__dirname, "../lib/en-us.toml"), 'utf8')); | var TPLS = TOML.parse(fs.readFileSync(path.join(__dirname, "../lib/en-us.toml"), 'utf8')); | ||||||
|  | 
 | ||||||
| /* | /* | ||||||
| if ('function' !== typeof TOML.stringify) { | if ('function' !== typeof TOML.stringify) { | ||||||
|   TOML.stringify = require('json2toml'); |   TOML.stringify = require('json2toml'); | ||||||
| @ -314,92 +314,186 @@ function askForConfig(state, mainCb) { | |||||||
|   next(); |   next(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var utils = { | var RC; | ||||||
|   request: function request(opts, fn) { |  | ||||||
|     if (!opts) { opts = {}; } |  | ||||||
|     var service = opts.service || 'config'; |  | ||||||
|     var req = http.request({ |  | ||||||
|       socketPath: state._ipc.path |  | ||||||
|     , method: opts.method || 'GET' |  | ||||||
|     , path: '/rpc/' + service |  | ||||||
|     }, function (resp) { |  | ||||||
|       var body = ''; |  | ||||||
| 
 | 
 | ||||||
|       function finish() { | function parseConfig(err, text) { | ||||||
|         if (200 !== resp.statusCode) { |   function handleConfig(err, config) { | ||||||
|           console.warn(resp.statusCode); |     //console.log('CONFIG');
 | ||||||
|           console.warn(body || ('get' + service + ' failed')); |     //console.log(config);
 | ||||||
|           //cb(new Error("not okay"), body);
 |     state.config = config; | ||||||
|           return; |     var verstrd = [ pkg.name + ' daemon v' + state.config.version ]; | ||||||
|         } |     if (state.config.version && state.config.version !== pkg.version) { | ||||||
| 
 |       console.info(verstr.join(' '), verstrd.join(' ')); | ||||||
|         if (!body) { fn(null, null); return; } |  | ||||||
| 
 |  | ||||||
|         try { |  | ||||||
|           body = JSON.parse(body); |  | ||||||
|         } catch(e) { |  | ||||||
|           // ignore
 |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         fn(null, body); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       if (resp.headers['content-length']) { |  | ||||||
|         resp.on('data', function (chunk) { |  | ||||||
|           body += chunk.toString(); |  | ||||||
|         }); |  | ||||||
|         resp.on('end', function () { |  | ||||||
|           finish(); |  | ||||||
|         }); |  | ||||||
|     } else { |     } else { | ||||||
|         finish(); |       console.info(verstr.join(' ')); | ||||||
|     } |     } | ||||||
|     }); | 
 | ||||||
|     req.on('error', function (err) { |  | ||||||
|       // ENOENT - never started, cleanly exited last start, or creating socket at a different path
 |  | ||||||
|       // ECONNREFUSED - leftover socket just needs to be restarted
 |  | ||||||
|     if ('ENOENT' === err.code || 'ECONNREFUSED' === err.code) { |     if ('ENOENT' === err.code || 'ECONNREFUSED' === err.code) { | ||||||
|         if (opts._taketwo) { |  | ||||||
|       console.error("Either the telebit service was not already (and could not be started) or its socket could not be written to."); |       console.error("Either the telebit service was not already (and could not be started) or its socket could not be written to."); | ||||||
|       console.error(err); |       console.error(err); | ||||||
|  |     } else if ('ENOTSOCK' === err.code) { | ||||||
|  |       console.error(err); | ||||||
|       return; |       return; | ||||||
|  |     } else { | ||||||
|  |       console.error(err); | ||||||
|     } |     } | ||||||
|         require('../usr/share/install-launcher.js').install({ env: process.env }, function (err) { |     if (err) { process.exit(101); return; } | ||||||
|           if (err) { fn(err); return; } | 
 | ||||||
|           opts._taketwo = true; |     //
 | ||||||
|           setTimeout(function () { |     // check for init first, before anything else
 | ||||||
|             utils.request(opts, fn); |     // because it has arguments that may help in
 | ||||||
|           }, 2500); |     // the next steps
 | ||||||
|  |     //
 | ||||||
|  |     if (-1 !== argv.indexOf('init')) { | ||||||
|  |       parsers.init(argv, function (err) { | ||||||
|  |         if (err) { | ||||||
|  |           console.error("Error while initializing config [init]:"); | ||||||
|  |           throw err; | ||||||
|  |         } | ||||||
|  |         getToken(function (err) { | ||||||
|  |           if (err) { | ||||||
|  |             console.error("Error while getting token [init]:"); | ||||||
|  |             throw err; | ||||||
|  |           } | ||||||
|  |           parseCli(state); | ||||||
|  |         }); | ||||||
|       }); |       }); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|       if ('ENOTSOCK' === err.code) { | 
 | ||||||
|         console.error(err); |     if (!state.config.relay || !state.config.token) { | ||||||
|         return; |       if (!state.config.relay) { | ||||||
|  |         state.config.relay = 'telebit.cloud'; | ||||||
|       } |       } | ||||||
|       console.error(err); | 
 | ||||||
|       return; |       //console.log("question the user?", Date.now());
 | ||||||
|  |       askForConfig(state, function (err, state) { | ||||||
|  |         // no errors actually get passed, so this is just future-proofing
 | ||||||
|  |         if (err) { throw err; } | ||||||
|  | 
 | ||||||
|  |         if (!state.config.token && state._can_pair) { | ||||||
|  |           state.config._otp = common.otp(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         //console.log("done questioning:", Date.now());
 | ||||||
|  |         if (!state.token && !state.config.token) { | ||||||
|  |           if (err) { | ||||||
|  |             console.error("Error while initializing config [init]:"); | ||||||
|  |             throw err; | ||||||
|  |           } | ||||||
|  |           getToken(function (err) { | ||||||
|  |             if (err) { | ||||||
|  |               console.error("Error while getting token [init]:"); | ||||||
|  |               throw err; | ||||||
|  |             } | ||||||
|  |             parseCli(state); | ||||||
|           }); |           }); | ||||||
|     req.end(); |         } else { | ||||||
|  |           parseCli(state); | ||||||
|         } |         } | ||||||
| , putConfig: function putConfig(service, args, fn) { |       }); | ||||||
|     var req = http.request({ |  | ||||||
|       socketPath: state._ipc.path |  | ||||||
|     , method: 'POST' |  | ||||||
|     , path: '/rpc/' + service + '?_body=' + encodeURIComponent(JSON.stringify(args)) |  | ||||||
|     }, function (resp) { |  | ||||||
| 
 |  | ||||||
|       function finish() { |  | ||||||
|         if ('function' === typeof fn) { |  | ||||||
|           fn(null, resp); |  | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     //console.log("no questioning:");
 | ||||||
|  |     parseCli(state); | ||||||
|  |   } | ||||||
|  |   function parseCli(/*state*/) { | ||||||
|  |     var special = [ | ||||||
|  |       'false', 'none', 'off', 'disable' | ||||||
|  |     , 'true', 'auto', 'on', 'enable' | ||||||
|  |     ]; | ||||||
|  |     if (-1 !== argv.indexOf('init')) { | ||||||
|  |       RC.request({ service: 'list', method: 'POST', data: [] }, handleRemoteRequest('list')); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ([ 'ssh', 'http', 'tcp' ].some(function (key) { | ||||||
|  |       if (key !== argv[0]) { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |       if (argv[1]) { | ||||||
|  |         if (String(argv[1]) === String(parseInt(argv[1], 10))) { | ||||||
|  |           // looks like a port
 | ||||||
|  |           argv[1] = parseInt(argv[1], 10); | ||||||
|  |         } else if (/\/|\\/.test(argv[1])) { | ||||||
|  |           // looks like a path
 | ||||||
|  |           argv[1] = path.resolve(argv[1]); | ||||||
|  |           // TODO make a default assignment here
 | ||||||
|  |         } else if (-1 === special.indexOf(argv[1])) { | ||||||
|  |           console.error("Not sure what you meant by '" + argv[1] + "'."); | ||||||
|  |           console.error("Remember: paths should begin with ." + path.sep + ", like '." + path.sep + argv[1] + "'"); | ||||||
|  |           return true; | ||||||
|  |         } | ||||||
|  |         RC.request({ service: argv[0], method: 'POST', data: argv.slice(1) }, handleRemoteRequest(argv[0])); | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  |       return true; | ||||||
|  |     })) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Two styles:
 | ||||||
|  |     //     http 3000
 | ||||||
|  |     //     http modulename
 | ||||||
|  |     function makeRpc(key) { | ||||||
|  |       if (key !== argv[0]) { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |       RC.request({ service: argv[0], method: 'POST', data: argv.slice(1) }, handleRemoteRequest(argv[0])); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     if ([ 'status', 'enable', 'disable', 'restart', 'list', 'save' ].some(makeRpc)) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     help(); | ||||||
|  |     process.exit(11); | ||||||
|  |   } | ||||||
|  |   try { | ||||||
|  |     state._clientConfig = JSON.parse(text || '{}'); | ||||||
|  |   } catch(e1) { | ||||||
|  |     try { | ||||||
|  |       state._clientConfig = YAML.safeLoad(text || '{}'); | ||||||
|  |     } catch(e2) { | ||||||
|  |       try { | ||||||
|  |         state._clientConfig = TOML.parse(text || ''); | ||||||
|  |       } catch(e3) { | ||||||
|  |         console.error(e1.message); | ||||||
|  |         console.error(e2.message); | ||||||
|  |         process.exit(1); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   state._clientConfig = camelCopy(state._clientConfig || {}) || {}; | ||||||
|  |   RC = require('../lib/remote-control-client.js').create(state); | ||||||
|  | 
 | ||||||
|  |   if (!Object.keys(state._clientConfig).length) { | ||||||
|  |     console.info('(' + state._ipc.comment + ": " + state._ipc.path + ')'); | ||||||
|     console.info(""); |     console.info(""); | ||||||
|         if (200 !== resp.statusCode) { |   } | ||||||
|  | 
 | ||||||
|  |   if ((err && 'ENOENT' === err.code) || !Object.keys(state._clientConfig).length) { | ||||||
|  |     if (!err || 'ENOENT' === err.code) { | ||||||
|  |       //console.warn("Empty config file. Run 'telebit init' to configure.\n");
 | ||||||
|  |     } else { | ||||||
|  |       console.warn("Couldn't load config:\n\n\t" + err.message + "\n"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function handleRemoteRequest(service, fn) { | ||||||
|  |     return function (err, body) { | ||||||
|  |       if ('function' === typeof fn) { | ||||||
|  |         fn(err, body); // XXX was resp
 | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       console.info(""); | ||||||
|  |       if (err) { | ||||||
|         console.warn("'" + service + "' may have failed." |         console.warn("'" + service + "' may have failed." | ||||||
|          + " Consider peaking at the logs either with 'journalctl -xeu telebit' or /opt/telebit/var/log/error.log"); |          + " Consider peaking at the logs either with 'journalctl -xeu telebit' or /opt/telebit/var/log/error.log"); | ||||||
|           console.warn(resp.statusCode, body); |         console.warn(err.statusCode, err.message); | ||||||
|         //cb(new Error("not okay"), body);
 |         //cb(new Error("not okay"), body);
 | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
| @ -413,6 +507,7 @@ var utils = { | |||||||
|         body = JSON.parse(body); |         body = JSON.parse(body); | ||||||
|       } catch(e) { |       } catch(e) { | ||||||
|         // ignore
 |         // ignore
 | ||||||
|  | 
 | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       if ("AWAIT_AUTH" === body.code) { |       if ("AWAIT_AUTH" === body.code) { | ||||||
| @ -439,62 +534,10 @@ var utils = { | |||||||
|         } |         } | ||||||
|         console.info(); |         console.info(); | ||||||
|       } |       } | ||||||
|  |     }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|       var body = ''; |   function getToken(fn) { | ||||||
|       if (resp.headers['content-length']) { |  | ||||||
|         resp.on('data', function (chunk) { |  | ||||||
|           body += chunk.toString(); |  | ||||||
|         }); |  | ||||||
|         resp.on('end', function () { |  | ||||||
|           finish(); |  | ||||||
|         }); |  | ||||||
|       } else { |  | ||||||
|         finish(); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|     req.on('error', function (err) { |  | ||||||
|       console.error('Put Config Error:'); |  | ||||||
|       console.error(err); |  | ||||||
|       return; |  | ||||||
|     }); |  | ||||||
|     req.end(); |  | ||||||
|   } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // Two styles:
 |  | ||||||
| //     http 3000
 |  | ||||||
| //     http modulename
 |  | ||||||
| function makeRpc(key) { |  | ||||||
|   if (key !== argv[0]) { |  | ||||||
|     return false; |  | ||||||
|   } |  | ||||||
|   utils.putConfig(argv[0], argv.slice(1)); |  | ||||||
|   return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function packConfig(config) { |  | ||||||
|   return Object.keys(config).map(function (key) { |  | ||||||
|     var val = config[key]; |  | ||||||
|     if ('undefined' === val) { |  | ||||||
|       throw new Error("'undefined' used as a string value"); |  | ||||||
|     } |  | ||||||
|     if ('undefined' === typeof val) { |  | ||||||
|       //console.warn('[DEBUG]', key, 'is present but undefined');
 |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     if (val && 'object' === typeof val && !Array.isArray(val)) { |  | ||||||
|       val = JSON.stringify(val); |  | ||||||
|     } |  | ||||||
|     return key + ':' + val; // converts arrays to strings with ,
 |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function getToken(err, state) { |  | ||||||
|   if (err) { |  | ||||||
|     console.error("Error while initializing config [init]:"); |  | ||||||
|     throw err; |  | ||||||
|   } |  | ||||||
|     state.relay = state.config.relay; |     state.relay = state.config.relay; | ||||||
| 
 | 
 | ||||||
|     // { _otp, config: {} }
 |     // { _otp, config: {} }
 | ||||||
| @ -520,17 +563,7 @@ function getToken(err, state) { | |||||||
|         state.config._otp = state.config._otp = authReq.otp; |         state.config._otp = state.config._otp = authReq.otp; | ||||||
| 
 | 
 | ||||||
|         if (!state.config.token && state._can_pair) { |         if (!state.config.token && state._can_pair) { | ||||||
|         console.info(""); |           console.info(TPLS.remote.code.replace(/0000/g, state.config._otp)); | ||||||
|         console.info("=============================================="); |  | ||||||
|         console.info("                 Hey, Listen!                 "); |  | ||||||
|         console.info("=============================================="); |  | ||||||
|         console.info("                                              "); |  | ||||||
|         console.info("  GO CHECK YOUR EMAIL!                        "); |  | ||||||
|         console.info("                                              "); |  | ||||||
|         console.info("  DEVICE PAIR CODE:     0000                  ".replace(/0000/g, state.config._otp)); |  | ||||||
|         console.info("                                              "); |  | ||||||
|         console.info("=============================================="); |  | ||||||
|         console.info(""); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         next(); |         next(); | ||||||
| @ -541,7 +574,7 @@ function getToken(err, state) { | |||||||
|         state._connecting = true; |         state._connecting = true; | ||||||
| 
 | 
 | ||||||
|         // TODO use php-style object querification
 |         // TODO use php-style object querification
 | ||||||
|       utils.putConfig('config', packConfig(state.config), function (err/*, body*/) { |         RC.request({ service: 'config', method: 'POST', data: state.config }, handleRemoteRequest('config', function (err/*, body*/) { | ||||||
|           if (err) { |           if (err) { | ||||||
|             state._error = err; |             state._error = err; | ||||||
|             console.error("Error while initializing config [connect]:"); |             console.error("Error while initializing config [connect]:"); | ||||||
| @ -550,7 +583,7 @@ function getToken(err, state) { | |||||||
|           } |           } | ||||||
|           console.info("waiting..."); |           console.info("waiting..."); | ||||||
|           next(); |           next(); | ||||||
|       }); |         })); | ||||||
|       } |       } | ||||||
|     , offer: function (token, next) { |     , offer: function (token, next) { | ||||||
|         //console.log("[offer] Pairing Enabled by Relay");
 |         //console.log("[offer] Pairing Enabled by Relay");
 | ||||||
| @ -565,7 +598,7 @@ function getToken(err, state) { | |||||||
|         } catch(e) { |         } catch(e) { | ||||||
|           console.warn("[warning] could not decode token"); |           console.warn("[warning] could not decode token"); | ||||||
|         } |         } | ||||||
|       utils.putConfig('config', packConfig(state.config), function (err/*, body*/) { |         RC.request({ service: 'config', method: 'POST', data: state.config }, handleRemoteRequest('config', function (err/*, body*/) { | ||||||
|           if (err) { |           if (err) { | ||||||
|             state._error = err; |             state._error = err; | ||||||
|             console.error("Error while initializing config [offer]:"); |             console.error("Error while initializing config [offer]:"); | ||||||
| @ -574,14 +607,14 @@ function getToken(err, state) { | |||||||
|           } |           } | ||||||
|           //console.log("Pairing Enabled Locally");
 |           //console.log("Pairing Enabled Locally");
 | ||||||
|           next(); |           next(); | ||||||
|       }); |         })); | ||||||
|       } |       } | ||||||
|     , granted: function (_, next) { |     , granted: function (_, next) { | ||||||
|         //console.log("[grant] Pairing complete!");
 |         //console.log("[grant] Pairing complete!");
 | ||||||
|         next(); |         next(); | ||||||
|       } |       } | ||||||
|     , end: function () { |     , end: function () { | ||||||
|       utils.putConfig('enable', [], function (err) { |         RC.request({ service: 'enable', method: 'POST', data: [] }, handleRemoteRequest('enable', function (err) { | ||||||
|           if (err) { console.error(err); return; } |           if (err) { console.error(err); return; } | ||||||
|           console.info("Success"); |           console.info("Success"); | ||||||
| 
 | 
 | ||||||
| @ -601,148 +634,14 @@ function getToken(err, state) { | |||||||
|           } |           } | ||||||
|           // end workaround
 |           // end workaround
 | ||||||
| 
 | 
 | ||||||
|         parseCli(state); |           //parseCli(state);
 | ||||||
|       }); |           fn(); | ||||||
|  |         })); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function parseCli(/*state*/) { |  | ||||||
|   var special = [ |  | ||||||
|     'false', 'none', 'off', 'disable' |  | ||||||
|   , 'true', 'auto', 'on', 'enable' |  | ||||||
|   ]; |  | ||||||
|   if (-1 !== argv.indexOf('init')) { |  | ||||||
|     utils.putConfig('list', []/*, function (err) { |  | ||||||
|     }*/); |  | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   if ([ 'ssh', 'http', 'tcp' ].some(function (key) { |   RC.request({ service: 'config', method: 'GET' }, handleRemoteRequest('config', handleConfig)); | ||||||
|     if (key !== argv[0]) { |  | ||||||
|       return false; |  | ||||||
|     } |  | ||||||
|     if (argv[1]) { |  | ||||||
|       if (String(argv[1]) === String(parseInt(argv[1], 10))) { |  | ||||||
|         // looks like a port
 |  | ||||||
|         argv[1] = parseInt(argv[1], 10); |  | ||||||
|       } else if (/\/|\\/.test(argv[1])) { |  | ||||||
|         // looks like a path
 |  | ||||||
|         argv[1] = path.resolve(argv[1]); |  | ||||||
|         // TODO make a default assignment here
 |  | ||||||
|       } else if (-1 === special.indexOf(argv[1])) { |  | ||||||
|         console.error("Not sure what you meant by '" + argv[1] + "'."); |  | ||||||
|         console.error("Remember: paths should begin with ." + path.sep + ", like '." + path.sep + argv[1] + "'"); |  | ||||||
|         return true; |  | ||||||
|       } |  | ||||||
|       utils.putConfig(argv[0], argv.slice(1)); |  | ||||||
|       return true; |  | ||||||
|     } |  | ||||||
|     return true; |  | ||||||
|   })) { |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if ([ 'status', 'enable', 'disable', 'restart', 'list', 'save' ].some(makeRpc)) { |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   help(); |  | ||||||
|   process.exit(11); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function handleConfig(err, config) { |  | ||||||
|   //console.log('CONFIG');
 |  | ||||||
|   //console.log(config);
 |  | ||||||
|   state.config = config; |  | ||||||
|   var verstrd = [ pkg.name + ' daemon v' + state.config.version ]; |  | ||||||
|   if (state.config.version && state.config.version !== pkg.version) { |  | ||||||
|     console.info(verstr.join(' '), verstrd.join(' ')); |  | ||||||
|   } else { |  | ||||||
|     console.info(verstr.join(' ')); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if (err) { console.error(err); process.exit(101); return; } |  | ||||||
| 
 |  | ||||||
|   //
 |  | ||||||
|   // check for init first, before anything else
 |  | ||||||
|   // because it has arguments that may help in
 |  | ||||||
|   // the next steps
 |  | ||||||
|   //
 |  | ||||||
|   if (-1 !== argv.indexOf('init')) { |  | ||||||
|     parsers.init(argv, getToken); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if (!state.config.relay || !state.config.token) { |  | ||||||
|     if (!state.config.relay) { |  | ||||||
|       state.config.relay = 'telebit.cloud'; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     //console.log("question the user?", Date.now());
 |  | ||||||
|     askForConfig(state, function (err, state) { |  | ||||||
|       // no errors actually get passed, so this is just future-proofing
 |  | ||||||
|       if (err) { throw err; } |  | ||||||
| 
 |  | ||||||
|       if (!state.config.token && state._can_pair) { |  | ||||||
|         state.config._otp = common.otp(); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       //console.log("done questioning:", Date.now());
 |  | ||||||
|       if (!state.token && !state.config.token) { |  | ||||||
|         getToken(err, state); |  | ||||||
|       } else { |  | ||||||
|         parseCli(state); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   //console.log("no questioning:");
 |  | ||||||
|   parseCli(state); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function parseConfig(err, text) { |  | ||||||
|   try { |  | ||||||
|     state._clientConfig = JSON.parse(text || '{}'); |  | ||||||
|   } catch(e1) { |  | ||||||
|     try { |  | ||||||
|       state._clientConfig = YAML.safeLoad(text || '{}'); |  | ||||||
|     } catch(e2) { |  | ||||||
|       try { |  | ||||||
|         state._clientConfig = TOML.parse(text || ''); |  | ||||||
|       } catch(e3) { |  | ||||||
|         console.error(e1.message); |  | ||||||
|         console.error(e2.message); |  | ||||||
|         process.exit(1); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   state._clientConfig = camelCopy(state._clientConfig || {}) || {}; |  | ||||||
|   common._init( |  | ||||||
|     // make a default working dir and log dir
 |  | ||||||
|     state._clientConfig.root || path.join(os.homedir(), '.local/share/telebit') |  | ||||||
|   , (state._clientConfig.root && path.join(state._clientConfig.root, 'etc')) |  | ||||||
|       || path.resolve(common.DEFAULT_CONFIG_PATH, '..') |  | ||||||
|   ); |  | ||||||
|   state._ipc = common.pipename(state._clientConfig, true); |  | ||||||
| 
 |  | ||||||
|   if (!Object.keys(state._clientConfig).length) { |  | ||||||
|     console.info('(' + state._ipc.comment + ": " + state._ipc.path + ')'); |  | ||||||
|     console.info(""); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if ((err && 'ENOENT' === err.code) || !Object.keys(state._clientConfig).length) { |  | ||||||
|     if (!err || 'ENOENT' === err.code) { |  | ||||||
|       //console.warn("Empty config file. Run 'telebit init' to configure.\n");
 |  | ||||||
|     } else { |  | ||||||
|       console.warn("Couldn't load config:\n\n\t" + err.message + "\n"); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   utils.request({ service: 'config' }, handleConfig); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var parsers = { | var parsers = { | ||||||
|  | |||||||
| @ -452,5 +452,17 @@ The secret flags are: | |||||||
| [remote] | [remote] | ||||||
| version = "telebit remote v{version}" | version = "telebit remote v{version}" | ||||||
| 
 | 
 | ||||||
|  | code = " | ||||||
|  | ============================================== | ||||||
|  |                  Hey, Listen! | ||||||
|  | ============================================== | ||||||
|  | 
 | ||||||
|  |   GO CHECK YOUR EMAIL! | ||||||
|  | 
 | ||||||
|  |   DEVICE PAIR CODE:     0000 | ||||||
|  | 
 | ||||||
|  | ============================================== | ||||||
|  | " | ||||||
|  | 
 | ||||||
| [daemon] | [daemon] | ||||||
| version = "telebit daemon v{version}" | version = "telebit daemon v{version}" | ||||||
|  | |||||||
							
								
								
									
										116
									
								
								lib/remote-control-client.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								lib/remote-control-client.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,116 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | var os = require('os'); | ||||||
|  | var path = require('path'); | ||||||
|  | var http = require('http'); | ||||||
|  | 
 | ||||||
|  | var common = require('./cli-common.js'); | ||||||
|  | 
 | ||||||
|  | function packConfig(config) { | ||||||
|  |   return Object.keys(config).map(function (key) { | ||||||
|  |     var val = config[key]; | ||||||
|  |     if ('undefined' === val) { | ||||||
|  |       throw new Error("'undefined' used as a string value"); | ||||||
|  |     } | ||||||
|  |     if ('undefined' === typeof val) { | ||||||
|  |       //console.warn('[DEBUG]', key, 'is present but undefined');
 | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (val && 'object' === typeof val && !Array.isArray(val)) { | ||||||
|  |       val = JSON.stringify(val); | ||||||
|  |     } | ||||||
|  |     return key + ':' + val; // converts arrays to strings with ,
 | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports.create = function (state) { | ||||||
|  |   common._init( | ||||||
|  |     // make a default working dir and log dir
 | ||||||
|  |     state._clientConfig.root || path.join(os.homedir(), '.local/share/telebit') | ||||||
|  |   , (state._clientConfig.root && path.join(state._clientConfig.root, 'etc')) | ||||||
|  |       || path.resolve(common.DEFAULT_CONFIG_PATH, '..') | ||||||
|  |   ); | ||||||
|  |   state._ipc = common.pipename(state._clientConfig, true); | ||||||
|  | 
 | ||||||
|  |   function makeResponder(service, resp, fn) { | ||||||
|  |     var body = ''; | ||||||
|  | 
 | ||||||
|  |     function finish() { | ||||||
|  |       var err; | ||||||
|  | 
 | ||||||
|  |       if (200 !== resp.statusCode) { | ||||||
|  |         err = new Error(body || ('get' + service + ' failed')); | ||||||
|  |         err.statusCode = resp.statusCode; | ||||||
|  |         err.code = "E_REQUEST"; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       try { | ||||||
|  |         body = JSON.parse(body); | ||||||
|  |       } catch(e) { | ||||||
|  |         // ignore
 | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       fn(err, body); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!resp.headers['content-length'] && !resp.headers['content-type']) { | ||||||
|  |       finish(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // TODO use readable
 | ||||||
|  |     resp.on('data', function (chunk) { | ||||||
|  |       body += chunk.toString(); | ||||||
|  |     }); | ||||||
|  |     resp.on('end', finish); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   var RC = {}; | ||||||
|  |   RC.request = function request(opts, fn) { | ||||||
|  |     if (!opts) { opts = {}; } | ||||||
|  |     var service = opts.service || 'config'; | ||||||
|  |     var args = opts.data; | ||||||
|  |     if (args && 'control' === service) { | ||||||
|  |       args = packConfig(args); | ||||||
|  |     } | ||||||
|  |     var json = JSON.stringify(args); | ||||||
|  |     var url = '/rpc/' + service; | ||||||
|  |     if (json) { | ||||||
|  |       url += ('?_body=' + encodeURIComponent(json)); | ||||||
|  |     } | ||||||
|  |     var method = opts.method || (args && 'POST') || 'GET'; | ||||||
|  |     var req = http.request({ | ||||||
|  |       socketPath: state._ipc.path | ||||||
|  |     , method: method | ||||||
|  |     , path: url | ||||||
|  |     }, function (resp) { | ||||||
|  |       makeResponder(service, resp, fn); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     req.on('error', function (err) { | ||||||
|  |       // ENOENT - never started, cleanly exited last start, or creating socket at a different path
 | ||||||
|  |       // ECONNREFUSED - leftover socket just needs to be restarted
 | ||||||
|  |       if ('ENOENT' === err.code || 'ECONNREFUSED' === err.code) { | ||||||
|  |         if (opts._taketwo) { | ||||||
|  |           fn(err); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |         require('../usr/share/install-launcher.js').install({ env: process.env }, function (err) { | ||||||
|  |           if (err) { fn(err); return; } | ||||||
|  |           opts._taketwo = true; | ||||||
|  |           setTimeout(function () { | ||||||
|  |             RC.request(opts, fn); | ||||||
|  |           }, 2500); | ||||||
|  |         }); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       fn(err); | ||||||
|  |     }); | ||||||
|  |     if ('POST' === method && opts.data) { | ||||||
|  |       req.write(json || opts.data); | ||||||
|  |     } | ||||||
|  |     req.end(); | ||||||
|  |   }; | ||||||
|  |   return RC; | ||||||
|  | }; | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user