MAJOR: Updates for Authenticated Web UI and CLI #30
| @ -8,11 +8,11 @@ var os = require('os'); | ||||
| //var url = require('url');
 | ||||
| var fs = require('fs'); | ||||
| var path = require('path'); | ||||
| var http = require('http'); | ||||
| //var https = require('https');
 | ||||
| var YAML = require('js-yaml'); | ||||
| var TOML = require('toml'); | ||||
| var TPLS = TOML.parse(fs.readFileSync(path.join(__dirname, "../lib/en-us.toml"), 'utf8')); | ||||
| 
 | ||||
| /* | ||||
| if ('function' !== typeof TOML.stringify) { | ||||
|   TOML.stringify = require('json2toml'); | ||||
| @ -314,92 +314,186 @@ function askForConfig(state, mainCb) { | ||||
|   next(); | ||||
| } | ||||
| 
 | ||||
| var utils = { | ||||
|   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 = ''; | ||||
| var RC; | ||||
| 
 | ||||
|       function finish() { | ||||
|         if (200 !== resp.statusCode) { | ||||
|           console.warn(resp.statusCode); | ||||
|           console.warn(body || ('get' + service + ' failed')); | ||||
|           //cb(new Error("not okay"), body);
 | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         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(); | ||||
|         }); | ||||
| function parseConfig(err, text) { | ||||
|   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 { | ||||
|         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 (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(err); | ||||
|     } else if ('ENOTSOCK' === err.code) { | ||||
|       console.error(err); | ||||
|       return; | ||||
|     } else { | ||||
|       console.error(err); | ||||
|     } | ||||
|         require('../usr/share/install-launcher.js').install({ env: process.env }, function (err) { | ||||
|           if (err) { fn(err); return; } | ||||
|           opts._taketwo = true; | ||||
|           setTimeout(function () { | ||||
|             utils.request(opts, fn); | ||||
|           }, 2500); | ||||
|     if (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, 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; | ||||
|     } | ||||
|       if ('ENOTSOCK' === err.code) { | ||||
|         console.error(err); | ||||
|         return; | ||||
| 
 | ||||
|     if (!state.config.relay || !state.config.token) { | ||||
|       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; | ||||
|     } | ||||
| 
 | ||||
|     //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(""); | ||||
|         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." | ||||
|          + " 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);
 | ||||
|         return; | ||||
|       } | ||||
| @ -413,6 +507,7 @@ var utils = { | ||||
|         body = JSON.parse(body); | ||||
|       } catch(e) { | ||||
|         // ignore
 | ||||
| 
 | ||||
|       } | ||||
| 
 | ||||
|       if ("AWAIT_AUTH" === body.code) { | ||||
| @ -439,62 +534,10 @@ var utils = { | ||||
|         } | ||||
|         console.info(); | ||||
|       } | ||||
|       } | ||||
| 
 | ||||
|       var body = ''; | ||||
|       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; | ||||
|   } | ||||
|   function getToken(fn) { | ||||
|     state.relay = state.config.relay; | ||||
| 
 | ||||
|     // { _otp, config: {} }
 | ||||
| @ -520,17 +563,7 @@ function getToken(err, state) { | ||||
|         state.config._otp = state.config._otp = authReq.otp; | ||||
| 
 | ||||
|         if (!state.config.token && state._can_pair) { | ||||
|         console.info(""); | ||||
|         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(""); | ||||
|           console.info(TPLS.remote.code.replace(/0000/g, state.config._otp)); | ||||
|         } | ||||
| 
 | ||||
|         next(); | ||||
| @ -541,7 +574,7 @@ function getToken(err, state) { | ||||
|         state._connecting = true; | ||||
| 
 | ||||
|         // 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) { | ||||
|             state._error = err; | ||||
|             console.error("Error while initializing config [connect]:"); | ||||
| @ -550,7 +583,7 @@ function getToken(err, state) { | ||||
|           } | ||||
|           console.info("waiting..."); | ||||
|           next(); | ||||
|       }); | ||||
|         })); | ||||
|       } | ||||
|     , offer: function (token, next) { | ||||
|         //console.log("[offer] Pairing Enabled by Relay");
 | ||||
| @ -565,7 +598,7 @@ function getToken(err, state) { | ||||
|         } catch(e) { | ||||
|           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) { | ||||
|             state._error = err; | ||||
|             console.error("Error while initializing config [offer]:"); | ||||
| @ -574,14 +607,14 @@ function getToken(err, state) { | ||||
|           } | ||||
|           //console.log("Pairing Enabled Locally");
 | ||||
|           next(); | ||||
|       }); | ||||
|         })); | ||||
|       } | ||||
|     , granted: function (_, next) { | ||||
|         //console.log("[grant] Pairing complete!");
 | ||||
|         next(); | ||||
|       } | ||||
|     , end: function () { | ||||
|       utils.putConfig('enable', [], function (err) { | ||||
|         RC.request({ service: 'enable', method: 'POST', data: [] }, handleRemoteRequest('enable', function (err) { | ||||
|           if (err) { console.error(err); return; } | ||||
|           console.info("Success"); | ||||
| 
 | ||||
| @ -601,148 +634,14 @@ function getToken(err, state) { | ||||
|           } | ||||
|           // 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) { | ||||
|     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); | ||||
|   RC.request({ service: 'config', method: 'GET' }, handleRemoteRequest('config', handleConfig)); | ||||
| } | ||||
| 
 | ||||
| var parsers = { | ||||
|  | ||||
| @ -452,5 +452,17 @@ The secret flags are: | ||||
| [remote] | ||||
| version = "telebit remote v{version}" | ||||
| 
 | ||||
| code = " | ||||
| ============================================== | ||||
|                  Hey, Listen! | ||||
| ============================================== | ||||
| 
 | ||||
|   GO CHECK YOUR EMAIL! | ||||
| 
 | ||||
|   DEVICE PAIR CODE:     0000 | ||||
| 
 | ||||
| ============================================== | ||||
| " | ||||
| 
 | ||||
| [daemon] | ||||
| 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