vrpc 3.0.0-alpha.3 → 3.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/CHANGELOG.md +28 -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 +194 -171
- package/vrpc/VrpcAgent.js +18 -17
- package/vrpc/VrpcClient.js +260 -113
- package/vrpc/VrpcNative.js +25 -2
- package/vrpc/adapter.hpp +4 -2
- package/vrpc/addon.cpp +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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -664,7 +676,7 @@ class VrpcClient extends EventEmitter {
|
|
|
664
676
|
.createHash('md5')
|
|
665
677
|
.update(clientInfo)
|
|
666
678
|
.digest('hex')
|
|
667
|
-
.
|
|
679
|
+
.substring(0, 12)
|
|
668
680
|
return `vc3${this._instance}${md5}` // 3 + 8 + 12 = 23 (max clientId)
|
|
669
681
|
}
|
|
670
682
|
|
|
@@ -793,35 +805,120 @@ class VrpcClient extends EventEmitter {
|
|
|
793
805
|
// Remove overloads
|
|
794
806
|
const uniqueFuncs = new Set(functions)
|
|
795
807
|
// Build proxy
|
|
796
|
-
uniqueFuncs.forEach(
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
808
|
+
uniqueFuncs.forEach(functionName => {
|
|
809
|
+
// as we are messing with Node.js' event system we have to intercept here
|
|
810
|
+
if (functionName === 'removeAllListeners') {
|
|
811
|
+
proxy[functionName] = async eventName => {
|
|
812
|
+
if (!eventName) {
|
|
813
|
+
throw new Error('VRPC does not support removing all listeners')
|
|
814
|
+
}
|
|
815
|
+
const topic = `${this._domain}/${agent}/${className}/${instance}:${eventName}`
|
|
816
|
+
const id = `__e__${topic}`
|
|
817
|
+
if (this._cachedSubscriptions[topic]) {
|
|
818
|
+
this._eventEmitter.removeAllListeners(id)
|
|
819
|
+
this._mqttUnsubscribe(topic)
|
|
820
|
+
delete this._cachedSubscriptions[topic]
|
|
821
|
+
}
|
|
822
|
+
try {
|
|
823
|
+
const json = {
|
|
824
|
+
c: instance,
|
|
825
|
+
f: functionName,
|
|
826
|
+
a: [eventName],
|
|
827
|
+
i: `${this._instance}-${this._invokeId++ %
|
|
828
|
+
Number.MAX_SAFE_INTEGER}`,
|
|
829
|
+
s: this._vrpcClientId,
|
|
830
|
+
v: VRPC_PROTOCOL_VERSION
|
|
831
|
+
}
|
|
832
|
+
this._mqttPublish(
|
|
833
|
+
`${targetTopic}/${functionName}`,
|
|
834
|
+
JSON.stringify(json)
|
|
835
|
+
)
|
|
836
|
+
return this._handleAgentAnswer(json, agent)
|
|
837
|
+
} catch (err) {
|
|
838
|
+
throw new Error(
|
|
839
|
+
`Could not remotely call "${functionName}" because: ${err.message}`
|
|
840
|
+
)
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
return
|
|
844
|
+
}
|
|
845
|
+
proxy[functionName] = async (...args) => {
|
|
846
|
+
const wrapped = this._wrapArguments({
|
|
847
|
+
agent,
|
|
848
|
+
className,
|
|
849
|
+
instance,
|
|
850
|
+
proxyId,
|
|
851
|
+
functionName,
|
|
852
|
+
args
|
|
853
|
+
})
|
|
854
|
+
if (!wrapped) return true // Skipping remote call -> handled locally
|
|
800
855
|
try {
|
|
801
856
|
const json = {
|
|
802
857
|
c: instance,
|
|
803
|
-
f:
|
|
858
|
+
f: functionName,
|
|
859
|
+
a: wrapped,
|
|
804
860
|
i: `${this._instance}-${this._invokeId++ %
|
|
805
861
|
Number.MAX_SAFE_INTEGER}`,
|
|
806
862
|
s: this._vrpcClientId,
|
|
807
|
-
a: wrapped,
|
|
808
863
|
v: VRPC_PROTOCOL_VERSION
|
|
809
864
|
}
|
|
810
|
-
this._mqttPublish(
|
|
811
|
-
|
|
865
|
+
this._mqttPublish(
|
|
866
|
+
`${targetTopic}/${functionName}`,
|
|
867
|
+
JSON.stringify(json)
|
|
868
|
+
)
|
|
869
|
+
return this._handleAgentAnswer(json, agent)
|
|
812
870
|
} catch (err) {
|
|
813
871
|
throw new Error(
|
|
814
|
-
`Could not remotely call "${
|
|
872
|
+
`Could not remotely call "${functionName}" because: ${err.message}`
|
|
815
873
|
)
|
|
816
874
|
}
|
|
817
875
|
}
|
|
818
876
|
})
|
|
877
|
+
if (!uniqueFuncs.has('vrpcOn') && !uniqueFuncs.has('vrpcOff')) {
|
|
878
|
+
proxy.vrpcOn = async (functionName, ...args) => {
|
|
879
|
+
if (!uniqueFuncs.has(functionName)) throw new Error('Bad magic')
|
|
880
|
+
const wrapped = this._wrapArguments({
|
|
881
|
+
agent,
|
|
882
|
+
className,
|
|
883
|
+
instance,
|
|
884
|
+
proxyId,
|
|
885
|
+
functionName: 'vrpcOn',
|
|
886
|
+
args: [functionName, ...args]
|
|
887
|
+
})
|
|
888
|
+
if (!wrapped) return // Skipping remote call -> handled locally
|
|
889
|
+
try {
|
|
890
|
+
const json = {
|
|
891
|
+
c: instance,
|
|
892
|
+
f: functionName,
|
|
893
|
+
a: wrapped.slice(1), // first argument was remote function name
|
|
894
|
+
i: `${this._instance}-${this._invokeId++ %
|
|
895
|
+
Number.MAX_SAFE_INTEGER}`,
|
|
896
|
+
s: this._vrpcClientId,
|
|
897
|
+
v: VRPC_PROTOCOL_VERSION
|
|
898
|
+
}
|
|
899
|
+
this._mqttPublish(
|
|
900
|
+
`${targetTopic}/${functionName}`,
|
|
901
|
+
JSON.stringify(json)
|
|
902
|
+
)
|
|
903
|
+
return this._handleAgentAnswer(json, agent)
|
|
904
|
+
} catch (err) {
|
|
905
|
+
throw new Error(
|
|
906
|
+
`Could not remotely call "${functionName}" because: ${err.message}`
|
|
907
|
+
)
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
proxy.vrpcOff = functionName => {
|
|
911
|
+
const topic = `${this._domain}/${agent}/${className}/${instance}-${functionName}`
|
|
912
|
+
const id = `__e__${topic}`
|
|
913
|
+
this._eventEmitter.removeAllListeners(id)
|
|
914
|
+
}
|
|
915
|
+
}
|
|
819
916
|
return proxy
|
|
820
917
|
}
|
|
821
918
|
|
|
822
|
-
async _handleAgentAnswer ({ i, c, f }) {
|
|
919
|
+
async _handleAgentAnswer ({ i, c, f }, agent) {
|
|
823
920
|
return new Promise((resolve, reject) => {
|
|
824
|
-
const msg = `Function call "${c}::${f}()" timed out (> ${this._timeout} ms)`
|
|
921
|
+
const msg = `Function call "${c}::${f}()" on agent "${agent}" timed out (> ${this._timeout} ms)`
|
|
825
922
|
const timer = setTimeout(() => {
|
|
826
923
|
this._eventEmitter.removeAllListeners(i)
|
|
827
924
|
reject(new Error(msg))
|
|
@@ -829,13 +926,14 @@ class VrpcClient extends EventEmitter {
|
|
|
829
926
|
this._eventEmitter.once(i, data => {
|
|
830
927
|
clearTimeout(timer)
|
|
831
928
|
if (data.e) {
|
|
832
|
-
reject(new Error(data.e))
|
|
929
|
+
reject(new Error(`[vrpc ${agent}-${c}-${f}]: ${data.e}`))
|
|
833
930
|
} else {
|
|
834
931
|
const ret = data.r
|
|
835
932
|
// Handle functions returning a promise
|
|
836
|
-
if (typeof ret === 'string' && ret.
|
|
933
|
+
if (typeof ret === 'string' && ret.substring(0, 5) === '__p__') {
|
|
837
934
|
this._eventEmitter.once(ret, promiseData => {
|
|
838
|
-
|
|
935
|
+
// TODO improve error message here, like above
|
|
936
|
+
if (promiseData.e) reject(new Error(`[vrpc ${agent}-${c}-${f}]: ${promiseData.e}`))
|
|
839
937
|
else resolve(promiseData.r)
|
|
840
938
|
})
|
|
841
939
|
} else {
|
|
@@ -866,79 +964,128 @@ class VrpcClient extends EventEmitter {
|
|
|
866
964
|
})
|
|
867
965
|
}
|
|
868
966
|
|
|
869
|
-
_wrapArguments (
|
|
967
|
+
_wrapArguments ({ agent, className, instance, proxyId, functionName, args }) {
|
|
870
968
|
const wrapped = []
|
|
871
|
-
|
|
872
|
-
args.forEach((x, i) => {
|
|
969
|
+
for (const [i, x] of args.entries()) {
|
|
873
970
|
// Check whether provided argument is a function
|
|
874
971
|
if (this._isFunction(x)) {
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
972
|
+
const callback = x
|
|
973
|
+
if (
|
|
974
|
+
functionName === 'vrpcOn' ||
|
|
975
|
+
(functionName === 'on' && i === 1 && typeof args[0] === 'string')
|
|
976
|
+
) {
|
|
977
|
+
// special case of an (remote-)event emitter registration
|
|
978
|
+
// we test three conditions:
|
|
979
|
+
// 1) functionName must be "on"
|
|
980
|
+
// 2) callback is second argument
|
|
981
|
+
// 3) first argument was string
|
|
982
|
+
const event = args[0]
|
|
983
|
+
const id = this._onRemoteEvent({
|
|
984
|
+
agent,
|
|
985
|
+
className,
|
|
986
|
+
instance,
|
|
987
|
+
event,
|
|
988
|
+
callback
|
|
989
|
+
})
|
|
990
|
+
// indicate that no network call is required
|
|
991
|
+
if (id === null) return null
|
|
883
992
|
wrapped.push(id)
|
|
993
|
+
continue
|
|
884
994
|
} else if (
|
|
885
995
|
(functionName === 'off' || functionName === 'removeListener') &&
|
|
886
996
|
i === 1 &&
|
|
887
997
|
typeof args[0] === 'string'
|
|
888
998
|
) {
|
|
889
|
-
const
|
|
890
|
-
|
|
999
|
+
const event = args[0]
|
|
1000
|
+
const id = this._offRemoteEvent({
|
|
1001
|
+
agent,
|
|
1002
|
+
className,
|
|
1003
|
+
instance,
|
|
1004
|
+
event,
|
|
1005
|
+
callback
|
|
1006
|
+
})
|
|
1007
|
+
// indicate that no network call is required
|
|
1008
|
+
if (id === null) return null
|
|
891
1009
|
wrapped.push(id)
|
|
892
|
-
|
|
1010
|
+
continue
|
|
893
1011
|
} else {
|
|
1012
|
+
// Regular function callback (can be static or member function)
|
|
1013
|
+
const remoteId = proxyId || `${agent}-${className}`
|
|
894
1014
|
const id = `__f__${remoteId}-${functionName}-${i}-${this._invokeId++ %
|
|
895
1015
|
Number.MAX_SAFE_INTEGER}`
|
|
896
1016
|
wrapped.push(id)
|
|
897
|
-
this._eventEmitter.once(id, ({ a }) =>
|
|
898
|
-
|
|
899
|
-
})
|
|
1017
|
+
this._eventEmitter.once(id, ({ a }) => x.apply(null, a))
|
|
1018
|
+
continue
|
|
900
1019
|
}
|
|
901
|
-
}
|
|
1020
|
+
}
|
|
1021
|
+
// special case of an EventEmitter provided as argument
|
|
1022
|
+
if (this._isEmitter(x)) {
|
|
902
1023
|
const { emitter, event } = x
|
|
903
|
-
const id =
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1024
|
+
const id = this._onRemoteEvent({
|
|
1025
|
+
agent,
|
|
1026
|
+
className,
|
|
1027
|
+
instance,
|
|
1028
|
+
event,
|
|
1029
|
+
callback: (...args) => emitter.emit(event, ...args)
|
|
907
1030
|
})
|
|
908
|
-
|
|
909
|
-
|
|
1031
|
+
// indicate that no network call is required
|
|
1032
|
+
if (id === null) return null
|
|
1033
|
+
wrapped.push(id)
|
|
1034
|
+
continue
|
|
910
1035
|
}
|
|
911
|
-
|
|
912
|
-
|
|
1036
|
+
// default behavior is to not touch
|
|
1037
|
+
wrapped.push(x)
|
|
1038
|
+
}
|
|
913
1039
|
return wrapped
|
|
914
1040
|
}
|
|
915
1041
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
this.
|
|
920
|
-
|
|
1042
|
+
_onRemoteEvent ({ agent, className, instance, event, callback }) {
|
|
1043
|
+
// the topic on which any remote event will be published to
|
|
1044
|
+
const topic = instance
|
|
1045
|
+
? `${this._domain}/${agent}/${className}/${instance}:${event}`
|
|
1046
|
+
: `${this._domain}/${agent}/${className}/${event}`
|
|
1047
|
+
// prepare a special id for the agent to know that events should be
|
|
1048
|
+
// published to the provided topic after the prefix
|
|
1049
|
+
const id = `__e__${topic}`
|
|
1050
|
+
// call our proxy callback when remote events were received
|
|
1051
|
+
const handler = ({ a }) => callback.apply(null, a)
|
|
1052
|
+
this._eventEmitter.on(id, handler)
|
|
1053
|
+
if (!this._cachedSubscriptions[topic]) {
|
|
1054
|
+
// when not yet subscribed to this topic do it now
|
|
1055
|
+
this._mqttSubscribe(topic)
|
|
1056
|
+
this._cachedSubscriptions[topic] = [{ callback, handler }]
|
|
1057
|
+
return id
|
|
921
1058
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
emitter.emit(id, ...a)
|
|
927
|
-
})
|
|
928
|
-
return id
|
|
1059
|
+
// otherwise just add the new callback-handler pair
|
|
1060
|
+
this._cachedSubscriptions[topic].push({ callback, handler })
|
|
1061
|
+
// indicate that no network call is required
|
|
1062
|
+
return null
|
|
929
1063
|
}
|
|
930
1064
|
|
|
931
|
-
|
|
932
|
-
const
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1065
|
+
_offRemoteEvent ({ agent, className, instance, event, callback }) {
|
|
1066
|
+
const topic = instance
|
|
1067
|
+
? `${this._domain}/${agent}/${className}/${instance}:${event}`
|
|
1068
|
+
: `${this._domain}/${agent}/${className}/${event}`
|
|
1069
|
+
const id = `__e__${topic}`
|
|
1070
|
+
if (this._cachedSubscriptions[topic]) {
|
|
1071
|
+
const index = this._cachedSubscriptions[topic].findIndex(x => x.callback === callback)
|
|
1072
|
+
if (index === -1) {
|
|
1073
|
+
this._log.warn(`Failed removing event listener ${id}`)
|
|
1074
|
+
return null
|
|
1075
|
+
}
|
|
1076
|
+
const [{ handler }] = this._cachedSubscriptions[topic].splice(index, 1)
|
|
1077
|
+
this._eventEmitter.removeListener(id, handler)
|
|
1078
|
+
if (this._cachedSubscriptions[topic].length === 0) {
|
|
1079
|
+
this._mqttUnsubscribe(topic)
|
|
1080
|
+
delete this._cachedSubscriptions[topic]
|
|
939
1081
|
return id
|
|
940
1082
|
}
|
|
1083
|
+
// indicate that no network call is required
|
|
1084
|
+
return null
|
|
1085
|
+
} else {
|
|
1086
|
+
this._log.warn(`Can not unsubscribe from non-existing event: ${event}`)
|
|
941
1087
|
}
|
|
1088
|
+
return id
|
|
942
1089
|
}
|
|
943
1090
|
|
|
944
1091
|
_stripSignature (method) {
|
|
@@ -1045,7 +1192,7 @@ class VrpcClient extends EventEmitter {
|
|
|
1045
1192
|
* @param {String} info.domain - Domain name
|
|
1046
1193
|
* @param {String} info.agent - Agent name
|
|
1047
1194
|
* @param {String} info.className - Class name
|
|
1048
|
-
* @param {Array.<String>} info.instances - Array of
|
|
1195
|
+
* @param {Array.<String>} info.instances - Array of instances
|
|
1049
1196
|
* @param {Array.<String>} info.memberFunctions - Array of member functions
|
|
1050
1197
|
* @param {Array.<String>} info.staticFunctions - Array of static functions
|
|
1051
1198
|
* @param {MetaData} info.meta - Object associating further information to functions
|
package/vrpc/VrpcNative.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
|
|
@@ -41,7 +41,7 @@ const EventEmitter = require('events')
|
|
|
41
41
|
const { nanoid } = require('nanoid')
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* Client capable of creating proxy
|
|
44
|
+
* Client capable of creating proxy classes and objects to locally call
|
|
45
45
|
* functions as provided through native addons.
|
|
46
46
|
*/
|
|
47
47
|
class VrpcNative {
|
|
@@ -64,6 +64,15 @@ class VrpcNative {
|
|
|
64
64
|
})
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Provides a proxy class to an existing one in the native addon
|
|
69
|
+
*
|
|
70
|
+
* You can use the returned class in the usual way. Static function calls
|
|
71
|
+
* are forwarded to the native addon, as are any instantiations and member
|
|
72
|
+
* function calls.
|
|
73
|
+
* @param {String} className The name of the class
|
|
74
|
+
* @returns Proxy Class
|
|
75
|
+
*/
|
|
67
76
|
getClass (className) {
|
|
68
77
|
if (!this.getAvailableClasses().includes(className)) {
|
|
69
78
|
throw new Error(`Native addon does not provide class: ${className}`)
|
|
@@ -226,6 +235,11 @@ class VrpcNative {
|
|
|
226
235
|
return Klass
|
|
227
236
|
}
|
|
228
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Deletes a proxy object and its underlying instance
|
|
240
|
+
* @param {Object} proxy A proxy object
|
|
241
|
+
* @returns True in case of success, false otherwise
|
|
242
|
+
*/
|
|
229
243
|
delete (proxy) {
|
|
230
244
|
if (!typeof proxy === 'object' || !proxy.vrpcProxyId) {
|
|
231
245
|
throw new Error('Provided argument seems not to be a VRPC proxy')
|
|
@@ -238,6 +252,15 @@ class VrpcNative {
|
|
|
238
252
|
return JSON.parse(this._adapter.call(JSON.stringify(json))).r
|
|
239
253
|
}
|
|
240
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Secondary option to call a static function (when creation of a proxy class
|
|
257
|
+
* seems to be too much overhead)
|
|
258
|
+
*
|
|
259
|
+
* @param {String} className The class on which the static function should be called
|
|
260
|
+
* @param {String} functionName Name of the static function
|
|
261
|
+
* @param {...any} args The function arguments
|
|
262
|
+
* @returns The output of the underlying static function
|
|
263
|
+
*/
|
|
241
264
|
callStatic (className, functionName, ...args) {
|
|
242
265
|
if (functionName === 'vrpcOff') {
|
|
243
266
|
const id = `__f__${className}-vrpcOn:${args[0]}`
|