chat-platform 2.1.4 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +3 -1
- package/__tests__/chat-platform.js +3 -3
- package/__tests__/context-provider-memory.js +1 -1
- package/__tests__/context-provider-sqlite.js +1 -1
- package/__tests__/universal-platform.js +1 -2
- package/chat-context-factory.js +2 -2
- package/chat-log.js +2 -3
- package/chat-platform.js +247 -61
- package/helpers/lcd.js +11 -4
- package/helpers/promises-queue.js +2 -2
- package/helpers/utils.js +1 -1
- package/lib/lcd.js +11 -4
- package/lib/red-stub.js +2 -2
- package/lib/utils.js +1 -1
- package/package.json +8 -7
- package/providers/memory.js +1 -1
- package/providers/plain-file.js +3 -4
- package/providers/sqlite.js +1 -1
- package/universal.js +2 -3
package/.eslintrc
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const moment = require('moment');
|
|
2
1
|
const assert = require('chai').assert;
|
|
3
2
|
const RED = require('../lib/red-stub')();
|
|
4
3
|
const ChatExpress = require('../chat-platform');
|
|
@@ -79,7 +78,7 @@ describe('Chat platform framework', () => {
|
|
|
79
78
|
chatIdKey: payload => payload.myChatIdKey,
|
|
80
79
|
userIdKey: payload => payload.myUserIdKey,
|
|
81
80
|
type: payload => payload.type,
|
|
82
|
-
tsKey: () =>
|
|
81
|
+
tsKey: () => new Date(),
|
|
83
82
|
transport: 'generic',
|
|
84
83
|
inboundMessageEvent: 'message',
|
|
85
84
|
connector: connector,
|
|
@@ -103,7 +102,8 @@ describe('Chat platform framework', () => {
|
|
|
103
102
|
assert.equal(message.payload.userId, '62');
|
|
104
103
|
assert.equal(message.payload.inbound, true);
|
|
105
104
|
assert.equal(message.payload.transport, 'generic');
|
|
106
|
-
assert.
|
|
105
|
+
assert.instanceOf(message.payload.ts, Date);
|
|
106
|
+
assert.isFalse(isNaN(message.payload.ts.getTime()));
|
|
107
107
|
assert.isFunction(message.chat);
|
|
108
108
|
const variables = message.chat().all();
|
|
109
109
|
assert.equal(variables.pending, false);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const moment = require('moment');
|
|
2
1
|
const chai = require('chai');
|
|
3
2
|
const assert = chai.assert;
|
|
4
3
|
const expect = chai.expect;
|
|
@@ -114,7 +113,7 @@ describe('Universal Connector', () => {
|
|
|
114
113
|
assert.equal(variables.payload, 'Bazinga');
|
|
115
114
|
assert.equal(variables.authorized, false);
|
|
116
115
|
assert.equal(variables.language, 'it');
|
|
117
|
-
assert.instanceOf(message.payload.ts,
|
|
116
|
+
assert.instanceOf(message.payload.ts, Date);
|
|
118
117
|
done();
|
|
119
118
|
});
|
|
120
119
|
chatServer.receive({
|
package/chat-context-factory.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const _ = require('
|
|
1
|
+
const _ = require('lodash');
|
|
2
2
|
|
|
3
3
|
const contextProviders = {};
|
|
4
4
|
contextProviders.memory = require('./providers/memory');
|
|
@@ -59,7 +59,7 @@ const ContextProviders = function(RED) {
|
|
|
59
59
|
},
|
|
60
60
|
|
|
61
61
|
hasProvider(provider) {
|
|
62
|
-
return _(methods.getProviders()
|
|
62
|
+
return _.includes(methods.getProviders(), provider);
|
|
63
63
|
},
|
|
64
64
|
|
|
65
65
|
getProvider(providerName, params = {}) {
|
package/chat-log.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
var _ = require('
|
|
2
|
-
var moment = require('moment');
|
|
1
|
+
var _ = require('lodash');
|
|
3
2
|
var fs = require('fs');
|
|
4
3
|
var lcd = require('./helpers/lcd');
|
|
5
4
|
|
|
@@ -103,7 +102,7 @@ module.exports = function(variables) {
|
|
|
103
102
|
+ (!_.isEmpty(name) ? '[' + name.join(' ') + '] ' : '')
|
|
104
103
|
+ (inbound ? '> ' : '< ')
|
|
105
104
|
+ (transport != null ? '[' + transport.toUpperCase() + '] ' : '')
|
|
106
|
-
+
|
|
105
|
+
+ new Date().toString() + ' - ' + stringifiedMessage;
|
|
107
106
|
}
|
|
108
107
|
}
|
|
109
108
|
|
package/chat-platform.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const _ = require('
|
|
2
|
-
const
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const ws = require('ws');
|
|
3
3
|
const clc = require('cli-color');
|
|
4
4
|
const prettyjson = require('prettyjson');
|
|
5
5
|
const { when } = require('./lib/utils');
|
|
@@ -7,7 +7,6 @@ const EventEmitter = require('events').EventEmitter;
|
|
|
7
7
|
const inherits = require('util').inherits;
|
|
8
8
|
const lcd = require('./helpers/lcd');
|
|
9
9
|
const Table = require('cli-table');
|
|
10
|
-
const request = require('request').defaults({ encoding: null });
|
|
11
10
|
|
|
12
11
|
const identity = function(obj) { return obj; };
|
|
13
12
|
const green = clc.greenBright;
|
|
@@ -25,11 +24,20 @@ if (global['redbot-chat-platform'] == null) {
|
|
|
25
24
|
params: {}
|
|
26
25
|
};
|
|
27
26
|
}
|
|
27
|
+
// WebSocket endpoints are mounted on the raw http server, which is shared by every chatbot of the
|
|
28
|
+
// instance: the registry has to live in the global space like the others, and it has to survive
|
|
29
|
+
// ChatExpress.reset() since the "upgrade" listener attached to the server is never removed
|
|
30
|
+
if (global['redbot-chat-platform'].wsRoutes == null) {
|
|
31
|
+
global['redbot-chat-platform'].wsRoutes = {};
|
|
32
|
+
global['redbot-chat-platform'].wsServers = new WeakSet();
|
|
33
|
+
}
|
|
28
34
|
|
|
29
35
|
let _messageTypes = global['redbot-chat-platform'].messageTypes;
|
|
30
36
|
let _events = global['redbot-chat-platform'].events;
|
|
31
37
|
let _platforms = global['redbot-chat-platform'].platforms;
|
|
32
38
|
let _params = global['redbot-chat-platform'].params;
|
|
39
|
+
let _wsRoutes = global['redbot-chat-platform'].wsRoutes;
|
|
40
|
+
let _wsServers = global['redbot-chat-platform'].wsServers;
|
|
33
41
|
let _globalCallbacks = {};
|
|
34
42
|
|
|
35
43
|
const ChatExpress = function(options) {
|
|
@@ -52,6 +60,9 @@ const ChatExpress = function(options) {
|
|
|
52
60
|
RED: null,
|
|
53
61
|
routes: null,
|
|
54
62
|
routesDescription: null,
|
|
63
|
+
wsRoutes: null,
|
|
64
|
+
wsRoutesDescription: null,
|
|
65
|
+
wsVerifyClient: null,
|
|
55
66
|
events: null,
|
|
56
67
|
relaxChatId: false,
|
|
57
68
|
bundle: false,
|
|
@@ -541,11 +552,13 @@ const ChatExpress = function(options) {
|
|
|
541
552
|
var options = chatServer.getOptions();
|
|
542
553
|
var connector = options.connector;
|
|
543
554
|
if (connector != null) {
|
|
544
|
-
if (options.inboundMessageEvent
|
|
555
|
+
if (_.isFunction(options.inboundMessageEvent)) {
|
|
545
556
|
connector.off(options.inboundMessageEvent);
|
|
546
557
|
}
|
|
547
558
|
_(events).each(function (callback, eventName) {
|
|
548
|
-
|
|
559
|
+
if (_.isFunction(eventName)) {
|
|
560
|
+
connector.off(eventName);
|
|
561
|
+
}
|
|
549
562
|
});
|
|
550
563
|
}
|
|
551
564
|
}
|
|
@@ -562,7 +575,7 @@ const ChatExpress = function(options) {
|
|
|
562
575
|
|
|
563
576
|
function unmountRoutes(RED, routes, chatServer) {
|
|
564
577
|
if (routes != null) {
|
|
565
|
-
const endpoints = _
|
|
578
|
+
const endpoints = _.keys(routes);
|
|
566
579
|
if (!_.isEmpty(endpoints)) {
|
|
567
580
|
let routesCount = RED.httpNode._router.stack.length;
|
|
568
581
|
let idx = 0;
|
|
@@ -571,7 +584,7 @@ const ChatExpress = function(options) {
|
|
|
571
584
|
const route = stack[idx];
|
|
572
585
|
if (route != null && route.name != null) {
|
|
573
586
|
const routeName = String(route.name).replace('bound ', '');
|
|
574
|
-
if (_.
|
|
587
|
+
if (_.includes(endpoints, routeName)) {
|
|
575
588
|
stack.splice(idx, 1);
|
|
576
589
|
} else {
|
|
577
590
|
idx += 1;
|
|
@@ -611,7 +624,7 @@ const ChatExpress = function(options) {
|
|
|
611
624
|
console.log(lcd.timestamp() + '');
|
|
612
625
|
// eslint-disable-next-line no-console
|
|
613
626
|
console.log(lcd.timestamp() + grey('------ WebHooks for ' + options.transport.toUpperCase() + '----------------'));
|
|
614
|
-
_(routes
|
|
627
|
+
_.each(routes, (middleware, route) => {
|
|
615
628
|
const host = 'http://localhost' + (uiPort != '80' ? ':' + uiPort : '');
|
|
616
629
|
const callback = generateCallback(route, chatServer);
|
|
617
630
|
// make description
|
|
@@ -634,6 +647,145 @@ const ChatExpress = function(options) {
|
|
|
634
647
|
return when(true);
|
|
635
648
|
}
|
|
636
649
|
|
|
650
|
+
/**
|
|
651
|
+
* @method wsPathname
|
|
652
|
+
* The pathname an upgrade request was sent to, without the query string
|
|
653
|
+
* @param {string} url
|
|
654
|
+
* @return {string}
|
|
655
|
+
*/
|
|
656
|
+
function wsPathname(url) {
|
|
657
|
+
const pathname = String(url != null ? url : '').split('?')[0];
|
|
658
|
+
// a trailing slash is the same endpoint: "/redbot/my-bot/ws/" === "/redbot/my-bot/ws"
|
|
659
|
+
return pathname.length > 1 && pathname.slice(-1) === '/' ? pathname.slice(0, -1) : pathname;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* @method wsFullPath
|
|
664
|
+
* Absolute path of a WebSocket endpoint. Like the Express ones, the paths declared by a platform are
|
|
665
|
+
* relative to `httpNodeRoot`, but an upgrade request never enters the Express router so the prefix
|
|
666
|
+
* has to be applied by hand
|
|
667
|
+
* @param {object} RED
|
|
668
|
+
* @param {string} route
|
|
669
|
+
* @return {string}
|
|
670
|
+
*/
|
|
671
|
+
function wsFullPath(RED, route) {
|
|
672
|
+
const root = RED.settings != null && !_.isEmpty(RED.settings.httpNodeRoot) ?
|
|
673
|
+
RED.settings.httpNodeRoot : '/';
|
|
674
|
+
const prefix = root.slice(-1) === '/' ? root.slice(0, -1) : root;
|
|
675
|
+
return `${prefix}${route.charAt(0) === '/' ? route : `/${route}`}`;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* @method dispatchUpgrade
|
|
680
|
+
* The one and only "upgrade" listener of the process: hand the request over to the WebSocket endpoint
|
|
681
|
+
* registered on that path, if any.
|
|
682
|
+
* Node-RED attaches its own listeners to the same server (the editor "comms" channel and the core
|
|
683
|
+
* WebSocket nodes) and, exactly like them, a request that matches nothing here is left alone instead
|
|
684
|
+
* of being destroyed, so that the other listeners can still handle it
|
|
685
|
+
*/
|
|
686
|
+
function dispatchUpgrade(request, socket, head) {
|
|
687
|
+
const entry = _wsRoutes[wsPathname(request.url)];
|
|
688
|
+
if (entry == null) {
|
|
689
|
+
// not one of ours: don't destroy the socket, another listener may want to handle it
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
entry.wss.handleUpgrade(request, socket, head, function done(connection) {
|
|
693
|
+
entry.wss.emit('connection', connection, request);
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* @method mountUpgradeListener
|
|
699
|
+
* Attach the dispatcher to the http server, once per server: a Node-RED deploy re-creates every
|
|
700
|
+
* configuration node, so a listener per chatbot would pile up at every deploy
|
|
701
|
+
* @param {object} server
|
|
702
|
+
*/
|
|
703
|
+
function mountUpgradeListener(server) {
|
|
704
|
+
if (_wsServers.has(server)) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
server.on('upgrade', dispatchUpgrade);
|
|
708
|
+
_wsServers.add(server);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* @method mountWsRoutes
|
|
713
|
+
* Mount the WebSocket endpoints of a chat server. Unlike the Express routes these are matched exactly,
|
|
714
|
+
* so `wsRoutes` can also be a function (bound to the chat server) returning the routes, for a platform
|
|
715
|
+
* that needs to build the path out of its own configuration (i.e. a bot id in the middle of the path)
|
|
716
|
+
* @param {object} RED
|
|
717
|
+
* @param {object|function} wsRoutes
|
|
718
|
+
* @param {object} wsRoutesDescription
|
|
719
|
+
* @param {object} chatServer
|
|
720
|
+
*/
|
|
721
|
+
// eslint-disable-next-line max-params
|
|
722
|
+
function mountWsRoutes(RED, wsRoutes, wsRoutesDescription, chatServer) {
|
|
723
|
+
const routes = _.isFunction(wsRoutes) ? wsRoutes.call(chatServer) : wsRoutes;
|
|
724
|
+
if (routes == null || _.isEmpty(routes)) {
|
|
725
|
+
return when(true);
|
|
726
|
+
}
|
|
727
|
+
// the descriptions are keyed by route: when the routes are generated, so are their descriptions
|
|
728
|
+
const descriptions = _.isFunction(wsRoutesDescription) ?
|
|
729
|
+
wsRoutesDescription.call(chatServer) : wsRoutesDescription;
|
|
730
|
+
if (RED == null || RED.server == null) {
|
|
731
|
+
chatServer.warn('Impossible to mount the WebSocket endpoints: ' +
|
|
732
|
+
(RED == null ? '"RED" param is empty' : '"RED.server" is not available'));
|
|
733
|
+
return when(true);
|
|
734
|
+
}
|
|
735
|
+
const options = chatServer.getOptions();
|
|
736
|
+
const uiPort = RED.settings.get('uiPort');
|
|
737
|
+
mountUpgradeListener(RED.server);
|
|
738
|
+
// eslint-disable-next-line no-console
|
|
739
|
+
console.log(lcd.timestamp() + '');
|
|
740
|
+
// eslint-disable-next-line no-console
|
|
741
|
+
console.log(lcd.timestamp() + grey('------ WebSockets for ' + options.transport.toUpperCase() + '--------------'));
|
|
742
|
+
_.each(routes, (handler, route) => {
|
|
743
|
+
const host = 'ws://localhost' + (uiPort != '80' ? ':' + uiPort : '');
|
|
744
|
+
const path = wsFullPath(RED, generateCallback(route, chatServer));
|
|
745
|
+
if (_wsRoutes[path] != null) {
|
|
746
|
+
chatServer.error(`The WebSocket endpoint ${path} is already mounted by another chatbot, skipped`);
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
let description = null;
|
|
750
|
+
if (descriptions != null && _.isString(descriptions[route])) {
|
|
751
|
+
description = descriptions[route];
|
|
752
|
+
} else if (descriptions != null && _.isFunction(descriptions[route])) {
|
|
753
|
+
description = descriptions[route].call(chatServer);
|
|
754
|
+
}
|
|
755
|
+
// eslint-disable-next-line no-console
|
|
756
|
+
console.log(lcd.timestamp() + green(host + path) + (description != null ? grey(' - ') + white(description) : ''));
|
|
757
|
+
const wss = new ws.Server(_.isFunction(options.wsVerifyClient) ?
|
|
758
|
+
{ noServer: true, verifyClient: options.wsVerifyClient.bind(chatServer) } : { noServer: true });
|
|
759
|
+
wss.setMaxListeners(0);
|
|
760
|
+
wss.on('connection', handler.bind(chatServer));
|
|
761
|
+
wss.on('error', error => chatServer.error(`WebSocket endpoint ${path}: ${error.message}`));
|
|
762
|
+
_wsRoutes[path] = { wss: wss, chatServer: chatServer };
|
|
763
|
+
return null;
|
|
764
|
+
});
|
|
765
|
+
// eslint-disable-next-line no-console
|
|
766
|
+
console.log(lcd.timestamp() + '');
|
|
767
|
+
return when(true);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* @method unmountWsRoutes
|
|
772
|
+
* Remove every WebSocket endpoint of a chat server. The dispatcher attached to the http server stays
|
|
773
|
+
* there (it's shared by the whole process and it does nothing without a matching endpoint)
|
|
774
|
+
* @param {object} chatServer
|
|
775
|
+
*/
|
|
776
|
+
function unmountWsRoutes(chatServer) {
|
|
777
|
+
_.keys(_wsRoutes).forEach(path => {
|
|
778
|
+
const entry = _wsRoutes[path];
|
|
779
|
+
if (entry == null || entry.chatServer !== chatServer) {
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
delete _wsRoutes[path];
|
|
783
|
+
// closing the server terminates the connected clients: the widgets will reconnect once the
|
|
784
|
+
// chatbot is up again, a Node-RED deploy re-creates the configuration nodes
|
|
785
|
+
entry.wss.close();
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
|
|
637
789
|
var methods = {
|
|
638
790
|
|
|
639
791
|
'in': function() {
|
|
@@ -687,8 +839,8 @@ const ChatExpress = function(options) {
|
|
|
687
839
|
if (type == null || typeof type !== 'string') {
|
|
688
840
|
throw 'Missing type in .registerMessageType()';
|
|
689
841
|
}
|
|
690
|
-
name = name != null ? name :
|
|
691
|
-
let typeDescriptor = _(_messageTypes
|
|
842
|
+
name = name != null ? name : _.upperFirst(type);
|
|
843
|
+
let typeDescriptor = _.find(_messageTypes, { type: type });
|
|
692
844
|
if (typeDescriptor == null) {
|
|
693
845
|
typeDescriptor = { type: type };
|
|
694
846
|
_messageTypes.push(typeDescriptor);
|
|
@@ -715,7 +867,7 @@ const ChatExpress = function(options) {
|
|
|
715
867
|
if (name == null || typeof name !== 'string') {
|
|
716
868
|
throw 'Missing name in .registerEvent()';
|
|
717
869
|
}
|
|
718
|
-
var eventDescriptor = _(_events
|
|
870
|
+
var eventDescriptor = _.find(_events, { name: name });
|
|
719
871
|
if (eventDescriptor == null) {
|
|
720
872
|
eventDescriptor = { name: name };
|
|
721
873
|
_events.push(eventDescriptor);
|
|
@@ -787,15 +939,48 @@ const ChatExpress = function(options) {
|
|
|
787
939
|
console.log(prettyjson.render(obj));
|
|
788
940
|
};
|
|
789
941
|
this.request = function(options = {}) {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
942
|
+
var url = options.url || options.uri;
|
|
943
|
+
// Translate "request"-style options into a "fetch" call
|
|
944
|
+
if (options.qs != null) {
|
|
945
|
+
var qs = new URLSearchParams(options.qs).toString();
|
|
946
|
+
if (qs) {
|
|
947
|
+
url += (url.indexOf('?') === -1 ? '?' : '&') + qs;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
var headers = _.extend({}, options.headers);
|
|
951
|
+
var body;
|
|
952
|
+
if (options.json != null && options.json !== true && options.json !== false) {
|
|
953
|
+
body = JSON.stringify(options.json);
|
|
954
|
+
if (headers['content-type'] == null && headers['Content-Type'] == null) {
|
|
955
|
+
headers['Content-Type'] = 'application/json';
|
|
956
|
+
}
|
|
957
|
+
} else if (options.json === true && options.body != null) {
|
|
958
|
+
body = JSON.stringify(options.body);
|
|
959
|
+
if (headers['content-type'] == null && headers['Content-Type'] == null) {
|
|
960
|
+
headers['Content-Type'] = 'application/json';
|
|
961
|
+
}
|
|
962
|
+
} else if (options.form != null) {
|
|
963
|
+
body = new URLSearchParams(options.form).toString();
|
|
964
|
+
if (headers['content-type'] == null && headers['Content-Type'] == null) {
|
|
965
|
+
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
966
|
+
}
|
|
967
|
+
} else if (options.body != null) {
|
|
968
|
+
body = options.body;
|
|
969
|
+
}
|
|
970
|
+
return fetch(url, {
|
|
971
|
+
method: options.method || 'GET',
|
|
972
|
+
headers: headers,
|
|
973
|
+
body: body
|
|
974
|
+
})
|
|
975
|
+
.then(function(response) {
|
|
976
|
+
// Preserve the previous behaviour ({ encoding: null }) of resolving with a Buffer
|
|
977
|
+
return response.arrayBuffer().then(function(buffer) {
|
|
978
|
+
return Buffer.from(buffer);
|
|
979
|
+
});
|
|
980
|
+
})
|
|
981
|
+
.catch(function() {
|
|
982
|
+
return Promise.reject(`Error calling URL ${url}`);
|
|
797
983
|
});
|
|
798
|
-
});
|
|
799
984
|
};
|
|
800
985
|
this.getOptions = function() {
|
|
801
986
|
return this.options;
|
|
@@ -815,7 +1000,7 @@ const ChatExpress = function(options) {
|
|
|
815
1000
|
return outboundMessage(message, this);
|
|
816
1001
|
} else {
|
|
817
1002
|
var task = when(true);
|
|
818
|
-
_(message.payload
|
|
1003
|
+
_.each(message.payload, function(payload) {
|
|
819
1004
|
task = task.then(function() {
|
|
820
1005
|
return outboundMessage(_.extend({}, message, { payload: payload }), _this);
|
|
821
1006
|
});
|
|
@@ -861,8 +1046,8 @@ const ChatExpress = function(options) {
|
|
|
861
1046
|
if (type == null || typeof type !== 'string') {
|
|
862
1047
|
throw 'Missing type in .registerMessageType()';
|
|
863
1048
|
}
|
|
864
|
-
name = name != null ? name :
|
|
865
|
-
let typeDescriptor = _(_messageTypes
|
|
1049
|
+
name = name != null ? name : _.upperFirst(type);
|
|
1050
|
+
let typeDescriptor = _.find(_messageTypes, { type: type });
|
|
866
1051
|
if (typeDescriptor == null) {
|
|
867
1052
|
typeDescriptor = { type: type };
|
|
868
1053
|
_messageTypes.push(typeDescriptor);
|
|
@@ -889,7 +1074,7 @@ const ChatExpress = function(options) {
|
|
|
889
1074
|
if (name == null || typeof name !== 'string') {
|
|
890
1075
|
throw 'Missing name in .registerEvent()';
|
|
891
1076
|
}
|
|
892
|
-
var eventDescriptor = _(_events
|
|
1077
|
+
var eventDescriptor = _.find(_events, { name: name });
|
|
893
1078
|
if (eventDescriptor == null) {
|
|
894
1079
|
eventDescriptor = { name: name };
|
|
895
1080
|
_events.push(eventDescriptor);
|
|
@@ -972,6 +1157,9 @@ const ChatExpress = function(options) {
|
|
|
972
1157
|
.then(function() {
|
|
973
1158
|
return mountRoutes(options.RED, options.routes, options.routesDescription, _this);
|
|
974
1159
|
})
|
|
1160
|
+
.then(function() {
|
|
1161
|
+
return mountWsRoutes(options.RED, options.wsRoutes, options.wsRoutesDescription, _this);
|
|
1162
|
+
})
|
|
975
1163
|
.then(function() {
|
|
976
1164
|
return mountEvents(options.events, _this);
|
|
977
1165
|
})
|
|
@@ -985,7 +1173,7 @@ const ChatExpress = function(options) {
|
|
|
985
1173
|
}
|
|
986
1174
|
// listen to inbound event
|
|
987
1175
|
var connector = options.connector;
|
|
988
|
-
if (connector != null && options.inboundMessageEvent != null) {
|
|
1176
|
+
if (connector != null && options.inboundMessageEvent != null && _.isFunction(connector.on)) {
|
|
989
1177
|
connector.on(options.inboundMessageEvent, function (message) {
|
|
990
1178
|
inboundMessage(message, chatServer);
|
|
991
1179
|
});
|
|
@@ -1060,6 +1248,7 @@ const ChatExpress = function(options) {
|
|
|
1060
1248
|
this.emit('stop');
|
|
1061
1249
|
var options = this.getOptions();
|
|
1062
1250
|
unmountRoutes(options.RED, options.routes, this);
|
|
1251
|
+
unmountWsRoutes(this);
|
|
1063
1252
|
unmountEvents(options.events, this);
|
|
1064
1253
|
var stack = when(true);
|
|
1065
1254
|
if (_.isFunction(options.onStop)) {
|
|
@@ -1086,20 +1275,20 @@ const ChatExpress = function(options) {
|
|
|
1086
1275
|
Static methods for ChatExpress
|
|
1087
1276
|
*/
|
|
1088
1277
|
ChatExpress.getMessageTypes = function() {
|
|
1089
|
-
return _
|
|
1278
|
+
return _.map(_messageTypes, function(item) {
|
|
1090
1279
|
return {
|
|
1091
1280
|
value: item.type,
|
|
1092
1281
|
label: item.name,
|
|
1093
|
-
platforms: _(item.platforms)
|
|
1282
|
+
platforms: _.keys(item.platforms)
|
|
1094
1283
|
};
|
|
1095
1284
|
});
|
|
1096
1285
|
};
|
|
1097
1286
|
ChatExpress.getEvents = function() {
|
|
1098
|
-
return _
|
|
1287
|
+
return _.map(_events, function(item) {
|
|
1099
1288
|
return {
|
|
1100
1289
|
value: item.name,
|
|
1101
1290
|
label: item.description,
|
|
1102
|
-
platforms: _(item.platforms)
|
|
1291
|
+
platforms: _.keys(item.platforms)
|
|
1103
1292
|
};
|
|
1104
1293
|
});
|
|
1105
1294
|
};
|
|
@@ -1110,19 +1299,18 @@ ChatExpress.getParams = function() {
|
|
|
1110
1299
|
function compatibilityTable(items, options) {
|
|
1111
1300
|
options = _.extend({ column: 'Column' }, options);
|
|
1112
1301
|
// collect platforms except universal
|
|
1113
|
-
var platforms = _
|
|
1114
|
-
.map(function(platform) {
|
|
1302
|
+
var platforms = _.reject(
|
|
1303
|
+
_.map(ChatExpress.getPlatforms(), function(platform) {
|
|
1115
1304
|
return platform.id;
|
|
1116
|
-
})
|
|
1117
|
-
|
|
1305
|
+
}),
|
|
1306
|
+
function(id) {
|
|
1118
1307
|
return id === 'universal';
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
.value();
|
|
1308
|
+
}
|
|
1309
|
+
).sort();
|
|
1122
1310
|
// build header
|
|
1123
1311
|
var head = [options.column];
|
|
1124
|
-
_
|
|
1125
|
-
head.push(
|
|
1312
|
+
_.each(platforms, function(name) {
|
|
1313
|
+
head.push(_.upperFirst(name));
|
|
1126
1314
|
});
|
|
1127
1315
|
var colAligns = ['left'];
|
|
1128
1316
|
_.times(platforms.length, function() {
|
|
@@ -1137,21 +1325,19 @@ function compatibilityTable(items, options) {
|
|
|
1137
1325
|
}
|
|
1138
1326
|
});
|
|
1139
1327
|
// message types columns
|
|
1140
|
-
_(
|
|
1141
|
-
.
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
.each(function(
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
row.push('');
|
|
1151
|
-
}
|
|
1152
|
-
});
|
|
1153
|
-
table.push(row);
|
|
1328
|
+
_.each(_.sortBy(items, function(type) {
|
|
1329
|
+
return type.name;
|
|
1330
|
+
}), function(type) {
|
|
1331
|
+
var row = [type.name];
|
|
1332
|
+
_.each(platforms, function(platform) {
|
|
1333
|
+
if (type.platforms[platform]) {
|
|
1334
|
+
row.push('✔');
|
|
1335
|
+
} else {
|
|
1336
|
+
row.push('');
|
|
1337
|
+
}
|
|
1154
1338
|
});
|
|
1339
|
+
table.push(row);
|
|
1340
|
+
});
|
|
1155
1341
|
// eslint-disable-next-line no-console
|
|
1156
1342
|
console.log(table.toString());
|
|
1157
1343
|
|
|
@@ -1172,21 +1358,21 @@ ChatExpress.showCompatibilityChart = function() {
|
|
|
1172
1358
|
* @return {Array}
|
|
1173
1359
|
*/
|
|
1174
1360
|
ChatExpress.getPlatforms = function() {
|
|
1175
|
-
var platforms = _
|
|
1361
|
+
var platforms = _.keys(_platforms);
|
|
1176
1362
|
|
|
1177
|
-
return _
|
|
1178
|
-
.map(function(platform) {
|
|
1363
|
+
return _.sortBy(
|
|
1364
|
+
_.map(platforms, function(platform) {
|
|
1179
1365
|
return {
|
|
1180
1366
|
id: _platforms[platform].id,
|
|
1181
1367
|
name: _platforms[platform].name,
|
|
1182
1368
|
universal: _platforms[platform].universal,
|
|
1183
1369
|
color: _platforms[platform].color
|
|
1184
1370
|
};
|
|
1185
|
-
})
|
|
1186
|
-
|
|
1371
|
+
}),
|
|
1372
|
+
function(platform) {
|
|
1187
1373
|
return platform.name != null ? platform.name : platform.id;
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1374
|
+
}
|
|
1375
|
+
);
|
|
1190
1376
|
};
|
|
1191
1377
|
|
|
1192
1378
|
/**
|
|
@@ -1199,9 +1385,9 @@ ChatExpress.getPlatforms = function() {
|
|
|
1199
1385
|
ChatExpress.isSupported = function(platform, type) {
|
|
1200
1386
|
if (type == null) {
|
|
1201
1387
|
const platforms = ChatExpress.getPlatforms();
|
|
1202
|
-
return _(platforms
|
|
1388
|
+
return _.find(platforms, { id: platform }) != null;
|
|
1203
1389
|
} else {
|
|
1204
|
-
const messageType = _(_messageTypes
|
|
1390
|
+
const messageType = _.find(_messageTypes, { type: type });
|
|
1205
1391
|
return messageType != null && messageType.platforms[platform];
|
|
1206
1392
|
}
|
|
1207
1393
|
};
|
|
@@ -1219,7 +1405,7 @@ ChatExpress.isSupported = function(platform, type) {
|
|
|
1219
1405
|
* @return {String} Null if no errors
|
|
1220
1406
|
*/
|
|
1221
1407
|
ChatExpress.isValidFile = function(platform, type, file) {
|
|
1222
|
-
const messageType = _(_messageTypes
|
|
1408
|
+
const messageType = _.find(_messageTypes, { type: type });
|
|
1223
1409
|
if (messageType != null) {
|
|
1224
1410
|
const validator = messageType.validators[platform];
|
|
1225
1411
|
if (validator != null) {
|
package/helpers/lcd.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
var clc = require('cli-color');
|
|
2
|
-
var _ = require('
|
|
2
|
+
var _ = require('lodash');
|
|
3
3
|
var prettyjson = require('prettyjson');
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
var MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
6
|
+
// Format a date as 'DD MMM HH:mm:ss' (e.g. '16 Jun 14:30:45')
|
|
7
|
+
function formatTimestamp(date) {
|
|
8
|
+
var pad = function(n) { return n < 10 ? '0' + n : '' + n; };
|
|
9
|
+
return pad(date.getDate()) + ' ' + MONTHS[date.getMonth()] + ' '
|
|
10
|
+
+ pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds());
|
|
11
|
+
}
|
|
5
12
|
|
|
6
13
|
var warn = clc.yellow;
|
|
7
14
|
var grey = clc.blackBright;
|
|
@@ -22,7 +29,7 @@ var LCD = {
|
|
|
22
29
|
red: red,
|
|
23
30
|
|
|
24
31
|
timestamp() {
|
|
25
|
-
return LCD.white(
|
|
32
|
+
return LCD.white(formatTimestamp(new Date()) + ' - [info] ');
|
|
26
33
|
},
|
|
27
34
|
|
|
28
35
|
dump: function(e, title) {
|
|
@@ -54,7 +61,7 @@ var LCD = {
|
|
|
54
61
|
_(payload).each(function(value, key) {
|
|
55
62
|
if (value instanceof Buffer) {
|
|
56
63
|
payload[key] = '<Buffer>';
|
|
57
|
-
} else if (value instanceof
|
|
64
|
+
} else if (value instanceof Date) {
|
|
58
65
|
payload[key] = value.toString();
|
|
59
66
|
}
|
|
60
67
|
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
var _ = require('
|
|
1
|
+
var _ = require('lodash');
|
|
2
2
|
|
|
3
3
|
var FileQueue = function() {
|
|
4
4
|
var tasks = [];
|
|
5
5
|
function removeTask(promise) {
|
|
6
|
-
tasks = _
|
|
6
|
+
tasks = _.reject(tasks, function(task) {
|
|
7
7
|
return task === promise;
|
|
8
8
|
});
|
|
9
9
|
}
|
package/helpers/utils.js
CHANGED
package/lib/lcd.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
var clc = require('cli-color');
|
|
2
|
-
var _ = require('
|
|
2
|
+
var _ = require('lodash');
|
|
3
3
|
var prettyjson = require('prettyjson');
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
var MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
6
|
+
// Format a date as 'DD MMM HH:mm:ss' (e.g. '16 Jun 14:30:45')
|
|
7
|
+
function formatTimestamp(date) {
|
|
8
|
+
var pad = function(n) { return n < 10 ? '0' + n : '' + n; };
|
|
9
|
+
return pad(date.getDate()) + ' ' + MONTHS[date.getMonth()] + ' '
|
|
10
|
+
+ pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds());
|
|
11
|
+
}
|
|
5
12
|
|
|
6
13
|
var warn = clc.yellow;
|
|
7
14
|
var grey = clc.blackBright;
|
|
@@ -22,7 +29,7 @@ var LCD = {
|
|
|
22
29
|
red: red,
|
|
23
30
|
|
|
24
31
|
timestamp() {
|
|
25
|
-
return LCD.white(
|
|
32
|
+
return LCD.white(formatTimestamp(new Date()) + ' - [info] ');
|
|
26
33
|
},
|
|
27
34
|
|
|
28
35
|
dump: function(e, title) {
|
|
@@ -57,7 +64,7 @@ var LCD = {
|
|
|
57
64
|
_(payload).each(function(value, key) {
|
|
58
65
|
if (value instanceof Buffer) {
|
|
59
66
|
payload[key] = '<Buffer>';
|
|
60
|
-
} else if (value instanceof
|
|
67
|
+
} else if (value instanceof Date) {
|
|
61
68
|
payload[key] = value.toString();
|
|
62
69
|
}
|
|
63
70
|
});
|
package/lib/red-stub.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
|
-
var _ = require('
|
|
2
|
+
var _ = require('lodash');
|
|
3
3
|
var ChatContextFactory = require('../chat-context-factory')({});
|
|
4
4
|
var ChatContextProvider = ChatContextFactory.getProvider('memory', {});
|
|
5
5
|
|
|
@@ -38,7 +38,7 @@ module.exports = function() {
|
|
|
38
38
|
|
|
39
39
|
environment: {
|
|
40
40
|
chat: function(chatId, obj) {
|
|
41
|
-
_(obj
|
|
41
|
+
_.each(obj, function(value, key) {
|
|
42
42
|
_chatContext.set(key, value);
|
|
43
43
|
});
|
|
44
44
|
}
|
package/lib/utils.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chat-platform",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Universal Chat Platform",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -10,16 +10,17 @@
|
|
|
10
10
|
},
|
|
11
11
|
"author": "",
|
|
12
12
|
"license": "ISC",
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
13
16
|
"dependencies": {
|
|
14
17
|
"cli-color": "^1.4.0",
|
|
15
18
|
"cli-table": "^0.3.1",
|
|
16
|
-
"
|
|
19
|
+
"lodash": "^4.18.1",
|
|
17
20
|
"prettyjson": "^1.2.1",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"underscore": "^1.9.1",
|
|
22
|
-
"underscore.string": "^3.3.5"
|
|
21
|
+
"sequelize": "^6.37.8",
|
|
22
|
+
"sqlite3": "^5.1.7",
|
|
23
|
+
"ws": "^7.5.7"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
25
26
|
"chai": "^4.1.1",
|
package/providers/memory.js
CHANGED
package/providers/plain-file.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
const _ = require('
|
|
1
|
+
const _ = require('lodash');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
-
const moment = require('moment');
|
|
4
3
|
const crypto = require('crypto');
|
|
5
4
|
|
|
6
5
|
const lcd = require('../helpers/lcd');
|
|
@@ -20,7 +19,7 @@ const parse = content => {
|
|
|
20
19
|
// go through every key/value to search for a date-like string
|
|
21
20
|
_(obj).each(function(value, key) {
|
|
22
21
|
if (_.isString(value) && value.match(date)) {
|
|
23
|
-
obj[key] =
|
|
22
|
+
obj[key] = new Date(value);
|
|
24
23
|
}
|
|
25
24
|
});
|
|
26
25
|
|
|
@@ -241,7 +240,7 @@ _.extend(FileStore.prototype, {
|
|
|
241
240
|
// go through every key/value to search for a date-like string
|
|
242
241
|
_(obj).each((value, key) => {
|
|
243
242
|
if (_.isString(value) && value.match(date)) {
|
|
244
|
-
obj[key] =
|
|
243
|
+
obj[key] = new Date(value);
|
|
245
244
|
}
|
|
246
245
|
});
|
|
247
246
|
} catch(e) {
|
package/providers/sqlite.js
CHANGED
package/universal.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
const moment = require('moment');
|
|
2
1
|
const ChatExpress = require('./chat-platform');
|
|
3
2
|
const utils = require('./lib/utils');
|
|
4
3
|
const when = utils.when;
|
|
5
|
-
const _ = require('
|
|
4
|
+
const _ = require('lodash');
|
|
6
5
|
|
|
7
6
|
const Universal = new ChatExpress({
|
|
8
7
|
transport: 'universal',
|
|
@@ -14,7 +13,7 @@ const Universal = new ChatExpress({
|
|
|
14
13
|
return payload.userId;
|
|
15
14
|
},
|
|
16
15
|
tsKey() {
|
|
17
|
-
return
|
|
16
|
+
return new Date();
|
|
18
17
|
},
|
|
19
18
|
language() {
|
|
20
19
|
return null;
|