vrpc 3.0.0-alpha.3 → 3.0.3
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/CHANGELOG.md +53 -0
- package/LICENSE +1 -1
- package/README.md +8 -9
- package/browser/vrpc.js +1 -1
- package/package.json +8 -5
- package/vrpc/VrpcAdapter.js +209 -171
- package/vrpc/VrpcAgent.js +32 -27
- package/vrpc/VrpcClient.js +273 -119
- package/vrpc/VrpcNative.js +25 -2
- package/vrpc/adapter.hpp +4 -2
- package/vrpc/addon.cpp +1 -1
- package/vrpc/vrpc-agent-js +1 -1
package/vrpc/VrpcClient.js
CHANGED
|
@@ -16,7 +16,7 @@ Author: Dr. Burkhard C. Heisen (https://github.com/heisenware/vrpc)
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
|
19
|
-
Copyright (c) 2018 -
|
|
19
|
+
Copyright (c) 2018 - 2022 Dr. Burkhard C. Heisen <burkhard.heisen@heisenware.com>.
|
|
20
20
|
|
|
21
21
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
22
22
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -59,9 +59,9 @@ class VrpcClient extends EventEmitter {
|
|
|
59
59
|
*
|
|
60
60
|
* @constructor
|
|
61
61
|
* @param {Object} options
|
|
62
|
-
* @param {String} options.
|
|
63
|
-
* @param {String} options.
|
|
64
|
-
* @param {String} options.
|
|
62
|
+
* @param {String} [options.username='<user>@<hostname>-<platform>-js'] MQTT username
|
|
63
|
+
* @param {String} [options.password] MQTT password (if no token is provided)
|
|
64
|
+
* @param {String} [options.token] Access token
|
|
65
65
|
* @param {String} options.domain Sets the domain
|
|
66
66
|
* @param {String} [options.agent="*"] Sets default agent
|
|
67
67
|
* @param {String} [options.broker="mqtts://vrpc.io:8883"] Broker url in form: `<scheme>://<host>:<port>`
|
|
@@ -70,14 +70,14 @@ class VrpcClient extends EventEmitter {
|
|
|
70
70
|
* @param {Boolean} [options.bestEffort=false] If true, message will be sent with best effort, i.e. no caching if offline
|
|
71
71
|
* @example
|
|
72
72
|
* const client = new VrpcClient({
|
|
73
|
-
* domain: '
|
|
73
|
+
* domain: 'vrpc',
|
|
74
74
|
* broker: 'mqtt://vrpc.io'
|
|
75
75
|
* })
|
|
76
76
|
*/
|
|
77
77
|
constructor ({
|
|
78
78
|
token,
|
|
79
|
-
username,
|
|
80
79
|
password,
|
|
80
|
+
username = VrpcClient._generateUserName(),
|
|
81
81
|
domain,
|
|
82
82
|
agent = '*',
|
|
83
83
|
broker = 'mqtts://vrpc.io:8883',
|
|
@@ -116,7 +116,7 @@ class VrpcClient extends EventEmitter {
|
|
|
116
116
|
this._invokeId = 0
|
|
117
117
|
this._proxyId = 0
|
|
118
118
|
this._client = null
|
|
119
|
-
this._cachedSubscriptions =
|
|
119
|
+
this._cachedSubscriptions = {}
|
|
120
120
|
this._proxies = {}
|
|
121
121
|
if (log === 'console') {
|
|
122
122
|
this._log = console
|
|
@@ -152,7 +152,7 @@ class VrpcClient extends EventEmitter {
|
|
|
152
152
|
let username = this._username
|
|
153
153
|
let password = this._password
|
|
154
154
|
if (this._token) {
|
|
155
|
-
username =
|
|
155
|
+
username = username || VrpcClient._generateUserName()
|
|
156
156
|
password = this._token
|
|
157
157
|
}
|
|
158
158
|
const options = {
|
|
@@ -162,6 +162,7 @@ class VrpcClient extends EventEmitter {
|
|
|
162
162
|
keepalive: 30,
|
|
163
163
|
clientId: this._mqttClientId,
|
|
164
164
|
rejectUnauthorized: false,
|
|
165
|
+
connectTimeout: this._timeout,
|
|
165
166
|
will: {
|
|
166
167
|
topic: `${this._vrpcClientId}/__clientInfo__`,
|
|
167
168
|
payload: JSON.stringify({ status: 'offline' })
|
|
@@ -229,10 +230,12 @@ class VrpcClient extends EventEmitter {
|
|
|
229
230
|
staticFunctions,
|
|
230
231
|
meta
|
|
231
232
|
} = json
|
|
232
|
-
if (removed.length !== 0)
|
|
233
|
+
if (removed.length !== 0) {
|
|
233
234
|
this.emit('instanceGone', removed, { domain, agent, className })
|
|
234
|
-
|
|
235
|
+
}
|
|
236
|
+
if (added.length !== 0) {
|
|
235
237
|
this.emit('instanceNew', added, { domain, agent, className })
|
|
238
|
+
}
|
|
236
239
|
this.emit('class', {
|
|
237
240
|
domain,
|
|
238
241
|
agent,
|
|
@@ -248,31 +251,29 @@ class VrpcClient extends EventEmitter {
|
|
|
248
251
|
this._eventEmitter.emit(i, { a, e, r })
|
|
249
252
|
}
|
|
250
253
|
})
|
|
251
|
-
|
|
252
|
-
|
|
254
|
+
return new Promise((resolve, reject) => {
|
|
255
|
+
this._client.once('offline', () => {
|
|
256
|
+
this._client.end()
|
|
253
257
|
reject(new Error(`Connection trial timed out (> ${this._timeout} ms)`))
|
|
254
|
-
}, this._timeout)
|
|
255
|
-
this.once('connect', () => {
|
|
256
|
-
clearTimeout(timer)
|
|
257
|
-
resolve()
|
|
258
258
|
})
|
|
259
|
+
this._client.once('connect', resolve)
|
|
259
260
|
})
|
|
260
261
|
}
|
|
261
262
|
|
|
262
263
|
/**
|
|
263
264
|
* Creates a new remote instance and provides a proxy to it.
|
|
264
265
|
*
|
|
265
|
-
* Remote instances can be "
|
|
266
|
-
*
|
|
267
|
-
* explicitly deleted. Life-cycle changes of
|
|
268
|
-
* under the `class`, `instanceNew`, and `instanceGone` events. A
|
|
269
|
-
* instance is created
|
|
266
|
+
* Remote instances can be "shared" or "isolated". Shared instances are
|
|
267
|
+
* visible and re-attachable across clients as long as they are not
|
|
268
|
+
* explicitly deleted. Life-cycle changes of shared instances are available
|
|
269
|
+
* under the `class`, `instanceNew`, and `instanceGone` events. A shared
|
|
270
|
+
* instance is created by default (`isIsolated: false`).
|
|
270
271
|
*
|
|
271
|
-
* When the `
|
|
272
|
-
*
|
|
273
|
-
*
|
|
272
|
+
* When the `isIsolated` option is true, the remote instance stays invisible
|
|
273
|
+
* to other clients and the corresponding proxy object is the only way to
|
|
274
|
+
* issue commands.
|
|
274
275
|
*
|
|
275
|
-
* **NOTE** When creating
|
|
276
|
+
* **NOTE** When creating an instance that already exists, the new proxy will
|
|
276
277
|
* simply attach to (and not re-create) it - just like `getInstance()` was
|
|
277
278
|
* called.
|
|
278
279
|
*
|
|
@@ -280,28 +281,32 @@ class VrpcClient extends EventEmitter {
|
|
|
280
281
|
* @param {String} options.className Name of the class which should be
|
|
281
282
|
* instantiated
|
|
282
283
|
* @param {String} [options.instance] Name of the created instance. If not
|
|
283
|
-
* provided an
|
|
284
|
+
* provided an id will be generated
|
|
284
285
|
* @param {Array} [options.args] Positional arguments for the constructor call
|
|
285
286
|
* @param {String} [options.agent] Agent name. If not provided class default
|
|
286
287
|
* is used
|
|
287
|
-
* @param {bool} [options.cacheProxy=false] If true the proxy object for a
|
|
288
|
-
* instance is cached and used in subsequent calls
|
|
289
|
-
* @
|
|
290
|
-
*
|
|
288
|
+
* @param {bool} [options.cacheProxy=false] If true the proxy object for a
|
|
289
|
+
* given instance is cached and (re-)used in subsequent calls
|
|
290
|
+
* @param {bool} [options.isIsolated=false] If true the created proxy will be
|
|
291
|
+
* visible only to the client who created it
|
|
292
|
+
* @returns {Promise<Proxy>} Object reflecting a proxy to the original object
|
|
293
|
+
* which is handled by the agent
|
|
291
294
|
* @example
|
|
292
|
-
* // create
|
|
295
|
+
* // create isolated instance
|
|
293
296
|
* const proxy1 = await client.create({
|
|
294
|
-
* className: 'Foo'
|
|
297
|
+
* className: 'Foo',
|
|
298
|
+
* instance: 'myPersonalInstance',
|
|
299
|
+
* isIsolated: true
|
|
295
300
|
* })
|
|
296
|
-
* // create
|
|
301
|
+
* // create shared instance
|
|
297
302
|
* const proxy2 = await client.create({
|
|
298
303
|
* className: 'Foo',
|
|
299
|
-
* instance: '
|
|
304
|
+
* instance: 'aSharedFooInstance'
|
|
300
305
|
* })
|
|
301
|
-
* // create
|
|
306
|
+
* // create shared instance providing three constructor arguments
|
|
302
307
|
* const proxy3 = await client.create({
|
|
303
308
|
* className: 'Bar',
|
|
304
|
-
* instance: '
|
|
309
|
+
* instance: 'mySharedBarInstance',
|
|
305
310
|
* args: [42, "second argument", { some: 'option' }]
|
|
306
311
|
* })
|
|
307
312
|
*/
|
|
@@ -317,9 +322,9 @@ class VrpcClient extends EventEmitter {
|
|
|
317
322
|
const json = {
|
|
318
323
|
c: className,
|
|
319
324
|
f: isIsolated ? '__createIsolated__' : '__createShared__',
|
|
325
|
+
a: [instance, ...args],
|
|
320
326
|
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
321
327
|
s: `${this._domain}/${os.hostname()}/${this._instance}`,
|
|
322
|
-
a: [instance, ...args],
|
|
323
328
|
v: VRPC_PROTOCOL_VERSION
|
|
324
329
|
}
|
|
325
330
|
const proxy = await this._getProxy(agent, className, json)
|
|
@@ -364,15 +369,15 @@ class VrpcClient extends EventEmitter {
|
|
|
364
369
|
const json = {
|
|
365
370
|
c: className,
|
|
366
371
|
f: '__delete__',
|
|
372
|
+
a: [instance],
|
|
367
373
|
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
368
374
|
s: `${this._domain}/${os.hostname()}/${this._instance}`,
|
|
369
|
-
a: [instance],
|
|
370
375
|
v: VRPC_PROTOCOL_VERSION
|
|
371
376
|
}
|
|
372
377
|
const topic = `${this._domain}/${agent}/${className}/__static__/__delete__`
|
|
373
378
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
374
379
|
if (this._proxies[instance]) delete this._proxies[instance]
|
|
375
|
-
return this._handleAgentAnswer(json)
|
|
380
|
+
return this._handleAgentAnswer(json, agent)
|
|
376
381
|
}
|
|
377
382
|
|
|
378
383
|
/**
|
|
@@ -391,20 +396,25 @@ class VrpcClient extends EventEmitter {
|
|
|
391
396
|
args = [],
|
|
392
397
|
agent = this._agent
|
|
393
398
|
} = {}) {
|
|
394
|
-
const wrapped = this._wrapArguments(
|
|
399
|
+
const wrapped = this._wrapArguments({
|
|
400
|
+
agent,
|
|
401
|
+
className,
|
|
402
|
+
functionName,
|
|
403
|
+
args
|
|
404
|
+
})
|
|
395
405
|
if (!wrapped) return // Skipping remote call -> handled locally
|
|
396
406
|
const json = {
|
|
397
407
|
c: className,
|
|
398
408
|
f: functionName,
|
|
409
|
+
a: wrapped,
|
|
399
410
|
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
400
411
|
s: this._vrpcClientId,
|
|
401
|
-
a: wrapped,
|
|
402
412
|
v: VRPC_PROTOCOL_VERSION
|
|
403
413
|
}
|
|
404
414
|
const topic = `${this._domain}/${agent}/${className}/__static__/${functionName}`
|
|
405
415
|
await this._waitUntilClassIsOnline(agent, className)
|
|
406
416
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
407
|
-
return this._handleAgentAnswer(json)
|
|
417
|
+
return this._handleAgentAnswer(json, agent)
|
|
408
418
|
}
|
|
409
419
|
|
|
410
420
|
/**
|
|
@@ -436,41 +446,43 @@ class VrpcClient extends EventEmitter {
|
|
|
436
446
|
const json = {
|
|
437
447
|
c: className,
|
|
438
448
|
f: functionName,
|
|
449
|
+
a: [],
|
|
439
450
|
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
440
451
|
s: this._vrpcClientId,
|
|
441
|
-
a: [],
|
|
442
452
|
v: VRPC_PROTOCOL_VERSION
|
|
443
453
|
}
|
|
444
454
|
if (agent === '*') {
|
|
445
455
|
const result = []
|
|
446
456
|
const agents = this.getAvailableAgents()
|
|
447
|
-
for (const
|
|
448
|
-
const topic = `${this._domain}/${
|
|
449
|
-
const wrapped = this._wrapArguments(
|
|
450
|
-
|
|
457
|
+
for (const agent of agents) {
|
|
458
|
+
const topic = `${this._domain}/${agent}/${className}/__static__/__callAll__`
|
|
459
|
+
const wrapped = this._wrapArguments({
|
|
460
|
+
agent,
|
|
461
|
+
className,
|
|
451
462
|
functionName,
|
|
452
|
-
|
|
453
|
-
)
|
|
463
|
+
args
|
|
464
|
+
})
|
|
454
465
|
if (!wrapped) continue // using cache
|
|
455
466
|
json.a = [functionName, ...wrapped]
|
|
456
|
-
await this._waitUntilClassIsOnline(
|
|
467
|
+
await this._waitUntilClassIsOnline(agent, className)
|
|
457
468
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
458
|
-
const tmp = await this._handleAgentAnswer(json)
|
|
469
|
+
const tmp = await this._handleAgentAnswer(json, agent)
|
|
459
470
|
result.push(...tmp)
|
|
460
471
|
}
|
|
461
472
|
return result
|
|
462
473
|
}
|
|
463
474
|
const topic = `${this._domain}/${agent}/${className}/__static__/__callAll__`
|
|
464
|
-
const wrapped = this._wrapArguments(
|
|
465
|
-
|
|
475
|
+
const wrapped = this._wrapArguments({
|
|
476
|
+
agent,
|
|
477
|
+
className,
|
|
466
478
|
functionName,
|
|
467
|
-
|
|
468
|
-
)
|
|
479
|
+
args
|
|
480
|
+
})
|
|
469
481
|
if (!wrapped) return // using cache
|
|
470
482
|
json.a = [functionName, ...wrapped]
|
|
471
483
|
await this._waitUntilClassIsOnline(agent, className)
|
|
472
484
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
473
|
-
return this._handleAgentAnswer(json)
|
|
485
|
+
return this._handleAgentAnswer(json, agent)
|
|
474
486
|
}
|
|
475
487
|
|
|
476
488
|
/**
|
|
@@ -530,7 +542,7 @@ class VrpcClient extends EventEmitter {
|
|
|
530
542
|
}
|
|
531
543
|
|
|
532
544
|
/**
|
|
533
|
-
* Retrieves all (
|
|
545
|
+
* Retrieves all (shared) instances on specific class and agent.
|
|
534
546
|
*
|
|
535
547
|
* @param {Object} [options]
|
|
536
548
|
* @param {String} options.className Class name
|
|
@@ -648,6 +660,10 @@ class VrpcClient extends EventEmitter {
|
|
|
648
660
|
return new Promise(resolve => this._client.end(false, {}, resolve))
|
|
649
661
|
}
|
|
650
662
|
|
|
663
|
+
static _generateUserName () {
|
|
664
|
+
return `${os.userInfo().username}@${os.hostname()}-${os.platform()}-js`
|
|
665
|
+
}
|
|
666
|
+
|
|
651
667
|
_createMqttClientId () {
|
|
652
668
|
const clientInfo =
|
|
653
669
|
os.arch() +
|
|
@@ -664,7 +680,7 @@ class VrpcClient extends EventEmitter {
|
|
|
664
680
|
.createHash('md5')
|
|
665
681
|
.update(clientInfo)
|
|
666
682
|
.digest('hex')
|
|
667
|
-
.
|
|
683
|
+
.substring(0, 12)
|
|
668
684
|
return `vc3${this._instance}${md5}` // 3 + 8 + 12 = 23 (max clientId)
|
|
669
685
|
}
|
|
670
686
|
|
|
@@ -793,35 +809,120 @@ class VrpcClient extends EventEmitter {
|
|
|
793
809
|
// Remove overloads
|
|
794
810
|
const uniqueFuncs = new Set(functions)
|
|
795
811
|
// Build proxy
|
|
796
|
-
uniqueFuncs.forEach(
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
812
|
+
uniqueFuncs.forEach(functionName => {
|
|
813
|
+
// as we are messing with Node.js' event system we have to intercept here
|
|
814
|
+
if (functionName === 'removeAllListeners') {
|
|
815
|
+
proxy[functionName] = async eventName => {
|
|
816
|
+
if (!eventName) {
|
|
817
|
+
throw new Error('VRPC does not support removing all listeners')
|
|
818
|
+
}
|
|
819
|
+
const topic = `${this._domain}/${agent}/${className}/${instance}:${eventName}`
|
|
820
|
+
const id = `__e__${topic}`
|
|
821
|
+
if (this._cachedSubscriptions[topic]) {
|
|
822
|
+
this._eventEmitter.removeAllListeners(id)
|
|
823
|
+
this._mqttUnsubscribe(topic)
|
|
824
|
+
delete this._cachedSubscriptions[topic]
|
|
825
|
+
}
|
|
826
|
+
try {
|
|
827
|
+
const json = {
|
|
828
|
+
c: instance,
|
|
829
|
+
f: functionName,
|
|
830
|
+
a: [eventName],
|
|
831
|
+
i: `${this._instance}-${this._invokeId++ %
|
|
832
|
+
Number.MAX_SAFE_INTEGER}`,
|
|
833
|
+
s: this._vrpcClientId,
|
|
834
|
+
v: VRPC_PROTOCOL_VERSION
|
|
835
|
+
}
|
|
836
|
+
this._mqttPublish(
|
|
837
|
+
`${targetTopic}/${functionName}`,
|
|
838
|
+
JSON.stringify(json)
|
|
839
|
+
)
|
|
840
|
+
return this._handleAgentAnswer(json, agent)
|
|
841
|
+
} catch (err) {
|
|
842
|
+
throw new Error(
|
|
843
|
+
`Could not remotely call "${functionName}" because: ${err.message}`
|
|
844
|
+
)
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return
|
|
848
|
+
}
|
|
849
|
+
proxy[functionName] = async (...args) => {
|
|
850
|
+
const wrapped = this._wrapArguments({
|
|
851
|
+
agent,
|
|
852
|
+
className,
|
|
853
|
+
instance,
|
|
854
|
+
proxyId,
|
|
855
|
+
functionName,
|
|
856
|
+
args
|
|
857
|
+
})
|
|
858
|
+
if (!wrapped) return true // Skipping remote call -> handled locally
|
|
800
859
|
try {
|
|
801
860
|
const json = {
|
|
802
861
|
c: instance,
|
|
803
|
-
f:
|
|
862
|
+
f: functionName,
|
|
863
|
+
a: wrapped,
|
|
804
864
|
i: `${this._instance}-${this._invokeId++ %
|
|
805
865
|
Number.MAX_SAFE_INTEGER}`,
|
|
806
866
|
s: this._vrpcClientId,
|
|
807
|
-
a: wrapped,
|
|
808
867
|
v: VRPC_PROTOCOL_VERSION
|
|
809
868
|
}
|
|
810
|
-
this._mqttPublish(
|
|
811
|
-
|
|
869
|
+
this._mqttPublish(
|
|
870
|
+
`${targetTopic}/${functionName}`,
|
|
871
|
+
JSON.stringify(json)
|
|
872
|
+
)
|
|
873
|
+
return this._handleAgentAnswer(json, agent)
|
|
812
874
|
} catch (err) {
|
|
813
875
|
throw new Error(
|
|
814
|
-
`Could not remotely call "${
|
|
876
|
+
`Could not remotely call "${functionName}" because: ${err.message}`
|
|
815
877
|
)
|
|
816
878
|
}
|
|
817
879
|
}
|
|
818
880
|
})
|
|
881
|
+
if (!uniqueFuncs.has('vrpcOn') && !uniqueFuncs.has('vrpcOff')) {
|
|
882
|
+
proxy.vrpcOn = async (functionName, ...args) => {
|
|
883
|
+
if (!uniqueFuncs.has(functionName)) throw new Error('Bad magic')
|
|
884
|
+
const wrapped = this._wrapArguments({
|
|
885
|
+
agent,
|
|
886
|
+
className,
|
|
887
|
+
instance,
|
|
888
|
+
proxyId,
|
|
889
|
+
functionName: 'vrpcOn',
|
|
890
|
+
args: [functionName, ...args]
|
|
891
|
+
})
|
|
892
|
+
if (!wrapped) return // Skipping remote call -> handled locally
|
|
893
|
+
try {
|
|
894
|
+
const json = {
|
|
895
|
+
c: instance,
|
|
896
|
+
f: functionName,
|
|
897
|
+
a: wrapped.slice(1), // first argument was remote function name
|
|
898
|
+
i: `${this._instance}-${this._invokeId++ %
|
|
899
|
+
Number.MAX_SAFE_INTEGER}`,
|
|
900
|
+
s: this._vrpcClientId,
|
|
901
|
+
v: VRPC_PROTOCOL_VERSION
|
|
902
|
+
}
|
|
903
|
+
this._mqttPublish(
|
|
904
|
+
`${targetTopic}/${functionName}`,
|
|
905
|
+
JSON.stringify(json)
|
|
906
|
+
)
|
|
907
|
+
return this._handleAgentAnswer(json, agent)
|
|
908
|
+
} catch (err) {
|
|
909
|
+
throw new Error(
|
|
910
|
+
`Could not remotely call "${functionName}" because: ${err.message}`
|
|
911
|
+
)
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
proxy.vrpcOff = functionName => {
|
|
915
|
+
const topic = `${this._domain}/${agent}/${className}/${instance}-${functionName}`
|
|
916
|
+
const id = `__e__${topic}`
|
|
917
|
+
this._eventEmitter.removeAllListeners(id)
|
|
918
|
+
}
|
|
919
|
+
}
|
|
819
920
|
return proxy
|
|
820
921
|
}
|
|
821
922
|
|
|
822
|
-
async _handleAgentAnswer ({ i, c, f }) {
|
|
923
|
+
async _handleAgentAnswer ({ i, c, f }, agent) {
|
|
823
924
|
return new Promise((resolve, reject) => {
|
|
824
|
-
const msg = `Function call "${c}::${f}()" timed out (> ${this._timeout} ms)`
|
|
925
|
+
const msg = `Function call "${c}::${f}()" on agent "${agent}" timed out (> ${this._timeout} ms)`
|
|
825
926
|
const timer = setTimeout(() => {
|
|
826
927
|
this._eventEmitter.removeAllListeners(i)
|
|
827
928
|
reject(new Error(msg))
|
|
@@ -829,13 +930,15 @@ class VrpcClient extends EventEmitter {
|
|
|
829
930
|
this._eventEmitter.once(i, data => {
|
|
830
931
|
clearTimeout(timer)
|
|
831
932
|
if (data.e) {
|
|
832
|
-
reject(new Error(data.e))
|
|
933
|
+
reject(new Error(`[vrpc ${agent}-${c}-${f}]: ${data.e}`))
|
|
833
934
|
} else {
|
|
834
935
|
const ret = data.r
|
|
835
936
|
// Handle functions returning a promise
|
|
836
|
-
if (typeof ret === 'string' && ret.
|
|
937
|
+
if (typeof ret === 'string' && ret.substring(0, 5) === '__p__') {
|
|
837
938
|
this._eventEmitter.once(ret, promiseData => {
|
|
838
|
-
|
|
939
|
+
// TODO improve error message here, like above
|
|
940
|
+
if (promiseData.e)
|
|
941
|
+
reject(new Error(`[vrpc ${agent}-${c}-${f}]: ${promiseData.e}`))
|
|
839
942
|
else resolve(promiseData.r)
|
|
840
943
|
})
|
|
841
944
|
} else {
|
|
@@ -866,79 +969,130 @@ class VrpcClient extends EventEmitter {
|
|
|
866
969
|
})
|
|
867
970
|
}
|
|
868
971
|
|
|
869
|
-
_wrapArguments (
|
|
972
|
+
_wrapArguments ({ agent, className, instance, proxyId, functionName, args }) {
|
|
870
973
|
const wrapped = []
|
|
871
|
-
|
|
872
|
-
args.forEach((x, i) => {
|
|
974
|
+
for (const [i, x] of args.entries()) {
|
|
873
975
|
// Check whether provided argument is a function
|
|
874
976
|
if (this._isFunction(x)) {
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
977
|
+
const callback = x
|
|
978
|
+
if (
|
|
979
|
+
functionName === 'vrpcOn' ||
|
|
980
|
+
(functionName === 'on' && i === 1 && typeof args[0] === 'string')
|
|
981
|
+
) {
|
|
982
|
+
// special case of an (remote-)event emitter registration
|
|
983
|
+
// we test three conditions:
|
|
984
|
+
// 1) functionName must be "on"
|
|
985
|
+
// 2) callback is second argument
|
|
986
|
+
// 3) first argument was string
|
|
987
|
+
const event = args[0]
|
|
988
|
+
const id = this._onRemoteEvent({
|
|
989
|
+
agent,
|
|
990
|
+
className,
|
|
991
|
+
instance,
|
|
992
|
+
event,
|
|
993
|
+
callback
|
|
994
|
+
})
|
|
995
|
+
// indicate that no network call is required
|
|
996
|
+
if (id === null) return null
|
|
883
997
|
wrapped.push(id)
|
|
998
|
+
continue
|
|
884
999
|
} else if (
|
|
885
1000
|
(functionName === 'off' || functionName === 'removeListener') &&
|
|
886
1001
|
i === 1 &&
|
|
887
1002
|
typeof args[0] === 'string'
|
|
888
1003
|
) {
|
|
889
|
-
const
|
|
890
|
-
|
|
1004
|
+
const event = args[0]
|
|
1005
|
+
const id = this._offRemoteEvent({
|
|
1006
|
+
agent,
|
|
1007
|
+
className,
|
|
1008
|
+
instance,
|
|
1009
|
+
event,
|
|
1010
|
+
callback
|
|
1011
|
+
})
|
|
1012
|
+
// indicate that no network call is required
|
|
1013
|
+
if (id === null) return null
|
|
891
1014
|
wrapped.push(id)
|
|
892
|
-
|
|
1015
|
+
continue
|
|
893
1016
|
} else {
|
|
1017
|
+
// Regular function callback (can be static or member function)
|
|
1018
|
+
const remoteId = proxyId || `${agent}-${className}`
|
|
894
1019
|
const id = `__f__${remoteId}-${functionName}-${i}-${this._invokeId++ %
|
|
895
1020
|
Number.MAX_SAFE_INTEGER}`
|
|
896
1021
|
wrapped.push(id)
|
|
897
|
-
this._eventEmitter.once(id, ({ a }) =>
|
|
898
|
-
|
|
899
|
-
})
|
|
1022
|
+
this._eventEmitter.once(id, ({ a }) => x.apply(null, a))
|
|
1023
|
+
continue
|
|
900
1024
|
}
|
|
901
|
-
}
|
|
1025
|
+
}
|
|
1026
|
+
// special case of an EventEmitter provided as argument
|
|
1027
|
+
if (this._isEmitter(x)) {
|
|
902
1028
|
const { emitter, event } = x
|
|
903
|
-
const id =
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1029
|
+
const id = this._onRemoteEvent({
|
|
1030
|
+
agent,
|
|
1031
|
+
className,
|
|
1032
|
+
instance,
|
|
1033
|
+
event,
|
|
1034
|
+
callback: (...args) => emitter.emit(event, ...args)
|
|
907
1035
|
})
|
|
908
|
-
|
|
909
|
-
|
|
1036
|
+
// indicate that no network call is required
|
|
1037
|
+
if (id === null) return null
|
|
1038
|
+
wrapped.push(id)
|
|
1039
|
+
continue
|
|
910
1040
|
}
|
|
911
|
-
|
|
912
|
-
|
|
1041
|
+
// default behavior is to not touch
|
|
1042
|
+
wrapped.push(x)
|
|
1043
|
+
}
|
|
913
1044
|
return wrapped
|
|
914
1045
|
}
|
|
915
1046
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
this.
|
|
920
|
-
|
|
1047
|
+
_onRemoteEvent ({ agent, className, instance, event, callback }) {
|
|
1048
|
+
// the topic on which any remote event will be published to
|
|
1049
|
+
const topic = instance
|
|
1050
|
+
? `${this._domain}/${agent}/${className}/${instance}:${event}`
|
|
1051
|
+
: `${this._domain}/${agent}/${className}/${event}`
|
|
1052
|
+
// prepare a special id for the agent to know that events should be
|
|
1053
|
+
// published to the provided topic after the prefix
|
|
1054
|
+
const id = `__e__${topic}`
|
|
1055
|
+
// call our proxy callback when remote events were received
|
|
1056
|
+
const handler = ({ a }) => callback.apply(null, a)
|
|
1057
|
+
this._eventEmitter.on(id, handler)
|
|
1058
|
+
if (!this._cachedSubscriptions[topic]) {
|
|
1059
|
+
// when not yet subscribed to this topic do it now
|
|
1060
|
+
this._mqttSubscribe(topic)
|
|
1061
|
+
this._cachedSubscriptions[topic] = [{ callback, handler }]
|
|
1062
|
+
return id
|
|
921
1063
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
emitter.emit(id, ...a)
|
|
927
|
-
})
|
|
928
|
-
return id
|
|
1064
|
+
// otherwise just add the new callback-handler pair
|
|
1065
|
+
this._cachedSubscriptions[topic].push({ callback, handler })
|
|
1066
|
+
// indicate that no network call is required
|
|
1067
|
+
return null
|
|
929
1068
|
}
|
|
930
1069
|
|
|
931
|
-
|
|
932
|
-
const
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1070
|
+
_offRemoteEvent ({ agent, className, instance, event, callback }) {
|
|
1071
|
+
const topic = instance
|
|
1072
|
+
? `${this._domain}/${agent}/${className}/${instance}:${event}`
|
|
1073
|
+
: `${this._domain}/${agent}/${className}/${event}`
|
|
1074
|
+
const id = `__e__${topic}`
|
|
1075
|
+
if (this._cachedSubscriptions[topic]) {
|
|
1076
|
+
const index = this._cachedSubscriptions[topic].findIndex(
|
|
1077
|
+
x => x.callback === callback
|
|
1078
|
+
)
|
|
1079
|
+
if (index === -1) {
|
|
1080
|
+
this._log.warn(`Failed removing event listener ${id}`)
|
|
1081
|
+
return null
|
|
1082
|
+
}
|
|
1083
|
+
const [{ handler }] = this._cachedSubscriptions[topic].splice(index, 1)
|
|
1084
|
+
this._eventEmitter.removeListener(id, handler)
|
|
1085
|
+
if (this._cachedSubscriptions[topic].length === 0) {
|
|
1086
|
+
this._mqttUnsubscribe(topic)
|
|
1087
|
+
delete this._cachedSubscriptions[topic]
|
|
939
1088
|
return id
|
|
940
1089
|
}
|
|
1090
|
+
// indicate that no network call is required
|
|
1091
|
+
return null
|
|
1092
|
+
} else {
|
|
1093
|
+
this._log.warn(`Can not unsubscribe from non-existing event: ${event}`)
|
|
941
1094
|
}
|
|
1095
|
+
return id
|
|
942
1096
|
}
|
|
943
1097
|
|
|
944
1098
|
_stripSignature (method) {
|
|
@@ -1045,7 +1199,7 @@ class VrpcClient extends EventEmitter {
|
|
|
1045
1199
|
* @param {String} info.domain - Domain name
|
|
1046
1200
|
* @param {String} info.agent - Agent name
|
|
1047
1201
|
* @param {String} info.className - Class name
|
|
1048
|
-
* @param {Array.<String>} info.instances - Array of
|
|
1202
|
+
* @param {Array.<String>} info.instances - Array of instances
|
|
1049
1203
|
* @param {Array.<String>} info.memberFunctions - Array of member functions
|
|
1050
1204
|
* @param {Array.<String>} info.staticFunctions - Array of static functions
|
|
1051
1205
|
* @param {MetaData} info.meta - Object associating further information to functions
|