Merge branch '0.0.11-beta' into master (#276 initial)
* This is the start of #276: Master will be caught up and some additional changes soon
This commit is contained in:
commit
9a1ede3055
|
@ -63,7 +63,7 @@ ENiGMA has been tested with many terminals. However, the following are suggested
|
||||||
## Installation
|
## Installation
|
||||||
On *nix type systems:
|
On *nix type systems:
|
||||||
```
|
```
|
||||||
curl -o- https://raw.githubusercontent.com/NuSkooler/enigma-bbs/master/misc/install.sh | bash
|
curl -o- https://raw.githubusercontent.com/NuSkooler/enigma-bbs/0.0.11-beta/misc/install.sh | bash
|
||||||
```
|
```
|
||||||
|
|
||||||
Please see [Installation Methods](https://nuskooler.github.io/enigma-bbs/installation/installation-methods.html) for Windows, Docker, and so on...
|
Please see [Installation Methods](https://nuskooler.github.io/enigma-bbs/installation/installation-methods.html) for Windows, Docker, and so on...
|
||||||
|
|
|
@ -40,6 +40,9 @@ npm install
|
||||||
Report your issue on Xibalba BBS, hop in #enigma-bbs on FreeNode and chat, or
|
Report your issue on Xibalba BBS, hop in #enigma-bbs on FreeNode and chat, or
|
||||||
[file a issue on GitHub](https://github.com/NuSkooler/enigma-bbs/issues).
|
[file a issue on GitHub](https://github.com/NuSkooler/enigma-bbs/issues).
|
||||||
|
|
||||||
|
# 0.0.10-alpha to 0.0.11-beta
|
||||||
|
* Node.js 12.x LTS is now in use. Follow standard Node.js upgrade procedures (e.g.: `nvm install 12 && nvm use 12`).
|
||||||
|
|
||||||
# 0.0.9-alpha to 0.0.10-alpha
|
# 0.0.9-alpha to 0.0.10-alpha
|
||||||
* Security related files such as private keys and certs are now looked for in `config/security` by default.
|
* Security related files such as private keys and certs are now looked for in `config/security` by default.
|
||||||
* Default archive handler for zip files has switched to InfoZip due to a bug in the latest p7Zip packages causing "volume not found" errors. Ensure you have the InfoZip `zip` and `unzip` commands in ENiGMA's path. You can switch back to 7Zip by overriding `archiveHandler` for `application/zip` in your `config.hjson` under `fileTypes` to `7Zip`.
|
* Default archive handler for zip files has switched to InfoZip due to a bug in the latest p7Zip packages causing "volume not found" errors. Ensure you have the InfoZip `zip` and `unzip` commands in ENiGMA's path. You can switch back to 7Zip by overriding `archiveHandler` for `application/zip` in your `config.hjson` under `fileTypes` to `7Zip`.
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
# Whats New
|
# Whats New
|
||||||
This document attempts to track **major** changes and additions in ENiGMA½. For details, see GitHub.
|
This document attempts to track **major** changes and additions in ENiGMA½. For details, see GitHub.
|
||||||
|
|
||||||
|
## 0.0.11-beta
|
||||||
|
* Upgraded from `alpha` to `beta` -- The software is far along and mature enough at this point!
|
||||||
|
* Development is now against Node.js 12.x LTS. Other versions may work but are not currently supported!
|
||||||
|
* [QWK support](/docs/messageareas/qwk.md)
|
||||||
|
* `oputil fb scan *areaTagWildcard*` scans all areas in which wildcard is matched.
|
||||||
|
* The archiver configuration `escapeTelnet` has been renamed `escapeIACs`. Support for the old value will be removed in the future.
|
||||||
|
|
||||||
## 0.0.10-alpha
|
## 0.0.10-alpha
|
||||||
+ `oputil.js user rename USERNAME NEWNAME`
|
+ `oputil.js user rename USERNAME NEWNAME`
|
||||||
+ `my_messages.js` module (defaulted to "m" at the message menu) to list public messages addressed to the currently logged in user. Takes into account their username and `real_name` property.
|
+ `my_messages.js` module (defaulted to "m" at the message menu) to list public messages addressed to the currently logged in user. Takes into account their username and `real_name` property.
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -288,6 +288,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qwkExportPacketCurrentConfig: {
|
||||||
|
mci: {
|
||||||
|
TL1: {
|
||||||
|
width: 70
|
||||||
|
}
|
||||||
|
TL2: {
|
||||||
|
width: 70
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mailMenuCreateMessage: {
|
mailMenuCreateMessage: {
|
||||||
0: {
|
0: {
|
||||||
mci: {
|
mci: {
|
||||||
|
|
|
@ -87,6 +87,7 @@ function ANSIEscapeParser(options) {
|
||||||
let pos = 0;
|
let pos = 0;
|
||||||
let start = 0;
|
let start = 0;
|
||||||
let charCode;
|
let charCode;
|
||||||
|
let lastCharCode;
|
||||||
|
|
||||||
while(pos < len) {
|
while(pos < len) {
|
||||||
charCode = text.charCodeAt(pos) & 0xff; // 8bit clean
|
charCode = text.charCodeAt(pos) & 0xff; // 8bit clean
|
||||||
|
@ -102,6 +103,12 @@ function ANSIEscapeParser(options) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LF :
|
case LF :
|
||||||
|
// Handle ANSI saved with UNIX-style LF's only
|
||||||
|
// vs the CRLF pairs
|
||||||
|
if (lastCharCode !== CR) {
|
||||||
|
self.column = 1;
|
||||||
|
}
|
||||||
|
|
||||||
self.emit('literal', text.slice(start, pos));
|
self.emit('literal', text.slice(start, pos));
|
||||||
start = pos;
|
start = pos;
|
||||||
|
|
||||||
|
@ -126,6 +133,7 @@ function ANSIEscapeParser(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
++pos;
|
++pos;
|
||||||
|
lastCharCode = charCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -204,23 +204,37 @@ module.exports = class ArchiveUtil {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
compressTo(archType, archivePath, files, cb) {
|
compressTo(archType, archivePath, files, workDir, cb) {
|
||||||
const archiver = this.getArchiver(archType, paths.extname(archivePath));
|
const archiver = this.getArchiver(archType, paths.extname(archivePath));
|
||||||
|
|
||||||
if(!archiver) {
|
if(!archiver) {
|
||||||
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!cb && _.isFunction(workDir)) {
|
||||||
|
cb = workDir;
|
||||||
|
workDir = null;
|
||||||
|
}
|
||||||
|
|
||||||
const fmtObj = {
|
const fmtObj = {
|
||||||
archivePath : archivePath,
|
archivePath : archivePath,
|
||||||
fileList : files.join(' '), // :TODO: probably need same hack as extractTo here!
|
fileList : files.join(' '), // :TODO: probably need same hack as extractTo here!
|
||||||
};
|
};
|
||||||
|
|
||||||
const args = archiver.compress.args.map( arg => stringFormat(arg, fmtObj) );
|
// :TODO: DRY with extractTo()
|
||||||
|
const args = archiver.compress.args.map( arg => {
|
||||||
|
return '{fileList}' === arg ? arg : stringFormat(arg, fmtObj);
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileListPos = args.indexOf('{fileList}');
|
||||||
|
if(fileListPos > -1) {
|
||||||
|
// replace {fileList} with 0:n sep file list arguments
|
||||||
|
args.splice.apply(args, [fileListPos, 1].concat(files));
|
||||||
|
}
|
||||||
|
|
||||||
let proc;
|
let proc;
|
||||||
try {
|
try {
|
||||||
proc = pty.spawn(archiver.compress.cmd, args, this.getPtyOpts());
|
proc = pty.spawn(archiver.compress.cmd, args, this.getPtyOpts(workDir));
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return cb(Errors.ExternalProcess(
|
return cb(Errors.ExternalProcess(
|
||||||
`Error spawning archiver process "${archiver.compress.cmd}" with args "${args.join(' ')}": ${e.message}`)
|
`Error spawning archiver process "${archiver.compress.cmd}" with args "${args.join(' ')}": ${e.message}`)
|
||||||
|
@ -332,15 +346,15 @@ module.exports = class ArchiveUtil {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getPtyOpts(extractPath) {
|
getPtyOpts(cwd) {
|
||||||
const opts = {
|
const opts = {
|
||||||
name : 'enigma-archiver',
|
name : 'enigma-archiver',
|
||||||
cols : 80,
|
cols : 80,
|
||||||
rows : 24,
|
rows : 24,
|
||||||
env : process.env,
|
env : process.env,
|
||||||
};
|
};
|
||||||
if(extractPath) {
|
if(cwd) {
|
||||||
opts.cwd = extractPath;
|
opts.cwd = cwd;
|
||||||
}
|
}
|
||||||
// :TODO: set cwd to supplied temp path if not sepcific extract
|
// :TODO: set cwd to supplied temp path if not sepcific extract
|
||||||
return opts;
|
return opts;
|
||||||
|
|
11
core/art.js
11
core/art.js
|
@ -49,7 +49,7 @@ function getFontNameFromSAUCE(sauce) {
|
||||||
|
|
||||||
function sliceAtEOF(data, eofMarker) {
|
function sliceAtEOF(data, eofMarker) {
|
||||||
let eof = data.length;
|
let eof = data.length;
|
||||||
const stopPos = Math.max(data.length - (256), 0); // 256 = 2 * sizeof(SAUCE)
|
const stopPos = Math.max(data.length - 256, 0); // 256 = 2 * sizeof(SAUCE)
|
||||||
|
|
||||||
for(let i = eof - 1; i > stopPos; i--) {
|
for(let i = eof - 1; i > stopPos; i--) {
|
||||||
if(eofMarker === data[i]) {
|
if(eofMarker === data[i]) {
|
||||||
|
@ -57,9 +57,16 @@ function sliceAtEOF(data, eofMarker) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(eof === data.length || eof < 128) {
|
|
||||||
|
if (eof === data.length) {
|
||||||
|
return data; // nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to prevent goofs
|
||||||
|
if (eof < 128 && 'SAUCE00' !== data.slice(eof + 1, eof + 8).toString()) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.slice(0, eof);
|
return data.slice(0, eof);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -189,8 +189,12 @@ function initialize(cb) {
|
||||||
function basicInit(callback) {
|
function basicInit(callback) {
|
||||||
logger.init();
|
logger.init();
|
||||||
logger.log.info(
|
logger.log.info(
|
||||||
{ version : require('../package.json').version },
|
{
|
||||||
'**** ENiGMA½ Bulletin Board System Starting Up! ****');
|
version : require('../package.json').version,
|
||||||
|
nodeVersion : process.version,
|
||||||
|
},
|
||||||
|
'**** ENiGMA½ Bulletin Board System Starting Up! ****'
|
||||||
|
);
|
||||||
|
|
||||||
process.on('SIGINT', shutdownSystem);
|
process.on('SIGINT', shutdownSystem);
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ function Client(/*input, output*/) {
|
||||||
|
|
||||||
this.user = new User();
|
this.user = new User();
|
||||||
this.currentTheme = { info : { name : 'N/A', description : 'None' } };
|
this.currentTheme = { info : { name : 'N/A', description : 'None' } };
|
||||||
this.lastKeyPressMs = Date.now();
|
this.lastActivityTime = Date.now();
|
||||||
this.menuStack = new MenuStack(this);
|
this.menuStack = new MenuStack(this);
|
||||||
this.acs = new ACS( { client : this, user : this.user } );
|
this.acs = new ACS( { client : this, user : this.user } );
|
||||||
this.mciCache = {};
|
this.mciCache = {};
|
||||||
|
@ -96,7 +96,7 @@ function Client(/*input, output*/) {
|
||||||
|
|
||||||
Object.defineProperty(this, 'node', {
|
Object.defineProperty(this, 'node', {
|
||||||
get : function() {
|
get : function() {
|
||||||
return self.session.id + 1;
|
return self.session.id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -107,11 +107,13 @@ function Client(/*input, output*/) {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setTemporaryDirectDataHandler = function(handler) {
|
this.setTemporaryDirectDataHandler = function(handler) {
|
||||||
|
this.dataPassthrough = true; // let implementations do with what they will here
|
||||||
this.input.removeAllListeners('data');
|
this.input.removeAllListeners('data');
|
||||||
this.input.on('data', handler);
|
this.input.on('data', handler);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.restoreDataHandler = function() {
|
this.restoreDataHandler = function() {
|
||||||
|
this.dataPassthrough = false;
|
||||||
this.input.removeAllListeners('data');
|
this.input.removeAllListeners('data');
|
||||||
this.input.on('data', this.dataHandler);
|
this.input.on('data', this.dataHandler);
|
||||||
};
|
};
|
||||||
|
@ -406,7 +408,7 @@ function Client(/*input, output*/) {
|
||||||
self.log.trace( { key : key, ch : escape(ch) }, 'User keyboard input'); // jshint ignore:line
|
self.log.trace( { key : key, ch : escape(ch) }, 'User keyboard input'); // jshint ignore:line
|
||||||
}
|
}
|
||||||
|
|
||||||
self.lastKeyPressMs = Date.now();
|
self.lastActivityTime = Date.now();
|
||||||
|
|
||||||
if(!self.ignoreInput) {
|
if(!self.ignoreInput) {
|
||||||
self.emit('key press', ch, key);
|
self.emit('key press', ch, key);
|
||||||
|
@ -438,7 +440,7 @@ Client.prototype.startIdleMonitor = function() {
|
||||||
this.stopIdleMonitor();
|
this.stopIdleMonitor();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastKeyPressMs = Date.now();
|
this.lastActivityTime = Date.now();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Every 1m, check for idle.
|
// Every 1m, check for idle.
|
||||||
|
@ -476,7 +478,7 @@ Client.prototype.startIdleMonitor = function() {
|
||||||
// use override value if set
|
// use override value if set
|
||||||
idleLogoutSeconds = this.idleLogoutSecondsOverride || idleLogoutSeconds;
|
idleLogoutSeconds = this.idleLogoutSecondsOverride || idleLogoutSeconds;
|
||||||
|
|
||||||
if(idleLogoutSeconds > 0 && (nowMs - this.lastKeyPressMs >= (idleLogoutSeconds * 1000))) {
|
if(idleLogoutSeconds > 0 && (nowMs - this.lastActivityTime >= (idleLogoutSeconds * 1000))) {
|
||||||
this.emit('idle timeout');
|
this.emit('idle timeout');
|
||||||
}
|
}
|
||||||
}, 1000 * 60);
|
}, 1000 * 60);
|
||||||
|
@ -489,6 +491,10 @@ Client.prototype.stopIdleMonitor = function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Client.prototype.explicitActivityTimeUpdate = function() {
|
||||||
|
this.lastActivityTime = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
Client.prototype.overrideIdleLogoutSeconds = function(seconds) {
|
Client.prototype.overrideIdleLogoutSeconds = function(seconds) {
|
||||||
this.idleLogoutSecondsOverride = seconds;
|
this.idleLogoutSecondsOverride = seconds;
|
||||||
};
|
};
|
||||||
|
|
|
@ -61,27 +61,27 @@ function getActiveConnectionList(authUsersOnly) {
|
||||||
|
|
||||||
function addNewClient(client, clientSock) {
|
function addNewClient(client, clientSock) {
|
||||||
//
|
//
|
||||||
// Assign ID/client ID to next lowest & available #
|
// Find a node ID "slot"
|
||||||
//
|
//
|
||||||
let id = 0;
|
let nodeId;
|
||||||
for(let i = 0; i < clientConnections.length; ++i) {
|
for (nodeId = 1; nodeId < Number.MAX_SAFE_INTEGER; ++nodeId) {
|
||||||
if(clientConnections[i].id > id) {
|
const existing = clientConnections.find(client => nodeId === client.node);
|
||||||
break;
|
if (!existing) {
|
||||||
|
break; // available slot
|
||||||
}
|
}
|
||||||
id++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client.session.id = id;
|
client.session.id = nodeId;
|
||||||
const remoteAddress = client.remoteAddress = clientSock.remoteAddress;
|
const remoteAddress = client.remoteAddress = clientSock.remoteAddress;
|
||||||
// create a unique identifier one-time ID for this session
|
// create a unique identifier one-time ID for this session
|
||||||
client.session.uniqueId = new hashids('ENiGMA½ClientSession').encode([ id, moment().valueOf() ]);
|
client.session.uniqueId = new hashids('ENiGMA½ClientSession').encode([ nodeId, moment().valueOf() ]);
|
||||||
|
|
||||||
clientConnections.push(client);
|
clientConnections.push(client);
|
||||||
clientConnections.sort( (c1, c2) => c1.session.id - c2.session.id);
|
clientConnections.sort( (c1, c2) => c1.session.id - c2.session.id);
|
||||||
|
|
||||||
// Create a client specific logger
|
// Create a client specific logger
|
||||||
// Note that this will be updated @ login with additional information
|
// Note that this will be updated @ login with additional information
|
||||||
client.log = logger.log.child( { clientId : id, sessionId : client.session.uniqueId } );
|
client.log = logger.log.child( { nodeId, sessionId : client.session.uniqueId } );
|
||||||
|
|
||||||
const connInfo = {
|
const connInfo = {
|
||||||
remoteAddress : remoteAddress,
|
remoteAddress : remoteAddress,
|
||||||
|
@ -101,7 +101,7 @@ function addNewClient(client, clientSock) {
|
||||||
{ client : client, connectionCount : clientConnections.length }
|
{ client : client, connectionCount : clientConnections.length }
|
||||||
);
|
);
|
||||||
|
|
||||||
return id;
|
return nodeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeClient(client) {
|
function removeClient(client) {
|
||||||
|
@ -114,7 +114,7 @@ function removeClient(client) {
|
||||||
logger.log.info(
|
logger.log.info(
|
||||||
{
|
{
|
||||||
connectionCount : clientConnections.length,
|
connectionCount : clientConnections.length,
|
||||||
clientId : client.session.id
|
nodeId : client.node,
|
||||||
},
|
},
|
||||||
'Client disconnected'
|
'Client disconnected'
|
||||||
);
|
);
|
||||||
|
|
|
@ -678,7 +678,7 @@ function getDefaultConfig() {
|
||||||
},
|
},
|
||||||
decompress : {
|
decompress : {
|
||||||
cmd : 'unzip',
|
cmd : 'unzip',
|
||||||
args : [ '{archivePath}', '-d', '{extractPath}' ],
|
args : [ '-n', '{archivePath}', '-d', '{extractPath}' ],
|
||||||
},
|
},
|
||||||
list : {
|
list : {
|
||||||
cmd : 'unzip',
|
cmd : 'unzip',
|
||||||
|
@ -688,7 +688,7 @@ function getDefaultConfig() {
|
||||||
},
|
},
|
||||||
extract : {
|
extract : {
|
||||||
cmd : 'unzip',
|
cmd : 'unzip',
|
||||||
args : [ '{archivePath}', '{fileList}', '-d', '{extractPath}' ],
|
args : [ '-n', '{archivePath}', '{fileList}', '-d', '{extractPath}' ],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -865,8 +865,7 @@ function getDefaultConfig() {
|
||||||
recvArgs : [
|
recvArgs : [
|
||||||
'--zmodem', '--binary', '--restricted', '--keep-uppercase', // dumps to CWD which is set to {uploadDir}
|
'--zmodem', '--binary', '--restricted', '--keep-uppercase', // dumps to CWD which is set to {uploadDir}
|
||||||
],
|
],
|
||||||
// :TODO: can we not just use --escape ?
|
processIACs : true, // escape/de-escape IACs (0xff)
|
||||||
escapeTelnet : true, // set to true to escape Telnet codes such as IAC
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
|
||||||
|
|
||||||
|
const CP437UnicodeTable = [
|
||||||
|
'\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006',
|
||||||
|
'\u0007', '\u0008', '\u0009', '\u000A', '\u000B', '\u000C', '\u000D',
|
||||||
|
'\u000E', '\u000F', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014',
|
||||||
|
'\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001A', '\u001B',
|
||||||
|
'\u001C', '\u001D', '\u001E', '\u001F', '\u0020', '\u0021', '\u0022',
|
||||||
|
'\u0023', '\u0024', '\u0025', '\u0026', '\u0027', '\u0028', '\u0029',
|
||||||
|
'\u002A', '\u002B', '\u002C', '\u002D', '\u002E', '\u002F', '\u0030',
|
||||||
|
'\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037',
|
||||||
|
'\u0038', '\u0039', '\u003A', '\u003B', '\u003C', '\u003D', '\u003E',
|
||||||
|
'\u003F', '\u0040', '\u0041', '\u0042', '\u0043', '\u0044', '\u0045',
|
||||||
|
'\u0046', '\u0047', '\u0048', '\u0049', '\u004A', '\u004B', '\u004C',
|
||||||
|
'\u004D', '\u004E', '\u004F', '\u0050', '\u0051', '\u0052', '\u0053',
|
||||||
|
'\u0054', '\u0055', '\u0056', '\u0057', '\u0058', '\u0059', '\u005A',
|
||||||
|
'\u005B', '\u005C', '\u005D', '\u005E', '\u005F', '\u0060', '\u0061',
|
||||||
|
'\u0062', '\u0063', '\u0064', '\u0065', '\u0066', '\u0067', '\u0068',
|
||||||
|
'\u0069', '\u006A', '\u006B', '\u006C', '\u006D', '\u006E', '\u006F',
|
||||||
|
'\u0070', '\u0071', '\u0072', '\u0073', '\u0074', '\u0075', '\u0076',
|
||||||
|
'\u0077', '\u0078', '\u0079', '\u007A', '\u007B', '\u007C', '\u007D',
|
||||||
|
'\u007E', '\u007F', '\u00C7', '\u00FC', '\u00E9', '\u00E2', '\u00E4',
|
||||||
|
'\u00E0', '\u00E5', '\u00E7', '\u00EA', '\u00EB', '\u00E8', '\u00EF',
|
||||||
|
'\u00EE', '\u00EC', '\u00C4', '\u00C5', '\u00C9', '\u00E6', '\u00C6',
|
||||||
|
'\u00F4', '\u00F6', '\u00F2', '\u00FB', '\u00F9', '\u00FF', '\u00D6',
|
||||||
|
'\u00DC', '\u00A2', '\u00A3', '\u00A5', '\u20A7', '\u0192', '\u00E1',
|
||||||
|
'\u00ED', '\u00F3', '\u00FA', '\u00F1', '\u00D1', '\u00AA', '\u00BA',
|
||||||
|
'\u00BF', '\u2310', '\u00AC', '\u00BD', '\u00BC', '\u00A1', '\u00AB',
|
||||||
|
'\u00BB', '\u2591', '\u2592', '\u2593', '\u2502', '\u2524', '\u2561',
|
||||||
|
'\u2562', '\u2556', '\u2555', '\u2563', '\u2551', '\u2557', '\u255D',
|
||||||
|
'\u255C', '\u255B', '\u2510', '\u2514', '\u2534', '\u252C', '\u251C',
|
||||||
|
'\u2500', '\u253C', '\u255E', '\u255F', '\u255A', '\u2554', '\u2569',
|
||||||
|
'\u2566', '\u2560', '\u2550', '\u256C', '\u2567', '\u2568', '\u2564',
|
||||||
|
'\u2565', '\u2559', '\u2558', '\u2552', '\u2553', '\u256B', '\u256A',
|
||||||
|
'\u2518', '\u250C', '\u2588', '\u2584', '\u258C', '\u2590', '\u2580',
|
||||||
|
'\u03B1', '\u00DF', '\u0393', '\u03C0', '\u03A3', '\u03C3', '\u00B5',
|
||||||
|
'\u03C4', '\u03A6', '\u0398', '\u03A9', '\u03B4', '\u221E', '\u03C6',
|
||||||
|
'\u03B5', '\u2229', '\u2261', '\u00B1', '\u2265', '\u2264', '\u2320',
|
||||||
|
'\u2321', '\u00F7', '\u2248', '\u00B0', '\u2219', '\u00B7', '\u221A',
|
||||||
|
'\u207F', '\u00B2', '\u25A0', '\u00A0'
|
||||||
|
];
|
||||||
|
|
||||||
|
const NonCP437EncodableRegExp = /[^\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000A\u000B\u000C\u000D\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F\u0020\u0021\u0022\u0023\u0024\u0025\u0026\u0027\u0028\u0029\u002A\u002B\u002C\u002D\u002E\u002F\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039\u003A\u003B\u003C\u003D\u003E\u003F\u0040\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004A\u004B\u004C\u004D\u004E\u004F\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0057\u0058\u0059\u005A\u005B\u005C\u005D\u005E\u005F\u0060\u0061\u0062\u0063\u0064\u0065\u0066\u0067\u0068\u0069\u006A\u006B\u006C\u006D\u006E\u006F\u0070\u0071\u0072\u0073\u0074\u0075\u0076\u0077\u0078\u0079\u007A\u007B\u007C\u007D\u007E\u007F\u00C7\u00FC\u00E9\u00E2\u00E4\u00E0\u00E5\u00E7\u00EA\u00EB\u00E8\u00EF\u00EE\u00EC\u00C4\u00C5\u00C9\u00E6\u00C6\u00F4\u00F6\u00F2\u00FB\u00F9\u00FF\u00D6\u00DC\u00A2\u00A3\u00A5\u20A7\u0192\u00E1\u00ED\u00F3\u00FA\u00F1\u00D1\u00AA\u00BA\u00BF\u2310\u00AC\u00BD\u00BC\u00A1\u00AB\u00BB\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255D\u255C\u255B\u2510\u2514\u2534\u252C\u251C\u2500\u253C\u255E\u255F\u255A\u2554\u2569\u2566\u2560\u2550\u256C\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256B\u256A\u2518\u250C\u2588\u2584\u258C\u2590\u2580\u03B1\u00DF\u0393\u03C0\u03A3\u03C3\u00B5\u03C4\u03A6\u0398\u03A9\u03B4\u221E\u03C6\u03B5\u2229\u2261\u00B1\u2265\u2264\u2320\u2321\u00F7\u2248\u00B0\u2219\u00B7\u221A\u207F\u00B2\u25A0\u00A0]/;
|
||||||
|
const isCP437Encodable = (s) => {
|
||||||
|
if (!s.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !NonCP437EncodableRegExp.test(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
CP437UnicodeTable,
|
||||||
|
isCP437Encodable,
|
||||||
|
}
|
|
@ -16,6 +16,10 @@ function trackDoorRunBegin(client, doorTag) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function trackDoorRunEnd(trackInfo) {
|
function trackDoorRunEnd(trackInfo) {
|
||||||
|
if (!trackInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { startTime, client, doorTag } = trackInfo;
|
const { startTime, client, doorTag } = trackInfo;
|
||||||
|
|
||||||
const diff = moment.duration(moment().diff(startTime));
|
const diff = moment.duration(moment().diff(startTime));
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
/* jslint node: true */
|
/* jslint node: true */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const FileEntry = require('./file_entry.js');
|
const FileEntry = require('./file_entry');
|
||||||
const UserProps = require('./user_property.js');
|
const UserProps = require('./user_property');
|
||||||
|
const Events = require('./events');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const { partition } = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
module.exports = class DownloadQueue {
|
module.exports = class DownloadQueue {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
|
@ -20,6 +21,10 @@ module.exports = class DownloadQueue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get(client) {
|
||||||
|
return new DownloadQueue(client);
|
||||||
|
}
|
||||||
|
|
||||||
get items() {
|
get items() {
|
||||||
return this.client.user.downloadQueue;
|
return this.client.user.downloadQueue;
|
||||||
}
|
}
|
||||||
|
@ -52,7 +57,7 @@ module.exports = class DownloadQueue {
|
||||||
fileIds = [ fileIds ];
|
fileIds = [ fileIds ];
|
||||||
}
|
}
|
||||||
|
|
||||||
const [ remain, removed ] = partition(this.client.user.downloadQueue, e => ( -1 === fileIds.indexOf(e.fileId) ));
|
const [ remain, removed ] = _.partition(this.client.user.downloadQueue, e => ( -1 === fileIds.indexOf(e.fileId) ));
|
||||||
this.client.user.downloadQueue = remain;
|
this.client.user.downloadQueue = remain;
|
||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
@ -76,4 +81,23 @@ module.exports = class DownloadQueue {
|
||||||
this.client.log.error( { error : e.message, property : prop }, 'Failed parsing download queue property');
|
this.client.log.error( { error : e.message, property : prop }, 'Failed parsing download queue property');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addTemporaryDownload(entry) {
|
||||||
|
this.add(entry, true); // true=systemFile
|
||||||
|
|
||||||
|
// clean up after ourselves when the session ends
|
||||||
|
const thisUniqueId = this.client.session.uniqueId;
|
||||||
|
Events.once(Events.getSystemEvents().ClientDisconnected, evt => {
|
||||||
|
if(thisUniqueId === _.get(evt, 'client.session.uniqueId')) {
|
||||||
|
FileEntry.removeEntry(entry, { removePhysFile : true }, err => {
|
||||||
|
const Log = require('./logger').log;
|
||||||
|
if(err) {
|
||||||
|
Log.warn( { fileId : entry.fileId, path : entry.filePath }, 'Failed removing temporary session download' );
|
||||||
|
} else {
|
||||||
|
Log.debug( { fileId : entry.fileId, path : entry.filePath }, 'Removed temporary session download item' );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -37,6 +37,8 @@ exports.Errors = {
|
||||||
MissingParam : (reason, reasonCode) => new EnigError('Missing paramter(s)', -32008, reason, reasonCode),
|
MissingParam : (reason, reasonCode) => new EnigError('Missing paramter(s)', -32008, reason, reasonCode),
|
||||||
MissingMci : (reason, reasonCode) => new EnigError('Missing required MCI code(s)', -32009, reason, reasonCode),
|
MissingMci : (reason, reasonCode) => new EnigError('Missing required MCI code(s)', -32009, reason, reasonCode),
|
||||||
BadLogin : (reason, reasonCode) => new EnigError('Bad login attempt', -32010, reason, reasonCode),
|
BadLogin : (reason, reasonCode) => new EnigError('Bad login attempt', -32010, reason, reasonCode),
|
||||||
|
UserInterrupt : (reason, reasonCode) => new EnigError('User interrupted', -32011, reason, reasonCode),
|
||||||
|
NothingToDo : (reason, reasonCode) => new EnigError('Nothing to do', -32012, reason, reasonCode),
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.ErrorReasons = {
|
exports.ErrorReasons = {
|
||||||
|
|
|
@ -17,6 +17,7 @@ const StatLog = require('./stat_log.js');
|
||||||
const UserProps = require('./user_property.js');
|
const UserProps = require('./user_property.js');
|
||||||
const SysProps = require('./system_property.js');
|
const SysProps = require('./system_property.js');
|
||||||
const SAUCE = require('./sauce.js');
|
const SAUCE = require('./sauce.js');
|
||||||
|
const { wildcardMatch } = require('./string_util');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
@ -40,6 +41,7 @@ exports.getAreaDefaultStorageDirectory = getAreaDefaultStorageDirectory;
|
||||||
exports.getAreaStorageLocations = getAreaStorageLocations;
|
exports.getAreaStorageLocations = getAreaStorageLocations;
|
||||||
exports.getDefaultFileAreaTag = getDefaultFileAreaTag;
|
exports.getDefaultFileAreaTag = getDefaultFileAreaTag;
|
||||||
exports.getFileAreaByTag = getFileAreaByTag;
|
exports.getFileAreaByTag = getFileAreaByTag;
|
||||||
|
exports.getFileAreasByTagWildcardRule = getFileAreasByTagWildcardRule;
|
||||||
exports.getFileEntryPath = getFileEntryPath;
|
exports.getFileEntryPath = getFileEntryPath;
|
||||||
exports.changeFileAreaWithOptions = changeFileAreaWithOptions;
|
exports.changeFileAreaWithOptions = changeFileAreaWithOptions;
|
||||||
exports.scanFile = scanFile;
|
exports.scanFile = scanFile;
|
||||||
|
@ -143,6 +145,15 @@ function getFileAreaByTag(areaTag) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFileAreasByTagWildcardRule(rule) {
|
||||||
|
const areaTags = Object.keys(Config().fileBase.areas)
|
||||||
|
.filter(areaTag => {
|
||||||
|
return !isInternalArea(areaTag) && wildcardMatch(areaTag, rule);
|
||||||
|
});
|
||||||
|
|
||||||
|
return areaTags.map(areaTag => getFileAreaByTag(areaTag));
|
||||||
|
}
|
||||||
|
|
||||||
function changeFileAreaWithOptions(client, areaTag, options, cb) {
|
function changeFileAreaWithOptions(client, areaTag, options, cb) {
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
|
|
|
@ -5,7 +5,7 @@ const UserProps = require('./user_property.js');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const uuidV4 = require('uuid/v4');
|
const { v4 : UUIDv4 } = require('uuid');
|
||||||
|
|
||||||
module.exports = class FileBaseFilters {
|
module.exports = class FileBaseFilters {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
|
@ -41,7 +41,7 @@ module.exports = class FileBaseFilters {
|
||||||
}
|
}
|
||||||
|
|
||||||
add(filterInfo) {
|
add(filterInfo) {
|
||||||
const filterUuid = uuidV4();
|
const filterUuid = UUIDv4();
|
||||||
|
|
||||||
filterInfo.tags = this.cleanTags(filterInfo.tags);
|
filterInfo.tags = this.cleanTags(filterInfo.tags);
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,6 @@ const FileEntry = require('./file_entry.js');
|
||||||
const FileArea = require('./file_base_area.js');
|
const FileArea = require('./file_base_area.js');
|
||||||
const { renderSubstr } = require('./string_util.js');
|
const { renderSubstr } = require('./string_util.js');
|
||||||
const { Errors } = require('./enig_error.js');
|
const { Errors } = require('./enig_error.js');
|
||||||
const Events = require('./events.js');
|
|
||||||
const Log = require('./logger.js').log;
|
|
||||||
const DownloadQueue = require('./download_queue.js');
|
const DownloadQueue = require('./download_queue.js');
|
||||||
const { exportFileList } = require('./file_base_list_export.js');
|
const { exportFileList } = require('./file_base_list_export.js');
|
||||||
|
|
||||||
|
@ -19,7 +17,7 @@ const fs = require('graceful-fs');
|
||||||
const fse = require('fs-extra');
|
const fse = require('fs-extra');
|
||||||
const paths = require('path');
|
const paths = require('path');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const uuidv4 = require('uuid/v4');
|
const { v4 : UUIDv4 } = require('uuid');
|
||||||
const yazl = require('yazl');
|
const yazl = require('yazl');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -28,7 +26,7 @@ const yazl = require('yazl');
|
||||||
tsFormat - timestamp format (theme 'short')
|
tsFormat - timestamp format (theme 'short')
|
||||||
descWidth - max desc width (45)
|
descWidth - max desc width (45)
|
||||||
progBarChar - progress bar character (▒)
|
progBarChar - progress bar character (▒)
|
||||||
compressThreshold - threshold to kick in comrpession for lists (1.44 MiB)
|
compressThreshold - threshold to kick in compression for lists (1.44 MiB)
|
||||||
templates - object containing:
|
templates - object containing:
|
||||||
header - filename of header template (misc/file_list_header.asc)
|
header - filename of header template (misc/file_list_header.asc)
|
||||||
entry - filename of entry template (misc/file_list_entry.asc)
|
entry - filename of entry template (misc/file_list_entry.asc)
|
||||||
|
@ -190,7 +188,7 @@ exports.getModule = class FileBaseListExport extends MenuModule {
|
||||||
|
|
||||||
const outputFileName = paths.join(
|
const outputFileName = paths.join(
|
||||||
sysTempDownloadDir,
|
sysTempDownloadDir,
|
||||||
`file_list_${uuidv4().substr(-8)}_${moment().format('YYYY-MM-DD')}.txt`
|
`file_list_${UUIDv4().substr(-8)}_${moment().format('YYYY-MM-DD')}.txt`
|
||||||
);
|
);
|
||||||
|
|
||||||
fs.writeFile(outputFileName, listBody, 'utf8', err => {
|
fs.writeFile(outputFileName, listBody, 'utf8', err => {
|
||||||
|
@ -222,28 +220,14 @@ exports.getModule = class FileBaseListExport extends MenuModule {
|
||||||
newEntry.persist(err => {
|
newEntry.persist(err => {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
// queue it!
|
// queue it!
|
||||||
const dlQueue = new DownloadQueue(self.client);
|
DownloadQueue.get(self.client).addTemporaryDownload(newEntry);
|
||||||
dlQueue.add(newEntry, true); // true=systemFile
|
|
||||||
|
|
||||||
// clean up after ourselves when the session ends
|
|
||||||
const thisClientId = self.client.session.id;
|
|
||||||
Events.once(Events.getSystemEvents().ClientDisconnected, evt => {
|
|
||||||
if(thisClientId === _.get(evt, 'client.session.id')) {
|
|
||||||
FileEntry.removeEntry(newEntry, { removePhysFile : true }, err => {
|
|
||||||
if(err) {
|
|
||||||
Log.warn( { fileId : newEntry.fileId, path : outputFileName }, 'Failed removing temporary session download' );
|
|
||||||
} else {
|
|
||||||
Log.debug( { fileId : newEntry.fileId, path : outputFileName }, 'Removed temporary session download item' );
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function done(callback) {
|
function done(callback) {
|
||||||
// re-enable idle monitor
|
// re-enable idle monitor
|
||||||
|
// :TODO: this should probably be moved down below at the end of the full waterfall
|
||||||
self.client.startIdleMonitor();
|
self.client.startIdleMonitor();
|
||||||
|
|
||||||
updateStatus('Exported list has been added to your download queue');
|
updateStatus('Exported list has been added to your download queue');
|
||||||
|
|
|
@ -364,6 +364,16 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
||||||
const external = this.protocolConfig.external;
|
const external = this.protocolConfig.external;
|
||||||
const cmd = external[`${this.direction}Cmd`];
|
const cmd = external[`${this.direction}Cmd`];
|
||||||
|
|
||||||
|
// support for handlers that need IACs taken care of over Telnet/etc.
|
||||||
|
const processIACs =
|
||||||
|
external.processIACs ||
|
||||||
|
external.escapeTelnet; // deprecated name
|
||||||
|
|
||||||
|
// :TODO: we should only do this when over Telnet (or derived, such as WebSockets)?
|
||||||
|
|
||||||
|
const IAC = Buffer.from([255]);
|
||||||
|
const EscapedIAC = Buffer.from([255, 255]);
|
||||||
|
|
||||||
this.client.log.debug(
|
this.client.log.debug(
|
||||||
{ cmd : cmd, args : args, tempDir : this.recvDirectory, direction : this.direction },
|
{ cmd : cmd, args : args, tempDir : this.recvDirectory, direction : this.direction },
|
||||||
'Executing external protocol'
|
'Executing external protocol'
|
||||||
|
@ -378,21 +388,68 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
||||||
|
|
||||||
const externalProc = pty.spawn(cmd, args, spawnOpts);
|
const externalProc = pty.spawn(cmd, args, spawnOpts);
|
||||||
|
|
||||||
|
let dataHits = 0;
|
||||||
|
const updateActivity = () => {
|
||||||
|
if (0 === (dataHits++ % 4)) {
|
||||||
|
this.client.explicitActivityTimeUpdate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.client.setTemporaryDirectDataHandler(data => {
|
this.client.setTemporaryDirectDataHandler(data => {
|
||||||
|
updateActivity();
|
||||||
|
|
||||||
// needed for things like sz/rz
|
// needed for things like sz/rz
|
||||||
if(external.escapeTelnet) {
|
if(processIACs) {
|
||||||
const tmp = data.toString('binary').replace(/\xff{2}/g, '\xff'); // de-escape
|
let iacPos = data.indexOf(EscapedIAC);
|
||||||
externalProc.write(Buffer.from(tmp, 'binary'));
|
if (-1 === iacPos) {
|
||||||
|
return externalProc.write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// at least one double (escaped) IAC
|
||||||
|
let lastPos = 0;
|
||||||
|
while (iacPos > -1) {
|
||||||
|
let rem = iacPos - lastPos;
|
||||||
|
if (rem >= 0) {
|
||||||
|
externalProc.write(data.slice(lastPos, iacPos + 1));
|
||||||
|
}
|
||||||
|
lastPos = iacPos + 2;
|
||||||
|
iacPos = data.indexOf(EscapedIAC, lastPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastPos < data.length) {
|
||||||
|
externalProc.write(data.slice(lastPos));
|
||||||
|
}
|
||||||
|
// const tmp = data.toString('binary').replace(/\xff{2}/g, '\xff'); // de-escape
|
||||||
|
// externalProc.write(Buffer.from(tmp, 'binary'));
|
||||||
} else {
|
} else {
|
||||||
externalProc.write(data);
|
externalProc.write(data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
externalProc.on('data', data => {
|
externalProc.on('data', data => {
|
||||||
|
updateActivity();
|
||||||
|
|
||||||
// needed for things like sz/rz
|
// needed for things like sz/rz
|
||||||
if(external.escapeTelnet) {
|
if(processIACs) {
|
||||||
const tmp = data.toString('binary').replace(/\xff/g, '\xff\xff'); // escape
|
let iacPos = data.indexOf(IAC);
|
||||||
this.client.term.rawWrite(Buffer.from(tmp, 'binary'));
|
if (-1 === iacPos) {
|
||||||
|
return this.client.term.rawWrite(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has at least a single IAC
|
||||||
|
let lastPos = 0;
|
||||||
|
while (iacPos !== -1) {
|
||||||
|
if (iacPos - lastPos > 0) {
|
||||||
|
this.client.term.rawWrite(data.slice(lastPos, iacPos));
|
||||||
|
}
|
||||||
|
this.client.term.rawWrite(EscapedIAC);
|
||||||
|
lastPos = iacPos + 1;
|
||||||
|
iacPos = data.indexOf(IAC, lastPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastPos < data.length) {
|
||||||
|
this.client.term.rawWrite(data.slice(lastPos));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.client.term.rawWrite(data);
|
this.client.term.rawWrite(data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,6 +165,74 @@ exports.PacketHeader = PacketHeader;
|
||||||
// * Writeup on differences between type 2, 2.2, and 2+:
|
// * Writeup on differences between type 2, 2.2, and 2+:
|
||||||
// http://walon.org/pub/fidonet/FTSC-nodelists-etc./pkt-types.txt
|
// http://walon.org/pub/fidonet/FTSC-nodelists-etc./pkt-types.txt
|
||||||
//
|
//
|
||||||
|
const PacketHeaderParser = new Parser()
|
||||||
|
.uint16le('origNode')
|
||||||
|
.uint16le('destNode')
|
||||||
|
.uint16le('year')
|
||||||
|
.uint16le('month')
|
||||||
|
.uint16le('day')
|
||||||
|
.uint16le('hour')
|
||||||
|
.uint16le('minute')
|
||||||
|
.uint16le('second')
|
||||||
|
.uint16le('baud')
|
||||||
|
.uint16le('packetType')
|
||||||
|
.uint16le('origNet')
|
||||||
|
.uint16le('destNet')
|
||||||
|
.int8('prodCodeLo')
|
||||||
|
.int8('prodRevLo') // aka serialNo
|
||||||
|
.buffer('password', { length : 8 }) // can't use string; need CP437 - see https://github.com/keichi/binary-parser/issues/33
|
||||||
|
.uint16le('origZone')
|
||||||
|
.uint16le('destZone')
|
||||||
|
//
|
||||||
|
// The following is "filler" in FTS-0001, specifics in
|
||||||
|
// FSC-0045 and FSC-0048
|
||||||
|
//
|
||||||
|
.uint16le('auxNet')
|
||||||
|
.uint16le('capWordValidate')
|
||||||
|
.int8('prodCodeHi')
|
||||||
|
.int8('prodRevHi')
|
||||||
|
.uint16le('capWord')
|
||||||
|
.uint16le('origZone2')
|
||||||
|
.uint16le('destZone2')
|
||||||
|
.uint16le('origPoint')
|
||||||
|
.uint16le('destPoint')
|
||||||
|
.uint32le('prodData');
|
||||||
|
|
||||||
|
const MessageHeaderParser = new Parser()
|
||||||
|
.uint16le('messageType')
|
||||||
|
.uint16le('ftn_msg_orig_node')
|
||||||
|
.uint16le('ftn_msg_dest_node')
|
||||||
|
.uint16le('ftn_msg_orig_net')
|
||||||
|
.uint16le('ftn_msg_dest_net')
|
||||||
|
.uint16le('ftn_attr_flags')
|
||||||
|
.uint16le('ftn_cost')
|
||||||
|
//
|
||||||
|
// It would be nice to just string() these, but we want CP437 which requires
|
||||||
|
// iconv. Another option would be to use a formatter, but until issue 33
|
||||||
|
// (https://github.com/keichi/binary-parser/issues/33) is fixed, this is cumbersome.
|
||||||
|
//
|
||||||
|
.array('modDateTime', {
|
||||||
|
type : 'uint8',
|
||||||
|
length : 20, // FTS-0001.016: 20 bytes
|
||||||
|
})
|
||||||
|
.array('toUserName', {
|
||||||
|
type : 'uint8',
|
||||||
|
// :TODO: array needs some soft of 'limit' field
|
||||||
|
readUntil : b => 0x00 === b,
|
||||||
|
})
|
||||||
|
.array('fromUserName', {
|
||||||
|
type : 'uint8',
|
||||||
|
readUntil : b => 0x00 === b,
|
||||||
|
})
|
||||||
|
.array('subject', {
|
||||||
|
type : 'uint8',
|
||||||
|
readUntil : b => 0x00 === b,
|
||||||
|
})
|
||||||
|
.array('message', {
|
||||||
|
type : 'uint8',
|
||||||
|
readUntil : b => 0x00 === b,
|
||||||
|
});
|
||||||
|
|
||||||
function Packet(options) {
|
function Packet(options) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
@ -175,39 +243,7 @@ function Packet(options) {
|
||||||
|
|
||||||
let packetHeader;
|
let packetHeader;
|
||||||
try {
|
try {
|
||||||
packetHeader = new Parser()
|
packetHeader = PacketHeaderParser.parse(packetBuffer);
|
||||||
.uint16le('origNode')
|
|
||||||
.uint16le('destNode')
|
|
||||||
.uint16le('year')
|
|
||||||
.uint16le('month')
|
|
||||||
.uint16le('day')
|
|
||||||
.uint16le('hour')
|
|
||||||
.uint16le('minute')
|
|
||||||
.uint16le('second')
|
|
||||||
.uint16le('baud')
|
|
||||||
.uint16le('packetType')
|
|
||||||
.uint16le('origNet')
|
|
||||||
.uint16le('destNet')
|
|
||||||
.int8('prodCodeLo')
|
|
||||||
.int8('prodRevLo') // aka serialNo
|
|
||||||
.buffer('password', { length : 8 }) // can't use string; need CP437 - see https://github.com/keichi/binary-parser/issues/33
|
|
||||||
.uint16le('origZone')
|
|
||||||
.uint16le('destZone')
|
|
||||||
//
|
|
||||||
// The following is "filler" in FTS-0001, specifics in
|
|
||||||
// FSC-0045 and FSC-0048
|
|
||||||
//
|
|
||||||
.uint16le('auxNet')
|
|
||||||
.uint16le('capWordValidate')
|
|
||||||
.int8('prodCodeHi')
|
|
||||||
.int8('prodRevHi')
|
|
||||||
.uint16le('capWord')
|
|
||||||
.uint16le('origZone2')
|
|
||||||
.uint16le('destZone2')
|
|
||||||
.uint16le('origPoint')
|
|
||||||
.uint16le('destPoint')
|
|
||||||
.uint32le('prodData')
|
|
||||||
.parse(packetBuffer);
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return Errors.Invalid(`Unable to parse FTN packet header: ${e.message}`);
|
return Errors.Invalid(`Unable to parse FTN packet header: ${e.message}`);
|
||||||
}
|
}
|
||||||
|
@ -544,41 +580,7 @@ function Packet(options) {
|
||||||
|
|
||||||
let msgData;
|
let msgData;
|
||||||
try {
|
try {
|
||||||
msgData = new Parser()
|
msgData = MessageHeaderParser.parse(packetBuffer);
|
||||||
.uint16le('messageType')
|
|
||||||
.uint16le('ftn_msg_orig_node')
|
|
||||||
.uint16le('ftn_msg_dest_node')
|
|
||||||
.uint16le('ftn_msg_orig_net')
|
|
||||||
.uint16le('ftn_msg_dest_net')
|
|
||||||
.uint16le('ftn_attr_flags')
|
|
||||||
.uint16le('ftn_cost')
|
|
||||||
//
|
|
||||||
// It would be nice to just string() these, but we want CP437 which requires
|
|
||||||
// iconv. Another option would be to use a formatter, but until issue 33
|
|
||||||
// (https://github.com/keichi/binary-parser/issues/33) is fixed, this is cumbersome.
|
|
||||||
//
|
|
||||||
.array('modDateTime', {
|
|
||||||
type : 'uint8',
|
|
||||||
length : 20, // FTS-0001.016: 20 bytes
|
|
||||||
})
|
|
||||||
.array('toUserName', {
|
|
||||||
type : 'uint8',
|
|
||||||
// :TODO: array needs some soft of 'limit' field
|
|
||||||
readUntil : b => 0x00 === b,
|
|
||||||
})
|
|
||||||
.array('fromUserName', {
|
|
||||||
type : 'uint8',
|
|
||||||
readUntil : b => 0x00 === b,
|
|
||||||
})
|
|
||||||
.array('subject', {
|
|
||||||
type : 'uint8',
|
|
||||||
readUntil : b => 0x00 === b,
|
|
||||||
})
|
|
||||||
.array('message', {
|
|
||||||
type : 'uint8',
|
|
||||||
readUntil : b => 0x00 === b,
|
|
||||||
})
|
|
||||||
.parse(packetBuffer);
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return cb(Errors.Invalid(`Failed to parse FTN message header: ${e.message}`));
|
return cb(Errors.Invalid(`Failed to parse FTN message header: ${e.message}`));
|
||||||
}
|
}
|
||||||
|
|
|
@ -375,26 +375,28 @@ function getCharacterSetIdentifierByEncoding(encodingName) {
|
||||||
return value ? `${value[0]} ${value[1]}` : encodingName.toUpperCase();
|
return value ? `${value[0]} ${value[1]}` : encodingName.toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// http://ftsc.org/docs/fts-5003.001
|
||||||
|
// http://www.unicode.org/L2/L1999/99325-N.htm
|
||||||
function getEncodingFromCharacterSetIdentifier(chrs) {
|
function getEncodingFromCharacterSetIdentifier(chrs) {
|
||||||
const ident = chrs.split(' ')[0].toUpperCase();
|
const ident = chrs.split(' ')[0].toUpperCase();
|
||||||
|
|
||||||
// :TODO: fill in the rest!!!
|
// :TODO: fill in the rest!!!
|
||||||
return {
|
return {
|
||||||
// level 1
|
// level 1
|
||||||
'ASCII' : 'iso-646-1',
|
'ASCII' : 'ascii', // ISO-646-1
|
||||||
'DUTCH' : 'iso-646',
|
'DUTCH' : 'ascii', // ISO-646
|
||||||
'FINNISH' : 'iso-646-10',
|
'FINNISH' : 'ascii', // ISO-646-10
|
||||||
'FRENCH' : 'iso-646',
|
'FRENCH' : 'ascii', // ISO-646
|
||||||
'CANADIAN' : 'iso-646',
|
'CANADIAN' : 'ascii', // ISO-646
|
||||||
'GERMAN' : 'iso-646',
|
'GERMAN' : 'ascii', // ISO-646
|
||||||
'ITALIAN' : 'iso-646',
|
'ITALIAN' : 'ascii', // ISO-646
|
||||||
'NORWEIG' : 'iso-646',
|
'NORWEIG' : 'ascii', // ISO-646
|
||||||
'PORTU' : 'iso-646',
|
'PORTU' : 'ascii', // ISO-646
|
||||||
'SPANISH' : 'iso-656',
|
'SPANISH' : 'iso-656',
|
||||||
'SWEDISH' : 'iso-646-10',
|
'SWEDISH' : 'ascii', // ISO-646-10
|
||||||
'SWISS' : 'iso-646',
|
'SWISS' : 'ascii', // ISO-646
|
||||||
'UK' : 'iso-646',
|
'UK' : 'ascii', // ISO-646
|
||||||
'ISO-10' : 'iso-646-10',
|
'ISO-10' : 'ascii', // ISO-646-10
|
||||||
|
|
||||||
// level 2
|
// level 2
|
||||||
'CP437' : 'cp437',
|
'CP437' : 'cp437',
|
||||||
|
|
|
@ -72,12 +72,12 @@ module.exports = class LoginServerModule extends ServerModule {
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('error', err => {
|
client.on('error', err => {
|
||||||
logger.log.info({ clientId : client.session.id, error : err.message }, 'Connection error');
|
logger.log.info({ nodeId : client.node, error : err.message }, 'Connection error');
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('close', err => {
|
client.on('close', err => {
|
||||||
const logFunc = err ? logger.log.info : logger.log.debug;
|
const logFunc = err ? logger.log.info : logger.log.debug;
|
||||||
logFunc( { clientId : client.session.id }, 'Connection closed');
|
logFunc( { nodeId : client.node }, 'Connection closed');
|
||||||
|
|
||||||
clientConns.removeClient(client);
|
clientConns.removeClient(client);
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
const { Errors } = require('./enig_error');
|
||||||
|
|
||||||
|
//
|
||||||
|
// Utils for dealing with Microsoft Binary Format (MBF) used
|
||||||
|
// in various BASIC languages, etc.
|
||||||
|
//
|
||||||
|
// - https://en.wikipedia.org/wiki/Microsoft_Binary_Format
|
||||||
|
// - https://stackoverflow.com/questions/2268191/how-to-convert-from-ieee-python-float-to-microsoft-basic-float
|
||||||
|
//
|
||||||
|
|
||||||
|
// Number to 32bit MBF
|
||||||
|
numToMbf32 = (v) => {
|
||||||
|
const mbf = Buffer.alloc(4);
|
||||||
|
|
||||||
|
if (0 === v) {
|
||||||
|
return mbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ieee = Buffer.alloc(4);
|
||||||
|
ieee.writeFloatLE(v, 0);
|
||||||
|
|
||||||
|
const sign = ieee[3] & 0x80;
|
||||||
|
let exp = (ieee[3] << 1) | (ieee[2] >> 7);
|
||||||
|
|
||||||
|
if (exp === 0xfe) {
|
||||||
|
throw Errors.Invalid(`${v} cannot be converted to mbf`);
|
||||||
|
}
|
||||||
|
|
||||||
|
exp += 2;
|
||||||
|
|
||||||
|
mbf[3] = exp;
|
||||||
|
mbf[2] = sign | (ieee[2] & 0x7f);
|
||||||
|
mbf[1] = ieee[1];
|
||||||
|
mbf[0] = ieee[0];
|
||||||
|
|
||||||
|
return mbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
mbf32ToNum = (mbf) => {
|
||||||
|
if (0 === mbf[3]) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ieee = Buffer.alloc(4);
|
||||||
|
const sign = mbf[2] & 0x80;
|
||||||
|
const exp = mbf[3] - 2;
|
||||||
|
|
||||||
|
ieee[3] = sign | (exp >> 1);
|
||||||
|
ieee[2] = (exp << 7) | (mbf[2] & 0x7f);
|
||||||
|
ieee[1] = mbf[1];
|
||||||
|
ieee[0] = mbf[0];
|
||||||
|
|
||||||
|
return ieee.readFloatLE(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
numToMbf32,
|
||||||
|
mbf32ToNum,
|
||||||
|
}
|
|
@ -11,6 +11,9 @@ const {
|
||||||
sanitizeString,
|
sanitizeString,
|
||||||
getISOTimestampString } = require('./database.js');
|
getISOTimestampString } = require('./database.js');
|
||||||
|
|
||||||
|
const { isCP437Encodable } = require('./cp437util');
|
||||||
|
const { containsNonLatinCodepoints } = require('./string_util');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isAnsi, isFormattedLine,
|
isAnsi, isFormattedLine,
|
||||||
splitTextAtTerms,
|
splitTextAtTerms,
|
||||||
|
@ -49,7 +52,8 @@ const SYSTEM_META_NAMES = {
|
||||||
const ADDRESS_FLAVOR = {
|
const ADDRESS_FLAVOR = {
|
||||||
Local : 'local', // local / non-remote addressing
|
Local : 'local', // local / non-remote addressing
|
||||||
FTN : 'ftn', // FTN style
|
FTN : 'ftn', // FTN style
|
||||||
Email : 'email',
|
Email : 'email', // From email
|
||||||
|
QWK : 'qwk', // QWK packet
|
||||||
};
|
};
|
||||||
|
|
||||||
const STATE_FLAGS0 = {
|
const STATE_FLAGS0 = {
|
||||||
|
@ -87,6 +91,13 @@ const FTN_PROPERTY_NAMES = {
|
||||||
FtnSeenBy : 'ftn_seen_by', // http://ftsc.org/docs/fts-0004.001
|
FtnSeenBy : 'ftn_seen_by', // http://ftsc.org/docs/fts-0004.001
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const QWKPropertyNames = {
|
||||||
|
MessageNumber : 'qwk_msg_num',
|
||||||
|
MessageStatus : 'qwk_msg_status', // See http://wiki.synchro.net/ref:qwk for a decent list
|
||||||
|
ConferenceNumber : 'qwk_conf_num',
|
||||||
|
InReplyToNum : 'qwk_in_reply_to_num', // note that we prefer the 'InReplyToMsgId' kludge if available
|
||||||
|
};
|
||||||
|
|
||||||
// :TODO: this is a ugly hack due to bad variable names - clean it up & just _.camelCase(k)!
|
// :TODO: this is a ugly hack due to bad variable names - clean it up & just _.camelCase(k)!
|
||||||
const MESSAGE_ROW_MAP = {
|
const MESSAGE_ROW_MAP = {
|
||||||
reply_to_message_id : 'replyToMsgId',
|
reply_to_message_id : 'replyToMsgId',
|
||||||
|
@ -96,15 +107,23 @@ const MESSAGE_ROW_MAP = {
|
||||||
module.exports = class Message {
|
module.exports = class Message {
|
||||||
constructor(
|
constructor(
|
||||||
{
|
{
|
||||||
messageId = 0, areaTag = Message.WellKnownAreaTags.Invalid, uuid, replyToMsgId = 0,
|
messageId = 0,
|
||||||
toUserName = '', fromUserName = '', subject = '', message = '', modTimestamp = moment(),
|
areaTag = Message.WellKnownAreaTags.Invalid,
|
||||||
meta, hashTags = [],
|
uuid,
|
||||||
|
replyToMsgId = 0,
|
||||||
|
toUserName = '',
|
||||||
|
fromUserName = '',
|
||||||
|
subject = '',
|
||||||
|
message = '',
|
||||||
|
modTimestamp = moment(),
|
||||||
|
meta,
|
||||||
|
hashTags = [],
|
||||||
} = { }
|
} = { }
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.messageId = messageId;
|
this.messageId = messageId;
|
||||||
this.areaTag = areaTag;
|
this.areaTag = areaTag;
|
||||||
this.uuid = uuid;
|
this.messageUuid = uuid;
|
||||||
this.replyToMsgId = replyToMsgId;
|
this.replyToMsgId = replyToMsgId;
|
||||||
this.toUserName = toUserName;
|
this.toUserName = toUserName;
|
||||||
this.fromUserName = fromUserName;
|
this.fromUserName = fromUserName;
|
||||||
|
@ -123,6 +142,10 @@ module.exports = class Message {
|
||||||
this.hashTags = hashTags;
|
this.hashTags = hashTags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get uuid() { // deprecated, will be removed in the near future
|
||||||
|
return this.messageUuid;
|
||||||
|
}
|
||||||
|
|
||||||
isValid() { return true; } // :TODO: obviously useless; look into this or remove it
|
isValid() { return true; } // :TODO: obviously useless; look into this or remove it
|
||||||
|
|
||||||
static isPrivateAreaTag(areaTag) {
|
static isPrivateAreaTag(areaTag) {
|
||||||
|
@ -137,6 +160,20 @@ module.exports = class Message {
|
||||||
return null !== _.get(this, 'meta.System.remote_from_user', null);
|
return null !== _.get(this, 'meta.System.remote_from_user', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isCP437Encodable() {
|
||||||
|
return isCP437Encodable(this.toUserName) &&
|
||||||
|
isCP437Encodable(this.fromUserName) &&
|
||||||
|
isCP437Encodable(this.subject) &&
|
||||||
|
isCP437Encodable(this.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
containsNonLatinCodepoints() {
|
||||||
|
return containsNonLatinCodepoints(this.toUserName) ||
|
||||||
|
containsNonLatinCodepoints(this.fromUserName) ||
|
||||||
|
containsNonLatinCodepoints(this.subject) ||
|
||||||
|
containsNonLatinCodepoints(this.message);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
:TODO: finish me
|
:TODO: finish me
|
||||||
static checkUserHasDeleteRights(user, messageIdOrUuid, cb) {
|
static checkUserHasDeleteRights(user, messageIdOrUuid, cb) {
|
||||||
|
@ -183,6 +220,10 @@ module.exports = class Message {
|
||||||
return FTN_PROPERTY_NAMES;
|
return FTN_PROPERTY_NAMES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get QWKPropertyNames() {
|
||||||
|
return QWKPropertyNames;
|
||||||
|
}
|
||||||
|
|
||||||
setLocalToUserId(userId) {
|
setLocalToUserId(userId) {
|
||||||
this.meta.System = this.meta.System || {};
|
this.meta.System = this.meta.System || {};
|
||||||
this.meta.System[Message.SystemMetaNames.LocalToUserID] = userId;
|
this.meta.System[Message.SystemMetaNames.LocalToUserID] = userId;
|
||||||
|
@ -259,7 +300,7 @@ module.exports = class Message {
|
||||||
filter.extraFields = []
|
filter.extraFields = []
|
||||||
|
|
||||||
filter.privateTagUserId = <userId> - if set, only private messages belonging to <userId> are processed
|
filter.privateTagUserId = <userId> - if set, only private messages belonging to <userId> are processed
|
||||||
- any other areaTag or confTag filters will be ignored
|
- areaTags filter ignored
|
||||||
- if NOT present, private areas are skipped
|
- if NOT present, private areas are skipped
|
||||||
|
|
||||||
*=NYI
|
*=NYI
|
||||||
|
@ -335,20 +376,23 @@ module.exports = class Message {
|
||||||
)`);
|
)`);
|
||||||
} else {
|
} else {
|
||||||
if(filter.areaTag && filter.areaTag.length > 0) {
|
if(filter.areaTag && filter.areaTag.length > 0) {
|
||||||
if(Array.isArray(filter.areaTag)) {
|
if (!Array.isArray(filter.areaTag)) {
|
||||||
const areaList = filter.areaTag
|
filter.areaTag = [ filter.areaTag ];
|
||||||
.filter(t => t != Message.WellKnownAreaTags.Private)
|
|
||||||
.map(t => `"${t}"`).join(', ');
|
|
||||||
if(areaList.length > 0) {
|
|
||||||
appendWhereClause(`m.area_tag IN(${areaList})`);
|
|
||||||
}
|
|
||||||
} else if(_.isString(filter.areaTag) && Message.WellKnownAreaTags.Private !== filter.areaTag) {
|
|
||||||
appendWhereClause(`m.area_tag = "${filter.areaTag}"`);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// explicit exclude of Private
|
const areaList = filter.areaTag
|
||||||
appendWhereClause(`m.area_tag != "${Message.WellKnownAreaTags.Private}"`, 'AND');
|
.filter(t => t !== Message.WellKnownAreaTags.Private)
|
||||||
|
.map(t => `"${t}"`).join(', ');
|
||||||
|
if(areaList.length > 0) {
|
||||||
|
appendWhereClause(`m.area_tag IN(${areaList})`);
|
||||||
|
} else {
|
||||||
|
// nothing to do; no areas remain
|
||||||
|
return cb(null, []);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// explicit exclude of Private
|
||||||
|
appendWhereClause(`m.area_tag != "${Message.WellKnownAreaTags.Private}"`, 'AND');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_.isNumber(filter.replyToMessageId)) {
|
if(_.isNumber(filter.replyToMessageId)) {
|
||||||
|
@ -652,8 +696,8 @@ module.exports = class Message {
|
||||||
function storeMessage(trans, callback) {
|
function storeMessage(trans, callback) {
|
||||||
// generate a UUID for this message if required (general case)
|
// generate a UUID for this message if required (general case)
|
||||||
const msgTimestamp = moment();
|
const msgTimestamp = moment();
|
||||||
if(!self.uuid) {
|
if(!self.messageUuid) {
|
||||||
self.uuid = Message.createMessageUUID(
|
self.messageUuid = Message.createMessageUUID(
|
||||||
self.areaTag,
|
self.areaTag,
|
||||||
msgTimestamp,
|
msgTimestamp,
|
||||||
self.subject,
|
self.subject,
|
||||||
|
@ -664,7 +708,10 @@ module.exports = class Message {
|
||||||
trans.run(
|
trans.run(
|
||||||
`INSERT INTO message (area_tag, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, message, modified_timestamp)
|
`INSERT INTO message (area_tag, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, message, modified_timestamp)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?);`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?);`,
|
||||||
[ self.areaTag, self.uuid, self.replyToMsgId, self.toUserName, self.fromUserName, self.subject, self.message, getISOTimestampString(msgTimestamp) ],
|
[
|
||||||
|
self.areaTag, self.messageUuid, self.replyToMsgId, self.toUserName,
|
||||||
|
self.fromUserName, self.subject, self.message, getISOTimestampString(msgTimestamp)
|
||||||
|
],
|
||||||
function inserted(err) { // use non-arrow function for 'this' scope
|
function inserted(err) { // use non-arrow function for 'this' scope
|
||||||
if(!err) {
|
if(!err) {
|
||||||
self.messageId = this.lastID;
|
self.messageId = this.lastID;
|
||||||
|
|
|
@ -24,11 +24,13 @@ exports.getAvailableMessageConferences = getAvailableMessageConferences;
|
||||||
exports.getSortedAvailMessageConferences = getSortedAvailMessageConferences;
|
exports.getSortedAvailMessageConferences = getSortedAvailMessageConferences;
|
||||||
exports.getAvailableMessageAreasByConfTag = getAvailableMessageAreasByConfTag;
|
exports.getAvailableMessageAreasByConfTag = getAvailableMessageAreasByConfTag;
|
||||||
exports.getSortedAvailMessageAreasByConfTag = getSortedAvailMessageAreasByConfTag;
|
exports.getSortedAvailMessageAreasByConfTag = getSortedAvailMessageAreasByConfTag;
|
||||||
|
exports.getAllAvailableMessageAreaTags = getAllAvailableMessageAreaTags;
|
||||||
exports.getDefaultMessageConferenceTag = getDefaultMessageConferenceTag;
|
exports.getDefaultMessageConferenceTag = getDefaultMessageConferenceTag;
|
||||||
exports.getDefaultMessageAreaTagByConfTag = getDefaultMessageAreaTagByConfTag;
|
exports.getDefaultMessageAreaTagByConfTag = getDefaultMessageAreaTagByConfTag;
|
||||||
exports.getSuitableMessageConfAndAreaTags = getSuitableMessageConfAndAreaTags;
|
exports.getSuitableMessageConfAndAreaTags = getSuitableMessageConfAndAreaTags;
|
||||||
exports.getMessageConferenceByTag = getMessageConferenceByTag;
|
exports.getMessageConferenceByTag = getMessageConferenceByTag;
|
||||||
exports.getMessageAreaByTag = getMessageAreaByTag;
|
exports.getMessageAreaByTag = getMessageAreaByTag;
|
||||||
|
exports.getMessageConfTagByAreaTag = getMessageConfTagByAreaTag;
|
||||||
exports.changeMessageConference = changeMessageConference;
|
exports.changeMessageConference = changeMessageConference;
|
||||||
exports.changeMessageArea = changeMessageArea;
|
exports.changeMessageArea = changeMessageArea;
|
||||||
exports.hasMessageConfAndAreaRead = hasMessageConfAndAreaRead;
|
exports.hasMessageConfAndAreaRead = hasMessageConfAndAreaRead;
|
||||||
|
@ -139,6 +141,20 @@ function getSortedAvailMessageAreasByConfTag(confTag, options) {
|
||||||
return areas;
|
return areas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAllAvailableMessageAreaTags(client, options) {
|
||||||
|
const areaTags = [];
|
||||||
|
|
||||||
|
// mask over older messy APIs for now
|
||||||
|
const confOpts = Object.assign({}, options, { noClient : client ? false : true });
|
||||||
|
const areaOpts = Object.assign({}, options, { client });
|
||||||
|
|
||||||
|
Object.keys(getAvailableMessageConferences(client, confOpts)).forEach(confTag => {
|
||||||
|
areaTags.push(...Object.keys(getAvailableMessageAreasByConfTag(confTag, areaOpts)));
|
||||||
|
});
|
||||||
|
|
||||||
|
return areaTags;
|
||||||
|
}
|
||||||
|
|
||||||
function getDefaultMessageConferenceTag(client, disableAcsCheck) {
|
function getDefaultMessageConferenceTag(client, disableAcsCheck) {
|
||||||
//
|
//
|
||||||
// Find the first conference marked 'default'. If found,
|
// Find the first conference marked 'default'. If found,
|
||||||
|
|
|
@ -0,0 +1,428 @@
|
||||||
|
// ENiGMA½
|
||||||
|
const { MenuModule } = require('./menu_module');
|
||||||
|
const Message = require('./message');
|
||||||
|
const { Errors } = require('./enig_error');
|
||||||
|
const {
|
||||||
|
getMessageAreaByTag,
|
||||||
|
getMessageConferenceByTag,
|
||||||
|
hasMessageConfAndAreaRead,
|
||||||
|
getAllAvailableMessageAreaTags,
|
||||||
|
} = require('./message_area');
|
||||||
|
const FileArea = require('./file_base_area');
|
||||||
|
const { QWKPacketWriter } = require('./qwk_mail_packet');
|
||||||
|
const { renderSubstr } = require('./string_util');
|
||||||
|
const Config = require('./config').get;
|
||||||
|
const FileEntry = require('./file_entry');
|
||||||
|
const DownloadQueue = require('./download_queue');
|
||||||
|
const { getISOTimestampString } = require('./database');
|
||||||
|
|
||||||
|
// deps
|
||||||
|
const async = require('async');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const fse = require('fs-extra');
|
||||||
|
const temptmp = require('temptmp');
|
||||||
|
const paths = require('path');
|
||||||
|
const { v4 : UUIDv4 } = require('uuid');
|
||||||
|
const moment = require('moment');
|
||||||
|
|
||||||
|
const FormIds = {
|
||||||
|
main : 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const MciViewIds = {
|
||||||
|
main : {
|
||||||
|
status : 1,
|
||||||
|
progressBar : 2,
|
||||||
|
|
||||||
|
customRangeStart : 10,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const UserProperties = {
|
||||||
|
ExportOptions : 'qwk_export_options',
|
||||||
|
ExportAreas : 'qwk_export_msg_areas',
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.moduleInfo = {
|
||||||
|
name : 'QWK Export',
|
||||||
|
desc : 'Exports a QWK Packet for download',
|
||||||
|
author : 'NuSkooler',
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getModule = class MessageBaseQWKExport extends MenuModule {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), options.extraArgs);
|
||||||
|
|
||||||
|
this.config.progBarChar = renderSubstr( (this.config.progBarChar || '▒'), 0, 1);
|
||||||
|
this.config.bbsID = this.config.bbsID || _.get(Config(), 'messageNetworks.qwk.bbsID', 'ENIGMA');
|
||||||
|
|
||||||
|
this.tempName = `${UUIDv4().substr(-8).toUpperCase()}.QWK`;
|
||||||
|
this.sysTempDownloadArea = FileArea.getFileAreaByTag(FileArea.WellKnownAreaTags.TempDownloads);
|
||||||
|
}
|
||||||
|
|
||||||
|
mciReady(mciData, cb) {
|
||||||
|
super.mciReady(mciData, err => {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
async.waterfall(
|
||||||
|
[
|
||||||
|
(callback) => {
|
||||||
|
this.prepViewController('main', FormIds.main, mciData.menu, err => {
|
||||||
|
return callback(err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(callback) => {
|
||||||
|
this.temptmp = temptmp.createTrackedSession('qwkuserexp');
|
||||||
|
this.temptmp.mkdir({ prefix : 'enigqwkwriter-'}, (err, tempDir) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tempPacketDir = tempDir;
|
||||||
|
|
||||||
|
const sysTempDownloadDir = FileArea.getAreaDefaultStorageDirectory(this.sysTempDownloadArea);
|
||||||
|
|
||||||
|
// ensure dir exists
|
||||||
|
fse.mkdirs(sysTempDownloadDir, err => {
|
||||||
|
return callback(err, sysTempDownloadDir);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(sysTempDownloadDir, callback) => {
|
||||||
|
this._performExport(sysTempDownloadDir, err => {
|
||||||
|
return callback(err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
],
|
||||||
|
err => {
|
||||||
|
this.temptmp.cleanup();
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
// :TODO: doesn't do anything currently:
|
||||||
|
if ('NORESULTS' === err.reasonCode) {
|
||||||
|
return this.gotoMenu(this.menuConfig.config.noResultsMenu || 'qwkExportNoResults');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.prevMenu();
|
||||||
|
}
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
finishedLoading() {
|
||||||
|
this.prevMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
_getUserQWKExportOptions() {
|
||||||
|
let qwkOptions = this.client.user.getProperty(UserProperties.ExportOptions);
|
||||||
|
try {
|
||||||
|
qwkOptions = JSON.parse(qwkOptions);
|
||||||
|
} catch(e) {
|
||||||
|
qwkOptions = {
|
||||||
|
enableQWKE : true,
|
||||||
|
enableHeadersExtension : true,
|
||||||
|
enableAtKludges : true,
|
||||||
|
archiveFormat : 'application/zip',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return qwkOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getUserQWKExportAreas() {
|
||||||
|
let qwkExportAreas = this.client.user.getProperty(UserProperties.ExportAreas);
|
||||||
|
try {
|
||||||
|
qwkExportAreas = JSON.parse(qwkExportAreas).map(exportArea => {
|
||||||
|
if (exportArea.newerThanTimestamp) {
|
||||||
|
exportArea.newerThanTimestamp = moment(exportArea.newerThanTimestamp);
|
||||||
|
}
|
||||||
|
return exportArea;
|
||||||
|
});
|
||||||
|
} catch(e) {
|
||||||
|
// default to all public and private without 'since'
|
||||||
|
qwkExportAreas = getAllAvailableMessageAreaTags(this.client).map(areaTag => {
|
||||||
|
return { areaTag };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Include user's private area
|
||||||
|
qwkExportAreas.push({
|
||||||
|
areaTag : Message.WellKnownAreaTags.Private,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return qwkExportAreas;
|
||||||
|
}
|
||||||
|
|
||||||
|
_performExport(sysTempDownloadDir, cb) {
|
||||||
|
const statusView = this.viewControllers.main.getView(MciViewIds.main.status);
|
||||||
|
const updateStatus = (status) => {
|
||||||
|
if (statusView) {
|
||||||
|
statusView.setText(status);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const progBarView = this.viewControllers.main.getView(MciViewIds.main.progressBar);
|
||||||
|
const updateProgressBar = (curr, total) => {
|
||||||
|
if (progBarView) {
|
||||||
|
const prog = Math.floor( (curr / total) * progBarView.dimens.width );
|
||||||
|
progBarView.setText(this.config.progBarChar.repeat(prog));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let cancel = false;
|
||||||
|
|
||||||
|
let lastProgUpdate = 0;
|
||||||
|
const progressHandler = (state, next) => {
|
||||||
|
// we can produce a TON of updates; only update progress at most every 3/4s
|
||||||
|
if (Date.now() - lastProgUpdate > 750) {
|
||||||
|
switch (state.step) {
|
||||||
|
case 'next_area' :
|
||||||
|
updateStatus(state.status);
|
||||||
|
updateProgressBar(0, 0);
|
||||||
|
this.updateCustomViewTextsWithFilter('main', MciViewIds.main.customRangeStart, state);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'message' :
|
||||||
|
updateStatus(state.status);
|
||||||
|
updateProgressBar(state.current, state.total);
|
||||||
|
this.updateCustomViewTextsWithFilter('main', MciViewIds.main.customRangeStart, state);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default :
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lastProgUpdate = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(cancel ? Errors.UserInterrupt('User canceled') : null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const keyPressHandler = (ch, key) => {
|
||||||
|
if('escape' === key.name) {
|
||||||
|
cancel = true;
|
||||||
|
this.client.removeListener('key press', keyPressHandler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let totalExported = 0;
|
||||||
|
const processMessagesWithFilter = (filter, cb) => {
|
||||||
|
Message.findMessages(filter, (err, messageIds) => {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let current = 1;
|
||||||
|
async.eachSeries(messageIds, (messageId, nextMessageId) => {
|
||||||
|
const message = new Message();
|
||||||
|
message.load({ messageId }, err => {
|
||||||
|
if (err) {
|
||||||
|
return nextMessageId(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const progress = {
|
||||||
|
message,
|
||||||
|
step : 'message',
|
||||||
|
total : ++totalExported,
|
||||||
|
areaCurrent : current,
|
||||||
|
areaCount : messageIds.length,
|
||||||
|
status : `${_.truncate(message.subject, { length : 25 })} (${current} / ${messageIds.length})`,
|
||||||
|
};
|
||||||
|
|
||||||
|
progressHandler(progress, err => {
|
||||||
|
if (err) {
|
||||||
|
return nextMessageId(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
packetWriter.appendMessage(message);
|
||||||
|
current += 1;
|
||||||
|
|
||||||
|
return nextMessageId(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
return cb(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const packetWriter = new QWKPacketWriter(
|
||||||
|
Object.assign(this._getUserQWKExportOptions(), {
|
||||||
|
user : this.client.user,
|
||||||
|
bbsID : this.config.bbsID,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
packetWriter.on('warning', warning => {
|
||||||
|
this.client.log.warn( { warning }, 'QWK packet writer warning');
|
||||||
|
});
|
||||||
|
|
||||||
|
async.waterfall(
|
||||||
|
[
|
||||||
|
(callback) => {
|
||||||
|
// don't count idle monitor while processing
|
||||||
|
this.client.stopIdleMonitor();
|
||||||
|
|
||||||
|
// let user cancel
|
||||||
|
this.client.on('key press', keyPressHandler);
|
||||||
|
|
||||||
|
packetWriter.once('ready', () => {
|
||||||
|
return callback(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
packetWriter.once('error', err => {
|
||||||
|
this.client.log.error( { error : err.message }, 'QWK packet writer error');
|
||||||
|
cancel = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
packetWriter.init();
|
||||||
|
},
|
||||||
|
(callback) => {
|
||||||
|
// For each public area -> for each message
|
||||||
|
const userExportAreas = this._getUserQWKExportAreas();
|
||||||
|
|
||||||
|
const publicExportAreas = userExportAreas
|
||||||
|
.filter(exportArea => {
|
||||||
|
return exportArea.areaTag !== Message.WellKnownAreaTags.Private;
|
||||||
|
});
|
||||||
|
async.eachSeries(publicExportAreas, (exportArea, nextExportArea) => {
|
||||||
|
const area = getMessageAreaByTag(exportArea.areaTag);
|
||||||
|
const conf = getMessageConferenceByTag(area.confTag);
|
||||||
|
if (!area || !conf) {
|
||||||
|
// :TODO: remove from user properties - this area does not exist
|
||||||
|
this.client.log.warn({ areaTag : exportArea.areaTag }, 'Cannot QWK export area as it does not exist');
|
||||||
|
return nextExportArea(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasMessageConfAndAreaRead(this.client, area)) {
|
||||||
|
this.client.log.warn({ areaTag : area.areaTag }, 'Cannot QWK export area due to ACS');
|
||||||
|
return nextExportArea(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const progress = {
|
||||||
|
conf,
|
||||||
|
area,
|
||||||
|
step : 'next_area',
|
||||||
|
status : `Gathering in ${conf.name} - ${area.name}...`,
|
||||||
|
};
|
||||||
|
|
||||||
|
progressHandler(progress, err => {
|
||||||
|
if (err) {
|
||||||
|
return nextExportArea(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
resultType : 'id',
|
||||||
|
areaTag : exportArea.areaTag,
|
||||||
|
newerThanTimestamp : exportArea.newerThanTimestamp
|
||||||
|
};
|
||||||
|
|
||||||
|
processMessagesWithFilter(filter, err => {
|
||||||
|
return nextExportArea(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
return callback(err, userExportAreas);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(userExportAreas, callback) => {
|
||||||
|
// Private messages to current user if the user has
|
||||||
|
// elected to export private messages
|
||||||
|
const privateExportArea = userExportAreas.find(exportArea => exportArea.areaTag === Message.WellKnownAreaTags.Private);
|
||||||
|
if (!privateExportArea) {
|
||||||
|
return callback(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
resultType : 'id',
|
||||||
|
privateTagUserId : this.client.user.userId,
|
||||||
|
newerThanTimestamp : privateExportArea.newerThanTimestamp,
|
||||||
|
};
|
||||||
|
return processMessagesWithFilter(filter, callback);
|
||||||
|
},
|
||||||
|
(callback) => {
|
||||||
|
let packetInfo;
|
||||||
|
packetWriter.once('packet', info => {
|
||||||
|
packetInfo = info;
|
||||||
|
});
|
||||||
|
|
||||||
|
packetWriter.once('finished', () => {
|
||||||
|
return callback(null, packetInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
packetWriter.finish(this.tempPacketDir);
|
||||||
|
},
|
||||||
|
(packetInfo, callback) => {
|
||||||
|
if (0 === totalExported) {
|
||||||
|
return callback(Errors.NothingToDo('No messages exported'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const sysDownloadPath = paths.join(sysTempDownloadDir, this.tempName);
|
||||||
|
fse.move(packetInfo.path, sysDownloadPath, err => {
|
||||||
|
return callback(null, sysDownloadPath, packetInfo);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(sysDownloadPath, packetInfo, callback) => {
|
||||||
|
const newEntry = new FileEntry({
|
||||||
|
areaTag : this.sysTempDownloadArea.areaTag,
|
||||||
|
fileName : paths.basename(sysDownloadPath),
|
||||||
|
storageTag : this.sysTempDownloadArea.storageTags[0],
|
||||||
|
meta : {
|
||||||
|
upload_by_username : this.client.user.username,
|
||||||
|
upload_by_user_id : this.client.user.userId,
|
||||||
|
byte_size : packetInfo.stats.size,
|
||||||
|
session_temp_dl : 1, // download is valid until session is over
|
||||||
|
|
||||||
|
// :TODO: something like this: allow to override the displayed/downloaded as filename
|
||||||
|
// separate from the actual on disk filename. E.g. we could always download as "ENIGMA.QWK"
|
||||||
|
//visible_filename : paths.basename(packetInfo.path),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
newEntry.desc = 'QWK Export';
|
||||||
|
|
||||||
|
newEntry.persist(err => {
|
||||||
|
if(!err) {
|
||||||
|
// queue it!
|
||||||
|
DownloadQueue.get(this.client).addTemporaryDownload(newEntry);
|
||||||
|
}
|
||||||
|
return callback(err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(callback) => {
|
||||||
|
// update user's export area dates; they can always change/reset them again
|
||||||
|
const updatedUserExportAreas = this._getUserQWKExportAreas().map(exportArea => {
|
||||||
|
return Object.assign(exportArea, {
|
||||||
|
newerThanTimestamp : getISOTimestampString(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.client.user.persistProperty(
|
||||||
|
UserProperties.ExportAreas,
|
||||||
|
JSON.stringify(updatedUserExportAreas),
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
err => {
|
||||||
|
this.client.startIdleMonitor(); // re-enable
|
||||||
|
this.client.removeListener('key press', keyPressHandler);
|
||||||
|
|
||||||
|
if (!err) {
|
||||||
|
updateStatus('A QWK packet has been placed in your download queue');
|
||||||
|
} else if (err.code === Errors.NothingToDo().code) {
|
||||||
|
updateStatus('No messages to export with current criteria');
|
||||||
|
err = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
|
@ -35,7 +35,7 @@ function startup(cb) {
|
||||||
|
|
||||||
function resolveMimeType(query) {
|
function resolveMimeType(query) {
|
||||||
if(mimeTypes.extensions[query]) {
|
if(mimeTypes.extensions[query]) {
|
||||||
return query; // alreaed a mime-type
|
return query; // already a mime-type
|
||||||
}
|
}
|
||||||
|
|
||||||
return mimeTypes.lookup(query) || undefined; // lookup() returns false; we want undefined
|
return mimeTypes.lookup(query) || undefined; // lookup() returns false; we want undefined
|
||||||
|
|
|
@ -50,7 +50,7 @@ exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
|
||||||
} else {
|
} else {
|
||||||
// note: not logging 'from' here as it's part of client.log.xxxx()
|
// note: not logging 'from' here as it's part of client.log.xxxx()
|
||||||
self.client.log.info(
|
self.client.log.info(
|
||||||
{ to : msg.toUserName, subject : msg.subject, uuid : msg.uuid },
|
{ to : msg.toUserName, subject : msg.subject, uuid : msg.messageUuid },
|
||||||
'Message persisted'
|
'Message persisted'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -521,7 +521,24 @@ function scanFileAreas() {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function scanAreas(callback) {
|
function scanAreas(callback) {
|
||||||
fileArea = require('../../core/file_base_area.js');
|
fileArea = require('../../core/file_base_area');
|
||||||
|
|
||||||
|
// Further expand any wildcards
|
||||||
|
let areaAndStorageInfoExpanded = [];
|
||||||
|
options.areaAndStorageInfo.forEach(info => {
|
||||||
|
if (info.areaTag.indexOf('*') > -1) {
|
||||||
|
const areas = fileArea.getFileAreasByTagWildcardRule(info.areaTag);
|
||||||
|
areas.forEach(area => {
|
||||||
|
areaAndStorageInfoExpanded.push(Object.assign({}, info, {
|
||||||
|
areaTag : area.areaTag,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
areaAndStorageInfoExpanded.push(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
options.areaAndStorageInfo = areaAndStorageInfoExpanded;
|
||||||
|
|
||||||
async.eachSeries(options.areaAndStorageInfo, (areaAndStorage, nextAreaTag) => {
|
async.eachSeries(options.areaAndStorageInfo, (areaAndStorage, nextAreaTag) => {
|
||||||
const areaInfo = fileArea.getFileAreaByTag(areaAndStorage.areaTag);
|
const areaInfo = fileArea.getFileAreaByTag(areaAndStorage.areaTag);
|
||||||
|
|
|
@ -59,6 +59,15 @@ Actions:
|
||||||
|
|
||||||
group USERNAME [+|-]GROUP Adds (+) or removes (-) user from a group
|
group USERNAME [+|-]GROUP Adds (+) or removes (-) user from a group
|
||||||
|
|
||||||
|
list [FILTER] List users with optional FILTER.
|
||||||
|
|
||||||
|
Valid filters:
|
||||||
|
all : All users (default).
|
||||||
|
disabled : Disabled users.
|
||||||
|
inactive : Inactive users.
|
||||||
|
active : Active (regular) users.
|
||||||
|
locked : Locked users.
|
||||||
|
|
||||||
info arguments:
|
info arguments:
|
||||||
--security Include security information in output
|
--security Include security information in output
|
||||||
|
|
||||||
|
@ -92,8 +101,12 @@ cat arguments:
|
||||||
Actions:
|
Actions:
|
||||||
scan AREA_TAG[@STORAGE_TAG] Scan specified area
|
scan AREA_TAG[@STORAGE_TAG] Scan specified area
|
||||||
|
|
||||||
May contain optional GLOB as last parameter.
|
Tips:
|
||||||
Example: ./oputil.js fb scan d0pew4r3z *.zip
|
- May contain optional GLOB as last parameter.
|
||||||
|
Example: ./oputil.js fb scan d0pew4r3z *.zip
|
||||||
|
|
||||||
|
- AREA_TAG may contain simple wildcards.
|
||||||
|
Example: ./oputil.js fb scan *warez*
|
||||||
|
|
||||||
info CRITERIA Display information about areas and/or files
|
info CRITERIA Display information about areas and/or files
|
||||||
|
|
||||||
|
@ -170,6 +183,11 @@ Actions:
|
||||||
|
|
||||||
import-areas PATH Import areas using FidoNet *.NA or AREAS.BBS file
|
import-areas PATH Import areas using FidoNet *.NA or AREAS.BBS file
|
||||||
|
|
||||||
|
qwk-dump PATH Dumps a QWK packet to stdout.
|
||||||
|
qwk-export [AREA_TAGS] PATH Exports one or more configured message area to a QWK
|
||||||
|
packet in the directory specified by PATH. The QWK
|
||||||
|
BBS ID will be obtained by the final component of PATH.
|
||||||
|
|
||||||
import-areas arguments:
|
import-areas arguments:
|
||||||
--conf CONF_TAG Conference tag in which to import areas
|
--conf CONF_TAG Conference tag in which to import areas
|
||||||
--network NETWORK Network name/key to associate FTN areas
|
--network NETWORK Network name/key to associate FTN areas
|
||||||
|
@ -177,6 +195,13 @@ import-areas arguments:
|
||||||
--type TYPE Area import type
|
--type TYPE Area import type
|
||||||
|
|
||||||
Valid types are "bbs" and "na".
|
Valid types are "bbs" and "na".
|
||||||
|
|
||||||
|
qwk-export arguments:
|
||||||
|
--user USER User in which to export for. Defaults to the SysOp.
|
||||||
|
--after TIMESTAMP Export only messages with a timestamp later than
|
||||||
|
TIMESTAMP.
|
||||||
|
--no-qwke Disable QWKE extensions.
|
||||||
|
--no-synchronet Disable Synchronet style extensions.
|
||||||
`
|
`
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,17 +10,19 @@ const {
|
||||||
initConfigAndDatabases,
|
initConfigAndDatabases,
|
||||||
getAnswers,
|
getAnswers,
|
||||||
writeConfig,
|
writeConfig,
|
||||||
} = require('./oputil_common.js');
|
} = require('./oputil_common.js');
|
||||||
const getHelpFor = require('./oputil_help.js').getHelpFor;
|
|
||||||
const Address = require('../ftn_address.js');
|
const getHelpFor = require('./oputil_help.js').getHelpFor;
|
||||||
const Errors = require('../enig_error.js').Errors;
|
const Address = require('../ftn_address.js');
|
||||||
|
const Errors = require('../enig_error.js').Errors;
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
const paths = require('path');
|
const paths = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const hjson = require('hjson');
|
const hjson = require('hjson');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
const moment = require('moment');
|
||||||
|
|
||||||
exports.handleMessageBaseCommand = handleMessageBaseCommand;
|
exports.handleMessageBaseCommand = handleMessageBaseCommand;
|
||||||
|
|
||||||
|
@ -434,6 +436,200 @@ function getImportEntries(importType, importData) {
|
||||||
return importEntries;
|
return importEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dumpQWKPacket() {
|
||||||
|
const packetPath = argv._[argv._.length - 1];
|
||||||
|
if(argv._.length < 3 || !packetPath || 0 === packetPath.length) {
|
||||||
|
return printUsageAndSetExitCode(getHelpFor('MessageBase'), ExitCodes.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
async.waterfall(
|
||||||
|
[
|
||||||
|
(callback) => {
|
||||||
|
return initConfigAndDatabases(callback);
|
||||||
|
},
|
||||||
|
(callback) => {
|
||||||
|
const { QWKPacketReader } = require('../qwk_mail_packet');
|
||||||
|
const reader = new QWKPacketReader(packetPath);
|
||||||
|
|
||||||
|
reader.on('error', err => {
|
||||||
|
console.error(`ERROR: ${err.message}`);
|
||||||
|
return callback(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.on('done', () => {
|
||||||
|
return callback(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.on('archive type', archiveType => {
|
||||||
|
console.info(`-> Archive type: ${archiveType}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.on('creator', creator => {
|
||||||
|
console.info(`-> Creator: ${creator}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.on('message', message => {
|
||||||
|
console.info('--- message ---');
|
||||||
|
console.info(`To: ${message.toUserName}`);
|
||||||
|
console.info(`From: ${message.fromUserName}`);
|
||||||
|
console.info(`Subject: ${message.subject}`);
|
||||||
|
console.info(`Message:\r\n${message.message}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.read();
|
||||||
|
}
|
||||||
|
],
|
||||||
|
err => {
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportQWKPacket() {
|
||||||
|
let packetPath = argv._[argv._.length - 1];
|
||||||
|
if(argv._.length < 3 || !packetPath || 0 === packetPath.length) {
|
||||||
|
return printUsageAndSetExitCode(getHelpFor('MessageBase'), ExitCodes.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// oputil mb qwk-export TAGS PATH [--user USER] [--after TIMESTAMP]
|
||||||
|
// [areaTag1,areaTag2,...] PATH --user USER --after TIMESTAMP
|
||||||
|
let bbsID = 'ENIGMA';
|
||||||
|
const filename = paths.basename(packetPath);
|
||||||
|
if (filename) {
|
||||||
|
const ext = paths.extname(filename);
|
||||||
|
bbsID = paths.basename(filename, ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
packetPath = paths.dirname(packetPath);
|
||||||
|
|
||||||
|
const posArgLen = argv._.length;
|
||||||
|
|
||||||
|
let areaTags;
|
||||||
|
if (4 === posArgLen) {
|
||||||
|
areaTags = argv._[posArgLen - 2].split(',');
|
||||||
|
} else {
|
||||||
|
areaTags = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let newerThanTimestamp = null;
|
||||||
|
if (argv.after) {
|
||||||
|
const ts = moment(argv.after);
|
||||||
|
if (ts.isValid()) {
|
||||||
|
newerThanTimestamp = ts.format();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const userName = argv.user || '-';
|
||||||
|
|
||||||
|
const writerOptions = {
|
||||||
|
enableQWKE : !(false === argv.qwke),
|
||||||
|
enableHeadersExtension : !(false === argv.synchronet),
|
||||||
|
enableAtKludges : !(false === argv.synchronet),
|
||||||
|
archiveFormat : argv.format || 'application/zip'
|
||||||
|
};
|
||||||
|
|
||||||
|
let totalExported = 0;
|
||||||
|
async.waterfall(
|
||||||
|
[
|
||||||
|
(callback) => {
|
||||||
|
return initConfigAndDatabases(callback);
|
||||||
|
},
|
||||||
|
(callback) => {
|
||||||
|
const User = require('../../core/user.js');
|
||||||
|
|
||||||
|
User.getUserIdAndName(userName, (err, userId) => {
|
||||||
|
if (err) {
|
||||||
|
if ('-' === userName) {
|
||||||
|
userId = 1;
|
||||||
|
} else {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return User.getUser(userId, callback);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(user, callback) => {
|
||||||
|
// populate area tags with all available to user
|
||||||
|
// if they were not explicitly supplied
|
||||||
|
if (!areaTags.length) {
|
||||||
|
const {
|
||||||
|
getAllAvailableMessageAreaTags
|
||||||
|
} = require('../../core/message_area');
|
||||||
|
|
||||||
|
areaTags = getAllAvailableMessageAreaTags();
|
||||||
|
}
|
||||||
|
return callback(null, user);
|
||||||
|
},
|
||||||
|
(user, callback) => {
|
||||||
|
const Message = require('../message');
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
resultType : 'id',
|
||||||
|
areaTag : areaTags,
|
||||||
|
newerThanTimestamp,
|
||||||
|
};
|
||||||
|
|
||||||
|
// public
|
||||||
|
Message.findMessages(filter, (err, publicMessageIds) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete filter.areaTag;
|
||||||
|
filter.privateTagUserId = user.userId;
|
||||||
|
|
||||||
|
Message.findMessages(filter, (err, privateMessageIds) => {
|
||||||
|
return callback(err, user, Message, privateMessageIds.concat(publicMessageIds));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(user, Message, messageIds, callback) => {
|
||||||
|
const { QWKPacketWriter } = require('../qwk_mail_packet');
|
||||||
|
const writer = new QWKPacketWriter(Object.assign(writerOptions, {
|
||||||
|
bbsID,
|
||||||
|
user,
|
||||||
|
}));
|
||||||
|
|
||||||
|
writer.on('ready', () => {
|
||||||
|
async.eachSeries(messageIds, (messageId, nextMessageId) => {
|
||||||
|
const message = new Message();
|
||||||
|
message.load( { messageId }, err => {
|
||||||
|
if (!err) {
|
||||||
|
writer.appendMessage(message);
|
||||||
|
++totalExported;
|
||||||
|
}
|
||||||
|
return nextMessageId(err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
writer.finish(packetPath);
|
||||||
|
if (err) {
|
||||||
|
console.error(`Failed to write one or more messages: ${err.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
writer.on('warning', err => {
|
||||||
|
console.warn(`!!! ${err.reason ? err.reason : err.message}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
writer.on('finished', () => {
|
||||||
|
return callback(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
writer.init();
|
||||||
|
}
|
||||||
|
],
|
||||||
|
err => {
|
||||||
|
if(err) {
|
||||||
|
return console.error(err.reason ? err.reason : err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info(`-> Exported ${totalExported} messages`);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function handleMessageBaseCommand() {
|
function handleMessageBaseCommand() {
|
||||||
|
|
||||||
function errUsage() {
|
function errUsage() {
|
||||||
|
@ -452,5 +648,7 @@ function handleMessageBaseCommand() {
|
||||||
return({
|
return({
|
||||||
areafix : areaFix,
|
areafix : areaFix,
|
||||||
'import-areas' : importAreas,
|
'import-areas' : importAreas,
|
||||||
|
'qwk-dump' : dumpQWKPacket,
|
||||||
|
'qwk-export' : exportQWKPacket,
|
||||||
}[action] || errUsage)();
|
}[action] || errUsage)();
|
||||||
}
|
}
|
|
@ -460,6 +460,66 @@ function twoFactorAuthOTP(user) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function listUsers() {
|
||||||
|
// oputil user list [disabled|inactive|active|locked|all]
|
||||||
|
// :TODO: --created-since SPEC and --last-called SPEC
|
||||||
|
// --created-since SPEC
|
||||||
|
// SPEC can be TIMESTAMP or e.g. "-1hour" or "-90days"
|
||||||
|
// :TODO: --sort name|id
|
||||||
|
let listWhat;
|
||||||
|
if (argv._.length > 2) {
|
||||||
|
listWhat = argv._[argv._.length - 1];
|
||||||
|
} else {
|
||||||
|
listWhat = 'all';
|
||||||
|
}
|
||||||
|
|
||||||
|
const User = require('../../core/user');
|
||||||
|
if (![ 'all' ].concat(Object.keys(User.AccountStatus)).includes(listWhat)) {
|
||||||
|
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
async.waterfall(
|
||||||
|
[
|
||||||
|
(callback) => {
|
||||||
|
const UserProps = require('../../core/user_property');
|
||||||
|
|
||||||
|
const userListOpts = {
|
||||||
|
properties : [
|
||||||
|
UserProps.AccountStatus,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
User.getUserList(userListOpts, (err, userList) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('all' === listWhat) {
|
||||||
|
return callback(null, userList);
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountStatusFilter = User.AccountStatus[listWhat].toString();
|
||||||
|
|
||||||
|
return callback(null, userList.filter(user => {
|
||||||
|
return user[UserProps.AccountStatus] === accountStatusFilter;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(userList, callback) => {
|
||||||
|
userList.forEach(user => {
|
||||||
|
|
||||||
|
console.info(`${user.userId}: ${user.userName}`);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
],
|
||||||
|
err => {
|
||||||
|
if(err) {
|
||||||
|
return console.error(err.reason ? err.reason : err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function handleUserCommand() {
|
function handleUserCommand() {
|
||||||
function errUsage() {
|
function errUsage() {
|
||||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||||
|
@ -470,20 +530,25 @@ function handleUserCommand() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const action = argv._[1];
|
const action = argv._[1];
|
||||||
const usernameIdx = [
|
const userRequired = ![ 'list' ].includes(action);
|
||||||
'pw', 'pass', 'passwd', 'password',
|
|
||||||
'group',
|
|
||||||
'mv', 'rename',
|
|
||||||
'2fa-otp', 'otp'
|
|
||||||
].includes(action) ? argv._.length - 2 : argv._.length - 1;
|
|
||||||
const userName = argv._[usernameIdx];
|
|
||||||
|
|
||||||
if(!userName) {
|
let userName;
|
||||||
|
if (userRequired) {
|
||||||
|
const usernameIdx = [
|
||||||
|
'pw', 'pass', 'passwd', 'password',
|
||||||
|
'group',
|
||||||
|
'mv', 'rename',
|
||||||
|
'2fa-otp', 'otp'
|
||||||
|
].includes(action) ? argv._.length - 2 : argv._.length - 1;
|
||||||
|
userName = argv._[usernameIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!userName && userRequired) {
|
||||||
return errUsage();
|
return errUsage();
|
||||||
}
|
}
|
||||||
|
|
||||||
initAndGetUser(userName, (err, user) => {
|
initAndGetUser(userName, (err, user) => {
|
||||||
if(err) {
|
if(userName && err) {
|
||||||
process.exitCode = ExitCodes.ERROR;
|
process.exitCode = ExitCodes.ERROR;
|
||||||
return console.error(err.message);
|
return console.error(err.message);
|
||||||
}
|
}
|
||||||
|
@ -512,6 +577,7 @@ function handleUserCommand() {
|
||||||
|
|
||||||
'2fa-otp' : twoFactorAuthOTP,
|
'2fa-otp' : twoFactorAuthOTP,
|
||||||
otp : twoFactorAuthOTP,
|
otp : twoFactorAuthOTP,
|
||||||
|
list : listUsers,
|
||||||
}[action] || errUsage)(user, action);
|
}[action] || errUsage)(user, action);
|
||||||
});
|
});
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -26,6 +26,25 @@ exports.SAUCE_SIZE = SAUCE_SIZE;
|
||||||
//
|
//
|
||||||
const SAUCE_VALID_DATA_TYPES = [0, 1, 2, 3, 4, 5, 6, 7, 8 ];
|
const SAUCE_VALID_DATA_TYPES = [0, 1, 2, 3, 4, 5, 6, 7, 8 ];
|
||||||
|
|
||||||
|
const SAUCEParser = new Parser()
|
||||||
|
.buffer('id', { length : 5 } )
|
||||||
|
.buffer('version', { length : 2 } )
|
||||||
|
.buffer('title', { length: 35 } )
|
||||||
|
.buffer('author', { length : 20 } )
|
||||||
|
.buffer('group', { length: 20 } )
|
||||||
|
.buffer('date', { length: 8 } )
|
||||||
|
.uint32le('fileSize')
|
||||||
|
.int8('dataType')
|
||||||
|
.int8('fileType')
|
||||||
|
.uint16le('tinfo1')
|
||||||
|
.uint16le('tinfo2')
|
||||||
|
.uint16le('tinfo3')
|
||||||
|
.uint16le('tinfo4')
|
||||||
|
.int8('numComments')
|
||||||
|
.int8('flags')
|
||||||
|
// :TODO: does this need to be optional?
|
||||||
|
.buffer('tinfos', { length: 22 } ); // SAUCE 00.5
|
||||||
|
|
||||||
function readSAUCE(data, cb) {
|
function readSAUCE(data, cb) {
|
||||||
if(data.length < SAUCE_SIZE) {
|
if(data.length < SAUCE_SIZE) {
|
||||||
return cb(Errors.DoesNotExist('No SAUCE record present'));
|
return cb(Errors.DoesNotExist('No SAUCE record present'));
|
||||||
|
@ -33,30 +52,11 @@ function readSAUCE(data, cb) {
|
||||||
|
|
||||||
let sauceRec;
|
let sauceRec;
|
||||||
try {
|
try {
|
||||||
sauceRec = new Parser()
|
sauceRec = SAUCEParser.parse(data.slice(data.length - SAUCE_SIZE));
|
||||||
.buffer('id', { length : 5 } )
|
|
||||||
.buffer('version', { length : 2 } )
|
|
||||||
.buffer('title', { length: 35 } )
|
|
||||||
.buffer('author', { length : 20 } )
|
|
||||||
.buffer('group', { length: 20 } )
|
|
||||||
.buffer('date', { length: 8 } )
|
|
||||||
.uint32le('fileSize')
|
|
||||||
.int8('dataType')
|
|
||||||
.int8('fileType')
|
|
||||||
.uint16le('tinfo1')
|
|
||||||
.uint16le('tinfo2')
|
|
||||||
.uint16le('tinfo3')
|
|
||||||
.uint16le('tinfo4')
|
|
||||||
.int8('numComments')
|
|
||||||
.int8('flags')
|
|
||||||
// :TODO: does this need to be optional?
|
|
||||||
.buffer('tinfos', { length: 22 } ) // SAUCE 00.5
|
|
||||||
.parse(data.slice(data.length - SAUCE_SIZE));
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return cb(Errors.Invalid('Invalid SAUCE record'));
|
return cb(Errors.Invalid('Invalid SAUCE record'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(!SAUCE_ID.equals(sauceRec.id)) {
|
if(!SAUCE_ID.equals(sauceRec.id)) {
|
||||||
return cb(Errors.DoesNotExist('No SAUCE record present'));
|
return cb(Errors.DoesNotExist('No SAUCE record present'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ const assert = require('assert');
|
||||||
const sane = require('sane');
|
const sane = require('sane');
|
||||||
const fse = require('fs-extra');
|
const fse = require('fs-extra');
|
||||||
const iconv = require('iconv-lite');
|
const iconv = require('iconv-lite');
|
||||||
const uuidV4 = require('uuid/v4');
|
const { v4 : UUIDv4 } = require('uuid');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'FTN BSO',
|
name : 'FTN BSO',
|
||||||
|
@ -517,8 +517,20 @@ function FTNMessageScanTossModule() {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
this.hasValidConfiguration = function() {
|
this.hasValidConfiguration = function({shouldLog = false} = {}) {
|
||||||
if(!_.has(this, 'moduleConfig.nodes') || !_.has(Config(), 'messageNetworks.ftn.areas')) {
|
const hasNodes = _.has(this, 'moduleConfig.nodes');
|
||||||
|
const hasAreas = _.has(Config(), 'messageNetworks.ftn.areas');
|
||||||
|
|
||||||
|
if(!hasNodes && !hasAreas) {
|
||||||
|
if (shouldLog) {
|
||||||
|
Log.warn(
|
||||||
|
{
|
||||||
|
'scannerTossers.ftn_bso.nodes' : hasNodes,
|
||||||
|
'messageNetworks.ftn.areas' : hasAreas,
|
||||||
|
},
|
||||||
|
'Missing one or more required configuration blocks'
|
||||||
|
);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1203,7 +1215,7 @@ function FTNMessageScanTossModule() {
|
||||||
//
|
//
|
||||||
if(true === _.get(Config(), [ 'messageNetworks', 'ftn', 'areas', config.localAreaTag, 'allowDupes' ], false)) {
|
if(true === _.get(Config(), [ 'messageNetworks', 'ftn', 'areas', config.localAreaTag, 'allowDupes' ], false)) {
|
||||||
// just generate a UUID & therefor always allow for dupes
|
// just generate a UUID & therefor always allow for dupes
|
||||||
message.uuid = uuidV4();
|
message.messageUuid = UUIDv4();
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
|
@ -1366,7 +1378,7 @@ function FTNMessageScanTossModule() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message.uuid = Message.createMessageUUID(
|
message.messageUuid = Message.createMessageUUID(
|
||||||
localAreaTag,
|
localAreaTag,
|
||||||
message.modTimestamp,
|
message.modTimestamp,
|
||||||
message.subject,
|
message.subject,
|
||||||
|
@ -1386,7 +1398,7 @@ function FTNMessageScanTossModule() {
|
||||||
if('SQLITE_CONSTRAINT' === err.code || 'DUPE_MSGID' === err.code) {
|
if('SQLITE_CONSTRAINT' === err.code || 'DUPE_MSGID' === err.code) {
|
||||||
const msgId = _.has(message.meta, 'FtnKludge.MSGID') ? message.meta.FtnKludge.MSGID : 'N/A';
|
const msgId = _.has(message.meta, 'FtnKludge.MSGID') ? message.meta.FtnKludge.MSGID : 'N/A';
|
||||||
Log.info(
|
Log.info(
|
||||||
{ area : localAreaTag, subject : message.subject, uuid : message.uuid, MSGID : msgId },
|
{ area : localAreaTag, subject : message.subject, uuid : message.messageUuid, MSGID : msgId },
|
||||||
'Not importing non-unique message');
|
'Not importing non-unique message');
|
||||||
|
|
||||||
return next(null);
|
return next(null);
|
||||||
|
@ -2151,6 +2163,8 @@ FTNMessageScanTossModule.prototype.processTicFilesInDirectory = function(importD
|
||||||
FTNMessageScanTossModule.prototype.startup = function(cb) {
|
FTNMessageScanTossModule.prototype.startup = function(cb) {
|
||||||
Log.info(`${exports.moduleInfo.name} Scanner/Tosser starting up`);
|
Log.info(`${exports.moduleInfo.name} Scanner/Tosser starting up`);
|
||||||
|
|
||||||
|
this.hasValidConfiguration({ shouldLog : true }); // just check and log
|
||||||
|
|
||||||
let importing = false;
|
let importing = false;
|
||||||
|
|
||||||
let self = this;
|
let self = this;
|
||||||
|
@ -2287,13 +2301,18 @@ FTNMessageScanTossModule.prototype.shutdown = function(cb) {
|
||||||
|
|
||||||
FTNMessageScanTossModule.prototype.performImport = function(cb) {
|
FTNMessageScanTossModule.prototype.performImport = function(cb) {
|
||||||
if(!this.hasValidConfiguration()) {
|
if(!this.hasValidConfiguration()) {
|
||||||
return cb(new Error('Missing or invalid configuration'));
|
return cb(Errors.MissingConfig('Invalid or missing configuration'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.each( [ 'inbound', 'secInbound' ], (inboundType, nextDir) => {
|
async.each( [ 'inbound', 'secInbound' ], (inboundType, nextDir) => {
|
||||||
self.importFromDirectory(inboundType, self.moduleConfig.paths[inboundType], () => {
|
const importDir = self.moduleConfig.paths[inboundType];
|
||||||
|
self.importFromDirectory(inboundType, importDir, err => {
|
||||||
|
if (err) {
|
||||||
|
Log.trace({ importDir, error : err.message }, 'Cannot perform FTN import for directory');
|
||||||
|
}
|
||||||
|
|
||||||
return nextDir(null);
|
return nextDir(null);
|
||||||
});
|
});
|
||||||
}, cb);
|
}, cb);
|
||||||
|
@ -2305,7 +2324,7 @@ FTNMessageScanTossModule.prototype.performExport = function(cb) {
|
||||||
// and let's find out what messages need exported.
|
// and let's find out what messages need exported.
|
||||||
//
|
//
|
||||||
if(!this.hasValidConfiguration()) {
|
if(!this.hasValidConfiguration()) {
|
||||||
return cb(new Error('Missing or invalid configuration'));
|
return cb(Errors.MissingConfig('Invalid or missing configuration'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
@ -2313,7 +2332,7 @@ FTNMessageScanTossModule.prototype.performExport = function(cb) {
|
||||||
async.eachSeries( [ 'EchoMail', 'NetMail' ], (type, nextType) => {
|
async.eachSeries( [ 'EchoMail', 'NetMail' ], (type, nextType) => {
|
||||||
self[`perform${type}Export`]( err => {
|
self[`perform${type}Export`]( err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
Log.warn( { error : err.message, type : type }, 'Error(s) during export' );
|
Log.warn( { type, error : err.message }, 'Error(s) during export' );
|
||||||
}
|
}
|
||||||
return nextType(null); // try next, always
|
return nextType(null); // try next, always
|
||||||
});
|
});
|
||||||
|
@ -2330,7 +2349,7 @@ FTNMessageScanTossModule.prototype.record = function(message) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const info = { uuid : message.uuid, subject : message.subject };
|
const info = { uuid : message.messageUuid, subject : message.subject };
|
||||||
|
|
||||||
function exportLog(err) {
|
function exportLog(err) {
|
||||||
if(err) {
|
if(err) {
|
||||||
|
@ -2344,7 +2363,7 @@ FTNMessageScanTossModule.prototype.record = function(message) {
|
||||||
Object.assign(info, { type : 'NetMail' } );
|
Object.assign(info, { type : 'NetMail' } );
|
||||||
|
|
||||||
if(this.exportingStart()) {
|
if(this.exportingStart()) {
|
||||||
this.exportNetMailMessagesToUplinks( [ message.uuid ], err => {
|
this.exportNetMailMessagesToUplinks( [ message.messageUuid ], err => {
|
||||||
this.exportingEnd( () => exportLog(err) );
|
this.exportingEnd( () => exportLog(err) );
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2357,7 +2376,7 @@ FTNMessageScanTossModule.prototype.record = function(message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.exportingStart()) {
|
if(this.exportingStart()) {
|
||||||
this.exportEchoMailMessagesToUplinks( [ message.uuid ], areaConfig, err => {
|
this.exportEchoMailMessagesToUplinks( [ message.messageUuid ], areaConfig, err => {
|
||||||
this.exportingEnd( () => exportLog(err) );
|
this.exportingEnd( () => exportLog(err) );
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -249,11 +249,10 @@ exports.getModule = class MrcModule extends ServerModule {
|
||||||
receiveFromClient(username, message) {
|
receiveFromClient(username, message) {
|
||||||
try {
|
try {
|
||||||
message = JSON.parse(message);
|
message = JSON.parse(message);
|
||||||
|
this.sendToMrcServer(message.from_user, message.from_room, message.to_user, message.to_site, message.to_room, message.body);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.debug({ server : 'MRC', user : username, message : message }, 'Dodgy message received from client');
|
Log.debug({ server : 'MRC', user : username, message : message }, 'Dodgy message received from client');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sendToMrcServer(message.from_user, message.from_room, message.to_user, message.to_site, message.to_room, message.body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -264,11 +263,11 @@ exports.getModule = class MrcModule extends ServerModule {
|
||||||
const line = [
|
const line = [
|
||||||
fromUser,
|
fromUser,
|
||||||
this.boardName,
|
this.boardName,
|
||||||
sanitiseRoomName(fromRoom),
|
sanitiseRoomName(fromRoom || ''),
|
||||||
sanitiseName(toUser || ''),
|
sanitiseName(toUser || ''),
|
||||||
sanitiseName(toSite || ''),
|
sanitiseName(toSite || ''),
|
||||||
sanitiseRoomName(toRoom || ''),
|
sanitiseRoomName(toRoom || ''),
|
||||||
sanitiseMessage(messageBody)
|
sanitiseMessage(messageBody || '')
|
||||||
].join('~') + '~';
|
].join('~') + '~';
|
||||||
|
|
||||||
// Log.debug({ server : 'MRC', data : line }, 'Sending data');
|
// Log.debug({ server : 'MRC', data : line }, 'Sending data');
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -15,6 +15,7 @@ const http = require('http');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const fs = require('graceful-fs');
|
const fs = require('graceful-fs');
|
||||||
const Writable = require('stream');
|
const Writable = require('stream');
|
||||||
|
const { Duplex } = require('stream');
|
||||||
const forEachSeries = require('async/forEachSeries');
|
const forEachSeries = require('async/forEachSeries');
|
||||||
|
|
||||||
const ModuleInfo = exports.moduleInfo = {
|
const ModuleInfo = exports.moduleInfo = {
|
||||||
|
@ -24,100 +25,91 @@ const ModuleInfo = exports.moduleInfo = {
|
||||||
packageName : 'codes.l33t.enigma.websocket.server',
|
packageName : 'codes.l33t.enigma.websocket.server',
|
||||||
};
|
};
|
||||||
|
|
||||||
function WebSocketClient(ws, req, serverType) {
|
class WebSocketClient extends TelnetClient {
|
||||||
|
constructor(ws, req, serverType) {
|
||||||
|
// allow WebSocket to act like a Duplex (socket)
|
||||||
|
const wsDuplex = new class WebSocketDuplex extends Duplex {
|
||||||
|
constructor(ws) {
|
||||||
|
super();
|
||||||
|
this.ws = ws;
|
||||||
|
|
||||||
Object.defineProperty(this, 'isSecure', {
|
this.ws.on('close', err => this.emit('close', err));
|
||||||
get : () => ('secure' === serverType || true === this.proxied) ? true : false,
|
this.ws.on('error', err => this.emit('error', err));
|
||||||
});
|
this.ws.on('message', data => this._data(data));
|
||||||
|
}
|
||||||
|
|
||||||
const self = this;
|
setClient(client, httpRequest) {
|
||||||
|
this.client = client;
|
||||||
|
|
||||||
this.dataHandler = function(data) {
|
// Support X-Forwarded-For and X-Real-IP headers for proxied connections
|
||||||
if(self.pipedDest) {
|
this.resolvedRemoteAddress =
|
||||||
self.pipedDest.write(data);
|
(this.client.proxied && (httpRequest.headers['x-forwarded-for'] || httpRequest.headers['x-real-ip'])) ||
|
||||||
|
httpRequest.connection.remoteAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
get remoteAddress() {
|
||||||
|
return this.resolvedRemoteAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
_write(data, encoding, cb) {
|
||||||
|
cb = cb || ( () => { /* eat it up */} ); // handle data writes after close
|
||||||
|
return this.ws.send(data, { binary : true }, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
_read() {
|
||||||
|
// dummy
|
||||||
|
}
|
||||||
|
|
||||||
|
_data(data) {
|
||||||
|
this.push(data);
|
||||||
|
}
|
||||||
|
}(ws);
|
||||||
|
|
||||||
|
super(wsDuplex);
|
||||||
|
wsDuplex.setClient(this, req);
|
||||||
|
|
||||||
|
// fudge remoteAddress on socket, which is now TelnetSocket
|
||||||
|
this.socket.remoteAddress = wsDuplex.remoteAddress;
|
||||||
|
|
||||||
|
wsDuplex.on('close', () => {
|
||||||
|
// we'll remove client connection which will in turn end() via our SocketBridge above
|
||||||
|
return this.emit('end');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.serverType = serverType;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Monitor connection status with ping/pong
|
||||||
|
//
|
||||||
|
ws.on('pong', () => {
|
||||||
|
Log.trace(`Pong from ${wsDuplex.remoteAddress}`);
|
||||||
|
ws.isConnectionAlive = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
Log.trace( { headers : req.headers }, 'WebSocket connection headers' );
|
||||||
|
|
||||||
|
//
|
||||||
|
// If the config allows it, look for 'x-forwarded-proto' as "https"
|
||||||
|
// to override |isSecure|
|
||||||
|
//
|
||||||
|
if(true === _.get(Config(), 'loginServers.webSocket.proxied') &&
|
||||||
|
'https' === req.headers['x-forwarded-proto'])
|
||||||
|
{
|
||||||
|
Log.debug(`Assuming secure connection due to X-Forwarded-Proto of "${req.headers['x-forwarded-proto']}"`);
|
||||||
|
this.proxied = true;
|
||||||
} else {
|
} else {
|
||||||
self.socketBridge.emit('data', data);
|
this.proxied = false;
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// This bridge makes accessible various calls that client sub classes
|
|
||||||
// want to access on I/O socket
|
|
||||||
//
|
|
||||||
this.socketBridge = new class SocketBridge extends Writable {
|
|
||||||
constructor(ws) {
|
|
||||||
super();
|
|
||||||
this.ws = ws;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
end() {
|
// start handshake process
|
||||||
return ws.close();
|
this.banner();
|
||||||
}
|
|
||||||
|
|
||||||
write(data, cb) {
|
|
||||||
cb = cb || ( () => { /* eat it up */} ); // handle data writes after close
|
|
||||||
|
|
||||||
return this.ws.send(data, { binary : true }, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
pipe(dest) {
|
|
||||||
Log.trace('WebSocket SocketBridge pipe()');
|
|
||||||
self.pipedDest = dest;
|
|
||||||
}
|
|
||||||
|
|
||||||
unpipe() {
|
|
||||||
Log.trace('WebSocket SocketBridge unpipe()');
|
|
||||||
self.pipedDest = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
resume() {
|
|
||||||
Log.trace('WebSocket SocketBridge resume()');
|
|
||||||
}
|
|
||||||
|
|
||||||
get remoteAddress() {
|
|
||||||
// Support X-Forwarded-For and X-Real-IP headers for proxied connections
|
|
||||||
return (self.proxied && (req.headers['x-forwarded-for'] || req.headers['x-real-ip'])) || req.connection.remoteAddress;
|
|
||||||
}
|
|
||||||
}(ws);
|
|
||||||
|
|
||||||
ws.on('message', this.dataHandler);
|
|
||||||
|
|
||||||
ws.on('close', () => {
|
|
||||||
// we'll remove client connection which will in turn end() via our SocketBridge above
|
|
||||||
return this.emit('end');
|
|
||||||
});
|
|
||||||
|
|
||||||
//
|
|
||||||
// Monitor connection status with ping/pong
|
|
||||||
//
|
|
||||||
ws.on('pong', () => {
|
|
||||||
Log.trace(`Pong from ${this.socketBridge.remoteAddress}`);
|
|
||||||
ws.isConnectionAlive = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
TelnetClient.call(this, this.socketBridge, this.socketBridge);
|
|
||||||
|
|
||||||
Log.trace( { headers : req.headers }, 'WebSocket connection headers' );
|
|
||||||
|
|
||||||
//
|
|
||||||
// If the config allows it, look for 'x-forwarded-proto' as "https"
|
|
||||||
// to override |isSecure|
|
|
||||||
//
|
|
||||||
if(true === _.get(Config(), 'loginServers.webSocket.proxied') &&
|
|
||||||
'https' === req.headers['x-forwarded-proto'])
|
|
||||||
{
|
|
||||||
Log.debug(`Assuming secure connection due to X-Forwarded-Proto of "${req.headers['x-forwarded-proto']}"`);
|
|
||||||
this.proxied = true;
|
|
||||||
} else {
|
|
||||||
this.proxied = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// start handshake process
|
get isSecure() {
|
||||||
this.banner();
|
return ('secure' === this.serverType || true === this.proxied) ? true : false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
require('util').inherits(WebSocketClient, TelnetClient);
|
|
||||||
|
|
||||||
const WSS_SERVER_TYPES = [ 'insecure', 'secure' ];
|
const WSS_SERVER_TYPES = [ 'insecure', 'secure' ];
|
||||||
|
|
||||||
exports.getModule = class WebSocketLoginServer extends LoginServerModule {
|
exports.getModule = class WebSocketLoginServer extends LoginServerModule {
|
||||||
|
@ -216,7 +208,7 @@ exports.getModule = class WebSocketLoginServer extends LoginServerModule {
|
||||||
|
|
||||||
server.wsServer.on('connection', (ws, req) => {
|
server.wsServer.on('connection', (ws, req) => {
|
||||||
const webSocketClient = new WebSocketClient(ws, req, serverType);
|
const webSocketClient = new WebSocketClient(ws, req, serverType);
|
||||||
this.handleNewClient(webSocketClient, webSocketClient.socketBridge, ModuleInfo);
|
this.handleNewClient(webSocketClient, webSocketClient.socket, ModuleInfo);
|
||||||
});
|
});
|
||||||
|
|
||||||
Log.info( { server : serverName, port : port }, 'Listening for connections' );
|
Log.info( { server : serverName, port : port }, 'Listening for connections' );
|
||||||
|
@ -227,9 +219,4 @@ exports.getModule = class WebSocketLoginServer extends LoginServerModule {
|
||||||
cb(err);
|
cb(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
webSocketConnection(conn) {
|
|
||||||
const webSocketClient = new WebSocketClient(conn);
|
|
||||||
this.handleNewClient(webSocketClient, webSocketClient.socketShim, ModuleInfo);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,6 +13,7 @@ exports.pad = pad;
|
||||||
exports.insert = insert;
|
exports.insert = insert;
|
||||||
exports.replaceAt = replaceAt;
|
exports.replaceAt = replaceAt;
|
||||||
exports.isPrintable = isPrintable;
|
exports.isPrintable = isPrintable;
|
||||||
|
exports.containsNonLatinCodepoints = containsNonLatinCodepoints;
|
||||||
exports.stripAllLineFeeds = stripAllLineFeeds;
|
exports.stripAllLineFeeds = stripAllLineFeeds;
|
||||||
exports.debugEscapedString = debugEscapedString;
|
exports.debugEscapedString = debugEscapedString;
|
||||||
exports.stringFromNullTermBuffer = stringFromNullTermBuffer;
|
exports.stringFromNullTermBuffer = stringFromNullTermBuffer;
|
||||||
|
@ -28,6 +29,7 @@ exports.isAnsi = isAnsi;
|
||||||
exports.isAnsiLine = isAnsiLine;
|
exports.isAnsiLine = isAnsiLine;
|
||||||
exports.isFormattedLine = isFormattedLine;
|
exports.isFormattedLine = isFormattedLine;
|
||||||
exports.splitTextAtTerms = splitTextAtTerms;
|
exports.splitTextAtTerms = splitTextAtTerms;
|
||||||
|
exports.wildcardMatch = wildcardMatch;
|
||||||
|
|
||||||
// :TODO: create Unicode version of this
|
// :TODO: create Unicode version of this
|
||||||
const VOWELS = [
|
const VOWELS = [
|
||||||
|
@ -196,6 +198,20 @@ function isPrintable(s) {
|
||||||
return !RE_NON_PRINTABLE.test(s);
|
return !RE_NON_PRINTABLE.test(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const NonLatinCodePointsRegExp = /[^\u0000-\u00ff]/;
|
||||||
|
|
||||||
|
function containsNonLatinCodepoints(s) {
|
||||||
|
if (!s.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.charCodeAt(0) > 255) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NonLatinCodepointsRegEx.test(s);
|
||||||
|
}
|
||||||
|
|
||||||
function stripAllLineFeeds(s) {
|
function stripAllLineFeeds(s) {
|
||||||
return s.replace(/\r?\n|[\r\u2028\u2029]/g, '');
|
return s.replace(/\r?\n|[\r\u2028\u2029]/g, '');
|
||||||
}
|
}
|
||||||
|
@ -459,3 +475,8 @@ function isAnsi(input) {
|
||||||
function splitTextAtTerms(s) {
|
function splitTextAtTerms(s) {
|
||||||
return s.split(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g);
|
return s.split(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function wildcardMatch(input, rule) {
|
||||||
|
const escapeRegex = (s) => s.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
|
||||||
|
return new RegExp("^" + rule.split("*").map(escapeRegex).join(".*") + "$").test(input);
|
||||||
|
}
|
|
@ -11,7 +11,16 @@ const async = require('async');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const net = require('net');
|
const net = require('net');
|
||||||
const EventEmitter = require('events');
|
const EventEmitter = require('events');
|
||||||
const buffers = require('buffers');
|
|
||||||
|
const {
|
||||||
|
TelnetSocket,
|
||||||
|
TelnetSpec :
|
||||||
|
{
|
||||||
|
Commands,
|
||||||
|
Options,
|
||||||
|
SubNegotiationCommands,
|
||||||
|
},
|
||||||
|
} = require('telnet-socket');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Expected configuration block:
|
Expected configuration block:
|
||||||
|
@ -33,7 +42,10 @@ exports.moduleInfo = {
|
||||||
author : 'Andrew Pamment',
|
author : 'Andrew Pamment',
|
||||||
};
|
};
|
||||||
|
|
||||||
const IAC_DO_TERM_TYPE = Buffer.from( [ 255, 253, 24 ] );
|
const IAC_DO_TERM_TYPE = TelnetSocket.commandBuffer(
|
||||||
|
Commands.DO,
|
||||||
|
Options.TTYPE,
|
||||||
|
);
|
||||||
|
|
||||||
class TelnetClientConnection extends EventEmitter {
|
class TelnetClientConnection extends EventEmitter {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
|
@ -46,6 +58,7 @@ class TelnetClientConnection extends EventEmitter {
|
||||||
restorePipe() {
|
restorePipe() {
|
||||||
if(!this.pipeRestored) {
|
if(!this.pipeRestored) {
|
||||||
this.pipeRestored = true;
|
this.pipeRestored = true;
|
||||||
|
this.client.dataPassthrough = false;
|
||||||
|
|
||||||
// client may have bailed
|
// client may have bailed
|
||||||
if(null !== _.get(this, 'client.term.output', null)) {
|
if(null !== _.get(this, 'client.term.output', null)) {
|
||||||
|
@ -62,6 +75,7 @@ class TelnetClientConnection extends EventEmitter {
|
||||||
this.emit('connected');
|
this.emit('connected');
|
||||||
|
|
||||||
this.pipeRestored = false;
|
this.pipeRestored = false;
|
||||||
|
this.client.dataPassthrough = true;
|
||||||
this.client.term.output.pipe(this.bridgeConnection);
|
this.client.term.output.pipe(this.bridgeConnection);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -69,7 +83,7 @@ class TelnetClientConnection extends EventEmitter {
|
||||||
this.client.term.rawWrite(data);
|
this.client.term.rawWrite(data);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Wait for a terminal type request, and send it eactly once.
|
// Wait for a terminal type request, and send it exactly once.
|
||||||
// This is enough (in additional to other negotiations handled in telnet.js)
|
// This is enough (in additional to other negotiations handled in telnet.js)
|
||||||
// to get us in on most systems
|
// to get us in on most systems
|
||||||
//
|
//
|
||||||
|
@ -110,25 +124,18 @@ class TelnetClientConnection extends EventEmitter {
|
||||||
// Create a TERMINAL-TYPE sub negotiation buffer using the
|
// Create a TERMINAL-TYPE sub negotiation buffer using the
|
||||||
// actual/current terminal type.
|
// actual/current terminal type.
|
||||||
//
|
//
|
||||||
let bufs = buffers();
|
const sendTermType = TelnetSocket.commandBuffer(
|
||||||
|
Commands.SB,
|
||||||
bufs.push(Buffer.from(
|
Options.TTYPE,
|
||||||
[
|
[
|
||||||
255, // IAC
|
SubNegotiationCommands.IS,
|
||||||
250, // SB
|
...Buffer.from(this.client.term.termType), // e.g. "ansi"
|
||||||
24, // TERMINAL-TYPE
|
Commands.IAC,
|
||||||
0, // IS
|
Commands.SE,
|
||||||
]
|
]
|
||||||
));
|
|
||||||
|
|
||||||
bufs.push(
|
|
||||||
Buffer.from(this.client.term.termType), // e.g. "ansi"
|
|
||||||
Buffer.from( [ 255, 240 ] ) // IAC, SE
|
|
||||||
);
|
);
|
||||||
|
return sendTermType;
|
||||||
return bufs.toBuffer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getModule = class TelnetBridgeModule extends MenuModule {
|
exports.getModule = class TelnetBridgeModule extends MenuModule {
|
||||||
|
|
|
@ -81,9 +81,9 @@ function userLogin(client, username, password, options, cb) {
|
||||||
if(existingClientConnection) {
|
if(existingClientConnection) {
|
||||||
client.log.info(
|
client.log.info(
|
||||||
{
|
{
|
||||||
existingClientId : existingClientConnection.session.id,
|
existingNodeId : existingClientConnection.node,
|
||||||
username : user.username,
|
username : user.username,
|
||||||
userId : user.userId
|
userId : user.userId
|
||||||
},
|
},
|
||||||
'Already logged in'
|
'Already logged in'
|
||||||
);
|
);
|
||||||
|
@ -97,11 +97,12 @@ function userLogin(client, username, password, options, cb) {
|
||||||
// update client logger with addition of username
|
// update client logger with addition of username
|
||||||
client.log = logger.log.child(
|
client.log = logger.log.child(
|
||||||
{
|
{
|
||||||
clientId : client.log.fields.clientId,
|
nodeId : client.log.fields.nodeId,
|
||||||
sessionId : client.log.fields.sessionId,
|
sessionId : client.log.fields.sessionId,
|
||||||
username : user.username,
|
username : user.username,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
client.log.info('Successful login');
|
client.log.info('Successful login');
|
||||||
|
|
||||||
// User's unique session identifier is the same as the connection itself
|
// User's unique session identifier is the same as the connection itself
|
||||||
|
|
|
@ -29,7 +29,7 @@ module.exports = {
|
||||||
UserComment : 'user_comment', // NYI
|
UserComment : 'user_comment', // NYI
|
||||||
AutoSignature : 'auto_signature',
|
AutoSignature : 'auto_signature',
|
||||||
|
|
||||||
DownloadQueue : 'dl_queue', // download_queue.js
|
DownloadQueue : 'dl_queue', // see download_queue.js
|
||||||
|
|
||||||
FailedLoginAttempts : 'failed_login_attempts',
|
FailedLoginAttempts : 'failed_login_attempts',
|
||||||
AccountLockedTs : 'account_locked_timestamp',
|
AccountLockedTs : 'account_locked_timestamp',
|
||||||
|
@ -64,5 +64,6 @@ module.exports = {
|
||||||
AuthFactor2OTP : 'auth_factor2_otp', // If present, OTP type for 2FA. See OTPTypes
|
AuthFactor2OTP : 'auth_factor2_otp', // If present, OTP type for 2FA. See OTPTypes
|
||||||
AuthFactor2OTPSecret : 'auth_factor2_otp_secret', // Secret used in conjunction with OTP 2FA
|
AuthFactor2OTPSecret : 'auth_factor2_otp_secret', // Secret used in conjunction with OTP 2FA
|
||||||
AuthFactor2OTPBackupCodes : 'auth_factor2_otp_backup', // JSON array of backup codes
|
AuthFactor2OTPBackupCodes : 'auth_factor2_otp_backup', // JSON array of backup codes
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,8 @@
|
||||||
- [Message networks]({{ site.baseurl }}{% link messageareas/message-networks.md %})
|
- [Message networks]({{ site.baseurl }}{% link messageareas/message-networks.md %})
|
||||||
- [BSO Import & Export]({{ site.baseurl }}{% link messageareas/bso-import-export.md %})
|
- [BSO Import & Export]({{ site.baseurl }}{% link messageareas/bso-import-export.md %})
|
||||||
- [Netmail]({{ site.baseurl }}{% link messageareas/netmail.md %})
|
- [Netmail]({{ site.baseurl }}{% link messageareas/netmail.md %})
|
||||||
|
- [QWK]({{ site.baseurl }}{% link messageareas/qwk.md %})
|
||||||
|
- [FTN]({{ site.baseurl }}{% link messageareas/ftn.md %})
|
||||||
|
|
||||||
- Art
|
- Art
|
||||||
- [General]({{ site.baseurl }}{% link art/general.md %})
|
- [General]({{ site.baseurl }}{% link art/general.md %})
|
||||||
|
@ -65,6 +67,7 @@
|
||||||
- BBSLink
|
- BBSLink
|
||||||
- Combatnet
|
- Combatnet
|
||||||
- Exodus
|
- Exodus
|
||||||
|
- [Telnet Bridge]({{ site.baseurl }}{% link modding/telnet-bridge.md %})
|
||||||
- [Existing Mods]({{ site.baseurl }}{% link modding/existing-mods.md %})
|
- [Existing Mods]({{ site.baseurl }}{% link modding/existing-mods.md %})
|
||||||
- [File Area List]({{ site.baseurl }}{% link modding/file-area-list.md %})
|
- [File Area List]({{ site.baseurl }}{% link modding/file-area-list.md %})
|
||||||
- [Last Callers]({{ site.baseurl }}{% link modding/last-callers.md %})
|
- [Last Callers]({{ site.baseurl }}{% link modding/last-callers.md %})
|
||||||
|
@ -86,6 +89,7 @@
|
||||||
- [Auto Signature Editor]({{ site.baseurl }}{% link modding/autosig-edit.md %})
|
- [Auto Signature Editor]({{ site.baseurl }}{% link modding/autosig-edit.md %})
|
||||||
|
|
||||||
- Administration
|
- Administration
|
||||||
|
- [Administration]({{ site.baseurl }}{% link admin/administration.md %})
|
||||||
- [oputil]({{ site.baseurl }}{% link admin/oputil.md %})
|
- [oputil]({{ site.baseurl }}{% link admin/oputil.md %})
|
||||||
- [Updating]({{ site.baseurl }}{% link admin/updating.md %})
|
- [Updating]({{ site.baseurl }}{% link admin/updating.md %})
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
---
|
||||||
|
layout: page
|
||||||
|
title: Administration
|
||||||
|
---
|
||||||
|
|
||||||
|
# Administration
|
||||||
|
|
||||||
|
## Keeping Up to Date
|
||||||
|
See [Updating](updating.md).
|
||||||
|
|
||||||
|
## Viewing Logs
|
||||||
|
See [Monitoring Logs](/docs/troubleshooting/monitoring-logs.md).
|
||||||
|
|
||||||
|
## Managing Users
|
||||||
|
User management is currently handled via the [oputil CLI](oputil.md).
|
||||||
|
|
||||||
|
## Backing Up Your System
|
||||||
|
It is *highly* recommended to perform **regular backups** of your system. Nothing is worse than spending a lot of time setting up a system only to have to go away unexpectedly!
|
||||||
|
|
||||||
|
In general, simply creating a copy/archive of your system is enough for the default configuration. If you have changed default paths to point outside of your main ENiGMA½ installation take special care to ensure these are preserved as well. Database files may be in a state of flux when simply copying files. See **Database Backups** below for details on consistent backups.
|
||||||
|
|
||||||
|
### Database Backups
|
||||||
|
[SQLite's CLI backup command](https://sqlite.org/cli.html#special_commands_to_sqlite3_dot_commands_) can be used for creating database backup files. This can be performed as an additional step to a full backup to ensure the database is backed up in a consistent state (whereas simply copying the files does not make any guarantees).
|
||||||
|
|
||||||
|
As an example, consider the following Bash script that creates foo.sqlite3.backup files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for dbfile in /path/to/enigma-bbs/db/*.sqlite3; do
|
||||||
|
sqlite3 $dbfile ".backup '/path/to/db_backup/$(basename $dbfile).backup'"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backup Tools
|
||||||
|
There are many backup solutions available across all platforms. Configuration of such tools is outside the scope of this documentation. With that said, the author has had great success with [Borg](https://www.borgbackup.org/).
|
||||||
|
|
||||||
|
## General Maintenance Tasks
|
||||||
|
### Vacuuming Database Files
|
||||||
|
SQLite database files become less performant over time and waste space. It is recommended to periodically vacuum your databases. Before proceeding, you should make a backup!
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```bash
|
||||||
|
sqlite3 ./db/message.sqlite3 "vacuum;"
|
||||||
|
```
|
|
@ -291,17 +291,32 @@ Actions:
|
||||||
|
|
||||||
import-areas PATH Import areas using FidoNet *.NA or AREAS.BBS file
|
import-areas PATH Import areas using FidoNet *.NA or AREAS.BBS file
|
||||||
|
|
||||||
|
qwk-dump PATH Dumps a QWK packet to stdout.
|
||||||
|
qwk-export [AREA_TAGS] PATH Exports one or more configured message area to a QWK
|
||||||
|
packet in the directory specified by PATH. The QWK
|
||||||
|
BBS ID will be obtained by the final component of PATH.
|
||||||
|
|
||||||
import-areas arguments:
|
import-areas arguments:
|
||||||
--conf CONF_TAG Conference tag in which to import areas
|
--conf CONF_TAG Conference tag in which to import areas
|
||||||
--network NETWORK Network name/key to associate FTN areas
|
--network NETWORK Network name/key to associate FTN areas
|
||||||
--uplinks UL1,UL2,... One or more uplinks (comma separated)
|
--uplinks UL1,UL2,... One or more uplinks (comma separated)
|
||||||
--type TYPE Area import type
|
--type TYPE Area import type
|
||||||
|
|
||||||
Valid types are "bbs" and "na"
|
Valid types are "bbs" and "na".
|
||||||
|
|
||||||
|
qwk-export arguments:
|
||||||
|
--user USER User in which to export for. Defaults to the SysOp.
|
||||||
|
--after TIMESTAMP Export only messages with a timestamp later than
|
||||||
|
TIMESTAMP.
|
||||||
|
--no-qwke Disable QWKE extensions.
|
||||||
|
--no-synchronet Disable Synchronet style extensions.
|
||||||
```
|
```
|
||||||
|
|
||||||
| Action | Description | Examples |
|
| Action | Description | Examples |
|
||||||
|-----------|-------------------|---------------------------------------|
|
|-----------|-------------------|---------------------------------------|
|
||||||
| `import-areas` | Imports areas using a FidoNet style *.NA or AREAS.BBS formatted file. Optionally maps areas to FTN networks. | `./oputil.js config import-areas /some/path/l33tnet.na` |
|
| `import-areas` | Imports areas using a FidoNet style *.NA or AREAS.BBS formatted file. Optionally maps areas to FTN networks. | `./oputil.js config import-areas /some/path/l33tnet.na` |
|
||||||
|
| `areafix` | Utility for sending AreaFix mails without logging into the system | |
|
||||||
|
| `qwk-dump` | Dump a QWK packet to stdout | `./oputil.js mb qwk-dump /path/to/XIBALBA.QWK` |
|
||||||
|
| `qwk-export` | Export messages to a QWK packet | `./oputil.js mb qwk-export /path/to/XIBALBA.QWK` |
|
||||||
|
|
||||||
When using the `import-areas` action, you will be prompted for any missing additional arguments described in "import-areas args".
|
When using the `import-areas` action, you will be prompted for any missing additional arguments described in "import-areas args".
|
||||||
|
|
|
@ -2,19 +2,30 @@
|
||||||
layout: page
|
layout: page
|
||||||
title: Updating
|
title: Updating
|
||||||
---
|
---
|
||||||
## Updating your Installation
|
# Updating
|
||||||
Updating ENiGMA½ can be a bit of a learning curve compared to other systems. Especially when running off of a development branch (such as `0.0.9-alpha` being the recommended branch as of this writing), you'll want frequent updates.
|
Keeping your system up to date ensures you have the latest fixes, features, and general improvements. Updating ENiGMA½ can be a bit of a learning curve compared to traditional binary-release systems you may be used to, especially when running from Git cloned source.
|
||||||
|
|
||||||
## Steps
|
## Updating From Source
|
||||||
In general the steps are as follows:
|
If you have installed using Git source (if you used the `install.sh` script) follow these general steps to update your system:
|
||||||
1. `cd /path/to/enigma-bbs`
|
|
||||||
2. `git pull`
|
1. **Back up your system**!
|
||||||
3. `npm update` or `yarn` to refresh any new or updated modules.
|
2. Pull down the latest source:
|
||||||
4. Merge updates to `config/menu_template.hjson` to your `config/yourbbsname-menu.hjson` file.
|
```bash
|
||||||
|
cd /path/to/enigma-bbs
|
||||||
|
git pull
|
||||||
|
```
|
||||||
|
3. :bulb: Review `WHATSNEW.md` and `UPDATE.md` for any specific instructions or changes to be aware of.
|
||||||
|
4. Update your dependencies:
|
||||||
|
```bash
|
||||||
|
npm install # or 'yarn'
|
||||||
|
```
|
||||||
|
4. Merge updates from `config/menu_template.hjson` to your `config/yourbbsname-menu.hjson` file (or simply use the template as a reference to spot any newly added default menus that you may wish to have on your system as well!).
|
||||||
5. If there are updates to the `art/themes/luciano_blocktronics/theme.hjson` file and you have a custom theme, you may want to look at them as well.
|
5. If there are updates to the `art/themes/luciano_blocktronics/theme.hjson` file and you have a custom theme, you may want to look at them as well.
|
||||||
|
6. Finally, restart your running ENiGMA½ instance.
|
||||||
|
|
||||||
Visual diff tools such as [DiffMerge](https://www.sourcegear.com/diffmerge/downloads.php) (free, works on all major platforms) can be very helpful here.
|
:information_source: Visual diff tools such as [DiffMerge](https://www.sourcegear.com/diffmerge/downloads.php) (free, works on all major platforms) can be very helpful for the tasks outlined above!
|
||||||
|
|
||||||
|
:information_source: It is recommended to tail the logs and poke around a bit after an update.
|
||||||
|
|
||||||
Remember to also keep an eye on [WHATSNEW](/WHATSNEW.md) and [UPGRADE](/UPGRADE.md)!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,36 @@ One of the most basic elements of BBS customization is through it's artwork. ENi
|
||||||
|
|
||||||
As a general rule, art files live in one of two places:
|
As a general rule, art files live in one of two places:
|
||||||
|
|
||||||
1. The `art/general` directory. This is where you place command non-themed art files.
|
1. The `art/general` directory. This is where you place common/non-themed art files.
|
||||||
2. Within a theme such as `art/themes/super_fancy_theme`.
|
2. Within a _theme_ such as `art/themes/super_fancy_theme`.
|
||||||
|
|
||||||
### Menu Entries
|
### Art in Menus
|
||||||
While art can be displayed programmatically such as from a custom module, the most basic and common form is via `menu.hjson` entries. This usually falls into one of two forms: a "standard" entry where a single `art` spec is utilized or a entry for a custom module where multiple pieces are declared and used. The second style usually takes the form of a `config.art` block with two or more entries.
|
While art can be displayed programmatically such as from a custom module, the most basic and common form is via `menu.hjson` entries. This usually falls into one of two forms.
|
||||||
|
|
||||||
A menu entry has a few elements that control how art is choosen and displayed. First, the `art` *spec* tells teh system how to look for the art asset. Second, the `config` block can further control aspecs of lookup and display:
|
**Form 1**: A "standard" entry where a single `art` spec is utilized:
|
||||||
|
```hjson
|
||||||
|
{
|
||||||
|
mainMenu: {
|
||||||
|
art: main_menu.ans
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Form 2**: An entry for a custom module where multiple pieces are declared and used. The second style usually takes the form of a `config.art` block with two or more entries:
|
||||||
|
```hjson
|
||||||
|
{
|
||||||
|
nodeMessage: {
|
||||||
|
config: {
|
||||||
|
art: {
|
||||||
|
header: node_msg_header
|
||||||
|
footer: node_msg_footer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A menu entry has a few elements that control how art is selected and displayed. First, the `art` *spec* tells the system how to look for the art asset. Second, the `config` block can further control aspects of lookup and display. The following table describes such entries:
|
||||||
|
|
||||||
| Item | Description|
|
| Item | Description|
|
||||||
|------|------------|
|
|------|------------|
|
||||||
|
@ -23,13 +46,13 @@ A menu entry has a few elements that control how art is choosen and displayed. F
|
||||||
| `cls` | Clear the screen before display if set to `true`. |
|
| `cls` | Clear the screen before display if set to `true`. |
|
||||||
| `random` | Set to `false` to explicitly disable random lookup. |
|
| `random` | Set to `false` to explicitly disable random lookup. |
|
||||||
| `types` | An optional array of types (aka file extensions) to consider for lookup. For example : `[ '.ans', '.asc' ]` |
|
| `types` | An optional array of types (aka file extensions) to consider for lookup. For example : `[ '.ans', '.asc' ]` |
|
||||||
| `readSauce` | May be set to `false` if you need to explictly disable SAUCE support. |
|
| `readSauce` | May be set to `false` if you need to explicitly disable SAUCE support. |
|
||||||
|
|
||||||
#### Art Spec
|
#### Art Spec
|
||||||
It was mentioned that the `art` member is a *spec*. The value of a `art` member controls how the system looks for an asset. The following forms are supported:
|
In the section above it is mentioned that the `art` member is a *spec*. The value of a `art` spec controls how the system looks for an asset. The following forms are supported:
|
||||||
|
|
||||||
* `FOO`: The system will look for `FOO.ANS`, `FOO.ASC`, `FOO.TXT`, etc. using the default search path. Unless otherwise specified if `FOO1.ANS`, `FOO2.ANS`, and so on exist, a random selection will be made.
|
* `FOO`: The system will look for `FOO.ANS`, `FOO.ASC`, `FOO.TXT`, etc. using the default search path. Unless otherwise specified if `FOO1.ANS`, `FOO2.ANS`, and so on exist, a random selection will be made.
|
||||||
* `FOO.ANS`: By specifying an extension, only that type will be searched for.
|
* `FOO.ANS`: By specifying an extension, only the exact match will be searched for.
|
||||||
* `rel/path/to/BAR.ANS`: Only match a path (relative to the system's `art` directory).
|
* `rel/path/to/BAR.ANS`: Only match a path (relative to the system's `art` directory).
|
||||||
* `/path/to/BAZ.ANS`: Exact path only.
|
* `/path/to/BAZ.ANS`: Exact path only.
|
||||||
|
|
||||||
|
@ -40,8 +63,27 @@ ENiGMA½ uses a fallback system for art selection. When a menu entry calls for a
|
||||||
3. In the system default theme directory.
|
3. In the system default theme directory.
|
||||||
4. In the `art/general` directory.
|
4. In the `art/general` directory.
|
||||||
|
|
||||||
|
#### ACS-Driven Conditionals
|
||||||
|
The [ACS](/docs/configuration/acs.md) system can be used to make conditional art selection choices. To do this, provide an array of possible values in your art spec. As an example:
|
||||||
|
```hjson
|
||||||
|
{
|
||||||
|
fancyMenu: {
|
||||||
|
art: [
|
||||||
|
{
|
||||||
|
acs: GM[l33t]
|
||||||
|
art: leet_art.ans
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// default
|
||||||
|
art: newb.asc
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### SyncTERM Style Fonts
|
#### SyncTERM Style Fonts
|
||||||
ENiGMA½ can set a [SyncTERM](http://syncterm.bbsdev.net/) style font for art display. This is supported by many popular BBS terminals besides just SyncTERM and is common for displaying Amiga style fonts for example. The system will use the `font` specifier or look for a font declared in an artworks SAUCE record (unless `readSauce` is `false`).
|
ENiGMA½ can set a [SyncTERM](http://syncterm.bbsdev.net/) style font for art display. This is supported by many other popular BBS terminals as well. A common usage is for displaying Amiga style fonts for example. The system will use the `font` specifier or look for a font declared in an artworks SAUCE record (unless `readSauce` is `false`).
|
||||||
|
|
||||||
The most common fonts are probably as follows:
|
The most common fonts are probably as follows:
|
||||||
|
|
||||||
|
@ -96,7 +138,7 @@ See [this specification](https://github.com/protomouse/synchronet/blob/master/sr
|
||||||
#### SyncTERM Style Baud Rates
|
#### SyncTERM Style Baud Rates
|
||||||
The `baudRate` member can set a [SyncTERM](http://syncterm.bbsdev.net/) style emulated baud rate. May be `300`, `600`, `1200`, `2400`, `4800`, `9600`, `19200`, `38400`, `57600`, `76800`, or `115200`. A value of `ulimited`, `off`, or `0` resets (disables) the rate. See [this specification](https://github.com/protomouse/synchronet/blob/master/src/conio/cterm.txt) for more information.
|
The `baudRate` member can set a [SyncTERM](http://syncterm.bbsdev.net/) style emulated baud rate. May be `300`, `600`, `1200`, `2400`, `4800`, `9600`, `19200`, `38400`, `57600`, `76800`, or `115200`. A value of `ulimited`, `off`, or `0` resets (disables) the rate. See [this specification](https://github.com/protomouse/synchronet/blob/master/src/conio/cterm.txt) for more information.
|
||||||
|
|
||||||
## Common Example
|
### Common Example
|
||||||
```hjson
|
```hjson
|
||||||
fullLogoffSequenceRandomBoardAd: {
|
fullLogoffSequenceRandomBoardAd: {
|
||||||
art: OTHRBBS
|
art: OTHRBBS
|
||||||
|
@ -109,3 +151,6 @@ fullLogoffSequenceRandomBoardAd: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### See Also
|
||||||
|
See also the [Show Art Module](/docs/modding/show-art.md) for more advanced art display!
|
|
@ -16,8 +16,8 @@ for a full listing. Many codes attempt to pay homage to Oblivion/2, iNiQUiTY, et
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
|------|--------------|
|
|------|--------------|
|
||||||
| `BN` | Board Name |
|
| `BN` | Board Name |
|
||||||
| `VL` | Version *label*, e.g. "ENiGMA½ v0.0.10-alpha" |
|
| `VL` | Version *label*, e.g. "ENiGMA½ v0.0.11-beta" |
|
||||||
| `VN` | Version *number*, eg.. "0.0.10-alpha" |
|
| `VN` | Version *number*, eg.. "0.0.11-beta" |
|
||||||
| `SN` | SysOp username |
|
| `SN` | SysOp username |
|
||||||
| `SR` | SysOp real name |
|
| `SR` | SysOp real name |
|
||||||
| `SL` | SysOp location |
|
| `SL` | SysOp location |
|
||||||
|
|
|
@ -8,10 +8,10 @@ ENiGMA½ offers a powerful and flexible file base. Configuration of file the fil
|
||||||
## ENiGMA½ File Base Key Concepts
|
## ENiGMA½ File Base Key Concepts
|
||||||
First, there are some core concepts you should understand:
|
First, there are some core concepts you should understand:
|
||||||
* Storage Tags
|
* Storage Tags
|
||||||
* Areas (and Area Tags)
|
* Area Tags
|
||||||
|
|
||||||
### Storage Tags
|
### Storage Tags
|
||||||
*Storage Tags* define paths to physical (file) storage locations that are referenced in a file *Area* entry. Each entry may be either a fully qualified path or a relative path. Relative paths are relative to the value set by the `areaStoragePrefix` key (defaults to `/path/to/enigma-bbs/file_base`).
|
*Storage Tags* define paths to physical (filesystem) storage locations that are referenced in a file *Area* entry. Each entry may be either a fully qualified path or a relative path. Relative paths are relative to the value set by the `fileBase.areaStoragePrefix` key (defaults to `/path/to/enigma-bbs/file_base`).
|
||||||
|
|
||||||
Below is an example defining some storage tags using the relative and fully qualified forms:
|
Below is an example defining some storage tags using the relative and fully qualified forms:
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ storageTags: {
|
||||||
:information_source: Remember that paths are case sensitive on most non-Windows systems!
|
:information_source: Remember that paths are case sensitive on most non-Windows systems!
|
||||||
|
|
||||||
### Areas
|
### Areas
|
||||||
File base *Areas* are configured using the `fileBase::areas` configuration block in `config.hjson`. Valid members for an area are as follows:
|
File base *Areas* are configured using the `fileBase.areas` configuration block in `config.hjson`. Each entry's block starts with an *area tag*. Valid members for an area are as follows:
|
||||||
|
|
||||||
| Item | Required | Description |
|
| Item | Required | Description |
|
||||||
|--------|---------------|------------------|
|
|--------|---------------|------------------|
|
||||||
|
@ -41,7 +41,7 @@ Example areas section:
|
||||||
|
|
||||||
```hjson
|
```hjson
|
||||||
areas: {
|
areas: {
|
||||||
retro_pc: {
|
retro_pc: { // an area tag!
|
||||||
name: Retro PC
|
name: Retro PC
|
||||||
desc: Oldschool PC/DOS
|
desc: Oldschool PC/DOS
|
||||||
storageTags: [ "retro_pc_dos", "retro_pc_bbs" ]
|
storageTags: [ "retro_pc_dos", "retro_pc_bbs" ]
|
||||||
|
@ -55,6 +55,7 @@ This combines the two concepts described above. When viewing the file areas from
|
||||||
|
|
||||||
```hjson
|
```hjson
|
||||||
fileBase: {
|
fileBase: {
|
||||||
|
// override the default relative location
|
||||||
areaStoragePrefix: /enigma-bbs/file_base
|
areaStoragePrefix: /enigma-bbs/file_base
|
||||||
|
|
||||||
storageTags: {
|
storageTags: {
|
||||||
|
|
|
@ -20,3 +20,9 @@ ENiGMA½ has strayed away from the old familiar setup here and instead takes a m
|
||||||
* Duplicates are checked for by cryptographically secure [SHA-256](https://en.wikipedia.org/wiki/SHA-2) hashes.
|
* Duplicates are checked for by cryptographically secure [SHA-256](https://en.wikipedia.org/wiki/SHA-2) hashes.
|
||||||
* Support for many archive and file formats. External utilities can easily be added to the configuration to extend for additional formats.
|
* Support for many archive and file formats. External utilities can easily be added to the configuration to extend for additional formats.
|
||||||
* Much, much more!
|
* Much, much more!
|
||||||
|
|
||||||
|
### Modding
|
||||||
|
The default ENiGMA½ approach for file areas may not be for everyone. Remember that you can mod everything your setup! Some inspirational examples:
|
||||||
|
* A more traditional set of areas and scrolling file listings.
|
||||||
|
* An S/X style integration of message areas and file areas.
|
||||||
|
* Something completely different! Some tweaks are possible without any code while others may require creating new JavaScript modules to use instead of the defaults.
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
layout: page
|
layout: page
|
||||||
title: TIC Support
|
title: TIC Support
|
||||||
---
|
---
|
||||||
ENiGMA½ supports TIC files. This is handled by mapping TIC areas to local file areas.
|
## TIC Support
|
||||||
|
ENiGMA½ supports FidoNet-Style TIC file attachments by mapping TIC areas to local file areas.
|
||||||
|
|
||||||
Under a given node defined in the `ftn_bso` config section in `config.hjson` (see
|
Under a given node defined in the `ftn_bso` config section in `config.hjson` (see
|
||||||
[BSO Import/Export](../messageareas/bso-import-export)), TIC configuration may be supplied:
|
[BSO Import/Export](../messageareas/bso-import-export)), TIC configuration may be supplied:
|
||||||
|
@ -28,11 +29,9 @@ Under a given node defined in the `ftn_bso` config section in `config.hjson` (se
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You then need to configure the mapping between TIC areas you want to carry, and the file
|
You then need to configure the mapping between TIC areas you want to carry, and the file base area and storage tag for them to be tossed to. Optionally you can also add hashtags to the tossed files to assist users in searching for files:
|
||||||
base area and storage tag for them to be tossed to. Optionally you can also add hashtags to the tossed
|
|
||||||
files to assist users in searching for files:
|
|
||||||
|
|
||||||
````hjson
|
```hjson
|
||||||
ticAreas: {
|
ticAreas: {
|
||||||
agn_node: {
|
agn_node: {
|
||||||
areaTag: msgNetworks
|
areaTag: msgNetworks
|
||||||
|
@ -41,14 +40,13 @@ ticAreas: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
````
|
```
|
||||||
Multiple TIC areas can be mapped to a single file base area.
|
Multiple TIC areas can be mapped to a single file base area.
|
||||||
|
|
||||||
## Example Configuration
|
### Example Configuration
|
||||||
|
An example configuration linking file base areas, FTN BSO node configuration and TIC area configuration.
|
||||||
|
|
||||||
An example configuration linking filebase areas, FTN BSO node configuration and TIC area configuration.
|
```hjson
|
||||||
|
|
||||||
````hjson
|
|
||||||
fileBase: {
|
fileBase: {
|
||||||
areaStoragePrefix: /home/bbs/file_areas/
|
areaStoragePrefix: /home/bbs/file_areas/
|
||||||
|
|
||||||
|
@ -97,4 +95,7 @@ ticAreas: {
|
||||||
hashTags: agoranet,infopack
|
hashTags: agoranet,infopack
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
````
|
```
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
[Message Networks](/docs/messageareas/message-networks.md)
|
||||||
|
|
|
@ -3,9 +3,9 @@ layout: page
|
||||||
title: Uploads
|
title: Uploads
|
||||||
---
|
---
|
||||||
## Uploads
|
## Uploads
|
||||||
The default ACS for file areas areas in ENiGMA½ is to allow read (viewing of the area), and downloads for users while only permitting SysOps to write (upload). See [File Base ACS](acs.md) for more information.
|
The default ACS for file areas in ENiGMA½ is to allow regular users 'read' and sysops 'read/write'. Read ACS includes listing and downloading while write allows for uploading. See [File Base ACS](acs.md) for more information.
|
||||||
|
|
||||||
To allow uploads to a particular area, change the ACS level for `write`. For example:
|
Let's allow regular users (in the "users" group) to upload to an area:
|
||||||
```hjson
|
```hjson
|
||||||
uploads: {
|
uploads: {
|
||||||
name: Uploads
|
name: Uploads
|
||||||
|
|
|
@ -6,10 +6,10 @@ title: Install Script
|
||||||
Under most Linux/UNIX like environments (Linux, BSD, OS X, ...) new users can simply execute the `install.sh` script to get everything up and running. Cut + paste the following into your terminal:
|
Under most Linux/UNIX like environments (Linux, BSD, OS X, ...) new users can simply execute the `install.sh` script to get everything up and running. Cut + paste the following into your terminal:
|
||||||
|
|
||||||
```
|
```
|
||||||
curl -o- https://raw.githubusercontent.com/NuSkooler/enigma-bbs/master/misc/install.sh | bash
|
curl -o- https://raw.githubusercontent.com/NuSkooler/enigma-bbs/0.0.11-beta/misc/install.sh | bash
|
||||||
```
|
```
|
||||||
|
|
||||||
You may review the [installation script](https://raw.githubusercontent.com/NuSkooler/enigma-bbs/master/misc/install.sh)
|
You may review the [installation script](https://raw.githubusercontent.com/NuSkooler/enigma-bbs/0.0.11-beta/misc/install.sh)
|
||||||
on GitHub before running it.
|
on GitHub before running it.
|
||||||
|
|
||||||
The script will install nvm, Node.js 6 and grab the latest ENiGMA BBS from GitHub. It will also guide you through creating a basic configuration file, and recommend some packages to install.
|
The script will install nvm, Node.js 6 and grab the latest ENiGMA BBS from GitHub. It will also guide you through creating a basic configuration file, and recommend some packages to install.
|
||||||
|
|
|
@ -28,6 +28,8 @@ System started!
|
||||||
```
|
```
|
||||||
Grab your favourite telnet client, connect to localhost:8888 and test out your installation.
|
Grab your favourite telnet client, connect to localhost:8888 and test out your installation.
|
||||||
|
|
||||||
|
To shut down the server, press Ctrl-C.
|
||||||
|
|
||||||
## Points of Interest
|
## Points of Interest
|
||||||
|
|
||||||
* The default port for Telnet is 8888 and 8889 for SSH.
|
* The default port for Telnet is 8888 and 8889 for SSH.
|
||||||
|
|
|
@ -5,57 +5,22 @@ title: BSO Import / Export
|
||||||
## BSO Import / Export
|
## BSO Import / Export
|
||||||
The scanner/tosser module `ftn_bso` provides **B**inkley **S**tyle **O**utbound (BSO) import/toss and scan/export of messages EchoMail and NetMail messages. Configuration is supplied in `config.hjson` under `scannerTossers.ftn_bso`.
|
The scanner/tosser module `ftn_bso` provides **B**inkley **S**tyle **O**utbound (BSO) import/toss and scan/export of messages EchoMail and NetMail messages. Configuration is supplied in `config.hjson` under `scannerTossers.ftn_bso`.
|
||||||
|
|
||||||
:information_source: ENiGMA½'s `ftn_bso` module is not a mailer and **makes no attempts** to perfrom packet transport! An external [mailer](http://www.filegate.net/bbsmailers.htm) such as [Binkd](https://github.com/pgul/binkd) is required for this!
|
:information_source: ENiGMA½'s `ftn_bso` module is not a mailer and **makes no attempts to perform packet transport**! An external [mailer](http://www.filegate.net/bbsmailers.htm) such as [Binkd](https://github.com/pgul/binkd) is required for this task.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
Let's look at some of the basic configuration:
|
Let's look at some of the basic configuration:
|
||||||
|
|
||||||
| Config Item | Required | Description |
|
| Config Item | Required | Description |
|
||||||
|-------------|----------|----------------------------------------------------------|
|
|-------------|----------|----------------------------------------------------------|
|
||||||
| `schedule` | :+1: | Sets `import` and `export` schedules. [Later style text parsing](https://bunkat.github.io/later/parsers.html#text) supported. `import` also can utilize a `@watch:<path/to/file>` syntax while `export` additionally supports `@immediate`. |
|
| `schedule` | :+1: | Sets `import` and `export` schedules. [Later style text parsing](https://bunkat.github.io/later/parsers.html#text) supported. `import` also can utilize a `@watch:<path/to/file>` syntax while `export` additionally supports `@immediate`. |
|
||||||
| `packetMsgEncoding` | :-1: | Override default `utf8` encoding.
|
| `packetMsgEncoding` | :-1: | Override default `utf8` encoding.
|
||||||
| `defaultNetwork` | :-1: | Explicitly set default network (by tag in `messageNetworks.ftn.networks`). If not set, the first found is used. |
|
| `defaultNetwork` | :-1: | Explicitly set default network (by tag found within `messageNetworks.ftn.networks`). If not set, the first found is used. |
|
||||||
| `nodes` | :+1: | Per-node settings. Entries (keys) here support wildcards for a portion of the FTN-style address (e.g.: `21:1/*`). `archiveType` may be set to a FTN supported archive extention that the system supports (TODO); if unset, only .PKT files are produced. `encoding` may be set to override `packetMsgEncoding` on a per-node basis. If the node requires a packet password, set `packetPassword` |
|
| `nodes` | :+1: | Per-node settings. Entries (keys) here support wildcards for a portion of the FTN-style address (e.g.: `21:1/*`). See **Nodes** below.
|
||||||
| `paths` | :-1: | An optional configuration block that can set a additional paths or override defaults. See "Paths" below. |
|
| `paths` | :-1: | An optional configuration block that can set a additional paths or override defaults. See **Paths** below. |
|
||||||
| `packetTargetByteSize` | :-1: | Overrides the system *target* packet (.pkt) size of 512000 bytes (512k) |
|
| `packetTargetByteSize` | :-1: | Overrides the system *target* packet (.pkt) size of 512000 bytes (512k) |
|
||||||
| `bundleTargetByteSize` | :-1: | Overrides the system *target* ArcMail bundle size of 2048000 bytes (2M) |
|
| `bundleTargetByteSize` | :-1: | Overrides the system *target* ArcMail bundle size of 2048000 bytes (2M) |
|
||||||
|
|
||||||
### Paths
|
#### Nodes
|
||||||
Paths for packet files work out of the box and are relative to your install directory. If you want to configure `reject` or `retain` to keep rejected/imported packet files respectively, set those values. You may override defaults as well.
|
|
||||||
|
|
||||||
| Key | Description | Default |
|
|
||||||
|-----|-------------|---------|
|
|
||||||
| `outbound` | *Base* path to write outbound (exported) packet files and bundles. | `enigma-bbs/mail/ftn_out/` |
|
|
||||||
| `inbound` | *Base* path to write inbound (ie: those written by an external mailer) packet files an bundles. | `enigma-bbs/mail/ftn_in/` |
|
|
||||||
| `secInbound` | *Base* path to write **secure** inbound packet files and bundles. | `enigma-bbs/mail/ftn_secin/` |
|
|
||||||
| `reject` | Path in which to write rejected packet files. | No default |
|
|
||||||
| `retain` | Path in which to write imported packet files. Useful for debugging or if you wish to archive the raw .pkt files. | No default |
|
|
||||||
|
|
||||||
|
|
||||||
## Scheduling
|
|
||||||
Schedules can be defined for importing and exporting via `import` and `export` under `schedule`. Each entry is allowed a "free form" text and/or special indicators for immediate export or watch file triggers.
|
|
||||||
|
|
||||||
* `@immediate`: A message will be immediately exported if this trigger is defined in a schedule. Only used for `export`.
|
|
||||||
* `@watch:/path/to/file`: This trigger watches the path specified for changes and will trigger an import or export when such events occur. Only used for `import`.
|
|
||||||
* Free form [Later style](https://bunkat.github.io/later/parsers.html#text) text — can be things like `at 5:00 pm` or `every 2 hours`.
|
|
||||||
|
|
||||||
See [Later text parsing documentation](http://bunkat.github.io/later/parsers.html#text) for more information.
|
|
||||||
|
|
||||||
### Example Schedule Configuration
|
|
||||||
|
|
||||||
```hjson
|
|
||||||
{
|
|
||||||
scannerTossers: {
|
|
||||||
ftn_bso: {
|
|
||||||
schedule: {
|
|
||||||
import: every 1 hours or @watch:/path/to/watchfile.ext
|
|
||||||
export: every 1 hours or @immediate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Nodes
|
|
||||||
The `nodes` section defines how to export messages for one or more uplinks.
|
The `nodes` section defines how to export messages for one or more uplinks.
|
||||||
|
|
||||||
A node entry starts with a [FTN address](http://ftsc.org/docs/old/fsp-1028.001) (up to 5D) **as a key** in `config.hjson`. This key may contain wildcard(s) for net/zone/node/point/domain.
|
A node entry starts with a [FTN address](http://ftsc.org/docs/old/fsp-1028.001) (up to 5D) **as a key** in `config.hjson`. This key may contain wildcard(s) for net/zone/node/point/domain.
|
||||||
|
@ -65,7 +30,7 @@ A node entry starts with a [FTN address](http://ftsc.org/docs/old/fsp-1028.001)
|
||||||
| `packetType` | :-1: | `2`, `2.2`, or `2+`. Defaults to `2+` for modern mailer compatiability. |
|
| `packetType` | :-1: | `2`, `2.2`, or `2+`. Defaults to `2+` for modern mailer compatiability. |
|
||||||
| `packetPassword` | :-1: | Optional password for the packet |
|
| `packetPassword` | :-1: | Optional password for the packet |
|
||||||
| `encoding` | :-1: | Encoding to use for message bodies; Defaults to `utf-8`. |
|
| `encoding` | :-1: | Encoding to use for message bodies; Defaults to `utf-8`. |
|
||||||
| `archiveType` | :-1: | Specifies the archive type (by extension) for ArcMail bundles. This should be `zip` for most setups. Other valid examples include `arc`, `arj`, `lhz`, `pak`, `sqz`, or `zoo`. See [Archivers](docs/configuration/archivers.md) for more information. |
|
| `archiveType` | :-1: | Specifies the archive type (by extension or MIME type) for ArcMail bundles. This should be `zip` (or `application/zip`) for most setups. Other valid examples include `arc`, `arj`, `lhz`, `pak`, `sqz`, or `zoo`. See [Archivers](docs/configuration/archivers.md) for more information. |
|
||||||
|
|
||||||
**Example**:
|
**Example**:
|
||||||
```hjson
|
```hjson
|
||||||
|
@ -85,7 +50,42 @@ A node entry starts with a [FTN address](http://ftsc.org/docs/old/fsp-1028.001)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## A More Complete Example
|
#### Paths
|
||||||
|
Paths for packet files work out of the box and are relative to your install directory. If you want to configure `reject` or `retain` to keep rejected/imported packet files respectively, set those values. You may override defaults as well.
|
||||||
|
|
||||||
|
| Key | Description | Default |
|
||||||
|
|-----|-------------|---------|
|
||||||
|
| `outbound` | *Base* path to write outbound (exported) packet files and bundles. | `enigma-bbs/mail/ftn_out/` |
|
||||||
|
| `inbound` | *Base* path to write inbound (ie: those written by an external mailer) packet files an bundles. | `enigma-bbs/mail/ftn_in/` |
|
||||||
|
| `secInbound` | *Base* path to write **secure** inbound packet files and bundles. | `enigma-bbs/mail/ftn_secin/` |
|
||||||
|
| `reject` | Path in which to write rejected packet files. | No default |
|
||||||
|
| `retain` | Path in which to write imported packet files. Useful for debugging or if you wish to archive the raw .pkt files. | No default |
|
||||||
|
|
||||||
|
### Scheduling
|
||||||
|
Schedules can be defined for importing and exporting via `import` and `export` under `schedule`. Each entry is allowed a "free form" text and/or special indicators for immediate export or watch file triggers.
|
||||||
|
|
||||||
|
* `@immediate`: A message will be immediately exported if this trigger is defined in a schedule. Only used for `export`.
|
||||||
|
* `@watch:/path/to/file`: This trigger watches the path specified for changes and will trigger an import or export when such events occur. Only used for `import`.
|
||||||
|
* Free form [Later style](https://bunkat.github.io/later/parsers.html#text) text — can be things like `at 5:00 pm` or `every 2 hours`.
|
||||||
|
|
||||||
|
See [Later text parsing documentation](http://bunkat.github.io/later/parsers.html#text) for more information.
|
||||||
|
|
||||||
|
#### Example Schedule Configuration
|
||||||
|
|
||||||
|
```hjson
|
||||||
|
{
|
||||||
|
scannerTossers: {
|
||||||
|
ftn_bso: {
|
||||||
|
schedule: {
|
||||||
|
import: every 1 hours or @watch:/path/to/watchfile.ext
|
||||||
|
export: every 1 hours or @immediate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### A More Complete Example
|
||||||
Below is a more complete example showing the sections described above.
|
Below is a more complete example showing the sections described above.
|
||||||
|
|
||||||
```hjson
|
```hjson
|
||||||
|
@ -149,7 +149,7 @@ do
|
||||||
done
|
done
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, create an Event Scheuler entry in your `config.hjson`. As an example:
|
Now, create an Event Scheduler entry in your `config.hjson`. As an example:
|
||||||
```hjson
|
```hjson
|
||||||
eventScheduler: {
|
eventScheduler: {
|
||||||
events: {
|
events: {
|
||||||
|
@ -163,4 +163,4 @@ eventScheduler: {
|
||||||
```
|
```
|
||||||
|
|
||||||
## Additional Resources
|
## Additional Resources
|
||||||
* [Blog entry on setting up ENiGMA + Binkd on CentOS7](https://l33t.codes/enigma-12-binkd-on-centos-7/). Note that this references an **older version**, so be wary of the `config.hjson` refernces!
|
[Blog entry on setting up ENiGMA + Binkd on CentOS7](https://l33t.codes/enigma-12-binkd-on-centos-7/). Note that this references an **older version**, so be wary of the `config.hjson` references!
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
---
|
||||||
|
layout: page
|
||||||
|
title: FidoNet-Style Networks (FTN)
|
||||||
|
---
|
||||||
|
|
||||||
|
## FidoNet-Style Networks (FTN)
|
||||||
|
[FidoNet](https://en.wikipedia.org/wiki/FidoNet) proper and other FidoNet-Style networks are supported by ENiGMA½. A bit of configuration and you'll be up and running in no time!
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
Getting a fully running FTN enabled system requires a few configuration points:
|
||||||
|
|
||||||
|
1. `messageNetworks.ftn.networks`: Declares available networks. That is, networks you wish to sync up with.
|
||||||
|
2. `messageNetworks.ftn.areas`: Establishes local area mappings (ENiGMA½ to/from FTN area tags) and per-area specific configurations.
|
||||||
|
3. `scannerTossers.ftn_bso`: General configuration for the scanner/tosser (import/export) process. This is also where we configure per-node (uplink) settings.
|
||||||
|
|
||||||
|
:information_source: ENiGMA½'s `ftn_bso` module is **not a mailer** and makes **no attempts** to perform packet transport! An external utility such as Binkd is required for this task.
|
||||||
|
|
||||||
|
#### Networks
|
||||||
|
The `networks` block is a per-network configuration where each entry's ID (or "key") may be referenced elsewhere in `config.hjson`. For example, consider two networks: ArakNet (`araknet`) and fsxNet (`fsxnet`):
|
||||||
|
|
||||||
|
```hjson
|
||||||
|
{
|
||||||
|
messageNetworks: {
|
||||||
|
ftn: {
|
||||||
|
networks: {
|
||||||
|
// it is recommended to use lowercase network tags
|
||||||
|
fsxnet: {
|
||||||
|
defaultZone: 21
|
||||||
|
localAddress: "21:1/121"
|
||||||
|
}
|
||||||
|
|
||||||
|
araknet: {
|
||||||
|
defaultZone: 10
|
||||||
|
localAddress: "10:101/9"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Areas
|
||||||
|
The `areas` section describes a mapping of local **area tags** configured in your `messageConferences` (see [Configuring a Message Area](configuring-a-message-area.md)) to a message network (described above), a FTN specific area tag, and remote uplink address(s). This section can be thought of similar to the *AREAS.BBS* file used by other BBS packages.
|
||||||
|
|
||||||
|
When ENiGMA½ imports messages, they will be placed in the local area that matches key under `areas` while exported messages will be sent to the relevant `network`.
|
||||||
|
|
||||||
|
| Config Item | Required | Description |
|
||||||
|
|-------------|----------|----------------------------------------------------------|
|
||||||
|
| `network` | :+1: | Associated network from the `networks` section above |
|
||||||
|
| `tag` | :+1: | FTN area tag (ie: `FSX_GEN`) |
|
||||||
|
| `uplinks` | :+1: | An array of FTN address uplink(s) for this network |
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```hjson
|
||||||
|
{
|
||||||
|
messageNetworks: {
|
||||||
|
ftn: {
|
||||||
|
areas: {
|
||||||
|
// it is recommended to use lowercase area tags
|
||||||
|
fsx_general: // *local* tag found within messageConferences
|
||||||
|
network: fsxnet // that we are mapping to this network
|
||||||
|
tag: FSX_GEN // ...and this remote FTN-specific tag
|
||||||
|
uplinks: [ "21:1/100" ] // a single string also allowed here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:information_source: You can import `AREAS.BBS` or FTN style `.NA` files using [oputil](/docs/admin/oputil.md)!
|
||||||
|
|
||||||
|
#### A More Complete Example
|
||||||
|
Below is a more complete *example* illustrating some of the concepts above:
|
||||||
|
|
||||||
|
```hjson
|
||||||
|
{
|
||||||
|
messageNetworks: {
|
||||||
|
ftn: {
|
||||||
|
networks: {
|
||||||
|
fsxnet: {
|
||||||
|
defaultZone: 21
|
||||||
|
localAddress: "21:1/121"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
areas: {
|
||||||
|
fsx_general: {
|
||||||
|
network: fsxnet
|
||||||
|
|
||||||
|
// ie as found in your info packs .NA file
|
||||||
|
tag: FSX_GEN
|
||||||
|
|
||||||
|
uplinks: [ "21:1/100" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:information_source: Remember for a complete FTN experience, you'll probably also want to configure [FTN/BSO scanner/tosser](bso-import-export.md) settings.
|
||||||
|
|
||||||
|
#### FTN/BSO Scanner Tosser
|
||||||
|
Please see the [FTN/BSO Scanner/Tosser](bso-import-export.md) documentation for information on this area.
|
|
@ -3,103 +3,22 @@ layout: page
|
||||||
title: Message Networks
|
title: Message Networks
|
||||||
---
|
---
|
||||||
## Message Networks
|
## Message Networks
|
||||||
ENiGMA½ considers all non-ENiGMA½, non-local messages (and their networks, such as FTN "external". That is, messages are only imported and exported from/to such a networks. Configuring such external message networks in ENiGMA½ requires three sections in your `config.hjson`.
|
ENiGMA½ supports external networks such as FidoNet-Style (FTN) and QWK by the way of importing and exporting to/from it's own internal format. This allows for a very flexible system that can easily be extended by creating new network modules.
|
||||||
|
|
||||||
1. `messageNetworks.<networkType>.networks`: declares available networks.
|
All message network configuration occurs under the `messageNetworks.<name>` block in `config.hjson` (where name is something such as `ftn` or `qwk`). The most basic of external message network configurations generally comprises of two sections:
|
||||||
2. `messageNetworks.<networkType>.areas`: establishes local area mappings and per-area specifics.
|
|
||||||
3. `scannerTossers.<name>`: general configuration for the scanner/tosser (import/export). This is also where we configure per-node settings.
|
|
||||||
|
|
||||||
### FTN Networks
|
1. `messageNetworks.<name>.networks`: Global/general configuration for a particular network where `<name>` is for example `ftn` or `qwk`.
|
||||||
|
2. `messageNetworks.<name>.areas`: Provides mapping of ENiGMA½ **area tags** to their external counterparts.
|
||||||
|
|
||||||
|
:information_source: A related section under `scannerTossers.<name>` may provide configuration for scanning (importing) and tossing (exporting) messages for a particular network type. As an example, FidoNet-Style networks often work with BinkleyTerm Style Outbound (BSO) and thus the [FTN/BSO scanner/tosser](bso-import-export.md) (`ftn_bso`) module.
|
||||||
|
|
||||||
|
### Currently Supported Networks
|
||||||
|
The following networks are supported out of the box. Remember that you can create modules to add others if desired!
|
||||||
|
|
||||||
|
#### FidoNet-Style (FTN)
|
||||||
FidoNet and FidoNet style (FTN) networks as well as a [FTN/BSO scanner/tosser](bso-import-export.md) (`ftn_bso` module) are configured via the `messageNetworks.ftn` and `scannerTossers.ftn_bso` blocks in `config.hjson`.
|
FidoNet and FidoNet style (FTN) networks as well as a [FTN/BSO scanner/tosser](bso-import-export.md) (`ftn_bso` module) are configured via the `messageNetworks.ftn` and `scannerTossers.ftn_bso` blocks in `config.hjson`.
|
||||||
|
|
||||||
:information_source: ENiGMA½'s `ftn_bso` module is **not a mailer** and makes **no attempts** to perform packet transport! An external utility such as Binkd is required for this!
|
See [FidoNet-Style Networks](ftn.md) for more information.
|
||||||
|
|
||||||
#### Networks
|
#### QWK
|
||||||
The `networks` block a per-network configuration where each entry's key may be referenced elsewhere in `config.hjson`.
|
See [QWK and QWK-Net Style Networks](qwk.md) for more information.
|
||||||
|
|
||||||
Example: the following example declares two networks: `araknet` and `fsxnet`:
|
|
||||||
```hjson
|
|
||||||
{
|
|
||||||
messageNetworks: {
|
|
||||||
ftn: {
|
|
||||||
networks: {
|
|
||||||
// it is recommended to use lowercase network tags
|
|
||||||
fsxnet: {
|
|
||||||
defaultZone: 21
|
|
||||||
localAddress: "21:1/121"
|
|
||||||
}
|
|
||||||
|
|
||||||
araknet: {
|
|
||||||
defaultZone: 10
|
|
||||||
localAddress: "10:101/9"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Areas
|
|
||||||
The `areas` section describes a mapping of local **area tags** configured in your `messageConferences` (see [Configuring a Message Area](configuring-a-message-area.md)) to a message network (described above), a FTN specific area tag, and remote uplink address(s). This section can be thought of similar to the *AREAS.BBS* file used by other BBS packages.
|
|
||||||
|
|
||||||
When ENiGMA½ imports messages, they will be placed in the local area that matches key under `areas` while exported messages will be sent to the relevant `network`.
|
|
||||||
|
|
||||||
| Config Item | Required | Description |
|
|
||||||
|-------------|----------|----------------------------------------------------------|
|
|
||||||
| `network` | :+1: | Associated network from the `networks` section above |
|
|
||||||
| `tag` | :+1: | FTN area tag (ie: `FSX_GEN`) |
|
|
||||||
| `uplinks` | :+1: | An array of FTN address uplink(s) for this network |
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```hjson
|
|
||||||
{
|
|
||||||
messageNetworks: {
|
|
||||||
ftn: {
|
|
||||||
areas: {
|
|
||||||
// it is recommended to use lowercase area tags
|
|
||||||
fsx_general: // *local* tag found within messageConferences
|
|
||||||
network: fsxnet // that we are mapping to this network
|
|
||||||
tag: FSX_GEN // ...and this remote FTN-specific tag
|
|
||||||
uplinks: [ "21:1/100" ] // a single string also allowed here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
:information_source: You can import `AREAS.BBS` or FTN style `.NA` files using [oputil](/docs/admin/oputil.md)!
|
|
||||||
|
|
||||||
### A More Complete Example
|
|
||||||
Below is a more complete *example* illustrating some of the concepts above:
|
|
||||||
|
|
||||||
```hjson
|
|
||||||
{
|
|
||||||
messageNetworks: {
|
|
||||||
ftn: {
|
|
||||||
networks: {
|
|
||||||
fsxnet: {
|
|
||||||
defaultZone: 21
|
|
||||||
localAddress: "21:1/121"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
areas: {
|
|
||||||
fsx_general: {
|
|
||||||
network: fsxnet
|
|
||||||
|
|
||||||
// ie as found in your info packs .NA file
|
|
||||||
tag: FSX_GEN
|
|
||||||
|
|
||||||
uplinks: [ "21:1/100" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
:information_source: Remember for a complete FTN experience, you'll probably also want to configure [FTN/BSO scanner/tosser](bso-import-export.md) settings.
|
|
||||||
|
|
||||||
### FTN/BSO Scanner Tosser
|
|
||||||
Please see the [FTN/BSO Scanner/Tosser](bso-import-export.md) documentation for information on this area.
|
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
---
|
||||||
|
layout: page
|
||||||
|
title: QWK Support
|
||||||
|
---
|
||||||
|
|
||||||
|
## QWK and QWK-Net Style Networks
|
||||||
|
As like all other networks such as FidoNet-Style (FTN) networks, ENiGMA½ considers QWK external to the system but can import and export the format.
|
||||||
|
|
||||||
|
### Supported Standards
|
||||||
|
QWK must be considered a semi-standard as there are many implementations. What follows is a short & incomplete list of such standards ENiGMA½ supports:
|
||||||
|
* The basic [QWK packet format](http://fileformats.archiveteam.org/wiki/QWK).
|
||||||
|
* [QWKE extensions](https://github.com/wwivbbs/wwiv/blob/master/specs/qwk/qwke.txt).
|
||||||
|
* [Synchronet BBS style extensions](http://wiki.synchro.net/ref:qwk) such as `HEADERS.DAT`, `@` kludges, and UTF-8 handling.
|
||||||
|
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
QWK configuration occurs in the `messageNetworks.qwk` config block of `config.hjson`. As QWK wants to deal with conference numbers and ENiGMA½ uses area tags (conferences and conference tags are only used for logical grouping), a mapping can be made.
|
||||||
|
|
||||||
|
:information_source: During a regular, non QWK-Net exports, conference numbers can be auto-generated. Note that for QWK-Net style networks, you will need to create mappings however.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```hjson
|
||||||
|
{
|
||||||
|
messageNetworks: {
|
||||||
|
qwk: {
|
||||||
|
areas: {
|
||||||
|
general: { // local ENiGMA½ area tag
|
||||||
|
conference: 1 // conference number to map to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### oputil
|
||||||
|
The `oputil.js` utility can export packet files, dump the messages of a packet to stdout, etc. See [the oputil documentation](/docs/admin/oputil.md) for more information.
|
||||||
|
|
||||||
|
### Offline Readers
|
||||||
|
A few of the offline readers that have been tested with QWK packet files produced by ENiGMA½:
|
||||||
|
|
||||||
|
| Software | Status | Notes |
|
||||||
|
|----------|--------|-------|
|
||||||
|
| MultiMail/Win v0.52 | Supported | Private mail seems to break even with bundles from other systems |
|
||||||
|
| SkyReader/W32 v1.00 | Supported | Works well. No QWKE or HEADERS.DAT support. Gets confused with low conference numbers. |
|
||||||
|
|
||||||
|
There are also [many other readers](https://www.softwolves.pp.se/old/2000/faq/bwprod) for various systems.
|
|
@ -3,7 +3,7 @@ layout: page
|
||||||
title: Local Doors
|
title: Local Doors
|
||||||
---
|
---
|
||||||
## Local Doors
|
## Local Doors
|
||||||
ENiGMA½ has many ways to add doors to your system. In addition to the many built in door server modules, local doors are of course also supported using the ! The `abracadabra` module!
|
ENiGMA½ has many ways to add doors to your system. In addition to the [many built in door server modules](door-servers.md), local doors are of course also supported using the ! The `abracadabra` module!
|
||||||
|
|
||||||
## The abracadabra Module
|
## The abracadabra Module
|
||||||
The `abracadabra` module provides a generic and flexible solution for many door types. Through this module you can execute native processes & scripts directly, and perform I/O through standard I/O (stdio) or a temporary TCP server.
|
The `abracadabra` module provides a generic and flexible solution for many door types. Through this module you can execute native processes & scripts directly, and perform I/O through standard I/O (stdio) or a temporary TCP server.
|
||||||
|
@ -149,7 +149,7 @@ Please see the [bivrost!](https://github.com/NuSkooler/bivrost) documentation fo
|
||||||
Pre-built binaries of bivrost! have been released under [Phenom Productions](https://www.phenomprod.com/) and can be found on various boards.
|
Pre-built binaries of bivrost! have been released under [Phenom Productions](https://www.phenomprod.com/) and can be found on various boards.
|
||||||
|
|
||||||
#### Alternative Workarounds
|
#### Alternative Workarounds
|
||||||
Alternative workarounds include Telnet Bridge (`telnet_bridge` module) to hook up Telnet-accessible (including local) door servers -- It may also be possible bridge via [NET2BBS](http://pcmicro.com/netfoss/guide/net2bbs.html).
|
Alternative workarounds include [Telnet Bridge module](telnet-bridge.md) to hook up Telnet-accessible (including local) door servers -- It may also be possible bridge via [NET2BBS](http://pcmicro.com/netfoss/guide/net2bbs.html).
|
||||||
|
|
||||||
### QEMU with abracadabra
|
### QEMU with abracadabra
|
||||||
[QEMU](http://wiki.qemu.org/Main_Page) provides a robust, cross platform solution for launching doors under many platforms (likely anywhere Node.js is supported and ENiGMA½ can run). Note however that there is an important and major caveat: **Multiple instances of a particular door/OS image should not be run at once!** Being more flexible means being a bit more complex. Let's look at an example for running L.O.R.D. under a UNIX like system such as Linux or FreeBSD.
|
[QEMU](http://wiki.qemu.org/Main_Page) provides a robust, cross platform solution for launching doors under many platforms (likely anywhere Node.js is supported and ENiGMA½ can run). Note however that there is an important and major caveat: **Multiple instances of a particular door/OS image should not be run at once!** Being more flexible means being a bit more complex. Let's look at an example for running L.O.R.D. under a UNIX like system such as Linux or FreeBSD.
|
||||||
|
@ -223,8 +223,13 @@ doorLORD: {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
* [Telnet Bridge](telnet-bridge.md)
|
||||||
|
* [Door Servers](door-servers.md)
|
||||||
|
|
||||||
## Additional Resources
|
## Additional Resources
|
||||||
### DOSBox
|
### DOS Emulation
|
||||||
|
* [DOSEMU](http://www.dosemu.org/)
|
||||||
* [DOSBox-X](https://github.com/joncampbell123/dosbox-x)
|
* [DOSBox-X](https://github.com/joncampbell123/dosbox-x)
|
||||||
|
|
||||||
### Door Downloads & Support Sites
|
### Door Downloads & Support Sites
|
||||||
|
|
|
@ -29,8 +29,8 @@ showWithExtraArgs: {
|
||||||
If the `showWithExtraArgs` menu was entered and passed `extraArgs` as the following:
|
If the `showWithExtraArgs` menu was entered and passed `extraArgs` as the following:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
fizzBang : true,
|
"fizzBang" : true,
|
||||||
fooBaz : "LOLART"
|
"fooBaz" : "LOLART"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
---
|
||||||
|
layout: page
|
||||||
|
title: Telnet Bridge
|
||||||
|
---
|
||||||
|
## Telnet Bridge
|
||||||
|
The `telnet_bridge` module allows "bridged" Telnet connections from your board to other Telnet services (such as other BBSes!).
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
### Config Block
|
||||||
|
Available `config` entries:
|
||||||
|
* `host`: Hostname or IP address to connect to.
|
||||||
|
* `port`: Port to connect to. Defaults to the standard Telnet port of `23`.
|
||||||
|
* `font`: A SyncTERM style font. Useful for example if you would like to connect form a "DOS" style BBS to an Amiga. See [the general art documentation on SyncTERM Style Fonts](/docs/art/general.md).
|
||||||
|
|
||||||
|
### Example
|
||||||
|
Below is an example `menu.hjson` entry that would connect to [Xibalba](https://xibalba.l33t.codes):
|
||||||
|
|
||||||
|
```hjson
|
||||||
|
{
|
||||||
|
telnetBridgeXibalba: {
|
||||||
|
desc: Xibalba BBS
|
||||||
|
module: telnet_bridge
|
||||||
|
config: {
|
||||||
|
host: xibalba.l33t.codes
|
||||||
|
port: 45510
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Extra Args
|
||||||
|
The `telnet_bridge` module can also accept standard `extraArgs` of the same configuration arguments described above. This can be illustrated with an example:
|
||||||
|
|
||||||
|
```hjson
|
||||||
|
telnetBridgeMenu: {
|
||||||
|
desc: Telnet Bridge
|
||||||
|
art: telnet_bridge
|
||||||
|
config: {
|
||||||
|
font: cp437
|
||||||
|
}
|
||||||
|
form: {
|
||||||
|
0: {
|
||||||
|
mci: {
|
||||||
|
VM1: {
|
||||||
|
argName: selection
|
||||||
|
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
board: BLACK Flag
|
||||||
|
soft: Mystic
|
||||||
|
data: bf
|
||||||
|
}
|
||||||
|
{
|
||||||
|
board: Xibalba
|
||||||
|
soft: ENiGMA½
|
||||||
|
data: xib
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// sort by 'board' fields above
|
||||||
|
sort: board
|
||||||
|
submit: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submit: {
|
||||||
|
*: [
|
||||||
|
{
|
||||||
|
value: { "selection" : "bf" }
|
||||||
|
action: @menu:telnetBridgeFromExtraFlags
|
||||||
|
extraArgs: {
|
||||||
|
host: blackflag.acid.org
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
value: { "selection" : "xib" }
|
||||||
|
action: @menu:telnetBridgeFromExtraFlags
|
||||||
|
extraArgs: {
|
||||||
|
host: xibalba.l33t.codes
|
||||||
|
port: 44510
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
telnetBridgeFromExtraFlags: {
|
||||||
|
desc: Telnet Bridge
|
||||||
|
module: telnet_bridge
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we've created a lightbar menu with custom items in which we'd use `itemFormat`'s with in a theme. When the user selects an item, the `telnetBridgeFromExtraFlags` menu is instantiated using the supplied `extraArgs`.
|
||||||
|
|
|
@ -16,6 +16,7 @@ Entries available under `config.loginServers.ssh`:
|
||||||
| `firstMenuNewUser` | :-1: | Menu presented to user when logging in with one of the usernames found within `users.newUserNames` in your `config.hjson`. Examples include `new` and `apply`. |
|
| `firstMenuNewUser` | :-1: | Menu presented to user when logging in with one of the usernames found within `users.newUserNames` in your `config.hjson`. Examples include `new` and `apply`. |
|
||||||
| `enabled` | :+1: | Set to `true` to enable the SSH server. |
|
| `enabled` | :+1: | Set to `true` to enable the SSH server. |
|
||||||
| `port` | :-1: | Override the default port of `8443`. |
|
| `port` | :-1: | Override the default port of `8443`. |
|
||||||
|
| `address` | :-1: | Sets an explicit bind address. |
|
||||||
| `algorithms` | :-1: | Configuration block for SSH algorithms. Includes keys of `kex`, `cipher`, `hmac`, and `compress`. See the algorithms section in the [ssh2-streams](https://github.com/mscdex/ssh2-streams#ssh2stream-methods) documentation for details. For defaults set by ENiGMA½, see `core/config.js`.
|
| `algorithms` | :-1: | Configuration block for SSH algorithms. Includes keys of `kex`, `cipher`, `hmac`, and `compress`. See the algorithms section in the [ssh2-streams](https://github.com/mscdex/ssh2-streams#ssh2stream-methods) documentation for details. For defaults set by ENiGMA½, see `core/config.js`.
|
||||||
| `traceConnections` | :-1: | Set to `true` to enable full trace-level information on SSH connections.
|
| `traceConnections` | :-1: | Set to `true` to enable full trace-level information on SSH connections.
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,11 @@ The Telnet *login server* provides a standard **non-secure** Telnet login experi
|
||||||
## Configuration
|
## Configuration
|
||||||
The following configuration can be made in `config.hjson` under the `loginServers.telnet` block:
|
The following configuration can be made in `config.hjson` under the `loginServers.telnet` block:
|
||||||
|
|
||||||
| Item | Required | Description |
|
| Key | Required | Description |
|
||||||
|------|----------|-------------|
|
|------|----------|-------------|
|
||||||
| `enabled` | :-1: Defaults to `true`. Set to `false` to disable Telnet |
|
| `enabled` | :-1: Defaults to `true`. Set to `false` to disable Telnet |
|
||||||
| `port` | :-1: | Override the default port of `8888`. |
|
| `port` | :-1: | Override the default port of `8888`. |
|
||||||
|
| `address` | :-1: | Sets an explicit bind address. |
|
||||||
| `firstMenu` | :-1: | First menu a telnet connected user is presented with. Defaults to `telnetConnected`. |
|
| `firstMenu` | :-1: | First menu a telnet connected user is presented with. Defaults to `telnetConnected`. |
|
||||||
|
|
||||||
### Example Configuration
|
### Example Configuration
|
||||||
|
|
|
@ -2,13 +2,10 @@
|
||||||
layout: page
|
layout: page
|
||||||
title: Web Server
|
title: Web Server
|
||||||
---
|
---
|
||||||
ENiGMA½ comes with a built in *content server* for supporting both HTTP and HTTPS. Currently the
|
ENiGMA½ comes with a built in *content server* for supporting both HTTP and HTTPS. Currently the [File Bases](file_base.md) registers routes for file downloads, password reset email links are handled via the server, and static files can also be served for your BBS. Other features will likely come in the future or you can easily write your own!
|
||||||
[File Bases](file_base.md) registers routes for file downloads, and static files can also be served
|
|
||||||
for your BBS. Other features will likely come in the future or you can easily write your own!
|
|
||||||
|
|
||||||
## Configuration
|
# Configuration
|
||||||
By default the web server is not enabled. To enable it, you will need to at a minimum configure two keys in
|
By default the web server is not enabled. To enable it, you will need to at a minimum configure two keys in the `contentServers.web` section of `config.hjson`:
|
||||||
the `contentServers::web` section of `config.hjson`:
|
|
||||||
|
|
||||||
```hjson
|
```hjson
|
||||||
contentServers: {
|
contentServers: {
|
||||||
|
@ -17,39 +14,44 @@ contentServers: {
|
||||||
|
|
||||||
http: {
|
http: {
|
||||||
enabled: true
|
enabled: true
|
||||||
|
port: 8080
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This will configure HTTP for port 8080 (override with `port`). To additionally enable HTTPS, you will need a
|
The following is a table of all configuration keys available under `contentServers.web`:
|
||||||
PEM encoded SSL certificate and private key. [LetsEncrypt](https://letsencrypt.org/) supply free trusted
|
| Key | Required | Description |
|
||||||
certificates that work perfectly with ENiGMA½.
|
|------|----------|-------------|
|
||||||
|
| `domain` | :+1: | Sets the domain, e.g. `bbs.yourdomain.com`. |
|
||||||
|
| `http` | :-1: | Sub configuration for HTTP (non-secure) connections. See **HTTP Configuration** below. |
|
||||||
|
| `overrideUrlPrefix` | :-1: | Instructs the system to be explicit when handing out URLs. Useful if your server is behind a transparent proxy. |
|
||||||
|
|
||||||
Once obtained, simply enable the HTTPS server:
|
### HTTP Configuration
|
||||||
|
Entries available under `contentServers.web.http`:
|
||||||
|
|
||||||
```hjson
|
| Key | Required | Description |
|
||||||
contentServers: {
|
|------|----------|-------------|
|
||||||
web: {
|
| `enable` | :+1: | Set to `true` to enable this server.
|
||||||
domain: bbs.yourdomain.com
|
| `port` | :-1: | Override the default port of `8080`. |
|
||||||
// set 'overrideUrlPrefix' if for example, you use a transparent proxy in front of ENiGMA and need to be explicit about URLs the system hands out
|
| `address` | :-1: | Sets an explicit bind address. |
|
||||||
overrideUrlPrefix: https://bbs.yourdomain.com
|
|
||||||
https: {
|
|
||||||
enabled: true
|
|
||||||
port: 8443
|
|
||||||
certPem: /path/to/your/cert.pem
|
|
||||||
keyPem: /path/to/your/cert_private_key.pem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If no certificate paths are supplied, ENiGMA½ will assume the defaults of `/config/https_cert.pem` and
|
### HTTPS Configuration
|
||||||
`/config/https_cert_key.pem` accordingly.
|
Entries available under `contentServers.web.https`:
|
||||||
|
|
||||||
### Static Routes
|
| Key | Required | Description |
|
||||||
Static files live relative to the `contentServers::web::staticRoot` path which defaults to `enigma-bbs/www`.
|
|------|----------|-------------|
|
||||||
|
| `enable` | :+1: | Set to `true` to enable this server.
|
||||||
|
| `port` | :-1: | Override the default port of `8080`. |
|
||||||
|
| `address` | :-1: | Sets an explicit bind address. |
|
||||||
|
| `certPem` | :+1: | Overrides the default certificate path of `/config/https_cert.pem`. Certificate must be in PEM format. See **Certificates** below. |
|
||||||
|
| `keyPem` | :+1: | Overrides the default certificate key path of `/config/https_cert_key.pem`. Key must be in PEM format. See **Certificates** below. |
|
||||||
|
|
||||||
### Custom Error Pages
|
#### Certificates
|
||||||
Customized error pages can be created for [HTTP error codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_Error)
|
If you don't have a TLS certificate for your domain, a good source for a certificate can be [LetsEncrypt](https://letsencrypt.org/) who supplies free and trusted TLS certificates.
|
||||||
by providing a `<error_code>.html` file in the *static routes* area. For example: `404.html`.
|
|
||||||
|
## Static Routes
|
||||||
|
Static files live relative to the `contentServers.web.staticRoot` path which defaults to `enigma-bbs/www`.
|
||||||
|
|
||||||
|
## Custom Error Pages
|
||||||
|
Customized error pages can be created for [HTTP error codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_Error) by providing a `<error_code>.html` file in the *static routes* area. For example: `404.html`.
|
||||||
|
|
|
@ -3,7 +3,7 @@ layout: page
|
||||||
title: Monitoring Logs
|
title: Monitoring Logs
|
||||||
---
|
---
|
||||||
## Monitoring Logs
|
## Monitoring Logs
|
||||||
ENiGMA½ does not produce much to stdout. Logs are produced by Bunyan which outputs each entry as a JSON object.
|
ENiGMA½ does not produce much to stdout. Logs are produced by [Bunyan](https://github.com/trentm/node-bunyan) which outputs each entry as a JSON object.
|
||||||
|
|
||||||
Start by installing bunyan and making it available on your path:
|
Start by installing bunyan and making it available on your path:
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ Start by installing bunyan and making it available on your path:
|
||||||
npm install bunyan -g
|
npm install bunyan -g
|
||||||
```
|
```
|
||||||
|
|
||||||
or with Yarn:
|
or via Yarn:
|
||||||
```bash
|
```bash
|
||||||
yarn global add bunyan
|
yarn global add bunyan
|
||||||
```
|
```
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
{ # this ensures the entire script is downloaded before execution
|
{ # this ensures the entire script is downloaded before execution
|
||||||
|
|
||||||
ENIGMA_NODE_VERSION=${ENIGMA_NODE_VERSION:=10}
|
ENIGMA_NODE_VERSION=${ENIGMA_NODE_VERSION:=12}
|
||||||
ENIGMA_BRANCH=${ENIGMA_BRANCH:=master}
|
ENIGMA_BRANCH=${ENIGMA_BRANCH:=master}
|
||||||
ENIGMA_INSTALL_DIR=${ENIGMA_INSTALL_DIR:=$HOME/enigma-bbs}
|
ENIGMA_INSTALL_DIR=${ENIGMA_INSTALL_DIR:=$HOME/enigma-bbs}
|
||||||
ENIGMA_SOURCE=${ENIGMA_SOURCE:=https://github.com/NuSkooler/enigma-bbs.git}
|
ENIGMA_SOURCE=${ENIGMA_SOURCE:=https://github.com/NuSkooler/enigma-bbs.git}
|
||||||
|
|
40
package.json
40
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "enigma-bbs",
|
"name": "enigma-bbs",
|
||||||
"version": "0.0.10-alpha",
|
"version": "0.0.11-beta",
|
||||||
"description": "ENiGMA½ Bulletin Board System",
|
"description": "ENiGMA½ Bulletin Board System",
|
||||||
"author": "Bryan Ashby <bryan@l33t.codes>",
|
"author": "Bryan Ashby <bryan@l33t.codes>",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
|
@ -22,44 +22,46 @@
|
||||||
"retro"
|
"retro"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "3.1.0",
|
"async": "3.2.0",
|
||||||
"binary-parser": "^1.5.0",
|
"binary-parser": "^1.6.2",
|
||||||
"buffers": "github:NuSkooler/node-buffers",
|
"buffers": "github:NuSkooler/node-buffers",
|
||||||
"bunyan": "^1.8.12",
|
"bunyan": "^1.8.12",
|
||||||
"exiftool": "^0.0.3",
|
"exiftool": "^0.0.3",
|
||||||
"fs-extra": "8.1.0",
|
"fs-extra": "9.0.0",
|
||||||
"glob": "7.1.6",
|
"glob": "7.1.6",
|
||||||
"graceful-fs": "^4.2.3",
|
"graceful-fs": "^4.2.4",
|
||||||
"hashids": "2.1.0",
|
"hashids": "2.2.1",
|
||||||
"hjson": "^3.2.1",
|
"hjson": "^3.2.1",
|
||||||
"iconv-lite": "0.5.0",
|
"iconv-lite": "0.5.1",
|
||||||
"inquirer": "^7.0.0",
|
"ini-config-parser": "^1.0.4",
|
||||||
|
"inquirer": "^7.1.0",
|
||||||
"later": "1.2.0",
|
"later": "1.2.0",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"lru-cache": "^5.1.1",
|
"lru-cache": "^5.1.1",
|
||||||
"mime-types": "2.1.25",
|
"mime-types": "2.1.27",
|
||||||
"minimist": "1.2.0",
|
"minimist": "1.2.5",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.25.3",
|
||||||
"nntp-server": "^1.0.3",
|
"nntp-server": "^1.0.3",
|
||||||
"node-pty": "^0.9.0",
|
"node-pty": "^0.9.0",
|
||||||
"nodemailer": "^6.3.1",
|
"nodemailer": "^6.4.6",
|
||||||
"otplib": "11.0.1",
|
"otplib": "11.0.1",
|
||||||
"qrcode-generator": "^1.4.4",
|
"qrcode-generator": "^1.4.4",
|
||||||
"rlogin": "^1.0.0",
|
"rlogin": "^1.0.0",
|
||||||
"sane": "4.1.0",
|
"sane": "4.1.0",
|
||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"sqlite3": "^4.1.0",
|
"sqlite3": "^4.2.0",
|
||||||
"sqlite3-trans": "^1.2.1",
|
"sqlite3-trans": "^1.2.2",
|
||||||
"ssh2": "0.8.6",
|
"ssh2": "0.8.9",
|
||||||
"temptmp": "^1.1.0",
|
"temptmp": "^1.1.0",
|
||||||
"uuid": "^3.3.3",
|
"uuid": "^8.0.0",
|
||||||
"uuid-parse": "1.1.0",
|
"uuid-parse": "1.1.0",
|
||||||
"ws": "^7.2.0",
|
"ws": "^7.3.0",
|
||||||
"xxhash": "^0.3.0",
|
"xxhash": "^0.3.0",
|
||||||
"yazl": "^2.5.1"
|
"yazl": "^2.5.1",
|
||||||
|
"telnet-socket" : "^0.2.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {},
|
"devDependencies": {},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
627
yarn.lock
627
yarn.lock
|
@ -10,21 +10,16 @@
|
||||||
exec-sh "^0.3.2"
|
exec-sh "^0.3.2"
|
||||||
minimist "^1.2.0"
|
minimist "^1.2.0"
|
||||||
|
|
||||||
|
"@types/color-name@^1.1.1":
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
|
||||||
|
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
|
||||||
|
|
||||||
abbrev@1:
|
abbrev@1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||||
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
|
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
|
||||||
|
|
||||||
ajv@^5.3.0:
|
|
||||||
version "5.5.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
|
|
||||||
integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=
|
|
||||||
dependencies:
|
|
||||||
co "^4.6.0"
|
|
||||||
fast-deep-equal "^1.0.0"
|
|
||||||
fast-json-stable-stringify "^2.0.0"
|
|
||||||
json-schema-traverse "^0.3.0"
|
|
||||||
|
|
||||||
ansi-escapes@^4.2.1:
|
ansi-escapes@^4.2.1:
|
||||||
version "4.2.1"
|
version "4.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.2.1.tgz#4dccdb846c3eee10f6d64dea66273eab90c37228"
|
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.2.1.tgz#4dccdb846c3eee10f6d64dea66273eab90c37228"
|
||||||
|
@ -47,12 +42,18 @@ ansi-regex@^4.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
|
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
|
||||||
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
|
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
|
||||||
|
|
||||||
ansi-styles@^3.2.1:
|
ansi-regex@^5.0.0:
|
||||||
version "3.2.1"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
|
||||||
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
|
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
|
||||||
|
|
||||||
|
ansi-styles@^4.1.0:
|
||||||
|
version "4.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
|
||||||
|
integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
|
||||||
dependencies:
|
dependencies:
|
||||||
color-convert "^1.9.0"
|
"@types/color-name" "^1.1.1"
|
||||||
|
color-convert "^2.0.1"
|
||||||
|
|
||||||
anymatch@^2.0.0:
|
anymatch@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
|
@ -107,53 +108,33 @@ array-unique@^0.3.2:
|
||||||
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
|
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
|
||||||
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
|
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
|
||||||
|
|
||||||
asn1@~0.2.0, asn1@~0.2.3:
|
asn1@~0.2.0:
|
||||||
version "0.2.4"
|
version "0.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||||
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
|
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
|
||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer "~2.1.0"
|
safer-buffer "~2.1.0"
|
||||||
|
|
||||||
assert-plus@1.0.0, assert-plus@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
|
||||||
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
|
|
||||||
|
|
||||||
assign-symbols@^1.0.0:
|
assign-symbols@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
|
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
|
||||||
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
|
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
|
||||||
|
|
||||||
async-limiter@^1.0.0:
|
async@3.2.0:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
|
||||||
|
integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==
|
||||||
|
|
||||||
|
at-least-node@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
|
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
|
||||||
integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
|
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
|
||||||
|
|
||||||
async@3.1.0:
|
|
||||||
version "3.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/async/-/async-3.1.0.tgz#42b3b12ae1b74927b5217d8c0016baaf62463772"
|
|
||||||
integrity sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ==
|
|
||||||
|
|
||||||
asynckit@^0.4.0:
|
|
||||||
version "0.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
|
||||||
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
|
|
||||||
|
|
||||||
atob@^2.1.1:
|
atob@^2.1.1:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||||
|
|
||||||
aws-sign2@~0.7.0:
|
|
||||||
version "0.7.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
|
||||||
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
|
|
||||||
|
|
||||||
aws4@^1.8.0:
|
|
||||||
version "1.8.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
|
||||||
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
|
||||||
|
|
||||||
balanced-match@^1.0.0:
|
balanced-match@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||||
|
@ -172,17 +153,17 @@ base@^0.11.1:
|
||||||
mixin-deep "^1.2.0"
|
mixin-deep "^1.2.0"
|
||||||
pascalcase "^0.1.1"
|
pascalcase "^0.1.1"
|
||||||
|
|
||||||
bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2:
|
bcrypt-pbkdf@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
||||||
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
|
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
|
||||||
dependencies:
|
dependencies:
|
||||||
tweetnacl "^0.14.3"
|
tweetnacl "^0.14.3"
|
||||||
|
|
||||||
binary-parser@^1.5.0:
|
binary-parser@1.6.2, binary-parser@^1.6.2:
|
||||||
version "1.5.0"
|
version "1.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/binary-parser/-/binary-parser-1.5.0.tgz#3e50de3a5076badbacd760e833e7d94892b9e9fa"
|
resolved "https://registry.yarnpkg.com/binary-parser/-/binary-parser-1.6.2.tgz#8410a82ffd9403271ec182bd91e63a09cee88cbe"
|
||||||
integrity sha512-z+hqNSnO7trFDPLihjUGTwlSTbcIzLYSCwnbiasFkRvCIY9F3ZTex7Mlm9UAP3w5mfHD3KxejnWFPJjtsVVMuw==
|
integrity sha512-cYAhKB51A9T/uylDvMK7uAYaPLWLwlferNOpnQ0E0fuO73yPi7kWaWiOm22BvuKxCbggmkiFN0VkuLg6gc+KQQ==
|
||||||
|
|
||||||
brace-expansion@^1.1.7:
|
brace-expansion@^1.1.7:
|
||||||
version "1.1.11"
|
version "1.1.11"
|
||||||
|
@ -256,19 +237,13 @@ capture-exit@^2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
rsvp "^4.8.4"
|
rsvp "^4.8.4"
|
||||||
|
|
||||||
caseless@~0.12.0:
|
chalk@^3.0.0:
|
||||||
version "0.12.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
|
||||||
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
|
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
|
||||||
|
|
||||||
chalk@^2.4.2:
|
|
||||||
version "2.4.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
|
||||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-styles "^3.2.1"
|
ansi-styles "^4.1.0"
|
||||||
escape-string-regexp "^1.0.5"
|
supports-color "^7.1.0"
|
||||||
supports-color "^5.3.0"
|
|
||||||
|
|
||||||
chardet@^0.7.0:
|
chardet@^0.7.0:
|
||||||
version "0.7.0"
|
version "0.7.0"
|
||||||
|
@ -302,16 +277,16 @@ cli-width@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
|
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
|
||||||
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
|
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
|
||||||
|
|
||||||
co@^4.6.0:
|
|
||||||
version "4.6.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
|
||||||
integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
|
|
||||||
|
|
||||||
code-point-at@^1.0.0:
|
code-point-at@^1.0.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
||||||
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
|
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
|
||||||
|
|
||||||
|
coffee-script@^1.12.4:
|
||||||
|
version "1.12.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.7.tgz#c05dae0cb79591d05b3070a8433a98c9a89ccc53"
|
||||||
|
integrity sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==
|
||||||
|
|
||||||
collection-visit@^1.0.0:
|
collection-visit@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
|
resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
|
||||||
|
@ -320,31 +295,17 @@ collection-visit@^1.0.0:
|
||||||
map-visit "^1.0.0"
|
map-visit "^1.0.0"
|
||||||
object-visit "^1.0.0"
|
object-visit "^1.0.0"
|
||||||
|
|
||||||
color-convert@^1.9.0:
|
color-convert@^2.0.1:
|
||||||
version "1.9.3"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
|
||||||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
|
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name "1.1.3"
|
color-name "~1.1.4"
|
||||||
|
|
||||||
color-name@1.1.3:
|
color-name@~1.1.4:
|
||||||
version "1.1.3"
|
version "1.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
|
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||||
|
|
||||||
combined-stream@1.0.6:
|
|
||||||
version "1.0.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818"
|
|
||||||
integrity sha1-cj599ugBrFYTETp+RFqbactjKBg=
|
|
||||||
dependencies:
|
|
||||||
delayed-stream "~1.0.0"
|
|
||||||
|
|
||||||
combined-stream@~1.0.6:
|
|
||||||
version "1.0.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828"
|
|
||||||
integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==
|
|
||||||
dependencies:
|
|
||||||
delayed-stream "~1.0.0"
|
|
||||||
|
|
||||||
component-emitter@^1.2.1:
|
component-emitter@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
|
@ -366,7 +327,7 @@ copy-descriptor@^0.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
|
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
|
||||||
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
|
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
|
||||||
|
|
||||||
core-util-is@1.0.2, core-util-is@~1.0.0:
|
core-util-is@~1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
||||||
|
@ -382,13 +343,6 @@ cross-spawn@^6.0.0:
|
||||||
shebang-command "^1.2.0"
|
shebang-command "^1.2.0"
|
||||||
which "^1.2.9"
|
which "^1.2.9"
|
||||||
|
|
||||||
dashdash@^1.12.0:
|
|
||||||
version "1.14.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
|
||||||
integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
|
|
||||||
dependencies:
|
|
||||||
assert-plus "^1.0.0"
|
|
||||||
|
|
||||||
debug@^2.1.2, debug@^2.2.0, debug@^2.3.3:
|
debug@^2.1.2, debug@^2.2.0, debug@^2.3.3:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
|
@ -408,6 +362,11 @@ decode-uri-component@^0.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
||||||
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
|
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
|
||||||
|
|
||||||
|
deep-extend@^0.5.1:
|
||||||
|
version "0.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.1.tgz#b894a9dd90d3023fbf1c55a394fb858eb2066f1f"
|
||||||
|
integrity sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==
|
||||||
|
|
||||||
deep-extend@^0.6.0:
|
deep-extend@^0.6.0:
|
||||||
version "0.6.0"
|
version "0.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
||||||
|
@ -447,11 +406,6 @@ del@^3.0.0:
|
||||||
pify "^3.0.0"
|
pify "^3.0.0"
|
||||||
rimraf "^2.2.8"
|
rimraf "^2.2.8"
|
||||||
|
|
||||||
delayed-stream@~1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
|
||||||
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
|
|
||||||
|
|
||||||
delegates@^1.0.0:
|
delegates@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||||
|
@ -479,14 +433,6 @@ dtrace-provider@~0.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
nan "^2.10.0"
|
nan "^2.10.0"
|
||||||
|
|
||||||
ecc-jsbn@~0.1.1:
|
|
||||||
version "0.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
|
|
||||||
integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
|
|
||||||
dependencies:
|
|
||||||
jsbn "~0.1.0"
|
|
||||||
safer-buffer "^2.1.0"
|
|
||||||
|
|
||||||
emoji-regex@^8.0.0:
|
emoji-regex@^8.0.0:
|
||||||
version "8.0.0"
|
version "8.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||||
|
@ -555,11 +501,6 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
|
||||||
assign-symbols "^1.0.0"
|
assign-symbols "^1.0.0"
|
||||||
is-extendable "^1.0.1"
|
is-extendable "^1.0.1"
|
||||||
|
|
||||||
extend@~3.0.2:
|
|
||||||
version "3.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
|
||||||
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
|
||||||
|
|
||||||
external-editor@^3.0.3:
|
external-editor@^3.0.3:
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27"
|
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27"
|
||||||
|
@ -583,26 +524,6 @@ extglob@^2.0.4:
|
||||||
snapdragon "^0.8.1"
|
snapdragon "^0.8.1"
|
||||||
to-regex "^3.0.1"
|
to-regex "^3.0.1"
|
||||||
|
|
||||||
extsprintf@1.3.0:
|
|
||||||
version "1.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
|
||||||
integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
|
|
||||||
|
|
||||||
extsprintf@^1.2.0:
|
|
||||||
version "1.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
|
||||||
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
|
||||||
|
|
||||||
fast-deep-equal@^1.0.0:
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614"
|
|
||||||
integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=
|
|
||||||
|
|
||||||
fast-json-stable-stringify@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
|
|
||||||
integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
|
|
||||||
|
|
||||||
fb-watchman@^2.0.0:
|
fb-watchman@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58"
|
resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58"
|
||||||
|
@ -632,20 +553,6 @@ for-in@^1.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||||
integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
|
integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
|
||||||
|
|
||||||
forever-agent@~0.6.1:
|
|
||||||
version "0.6.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
|
||||||
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
|
|
||||||
|
|
||||||
form-data@~2.3.2:
|
|
||||||
version "2.3.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099"
|
|
||||||
integrity sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=
|
|
||||||
dependencies:
|
|
||||||
asynckit "^0.4.0"
|
|
||||||
combined-stream "1.0.6"
|
|
||||||
mime-types "^2.1.12"
|
|
||||||
|
|
||||||
fragment-cache@^0.2.1:
|
fragment-cache@^0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
|
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
|
||||||
|
@ -661,14 +568,15 @@ from2@^2.3.0:
|
||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
readable-stream "^2.0.0"
|
readable-stream "^2.0.0"
|
||||||
|
|
||||||
fs-extra@8.1.0:
|
fs-extra@9.0.0:
|
||||||
version "8.1.0"
|
version "9.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.0.tgz#b6afc31036e247b2466dc99c29ae797d5d4580a3"
|
||||||
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
|
integrity sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==
|
||||||
dependencies:
|
dependencies:
|
||||||
|
at-least-node "^1.0.0"
|
||||||
graceful-fs "^4.2.0"
|
graceful-fs "^4.2.0"
|
||||||
jsonfile "^4.0.0"
|
jsonfile "^6.0.1"
|
||||||
universalify "^0.1.0"
|
universalify "^1.0.0"
|
||||||
|
|
||||||
fs-minipass@^1.2.5:
|
fs-minipass@^1.2.5:
|
||||||
version "1.2.5"
|
version "1.2.5"
|
||||||
|
@ -708,13 +616,6 @@ get-value@^2.0.3, get-value@^2.0.6:
|
||||||
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
|
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
|
||||||
integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
|
integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
|
||||||
|
|
||||||
getpass@^0.1.1:
|
|
||||||
version "0.1.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
|
|
||||||
integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
|
|
||||||
dependencies:
|
|
||||||
assert-plus "^1.0.0"
|
|
||||||
|
|
||||||
glob@7.1.6:
|
glob@7.1.6:
|
||||||
version "7.1.6"
|
version "7.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
||||||
|
@ -771,28 +672,15 @@ graceful-fs@^4.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b"
|
||||||
integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==
|
integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==
|
||||||
|
|
||||||
graceful-fs@^4.2.3:
|
graceful-fs@^4.2.4:
|
||||||
version "4.2.3"
|
version "4.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
||||||
integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
|
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
|
||||||
|
|
||||||
har-schema@^2.0.0:
|
has-flag@^4.0.0:
|
||||||
version "2.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
|
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||||
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
|
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||||
|
|
||||||
har-validator@~5.1.0:
|
|
||||||
version "5.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.0.tgz#44657f5688a22cfd4b72486e81b3a3fb11742c29"
|
|
||||||
integrity sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==
|
|
||||||
dependencies:
|
|
||||||
ajv "^5.3.0"
|
|
||||||
har-schema "^2.0.0"
|
|
||||||
|
|
||||||
has-flag@^3.0.0:
|
|
||||||
version "3.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
|
||||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
|
||||||
|
|
||||||
has-unicode@^2.0.0:
|
has-unicode@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
|
@ -830,29 +718,20 @@ has-values@^1.0.0:
|
||||||
is-number "^3.0.0"
|
is-number "^3.0.0"
|
||||||
kind-of "^4.0.0"
|
kind-of "^4.0.0"
|
||||||
|
|
||||||
hashids@2.1.0:
|
hashids@2.2.1:
|
||||||
version "2.1.0"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/hashids/-/hashids-2.1.0.tgz#96eec6de3081a76dcd839650a6a26e6081e729d3"
|
resolved "https://registry.yarnpkg.com/hashids/-/hashids-2.2.1.tgz#ad0c600f0083aa0df7451dfd184e53db34f71289"
|
||||||
integrity sha512-N53K2p7TrwKLNHKHcEDH+qpiAgO9JfyPEg8Tfy4fB9AcVhwxlTanJ55HVV9BQJQ6ajM1Wfmtl2wgKuEbcucolw==
|
integrity sha512-+hQeKWwpSDiWFeu/3jKUvwboE4Z035gR6FnpscbHPOEEjCbgv2px9/Mlb3O0nOTRyZOw4MMFRYfVL3zctOV6OQ==
|
||||||
|
|
||||||
hjson@^3.2.1:
|
hjson@^3.2.1:
|
||||||
version "3.2.1"
|
version "3.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/hjson/-/hjson-3.2.1.tgz#20de41dc87fc9a10d1557d0230b0e02afb1b09ac"
|
resolved "https://registry.yarnpkg.com/hjson/-/hjson-3.2.1.tgz#20de41dc87fc9a10d1557d0230b0e02afb1b09ac"
|
||||||
integrity sha512-OhhrFMeC7dVuA1xvxuXGTv/yTdhTvbe8hz+3LgVNsfi9+vgz0sF/RrkuX8eegpKaMc9cwYwydImBH6iePoJtdQ==
|
integrity sha512-OhhrFMeC7dVuA1xvxuXGTv/yTdhTvbe8hz+3LgVNsfi9+vgz0sF/RrkuX8eegpKaMc9cwYwydImBH6iePoJtdQ==
|
||||||
|
|
||||||
http-signature@~1.2.0:
|
iconv-lite@0.5.1:
|
||||||
version "1.2.0"
|
version "0.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.5.1.tgz#b2425d3c7b18f7219f2ca663d103bddb91718d64"
|
||||||
integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
|
integrity sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==
|
||||||
dependencies:
|
|
||||||
assert-plus "^1.0.0"
|
|
||||||
jsprim "^1.2.2"
|
|
||||||
sshpk "^1.7.0"
|
|
||||||
|
|
||||||
iconv-lite@0.5.0:
|
|
||||||
version "0.5.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.5.0.tgz#59cdde0a2a297cc2aeb0c6445a195ee89f127550"
|
|
||||||
integrity sha512-NnEhI9hIEKHOzJ4f697DMz9IQEXr/MMJ5w64vN2/4Ai+wRnvV7SBrL0KLoRlwaKVghOc7LQ5YkPLuX146b6Ydw==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer ">= 2.1.2 < 3"
|
safer-buffer ">= 2.1.2 < 3"
|
||||||
|
|
||||||
|
@ -883,28 +762,37 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
|
||||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||||
|
|
||||||
|
ini-config-parser@^1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/ini-config-parser/-/ini-config-parser-1.0.4.tgz#0abc75cb68c506204712d2b4861400b6adbfda78"
|
||||||
|
integrity sha512-5hLh5Cqai67pTrLQ9q/K/3EtSP2Tzu41AZzwPLSegkkMkc42dGweLgkbiocCBiBBEg2fPhs6pKmdFhwj5Ul3Bg==
|
||||||
|
dependencies:
|
||||||
|
coffee-script "^1.12.4"
|
||||||
|
deep-extend "^0.5.1"
|
||||||
|
rimraf "^2.6.1"
|
||||||
|
|
||||||
ini@~1.3.0:
|
ini@~1.3.0:
|
||||||
version "1.3.5"
|
version "1.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||||
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
|
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
|
||||||
|
|
||||||
inquirer@^7.0.0:
|
inquirer@^7.1.0:
|
||||||
version "7.0.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.0.tgz#9e2b032dde77da1db5db804758b8fea3a970519a"
|
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29"
|
||||||
integrity sha512-rSdC7zelHdRQFkWnhsMu2+2SO41mpv2oF2zy4tMhmiLWkcKbOAs87fWAJhVXttKVwhdZvymvnuM95EyEXg2/tQ==
|
integrity sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-escapes "^4.2.1"
|
ansi-escapes "^4.2.1"
|
||||||
chalk "^2.4.2"
|
chalk "^3.0.0"
|
||||||
cli-cursor "^3.1.0"
|
cli-cursor "^3.1.0"
|
||||||
cli-width "^2.0.0"
|
cli-width "^2.0.0"
|
||||||
external-editor "^3.0.3"
|
external-editor "^3.0.3"
|
||||||
figures "^3.0.0"
|
figures "^3.0.0"
|
||||||
lodash "^4.17.15"
|
lodash "^4.17.15"
|
||||||
mute-stream "0.0.8"
|
mute-stream "0.0.8"
|
||||||
run-async "^2.2.0"
|
run-async "^2.4.0"
|
||||||
rxjs "^6.4.0"
|
rxjs "^6.5.3"
|
||||||
string-width "^4.1.0"
|
string-width "^4.1.0"
|
||||||
strip-ansi "^5.1.0"
|
strip-ansi "^6.0.0"
|
||||||
through "^2.3.6"
|
through "^2.3.6"
|
||||||
|
|
||||||
is-accessor-descriptor@^0.1.6:
|
is-accessor-descriptor@^0.1.6:
|
||||||
|
@ -1020,21 +908,11 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
isobject "^3.0.1"
|
isobject "^3.0.1"
|
||||||
|
|
||||||
is-promise@^2.1.0:
|
|
||||||
version "2.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
|
|
||||||
integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=
|
|
||||||
|
|
||||||
is-stream@^1.1.0:
|
is-stream@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||||
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
|
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
|
||||||
|
|
||||||
is-typedarray@~1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
|
||||||
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
|
|
||||||
|
|
||||||
is-windows@^1.0.2:
|
is-windows@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
||||||
|
@ -1062,48 +940,15 @@ isobject@^3.0.0, isobject@^3.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||||
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
|
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
|
||||||
|
|
||||||
isstream@~0.1.2:
|
jsonfile@^6.0.1:
|
||||||
version "0.1.2"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179"
|
||||||
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
|
integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==
|
||||||
|
dependencies:
|
||||||
jsbn@~0.1.0:
|
universalify "^1.0.0"
|
||||||
version "0.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
|
||||||
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
|
|
||||||
|
|
||||||
json-schema-traverse@^0.3.0:
|
|
||||||
version "0.3.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
|
|
||||||
integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=
|
|
||||||
|
|
||||||
json-schema@0.2.3:
|
|
||||||
version "0.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
|
|
||||||
integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
|
|
||||||
|
|
||||||
json-stringify-safe@~5.0.1:
|
|
||||||
version "5.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
|
||||||
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
|
|
||||||
|
|
||||||
jsonfile@^4.0.0:
|
|
||||||
version "4.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
|
||||||
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
|
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs "^4.1.6"
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
jsprim@^1.2.2:
|
|
||||||
version "1.4.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
|
|
||||||
integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=
|
|
||||||
dependencies:
|
|
||||||
assert-plus "1.0.0"
|
|
||||||
extsprintf "1.3.0"
|
|
||||||
json-schema "0.2.3"
|
|
||||||
verror "1.10.0"
|
|
||||||
|
|
||||||
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
|
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
|
||||||
version "3.2.2"
|
version "3.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
|
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
|
||||||
|
@ -1138,11 +983,6 @@ lodash@^4.17.15:
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||||
|
|
||||||
lodash@^4.17.4:
|
|
||||||
version "4.17.11"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
|
|
||||||
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
|
|
||||||
|
|
||||||
lru-cache@^5.1.1:
|
lru-cache@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
|
||||||
|
@ -1188,29 +1028,17 @@ micromatch@^3.1.4:
|
||||||
snapdragon "^0.8.1"
|
snapdragon "^0.8.1"
|
||||||
to-regex "^3.0.2"
|
to-regex "^3.0.2"
|
||||||
|
|
||||||
mime-db@1.42.0:
|
mime-db@1.44.0:
|
||||||
version "1.42.0"
|
version "1.44.0"
|
||||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac"
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
|
||||||
integrity sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==
|
integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==
|
||||||
|
|
||||||
mime-db@~1.36.0:
|
mime-types@2.1.27:
|
||||||
version "1.36.0"
|
version "2.1.27"
|
||||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.36.0.tgz#5020478db3c7fe93aad7bbcc4dcf869c43363397"
|
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
|
||||||
integrity sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==
|
integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==
|
||||||
|
|
||||||
mime-types@2.1.25:
|
|
||||||
version "2.1.25"
|
|
||||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.25.tgz#39772d46621f93e2a80a856c53b86a62156a6437"
|
|
||||||
integrity sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
mime-db "1.42.0"
|
mime-db "1.44.0"
|
||||||
|
|
||||||
mime-types@^2.1.12, mime-types@~2.1.19:
|
|
||||||
version "2.1.20"
|
|
||||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.20.tgz#930cb719d571e903738520f8470911548ca2cc19"
|
|
||||||
integrity sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==
|
|
||||||
dependencies:
|
|
||||||
mime-db "~1.36.0"
|
|
||||||
|
|
||||||
mimic-fn@^2.1.0:
|
mimic-fn@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
|
@ -1229,7 +1057,12 @@ minimist@0.0.8:
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||||
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
|
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
|
||||||
|
|
||||||
minimist@1.2.0, minimist@^1.1.1, minimist@^1.2.0:
|
minimist@1.2.5:
|
||||||
|
version "1.2.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||||
|
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||||
|
|
||||||
|
minimist@^1.1.1, minimist@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||||
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
|
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
|
||||||
|
@ -1269,10 +1102,10 @@ moment@^2.10.6:
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
|
||||||
integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=
|
integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=
|
||||||
|
|
||||||
moment@^2.24.0:
|
moment@^2.25.3:
|
||||||
version "2.24.0"
|
version "2.25.3"
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.25.3.tgz#252ff41319cf41e47761a1a88cab30edfe9808c0"
|
||||||
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
|
integrity sha512-PuYv0PHxZvzc15Sp8ybUCoQ+xpyPWvjOuK72a5ovzp2LI32rJXOiIfyoFoYvG3s6EwwrdkMyWuRiEHSZRLJNdg==
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
|
@ -1388,10 +1221,10 @@ node-pty@^0.9.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
nan "^2.14.0"
|
nan "^2.14.0"
|
||||||
|
|
||||||
nodemailer@^6.3.1:
|
nodemailer@^6.4.6:
|
||||||
version "6.3.1"
|
version "6.4.6"
|
||||||
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.3.1.tgz#2784beebac6b9f014c424c54dbdcc5c4d1221346"
|
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.6.tgz#d37f504f6560b36616f646a606894fe18819107f"
|
||||||
integrity sha512-j0BsSyaMlyadEDEypK/F+xlne2K5m6wzPYMXS/yxKI0s7jmT1kBx6GEKRVbZmyYfKOsjkeC/TiMVDJBI/w5gMQ==
|
integrity sha512-/kJ+FYVEm2HuUlw87hjSqTss+GU35D4giOpdSfGp7DO+5h6RlJj7R94YaYHOkoxu1CSaM0d3WRBtCzwXrY6MKA==
|
||||||
|
|
||||||
nopt@^4.0.1:
|
nopt@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
|
@ -1443,11 +1276,6 @@ number-is-nan@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
|
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
|
||||||
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
|
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
|
||||||
|
|
||||||
oauth-sign@~0.9.0:
|
|
||||||
version "0.9.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
|
||||||
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
|
|
||||||
|
|
||||||
object-assign@^4.0.1, object-assign@^4.1.0:
|
object-assign@^4.0.1, object-assign@^4.1.0:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
|
@ -1545,11 +1373,6 @@ path-key@^2.0.0, path-key@^2.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
|
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
|
||||||
integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
|
integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
|
||||||
|
|
||||||
performance-now@^2.1.0:
|
|
||||||
version "2.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
|
||||||
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
|
|
||||||
|
|
||||||
pify@^2.0.0:
|
pify@^2.0.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||||
|
@ -1582,11 +1405,6 @@ process-nextick-args@~2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
||||||
integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
|
integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
|
||||||
|
|
||||||
psl@^1.1.24:
|
|
||||||
version "1.1.29"
|
|
||||||
resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67"
|
|
||||||
integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==
|
|
||||||
|
|
||||||
pump@^3.0.0:
|
pump@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
|
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
|
||||||
|
@ -1595,21 +1413,11 @@ pump@^3.0.0:
|
||||||
end-of-stream "^1.1.0"
|
end-of-stream "^1.1.0"
|
||||||
once "^1.3.1"
|
once "^1.3.1"
|
||||||
|
|
||||||
punycode@^1.4.1:
|
|
||||||
version "1.4.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
|
||||||
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
|
|
||||||
|
|
||||||
qrcode-generator@^1.4.4:
|
qrcode-generator@^1.4.4:
|
||||||
version "1.4.4"
|
version "1.4.4"
|
||||||
resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-1.4.4.tgz#63f771224854759329a99048806a53ed278740e7"
|
resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-1.4.4.tgz#63f771224854759329a99048806a53ed278740e7"
|
||||||
integrity sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==
|
integrity sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==
|
||||||
|
|
||||||
qs@~6.5.2:
|
|
||||||
version "6.5.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
|
||||||
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
|
||||||
|
|
||||||
rc@^1.2.7:
|
rc@^1.2.7:
|
||||||
version "1.2.8"
|
version "1.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||||
|
@ -1665,32 +1473,6 @@ repeat-string@^1.6.1:
|
||||||
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
|
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
|
||||||
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
|
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
|
||||||
|
|
||||||
request@^2.87.0:
|
|
||||||
version "2.88.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
|
|
||||||
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
|
|
||||||
dependencies:
|
|
||||||
aws-sign2 "~0.7.0"
|
|
||||||
aws4 "^1.8.0"
|
|
||||||
caseless "~0.12.0"
|
|
||||||
combined-stream "~1.0.6"
|
|
||||||
extend "~3.0.2"
|
|
||||||
forever-agent "~0.6.1"
|
|
||||||
form-data "~2.3.2"
|
|
||||||
har-validator "~5.1.0"
|
|
||||||
http-signature "~1.2.0"
|
|
||||||
is-typedarray "~1.0.0"
|
|
||||||
isstream "~0.1.2"
|
|
||||||
json-stringify-safe "~5.0.1"
|
|
||||||
mime-types "~2.1.19"
|
|
||||||
oauth-sign "~0.9.0"
|
|
||||||
performance-now "^2.1.0"
|
|
||||||
qs "~6.5.2"
|
|
||||||
safe-buffer "^5.1.2"
|
|
||||||
tough-cookie "~2.4.3"
|
|
||||||
tunnel-agent "^0.6.0"
|
|
||||||
uuid "^3.3.2"
|
|
||||||
|
|
||||||
resolve-url@^0.2.1:
|
resolve-url@^0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
|
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
|
||||||
|
@ -1733,21 +1515,19 @@ rsvp@^4.8.4:
|
||||||
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
|
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
|
||||||
integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
|
integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
|
||||||
|
|
||||||
run-async@^2.2.0:
|
run-async@^2.4.0:
|
||||||
version "2.3.0"
|
version "2.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
|
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
|
||||||
integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA=
|
integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==
|
||||||
dependencies:
|
|
||||||
is-promise "^2.1.0"
|
|
||||||
|
|
||||||
rxjs@^6.4.0:
|
rxjs@^6.5.3:
|
||||||
version "6.4.0"
|
version "6.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504"
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec"
|
||||||
integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==
|
integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.9.0"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||||
|
@ -1764,7 +1544,7 @@ safe-regex@^1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ret "~0.1.10"
|
ret "~0.1.10"
|
||||||
|
|
||||||
"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
"safer-buffer@>= 2.1.2 < 3", safer-buffer@~2.1.0:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
|
@ -1918,53 +1698,36 @@ split2@^3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
readable-stream "^3.0.0"
|
readable-stream "^3.0.0"
|
||||||
|
|
||||||
sqlite3-trans@^1.2.1:
|
sqlite3-trans@^1.2.2:
|
||||||
version "1.2.1"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/sqlite3-trans/-/sqlite3-trans-1.2.1.tgz#642dff9f6da53d533ccd264b49e68c8818542255"
|
resolved "https://registry.yarnpkg.com/sqlite3-trans/-/sqlite3-trans-1.2.2.tgz#faf268cc8d04dfd1a4854d64a70a229bdb50609f"
|
||||||
integrity sha512-KLtR+PBZN/moxDTKWTwWypkunDCJ0oi5vknjht8omjUXswwUEf+MX2DKtgQB1V5Tsjgc4mL4mHjv9zp7+FHs5g==
|
integrity sha512-+c2je0JMgPeNYHM7vMwEv/nHqOMYa5NNgQDcUyFkVMJ5QHATOQ+GywJptlVbkRCjgSTctmighfWLwUHPlkXbSQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.17.4"
|
lodash "^4.17.15"
|
||||||
|
|
||||||
sqlite3@^4.1.0:
|
sqlite3@^4.2.0:
|
||||||
version "4.1.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-4.1.0.tgz#e051fb9c133be15726322a69e2e37ec560368380"
|
resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-4.2.0.tgz#49026d665e9fc4f922e56fb9711ba5b4c85c4901"
|
||||||
integrity sha512-RvqoKxq+8pDHsJo7aXxsFR18i+dU2Wp5o12qAJOV5LNcDt+fgJsc2QKKg3sIRfXrN9ZjzY1T7SNe/DFVqAXjaw==
|
integrity sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg==
|
||||||
dependencies:
|
dependencies:
|
||||||
nan "^2.12.1"
|
nan "^2.12.1"
|
||||||
node-pre-gyp "^0.11.0"
|
node-pre-gyp "^0.11.0"
|
||||||
request "^2.87.0"
|
|
||||||
|
|
||||||
ssh2-streams@~0.4.7:
|
ssh2-streams@~0.4.10:
|
||||||
version "0.4.7"
|
version "0.4.10"
|
||||||
resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.7.tgz#093b89069de9cf5f06feff0601a5301471b01611"
|
resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.10.tgz#48ef7e8a0e39d8f2921c30521d56dacb31d23a34"
|
||||||
integrity sha512-JhF8BNfeguOqVHOLhXjzLlRKlUP8roAEhiT/y+NcBQCqpRUupLNrRf2M+549OPNVGx21KgKktug4P3MY/IvTig==
|
integrity sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
asn1 "~0.2.0"
|
asn1 "~0.2.0"
|
||||||
bcrypt-pbkdf "^1.0.2"
|
bcrypt-pbkdf "^1.0.2"
|
||||||
streamsearch "~0.1.2"
|
streamsearch "~0.1.2"
|
||||||
|
|
||||||
ssh2@0.8.6:
|
ssh2@0.8.9:
|
||||||
version "0.8.6"
|
version "0.8.9"
|
||||||
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.6.tgz#dcc62e1d3b9e58a21f711f5186f043e4e792e6da"
|
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3"
|
||||||
integrity sha512-T0cPmEtmtC8WxSupicFDjx3vVUdNXO8xu2a/D5bjt8ixOUCe387AgvxU3mJgEHpu7+Sq1ZYx4d3P2pl/yxMH+w==
|
integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==
|
||||||
dependencies:
|
dependencies:
|
||||||
ssh2-streams "~0.4.7"
|
ssh2-streams "~0.4.10"
|
||||||
|
|
||||||
sshpk@^1.7.0:
|
|
||||||
version "1.14.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98"
|
|
||||||
integrity sha1-xvxhZIo9nE52T9P8306hBeSSupg=
|
|
||||||
dependencies:
|
|
||||||
asn1 "~0.2.3"
|
|
||||||
assert-plus "^1.0.0"
|
|
||||||
dashdash "^1.12.0"
|
|
||||||
getpass "^0.1.1"
|
|
||||||
safer-buffer "^2.0.2"
|
|
||||||
optionalDependencies:
|
|
||||||
bcrypt-pbkdf "^1.0.0"
|
|
||||||
ecc-jsbn "~0.1.1"
|
|
||||||
jsbn "~0.1.0"
|
|
||||||
tweetnacl "~0.14.0"
|
|
||||||
|
|
||||||
static-extend@^0.1.1:
|
static-extend@^0.1.1:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
|
@ -2033,13 +1796,20 @@ strip-ansi@^4.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-regex "^3.0.0"
|
ansi-regex "^3.0.0"
|
||||||
|
|
||||||
strip-ansi@^5.1.0, strip-ansi@^5.2.0:
|
strip-ansi@^5.2.0:
|
||||||
version "5.2.0"
|
version "5.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
|
||||||
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
|
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-regex "^4.1.0"
|
ansi-regex "^4.1.0"
|
||||||
|
|
||||||
|
strip-ansi@^6.0.0:
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
|
||||||
|
integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^5.0.0"
|
||||||
|
|
||||||
strip-eof@^1.0.0:
|
strip-eof@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
|
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
|
||||||
|
@ -2050,12 +1820,12 @@ strip-json-comments@~2.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||||
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
||||||
|
|
||||||
supports-color@^5.3.0:
|
supports-color@^7.1.0:
|
||||||
version "5.5.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
|
||||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^3.0.0"
|
has-flag "^4.0.0"
|
||||||
|
|
||||||
tar@^4:
|
tar@^4:
|
||||||
version "4.4.6"
|
version "4.4.6"
|
||||||
|
@ -2070,6 +1840,14 @@ tar@^4:
|
||||||
safe-buffer "^5.1.2"
|
safe-buffer "^5.1.2"
|
||||||
yallist "^3.0.2"
|
yallist "^3.0.2"
|
||||||
|
|
||||||
|
telnet-socket@^0.2.3:
|
||||||
|
version "0.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/telnet-socket/-/telnet-socket-0.2.3.tgz#0ffdc64ea957cb64f8ac5287d45a857f1c05a16e"
|
||||||
|
integrity sha512-PbZycTkGq6VcVUa35FYFySx4pCzmJo4xoMX6cimls1/kv/lrgMfddKfgjBKt6HQuokkkDfieDhGLq/L/P2Unaw==
|
||||||
|
dependencies:
|
||||||
|
binary-parser "1.6.2"
|
||||||
|
buffers "github:NuSkooler/node-buffers"
|
||||||
|
|
||||||
temptmp@^1.1.0:
|
temptmp@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/temptmp/-/temptmp-1.1.0.tgz#bfbbff858d7f7d59c563fbf069758a7775ecd431"
|
resolved "https://registry.yarnpkg.com/temptmp/-/temptmp-1.1.0.tgz#bfbbff858d7f7d59c563fbf069758a7775ecd431"
|
||||||
|
@ -2132,14 +1910,6 @@ to-regex@^3.0.1, to-regex@^3.0.2:
|
||||||
regex-not "^1.0.2"
|
regex-not "^1.0.2"
|
||||||
safe-regex "^1.1.0"
|
safe-regex "^1.1.0"
|
||||||
|
|
||||||
tough-cookie@~2.4.3:
|
|
||||||
version "2.4.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
|
|
||||||
integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
|
|
||||||
dependencies:
|
|
||||||
psl "^1.1.24"
|
|
||||||
punycode "^1.4.1"
|
|
||||||
|
|
||||||
truncate-utf8-bytes@^1.0.0:
|
truncate-utf8-bytes@^1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b"
|
resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b"
|
||||||
|
@ -2152,14 +1922,7 @@ tslib@^1.9.0:
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
||||||
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
|
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
|
||||||
|
|
||||||
tunnel-agent@^0.6.0:
|
tweetnacl@^0.14.3:
|
||||||
version "0.6.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
|
||||||
integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=
|
|
||||||
dependencies:
|
|
||||||
safe-buffer "^5.0.1"
|
|
||||||
|
|
||||||
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
|
||||||
version "0.14.5"
|
version "0.14.5"
|
||||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||||
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
|
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
|
||||||
|
@ -2179,10 +1942,10 @@ union-value@^1.0.0:
|
||||||
is-extendable "^0.1.1"
|
is-extendable "^0.1.1"
|
||||||
set-value "^0.4.3"
|
set-value "^0.4.3"
|
||||||
|
|
||||||
universalify@^0.1.0:
|
universalify@^1.0.0:
|
||||||
version "0.1.2"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d"
|
||||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==
|
||||||
|
|
||||||
unset-value@^1.0.0:
|
unset-value@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
|
@ -2217,24 +1980,10 @@ uuid-parse@1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/uuid-parse/-/uuid-parse-1.1.0.tgz#7061c5a1384ae0e1f943c538094597e1b5f3a65b"
|
resolved "https://registry.yarnpkg.com/uuid-parse/-/uuid-parse-1.1.0.tgz#7061c5a1384ae0e1f943c538094597e1b5f3a65b"
|
||||||
integrity sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==
|
integrity sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==
|
||||||
|
|
||||||
uuid@^3.3.2:
|
uuid@^8.0.0:
|
||||||
version "3.3.2"
|
version "8.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c"
|
||||||
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
|
integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==
|
||||||
|
|
||||||
uuid@^3.3.3:
|
|
||||||
version "3.3.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
|
|
||||||
integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
|
|
||||||
|
|
||||||
verror@1.10.0:
|
|
||||||
version "1.10.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
|
|
||||||
integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=
|
|
||||||
dependencies:
|
|
||||||
assert-plus "^1.0.0"
|
|
||||||
core-util-is "1.0.2"
|
|
||||||
extsprintf "^1.2.0"
|
|
||||||
|
|
||||||
walker@~1.0.5:
|
walker@~1.0.5:
|
||||||
version "1.0.7"
|
version "1.0.7"
|
||||||
|
@ -2262,12 +2011,10 @@ wrappy@1:
|
||||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||||
|
|
||||||
ws@^7.2.0:
|
ws@^7.3.0:
|
||||||
version "7.2.0"
|
version "7.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.0.tgz#422eda8c02a4b5dba7744ba66eebbd84bcef0ec7"
|
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd"
|
||||||
integrity sha512-+SqNqFbwTm/0DC18KYzIsMTnEWpLwJsiasW/O17la4iDRRIO9uaHbvKiAS3AHgTiuuWerK/brj4O6MYZkei9xg==
|
integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==
|
||||||
dependencies:
|
|
||||||
async-limiter "^1.0.0"
|
|
||||||
|
|
||||||
xtend@~4.0.1:
|
xtend@~4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
|
|
Loading…
Reference in New Issue