feature(distribute) add ability to import config from remote export server

This commit is contained in:
coderaiser 2018-08-17 15:06:45 +03:00
parent b8e4f9659a
commit 1cdd95e9e1
20 changed files with 836 additions and 22 deletions

View file

@ -40,6 +40,7 @@
"io",
"js",
"maintainers",
"microservice",
"minification",
"mouseup",
"named",

74
HELP.md
View file

@ -94,23 +94,34 @@ Cloud Commander supports command line parameters:
| `--terminal-path` | set terminal path
| `--vim` | enable vim hot keys
| `--columns` | set visible columns
| `--export` | enable export of config through a server
| `--export-token` | authorization token used by export server
| `--import` | enable import of config
| `--import-token` | authorization token used to connect to export server
| `--import-url` | url of an import server
| `--import-listen` | enable listen on config updates from import server
| `--log` | enable logging
| `--no-server` | do not start server
| `--no-auth` | disable authorization
| `--no-online` | load scripts from local server
| `--no-open` | do not open web browser when server started
| `--no-name` | set empty tab name in web browser
| `--no-one-file-panel` | show two file panels
| `--no-name` | set default tab name in web browser
| `--no-keys-panel` | hide keys panel
| `--no-one-file-panel` | show two file panels
| `--no-progress` | do not show progress of file operations
| `--no-confirm-copy` | do not confirm copy
| `--no-confirm-move` | do not confirm move
| `--no-contact` | disable contact
| `--no-config-dialog` | disable config dialog
| `--no-console` | disable console
| `--no-sync-console-path` | do not sync console path
| `--no-contact` | disable contact
| `--no-terminal` | disable terminal
| `--no-vim` | disable vim hot keys
| `--no-columns` | set visible default columns
| `--no-columns` | set default visible columns
| `--no-export` | disable export config through a server
| `--no-import` | disable import of config
| `--no-import-listen` | disable listen on config updates from import server
| `--no-log` | disable logging
If no parameters given Cloud Commander reads information from `~/.cloudcmd.json` and use
port from it (`8000` default). if port variables `PORT` or `VCAP_APP_PORT` isn't exist.
@ -382,6 +393,13 @@ Here is description of options:
"terminalPath" : '', /* path of a terminal */
"vim" : false, /* disable vim hot keys */
"columns" : "name-size-date-owner-mode", /* set visible columns */
"export" : false, /* enable export of config through a server */
"exportToken" : "root", /* token used by export server */
"import" : false, /* enable import of config */
"import-url" : "http://localhost:8000", /* url of an export server */
"importToken" : "root", /* token used to connect to export server */
"importListen" : false, /* listen on config updates from import server */
"log" : true /* logging */
}
```
@ -407,6 +425,54 @@ Some config options can be overridden with `environment variables` such:
- `CLOUDCMD_VIM` - enable vim hot keys
- `CLOUDCMD_CONFIRM_COPY` - confirm copy
- `CLOUDCMD_CONFIRM_MOVE` - confirm move
- `CLOUDCMD_EXPORT` - enable export of config through a server
- `CLOUDCMD_EXPORT_TOKEN` - authorization token used by export server
- `CLOUDCMD_IMPORT` - enable import of config
- `CLOUDCMD_IMPORT_TOKEN` - authorization token used to connect to export server
- `CLOUDCMD_IMPORT_URL` - url of an import server
- `CLOUDCMD_IMPORT_LISTEN`- enable listen on config updates from import server
### Distribute
Being able to configure `Cloud Commander` remotely opens the doors to using it as microservice and that's what distribute options set out to do.
There is `an export server` and `import client` and they enabled with `--export` and `--import` accordingly. There is a `token` it should be the same
in `--import-token` and `export-token`. To use report you should provide `--import-url` to `import client` so it could connect to an `export server`.
There is 2 ways imports client can receive config from an export server:
- full config at startup (default)
- get every updated option (with help of `--import-listen` flag)
There is an example of using distribute options in `Cloud Commander` to get config from remote instance.
Here is an `export server`:
```
coderaiser@cloudcmd:~$ cloudcmd --port 1234 --export --export-token=cloudcmd
```
And `import client`:
```
coderaiser@cloudcmd:~$ cloudcmd --name importer --port 4321 --import-url http://127.0.0.1:1234 --import-token=cloudcmd --no-server --save
```
Here is the log of `export server`:
```
url: http://localhost:1233/
2018.08.23 13:41:45 -> export: try to auth from importer [127.0.0.1:4444]
2018.08.23 13:41:45 -> export: connected to importer [127.0.0.1:4444]
2018.08.23 13:41:45 -> export: disconnected importer [127.0.0.1:4444]
```
And log of `import client`:
```
2018.08.23 13:47:36 -> import: try to auth to http://127.0.0.1:1234
2018.08.23 13:47:36 -> import: connected to http://127.0.0.1:1234
2018.08.23 13:47:36 -> import: config received from http://localhost:1234
2018.08.23 13:47:36 -> import: disconnected from http://127.0.0.1:1234
```
When `import client` uses `--import-listen` persistent connection used and client receives live updates from the `import server`.
Menu
---------------

View file

@ -111,6 +111,35 @@
"description": "confirm move",
"value": "true",
"required": false
},
"CLOUDCMD_EXPORT": {
"description": "enable export of config through a server",
"value": "false",
"required": false
},
"CLOUDCMD_EXPORT_TOKEN": {
"description": "authorization token used by export server",
"value": "root",
"required": false
},
"CLOUDCMD_IMPORT": {
"description": "enable import of config",
"value": "false",
"required": false
},
"CLOUDCMD_IMPORT_TOKEN": {
"description": "authorization token used to connect to export server",
"value": "root",
"required": false
},
"CLOUDCMD_IMPORT_URL": {
"description": "url of an import server",
"value": "http://localhost:8000",
"required": false
},
"CLOUDCMD_IMPORT_LISTEN": {
"description": "enable listen on config updates from import server",
"value": "false",
"required": false
}
}
}

View file

@ -5,10 +5,15 @@
const Info = require('../package');
const DIR_SERVER = '../server/';
const promisify = require('es6-promisify').promisify;
const wraptile = require('wraptile/legacy');
const exit = require(DIR_SERVER + 'exit');
const config = require(DIR_SERVER + 'config');
const env = require(DIR_SERVER + 'env');
const noop = () => {};
const choose = (a, b) => {
if (a === undefined)
return b;
@ -30,6 +35,9 @@ const args = require('minimist')(argv.slice(2), {
'prefix',
'terminal-path',
'columns',
'import-url',
'import-token',
'export-token',
],
boolean: [
'auth',
@ -50,6 +58,11 @@ const args = require('minimist')(argv.slice(2), {
'show-config',
'vim',
'keys-panel',
'color',
'export',
'import',
'import-listen',
'log',
],
default: {
server : true,
@ -68,6 +81,14 @@ const args = require('minimist')(argv.slice(2), {
console : choose(env.bool('console'), config('console')),
contact : choose(env.bool('contact'), config('contact')),
terminal : choose(env.bool('terminal'), config('terminal')),
columns : env('columns') || config('columns') || '',
vim : choose(env.bool('vim'), config('vim')),
log : config('log'),
'import-url': env('import_url') || config('importUrl'),
'import-listen': choose(env.bool('import-listen'), config('importListen')),
import : choose(env.bool('import'), config('import')),
export : choose(env.bool('export'), config('export')),
'sync-console-path': choose(env.bool('sync_console_path'), config('syncConsolePath')),
'config-dialog': choose(env.bool('config_dialog'), config('configDialog')),
@ -75,9 +96,9 @@ const args = require('minimist')(argv.slice(2), {
'one-file-panel': choose(env.bool('one_file_panel'), config('oneFilePanel')),
'confirm-copy': choose(env.bool('confirm_copy'), config('confirmCopy')),
'confirm-move': choose(env.bool('confirm_move'), config('confirmMove')),
'vim': choose(env.bool('vim'), config('vim')),
'columns': env('columns') || config('columns') || '',
'keys-panel': env.bool('keys_panel') || config('keysPanel'),
'import-token': env('import_token') || config('importToken'),
'export-token': env('export_token') || config('exportToken'),
},
alias: {
v: 'version',
@ -106,7 +127,6 @@ function main() {
repl();
checkUpdate();
port(args.port);
config('name', args.name);
@ -125,11 +145,18 @@ function main() {
config('root', args.root);
config('vim', args.vim);
config('columns', args.columns);
config('log', args.log);
config('confirmCopy', args['confirm-copy']);
config('confirmMove', args['confirm-move']);
config('oneFilePanel', args['one-file-panel']);
config('configDialog', args['config-dialog']);
config('keysPanel', args['keys-panel']);
config('export', args.export);
config('exportToken', args['export-token']);
config('import', args.import);
config('importToken', args['import-token']);
config('importListen', args['import-listen']);
config('importUrl', args['import-url']);
readConfig(args.config);
@ -151,12 +178,14 @@ function main() {
if (args['show-config'])
showConfig();
if (!args.save)
return start(options);
const startWraped = wraptile(start, options);
const distribute = require('../server/distribute');
const importConfig = promisify(distribute.import);
const caller = (fn) => fn();
config.save(() => {
start(options);
});
importConfig()
.then(args.save ? caller(config.save) : noop)
.then(startWraped(options));
}
function validateRoot(root) {
@ -166,7 +195,6 @@ function validateRoot(root) {
function getPassword(password) {
const criton = require('criton');
return criton(password, config('algo'));
}
@ -241,7 +269,6 @@ function repl() {
function checkUpdate() {
const load = require('package-json');
const noop = () => {};
load(Info.name, 'latest')
.then(showUpdateInfo)

33
common/datetime.js Normal file
View file

@ -0,0 +1,33 @@
'use strict';
const shortdate = require('shortdate');
module.exports = (date) => {
date = date || new Date();
check(date);
const timeStr = shorttime(date);
const dateStr = shortdate(date);
return `${dateStr} ${timeStr}`;
};
const addZero = (a) => {
if (a > 9)
return a;
return `0${a}`;
};
function shorttime(date) {
const seconds = addZero(date.getSeconds());
const minutes = addZero(date.getMinutes());
const hours = addZero(date.getHours());
return `${hours}:${minutes}:${seconds}`;
}
function check(date) {
if (!(date instanceof Date))
throw Error('date should be instanceof Date!');
}

54
common/datetime.spec.js Normal file
View file

@ -0,0 +1,54 @@
'use strict';
const test = require('tape');
const sinon = require('sinon');
const datetime = require('./datetime');
test('common: datetime', (t) => {
const dateStr = 'Fri, 17 Aug 2018 10:56:48';
const result = datetime(new Date(dateStr));
const expected = '2018.08.17 10:56:48';
t.equals(result, expected, 'should equal');
t.end();
});
test('common: datetime: no arg', (t) => {
const {Date} = global;
let called = false;
const myDate = class extends Date {
constructor() {
super();
called = true;
}
};
global.Date = myDate;
datetime();
global.Date = Date;
t.ok(called, 'should call new Date');
t.end();
});
test('common: 0 before number', (t) => {
const dateStr = 'Fri, 17 Aug 2018 10:56:08';
const result = datetime(new Date(dateStr));
const expected = '2018.08.17 10:56:08';
t.equals(result, expected, 'should equal');
t.end();
});
test('common: datetime: wrong args', (t) => {
const fn = () => datetime({});
t.throws(fn, /date should be instanceof Date!/, 'should throw');
t.end();
});

View file

@ -29,6 +29,13 @@
"terminalPath": "",
"showConfig": false,
"vim": false,
"columns": "name-size-date-owner-mode"
"columns": "name-size-date-owner-mode",
"export": false,
"exportToken": "root",
"import": false,
"importToken": "root",
"importUrl": "http://localhost:8000",
"importListen": false,
"log": true
}

View file

@ -28,12 +28,19 @@
"--terminal-path ": "set terminal path",
"--vim ": "enable vim hot keys",
"--columns ": "set visible columns",
"--export ": "enable export of config through a server",
"--export-token ": "authorization token used by export server",
"--import ": "enable import of config",
"--import-url ": "url of an export server",
"--import-token ": "authorization token used to connect to export server",
"--import-listen ": "enable listen on config updates from import server",
"--log ": "enable logging",
"--no-server ": "do not start server",
"--no-auth ": "disable authorization",
"--no-online ": "load scripts from local server",
"--no-open ": "do not open web browser when server started",
"--no-name ": "set default tab name in web browser",
"--no-one-panel ": "show two file panels",
"--no-one-file-panel ": "show two file panels",
"--no-keys-panel ": "hide keys panel",
"--no-one-file-panel ": "show two file panels",
"--no-progress ": "do not show progress of file operations",
@ -45,5 +52,9 @@
"--no-contact ": "disable contact",
"--no-terminal ": "disable terminal",
"--no-vim ": "disable vim hot keys",
"--no-columns ": "set default visible columns"
"--no-columns ": "set default visible columns",
"--no-export ": "disable export config through a server",
"--no-import ": "disable import of config",
"--no-import-listen ": "disable listen on config updates from import server",
"--no-log ": "disable logging"
}

View file

@ -51,6 +51,13 @@ programs in browser from any computer, mobile or tablet device.
--terminal-path set terminal path
--vim enable vim hot keys
--columns set visible columns
--export enable export of config through a server
--export-token authorization token used by export server
--import enable import of config
--import-url url of an import server
--import-token authorization token used to connect to export server
--import-listen enable listen on config updates from import server
--log enable logging
--no-auth disable authorization
--no-server do not start server
--no-online load scripts from local server
@ -68,6 +75,11 @@ programs in browser from any computer, mobile or tablet device.
--no-terminal disable terminal
--no-vim disable vim hot keys
--no-columns set visible default columns
--no-export disable export of config through a server
--no-import disable import of config
--no-import-url url of an import server
--no-import-listen disable listen on config updates from import server
--no-log disable logging
.SH RESOURCES AND DOCUMENTATION

View file

@ -4,7 +4,11 @@
"env": {
"cloudcmd_config_dialog": "false",
"cloudcmd_terminal": "true",
"cloudcmd_terminal_path": "gritty"
"cloudcmd_terminal_path": "gritty",
"cloudcmd_import": true,
"cloudcmd_import_listen": true,
"cloudcmd_import_token": "hello-world",
"cloudcmd_import_url": "http://config.cloudcmd.io"
},
"engines": {
"node": "8"

View file

@ -142,6 +142,7 @@
"mellow": "^2.0.0",
"minimist": "^1.2.0",
"nomine": "^2.0.0",
"object.omit": "^3.0.0",
"onezip": "^2.0.0",
"opn": "^5.1.0",
"package-json": "^4.0.1",
@ -149,7 +150,9 @@
"pullout": "^2.0.0",
"rendy": "^2.0.0",
"restafary": "^3.0.0",
"shortdate": "^1.2.0",
"socket.io": "^2.0.3",
"socket.io-client": "^2.1.1",
"squad": "^2.0.0",
"table": "^4.0.1",
"try-catch": "^2.0.0",
@ -202,11 +205,9 @@
"rimraf": "^2.5.4",
"scroll-into-view-if-needed": "^2.2.5",
"serviceworker-webpack-plugin": "^1.0.1",
"shortdate": "^1.0.1",
"sinon": "^6.0.4",
"sinon-called-with-diff": "^2.0.0",
"smalltalk": "^3.1.0",
"socket.io-client": "^2.0.1",
"style-loader": "^0.22.0",
"stylelint": "^9.0.0",
"stylelint-config-standard": "^18.0.0",

View file

@ -17,6 +17,7 @@ const validate = require(DIR + 'validate');
const prefixer = require(DIR + 'prefixer');
const pluginer = require(DIR + 'plugins');
const terminal = require(DIR + 'terminal');
const distribute = require(DIR + 'distribute');
const currify = require('currify/legacy');
const apart = require('apart');
@ -147,6 +148,8 @@ function listen(prefix, socket) {
auth,
prefix: prefix + '/gritty',
});
distribute.export(socket);
}
function cloudcmd(prefix, plugins, modules) {

View file

@ -6,6 +6,7 @@ const DIR = DIR_SERVER + '../';
const path = require('path');
const fs = require('fs');
const Emitter = require('events');
const exit = require(DIR_SERVER + 'exit');
const CloudFunc = require(DIR_COMMON + 'cloudfunc');
@ -35,6 +36,7 @@ const send = swap(ponse.send);
const formatMsg = currify((a, b) => CloudFunc.formatMsg(a, b));
const apiURL = CloudFunc.apiURL;
const changeEmitter = new Emitter();
const ConfigPath = path.join(DIR, 'json/config.json');
const ConfigHome = path.join(HOME, '.cloudcmd.json');
@ -59,8 +61,16 @@ const config = Object.assign({}, rootConfig, configHome);
const connectionWraped = wraptile(connection);
module.exports = manage;
module.exports.save = _save;
module.exports.save = save;
module.exports.middle = middle;
module.exports.subscribe = (fn) => {
changeEmitter.on('change', fn);
};
module.exports.unsubscribe = (fn) => {
changeEmitter.removeListener('change', fn);
};
module.exports.listen = (socket, auth) => {
check(socket, auth);
@ -83,6 +93,10 @@ function manage(key, value) {
return config[key];
config[key] = value;
changeEmitter.emit('change', key, value);
return `${key} = ${value}`;
}
function _save(callback) {

114
server/distribute/export.js Normal file
View file

@ -0,0 +1,114 @@
'use strict';
const currify = require('currify/legacy');
const wraptile = require('wraptile/legacy');
const squad = require('squad/legacy');
const omit = require('object.omit');
const config = require('../config');
const log = require('./log');
const exportStr = log.exportStr;
const connectedStr = log.connectedStr;
const disconnectedStr = log.disconnectedStr;
const authTryStr = log.authTryStr;
const makeColor = log.makeColor;
const getMessage = log.getMessage;
const getDescription = log.getDescription;
const logWraped = log.logWraped;
const omitList = [
'auth',
'username',
'password',
'algo',
'name',
'ip',
'port',
'root',
'import',
'importUrl',
'importToken',
'export',
'exportToken',
'log',
'configDialog',
];
const omitConfig = wraptile((config) => omit(config, omitList));
module.exports = (socket) => {
if (!config('export'))
return;
const prefix = config('prefix');
const distributePrefix = `${prefix}/distribute`;
const onError = squad(logWraped(exportStr), getMessage);
const onConnectError = squad(logWraped(exportStr), getDescription);
socket.of(distributePrefix)
.on('connection', onConnection(push))
.on('error', onError)
.on('connect_error', onConnectError);
};
const push = currify((socket, key, value) => {
if (omitList.includes(key))
return;
socket.emit('change', key, value);
});
function getHost(socket) {
const remoteAddress = socket.request.connection.remoteAddress;
const name = socket.handshake.query.name;
const port = socket.handshake.query.port;
const color = socket.handshake.query.color;
if (!name)
return `${remoteAddress}:${port}`;
const colorName = makeColor(name, color);
return `${colorName} [${remoteAddress}:${port}]`;
}
const connectPush = wraptile((push, socket) => {
socket.emit('accept');
const host = getHost(socket);
const subscription = push(socket);
socket.on('disconnect', onDisconnect(subscription, host));
log(exportStr, `${connectedStr} to ${host}`);
socket.emit('config', omitConfig(config('*')));
config.subscribe(subscription);
});
const onConnection = currify((push, socket) => {
const host = getHost(socket);
const reject = () => {
socket.emit('reject');
socket.disconnect();
};
log(exportStr, `${authTryStr} from ${host}`);
socket.on('auth', auth(connectPush(push, socket), reject));
});
const auth = currify((fn, reject, token) => {
if (token === config('exportToken'))
return fn();
reject();
});
const onDisconnect = wraptile((subscription, host) => {
config.unsubscribe(subscription);
log(exportStr, `${disconnectedStr} from ${host}`);
});

View file

@ -0,0 +1,45 @@
'use strict';
const {promisify} = require('util');
const test = require('tape');
const io = require('socket.io-client');
const {connect} = require('../../test/before');
const config = require('../config');
test('distribute: export', async (t) => {
const defaultConfig = {
export: true,
exportToken: 'a',
vim: true,
log: false,
};
const {port, done} = await connect({
config: defaultConfig
});
const url = `http://localhost:${port}/distribute?port=${1111}`;
const socket = io.connect(url);
const name = config('name');
socket.on('connect', () => {
socket.emit('auth', 'a');
});
socket.on('accept', () => {
config('vim', false);
config('auth', true);
});
socket.on('change', async () => {
socket.close();
await done();
t.pass('should emit change');
t.end();
});
});

129
server/distribute/import.js Normal file
View file

@ -0,0 +1,129 @@
'use strict';
const currify = require('currify/legacy');
const wraptile = require('wraptile/legacy');
const squad = require('squad/legacy');
const fullstore = require('fullstore/legacy');
const io = require('socket.io-client');
const forEachKey = currify(require('for-each-key/legacy'));
const config = require('../config');
const log = require('./log');
const importStr = log.importStr;
const connectedStr = log.connectedStr;
const disconnectedStr = log.disconnectedStr;
const tokenRejectedStr = log.tokenRejectedStr;
const authTryStr = log.authTryStr;
const makeColor = log.makeColor;
const stringToRGB = log.stringToRGB;
const getMessage = log.getMessage;
const getDescription = log.getDescription;
const logWraped = log.logWraped;
const equal = (a, b) => `${a}=${b}`;
const append = currify((obj, a, b) => obj.value += b && equal(a, b) + '&');
const wrapApply = (f, disconnect) => (status) => () => f(null, {
status,
disconnect,
});
const closeIfNot = wraptile((socket, is) => !is && socket.close());
const addUrl = currify((url, a) => `${url}: ${a}`);
const getColorUrl = (url, name) => {
if (!name)
return url;
return makeColor(url, stringToRGB(name));
};
const rmListeners = wraptile((socket, listeners) => {
socket.removeListener('connect', listeners.onConnect);
socket.removeListener('config', listeners.onConfig);
socket.removeListener('error', listeners.onError);
socket.removeListener('connection_error', listeners.onError);
});
const canceled = (f) => f(null, {
status: 'canceled',
disconnect: () => {},
});
const done = wraptile((fn, store) => fn(null, {
status: store()
}));
const emitAuth = wraptile((importUrl, socket) => {
log(importStr, `${authTryStr} to ${importUrl}`);
socket.emit('auth', config('importToken'));
});
module.exports = (options, fn) => {
fn = fn || options;
if (!config('import'))
return canceled(fn);
const importUrl = config('importUrl');
const importListen = config('importListen');
const name = config('name');
const port = config('port');
const query = toLine({
name,
port,
});
const url = `${importUrl}/distribute?${query}`;
const socket = io.connect(url, options);
const superFn = wrapApply(fn, socket.close.bind(socket));
const colorUrl = getColorUrl(importUrl, name);
const close = closeIfNot(socket, importListen);
const statusStore = fullstore();
const statusStoreWraped = wraptile(statusStore);
const onConfig = squad(close, logWraped(importStr, `config received from ${colorUrl}`), statusStoreWraped('received'), forEachKey(config));
const onError = squad(superFn('error'), logWraped(importStr), addUrl(colorUrl), getMessage);
const onConnectError = squad(superFn('connect_error'), logWraped(importStr), addUrl(colorUrl), getDescription);
const onConnect = emitAuth(importUrl, socket);
const onAccept = logWraped(importStr,`${connectedStr} to ${colorUrl}`);
const onDisconnect = squad(done(fn, statusStore), logWraped(importStr, `${disconnectedStr} from ${colorUrl}`), rmListeners(socket, {
onError,
onConnect,
onConfig,
}));
const onChange = squad(logWraped(importStr), config);
const onReject = squad(superFn('reject'), logWraped(importStr, tokenRejectedStr));
socket.on('connect', onConnect);
socket.on('accept', onAccept);
socket.on('disconnect', onDisconnect);
socket.on('config', onConfig);
socket.on('error', onError);
socket.on('connect_error', onConnectError);
socket.on('reject', onReject);
if (config('importListen'))
socket.on('change', onChange);
};
function toLine(obj) {
const result = {
value: '',
};
forEachKey(append(result), obj);
const start = 0;
const end = 1;
const backward = -1;
return result.value.slice(start, backward * end);
}

View file

@ -0,0 +1,183 @@
'use strict';
const test = require('tape');
const {promisify} = require('util');
const tryToCatch = require('try-to-catch');
const io = require('socket.io-client');
const mockRequire = require('mock-require');
const {connect} = require('../../test/before');
const config = require('../config');
const distribute = {
import: promisify(require('./import')),
};
test('distribute: import: canceled', async (t) => {
const {done, port} = await connect({
config: {
export: false,
import: false,
importListen: false,
log: false,
}
});
const {status} = await distribute.import();
await done();
t.equal(status, 'canceled', 'should equal');
t.end();
});
test('distribute: import: received: no error', async (t) => {
const {done, port} = await connect({
config: {
import: true,
importListen: false,
export: true,
log: false,
}
});
config('importUrl', `http://localhost:${port}`);
const [e] = await tryToCatch(distribute.import);
await done();
t.notOk(e, 'should not be error');
t.end();
});
test('distribute: import: received', async (t) => {
const {done, port} = await connect({
config: {
name: 'bill',
import: true,
importToken: 'a',
exportToken: 'a',
export: true,
importListen: false,
log: false,
}
});
config('importUrl', `http://localhost:${port}`);
const {status} = await distribute.import();
await done();
t.equal(status, 'received','should equal');
t.end();
});
test('distribute: import: received: auth: reject', async (t) => {
const {done, port} = await connect({
config: {
name: 'bill',
import: true,
importToken: 'xxxxx',
exportToken: 'bbbbb',
export: true,
importListen: false,
log: false,
}
});
config('importUrl', `http://localhost:${port}`);
const {status} = await distribute.import();
await done();
t.equal(status, 'reject','should equal');
t.end();
});
test('distribute: import: received: auth: accept', async (t) => {
const {done, port} = await connect({
config: {
name: 'bill',
import: true,
importToken: 'xxxxx',
exportToken: 'xxxxx',
export: true,
importListen: false,
log: false,
}
});
config('importUrl', `http://localhost:${port}`);
const {status} = await distribute.import();
await done();
t.equal(status, 'received','should equal');
t.end();
});
test('distribute: import: received: no name', async (t) => {
const {done, port} = await connect({
config: {
name: '',
import: true,
export: true,
importListen: false,
log: false,
}
});
config('importUrl', `http://localhost:${port}`);
const {status} = await distribute.import();
await done();
t.equal(status, 'received','should equal');
t.end();
});
test('distribute: import: error', async (t) => {
const {done, port} = await connect({
config: {
import: true,
export: false,
importListen: false,
log: false,
}
});
config('importUrl', `http://localhost:0`);
const {status} = await distribute.import({
reconnection: false,
});
await done();
t.equal(status, 'connect_error','should equal');
t.end();
});
test('distribute: import: config:change: no export', async (t) => {
const {done, port} = await connect({
config: {
import: true,
export: false,
importListen: true,
log: false,
}
});
const {status} = await distribute.import({
reconnection: false,
});
await done();
t.equal(status, 'connect_error','should equal');
t.end();
});
process.on('unhandledRejection', console.log);

View file

@ -0,0 +1,4 @@
'use strict';
module.exports.import = require('./import');
module.exports.export = require('./export');

45
server/distribute/log.js Normal file
View file

@ -0,0 +1,45 @@
'use strict';
const wraptile = require('wraptile/legacy');
const chalk = require('chalk');
const config = require('../config');
const datetime = require('../../common/datetime');
const log = (name, msg) => config('log') && console.log(`${datetime()} -> ${name}: ${msg}`);
const makeColor = (a, color) => chalk.rgb(color || stringToRGB(a))(a);
const getMessage = (e) => e.message || e;
const getDescription = (e) => `${e.type}: ${e.description}`;
module.exports = log;
module.exports.logWraped = wraptile(log);
module.exports.stringToRGB = stringToRGB;
module.exports.makeColor = makeColor;
module.exports.getMessage = getMessage;
module.exports.getDescription = getDescription;
module.exports.importStr = 'import';
module.exports.exportStr = 'export';
module.exports.connectedStr = chalk.green('connected');
module.exports.disconnectedStr = chalk.red('disconnected');
module.exports.tokenRejectedStr = chalk.red('token rejected');
module.exports.authTryStr = chalk.yellow('try to auth');
function stringToRGB(a) {
return [
a.charCodeAt(0),
a.length,
crc(a),
];
}
const add = (a, b) => {
return a + b.charCodeAt(0);
};
function crc(a) {
return a
.split('')
.reduce(add, 0);
}

View file

@ -0,0 +1,32 @@
'use strict';
const test = require('tape');
const log = require('./log');
const config = require('../config');
test('distribute: log: getMessage', (t) => {
const e = 'hello';
const result = log.getMessage(e)
t.equal(e, result, 'should equal');
t.end();
});
test('distribute: log: getMessage: message', (t) => {
const message = 'hello';
const result = log.getMessage({
message
})
t.equal(result, message, 'should equal');
t.end();
});
test('distribute: log: config', (t) => {
const logOriginal = config('log');
config('log', true);
log('log', 'test message');
config('log', logOriginal);
t.end();
});