xcraft-core-busclient 5.0.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/.editorconfig +9 -0
- package/.eslintrc.js +28 -0
- package/README.md +10 -0
- package/config.js +13 -0
- package/index.js +566 -0
- package/lib/command.js +148 -0
- package/lib/events.js +223 -0
- package/lib/resp.js +241 -0
- package/package.json +44 -0
package/.editorconfig
ADDED
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
root: true,
|
|
5
|
+
parserOptions: {
|
|
6
|
+
ecmaVersion: 7,
|
|
7
|
+
sourceType: 'module',
|
|
8
|
+
ecmaFeatures: {
|
|
9
|
+
jsx: true,
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
env: {
|
|
13
|
+
browser: true,
|
|
14
|
+
mocha: true,
|
|
15
|
+
node: true,
|
|
16
|
+
es6: true,
|
|
17
|
+
},
|
|
18
|
+
parser: 'babel-eslint',
|
|
19
|
+
plugins: ['react', 'babel'],
|
|
20
|
+
extends: ['prettier', 'eslint:recommended', 'plugin:react/recommended'],
|
|
21
|
+
rules: {
|
|
22
|
+
// Other rules
|
|
23
|
+
'no-console': 'off',
|
|
24
|
+
'valid-jsdoc': ['error', {requireReturn: false}],
|
|
25
|
+
'eqeqeq': 'error',
|
|
26
|
+
'react/display-name': 'off',
|
|
27
|
+
},
|
|
28
|
+
};
|
package/README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# xcraft-core-busclient
|
|
2
|
+
|
|
3
|
+
Client for the Xcraft bus.
|
|
4
|
+
|
|
5
|
+
This module provides the interfaces in order to connect to a server, send
|
|
6
|
+
commands and send or subscribe to events.
|
|
7
|
+
|
|
8
|
+
It provides the global busClient too. This handler must be used between the
|
|
9
|
+
modules in order to have always the same connection everywhere. Otherwise
|
|
10
|
+
you can connect to several servers by instancing a new busClient for each one.
|
package/config.js
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const moduleName = 'busclient';
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
Router,
|
|
7
|
+
Cache,
|
|
8
|
+
helpers: {extractIds},
|
|
9
|
+
} = require('xcraft-core-transport');
|
|
10
|
+
const {v4: uuidV4} = require('uuid');
|
|
11
|
+
|
|
12
|
+
const xLog = require('xcraft-core-log')(moduleName, null);
|
|
13
|
+
const xUtils = require('xcraft-core-utils');
|
|
14
|
+
|
|
15
|
+
const {EventEmitter} = require('events');
|
|
16
|
+
const Resp = require('./lib/resp.js');
|
|
17
|
+
|
|
18
|
+
let globalBusClient = null;
|
|
19
|
+
|
|
20
|
+
class BusClient extends EventEmitter {
|
|
21
|
+
constructor(busConfig, subscriptions) {
|
|
22
|
+
super();
|
|
23
|
+
|
|
24
|
+
this._busConfig = busConfig; /* can be null */
|
|
25
|
+
|
|
26
|
+
const id = uuidV4();
|
|
27
|
+
this._subSocket = new Router(id, 'sub', xLog);
|
|
28
|
+
this._pushSocket = new Router(id, 'push', xLog);
|
|
29
|
+
|
|
30
|
+
this._eventsRegistry = {};
|
|
31
|
+
this._commandsRegistry = {};
|
|
32
|
+
this._eventsCache = new Cache();
|
|
33
|
+
|
|
34
|
+
this._token = 'invalid';
|
|
35
|
+
this._orcName = null;
|
|
36
|
+
this._autoconnect = false;
|
|
37
|
+
this._connected = false;
|
|
38
|
+
this._subClosed = true;
|
|
39
|
+
this._pushClosed = true;
|
|
40
|
+
|
|
41
|
+
const Events = require('./lib/events.js');
|
|
42
|
+
this.events = new Events(this, this._subSocket);
|
|
43
|
+
|
|
44
|
+
const Command = require('./lib/command.js');
|
|
45
|
+
this.command = new Command(this, this._pushSocket);
|
|
46
|
+
|
|
47
|
+
let autoConnectToken = '';
|
|
48
|
+
|
|
49
|
+
const subs = subscriptions || [
|
|
50
|
+
'greathall::*' /* broadcasted by server */,
|
|
51
|
+
'gameover' /* broadcasted by bus */,
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
subs.forEach((sub) => this._subSocket.subscribe(sub));
|
|
55
|
+
|
|
56
|
+
this._onCloseSubscribers = {};
|
|
57
|
+
this._onConnectSubscribers = {};
|
|
58
|
+
|
|
59
|
+
const onClosed = (err) => {
|
|
60
|
+
if (!this._subClosed || !this._pushClosed) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.emit('close');
|
|
65
|
+
|
|
66
|
+
if (!err) {
|
|
67
|
+
xLog.verb(`bus stopped for ${this._orcName || 'greathall'}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
Object.keys(this._onCloseSubscribers).forEach((key) =>
|
|
71
|
+
this._onCloseSubscribers[key].callback(err)
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const onConnected = (err) => {
|
|
76
|
+
if (this._subClosed || this._pushClosed) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!err) {
|
|
81
|
+
xLog.verb('Connected');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
Object.keys(this._onConnectSubscribers).forEach((key) =>
|
|
85
|
+
this._onConnectSubscribers[key].callback(err)
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const onReconnectAttempt = (from) => {
|
|
90
|
+
if (!this._connected) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
xLog.warn(`Attempt a reconnect for ${from}`);
|
|
95
|
+
|
|
96
|
+
this._connected = false;
|
|
97
|
+
|
|
98
|
+
if (from === 'push') {
|
|
99
|
+
this._subSocket.destroySockets();
|
|
100
|
+
} else if (from === 'sub') {
|
|
101
|
+
this._pushSocket.destroySockets();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.emit('reconnect attempt');
|
|
105
|
+
|
|
106
|
+
this._registerAutoconnect(() => {
|
|
107
|
+
this.emit('reconnect');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
this._autoconnect = true;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
this._subSocket
|
|
114
|
+
.on('close', (err) => {
|
|
115
|
+
this._subClosed = true;
|
|
116
|
+
onClosed(err);
|
|
117
|
+
})
|
|
118
|
+
.on('connect', () => {
|
|
119
|
+
xLog.verb('Bus client subscribed to notifications bus');
|
|
120
|
+
this._subClosed = false;
|
|
121
|
+
onConnected();
|
|
122
|
+
})
|
|
123
|
+
.on('error', (err) => {
|
|
124
|
+
this._subClosed = true;
|
|
125
|
+
onClosed(err);
|
|
126
|
+
onConnected(err);
|
|
127
|
+
})
|
|
128
|
+
.on('reconnect attempt', () => {
|
|
129
|
+
this._subClosed = true;
|
|
130
|
+
onReconnectAttempt('sub');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
this._pushSocket
|
|
134
|
+
.on('close', (err) => {
|
|
135
|
+
this._pushClosed = true;
|
|
136
|
+
onClosed(err);
|
|
137
|
+
})
|
|
138
|
+
.on('connect', () => {
|
|
139
|
+
xLog.verb('Bus client ready to send on command bus');
|
|
140
|
+
this._pushClosed = false;
|
|
141
|
+
onConnected();
|
|
142
|
+
})
|
|
143
|
+
.on('error', (err) => {
|
|
144
|
+
this._pushClosed = true;
|
|
145
|
+
onClosed(err);
|
|
146
|
+
onConnected(err);
|
|
147
|
+
})
|
|
148
|
+
.on('reconnect attempt', () => {
|
|
149
|
+
this._pushClosed = true;
|
|
150
|
+
onReconnectAttempt('push');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
this._subSocket.on('message', (topic, msg) => {
|
|
154
|
+
if (topic === 'gameover') {
|
|
155
|
+
xLog.info('Game Over');
|
|
156
|
+
this._connected = false;
|
|
157
|
+
this.stop();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (topic.startsWith('greathall::')) {
|
|
162
|
+
if (topic === 'greathall::heartbeat') {
|
|
163
|
+
if (this._autoconnect) {
|
|
164
|
+
this._autoconnect = false;
|
|
165
|
+
autoConnectToken = xUtils.crypto.genToken();
|
|
166
|
+
this._subSocket.subscribe(
|
|
167
|
+
autoConnectToken + '::autoconnect.finished'
|
|
168
|
+
);
|
|
169
|
+
this.command.send(
|
|
170
|
+
'autoconnect',
|
|
171
|
+
{
|
|
172
|
+
autoConnectToken,
|
|
173
|
+
nice: this._busConfig ? this._busConfig.nice : 0,
|
|
174
|
+
noForwarding: this._busConfig
|
|
175
|
+
? this._busConfig.noForwarding || false
|
|
176
|
+
: false,
|
|
177
|
+
},
|
|
178
|
+
'greathall'
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (topic === 'greathall::bus.commands.registry') {
|
|
185
|
+
this._commandsRegistry = msg.data.registry;
|
|
186
|
+
this._commandsRegistryTime = new Date().toISOString();
|
|
187
|
+
this.emit('commands.registry', null, {
|
|
188
|
+
token: msg.data.token,
|
|
189
|
+
time: this._commandsRegistryTime,
|
|
190
|
+
});
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (topic === 'greathall::bus.token.changed') {
|
|
195
|
+
this.emit('token.changed');
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (
|
|
200
|
+
topic === 'greathall::bus.orcname.changed' &&
|
|
201
|
+
this._token === msg.data.token
|
|
202
|
+
) {
|
|
203
|
+
this.emit(
|
|
204
|
+
'orcname.changed',
|
|
205
|
+
msg.data.oldOrcName,
|
|
206
|
+
msg.data.newOrcName
|
|
207
|
+
);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (topic === 'greathall::bus.reconnect') {
|
|
212
|
+
switch (msg.data.status) {
|
|
213
|
+
case 'attempt':
|
|
214
|
+
this.emit('reconnect attempt');
|
|
215
|
+
break;
|
|
216
|
+
case 'done':
|
|
217
|
+
this.emit('reconnect');
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (
|
|
225
|
+
!this._connected &&
|
|
226
|
+
topic === autoConnectToken + '::autoconnect.finished'
|
|
227
|
+
) {
|
|
228
|
+
const escapeTopic = xUtils.regex.toXcraftRegExpStr(
|
|
229
|
+
'autoconnect.finished'
|
|
230
|
+
);
|
|
231
|
+
this._connected = true;
|
|
232
|
+
this._subSocket.unsubscribe(
|
|
233
|
+
autoConnectToken + '::autoconnect.finished'
|
|
234
|
+
);
|
|
235
|
+
this._eventsRegistry[escapeTopic].handler(msg); // FIXME: replace by a getter
|
|
236
|
+
this.unregisterEvents('autoconnect.finished');
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!this._connected) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const orcName = this.getOrcName() || 'greathall';
|
|
245
|
+
|
|
246
|
+
if (msg.token !== this._token) {
|
|
247
|
+
xLog.info('invalid token, event discarded');
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const handlers = this._eventsCache.map(
|
|
252
|
+
topic,
|
|
253
|
+
(id, key) => this._eventsRegistry[key].handler
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
if (handlers.length) {
|
|
257
|
+
xLog.verb(`notification received: ${topic} for ${orcName}`);
|
|
258
|
+
handlers.forEach((handler) => handler(msg));
|
|
259
|
+
} else if (topic !== 'greathall::heartbeat') {
|
|
260
|
+
xLog.info(
|
|
261
|
+
`event sent on ${topic} discarded (no subscriber, current orc: ${orcName})`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
destroyPushSocket() {
|
|
268
|
+
this._pushSocket.destroySockets();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
_registerAutoconnect(callback, err) {
|
|
272
|
+
this.registerEvents('autoconnect.finished', (msg) => {
|
|
273
|
+
const isNewOrcName = this._orcName !== msg.data.orcName;
|
|
274
|
+
|
|
275
|
+
if (this._token !== 'invalid') {
|
|
276
|
+
if (this._token !== msg.data.token) {
|
|
277
|
+
xLog.warn(
|
|
278
|
+
`reconnecting to the server has provided a new token: ${this._token} -> ${msg.data.token}`
|
|
279
|
+
);
|
|
280
|
+
this.emit('token.changed');
|
|
281
|
+
} else if (this._orcName && isNewOrcName) {
|
|
282
|
+
xLog.warn(
|
|
283
|
+
`reconnecting to the server has provided a new orcName: ${this._orcName} -> ${msg.data.orcName}`
|
|
284
|
+
);
|
|
285
|
+
this.emit('orcname.changed', this._orcName, msg.data.orcName);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
this._token = msg.data.token;
|
|
290
|
+
if (!this._orcName) {
|
|
291
|
+
this._orcName = msg.data.orcName;
|
|
292
|
+
}
|
|
293
|
+
this._commandsRegistry = msg.data.cmdRegistry;
|
|
294
|
+
this._commandsRegistryTime = new Date().toISOString();
|
|
295
|
+
this.emit('commands.registry', null, {
|
|
296
|
+
token: msg.data.token,
|
|
297
|
+
time: this._commandsRegistryTime,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
xLog.info(this._orcName + ' is serving ' + this._token + ' Great Hall');
|
|
301
|
+
|
|
302
|
+
if (this._orcName && isNewOrcName) {
|
|
303
|
+
this._subSocket.subscribe(this._orcName + '::*');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (callback) {
|
|
307
|
+
callback(err, msg.data.isLoaded);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
_subscribeClose(callback) {
|
|
313
|
+
const key = uuidV4();
|
|
314
|
+
this._onCloseSubscribers[key] = {
|
|
315
|
+
callback,
|
|
316
|
+
unsubscribe: () => {
|
|
317
|
+
delete this._onCloseSubscribers[key];
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
return this._onCloseSubscribers[key].unsubscribe;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
_subscribeConnect(callback) {
|
|
324
|
+
const key = uuidV4();
|
|
325
|
+
this._onConnectSubscribers[key] = {
|
|
326
|
+
callback,
|
|
327
|
+
unsubscribe: () => {
|
|
328
|
+
delete this._onConnectSubscribers[key];
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
return this._onConnectSubscribers[key].unsubscribe;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Connect the client to the buses.
|
|
336
|
+
*
|
|
337
|
+
* When the busToken is null, the client tries to autoconnect to the server.
|
|
338
|
+
* It's a trivial mechanism, there is no support for user authentication.
|
|
339
|
+
*
|
|
340
|
+
* The busToken must be passed only when BusClient is used on the server
|
|
341
|
+
* side. In all other cases, the argument _must_ be null.
|
|
342
|
+
*
|
|
343
|
+
* @param {string} backend - Transport's backend (ee or axon).
|
|
344
|
+
* @param {string} busToken - Server's token, or null for autoconnect.
|
|
345
|
+
* @param {function(err)} callback - Callback.
|
|
346
|
+
*/
|
|
347
|
+
connect(backend, busToken, callback) {
|
|
348
|
+
const fs = require('fs');
|
|
349
|
+
const path = require('path');
|
|
350
|
+
const xEtc = require('xcraft-core-etc')();
|
|
351
|
+
const xHost = require('xcraft-core-host');
|
|
352
|
+
|
|
353
|
+
const {resourcesPath} = xHost;
|
|
354
|
+
const appArgs = xHost.appArgs();
|
|
355
|
+
|
|
356
|
+
xLog.verb('Connecting...');
|
|
357
|
+
|
|
358
|
+
const unsubscribe = this._subscribeConnect((err) => {
|
|
359
|
+
unsubscribe();
|
|
360
|
+
|
|
361
|
+
if (err) {
|
|
362
|
+
callback(err);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/* TODO: Explain auto-connect mecha */
|
|
367
|
+
if (!busToken) {
|
|
368
|
+
/* Autoconnect is sent when the server is ready (heartbeat). */
|
|
369
|
+
this._registerAutoconnect(callback, err);
|
|
370
|
+
this._autoconnect = true;
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
this._connected = true;
|
|
375
|
+
this._token = busToken;
|
|
376
|
+
xLog.verb('Connected with token: ' + this._token);
|
|
377
|
+
|
|
378
|
+
callback();
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
let busConfig = this._busConfig;
|
|
382
|
+
if (!busConfig) {
|
|
383
|
+
busConfig = xEtc.load('xcraft-core-bus');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/* The TLS certificate is ignored in case of unix socket use */
|
|
387
|
+
if (
|
|
388
|
+
appArgs.tls !== false &&
|
|
389
|
+
!busConfig.noTLS &&
|
|
390
|
+
!busConfig.unixSocketId &&
|
|
391
|
+
!busConfig.caPath
|
|
392
|
+
) {
|
|
393
|
+
const resCaPath = path.join(resourcesPath, 'server-cert.pem');
|
|
394
|
+
if (fs.existsSync(resCaPath)) {
|
|
395
|
+
busConfig.caPath = resCaPath;
|
|
396
|
+
xEtc.saveRun('xcraft-core-busclient', busConfig);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const options = {
|
|
401
|
+
timeout: busConfig.timeout,
|
|
402
|
+
noForwarding: busConfig.noForwarding,
|
|
403
|
+
};
|
|
404
|
+
if (busConfig.caPath) {
|
|
405
|
+
options.caPath = busConfig.caPath;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
this._subSocket.connect(backend, {
|
|
409
|
+
port: parseInt(busConfig.notifierPort),
|
|
410
|
+
host: busConfig.host,
|
|
411
|
+
unixSocketId: busConfig.unixSocketId,
|
|
412
|
+
...options,
|
|
413
|
+
});
|
|
414
|
+
this._pushSocket.connect(backend, {
|
|
415
|
+
port: parseInt(busConfig.commanderPort),
|
|
416
|
+
host: busConfig.host,
|
|
417
|
+
unixSocketId: busConfig.unixSocketId,
|
|
418
|
+
...options,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Close the connections on the buses.
|
|
424
|
+
*
|
|
425
|
+
* @param {function(err)} callback - Callback.
|
|
426
|
+
*/
|
|
427
|
+
stop(callback) {
|
|
428
|
+
xLog.verb(`Stopping for ${this._orcName || 'greathall'}...`);
|
|
429
|
+
|
|
430
|
+
const unsubscribe = this._subscribeClose((err) => {
|
|
431
|
+
unsubscribe();
|
|
432
|
+
if (callback) {
|
|
433
|
+
callback(err);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
this._connected = false;
|
|
438
|
+
this._subSocket.stop();
|
|
439
|
+
this._pushSocket.stop();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Return a new empty message for the commands.
|
|
444
|
+
*
|
|
445
|
+
* The isNested attribute is set when the command is called from the server
|
|
446
|
+
* side but with an orc name.
|
|
447
|
+
*
|
|
448
|
+
* @param {string} topic - Event's topic or command's name.
|
|
449
|
+
* @param {string} which - The sender's identity (orcName).
|
|
450
|
+
* @return {Object} the new message.
|
|
451
|
+
*/
|
|
452
|
+
newMessage(topic, which) {
|
|
453
|
+
const id = uuidV4();
|
|
454
|
+
const isNested =
|
|
455
|
+
topic &&
|
|
456
|
+
!topic.includes('::') && // is a command
|
|
457
|
+
!!(this.isServerSide() && which && which !== 'greathall');
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
_xcraftMessage: true,
|
|
461
|
+
token: this.getToken(),
|
|
462
|
+
orcName: which,
|
|
463
|
+
id,
|
|
464
|
+
topic,
|
|
465
|
+
data: {},
|
|
466
|
+
isNested,
|
|
467
|
+
isError: topic && topic.endsWith('.error'),
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Patch a message for re-sending to an other server.
|
|
473
|
+
*
|
|
474
|
+
* It's especially useful in the case of the Horde when a command must be
|
|
475
|
+
* forwarded to an other server.
|
|
476
|
+
*
|
|
477
|
+
* @param {Object} msg - Xcraft message.
|
|
478
|
+
*/
|
|
479
|
+
patchMessage(msg) {
|
|
480
|
+
msg.token = this.getToken();
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
_unregister(id, escapeTopic) {
|
|
484
|
+
this._eventsCache.del(id, escapeTopic);
|
|
485
|
+
delete this._eventsRegistry[escapeTopic];
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
registerEvents(topic, handler) {
|
|
489
|
+
const escapeTopic = xUtils.regex.toXcraftRegExpStr(topic);
|
|
490
|
+
|
|
491
|
+
if (this._eventsRegistry[escapeTopic]) {
|
|
492
|
+
xLog.info(`${topic} already registered, unsub and sub again`);
|
|
493
|
+
this.unregisterEvents(topic);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const re = new RegExp(escapeTopic);
|
|
497
|
+
const ids = extractIds(topic);
|
|
498
|
+
const id = ids[ids.length - 1];
|
|
499
|
+
this._eventsCache.set(id, escapeTopic, re);
|
|
500
|
+
this._eventsRegistry[escapeTopic] = {
|
|
501
|
+
topic: re,
|
|
502
|
+
handler,
|
|
503
|
+
unregister: () => this._unregister(id, escapeTopic),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
unregisterEvents(topic) {
|
|
508
|
+
const escapeTopic = xUtils.regex.toXcraftRegExpStr(topic);
|
|
509
|
+
if (this._eventsRegistry[escapeTopic]) {
|
|
510
|
+
this._eventsRegistry[escapeTopic].unregister();
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
isServerSide() {
|
|
515
|
+
return !this._orcName;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
getToken() {
|
|
519
|
+
return this._token;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
getOrcName() {
|
|
523
|
+
return this._orcName;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
getCommandsRegistry() {
|
|
527
|
+
return this._commandsRegistry;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
getCommandsRegistryTime() {
|
|
531
|
+
return this._commandsRegistryTime;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
isConnected() {
|
|
535
|
+
return this._connected;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
newResponse() {
|
|
539
|
+
return exports.newResponse.apply(this, arguments);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
getNice() {
|
|
543
|
+
const nice = this._busConfig && this._busConfig.nice;
|
|
544
|
+
return nice || 0;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
exports.newResponse = function (moduleName, orcName, routing) {
|
|
549
|
+
let self = null;
|
|
550
|
+
if (this instanceof BusClient) {
|
|
551
|
+
self = this;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return new Resp(self, moduleName, orcName, routing);
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
exports.initGlobal = function () {
|
|
558
|
+
globalBusClient = new BusClient();
|
|
559
|
+
return globalBusClient;
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
exports.getGlobal = function () {
|
|
563
|
+
return globalBusClient;
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
exports.BusClient = BusClient;
|
package/lib/command.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var moduleName = 'busclient/command';
|
|
4
|
+
|
|
5
|
+
const xBus = require('xcraft-core-bus');
|
|
6
|
+
var xLog = require('xcraft-core-log')(moduleName, null);
|
|
7
|
+
|
|
8
|
+
function Command(busClient, pushSocket) {
|
|
9
|
+
this._busClient = busClient;
|
|
10
|
+
this._push = pushSocket;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
Command.prototype.connectedWith = function () {
|
|
14
|
+
return this._push.connectedWith();
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
Command.prototype.newMessage = function (cmd, which) {
|
|
18
|
+
if (!which) {
|
|
19
|
+
which = this._busClient.getOrcName() || 'greathall';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return this._busClient.newMessage(cmd, which);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
Command.prototype.retry = function (msg) {
|
|
26
|
+
return this.send(msg.topic, msg);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Send a command on the bus.
|
|
31
|
+
*
|
|
32
|
+
* If a callback is specified, the finished topic is automatically
|
|
33
|
+
* subscribed to the events bus. Then when the callback is called, the
|
|
34
|
+
* topic is unsubscribed.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} cmd - Command's name.
|
|
37
|
+
* @param {Object} data - Message or map of arguments passed to the command.
|
|
38
|
+
* @param {string} which - Orc name or greathall.
|
|
39
|
+
* @param {function(err, results)} [finishHandler] - Callback.
|
|
40
|
+
* @param {Object} options - Options like forceNested.
|
|
41
|
+
*/
|
|
42
|
+
Command.prototype.send = function (
|
|
43
|
+
cmd,
|
|
44
|
+
data,
|
|
45
|
+
which,
|
|
46
|
+
finishHandler,
|
|
47
|
+
options = {}
|
|
48
|
+
) {
|
|
49
|
+
if (!which) {
|
|
50
|
+
which = this._busClient.getOrcName() || 'greathall';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let busMessage = data;
|
|
54
|
+
if (!data || !data._xcraftMessage) {
|
|
55
|
+
busMessage = this.newMessage(cmd, which);
|
|
56
|
+
busMessage.data = data;
|
|
57
|
+
busMessage.originRouter = this._push.connectedWith();
|
|
58
|
+
} else if (data._xcraftMessage) {
|
|
59
|
+
this._busClient.patchMessage(busMessage);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!busMessage.arp) {
|
|
63
|
+
const {appId, appArgs} = require('xcraft-core-host');
|
|
64
|
+
const {tribe} = appArgs();
|
|
65
|
+
const routingKey = tribe ? `${appId}-${tribe}` : appId;
|
|
66
|
+
let token = xBus.getToken();
|
|
67
|
+
let nodeName = routingKey;
|
|
68
|
+
let {orcName} = busMessage;
|
|
69
|
+
let nice = 0;
|
|
70
|
+
let noForwarding = false;
|
|
71
|
+
if (cmd === 'autoconnect') {
|
|
72
|
+
orcName = busMessage.data.autoConnectToken;
|
|
73
|
+
nice = busMessage.data.nice;
|
|
74
|
+
noForwarding = busMessage.data.noForwarding;
|
|
75
|
+
/* `token` can be empty, because it's provided by the autoconnect */
|
|
76
|
+
} else if (!token) {
|
|
77
|
+
token = orcName.split('@')[1];
|
|
78
|
+
nodeName = 'zog';
|
|
79
|
+
if (!token) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`this ARP entry without token is forbidden; it's seems that your client is using a forbidden orcName or you are not connected properly to the server`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
busMessage.arp = {
|
|
86
|
+
[orcName]: {token, nice, noForwarding, nodeName},
|
|
87
|
+
};
|
|
88
|
+
busMessage.router = this.connectedWith();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (options && options.forceNested) {
|
|
92
|
+
busMessage.isNested = true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (finishHandler) {
|
|
96
|
+
/* Subscribe to end and error command notification. */
|
|
97
|
+
const unsubscribe = this._busClient.events.subscribe(
|
|
98
|
+
`${which}::${cmd}.${busMessage.id}.(error|finished)`,
|
|
99
|
+
(msg) => {
|
|
100
|
+
unsubscribe();
|
|
101
|
+
if (msg.isError) {
|
|
102
|
+
// Generator exception handling,
|
|
103
|
+
// see https://gist.github.com/deepak/e325fa2d1a39984fd3e9
|
|
104
|
+
setTimeout(() => {
|
|
105
|
+
try {
|
|
106
|
+
let err = {};
|
|
107
|
+
if (msg.data && msg.data.message) {
|
|
108
|
+
err.message = msg.data.message;
|
|
109
|
+
if (msg.data.id) {
|
|
110
|
+
err.id = msg.data.id;
|
|
111
|
+
}
|
|
112
|
+
if (msg.data.code) {
|
|
113
|
+
err.code = msg.data.code;
|
|
114
|
+
}
|
|
115
|
+
if (msg.data.stack) {
|
|
116
|
+
err.stack = msg.data.stack;
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
err.message = msg.data;
|
|
120
|
+
}
|
|
121
|
+
if (!err.topic) {
|
|
122
|
+
err.topic = msg.topic;
|
|
123
|
+
}
|
|
124
|
+
if (!err.code) {
|
|
125
|
+
err.code = 'XCRAFT_CMD_ERROR';
|
|
126
|
+
}
|
|
127
|
+
finishHandler(err);
|
|
128
|
+
} catch (ex) {
|
|
129
|
+
console.error(
|
|
130
|
+
`Unexpected error: ${ex.stack || ex.message || ex}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}, 0);
|
|
134
|
+
} else {
|
|
135
|
+
finishHandler(null, msg);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
this._push.connectedWith()
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
xLog.verb('finish handler registered for cmd: ' + cmd);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
xLog.verb("client send '%s' command", cmd);
|
|
145
|
+
this._push.send(cmd, busMessage);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
module.exports = Command;
|
package/lib/events.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var moduleName = 'busclient/events';
|
|
4
|
+
|
|
5
|
+
var xLog = require('xcraft-core-log')(moduleName, null);
|
|
6
|
+
var xBus = require('xcraft-core-bus');
|
|
7
|
+
|
|
8
|
+
function Events(busClient, subSocket) {
|
|
9
|
+
this._busClient = busClient;
|
|
10
|
+
this._sub = subSocket;
|
|
11
|
+
this._prevTopic = '';
|
|
12
|
+
this._handlers = new Map();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
Events.prototype.lastPerf = function () {
|
|
16
|
+
return this._sub.lastPerf;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
Events.prototype.connectedWith = function () {
|
|
20
|
+
return this._sub.connectedWith();
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Catch all events.
|
|
25
|
+
*
|
|
26
|
+
* @param {function(topic, msg)} handler - Main messages handler
|
|
27
|
+
* @param {*} proxy - Enable proxy mode (disable Xcraft serialization)
|
|
28
|
+
*/
|
|
29
|
+
Events.prototype.catchAll = function (handler, proxy = false) {
|
|
30
|
+
this._sub.on('message', handler, proxy);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Subscribe to a topic, an event.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} topic - Event's name.
|
|
37
|
+
* @param {function(msg)} handler - Handler to attach to this topic.
|
|
38
|
+
* @param {string} [backend] - Transport backend to use (or null for all).
|
|
39
|
+
* @param {string} [orcName] - Subscriber orcName (internal use)
|
|
40
|
+
* @return {function} unsubscribe function.
|
|
41
|
+
*/
|
|
42
|
+
Events.prototype.subscribe = function (topic, handler, backend, orcName) {
|
|
43
|
+
if (!topic.includes('::')) {
|
|
44
|
+
xLog.err(new Error().stack);
|
|
45
|
+
throw new Error('namespace missing');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (topic.includes('undefined')) {
|
|
49
|
+
xLog.warn(`bad event subscription detected: ${topic}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
xLog.verb('client inserting handler to topic: ' + topic);
|
|
53
|
+
|
|
54
|
+
const id = Symbol();
|
|
55
|
+
|
|
56
|
+
const unsubscribe = (handleReturn) => {
|
|
57
|
+
if (!this._handlers.has(topic)) {
|
|
58
|
+
if (!handleReturn) {
|
|
59
|
+
xLog.warn(`no unsubscribe because ${topic} is no longer available`);
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const handlers = this._handlers.get(topic);
|
|
64
|
+
handlers.delete(id);
|
|
65
|
+
if (handlers.size === 0) {
|
|
66
|
+
this.unsubscribeAll(topic, backend, orcName);
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
if (this._handlers.has(topic)) {
|
|
72
|
+
this._handlers.get(topic).set(id, handler);
|
|
73
|
+
return unsubscribe;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this._handlers.set(topic, new Map([[id, handler]]));
|
|
77
|
+
this._sub.subscribe(topic, backend, orcName);
|
|
78
|
+
|
|
79
|
+
const handlers = this._handlers.get(topic);
|
|
80
|
+
|
|
81
|
+
/* register a pre-handler for deserialize object if needed */
|
|
82
|
+
this._busClient.registerEvents(topic, (msg) => {
|
|
83
|
+
/* FIXME: it's not safe. */
|
|
84
|
+
if (msg.serialized) {
|
|
85
|
+
msg.data = JSON.parse(msg.data, function (key, value) {
|
|
86
|
+
if (
|
|
87
|
+
value &&
|
|
88
|
+
typeof value === 'string' &&
|
|
89
|
+
value.substr(0, 8) === 'function'
|
|
90
|
+
) {
|
|
91
|
+
var startBody = value.indexOf('{') + 1;
|
|
92
|
+
var endBody = value.lastIndexOf('}');
|
|
93
|
+
var startArgs = value.indexOf('(') + 1;
|
|
94
|
+
var endArgs = value.indexOf(')');
|
|
95
|
+
|
|
96
|
+
return new Function(
|
|
97
|
+
value.substring(startArgs, endArgs) /* jshint ignore:line */,
|
|
98
|
+
value.substring(startBody, endBody)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return value;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* finally call user code (with or without deserialized data) */
|
|
107
|
+
handlers.forEach((handler) => handler(msg));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return unsubscribe;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Unsubscribe from a topic, event.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} topic - Event's name.
|
|
117
|
+
* @param {string} [backend] - Transport backend to use (or null for all).
|
|
118
|
+
* @param {string} [orcName] - Subscriber orcName (internal use)
|
|
119
|
+
*/
|
|
120
|
+
Events.prototype.unsubscribeAll = function (topic, backend, orcName) {
|
|
121
|
+
if (!topic.includes('::')) {
|
|
122
|
+
xLog.err(new Error().stack);
|
|
123
|
+
throw new Error('namespace missing');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
xLog.verb('client removing handler on topic: ' + topic);
|
|
127
|
+
|
|
128
|
+
this._sub.unsubscribe(topic, backend, orcName);
|
|
129
|
+
this._busClient.unregisterEvents(topic);
|
|
130
|
+
this._handlers.delete(topic);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
Events.prototype.heartbeat = function () {
|
|
134
|
+
if (!this._busClient.isServerSide()) {
|
|
135
|
+
const err = new Error('only the server can send heartbeats');
|
|
136
|
+
xLog.err(err.stack);
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const topic = 'greathall::heartbeat';
|
|
141
|
+
const notifier = xBus.getNotifier();
|
|
142
|
+
|
|
143
|
+
notifier.send(topic);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Send an event on the bus.
|
|
148
|
+
*
|
|
149
|
+
* The \p data can be stringified for example in the case of a simple
|
|
150
|
+
* function. Of course, the function must be standalone.
|
|
151
|
+
*
|
|
152
|
+
* @param {string} topic - Event's name.
|
|
153
|
+
* @param {Object} [data] - Payload.
|
|
154
|
+
* @param {boolean} [serialize] - Stringify the object.
|
|
155
|
+
* @param {string} [routing] - Router info (ee or axon).
|
|
156
|
+
*/
|
|
157
|
+
Events.prototype.send = function (topic, data, serialize, routing) {
|
|
158
|
+
if (!this._busClient.isServerSide()) {
|
|
159
|
+
const err = new Error('only the server can send events');
|
|
160
|
+
xLog.err(err.stack);
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!topic.includes('::')) {
|
|
165
|
+
const err = new Error('namespace missing');
|
|
166
|
+
xLog.err(err.stack);
|
|
167
|
+
throw err;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const which = topic.split('::', 1)[0];
|
|
171
|
+
|
|
172
|
+
var notifier = xBus.getNotifier();
|
|
173
|
+
|
|
174
|
+
let busMessage = data;
|
|
175
|
+
if (!data || !data._xcraftMessage) {
|
|
176
|
+
busMessage = this._busClient.newMessage(topic, which);
|
|
177
|
+
|
|
178
|
+
if (serialize) {
|
|
179
|
+
busMessage.data = JSON.stringify(data, (key, value) =>
|
|
180
|
+
typeof value === 'function' ? value.toString() : value
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
busMessage.serialized = true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
busMessage.data = data;
|
|
187
|
+
} else if (data._xcraftMessage) {
|
|
188
|
+
this._busClient.patchMessage(busMessage);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let isActivity = false;
|
|
192
|
+
|
|
193
|
+
if (routing) {
|
|
194
|
+
busMessage.router = routing.router;
|
|
195
|
+
busMessage.originRouter = routing.originRouter;
|
|
196
|
+
if (routing.forwarding) {
|
|
197
|
+
busMessage.forwarding = routing.forwarding;
|
|
198
|
+
}
|
|
199
|
+
isActivity = routing.activity;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* Reduce noise... */
|
|
203
|
+
if (topic !== this._prevTopic) {
|
|
204
|
+
xLog.verb('client send notification(s) on topic:' + topic);
|
|
205
|
+
this._prevTopic = topic;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
notifier.send(topic, busMessage);
|
|
209
|
+
|
|
210
|
+
if (isActivity) {
|
|
211
|
+
topic += '.activity';
|
|
212
|
+
busMessage = this._busClient.newMessage(topic, which);
|
|
213
|
+
notifier.send(topic, busMessage);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
Events.prototype.status = {
|
|
218
|
+
succeeded: 1,
|
|
219
|
+
failed: 2,
|
|
220
|
+
canceled: 3,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
module.exports = Events;
|
package/lib/resp.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const watt = require('gigawatts');
|
|
4
|
+
|
|
5
|
+
class Events {
|
|
6
|
+
constructor(busClient, log, orcName, routing) {
|
|
7
|
+
this._busClient = busClient;
|
|
8
|
+
this._orcName = orcName;
|
|
9
|
+
this._routing = routing;
|
|
10
|
+
|
|
11
|
+
this.status = (this._busClient && this._busClient.events.status) || {};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get routing() {
|
|
15
|
+
return this._routing;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_fixTopic(topic) {
|
|
19
|
+
return topic.includes('::') ? topic : `${this._orcName}::${topic}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
catchAll(handler) {
|
|
23
|
+
if (!this._busClient) {
|
|
24
|
+
this._log.err('events.catchAll not available');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
this._busClient.events.catchAll(handler);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
subscribe(topic, handler) {
|
|
31
|
+
if (!this._busClient) {
|
|
32
|
+
this._log.err('events.subscribe not available');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return this._busClient.events.subscribe(
|
|
37
|
+
this._fixTopic(topic),
|
|
38
|
+
handler,
|
|
39
|
+
undefined,
|
|
40
|
+
this._orcName
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
unsubscribeAll(topic) {
|
|
45
|
+
if (!this._busClient) {
|
|
46
|
+
this._log.err('events.unsubscribe not available');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this._busClient.events.unsubscribe(this._fixTopic(topic));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
send(topic, data, serialize, forwarding = null) {
|
|
54
|
+
if (!this._busClient) {
|
|
55
|
+
this._log.err('events.send not available');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let routing = this._routing;
|
|
60
|
+
if (forwarding) {
|
|
61
|
+
switch (typeof forwarding) {
|
|
62
|
+
case 'string': {
|
|
63
|
+
const appId = forwarding;
|
|
64
|
+
if (appId) {
|
|
65
|
+
/* FIXME: not sure if it's still used. If yes, we must provide the tribe number
|
|
66
|
+
* in order to be right.
|
|
67
|
+
*/
|
|
68
|
+
routing = Object.assign(
|
|
69
|
+
{forwarding: {router: 'ee', appId}},
|
|
70
|
+
this._routing
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case 'object': {
|
|
76
|
+
routing = forwarding;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this._busClient.events.send(
|
|
83
|
+
this._fixTopic(topic),
|
|
84
|
+
data,
|
|
85
|
+
serialize,
|
|
86
|
+
routing
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
class Command {
|
|
92
|
+
constructor(busClient, log, orcName) {
|
|
93
|
+
this._log = log;
|
|
94
|
+
this._busClient = busClient;
|
|
95
|
+
this._orcName = orcName;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
retry(msg) {
|
|
99
|
+
if (!this._busClient) {
|
|
100
|
+
this._log.err('command.retry not available');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this._busClient.command.retry(msg);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
_send(cmd, data, options, finishHandler) {
|
|
108
|
+
if (!this._busClient) {
|
|
109
|
+
this._log.err('command.send not available');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const orcName = (data && data.$orcName) || this._orcName;
|
|
114
|
+
this._busClient.command.send(cmd, data, orcName, finishHandler, options);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
send(cmd, data, finishHandler) {
|
|
118
|
+
this._send(cmd, data, null, finishHandler);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
nestedSend(cmd, data, finishHandler) {
|
|
122
|
+
this._send(cmd, data, {forceNested: true}, finishHandler);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
class Resp {
|
|
127
|
+
constructor(busClient, moduleName, orcName, routing = null) {
|
|
128
|
+
this._busClient = busClient;
|
|
129
|
+
this._cmdNames = {};
|
|
130
|
+
this._orcName = orcName;
|
|
131
|
+
|
|
132
|
+
/* Add a way for sending with the token server instead of an orcName */
|
|
133
|
+
if (orcName === 'token') {
|
|
134
|
+
const token = this._busClient.getToken();
|
|
135
|
+
if (token === 'invalid') {
|
|
136
|
+
throw new Error(
|
|
137
|
+
'A Resp cannot be created with a disconnected busClient (invalid token)'
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
orcName = 'greathall@' + token;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
this.log = require('xcraft-core-log')(moduleName, this);
|
|
144
|
+
this.events = new Events(busClient, this.log, orcName, routing);
|
|
145
|
+
this.command = new Command(busClient, this.log, orcName);
|
|
146
|
+
|
|
147
|
+
watt.wrapAll(this, 'connect', 'stop');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
_makeCmdNames() {
|
|
151
|
+
if (!this._busClient) {
|
|
152
|
+
return {};
|
|
153
|
+
}
|
|
154
|
+
this._cmdNames = Object.assign(
|
|
155
|
+
{},
|
|
156
|
+
...Object.entries(this._busClient.getCommandsRegistry()).map(
|
|
157
|
+
([key, infos]) => {
|
|
158
|
+
let value =
|
|
159
|
+
(infos.questOptions && infos.questOptions.rankingPredictions) ||
|
|
160
|
+
true;
|
|
161
|
+
return {[key]: value};
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
get orcName() {
|
|
168
|
+
return this._orcName;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
*connect(next) {
|
|
172
|
+
yield this._busClient.connect('axon', null, next);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
*stop(next) {
|
|
176
|
+
yield this._busClient.stop(next);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
isConnected() {
|
|
180
|
+
return this._busClient ? this._busClient.isConnected() : false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
onTokenChanged(callback) {
|
|
184
|
+
this._busClient.on('token.changed', callback);
|
|
185
|
+
return () => this._busClient.removeListener('token.changed', callback);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
onOrcnameChanged(callback) {
|
|
189
|
+
this._busClient.on('orcname.changed', callback);
|
|
190
|
+
return () => this._busClient.removeListener('orcname.changed', callback);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @brief Listener for reconnection
|
|
195
|
+
*
|
|
196
|
+
* Pass 'attempt' when it tries to reconnect and 'done'
|
|
197
|
+
* when it's connected again.
|
|
198
|
+
*
|
|
199
|
+
* @param {*} callback - Callback
|
|
200
|
+
* @returns {*} unsub
|
|
201
|
+
*/
|
|
202
|
+
onReconnect(callback) {
|
|
203
|
+
this._busClient
|
|
204
|
+
.on('reconnect', () => callback('done'))
|
|
205
|
+
.on('reconnect attempt', () => callback('attempt'));
|
|
206
|
+
return () => {
|
|
207
|
+
this._busClient.removeListener('reconnect', callback);
|
|
208
|
+
this._busClient.removeListener('reconnect attempt', callback);
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
onCommandsRegistry(callback) {
|
|
213
|
+
const _callback = () => {
|
|
214
|
+
this._makeCmdNames();
|
|
215
|
+
callback();
|
|
216
|
+
};
|
|
217
|
+
this._busClient.on('commands.registry', _callback);
|
|
218
|
+
return () => this._busClient.removeListener('commands.registry', _callback);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
getCommandsRegistry() {
|
|
222
|
+
return this._busClient ? this._busClient.getCommandsRegistry() : {};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
getCommandsRegistryTime() {
|
|
226
|
+
return this._busClient ? this._busClient.getCommandsRegistryTime() : 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
getCommandsNames() {
|
|
230
|
+
this._makeCmdNames();
|
|
231
|
+
return this._cmdNames;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
hasCommand(cmdName) {
|
|
235
|
+
return this._busClient
|
|
236
|
+
? !!this._busClient.getCommandsRegistry()[cmdName]
|
|
237
|
+
: false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
module.exports = Resp;
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "xcraft-core-busclient",
|
|
3
|
+
"version": "5.0.0",
|
|
4
|
+
"description": "Xcraft bus client",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=14"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"xcraft",
|
|
14
|
+
"bus",
|
|
15
|
+
"client"
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/Xcraft-Inc/xcraft-core-busclient.git"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"gigawatts": "^4.0.1",
|
|
23
|
+
"uuid": "^8.3.2",
|
|
24
|
+
"xcraft-axon": "^2.3.2",
|
|
25
|
+
"xcraft-core-bus": "^5.0.0",
|
|
26
|
+
"xcraft-core-host": "^3.0.0",
|
|
27
|
+
"xcraft-core-etc": "^3.0.0",
|
|
28
|
+
"xcraft-core-log": "^2.0.0",
|
|
29
|
+
"xcraft-core-transport": "^4.0.0",
|
|
30
|
+
"xcraft-core-utils": "^4.0.0"
|
|
31
|
+
},
|
|
32
|
+
"author": "Mathieu Schroeter & Samuel Loup",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/Xcraft-Inc/xcraft-core-busclient/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/Xcraft-Inc/xcraft-core-busclient#readme",
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"prettier": "2.0.4",
|
|
40
|
+
"xcraft-dev-prettier": "^2.0.0",
|
|
41
|
+
"xcraft-dev-rules": "^2.0.0"
|
|
42
|
+
},
|
|
43
|
+
"prettier": "xcraft-dev-prettier"
|
|
44
|
+
}
|