vrpc 3.0.0-alpha.1 → 3.0.1
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 +63 -2
- package/LICENSE +1 -1
- package/README.md +12 -15
- package/browser/vrpc.js +1 -1
- package/package.json +8 -5
- package/vrpc/VrpcAdapter.js +234 -161
- package/vrpc/VrpcAgent.js +25 -18
- package/vrpc/VrpcClient.js +305 -289
- 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)
|
|
@@ -336,37 +341,15 @@ class VrpcClient extends EventEmitter {
|
|
|
336
341
|
*
|
|
337
342
|
* @param {String} instance The instance to be retrieved
|
|
338
343
|
* @param {Object} [options] Explicitly define agent and class
|
|
339
|
-
* @param {String} options.className Name of the instance's class
|
|
340
|
-
* @param {String} options.agent Agent name. If not provided class default is used
|
|
341
|
-
* @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
|
|
342
347
|
* @returns {Promise<Proxy>} Proxy object reflecting the remotely existing instance
|
|
343
348
|
*/
|
|
344
|
-
async getInstance (instance, options) {
|
|
349
|
+
async getInstance (instance, options = {}) {
|
|
345
350
|
if (this._proxies[instance]) return this._proxies[instance]
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
agent,
|
|
349
|
-
className,
|
|
350
|
-
instance: instanceString
|
|
351
|
-
} = await this._getInstanceData(instance, options)
|
|
352
|
-
return this._createProxy(agent, className, instanceString)
|
|
353
|
-
} else {
|
|
354
|
-
// deprecate this
|
|
355
|
-
this._indicateDeprecation(
|
|
356
|
-
'VrpcClient.getInstance(): ' +
|
|
357
|
-
'Using an object as single argument is deprecated, ' +
|
|
358
|
-
'use "getInstance(instance, options)" instead.'
|
|
359
|
-
)
|
|
360
|
-
const fakedOptions = { ...instance }
|
|
361
|
-
const fakedInstance = fakedOptions.instance
|
|
362
|
-
delete fakedOptions.instance
|
|
363
|
-
const {
|
|
364
|
-
agent,
|
|
365
|
-
className,
|
|
366
|
-
instance: instanceString
|
|
367
|
-
} = await this._getInstanceData(fakedInstance, fakedOptions)
|
|
368
|
-
return this._createProxy(agent, className, instanceString)
|
|
369
|
-
}
|
|
351
|
+
const { agent, className } = await this._getInstanceData(instance, options)
|
|
352
|
+
return this._createProxy(agent, className, instance)
|
|
370
353
|
}
|
|
371
354
|
|
|
372
355
|
/**
|
|
@@ -378,36 +361,23 @@ class VrpcClient extends EventEmitter {
|
|
|
378
361
|
* @param {String} instance The instance to be deleted
|
|
379
362
|
* @param {Object} [options] Explicitly define agent and class
|
|
380
363
|
* @param {String} options.className Name of the instance's class
|
|
381
|
-
* @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
|
|
382
365
|
* @returns {Promise<Boolean>} true if successful, false otherwise
|
|
383
366
|
*/
|
|
384
|
-
async delete (instance, options) {
|
|
385
|
-
|
|
386
|
-
if (typeof instance === 'string') {
|
|
387
|
-
instanceData = await this._getInstanceData(instance)
|
|
388
|
-
if (options) instanceData = { ...options, ...instanceData }
|
|
389
|
-
} else {
|
|
390
|
-
// deprecate this
|
|
391
|
-
this._indicateDeprecation(
|
|
392
|
-
'VrpcClient.delete(): ' +
|
|
393
|
-
'Using an object as single argument is deprecated, ' +
|
|
394
|
-
'use "delete(instance, options)" instead.'
|
|
395
|
-
)
|
|
396
|
-
instanceData = { ...instanceData, ...instance }
|
|
397
|
-
}
|
|
398
|
-
const { agent, className, instance: instanceString } = instanceData
|
|
367
|
+
async delete (instance, options = {}) {
|
|
368
|
+
const { agent, className } = await this._getInstanceData(instance, options)
|
|
399
369
|
const json = {
|
|
400
370
|
c: className,
|
|
401
371
|
f: '__delete__',
|
|
372
|
+
a: [instance],
|
|
402
373
|
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
403
374
|
s: `${this._domain}/${os.hostname()}/${this._instance}`,
|
|
404
|
-
a: [instanceString],
|
|
405
375
|
v: VRPC_PROTOCOL_VERSION
|
|
406
376
|
}
|
|
407
377
|
const topic = `${this._domain}/${agent}/${className}/__static__/__delete__`
|
|
408
378
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
409
379
|
if (this._proxies[instance]) delete this._proxies[instance]
|
|
410
|
-
return this._handleAgentAnswer(json)
|
|
380
|
+
return this._handleAgentAnswer(json, agent)
|
|
411
381
|
}
|
|
412
382
|
|
|
413
383
|
/**
|
|
@@ -426,20 +396,25 @@ class VrpcClient extends EventEmitter {
|
|
|
426
396
|
args = [],
|
|
427
397
|
agent = this._agent
|
|
428
398
|
} = {}) {
|
|
429
|
-
const wrapped = this._wrapArguments(
|
|
399
|
+
const wrapped = this._wrapArguments({
|
|
400
|
+
agent,
|
|
401
|
+
className,
|
|
402
|
+
functionName,
|
|
403
|
+
args
|
|
404
|
+
})
|
|
430
405
|
if (!wrapped) return // Skipping remote call -> handled locally
|
|
431
406
|
const json = {
|
|
432
407
|
c: className,
|
|
433
408
|
f: functionName,
|
|
409
|
+
a: wrapped,
|
|
434
410
|
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
435
411
|
s: this._vrpcClientId,
|
|
436
|
-
a: wrapped,
|
|
437
412
|
v: VRPC_PROTOCOL_VERSION
|
|
438
413
|
}
|
|
439
414
|
const topic = `${this._domain}/${agent}/${className}/__static__/${functionName}`
|
|
440
415
|
await this._waitUntilClassIsOnline(agent, className)
|
|
441
416
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
442
|
-
return this._handleAgentAnswer(json)
|
|
417
|
+
return this._handleAgentAnswer(json, agent)
|
|
443
418
|
}
|
|
444
419
|
|
|
445
420
|
/**
|
|
@@ -471,41 +446,43 @@ class VrpcClient extends EventEmitter {
|
|
|
471
446
|
const json = {
|
|
472
447
|
c: className,
|
|
473
448
|
f: functionName,
|
|
449
|
+
a: [],
|
|
474
450
|
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
475
451
|
s: this._vrpcClientId,
|
|
476
|
-
a: [],
|
|
477
452
|
v: VRPC_PROTOCOL_VERSION
|
|
478
453
|
}
|
|
479
454
|
if (agent === '*') {
|
|
480
455
|
const result = []
|
|
481
456
|
const agents = this.getAvailableAgents()
|
|
482
|
-
for (const
|
|
483
|
-
const topic = `${this._domain}/${
|
|
484
|
-
const wrapped = this._wrapArguments(
|
|
485
|
-
|
|
457
|
+
for (const agent of agents) {
|
|
458
|
+
const topic = `${this._domain}/${agent}/${className}/__static__/__callAll__`
|
|
459
|
+
const wrapped = this._wrapArguments({
|
|
460
|
+
agent,
|
|
461
|
+
className,
|
|
486
462
|
functionName,
|
|
487
|
-
|
|
488
|
-
)
|
|
463
|
+
args
|
|
464
|
+
})
|
|
489
465
|
if (!wrapped) continue // using cache
|
|
490
466
|
json.a = [functionName, ...wrapped]
|
|
491
|
-
await this._waitUntilClassIsOnline(
|
|
467
|
+
await this._waitUntilClassIsOnline(agent, className)
|
|
492
468
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
493
|
-
const tmp = await this._handleAgentAnswer(json)
|
|
469
|
+
const tmp = await this._handleAgentAnswer(json, agent)
|
|
494
470
|
result.push(...tmp)
|
|
495
471
|
}
|
|
496
472
|
return result
|
|
497
473
|
}
|
|
498
474
|
const topic = `${this._domain}/${agent}/${className}/__static__/__callAll__`
|
|
499
|
-
const wrapped = this._wrapArguments(
|
|
500
|
-
|
|
475
|
+
const wrapped = this._wrapArguments({
|
|
476
|
+
agent,
|
|
477
|
+
className,
|
|
501
478
|
functionName,
|
|
502
|
-
|
|
503
|
-
)
|
|
479
|
+
args
|
|
480
|
+
})
|
|
504
481
|
if (!wrapped) return // using cache
|
|
505
482
|
json.a = [functionName, ...wrapped]
|
|
506
483
|
await this._waitUntilClassIsOnline(agent, className)
|
|
507
484
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
508
|
-
return this._handleAgentAnswer(json)
|
|
485
|
+
return this._handleAgentAnswer(json, agent)
|
|
509
486
|
}
|
|
510
487
|
|
|
511
488
|
/**
|
|
@@ -535,19 +512,7 @@ class VrpcClient extends EventEmitter {
|
|
|
535
512
|
* @param {Boolean} [options.mustBeOnline=true] Only retrieve currently online agents
|
|
536
513
|
* @returns {Array} Array of agent names.
|
|
537
514
|
*/
|
|
538
|
-
getAvailableAgents (
|
|
539
|
-
/// DEPRECATION INFORMATION AND WORKAROUND - TO BE REMOVED IN 3.x
|
|
540
|
-
// `options = {}` was `domain = this._domain` before
|
|
541
|
-
if (typeof options === 'string') {
|
|
542
|
-
this._indicateDeprecation(
|
|
543
|
-
'VrpcClient.getAvailableAgents(): ' +
|
|
544
|
-
'Providing a domain here has no effect and will be an error in future.'
|
|
545
|
-
)
|
|
546
|
-
options = { mustBeOnline: true }
|
|
547
|
-
}
|
|
548
|
-
///
|
|
549
|
-
|
|
550
|
-
const { mustBeOnline = true } = options
|
|
515
|
+
getAvailableAgents ({ mustBeOnline = true } = {}) {
|
|
551
516
|
const agents = []
|
|
552
517
|
if (mustBeOnline) {
|
|
553
518
|
// loop agents
|
|
@@ -568,34 +533,7 @@ class VrpcClient extends EventEmitter {
|
|
|
568
533
|
* @param {Boolean} [options.mustBeOnline=true] Only retrieve currently online classes
|
|
569
534
|
* @returns {Array} Array of class names.
|
|
570
535
|
*/
|
|
571
|
-
getAvailableClasses (
|
|
572
|
-
options = { agent: this._agent, mustBeOnline: true },
|
|
573
|
-
domain
|
|
574
|
-
) {
|
|
575
|
-
/// DEPRECATION INFORMATION AND WORKAROUND - TO BE REMOVED IN 3.x
|
|
576
|
-
let agent = this._agent
|
|
577
|
-
let mustBeOnline = true
|
|
578
|
-
// `options = {}` was `agent = this._agent` before
|
|
579
|
-
if (typeof options === 'string') {
|
|
580
|
-
this._indicateDeprecation(
|
|
581
|
-
'VrpcClient.getAvailableClasses(): ' +
|
|
582
|
-
'setting the agent as string argument is deprecated.`' +
|
|
583
|
-
'Use `{ agent: <myAgent> }` instead.'
|
|
584
|
-
)
|
|
585
|
-
agent = options
|
|
586
|
-
} else {
|
|
587
|
-
agent = options.agent
|
|
588
|
-
mustBeOnline = options.mustBeOnline
|
|
589
|
-
}
|
|
590
|
-
// was `domain = this._domain` before
|
|
591
|
-
if (domain) {
|
|
592
|
-
this._indicateDeprecation(
|
|
593
|
-
'VrpcClient.getAvailableClasses(): ' +
|
|
594
|
-
'Providing a domain here has no effect and will be an error in future.'
|
|
595
|
-
)
|
|
596
|
-
}
|
|
597
|
-
///
|
|
598
|
-
|
|
536
|
+
getAvailableClasses ({ agent = this._agent, mustBeOnline = true } = {}) {
|
|
599
537
|
if (agent === '*') throw new Error('Agent must be specified')
|
|
600
538
|
if (!this._agents[agent]) return []
|
|
601
539
|
const { classes, status } = this._agents[agent]
|
|
@@ -604,43 +542,19 @@ class VrpcClient extends EventEmitter {
|
|
|
604
542
|
}
|
|
605
543
|
|
|
606
544
|
/**
|
|
607
|
-
* Retrieves all (
|
|
545
|
+
* Retrieves all (shared) instances on specific class and agent.
|
|
608
546
|
*
|
|
609
|
-
* @param {String} className Class name
|
|
610
547
|
* @param {Object} [options]
|
|
548
|
+
* @param {String} options.className Class name
|
|
611
549
|
* @param {String} [options.agent] Agent name. If not provided class default is used
|
|
612
550
|
* @param {Boolean} [options.mustBeOnline=true] Only retrieve currently online classes
|
|
613
551
|
* @returns {Array} Array of instance names
|
|
614
552
|
*/
|
|
615
|
-
getAvailableInstances (
|
|
553
|
+
getAvailableInstances ({
|
|
616
554
|
className,
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
) {
|
|
620
|
-
/// DEPRECATION INFORMATION AND WORKAROUND - TO BE REMOVED IN 3.x
|
|
621
|
-
let agent = this._agent
|
|
622
|
-
let mustBeOnline = true
|
|
623
|
-
// `options = {}` was `agent = this._agent` before
|
|
624
|
-
if (typeof options === 'string') {
|
|
625
|
-
this._indicateDeprecation(
|
|
626
|
-
'VrpcClient.getAvailableInstances(): ' +
|
|
627
|
-
'Setting the agent as string argument is deprecated.`' +
|
|
628
|
-
'Use `{ agent: <myAgent> }` instead.'
|
|
629
|
-
)
|
|
630
|
-
agent = options
|
|
631
|
-
} else {
|
|
632
|
-
agent = options.agent
|
|
633
|
-
mustBeOnline = options.mustBeOnline
|
|
634
|
-
}
|
|
635
|
-
// was `domain = this._domain` before
|
|
636
|
-
if (domain) {
|
|
637
|
-
this._indicateDeprecation(
|
|
638
|
-
'VrpcClient.getAvailableInstances(): ' +
|
|
639
|
-
'Providing a domain here has no effect and will be an error in future.'
|
|
640
|
-
)
|
|
641
|
-
}
|
|
642
|
-
///
|
|
643
|
-
|
|
555
|
+
agent = this._agent,
|
|
556
|
+
mustBeOnline = true
|
|
557
|
+
} = {}) {
|
|
644
558
|
if (agent === '*') throw new Error('Agent must be specified')
|
|
645
559
|
if (!this._agents[agent]) return []
|
|
646
560
|
const { classes, status } = this._agents[agent]
|
|
@@ -651,41 +565,17 @@ class VrpcClient extends EventEmitter {
|
|
|
651
565
|
/**
|
|
652
566
|
* Retrieves all member functions of specific class and agent.
|
|
653
567
|
*
|
|
654
|
-
* @param {String} className Class name
|
|
655
568
|
* @param {Object} [options]
|
|
569
|
+
* @param {String} options.className Class name
|
|
656
570
|
* @param {String} [options.agent] Agent name. If not provided class default is used
|
|
657
571
|
* @param {Boolean} [options.mustBeOnline=true] Only retrieve currently online classes
|
|
658
572
|
* @returns {Array} Array of member function names
|
|
659
573
|
*/
|
|
660
|
-
getAvailableMemberFunctions (
|
|
574
|
+
getAvailableMemberFunctions ({
|
|
661
575
|
className,
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
) {
|
|
665
|
-
/// DEPRECATION INFORMATION AND WORKAROUND - TO BE REMOVED IN 3.x
|
|
666
|
-
let agent = this._agent
|
|
667
|
-
let mustBeOnline = true
|
|
668
|
-
// `options = {}` was `agent = this._agent` before
|
|
669
|
-
if (typeof options === 'string') {
|
|
670
|
-
this._indicateDeprecation(
|
|
671
|
-
'VrpcClient.getAvailableMemberFunctions(): ' +
|
|
672
|
-
'Setting the agent as string argument is deprecated.`' +
|
|
673
|
-
'Use `{ agent: <myAgent> }` instead.'
|
|
674
|
-
)
|
|
675
|
-
agent = options
|
|
676
|
-
} else {
|
|
677
|
-
agent = options.agent
|
|
678
|
-
mustBeOnline = options.mustBeOnline
|
|
679
|
-
}
|
|
680
|
-
// was `domain = this._domain` before
|
|
681
|
-
if (domain) {
|
|
682
|
-
this._indicateDeprecation(
|
|
683
|
-
'VrpcClient.getAvailableMemberFunctions(): ' +
|
|
684
|
-
'Providing a domain here has no effect and will be an error in future.'
|
|
685
|
-
)
|
|
686
|
-
}
|
|
687
|
-
///
|
|
688
|
-
|
|
576
|
+
agent = this._agent,
|
|
577
|
+
mustBeOnline = true
|
|
578
|
+
} = {}) {
|
|
689
579
|
if (agent === '*') throw new Error('Agent must be specified')
|
|
690
580
|
if (!this._agents[agent]) return []
|
|
691
581
|
const { classes, status } = this._agents[agent]
|
|
@@ -698,41 +588,17 @@ class VrpcClient extends EventEmitter {
|
|
|
698
588
|
/**
|
|
699
589
|
* Retrieves all static functions of specific class and agent.
|
|
700
590
|
*
|
|
701
|
-
* @param {String} className Class name
|
|
702
591
|
* @param {Object} [options]
|
|
592
|
+
* @param {String} options.className Class name
|
|
703
593
|
* @param {String} [options.agent] Agent name. If not provided class default is used
|
|
704
594
|
* @param {Boolean} [options.mustBeOnline=true] Only retrieve currently online classes
|
|
705
595
|
* @returns {Array} Array of static function names
|
|
706
596
|
*/
|
|
707
|
-
getAvailableStaticFunctions (
|
|
597
|
+
getAvailableStaticFunctions ({
|
|
708
598
|
className,
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
) {
|
|
712
|
-
/// DEPRECATION INFORMATION AND WORKAROUND - TO BE REMOVED IN 3.x
|
|
713
|
-
let agent = this._agent
|
|
714
|
-
let mustBeOnline = true
|
|
715
|
-
// `options = {}` was `agent = this._agent` before
|
|
716
|
-
if (typeof options === 'string') {
|
|
717
|
-
this._indicateDeprecation(
|
|
718
|
-
'VrpcClient.getAvailableStaticFunctions(): ' +
|
|
719
|
-
'Setting the agent as string argument is deprecated.`' +
|
|
720
|
-
'Use `{ agent: <myAgent> }` instead.'
|
|
721
|
-
)
|
|
722
|
-
agent = options
|
|
723
|
-
} else {
|
|
724
|
-
agent = options.agent
|
|
725
|
-
mustBeOnline = options.mustBeOnline
|
|
726
|
-
}
|
|
727
|
-
// was `domain = this._domain` before
|
|
728
|
-
if (domain) {
|
|
729
|
-
this._indicateDeprecation(
|
|
730
|
-
'VrpcClient.getAvailableStaticFunctions(): ' +
|
|
731
|
-
'Providing a domain here has no effect and will be an error in future.'
|
|
732
|
-
)
|
|
733
|
-
}
|
|
734
|
-
///
|
|
735
|
-
|
|
599
|
+
agent = this._agent,
|
|
600
|
+
mustBeOnline = true
|
|
601
|
+
} = {}) {
|
|
736
602
|
if (agent === '*') throw new Error('Agent must be specified')
|
|
737
603
|
if (!this._agents[agent]) return []
|
|
738
604
|
const { classes, status } = this._agents[agent]
|
|
@@ -810,9 +676,8 @@ class VrpcClient extends EventEmitter {
|
|
|
810
676
|
.createHash('md5')
|
|
811
677
|
.update(clientInfo)
|
|
812
678
|
.digest('hex')
|
|
813
|
-
.
|
|
814
|
-
//
|
|
815
|
-
return `vc${this._instance}${md5}` // 2 + 8 + 13 = 23 (max clientId)
|
|
679
|
+
.substring(0, 12)
|
|
680
|
+
return `vc3${this._instance}${md5}` // 3 + 8 + 12 = 23 (max clientId)
|
|
816
681
|
}
|
|
817
682
|
|
|
818
683
|
_createVrpcClientId () {
|
|
@@ -940,34 +805,120 @@ class VrpcClient extends EventEmitter {
|
|
|
940
805
|
// Remove overloads
|
|
941
806
|
const uniqueFuncs = new Set(functions)
|
|
942
807
|
// Build proxy
|
|
943
|
-
uniqueFuncs.forEach(
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
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
|
|
947
855
|
try {
|
|
948
856
|
const json = {
|
|
949
857
|
c: instance,
|
|
950
|
-
f:
|
|
951
|
-
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
952
|
-
s: this._vrpcClientId,
|
|
858
|
+
f: functionName,
|
|
953
859
|
a: wrapped,
|
|
860
|
+
i: `${this._instance}-${this._invokeId++ %
|
|
861
|
+
Number.MAX_SAFE_INTEGER}`,
|
|
862
|
+
s: this._vrpcClientId,
|
|
954
863
|
v: VRPC_PROTOCOL_VERSION
|
|
955
864
|
}
|
|
956
|
-
this._mqttPublish(
|
|
957
|
-
|
|
865
|
+
this._mqttPublish(
|
|
866
|
+
`${targetTopic}/${functionName}`,
|
|
867
|
+
JSON.stringify(json)
|
|
868
|
+
)
|
|
869
|
+
return this._handleAgentAnswer(json, agent)
|
|
958
870
|
} catch (err) {
|
|
959
871
|
throw new Error(
|
|
960
|
-
`Could not remotely call "${
|
|
872
|
+
`Could not remotely call "${functionName}" because: ${err.message}`
|
|
961
873
|
)
|
|
962
874
|
}
|
|
963
875
|
}
|
|
964
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
|
+
}
|
|
965
916
|
return proxy
|
|
966
917
|
}
|
|
967
918
|
|
|
968
|
-
async _handleAgentAnswer ({ i, c, f }) {
|
|
919
|
+
async _handleAgentAnswer ({ i, c, f }, agent) {
|
|
969
920
|
return new Promise((resolve, reject) => {
|
|
970
|
-
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)`
|
|
971
922
|
const timer = setTimeout(() => {
|
|
972
923
|
this._eventEmitter.removeAllListeners(i)
|
|
973
924
|
reject(new Error(msg))
|
|
@@ -975,13 +926,14 @@ class VrpcClient extends EventEmitter {
|
|
|
975
926
|
this._eventEmitter.once(i, data => {
|
|
976
927
|
clearTimeout(timer)
|
|
977
928
|
if (data.e) {
|
|
978
|
-
reject(new Error(data.e))
|
|
929
|
+
reject(new Error(`[vrpc ${agent}-${c}-${f}]: ${data.e}`))
|
|
979
930
|
} else {
|
|
980
931
|
const ret = data.r
|
|
981
932
|
// Handle functions returning a promise
|
|
982
|
-
if (typeof ret === 'string' && ret.
|
|
933
|
+
if (typeof ret === 'string' && ret.substring(0, 5) === '__p__') {
|
|
983
934
|
this._eventEmitter.once(ret, promiseData => {
|
|
984
|
-
|
|
935
|
+
// TODO improve error message here, like above
|
|
936
|
+
if (promiseData.e) reject(new Error(`[vrpc ${agent}-${c}-${f}]: ${promiseData.e}`))
|
|
985
937
|
else resolve(promiseData.r)
|
|
986
938
|
})
|
|
987
939
|
} else {
|
|
@@ -1012,79 +964,128 @@ class VrpcClient extends EventEmitter {
|
|
|
1012
964
|
})
|
|
1013
965
|
}
|
|
1014
966
|
|
|
1015
|
-
_wrapArguments (
|
|
967
|
+
_wrapArguments ({ agent, className, instance, proxyId, functionName, args }) {
|
|
1016
968
|
const wrapped = []
|
|
1017
|
-
|
|
1018
|
-
args.forEach((x, i) => {
|
|
969
|
+
for (const [i, x] of args.entries()) {
|
|
1019
970
|
// Check whether provided argument is a function
|
|
1020
971
|
if (this._isFunction(x)) {
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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
|
|
1029
992
|
wrapped.push(id)
|
|
993
|
+
continue
|
|
1030
994
|
} else if (
|
|
1031
995
|
(functionName === 'off' || functionName === 'removeListener') &&
|
|
1032
996
|
i === 1 &&
|
|
1033
997
|
typeof args[0] === 'string'
|
|
1034
998
|
) {
|
|
1035
|
-
const
|
|
1036
|
-
|
|
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
|
|
1037
1009
|
wrapped.push(id)
|
|
1038
|
-
|
|
1010
|
+
continue
|
|
1039
1011
|
} else {
|
|
1012
|
+
// Regular function callback (can be static or member function)
|
|
1013
|
+
const remoteId = proxyId || `${agent}-${className}`
|
|
1040
1014
|
const id = `__f__${remoteId}-${functionName}-${i}-${this._invokeId++ %
|
|
1041
1015
|
Number.MAX_SAFE_INTEGER}`
|
|
1042
1016
|
wrapped.push(id)
|
|
1043
|
-
this._eventEmitter.once(id, ({ a }) =>
|
|
1044
|
-
|
|
1045
|
-
})
|
|
1017
|
+
this._eventEmitter.once(id, ({ a }) => x.apply(null, a))
|
|
1018
|
+
continue
|
|
1046
1019
|
}
|
|
1047
|
-
}
|
|
1020
|
+
}
|
|
1021
|
+
// special case of an EventEmitter provided as argument
|
|
1022
|
+
if (this._isEmitter(x)) {
|
|
1048
1023
|
const { emitter, event } = x
|
|
1049
|
-
const id =
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1024
|
+
const id = this._onRemoteEvent({
|
|
1025
|
+
agent,
|
|
1026
|
+
className,
|
|
1027
|
+
instance,
|
|
1028
|
+
event,
|
|
1029
|
+
callback: (...args) => emitter.emit(event, ...args)
|
|
1053
1030
|
})
|
|
1054
|
-
|
|
1055
|
-
|
|
1031
|
+
// indicate that no network call is required
|
|
1032
|
+
if (id === null) return null
|
|
1033
|
+
wrapped.push(id)
|
|
1034
|
+
continue
|
|
1056
1035
|
}
|
|
1057
|
-
|
|
1058
|
-
|
|
1036
|
+
// default behavior is to not touch
|
|
1037
|
+
wrapped.push(x)
|
|
1038
|
+
}
|
|
1059
1039
|
return wrapped
|
|
1060
1040
|
}
|
|
1061
1041
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
this.
|
|
1066
|
-
|
|
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
|
|
1067
1058
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
emitter.emit(id, ...a)
|
|
1073
|
-
})
|
|
1074
|
-
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
|
|
1075
1063
|
}
|
|
1076
1064
|
|
|
1077
|
-
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
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]
|
|
1085
1081
|
return id
|
|
1086
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}`)
|
|
1087
1087
|
}
|
|
1088
|
+
return id
|
|
1088
1089
|
}
|
|
1089
1090
|
|
|
1090
1091
|
_stripSignature (method) {
|
|
@@ -1112,7 +1113,7 @@ class VrpcClient extends EventEmitter {
|
|
|
1112
1113
|
)
|
|
1113
1114
|
}
|
|
1114
1115
|
|
|
1115
|
-
async _getInstanceData (instance, options
|
|
1116
|
+
async _getInstanceData (instance, options) {
|
|
1116
1117
|
const instanceData = this._getInstanceFromCache(instance, options)
|
|
1117
1118
|
if (!instanceData) {
|
|
1118
1119
|
if (options.noWait) {
|
|
@@ -1124,6 +1125,21 @@ class VrpcClient extends EventEmitter {
|
|
|
1124
1125
|
}
|
|
1125
1126
|
|
|
1126
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
|
+
}
|
|
1127
1143
|
// loop agents
|
|
1128
1144
|
for (const agent in this._agents) {
|
|
1129
1145
|
if (options.agent && agent !== options.agent) continue
|
|
@@ -1176,7 +1192,7 @@ class VrpcClient extends EventEmitter {
|
|
|
1176
1192
|
* @param {String} info.domain - Domain name
|
|
1177
1193
|
* @param {String} info.agent - Agent name
|
|
1178
1194
|
* @param {String} info.className - Class name
|
|
1179
|
-
* @param {Array.<String>} info.instances - Array of
|
|
1195
|
+
* @param {Array.<String>} info.instances - Array of instances
|
|
1180
1196
|
* @param {Array.<String>} info.memberFunctions - Array of member functions
|
|
1181
1197
|
* @param {Array.<String>} info.staticFunctions - Array of static functions
|
|
1182
1198
|
* @param {MetaData} info.meta - Object associating further information to functions
|