vrpc 2.5.0 → 3.0.0-alpha.2

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.
@@ -10,9 +10,9 @@ __\/\\\_______\/\\\__/\\\///////\\\___\/\\\/////////\\\____/\\\////////__
10
10
  ________\///________\///________\///__\///___________________\/////////__
11
11
 
12
12
 
13
- Non-intrusively binds code and provides access in form of asynchronous remote
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/bheisen/vrpc)
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>.
@@ -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 VrpcRemote extends EventEmitter {
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 VrpcRemote({
72
+ * const client = new VrpcClient({
70
73
  * domain: 'public.vrpc',
71
74
  * broker: 'mqtt://vrpc.io'
72
75
  * })
@@ -86,11 +89,15 @@ class VrpcRemote extends EventEmitter {
86
89
  // domain sanity check
87
90
  if (!domain) throw new Error('The domain must be specified')
88
91
  if (domain.match(/[+/#*]/)) {
89
- throw new Error('The domain must NOT contain any of those characters: "+", "/", "#", "*"')
92
+ throw new Error(
93
+ 'The domain must NOT contain any of those characters: "+", "/", "#", "*"'
94
+ )
90
95
  }
91
96
  // agent sanity check
92
97
  if (agent.match(/[+/#]/)) {
93
- throw new Error('The agent must NOT contain any of those characters: "+", "/", "#"')
98
+ throw new Error(
99
+ 'The agent must NOT contain any of those characters: "+", "/", "#"'
100
+ )
94
101
  }
95
102
  this._token = token
96
103
  this._username = username
@@ -101,12 +108,13 @@ class VrpcRemote extends EventEmitter {
101
108
  this._timeout = timeout
102
109
  this._qos = this._qos = bestEffort ? 0 : 1
103
110
 
104
- this._instance = crypto.randomBytes(2).toString('hex')
111
+ this._instance = nanoid(8)
105
112
  this._mqttClientId = this._createMqttClientId()
106
113
  this._vrpcClientId = this._createVrpcClientId()
107
114
  this._agents = {}
108
115
  this._eventEmitter = new EventEmitter()
109
116
  this._invokeId = 0
117
+ this._proxyId = 0
110
118
  this._client = null
111
119
  this._cachedSubscriptions = new Map()
112
120
  this._proxies = {}
@@ -118,6 +126,15 @@ class VrpcRemote extends EventEmitter {
118
126
  }
119
127
  }
120
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
+
121
138
  /**
122
139
  * Actually connects to the MQTT broker.
123
140
  *
@@ -154,15 +171,15 @@ class VrpcRemote extends EventEmitter {
154
171
  if (password === undefined) delete options.password
155
172
  this._client = mqtt.connect(this._broker, options)
156
173
 
157
- this._client.on('error', (err) => {
174
+ this._client.on('error', err => {
158
175
  this.emit('error', err)
159
176
  })
160
177
 
161
- this._client.stream.on('error', (err) => {
178
+ this._client.stream.on('error', err => {
162
179
  this.emit('error', err)
163
180
  })
164
181
 
165
- this.on('error', (err) => {
182
+ this.on('error', err => {
166
183
  this._log.debug(`Encountered MQTT error: ${err.message}`)
167
184
  })
168
185
 
@@ -194,7 +211,7 @@ class VrpcRemote extends EventEmitter {
194
211
  this._agents[agent].hostname = hostname
195
212
  this._agents[agent].version = version
196
213
  this.emit('agent', { domain, agent, status, hostname, version })
197
- // ClassInfo message
214
+ // ClassInfo message
198
215
  } else if (instance === '__classInfo__') {
199
216
  // Json properties: { klass, instances, memberFunctions, staticFunctions }
200
217
  const json = JSON.parse(message.toString())
@@ -212,24 +229,23 @@ class VrpcRemote extends EventEmitter {
212
229
  staticFunctions,
213
230
  meta
214
231
  } = json
215
- if (removed.length !== 0) this.emit('instanceGone', removed, { domain, agent, className })
216
- if (added.length !== 0) this.emit('instanceNew', added, { domain, agent, className })
217
- this.emit(
218
- 'class',
219
- {
220
- domain,
221
- agent,
222
- className,
223
- instances,
224
- memberFunctions,
225
- staticFunctions,
226
- meta
227
- }
228
- )
229
- // RPC message
232
+ if (removed.length !== 0)
233
+ this.emit('instanceGone', removed, { domain, agent, className })
234
+ if (added.length !== 0)
235
+ this.emit('instanceNew', added, { domain, agent, className })
236
+ this.emit('class', {
237
+ domain,
238
+ agent,
239
+ className,
240
+ instances,
241
+ memberFunctions,
242
+ staticFunctions,
243
+ meta
244
+ })
245
+ // RPC message
230
246
  } else {
231
- const { id, data } = JSON.parse(message.toString())
232
- this._eventEmitter.emit(id, data)
247
+ const { i, a, e, r } = JSON.parse(message.toString())
248
+ this._eventEmitter.emit(i, { a, e, r })
233
249
  }
234
250
  })
235
251
  await new Promise((resolve, reject) => {
@@ -243,11 +259,6 @@ class VrpcRemote extends EventEmitter {
243
259
  })
244
260
  }
245
261
 
246
- async connected () {
247
- this._indicateDeprecation('VrpcRemote.connected(): This API usage is deprecated, use `connect()` instead.')
248
- return this.connect()
249
- }
250
-
251
262
  /**
252
263
  * Creates a new remote instance and provides a proxy to it.
253
264
  *
@@ -296,23 +307,20 @@ class VrpcRemote extends EventEmitter {
296
307
  */
297
308
  async create ({
298
309
  className,
299
- instance,
310
+ instance = nanoid(8),
300
311
  args = [],
301
312
  agent = this._agent,
302
- cacheProxy = false
313
+ cacheProxy = false,
314
+ isIsolated = false
303
315
  } = {}) {
304
316
  if (agent === '*') throw new Error('Agent must be specified')
305
- const data = instance ? { _1: instance } : {}
306
- const offset = instance ? 2 : 1
307
- args.forEach((value, index) => {
308
- data[`_${index + offset}`] = value
309
- })
310
317
  const json = {
311
- context: className,
312
- method: instance ? '__createNamed__' : '__create__',
313
- id: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
314
- sender: `${this._domain}/${os.hostname()}/${this._instance}`,
315
- data
318
+ c: className,
319
+ f: isIsolated ? '__createIsolated__' : '__createShared__',
320
+ i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
321
+ s: `${this._domain}/${os.hostname()}/${this._instance}`,
322
+ a: [instance, ...args],
323
+ v: VRPC_PROTOCOL_VERSION
316
324
  }
317
325
  const proxy = await this._getProxy(agent, className, json)
318
326
  if (instance && cacheProxy) this._proxies[instance] = proxy
@@ -342,11 +350,12 @@ class VrpcRemote extends EventEmitter {
342
350
  instance: instanceString
343
351
  } = await this._getInstanceData(instance, options)
344
352
  return this._createProxy(agent, className, instanceString)
345
- } else { // deprecate this
353
+ } else {
354
+ // deprecate this
346
355
  this._indicateDeprecation(
347
- 'VrpcRemote.getInstance(): ' +
348
- 'Using an object as single argument is deprecated, ' +
349
- 'use "getInstance(instance, options)" instead.'
356
+ 'VrpcClient.getInstance(): ' +
357
+ 'Using an object as single argument is deprecated, ' +
358
+ 'use "getInstance(instance, options)" instead.'
350
359
  )
351
360
  const fakedOptions = { ...instance }
352
361
  const fakedInstance = fakedOptions.instance
@@ -377,21 +386,23 @@ class VrpcRemote extends EventEmitter {
377
386
  if (typeof instance === 'string') {
378
387
  instanceData = await this._getInstanceData(instance)
379
388
  if (options) instanceData = { ...options, ...instanceData }
380
- } else { // deprecate this
389
+ } else {
390
+ // deprecate this
381
391
  this._indicateDeprecation(
382
- 'VrpcRemote.delete(): ' +
383
- 'Using an object as single argument is deprecated, ' +
384
- 'use "delete(instance, options)" instead.'
392
+ 'VrpcClient.delete(): ' +
393
+ 'Using an object as single argument is deprecated, ' +
394
+ 'use "delete(instance, options)" instead.'
385
395
  )
386
396
  instanceData = { ...instanceData, ...instance }
387
397
  }
388
398
  const { agent, className, instance: instanceString } = instanceData
389
399
  const json = {
390
- context: className,
391
- method: '__delete__',
392
- id: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
393
- sender: `${this._domain}/${os.hostname()}/${this._instance}`,
394
- data: { _1: instanceString }
400
+ c: className,
401
+ f: '__delete__',
402
+ i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
403
+ s: `${this._domain}/${os.hostname()}/${this._instance}`,
404
+ a: [instanceString],
405
+ v: VRPC_PROTOCOL_VERSION
395
406
  }
396
407
  const topic = `${this._domain}/${agent}/${className}/__static__/__delete__`
397
408
  this._mqttPublish(topic, JSON.stringify(json))
@@ -415,12 +426,15 @@ class VrpcRemote extends EventEmitter {
415
426
  args = [],
416
427
  agent = this._agent
417
428
  } = {}) {
429
+ const wrapped = this._wrapArguments(className, functionName, ...args)
430
+ if (!wrapped) return // Skipping remote call -> handled locally
418
431
  const json = {
419
- context: className,
420
- method: functionName,
421
- id: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
422
- sender: this._vrpcClientId,
423
- data: this._packData(className, functionName, ...args)
432
+ c: className,
433
+ f: functionName,
434
+ i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
435
+ s: this._vrpcClientId,
436
+ a: wrapped,
437
+ v: VRPC_PROTOCOL_VERSION
424
438
  }
425
439
  const topic = `${this._domain}/${agent}/${className}/__static__/${functionName}`
426
440
  await this._waitUntilClassIsOnline(agent, className)
@@ -430,19 +444,23 @@ class VrpcRemote extends EventEmitter {
430
444
 
431
445
  /**
432
446
  * Calls the same function on all instances of a given class and returns an
433
- * aggregated result.
447
+ * aggregated result. It as well allows for batch event and callback
448
+ * registrations. In this case the instanceId of the emitter is injected as
449
+ * first argument of any event callback.
434
450
  *
435
451
  * NOTE: When no agent was specified as class default and no agent is
436
452
  * specified when calling this function, callAll will act on the requested
437
- * class across all available agents. The same is true when explicitly using
438
- * a wildcard (*) as agent value.
453
+ * class across all available agents. The same is true when explicitly using a
454
+ * wildcard (*) as agent value.
439
455
  *
440
456
  * @param {Object} options
441
457
  * @param {String} options.className Name of the static function's class
442
- * @param {Array} [options.args] Positional arguments of the static function call
443
- * @param {String} [options.agent] Agent name. If not provided class default is used
444
- * @returns {Promise<Object[]>} An array of objects `{ id, val, err }` carrying
445
- * the instance id, the return value and potential errors
458
+ * @param {Array} [options.args] Positional arguments of the static function
459
+ * call
460
+ * @param {String} [options.agent] Agent name. If not provided class default
461
+ * is used
462
+ * @returns {Promise<Object[]>} An array of objects `{ id, val, err }`
463
+ * carrying the instance id, the return value and potential errors
446
464
  */
447
465
  async callAll ({
448
466
  className,
@@ -450,20 +468,26 @@ class VrpcRemote extends EventEmitter {
450
468
  args = [],
451
469
  agent = this._agent
452
470
  } = {}) {
453
- const data = { _1: functionName }
454
- args.forEach((value, index) => (data[`_${index + 2}`] = value))
455
471
  const json = {
456
- context: className,
457
- method: functionName,
458
- id: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
459
- sender: this._vrpcClientId,
460
- data
472
+ c: className,
473
+ f: functionName,
474
+ i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
475
+ s: this._vrpcClientId,
476
+ a: [],
477
+ v: VRPC_PROTOCOL_VERSION
461
478
  }
462
479
  if (agent === '*') {
463
480
  const result = []
464
481
  const agents = this.getAvailableAgents()
465
482
  for (const x of agents) {
466
483
  const topic = `${this._domain}/${x}/${className}/__static__/__callAll__`
484
+ const wrapped = this._wrapArguments(
485
+ `${x}-${className}`,
486
+ functionName,
487
+ ...args
488
+ )
489
+ if (!wrapped) continue // using cache
490
+ json.a = [functionName, ...wrapped]
467
491
  await this._waitUntilClassIsOnline(x, className)
468
492
  this._mqttPublish(topic, JSON.stringify(json))
469
493
  const tmp = await this._handleAgentAnswer(json)
@@ -472,41 +496,22 @@ class VrpcRemote extends EventEmitter {
472
496
  return result
473
497
  }
474
498
  const topic = `${this._domain}/${agent}/${className}/__static__/__callAll__`
499
+ const wrapped = this._wrapArguments(
500
+ `${agent}-${className}`,
501
+ functionName,
502
+ ...args
503
+ )
504
+ if (!wrapped) return // using cache
505
+ json.a = [functionName, ...wrapped]
475
506
  await this._waitUntilClassIsOnline(agent, className)
476
507
  this._mqttPublish(topic, JSON.stringify(json))
477
508
  return this._handleAgentAnswer(json)
478
509
  }
479
510
 
480
- /**
481
- * Retrieves all agents, instances, classes, member and static
482
- * functions potentially available for remote control.
483
- * @deprecated
484
- * @returns {Object} SystemInformation
485
- * ```ts
486
- * type SystemInformation = {
487
- * [domain].agents[agent].status: string, // 'offline' or 'online'
488
- * [domain].agents[agent].hostname: string,
489
- * [domain].agents[agent].version: string,
490
- * [domain].agents[agent].classes[className].instances: string[],
491
- * [domain].agents[agent].classes[className].memberFunctions: string[],
492
- * [domain].agents[agent].classes[className].staticFunctions: string[],
493
- * [domain].agents[agent].classes[className].meta?: MetaData
494
- * }
495
- * ```
496
- */
497
- getAvailabilities () {
498
- this._indicateDeprecation(
499
- 'VrpcRemote.getAvailabilities(): ' +
500
- 'This function is deprecated and will be removed soon, ' +
501
- 'use `getSystemInformation()` instead.'
502
- )
503
- return { [this._domain]: { agents: this._agents } }
504
- }
505
-
506
511
  /**
507
512
  * Retrieves all information about the currently available components.
508
513
  *
509
- * @returns {Object} SystemInformation
514
+ * @returns {Object} SystemInformation
510
515
  * ```ts
511
516
  * type SystemInformation = {
512
517
  * [agent].status: string, // 'offline' or 'online'
@@ -523,21 +528,6 @@ class VrpcRemote extends EventEmitter {
523
528
  return this._agents
524
529
  }
525
530
 
526
- /**
527
- * Retrieves all domains on which agents can be remote controlled
528
- *
529
- * @deprecated
530
- * @returns {Array} Array of domain names
531
- */
532
- getAvailableDomains () {
533
- this._indicateDeprecation(
534
- 'VrpcRemote.getAvailableDomains(): ' +
535
- 'this function will be removed in the next major release, as the client' +
536
- 'works with a single domain anyways'
537
- )
538
- return [this._domain]
539
- }
540
-
541
531
  /**
542
532
  * Retrieves all available agents.
543
533
  *
@@ -550,8 +540,8 @@ class VrpcRemote extends EventEmitter {
550
540
  // `options = {}` was `domain = this._domain` before
551
541
  if (typeof options === 'string') {
552
542
  this._indicateDeprecation(
553
- 'VrpcRemote.getAvailableAgents(): ' +
554
- 'Providing a domain here has no effect and will be an error in future.'
543
+ 'VrpcClient.getAvailableAgents(): ' +
544
+ 'Providing a domain here has no effect and will be an error in future.'
555
545
  )
556
546
  options = { mustBeOnline: true }
557
547
  }
@@ -578,16 +568,19 @@ class VrpcRemote extends EventEmitter {
578
568
  * @param {Boolean} [options.mustBeOnline=true] Only retrieve currently online classes
579
569
  * @returns {Array} Array of class names.
580
570
  */
581
- getAvailableClasses (options = { agent: this._agent, mustBeOnline: true }, domain) {
571
+ getAvailableClasses (
572
+ options = { agent: this._agent, mustBeOnline: true },
573
+ domain
574
+ ) {
582
575
  /// DEPRECATION INFORMATION AND WORKAROUND - TO BE REMOVED IN 3.x
583
576
  let agent = this._agent
584
577
  let mustBeOnline = true
585
578
  // `options = {}` was `agent = this._agent` before
586
579
  if (typeof options === 'string') {
587
580
  this._indicateDeprecation(
588
- 'VrpcRemote.getAvailableClasses(): ' +
589
- 'setting the agent as string argument is deprecated.`' +
590
- 'Use `{ agent: <myAgent> }` instead.'
581
+ 'VrpcClient.getAvailableClasses(): ' +
582
+ 'setting the agent as string argument is deprecated.`' +
583
+ 'Use `{ agent: <myAgent> }` instead.'
591
584
  )
592
585
  agent = options
593
586
  } else {
@@ -597,8 +590,8 @@ class VrpcRemote extends EventEmitter {
597
590
  // was `domain = this._domain` before
598
591
  if (domain) {
599
592
  this._indicateDeprecation(
600
- 'VrpcRemote.getAvailableClasses(): ' +
601
- 'Providing a domain here has no effect and will be an error in future.'
593
+ 'VrpcClient.getAvailableClasses(): ' +
594
+ 'Providing a domain here has no effect and will be an error in future.'
602
595
  )
603
596
  }
604
597
  ///
@@ -630,9 +623,9 @@ class VrpcRemote extends EventEmitter {
630
623
  // `options = {}` was `agent = this._agent` before
631
624
  if (typeof options === 'string') {
632
625
  this._indicateDeprecation(
633
- 'VrpcRemote.getAvailableInstances(): ' +
634
- 'Setting the agent as string argument is deprecated.`' +
635
- 'Use `{ agent: <myAgent> }` instead.'
626
+ 'VrpcClient.getAvailableInstances(): ' +
627
+ 'Setting the agent as string argument is deprecated.`' +
628
+ 'Use `{ agent: <myAgent> }` instead.'
636
629
  )
637
630
  agent = options
638
631
  } else {
@@ -642,8 +635,8 @@ class VrpcRemote extends EventEmitter {
642
635
  // was `domain = this._domain` before
643
636
  if (domain) {
644
637
  this._indicateDeprecation(
645
- 'VrpcRemote.getAvailableInstances(): ' +
646
- 'Providing a domain here has no effect and will be an error in future.'
638
+ 'VrpcClient.getAvailableInstances(): ' +
639
+ 'Providing a domain here has no effect and will be an error in future.'
647
640
  )
648
641
  }
649
642
  ///
@@ -675,9 +668,9 @@ class VrpcRemote extends EventEmitter {
675
668
  // `options = {}` was `agent = this._agent` before
676
669
  if (typeof options === 'string') {
677
670
  this._indicateDeprecation(
678
- 'VrpcRemote.getAvailableMemberFunctions(): ' +
679
- 'Setting the agent as string argument is deprecated.`' +
680
- 'Use `{ agent: <myAgent> }` instead.'
671
+ 'VrpcClient.getAvailableMemberFunctions(): ' +
672
+ 'Setting the agent as string argument is deprecated.`' +
673
+ 'Use `{ agent: <myAgent> }` instead.'
681
674
  )
682
675
  agent = options
683
676
  } else {
@@ -687,8 +680,8 @@ class VrpcRemote extends EventEmitter {
687
680
  // was `domain = this._domain` before
688
681
  if (domain) {
689
682
  this._indicateDeprecation(
690
- 'VrpcRemote.getAvailableMemberFunctions(): ' +
691
- 'Providing a domain here has no effect and will be an error in future.'
683
+ 'VrpcClient.getAvailableMemberFunctions(): ' +
684
+ 'Providing a domain here has no effect and will be an error in future.'
692
685
  )
693
686
  }
694
687
  ///
@@ -722,9 +715,9 @@ class VrpcRemote extends EventEmitter {
722
715
  // `options = {}` was `agent = this._agent` before
723
716
  if (typeof options === 'string') {
724
717
  this._indicateDeprecation(
725
- 'VrpcRemote.getAvailableStaticFunctions(): ' +
726
- 'Setting the agent as string argument is deprecated.`' +
727
- 'Use `{ agent: <myAgent> }` instead.'
718
+ 'VrpcClient.getAvailableStaticFunctions(): ' +
719
+ 'Setting the agent as string argument is deprecated.`' +
720
+ 'Use `{ agent: <myAgent> }` instead.'
728
721
  )
729
722
  agent = options
730
723
  } else {
@@ -734,8 +727,8 @@ class VrpcRemote extends EventEmitter {
734
727
  // was `domain = this._domain` before
735
728
  if (domain) {
736
729
  this._indicateDeprecation(
737
- 'VrpcRemote.getAvailableStaticFunctions(): ' +
738
- 'Providing a domain here has no effect and will be an error in future.'
730
+ 'VrpcClient.getAvailableStaticFunctions(): ' +
731
+ 'Providing a domain here has no effect and will be an error in future.'
739
732
  )
740
733
  }
741
734
  ///
@@ -757,10 +750,7 @@ class VrpcRemote extends EventEmitter {
757
750
  * @param {String} options.agent Agent name. If not provided class default is used
758
751
  * @returns {Promise} Promise that resolves once re-connected
759
752
  */
760
- async reconnectWithToken (
761
- token,
762
- { agent = this._agent } = {}
763
- ) {
753
+ async reconnectWithToken (token, { agent = this._agent } = {}) {
764
754
  this._token = token
765
755
  this._agent = agent
766
756
  this._client.end(() => this.connect())
@@ -799,19 +789,29 @@ class VrpcRemote extends EventEmitter {
799
789
  async end () {
800
790
  this._mqttPublish(
801
791
  `${this._vrpcClientId}/__clientInfo__`,
802
- JSON.stringify({ status: 'offline' })
792
+ JSON.stringify({ status: 'offline', v: VRPC_PROTOCOL_VERSION })
803
793
  )
804
794
  return new Promise(resolve => this._client.end(false, {}, resolve))
805
795
  }
806
796
 
807
797
  _createMqttClientId () {
808
- const clientInfo = os.arch() + JSON.stringify(os.cpus()) + os.homedir() +
809
- os.hostname() + JSON.stringify(os.networkInterfaces()) + os.platform() +
810
- os.release() + os.totalmem() + os.type()
798
+ const clientInfo =
799
+ os.arch() +
800
+ JSON.stringify(os.cpus()) +
801
+ os.homedir() +
802
+ os.hostname() +
803
+ JSON.stringify(os.networkInterfaces()) +
804
+ os.platform() +
805
+ os.release() +
806
+ os.totalmem() +
807
+ os.type()
811
808
  // console.log('ClientInfo:', clientInfo)
812
- const md5 = crypto.createHash('md5').update(clientInfo).digest('hex').substr(0, 13)
813
- // FIXME (3.x): use vrpcp -> vrpcc
814
- return `vrpcp${this._instance}X${md5}` // 5 + 4 + 1 + 13 = 23 (max clientId)
809
+ const md5 = crypto
810
+ .createHash('md5')
811
+ .update(clientInfo)
812
+ .digest('hex')
813
+ .substr(0, 12)
814
+ return `vc3${this._instance}${md5}` // 3 + 8 + 12 = 23 (max clientId)
815
815
  }
816
816
 
817
817
  _createVrpcClientId () {
@@ -819,29 +819,44 @@ class VrpcRemote extends EventEmitter {
819
819
  }
820
820
 
821
821
  _mqttPublish (topic, message, options) {
822
- this._client.publish(topic, message, { qos: this._qos, ...options }, (err) => {
823
- if (err) {
824
- this._log.warn(`Could not publish MQTT message because: ${err.message}`)
822
+ this._client.publish(
823
+ topic,
824
+ message,
825
+ { qos: this._qos, ...options },
826
+ err => {
827
+ if (err) {
828
+ this._log.warn(
829
+ `Could not publish MQTT message because: ${err.message}`
830
+ )
831
+ }
825
832
  }
826
- })
833
+ )
827
834
  }
828
835
 
829
836
  _mqttSubscribe (topic, options) {
830
- this._client.subscribe(topic, { qos: this._qos, ...options }, (err, granted) => {
831
- if (err) {
832
- this._log.warn(`Could not subscribe to topic: ${topic} because: ${err.message}`)
833
- } else {
834
- if (granted.length === 0) {
835
- this._log.debug(`Already subscribed to: ${topic}`)
837
+ this._client.subscribe(
838
+ topic,
839
+ { qos: this._qos, ...options },
840
+ (err, granted) => {
841
+ if (err) {
842
+ this._log.warn(
843
+ `Could not subscribe to topic: ${topic} because: ${err.message}`
844
+ )
845
+ } else {
846
+ if (granted.length === 0) {
847
+ this._log.debug(`Already subscribed to: ${topic}`)
848
+ }
836
849
  }
837
850
  }
838
- })
851
+ )
839
852
  }
840
853
 
841
854
  _mqttUnsubscribe (topic, options) {
842
- this._client.unsubscribe(topic, options, (err) => {
855
+ this._client.unsubscribe(topic, options, err => {
843
856
  if (err) {
844
- this._log.warn(`Could not unsubscribe from topic: ${topic} because: ${err.message}`)
857
+ this._log.warn(
858
+ `Could not unsubscribe from topic: ${topic} because: ${err.message}`
859
+ )
845
860
  }
846
861
  })
847
862
  }
@@ -853,25 +868,22 @@ class VrpcRemote extends EventEmitter {
853
868
  }
854
869
 
855
870
  async _getProxy (agent, className, json) {
856
- const { method } = json
857
- const topic = `${this._domain}/${agent}/${className}/__static__/${method}`
871
+ const { f, i } = json
872
+ const topic = `${this._domain}/${agent}/${className}/__static__/${f}`
858
873
  await this._waitUntilClassIsOnline(agent, className)
859
874
  this._mqttPublish(topic, JSON.stringify(json))
860
875
  return new Promise((resolve, reject) => {
861
876
  const msg = `Proxy creation for class "${className}" on agent "${agent}" and domain "${this._domain}" timed out (> ${this._timeout} ms)`
862
- const timer = setTimeout(
863
- () => {
864
- this._eventEmitter.removeAllListeners(json.id)
865
- reject(new Error(msg))
866
- },
867
- this._timeout
868
- )
869
- this._eventEmitter.once(json.id, data => {
877
+ const timer = setTimeout(() => {
878
+ this._eventEmitter.removeAllListeners(i)
879
+ reject(new Error(msg))
880
+ }, this._timeout)
881
+ this._eventEmitter.once(i, ({ e, r }) => {
870
882
  clearTimeout(timer)
871
- if (data.e) {
872
- reject(new Error(data.e))
883
+ if (e) {
884
+ reject(new Error(e))
873
885
  } else {
874
- const proxy = this._createProxy(agent, className, data.r)
886
+ const proxy = this._createProxy(agent, className, r)
875
887
  resolve(proxy)
876
888
  }
877
889
  })
@@ -891,7 +903,11 @@ class VrpcRemote extends EventEmitter {
891
903
  }
892
904
  timer = setTimeout(() => {
893
905
  this.removeListener('class', checkClass)
894
- reject(new Error(`Proxy creation for class "${className}" on agent "${agent}" and domain "${this._domain}" timed out (> ${this._timeout} ms)`))
906
+ reject(
907
+ new Error(
908
+ `Proxy creation for class "${className}" on agent "${agent}" and domain "${this._domain}" timed out (> ${this._timeout} ms)`
909
+ )
910
+ )
895
911
  }, this._timeout)
896
912
  this.on('class', checkClass)
897
913
  })
@@ -907,10 +923,11 @@ class VrpcRemote extends EventEmitter {
907
923
 
908
924
  _createProxy (agent, className, instance) {
909
925
  const targetTopic = `${this._domain}/${agent}/${className}/${instance}`
910
- const proxyId = crypto.randomBytes(2).toString('hex')
926
+ const proxyId = `${this._instance}-${this._proxyId++}`
911
927
  const proxy = {
912
- _targetId: instance,
913
- _proxyId: proxyId
928
+ vrpcClientId: this._vrpcClientId,
929
+ vrpcInstanceId: instance,
930
+ vrpcProxyId: proxyId
914
931
  }
915
932
  let functions = this._agents[agent].classes[className].memberFunctions
916
933
  // Strip off argument signature
@@ -924,37 +941,37 @@ class VrpcRemote extends EventEmitter {
924
941
  // Build proxy
925
942
  uniqueFuncs.forEach(name => {
926
943
  proxy[name] = async (...args) => {
944
+ const wrapped = this._wrapArguments(proxyId, name, ...args)
945
+ if (!wrapped) return // Skipping remote call -> handled locally
927
946
  try {
928
947
  const json = {
929
- context: instance,
930
- method: name,
931
- id: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
932
- sender: this._vrpcClientId,
933
- data: this._packData(proxyId, name, ...args)
948
+ c: instance,
949
+ f: name,
950
+ i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
951
+ s: this._vrpcClientId,
952
+ a: wrapped,
953
+ v: VRPC_PROTOCOL_VERSION
934
954
  }
935
- // Skipping remote call -> handled locally
936
- if (json.data === null) return
937
955
  this._mqttPublish(`${targetTopic}/${name}`, JSON.stringify(json))
938
956
  return this._handleAgentAnswer(json)
939
957
  } catch (err) {
940
- throw new Error(`Could not remotely call "${name}" because: ${err.message}`)
958
+ throw new Error(
959
+ `Could not remotely call "${name}" because: ${err.message}`
960
+ )
941
961
  }
942
962
  }
943
963
  })
944
964
  return proxy
945
965
  }
946
966
 
947
- async _handleAgentAnswer ({ id, context, method }) {
967
+ async _handleAgentAnswer ({ i, c, f }) {
948
968
  return new Promise((resolve, reject) => {
949
- const msg = `Function call "${context}::${method}()" timed out (> ${this._timeout} ms)`
950
- const timer = setTimeout(
951
- () => {
952
- this._eventEmitter.removeAllListeners(id)
953
- reject(new Error(msg))
954
- },
955
- this._timeout
956
- )
957
- this._eventEmitter.once(id, data => {
969
+ const msg = `Function call "${c}::${f}()" timed out (> ${this._timeout} ms)`
970
+ const timer = setTimeout(() => {
971
+ this._eventEmitter.removeAllListeners(i)
972
+ reject(new Error(msg))
973
+ }, this._timeout)
974
+ this._eventEmitter.once(i, data => {
958
975
  clearTimeout(timer)
959
976
  if (data.e) {
960
977
  reject(new Error(data.e))
@@ -976,7 +993,7 @@ class VrpcRemote extends EventEmitter {
976
993
 
977
994
  async _waitForInstance (instance, options = {}) {
978
995
  return new Promise((resolve, reject) => {
979
- const handler = (timer) => (instances, { agent, className }) => {
996
+ const handler = timer => (instances, { agent, className }) => {
980
997
  if (instances.includes(instance)) {
981
998
  if (options.agent && agent !== options.agent) return
982
999
  if (options.className && className !== options.className) return
@@ -985,75 +1002,64 @@ class VrpcRemote extends EventEmitter {
985
1002
  resolve({ agent, className, instance })
986
1003
  }
987
1004
  }
988
- const timer = setTimeout(
989
- () => {
990
- this.removeListener('instanceNew', handler(timer))
991
- const msg = `Could not find instance: ${instance} (> ${this._timeout} ms)`
992
- reject(new Error(msg))
993
- },
994
- this._timeout
995
- )
1005
+ const timer = setTimeout(() => {
1006
+ this.removeListener('instanceNew', handler(timer))
1007
+ const msg = `Could not find instance: ${instance} (> ${this._timeout} ms)`
1008
+ reject(new Error(msg))
1009
+ }, this._timeout)
996
1010
  this.on('instanceNew', handler(timer))
997
1011
  })
998
1012
  }
999
1013
 
1000
- _packData (proxyId, functionName, ...args) {
1001
- const data = {}
1014
+ _wrapArguments (remoteId, functionName, ...args) {
1015
+ const wrapped = []
1002
1016
  let isHandledLocally = false
1003
- args.forEach((value, index) => {
1017
+ args.forEach((x, i) => {
1004
1018
  // Check whether provided argument is a function
1005
- if (this._isFunction(value)) {
1019
+ if (this._isFunction(x)) {
1006
1020
  // Check special case of an event emitter registration
1007
1021
  // We test three conditions:
1008
1022
  // 1) functionName must be "on"
1009
1023
  // 2) callback is second argument
1010
1024
  // 3) first argument was string
1011
- if (functionName === 'on' &&
1012
- index === 1 &&
1013
- typeof args[0] === 'string'
1014
- ) {
1015
- const id = this._addEventSubscription(proxyId, args[0], value)
1025
+ if (functionName === 'on' && i === 1 && typeof args[0] === 'string') {
1026
+ const id = this._addEventSubscription(remoteId, args[0], x)
1016
1027
  if (!id) isHandledLocally = true
1017
- data[`_${index + 1}`] = id
1028
+ wrapped.push(id)
1018
1029
  } else if (
1019
1030
  (functionName === 'off' || functionName === 'removeListener') &&
1020
- index === 1 &&
1031
+ i === 1 &&
1021
1032
  typeof args[0] === 'string'
1022
1033
  ) {
1023
- const id = this._removeEventSubscription(proxyId, args[0], value)
1034
+ const id = this._removeEventSubscription(remoteId, args[0], x)
1024
1035
  if (!id) isHandledLocally = true
1025
- data[`_${index + 1}`] = id
1026
- // Regular function callback
1036
+ wrapped.push(id)
1037
+ // Regular function callback
1027
1038
  } else {
1028
- const id = `__f__${proxyId}-${functionName}-${index}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`
1029
- data[`_${index + 1}`] = id
1030
- this._eventEmitter.once(id, data => {
1031
- const args = Object.keys(data).sort()
1032
- .filter(x => x[0] === '_')
1033
- .map(x => data[x])
1034
- value.apply(null, args) // This is the actual function call
1039
+ const id = `__f__${remoteId}-${functionName}-${i}-${this._invokeId++ %
1040
+ Number.MAX_SAFE_INTEGER}`
1041
+ wrapped.push(id)
1042
+ this._eventEmitter.once(id, ({ a }) => {
1043
+ x.apply(null, a) // This is the actual function call
1035
1044
  })
1036
1045
  }
1037
- } else if (this._isEmitter(value)) {
1038
- const { emitter, event } = value
1039
- const id = `__f__${proxyId}-${functionName}-${index}-${event}`
1040
- data[`_${index + 1}`] = id
1041
- this._eventEmitter.on(id, data => {
1042
- const args = Object.keys(data).sort()
1043
- .filter(x => x[0] === '_')
1044
- .map(x => data[x])
1045
- emitter.emit(event, ...args)
1046
+ } else if (this._isEmitter(x)) {
1047
+ const { emitter, event } = x
1048
+ const id = `__f__${remoteId}-${functionName}-${i}-${event}`
1049
+ wrapped.push(id)
1050
+ this._eventEmitter.on(id, ({ a }) => {
1051
+ emitter.emit(event, ...a)
1046
1052
  })
1047
1053
  } else {
1048
- data[`_${index + 1}`] = value
1054
+ wrapped.push(x)
1049
1055
  }
1050
1056
  })
1051
1057
  if (isHandledLocally) return null
1052
- return data
1058
+ return wrapped
1053
1059
  }
1054
1060
 
1055
- _addEventSubscription (proxyId, event, callback) {
1056
- const id = `__f__${proxyId}-${event}`
1061
+ _addEventSubscription (remoteId, event, callback) {
1062
+ const id = `__f__${remoteId}-${event}`
1057
1063
  if (this._cachedSubscriptions.has(id)) {
1058
1064
  this._cachedSubscriptions.get(id).on(id, callback)
1059
1065
  return
@@ -1061,17 +1067,14 @@ class VrpcRemote extends EventEmitter {
1061
1067
  const emitter = new EventEmitter()
1062
1068
  emitter.on(id, callback)
1063
1069
  this._cachedSubscriptions.set(id, emitter)
1064
- this._eventEmitter.on(id, (data) => {
1065
- const args = Object.keys(data).sort()
1066
- .filter(x => x[0] === '_')
1067
- .map(x => data[x])
1068
- emitter.emit(id, ...args)
1070
+ this._eventEmitter.on(id, ({ a }) => {
1071
+ emitter.emit(id, ...a)
1069
1072
  })
1070
1073
  return id
1071
1074
  }
1072
1075
 
1073
- _removeEventSubscription (proxyId, event, callback) {
1074
- const id = `__f__${proxyId}-${event}`
1076
+ _removeEventSubscription (remoteId, event, callback) {
1077
+ const id = `__f__${remoteId}-${event}`
1075
1078
  if (this._cachedSubscriptions.has(id)) {
1076
1079
  const emitter = this._cachedSubscriptions.get(id)
1077
1080
  emitter.removeListener(id, callback)
@@ -1092,8 +1095,10 @@ class VrpcRemote extends EventEmitter {
1092
1095
  _isFunction (variable) {
1093
1096
  const getType = {}
1094
1097
  const type = getType.toString.call(variable)
1095
- return variable &&
1098
+ return (
1099
+ variable &&
1096
1100
  (type === '[object Function]' || type === '[object AsyncFunction]')
1101
+ )
1097
1102
  }
1098
1103
 
1099
1104
  _isEmitter (variable) {
@@ -1109,7 +1114,9 @@ class VrpcRemote extends EventEmitter {
1109
1114
  async _getInstanceData (instance, options = {}) {
1110
1115
  const instanceData = this._getInstanceFromCache(instance, options)
1111
1116
  if (!instanceData) {
1112
- if (options.noWait) throw new Error(`Could not find instance: ${instance}`)
1117
+ if (options.noWait) {
1118
+ throw new Error(`Could not find instance: ${instance}`)
1119
+ }
1113
1120
  return this._waitForInstance(instance, options)
1114
1121
  }
1115
1122
  return instanceData
@@ -1148,7 +1155,7 @@ class VrpcRemote extends EventEmitter {
1148
1155
  * This event is fired whenever an agent is added or removed, or whenever
1149
1156
  * an agent changes its status (switches between online or offline).
1150
1157
  *
1151
- * @event VrpcRemote#agent
1158
+ * @event VrpcClient#agent
1152
1159
  * @param {Object} info
1153
1160
  * @param {String} info.domain - Domain name
1154
1161
  * @param {String} info.agent - Agent name
@@ -1163,7 +1170,7 @@ class VrpcRemote extends EventEmitter {
1163
1170
  * Emitted whenever a class is added or removed, or when instances
1164
1171
  * or functions of this class have changed.
1165
1172
  *
1166
- * @event VrpcRemote#class
1173
+ * @event VrpcClient#class
1167
1174
  * @param {Object} info
1168
1175
  * @param {String} info.domain - Domain name
1169
1176
  * @param {String} info.agent - Agent name
@@ -1175,57 +1182,57 @@ class VrpcRemote extends EventEmitter {
1175
1182
  */
1176
1183
 
1177
1184
  /**
1178
- * Event 'instanceNew'
1179
- *
1180
- * Emitted whenever a new instance was created.
1181
- *
1182
- * @event VrpcRemote#instanceNew
1183
- *
1184
- * @param {Array.<String>} addedInstances - An array of newly added instances
1185
- * @param {Object} info
1186
- * @param {String} info.domain - Domain name
1187
- * @param {String} info.agent - Agent name
1188
- * @param {String} info.className - Class name
1189
- */
1185
+ * Event 'instanceNew'
1186
+ *
1187
+ * Emitted whenever a new instance was created.
1188
+ *
1189
+ * @event VrpcClient#instanceNew
1190
+ *
1191
+ * @param {Array.<String>} addedInstances - An array of newly added instances
1192
+ * @param {Object} info
1193
+ * @param {String} info.domain - Domain name
1194
+ * @param {String} info.agent - Agent name
1195
+ * @param {String} info.className - Class name
1196
+ */
1190
1197
 
1191
1198
  /**
1192
- * Event 'instanceGone'
1193
- *
1194
- * Emitted whenever a new instance was removed.
1195
- *
1196
- * @event VrpcRemote#instanceGone
1197
- *
1198
- * @param {Array.<String>} removedInstances - An array of removed instances
1199
- * @param {Object} info
1200
- * @param {String} info.domain - Domain name
1201
- * @param {String} info.agent - Agent name
1202
- * @param {String} info.className - Class name
1203
- */
1199
+ * Event 'instanceGone'
1200
+ *
1201
+ * Emitted whenever a new instance was removed.
1202
+ *
1203
+ * @event VrpcClient#instanceGone
1204
+ *
1205
+ * @param {Array.<String>} removedInstances - An array of removed instances
1206
+ * @param {Object} info
1207
+ * @param {String} info.domain - Domain name
1208
+ * @param {String} info.agent - Agent name
1209
+ * @param {String} info.className - Class name
1210
+ */
1204
1211
 
1205
1212
  /**
1206
1213
  * Event 'connect'
1207
1214
  *
1208
1215
  * Emitted on successful (re)connection (i.e. connack rc=0).
1209
1216
  *
1210
- * @event VrpcRemote#connect
1217
+ * @event VrpcClient#connect
1211
1218
  * @type {Object}
1212
1219
  * @property {Boolean} sessionPresent - A session from a previous connection is already present
1213
- */
1220
+ */
1214
1221
 
1215
1222
  /**
1216
1223
  * Event 'reconnect'
1217
1224
  *
1218
1225
  * Emitted when a reconnect starts.
1219
1226
  *
1220
- * @event VrpcRemote#reconnect
1221
- */
1227
+ * @event VrpcClient#reconnect
1228
+ */
1222
1229
 
1223
1230
  /**
1224
1231
  * Event 'close'
1225
1232
  *
1226
1233
  * Emitted after a disconnection.
1227
1234
  *
1228
- * @event VrpcRemote#close
1235
+ * @event VrpcClient#close
1229
1236
  */
1230
1237
 
1231
1238
  /**
@@ -1233,7 +1240,7 @@ class VrpcRemote extends EventEmitter {
1233
1240
  *
1234
1241
  * Emitted when the client goes offline.
1235
1242
  *
1236
- * @event VrpcRemote#offline
1243
+ * @event VrpcClient#offline
1237
1244
  */
1238
1245
 
1239
1246
  /**
@@ -1248,7 +1255,7 @@ class VrpcRemote extends EventEmitter {
1248
1255
  * - EADDRINUSE
1249
1256
  * - ENOTFOUND
1250
1257
  *
1251
- * @event VrpcRemote#error
1258
+ * @event VrpcClient#error
1252
1259
  * @type {Object} Error
1253
1260
  */
1254
1261
 
@@ -1258,7 +1265,7 @@ class VrpcRemote extends EventEmitter {
1258
1265
  * Emitted when mqtt.Client#end() is called. If a callback was passed to
1259
1266
  * mqtt.Client#end(), this event is emitted once the callback returns.
1260
1267
  *
1261
- * @event VrpcRemote#end
1268
+ * @event VrpcClient#end
1262
1269
  */
1263
1270
 
1264
- module.exports = VrpcRemote
1271
+ module.exports = VrpcClient