vrpc 2.6.0 → 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 +83 -0
- package/LICENSE +1 -1
- package/README.md +34 -60
- package/binding.gyp.no-auto +4 -26
- package/browser/vrpc.js +1 -1
- package/index.js +2 -2
- package/package.json +16 -17
- package/vrpc/VrpcAdapter.js +293 -311
- package/vrpc/VrpcAgent.js +94 -87
- package/vrpc/{VrpcRemote.js → VrpcClient.js} +375 -413
- package/vrpc/VrpcNative.js +330 -0
- package/vrpc/{vrpc.hpp → adapter.hpp} +1003 -1115
- package/vrpc/addon.cpp +6 -6
- package/vrpc/json.hpp +1769 -870
- package/vrpc/vrpc-agent-js +4 -4
- package/README.rst +0 -18
- package/third_party/include/catch.hpp +0 -12749
- package/third_party/install.sh +0 -22
- package/vrpc/VrpcLocal.js +0 -269
- package/vrpc/VrpcLocal.py +0 -171
- package/vrpc/__init__.py +0 -1
- package/vrpc/module.cpp +0 -183
|
@@ -10,13 +10,13 @@ __\/\\\_______\/\\\__/\\\///////\\\___\/\\\/////////\\\____/\\\////////__
|
|
|
10
10
|
________\///________\///________\///__\///___________________\/////////__
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
Non-intrusively
|
|
13
|
+
Non-intrusively adapts code and provides access in form of asynchronous remote
|
|
14
14
|
procedure calls (RPC).
|
|
15
|
-
Author: Dr. Burkhard C. Heisen (https://github.com/
|
|
15
|
+
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
|
|
@@ -39,16 +39,19 @@ SOFTWARE.
|
|
|
39
39
|
|
|
40
40
|
const os = require('os')
|
|
41
41
|
const crypto = require('crypto')
|
|
42
|
+
const { nanoid } = require('nanoid')
|
|
42
43
|
const mqtt = require('mqtt')
|
|
43
44
|
const EventEmitter = require('events')
|
|
44
45
|
|
|
46
|
+
const VRPC_PROTOCOL_VERSION = 3
|
|
47
|
+
|
|
45
48
|
/**
|
|
46
49
|
* Client capable of creating proxy objects and remotely calling
|
|
47
50
|
* functions as provided through one or more (distributed) agents.
|
|
48
51
|
*
|
|
49
52
|
* @extends EventEmitter
|
|
50
53
|
*/
|
|
51
|
-
class
|
|
54
|
+
class VrpcClient extends EventEmitter {
|
|
52
55
|
/**
|
|
53
56
|
* Constructs a remote client, able to communicate with any distributed agents
|
|
54
57
|
*
|
|
@@ -66,7 +69,7 @@ class VrpcRemote extends EventEmitter {
|
|
|
66
69
|
* @param {Object} [options.log=console] Log object (must support debug, info, warn, and error level)
|
|
67
70
|
* @param {Boolean} [options.bestEffort=false] If true, message will be sent with best effort, i.e. no caching if offline
|
|
68
71
|
* @example
|
|
69
|
-
* const client = new
|
|
72
|
+
* const client = new VrpcClient({
|
|
70
73
|
* domain: 'public.vrpc',
|
|
71
74
|
* broker: 'mqtt://vrpc.io'
|
|
72
75
|
* })
|
|
@@ -105,7 +108,7 @@ class VrpcRemote extends EventEmitter {
|
|
|
105
108
|
this._timeout = timeout
|
|
106
109
|
this._qos = this._qos = bestEffort ? 0 : 1
|
|
107
110
|
|
|
108
|
-
this._instance =
|
|
111
|
+
this._instance = nanoid(8)
|
|
109
112
|
this._mqttClientId = this._createMqttClientId()
|
|
110
113
|
this._vrpcClientId = this._createVrpcClientId()
|
|
111
114
|
this._agents = {}
|
|
@@ -113,7 +116,7 @@ class VrpcRemote extends EventEmitter {
|
|
|
113
116
|
this._invokeId = 0
|
|
114
117
|
this._proxyId = 0
|
|
115
118
|
this._client = null
|
|
116
|
-
this._cachedSubscriptions =
|
|
119
|
+
this._cachedSubscriptions = {}
|
|
117
120
|
this._proxies = {}
|
|
118
121
|
if (log === 'console') {
|
|
119
122
|
this._log = console
|
|
@@ -123,6 +126,15 @@ class VrpcRemote extends EventEmitter {
|
|
|
123
126
|
}
|
|
124
127
|
}
|
|
125
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Provides a unique id for this client instance
|
|
131
|
+
*
|
|
132
|
+
* @returns {String} clientId
|
|
133
|
+
*/
|
|
134
|
+
getClientId () {
|
|
135
|
+
return this._vrpcClientId
|
|
136
|
+
}
|
|
137
|
+
|
|
126
138
|
/**
|
|
127
139
|
* Actually connects to the MQTT broker.
|
|
128
140
|
*
|
|
@@ -150,6 +162,7 @@ class VrpcRemote extends EventEmitter {
|
|
|
150
162
|
keepalive: 30,
|
|
151
163
|
clientId: this._mqttClientId,
|
|
152
164
|
rejectUnauthorized: false,
|
|
165
|
+
connectTimeout: this._timeout,
|
|
153
166
|
will: {
|
|
154
167
|
topic: `${this._vrpcClientId}/__clientInfo__`,
|
|
155
168
|
payload: JSON.stringify({ status: 'offline' })
|
|
@@ -217,10 +230,12 @@ class VrpcRemote extends EventEmitter {
|
|
|
217
230
|
staticFunctions,
|
|
218
231
|
meta
|
|
219
232
|
} = json
|
|
220
|
-
if (removed.length !== 0)
|
|
233
|
+
if (removed.length !== 0) {
|
|
221
234
|
this.emit('instanceGone', removed, { domain, agent, className })
|
|
222
|
-
|
|
235
|
+
}
|
|
236
|
+
if (added.length !== 0) {
|
|
223
237
|
this.emit('instanceNew', added, { domain, agent, className })
|
|
238
|
+
}
|
|
224
239
|
this.emit('class', {
|
|
225
240
|
domain,
|
|
226
241
|
agent,
|
|
@@ -232,42 +247,33 @@ class VrpcRemote extends EventEmitter {
|
|
|
232
247
|
})
|
|
233
248
|
// RPC message
|
|
234
249
|
} else {
|
|
235
|
-
const {
|
|
236
|
-
this._eventEmitter.emit(
|
|
250
|
+
const { i, a, e, r } = JSON.parse(message.toString())
|
|
251
|
+
this._eventEmitter.emit(i, { a, e, r })
|
|
237
252
|
}
|
|
238
253
|
})
|
|
239
|
-
|
|
240
|
-
|
|
254
|
+
return new Promise((resolve, reject) => {
|
|
255
|
+
this._client.once('offline', () => {
|
|
256
|
+
this._client.end()
|
|
241
257
|
reject(new Error(`Connection trial timed out (> ${this._timeout} ms)`))
|
|
242
|
-
}, this._timeout)
|
|
243
|
-
this.once('connect', () => {
|
|
244
|
-
clearTimeout(timer)
|
|
245
|
-
resolve()
|
|
246
258
|
})
|
|
259
|
+
this._client.once('connect', resolve)
|
|
247
260
|
})
|
|
248
261
|
}
|
|
249
262
|
|
|
250
|
-
async connected () {
|
|
251
|
-
this._indicateDeprecation(
|
|
252
|
-
'VrpcRemote.connected(): This API usage is deprecated, use `connect()` instead.'
|
|
253
|
-
)
|
|
254
|
-
return this.connect()
|
|
255
|
-
}
|
|
256
|
-
|
|
257
263
|
/**
|
|
258
264
|
* Creates a new remote instance and provides a proxy to it.
|
|
259
265
|
*
|
|
260
|
-
* Remote instances can be "
|
|
261
|
-
*
|
|
262
|
-
* explicitly deleted. Life-cycle changes of
|
|
263
|
-
* under the `class`, `instanceNew`, and `instanceGone` events. A
|
|
264
|
-
* 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`).
|
|
265
271
|
*
|
|
266
|
-
* When the `
|
|
267
|
-
*
|
|
268
|
-
*
|
|
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.
|
|
269
275
|
*
|
|
270
|
-
* **NOTE** When creating
|
|
276
|
+
* **NOTE** When creating an instance that already exists, the new proxy will
|
|
271
277
|
* simply attach to (and not re-create) it - just like `getInstance()` was
|
|
272
278
|
* called.
|
|
273
279
|
*
|
|
@@ -275,50 +281,51 @@ class VrpcRemote extends EventEmitter {
|
|
|
275
281
|
* @param {String} options.className Name of the class which should be
|
|
276
282
|
* instantiated
|
|
277
283
|
* @param {String} [options.instance] Name of the created instance. If not
|
|
278
|
-
* provided an
|
|
284
|
+
* provided an id will be generated
|
|
279
285
|
* @param {Array} [options.args] Positional arguments for the constructor call
|
|
280
286
|
* @param {String} [options.agent] Agent name. If not provided class default
|
|
281
287
|
* is used
|
|
282
|
-
* @param {bool} [options.cacheProxy=false] If true the proxy object for a
|
|
283
|
-
* instance is cached and used in subsequent calls
|
|
284
|
-
* @
|
|
285
|
-
*
|
|
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
|
|
286
294
|
* @example
|
|
287
|
-
* // create
|
|
295
|
+
* // create isolated instance
|
|
288
296
|
* const proxy1 = await client.create({
|
|
289
|
-
* className: 'Foo'
|
|
297
|
+
* className: 'Foo',
|
|
298
|
+
* instance: 'myPersonalInstance',
|
|
299
|
+
* isIsolated: true
|
|
290
300
|
* })
|
|
291
|
-
* // create
|
|
301
|
+
* // create shared instance
|
|
292
302
|
* const proxy2 = await client.create({
|
|
293
303
|
* className: 'Foo',
|
|
294
|
-
* instance: '
|
|
304
|
+
* instance: 'aSharedFooInstance'
|
|
295
305
|
* })
|
|
296
|
-
* // create
|
|
306
|
+
* // create shared instance providing three constructor arguments
|
|
297
307
|
* const proxy3 = await client.create({
|
|
298
308
|
* className: 'Bar',
|
|
299
|
-
* instance: '
|
|
309
|
+
* instance: 'mySharedBarInstance',
|
|
300
310
|
* args: [42, "second argument", { some: 'option' }]
|
|
301
311
|
* })
|
|
302
312
|
*/
|
|
303
313
|
async create ({
|
|
304
314
|
className,
|
|
305
|
-
instance,
|
|
315
|
+
instance = nanoid(8),
|
|
306
316
|
args = [],
|
|
307
317
|
agent = this._agent,
|
|
308
|
-
cacheProxy = false
|
|
318
|
+
cacheProxy = false,
|
|
319
|
+
isIsolated = false
|
|
309
320
|
} = {}) {
|
|
310
321
|
if (agent === '*') throw new Error('Agent must be specified')
|
|
311
|
-
const data = instance ? { _1: instance } : {}
|
|
312
|
-
const offset = instance ? 2 : 1
|
|
313
|
-
args.forEach((value, index) => {
|
|
314
|
-
data[`_${index + offset}`] = value
|
|
315
|
-
})
|
|
316
322
|
const json = {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
323
|
+
c: className,
|
|
324
|
+
f: isIsolated ? '__createIsolated__' : '__createShared__',
|
|
325
|
+
a: [instance, ...args],
|
|
326
|
+
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
327
|
+
s: `${this._domain}/${os.hostname()}/${this._instance}`,
|
|
328
|
+
v: VRPC_PROTOCOL_VERSION
|
|
322
329
|
}
|
|
323
330
|
const proxy = await this._getProxy(agent, className, json)
|
|
324
331
|
if (instance && cacheProxy) this._proxies[instance] = proxy
|
|
@@ -334,37 +341,15 @@ class VrpcRemote extends EventEmitter {
|
|
|
334
341
|
*
|
|
335
342
|
* @param {String} instance The instance to be retrieved
|
|
336
343
|
* @param {Object} [options] Explicitly define agent and class
|
|
337
|
-
* @param {String} options.className Name of the instance's class
|
|
338
|
-
* @param {String} options.agent Agent name. If not provided class default is used
|
|
339
|
-
* @param {bool} options.noWait If true immediately fail if instance could not be found in local cache
|
|
344
|
+
* @param {String} [options.className] Name of the instance's class
|
|
345
|
+
* @param {String} [options.agent] Agent name. If not provided class default is used as priority hit
|
|
346
|
+
* @param {bool} [options.noWait=false] If true immediately fail if instance could not be found in local cache
|
|
340
347
|
* @returns {Promise<Proxy>} Proxy object reflecting the remotely existing instance
|
|
341
348
|
*/
|
|
342
|
-
async getInstance (instance, options) {
|
|
349
|
+
async getInstance (instance, options = {}) {
|
|
343
350
|
if (this._proxies[instance]) return this._proxies[instance]
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
agent,
|
|
347
|
-
className,
|
|
348
|
-
instance: instanceString
|
|
349
|
-
} = await this._getInstanceData(instance, options)
|
|
350
|
-
return this._createProxy(agent, className, instanceString)
|
|
351
|
-
} else {
|
|
352
|
-
// deprecate this
|
|
353
|
-
this._indicateDeprecation(
|
|
354
|
-
'VrpcRemote.getInstance(): ' +
|
|
355
|
-
'Using an object as single argument is deprecated, ' +
|
|
356
|
-
'use "getInstance(instance, options)" instead.'
|
|
357
|
-
)
|
|
358
|
-
const fakedOptions = { ...instance }
|
|
359
|
-
const fakedInstance = fakedOptions.instance
|
|
360
|
-
delete fakedOptions.instance
|
|
361
|
-
const {
|
|
362
|
-
agent,
|
|
363
|
-
className,
|
|
364
|
-
instance: instanceString
|
|
365
|
-
} = await this._getInstanceData(fakedInstance, fakedOptions)
|
|
366
|
-
return this._createProxy(agent, className, instanceString)
|
|
367
|
-
}
|
|
351
|
+
const { agent, className } = await this._getInstanceData(instance, options)
|
|
352
|
+
return this._createProxy(agent, className, instance)
|
|
368
353
|
}
|
|
369
354
|
|
|
370
355
|
/**
|
|
@@ -376,35 +361,23 @@ class VrpcRemote extends EventEmitter {
|
|
|
376
361
|
* @param {String} instance The instance to be deleted
|
|
377
362
|
* @param {Object} [options] Explicitly define agent and class
|
|
378
363
|
* @param {String} options.className Name of the instance's class
|
|
379
|
-
* @param {String} options.agent Agent name. If not provided class default is used
|
|
364
|
+
* @param {String} options.agent Agent name. If not provided class default is used as priority hit
|
|
380
365
|
* @returns {Promise<Boolean>} true if successful, false otherwise
|
|
381
366
|
*/
|
|
382
|
-
async delete (instance, options) {
|
|
383
|
-
|
|
384
|
-
if (typeof instance === 'string') {
|
|
385
|
-
instanceData = await this._getInstanceData(instance)
|
|
386
|
-
if (options) instanceData = { ...options, ...instanceData }
|
|
387
|
-
} else {
|
|
388
|
-
// deprecate this
|
|
389
|
-
this._indicateDeprecation(
|
|
390
|
-
'VrpcRemote.delete(): ' +
|
|
391
|
-
'Using an object as single argument is deprecated, ' +
|
|
392
|
-
'use "delete(instance, options)" instead.'
|
|
393
|
-
)
|
|
394
|
-
instanceData = { ...instanceData, ...instance }
|
|
395
|
-
}
|
|
396
|
-
const { agent, className, instance: instanceString } = instanceData
|
|
367
|
+
async delete (instance, options = {}) {
|
|
368
|
+
const { agent, className } = await this._getInstanceData(instance, options)
|
|
397
369
|
const json = {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
370
|
+
c: className,
|
|
371
|
+
f: '__delete__',
|
|
372
|
+
a: [instance],
|
|
373
|
+
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
374
|
+
s: `${this._domain}/${os.hostname()}/${this._instance}`,
|
|
375
|
+
v: VRPC_PROTOCOL_VERSION
|
|
403
376
|
}
|
|
404
377
|
const topic = `${this._domain}/${agent}/${className}/__static__/__delete__`
|
|
405
378
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
406
379
|
if (this._proxies[instance]) delete this._proxies[instance]
|
|
407
|
-
return this._handleAgentAnswer(json)
|
|
380
|
+
return this._handleAgentAnswer(json, agent)
|
|
408
381
|
}
|
|
409
382
|
|
|
410
383
|
/**
|
|
@@ -423,19 +396,25 @@ class VrpcRemote extends EventEmitter {
|
|
|
423
396
|
args = [],
|
|
424
397
|
agent = this._agent
|
|
425
398
|
} = {}) {
|
|
426
|
-
const wrapped = this._wrapArguments(
|
|
399
|
+
const wrapped = this._wrapArguments({
|
|
400
|
+
agent,
|
|
401
|
+
className,
|
|
402
|
+
functionName,
|
|
403
|
+
args
|
|
404
|
+
})
|
|
427
405
|
if (!wrapped) return // Skipping remote call -> handled locally
|
|
428
406
|
const json = {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
407
|
+
c: className,
|
|
408
|
+
f: functionName,
|
|
409
|
+
a: wrapped,
|
|
410
|
+
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
411
|
+
s: this._vrpcClientId,
|
|
412
|
+
v: VRPC_PROTOCOL_VERSION
|
|
434
413
|
}
|
|
435
414
|
const topic = `${this._domain}/${agent}/${className}/__static__/${functionName}`
|
|
436
415
|
await this._waitUntilClassIsOnline(agent, className)
|
|
437
416
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
438
|
-
return this._handleAgentAnswer(json)
|
|
417
|
+
return this._handleAgentAnswer(json, agent)
|
|
439
418
|
}
|
|
440
419
|
|
|
441
420
|
/**
|
|
@@ -465,67 +444,45 @@ class VrpcRemote extends EventEmitter {
|
|
|
465
444
|
agent = this._agent
|
|
466
445
|
} = {}) {
|
|
467
446
|
const json = {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
447
|
+
c: className,
|
|
448
|
+
f: functionName,
|
|
449
|
+
a: [],
|
|
450
|
+
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
451
|
+
s: this._vrpcClientId,
|
|
452
|
+
v: VRPC_PROTOCOL_VERSION
|
|
472
453
|
}
|
|
473
454
|
if (agent === '*') {
|
|
474
455
|
const result = []
|
|
475
456
|
const agents = this.getAvailableAgents()
|
|
476
|
-
for (const
|
|
477
|
-
const topic = `${this._domain}/${
|
|
478
|
-
const wrapped = this._wrapArguments(
|
|
479
|
-
|
|
457
|
+
for (const agent of agents) {
|
|
458
|
+
const topic = `${this._domain}/${agent}/${className}/__static__/__callAll__`
|
|
459
|
+
const wrapped = this._wrapArguments({
|
|
460
|
+
agent,
|
|
461
|
+
className,
|
|
480
462
|
functionName,
|
|
481
|
-
|
|
482
|
-
)
|
|
463
|
+
args
|
|
464
|
+
})
|
|
483
465
|
if (!wrapped) continue // using cache
|
|
484
|
-
json.
|
|
485
|
-
await this._waitUntilClassIsOnline(
|
|
466
|
+
json.a = [functionName, ...wrapped]
|
|
467
|
+
await this._waitUntilClassIsOnline(agent, className)
|
|
486
468
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
487
|
-
const tmp = await this._handleAgentAnswer(json)
|
|
469
|
+
const tmp = await this._handleAgentAnswer(json, agent)
|
|
488
470
|
result.push(...tmp)
|
|
489
471
|
}
|
|
490
472
|
return result
|
|
491
473
|
}
|
|
492
474
|
const topic = `${this._domain}/${agent}/${className}/__static__/__callAll__`
|
|
493
|
-
const wrapped = this._wrapArguments(
|
|
494
|
-
|
|
475
|
+
const wrapped = this._wrapArguments({
|
|
476
|
+
agent,
|
|
477
|
+
className,
|
|
495
478
|
functionName,
|
|
496
|
-
|
|
497
|
-
)
|
|
479
|
+
args
|
|
480
|
+
})
|
|
498
481
|
if (!wrapped) return // using cache
|
|
499
|
-
json.
|
|
482
|
+
json.a = [functionName, ...wrapped]
|
|
500
483
|
await this._waitUntilClassIsOnline(agent, className)
|
|
501
484
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
502
|
-
return this._handleAgentAnswer(json)
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* Retrieves all agents, instances, classes, member and static
|
|
507
|
-
* functions potentially available for remote control.
|
|
508
|
-
* @deprecated
|
|
509
|
-
* @returns {Object} SystemInformation
|
|
510
|
-
* ```ts
|
|
511
|
-
* type SystemInformation = {
|
|
512
|
-
* [domain].agents[agent].status: string, // 'offline' or 'online'
|
|
513
|
-
* [domain].agents[agent].hostname: string,
|
|
514
|
-
* [domain].agents[agent].version: string,
|
|
515
|
-
* [domain].agents[agent].classes[className].instances: string[],
|
|
516
|
-
* [domain].agents[agent].classes[className].memberFunctions: string[],
|
|
517
|
-
* [domain].agents[agent].classes[className].staticFunctions: string[],
|
|
518
|
-
* [domain].agents[agent].classes[className].meta?: MetaData
|
|
519
|
-
* }
|
|
520
|
-
* ```
|
|
521
|
-
*/
|
|
522
|
-
getAvailabilities () {
|
|
523
|
-
this._indicateDeprecation(
|
|
524
|
-
'VrpcRemote.getAvailabilities(): ' +
|
|
525
|
-
'This function is deprecated and will be removed soon, ' +
|
|
526
|
-
'use `getSystemInformation()` instead.'
|
|
527
|
-
)
|
|
528
|
-
return { [this._domain]: { agents: this._agents } }
|
|
485
|
+
return this._handleAgentAnswer(json, agent)
|
|
529
486
|
}
|
|
530
487
|
|
|
531
488
|
/**
|
|
@@ -548,21 +505,6 @@ class VrpcRemote extends EventEmitter {
|
|
|
548
505
|
return this._agents
|
|
549
506
|
}
|
|
550
507
|
|
|
551
|
-
/**
|
|
552
|
-
* Retrieves all domains on which agents can be remote controlled
|
|
553
|
-
*
|
|
554
|
-
* @deprecated
|
|
555
|
-
* @returns {Array} Array of domain names
|
|
556
|
-
*/
|
|
557
|
-
getAvailableDomains () {
|
|
558
|
-
this._indicateDeprecation(
|
|
559
|
-
'VrpcRemote.getAvailableDomains(): ' +
|
|
560
|
-
'this function will be removed in the next major release, as the client' +
|
|
561
|
-
'works with a single domain anyways'
|
|
562
|
-
)
|
|
563
|
-
return [this._domain]
|
|
564
|
-
}
|
|
565
|
-
|
|
566
508
|
/**
|
|
567
509
|
* Retrieves all available agents.
|
|
568
510
|
*
|
|
@@ -570,19 +512,7 @@ class VrpcRemote extends EventEmitter {
|
|
|
570
512
|
* @param {Boolean} [options.mustBeOnline=true] Only retrieve currently online agents
|
|
571
513
|
* @returns {Array} Array of agent names.
|
|
572
514
|
*/
|
|
573
|
-
getAvailableAgents (
|
|
574
|
-
/// DEPRECATION INFORMATION AND WORKAROUND - TO BE REMOVED IN 3.x
|
|
575
|
-
// `options = {}` was `domain = this._domain` before
|
|
576
|
-
if (typeof options === 'string') {
|
|
577
|
-
this._indicateDeprecation(
|
|
578
|
-
'VrpcRemote.getAvailableAgents(): ' +
|
|
579
|
-
'Providing a domain here has no effect and will be an error in future.'
|
|
580
|
-
)
|
|
581
|
-
options = { mustBeOnline: true }
|
|
582
|
-
}
|
|
583
|
-
///
|
|
584
|
-
|
|
585
|
-
const { mustBeOnline = true } = options
|
|
515
|
+
getAvailableAgents ({ mustBeOnline = true } = {}) {
|
|
586
516
|
const agents = []
|
|
587
517
|
if (mustBeOnline) {
|
|
588
518
|
// loop agents
|
|
@@ -603,34 +533,7 @@ class VrpcRemote extends EventEmitter {
|
|
|
603
533
|
* @param {Boolean} [options.mustBeOnline=true] Only retrieve currently online classes
|
|
604
534
|
* @returns {Array} Array of class names.
|
|
605
535
|
*/
|
|
606
|
-
getAvailableClasses (
|
|
607
|
-
options = { agent: this._agent, mustBeOnline: true },
|
|
608
|
-
domain
|
|
609
|
-
) {
|
|
610
|
-
/// DEPRECATION INFORMATION AND WORKAROUND - TO BE REMOVED IN 3.x
|
|
611
|
-
let agent = this._agent
|
|
612
|
-
let mustBeOnline = true
|
|
613
|
-
// `options = {}` was `agent = this._agent` before
|
|
614
|
-
if (typeof options === 'string') {
|
|
615
|
-
this._indicateDeprecation(
|
|
616
|
-
'VrpcRemote.getAvailableClasses(): ' +
|
|
617
|
-
'setting the agent as string argument is deprecated.`' +
|
|
618
|
-
'Use `{ agent: <myAgent> }` instead.'
|
|
619
|
-
)
|
|
620
|
-
agent = options
|
|
621
|
-
} else {
|
|
622
|
-
agent = options.agent
|
|
623
|
-
mustBeOnline = options.mustBeOnline
|
|
624
|
-
}
|
|
625
|
-
// was `domain = this._domain` before
|
|
626
|
-
if (domain) {
|
|
627
|
-
this._indicateDeprecation(
|
|
628
|
-
'VrpcRemote.getAvailableClasses(): ' +
|
|
629
|
-
'Providing a domain here has no effect and will be an error in future.'
|
|
630
|
-
)
|
|
631
|
-
}
|
|
632
|
-
///
|
|
633
|
-
|
|
536
|
+
getAvailableClasses ({ agent = this._agent, mustBeOnline = true } = {}) {
|
|
634
537
|
if (agent === '*') throw new Error('Agent must be specified')
|
|
635
538
|
if (!this._agents[agent]) return []
|
|
636
539
|
const { classes, status } = this._agents[agent]
|
|
@@ -639,43 +542,19 @@ class VrpcRemote extends EventEmitter {
|
|
|
639
542
|
}
|
|
640
543
|
|
|
641
544
|
/**
|
|
642
|
-
* Retrieves all (
|
|
545
|
+
* Retrieves all (shared) instances on specific class and agent.
|
|
643
546
|
*
|
|
644
|
-
* @param {String} className Class name
|
|
645
547
|
* @param {Object} [options]
|
|
548
|
+
* @param {String} options.className Class name
|
|
646
549
|
* @param {String} [options.agent] Agent name. If not provided class default is used
|
|
647
550
|
* @param {Boolean} [options.mustBeOnline=true] Only retrieve currently online classes
|
|
648
551
|
* @returns {Array} Array of instance names
|
|
649
552
|
*/
|
|
650
|
-
getAvailableInstances (
|
|
553
|
+
getAvailableInstances ({
|
|
651
554
|
className,
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
) {
|
|
655
|
-
/// DEPRECATION INFORMATION AND WORKAROUND - TO BE REMOVED IN 3.x
|
|
656
|
-
let agent = this._agent
|
|
657
|
-
let mustBeOnline = true
|
|
658
|
-
// `options = {}` was `agent = this._agent` before
|
|
659
|
-
if (typeof options === 'string') {
|
|
660
|
-
this._indicateDeprecation(
|
|
661
|
-
'VrpcRemote.getAvailableInstances(): ' +
|
|
662
|
-
'Setting the agent as string argument is deprecated.`' +
|
|
663
|
-
'Use `{ agent: <myAgent> }` instead.'
|
|
664
|
-
)
|
|
665
|
-
agent = options
|
|
666
|
-
} else {
|
|
667
|
-
agent = options.agent
|
|
668
|
-
mustBeOnline = options.mustBeOnline
|
|
669
|
-
}
|
|
670
|
-
// was `domain = this._domain` before
|
|
671
|
-
if (domain) {
|
|
672
|
-
this._indicateDeprecation(
|
|
673
|
-
'VrpcRemote.getAvailableInstances(): ' +
|
|
674
|
-
'Providing a domain here has no effect and will be an error in future.'
|
|
675
|
-
)
|
|
676
|
-
}
|
|
677
|
-
///
|
|
678
|
-
|
|
555
|
+
agent = this._agent,
|
|
556
|
+
mustBeOnline = true
|
|
557
|
+
} = {}) {
|
|
679
558
|
if (agent === '*') throw new Error('Agent must be specified')
|
|
680
559
|
if (!this._agents[agent]) return []
|
|
681
560
|
const { classes, status } = this._agents[agent]
|
|
@@ -686,41 +565,17 @@ class VrpcRemote extends EventEmitter {
|
|
|
686
565
|
/**
|
|
687
566
|
* Retrieves all member functions of specific class and agent.
|
|
688
567
|
*
|
|
689
|
-
* @param {String} className Class name
|
|
690
568
|
* @param {Object} [options]
|
|
569
|
+
* @param {String} options.className Class name
|
|
691
570
|
* @param {String} [options.agent] Agent name. If not provided class default is used
|
|
692
571
|
* @param {Boolean} [options.mustBeOnline=true] Only retrieve currently online classes
|
|
693
572
|
* @returns {Array} Array of member function names
|
|
694
573
|
*/
|
|
695
|
-
getAvailableMemberFunctions (
|
|
574
|
+
getAvailableMemberFunctions ({
|
|
696
575
|
className,
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
) {
|
|
700
|
-
/// DEPRECATION INFORMATION AND WORKAROUND - TO BE REMOVED IN 3.x
|
|
701
|
-
let agent = this._agent
|
|
702
|
-
let mustBeOnline = true
|
|
703
|
-
// `options = {}` was `agent = this._agent` before
|
|
704
|
-
if (typeof options === 'string') {
|
|
705
|
-
this._indicateDeprecation(
|
|
706
|
-
'VrpcRemote.getAvailableMemberFunctions(): ' +
|
|
707
|
-
'Setting the agent as string argument is deprecated.`' +
|
|
708
|
-
'Use `{ agent: <myAgent> }` instead.'
|
|
709
|
-
)
|
|
710
|
-
agent = options
|
|
711
|
-
} else {
|
|
712
|
-
agent = options.agent
|
|
713
|
-
mustBeOnline = options.mustBeOnline
|
|
714
|
-
}
|
|
715
|
-
// was `domain = this._domain` before
|
|
716
|
-
if (domain) {
|
|
717
|
-
this._indicateDeprecation(
|
|
718
|
-
'VrpcRemote.getAvailableMemberFunctions(): ' +
|
|
719
|
-
'Providing a domain here has no effect and will be an error in future.'
|
|
720
|
-
)
|
|
721
|
-
}
|
|
722
|
-
///
|
|
723
|
-
|
|
576
|
+
agent = this._agent,
|
|
577
|
+
mustBeOnline = true
|
|
578
|
+
} = {}) {
|
|
724
579
|
if (agent === '*') throw new Error('Agent must be specified')
|
|
725
580
|
if (!this._agents[agent]) return []
|
|
726
581
|
const { classes, status } = this._agents[agent]
|
|
@@ -733,41 +588,17 @@ class VrpcRemote extends EventEmitter {
|
|
|
733
588
|
/**
|
|
734
589
|
* Retrieves all static functions of specific class and agent.
|
|
735
590
|
*
|
|
736
|
-
* @param {String} className Class name
|
|
737
591
|
* @param {Object} [options]
|
|
592
|
+
* @param {String} options.className Class name
|
|
738
593
|
* @param {String} [options.agent] Agent name. If not provided class default is used
|
|
739
594
|
* @param {Boolean} [options.mustBeOnline=true] Only retrieve currently online classes
|
|
740
595
|
* @returns {Array} Array of static function names
|
|
741
596
|
*/
|
|
742
|
-
getAvailableStaticFunctions (
|
|
597
|
+
getAvailableStaticFunctions ({
|
|
743
598
|
className,
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
) {
|
|
747
|
-
/// DEPRECATION INFORMATION AND WORKAROUND - TO BE REMOVED IN 3.x
|
|
748
|
-
let agent = this._agent
|
|
749
|
-
let mustBeOnline = true
|
|
750
|
-
// `options = {}` was `agent = this._agent` before
|
|
751
|
-
if (typeof options === 'string') {
|
|
752
|
-
this._indicateDeprecation(
|
|
753
|
-
'VrpcRemote.getAvailableStaticFunctions(): ' +
|
|
754
|
-
'Setting the agent as string argument is deprecated.`' +
|
|
755
|
-
'Use `{ agent: <myAgent> }` instead.'
|
|
756
|
-
)
|
|
757
|
-
agent = options
|
|
758
|
-
} else {
|
|
759
|
-
agent = options.agent
|
|
760
|
-
mustBeOnline = options.mustBeOnline
|
|
761
|
-
}
|
|
762
|
-
// was `domain = this._domain` before
|
|
763
|
-
if (domain) {
|
|
764
|
-
this._indicateDeprecation(
|
|
765
|
-
'VrpcRemote.getAvailableStaticFunctions(): ' +
|
|
766
|
-
'Providing a domain here has no effect and will be an error in future.'
|
|
767
|
-
)
|
|
768
|
-
}
|
|
769
|
-
///
|
|
770
|
-
|
|
599
|
+
agent = this._agent,
|
|
600
|
+
mustBeOnline = true
|
|
601
|
+
} = {}) {
|
|
771
602
|
if (agent === '*') throw new Error('Agent must be specified')
|
|
772
603
|
if (!this._agents[agent]) return []
|
|
773
604
|
const { classes, status } = this._agents[agent]
|
|
@@ -824,7 +655,7 @@ class VrpcRemote extends EventEmitter {
|
|
|
824
655
|
async end () {
|
|
825
656
|
this._mqttPublish(
|
|
826
657
|
`${this._vrpcClientId}/__clientInfo__`,
|
|
827
|
-
JSON.stringify({ status: 'offline' })
|
|
658
|
+
JSON.stringify({ status: 'offline', v: VRPC_PROTOCOL_VERSION })
|
|
828
659
|
)
|
|
829
660
|
return new Promise(resolve => this._client.end(false, {}, resolve))
|
|
830
661
|
}
|
|
@@ -845,9 +676,8 @@ class VrpcRemote extends EventEmitter {
|
|
|
845
676
|
.createHash('md5')
|
|
846
677
|
.update(clientInfo)
|
|
847
678
|
.digest('hex')
|
|
848
|
-
.
|
|
849
|
-
//
|
|
850
|
-
return `vrpcp${this._instance}X${md5}` // 5 + 4 + 1 + 13 = 23 (max clientId)
|
|
679
|
+
.substring(0, 12)
|
|
680
|
+
return `vc3${this._instance}${md5}` // 3 + 8 + 12 = 23 (max clientId)
|
|
851
681
|
}
|
|
852
682
|
|
|
853
683
|
_createVrpcClientId () {
|
|
@@ -904,22 +734,22 @@ class VrpcRemote extends EventEmitter {
|
|
|
904
734
|
}
|
|
905
735
|
|
|
906
736
|
async _getProxy (agent, className, json) {
|
|
907
|
-
const {
|
|
908
|
-
const topic = `${this._domain}/${agent}/${className}/__static__/${
|
|
737
|
+
const { f, i } = json
|
|
738
|
+
const topic = `${this._domain}/${agent}/${className}/__static__/${f}`
|
|
909
739
|
await this._waitUntilClassIsOnline(agent, className)
|
|
910
740
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
911
741
|
return new Promise((resolve, reject) => {
|
|
912
742
|
const msg = `Proxy creation for class "${className}" on agent "${agent}" and domain "${this._domain}" timed out (> ${this._timeout} ms)`
|
|
913
743
|
const timer = setTimeout(() => {
|
|
914
|
-
this._eventEmitter.removeAllListeners(
|
|
744
|
+
this._eventEmitter.removeAllListeners(i)
|
|
915
745
|
reject(new Error(msg))
|
|
916
746
|
}, this._timeout)
|
|
917
|
-
this._eventEmitter.once(
|
|
747
|
+
this._eventEmitter.once(i, ({ e, r }) => {
|
|
918
748
|
clearTimeout(timer)
|
|
919
|
-
if (
|
|
920
|
-
reject(new Error(
|
|
749
|
+
if (e) {
|
|
750
|
+
reject(new Error(e))
|
|
921
751
|
} else {
|
|
922
|
-
const proxy = this._createProxy(agent, className,
|
|
752
|
+
const proxy = this._createProxy(agent, className, r)
|
|
923
753
|
resolve(proxy)
|
|
924
754
|
}
|
|
925
755
|
})
|
|
@@ -961,8 +791,9 @@ class VrpcRemote extends EventEmitter {
|
|
|
961
791
|
const targetTopic = `${this._domain}/${agent}/${className}/${instance}`
|
|
962
792
|
const proxyId = `${this._instance}-${this._proxyId++}`
|
|
963
793
|
const proxy = {
|
|
964
|
-
|
|
965
|
-
|
|
794
|
+
vrpcClientId: this._vrpcClientId,
|
|
795
|
+
vrpcInstanceId: instance,
|
|
796
|
+
vrpcProxyId: proxyId
|
|
966
797
|
}
|
|
967
798
|
let functions = this._agents[agent].classes[className].memberFunctions
|
|
968
799
|
// Strip off argument signature
|
|
@@ -974,48 +805,135 @@ class VrpcRemote extends EventEmitter {
|
|
|
974
805
|
// Remove overloads
|
|
975
806
|
const uniqueFuncs = new Set(functions)
|
|
976
807
|
// Build proxy
|
|
977
|
-
uniqueFuncs.forEach(
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
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
|
|
981
855
|
try {
|
|
982
856
|
const json = {
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
857
|
+
c: instance,
|
|
858
|
+
f: functionName,
|
|
859
|
+
a: wrapped,
|
|
860
|
+
i: `${this._instance}-${this._invokeId++ %
|
|
986
861
|
Number.MAX_SAFE_INTEGER}`,
|
|
987
|
-
|
|
988
|
-
|
|
862
|
+
s: this._vrpcClientId,
|
|
863
|
+
v: VRPC_PROTOCOL_VERSION
|
|
989
864
|
}
|
|
990
|
-
this._mqttPublish(
|
|
991
|
-
|
|
865
|
+
this._mqttPublish(
|
|
866
|
+
`${targetTopic}/${functionName}`,
|
|
867
|
+
JSON.stringify(json)
|
|
868
|
+
)
|
|
869
|
+
return this._handleAgentAnswer(json, agent)
|
|
992
870
|
} catch (err) {
|
|
993
871
|
throw new Error(
|
|
994
|
-
`Could not remotely call "${
|
|
872
|
+
`Could not remotely call "${functionName}" because: ${err.message}`
|
|
995
873
|
)
|
|
996
874
|
}
|
|
997
875
|
}
|
|
998
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
|
+
}
|
|
999
916
|
return proxy
|
|
1000
917
|
}
|
|
1001
918
|
|
|
1002
|
-
async _handleAgentAnswer ({
|
|
919
|
+
async _handleAgentAnswer ({ i, c, f }, agent) {
|
|
1003
920
|
return new Promise((resolve, reject) => {
|
|
1004
|
-
const msg = `Function call "${
|
|
921
|
+
const msg = `Function call "${c}::${f}()" on agent "${agent}" timed out (> ${this._timeout} ms)`
|
|
1005
922
|
const timer = setTimeout(() => {
|
|
1006
|
-
this._eventEmitter.removeAllListeners(
|
|
923
|
+
this._eventEmitter.removeAllListeners(i)
|
|
1007
924
|
reject(new Error(msg))
|
|
1008
925
|
}, this._timeout)
|
|
1009
|
-
this._eventEmitter.once(
|
|
926
|
+
this._eventEmitter.once(i, data => {
|
|
1010
927
|
clearTimeout(timer)
|
|
1011
928
|
if (data.e) {
|
|
1012
|
-
reject(new Error(data.e))
|
|
929
|
+
reject(new Error(`[vrpc ${agent}-${c}-${f}]: ${data.e}`))
|
|
1013
930
|
} else {
|
|
1014
931
|
const ret = data.r
|
|
1015
932
|
// Handle functions returning a promise
|
|
1016
|
-
if (typeof ret === 'string' && ret.
|
|
933
|
+
if (typeof ret === 'string' && ret.substring(0, 5) === '__p__') {
|
|
1017
934
|
this._eventEmitter.once(ret, promiseData => {
|
|
1018
|
-
|
|
935
|
+
// TODO improve error message here, like above
|
|
936
|
+
if (promiseData.e) reject(new Error(`[vrpc ${agent}-${c}-${f}]: ${promiseData.e}`))
|
|
1019
937
|
else resolve(promiseData.r)
|
|
1020
938
|
})
|
|
1021
939
|
} else {
|
|
@@ -1046,91 +964,128 @@ class VrpcRemote extends EventEmitter {
|
|
|
1046
964
|
})
|
|
1047
965
|
}
|
|
1048
966
|
|
|
1049
|
-
_wrapArguments (
|
|
967
|
+
_wrapArguments ({ agent, className, instance, proxyId, functionName, args }) {
|
|
1050
968
|
const wrapped = []
|
|
1051
|
-
|
|
1052
|
-
args.forEach((x, i) => {
|
|
969
|
+
for (const [i, x] of args.entries()) {
|
|
1053
970
|
// Check whether provided argument is a function
|
|
1054
971
|
if (this._isFunction(x)) {
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
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
|
|
1063
992
|
wrapped.push(id)
|
|
993
|
+
continue
|
|
1064
994
|
} else if (
|
|
1065
995
|
(functionName === 'off' || functionName === 'removeListener') &&
|
|
1066
996
|
i === 1 &&
|
|
1067
997
|
typeof args[0] === 'string'
|
|
1068
998
|
) {
|
|
1069
|
-
const
|
|
1070
|
-
|
|
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
|
|
1071
1009
|
wrapped.push(id)
|
|
1072
|
-
|
|
1010
|
+
continue
|
|
1073
1011
|
} else {
|
|
1012
|
+
// Regular function callback (can be static or member function)
|
|
1013
|
+
const remoteId = proxyId || `${agent}-${className}`
|
|
1074
1014
|
const id = `__f__${remoteId}-${functionName}-${i}-${this._invokeId++ %
|
|
1075
1015
|
Number.MAX_SAFE_INTEGER}`
|
|
1076
1016
|
wrapped.push(id)
|
|
1077
|
-
this._eventEmitter.once(id,
|
|
1078
|
-
|
|
1079
|
-
.sort()
|
|
1080
|
-
.filter(x => x[0] === '_')
|
|
1081
|
-
.map(x => data[x])
|
|
1082
|
-
x.apply(null, args) // This is the actual function call
|
|
1083
|
-
})
|
|
1017
|
+
this._eventEmitter.once(id, ({ a }) => x.apply(null, a))
|
|
1018
|
+
continue
|
|
1084
1019
|
}
|
|
1085
|
-
}
|
|
1020
|
+
}
|
|
1021
|
+
// special case of an EventEmitter provided as argument
|
|
1022
|
+
if (this._isEmitter(x)) {
|
|
1086
1023
|
const { emitter, event } = x
|
|
1087
|
-
const id =
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
.map(x => data[x])
|
|
1094
|
-
emitter.emit(event, ...args)
|
|
1024
|
+
const id = this._onRemoteEvent({
|
|
1025
|
+
agent,
|
|
1026
|
+
className,
|
|
1027
|
+
instance,
|
|
1028
|
+
event,
|
|
1029
|
+
callback: (...args) => emitter.emit(event, ...args)
|
|
1095
1030
|
})
|
|
1096
|
-
|
|
1097
|
-
|
|
1031
|
+
// indicate that no network call is required
|
|
1032
|
+
if (id === null) return null
|
|
1033
|
+
wrapped.push(id)
|
|
1034
|
+
continue
|
|
1098
1035
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1036
|
+
// default behavior is to not touch
|
|
1037
|
+
wrapped.push(x)
|
|
1038
|
+
}
|
|
1101
1039
|
return wrapped
|
|
1102
1040
|
}
|
|
1103
1041
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
this.
|
|
1108
|
-
|
|
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
|
|
1109
1058
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
const args = Object.keys(data)
|
|
1115
|
-
.sort()
|
|
1116
|
-
.filter(x => x[0] === '_')
|
|
1117
|
-
.map(x => data[x])
|
|
1118
|
-
emitter.emit(id, ...args)
|
|
1119
|
-
})
|
|
1120
|
-
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
|
|
1121
1063
|
}
|
|
1122
1064
|
|
|
1123
|
-
|
|
1124
|
-
const
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
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]
|
|
1131
1081
|
return id
|
|
1132
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}`)
|
|
1133
1087
|
}
|
|
1088
|
+
return id
|
|
1134
1089
|
}
|
|
1135
1090
|
|
|
1136
1091
|
_stripSignature (method) {
|
|
@@ -1158,7 +1113,7 @@ class VrpcRemote extends EventEmitter {
|
|
|
1158
1113
|
)
|
|
1159
1114
|
}
|
|
1160
1115
|
|
|
1161
|
-
async _getInstanceData (instance, options
|
|
1116
|
+
async _getInstanceData (instance, options) {
|
|
1162
1117
|
const instanceData = this._getInstanceFromCache(instance, options)
|
|
1163
1118
|
if (!instanceData) {
|
|
1164
1119
|
if (options.noWait) {
|
|
@@ -1170,6 +1125,21 @@ class VrpcRemote extends EventEmitter {
|
|
|
1170
1125
|
}
|
|
1171
1126
|
|
|
1172
1127
|
_getInstanceFromCache (instance, options = {}) {
|
|
1128
|
+
// when no agent is specified, but an explicit class default exists give
|
|
1129
|
+
// such instances priority in being found
|
|
1130
|
+
if (!options.agent && this._agent !== '*') {
|
|
1131
|
+
const { classes, status } = this._agents[this._agent]
|
|
1132
|
+
if (!classes || status === 'offline') {
|
|
1133
|
+
for (const className in classes) {
|
|
1134
|
+
if (options.className && className !== options.className) continue
|
|
1135
|
+
const { instances } = classes[className]
|
|
1136
|
+
if (!instances) continue
|
|
1137
|
+
if (instances.includes(instance)) {
|
|
1138
|
+
return { agent, className, instance }
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1173
1143
|
// loop agents
|
|
1174
1144
|
for (const agent in this._agents) {
|
|
1175
1145
|
if (options.agent && agent !== options.agent) continue
|
|
@@ -1194,14 +1164,6 @@ class VrpcRemote extends EventEmitter {
|
|
|
1194
1164
|
this._log.warn('DEPRECATED ', msg)
|
|
1195
1165
|
}
|
|
1196
1166
|
}
|
|
1197
|
-
|
|
1198
|
-
static _argsArrayToObject (args) {
|
|
1199
|
-
const obj = {}
|
|
1200
|
-
args.forEach((x, i) => {
|
|
1201
|
-
obj[`_${i + 1}`] = x
|
|
1202
|
-
})
|
|
1203
|
-
return obj
|
|
1204
|
-
}
|
|
1205
1167
|
}
|
|
1206
1168
|
|
|
1207
1169
|
/**
|
|
@@ -1210,7 +1172,7 @@ class VrpcRemote extends EventEmitter {
|
|
|
1210
1172
|
* This event is fired whenever an agent is added or removed, or whenever
|
|
1211
1173
|
* an agent changes its status (switches between online or offline).
|
|
1212
1174
|
*
|
|
1213
|
-
* @event
|
|
1175
|
+
* @event VrpcClient#agent
|
|
1214
1176
|
* @param {Object} info
|
|
1215
1177
|
* @param {String} info.domain - Domain name
|
|
1216
1178
|
* @param {String} info.agent - Agent name
|
|
@@ -1225,12 +1187,12 @@ class VrpcRemote extends EventEmitter {
|
|
|
1225
1187
|
* Emitted whenever a class is added or removed, or when instances
|
|
1226
1188
|
* or functions of this class have changed.
|
|
1227
1189
|
*
|
|
1228
|
-
* @event
|
|
1190
|
+
* @event VrpcClient#class
|
|
1229
1191
|
* @param {Object} info
|
|
1230
1192
|
* @param {String} info.domain - Domain name
|
|
1231
1193
|
* @param {String} info.agent - Agent name
|
|
1232
1194
|
* @param {String} info.className - Class name
|
|
1233
|
-
* @param {Array.<String>} info.instances - Array of
|
|
1195
|
+
* @param {Array.<String>} info.instances - Array of instances
|
|
1234
1196
|
* @param {Array.<String>} info.memberFunctions - Array of member functions
|
|
1235
1197
|
* @param {Array.<String>} info.staticFunctions - Array of static functions
|
|
1236
1198
|
* @param {MetaData} info.meta - Object associating further information to functions
|
|
@@ -1241,7 +1203,7 @@ class VrpcRemote extends EventEmitter {
|
|
|
1241
1203
|
*
|
|
1242
1204
|
* Emitted whenever a new instance was created.
|
|
1243
1205
|
*
|
|
1244
|
-
* @event
|
|
1206
|
+
* @event VrpcClient#instanceNew
|
|
1245
1207
|
*
|
|
1246
1208
|
* @param {Array.<String>} addedInstances - An array of newly added instances
|
|
1247
1209
|
* @param {Object} info
|
|
@@ -1255,7 +1217,7 @@ class VrpcRemote extends EventEmitter {
|
|
|
1255
1217
|
*
|
|
1256
1218
|
* Emitted whenever a new instance was removed.
|
|
1257
1219
|
*
|
|
1258
|
-
* @event
|
|
1220
|
+
* @event VrpcClient#instanceGone
|
|
1259
1221
|
*
|
|
1260
1222
|
* @param {Array.<String>} removedInstances - An array of removed instances
|
|
1261
1223
|
* @param {Object} info
|
|
@@ -1269,7 +1231,7 @@ class VrpcRemote extends EventEmitter {
|
|
|
1269
1231
|
*
|
|
1270
1232
|
* Emitted on successful (re)connection (i.e. connack rc=0).
|
|
1271
1233
|
*
|
|
1272
|
-
* @event
|
|
1234
|
+
* @event VrpcClient#connect
|
|
1273
1235
|
* @type {Object}
|
|
1274
1236
|
* @property {Boolean} sessionPresent - A session from a previous connection is already present
|
|
1275
1237
|
*/
|
|
@@ -1279,7 +1241,7 @@ class VrpcRemote extends EventEmitter {
|
|
|
1279
1241
|
*
|
|
1280
1242
|
* Emitted when a reconnect starts.
|
|
1281
1243
|
*
|
|
1282
|
-
* @event
|
|
1244
|
+
* @event VrpcClient#reconnect
|
|
1283
1245
|
*/
|
|
1284
1246
|
|
|
1285
1247
|
/**
|
|
@@ -1287,7 +1249,7 @@ class VrpcRemote extends EventEmitter {
|
|
|
1287
1249
|
*
|
|
1288
1250
|
* Emitted after a disconnection.
|
|
1289
1251
|
*
|
|
1290
|
-
* @event
|
|
1252
|
+
* @event VrpcClient#close
|
|
1291
1253
|
*/
|
|
1292
1254
|
|
|
1293
1255
|
/**
|
|
@@ -1295,7 +1257,7 @@ class VrpcRemote extends EventEmitter {
|
|
|
1295
1257
|
*
|
|
1296
1258
|
* Emitted when the client goes offline.
|
|
1297
1259
|
*
|
|
1298
|
-
* @event
|
|
1260
|
+
* @event VrpcClient#offline
|
|
1299
1261
|
*/
|
|
1300
1262
|
|
|
1301
1263
|
/**
|
|
@@ -1310,7 +1272,7 @@ class VrpcRemote extends EventEmitter {
|
|
|
1310
1272
|
* - EADDRINUSE
|
|
1311
1273
|
* - ENOTFOUND
|
|
1312
1274
|
*
|
|
1313
|
-
* @event
|
|
1275
|
+
* @event VrpcClient#error
|
|
1314
1276
|
* @type {Object} Error
|
|
1315
1277
|
*/
|
|
1316
1278
|
|
|
@@ -1320,7 +1282,7 @@ class VrpcRemote extends EventEmitter {
|
|
|
1320
1282
|
* Emitted when mqtt.Client#end() is called. If a callback was passed to
|
|
1321
1283
|
* mqtt.Client#end(), this event is emitted once the callback returns.
|
|
1322
1284
|
*
|
|
1323
|
-
* @event
|
|
1285
|
+
* @event VrpcClient#end
|
|
1324
1286
|
*/
|
|
1325
1287
|
|
|
1326
|
-
module.exports =
|
|
1288
|
+
module.exports = VrpcClient
|