vrpc 2.4.0 → 3.0.0-alpha.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.
@@ -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,14 +108,16 @@ 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()
120
+ this._proxies = {}
112
121
  if (log === 'console') {
113
122
  this._log = console
114
123
  this._log.debug = () => {}
@@ -117,6 +126,15 @@ class VrpcRemote extends EventEmitter {
117
126
  }
118
127
  }
119
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
+
120
138
  /**
121
139
  * Actually connects to the MQTT broker.
122
140
  *
@@ -153,15 +171,15 @@ class VrpcRemote extends EventEmitter {
153
171
  if (password === undefined) delete options.password
154
172
  this._client = mqtt.connect(this._broker, options)
155
173
 
156
- this._client.on('error', (err) => {
174
+ this._client.on('error', err => {
157
175
  this.emit('error', err)
158
176
  })
159
177
 
160
- this._client.stream.on('error', (err) => {
178
+ this._client.stream.on('error', err => {
161
179
  this.emit('error', err)
162
180
  })
163
181
 
164
- this.on('error', (err) => {
182
+ this.on('error', err => {
165
183
  this._log.debug(`Encountered MQTT error: ${err.message}`)
166
184
  })
167
185
 
@@ -193,7 +211,7 @@ class VrpcRemote extends EventEmitter {
193
211
  this._agents[agent].hostname = hostname
194
212
  this._agents[agent].version = version
195
213
  this.emit('agent', { domain, agent, status, hostname, version })
196
- // ClassInfo message
214
+ // ClassInfo message
197
215
  } else if (instance === '__classInfo__') {
198
216
  // Json properties: { klass, instances, memberFunctions, staticFunctions }
199
217
  const json = JSON.parse(message.toString())
@@ -211,24 +229,23 @@ class VrpcRemote extends EventEmitter {
211
229
  staticFunctions,
212
230
  meta
213
231
  } = json
214
- if (removed.length !== 0) this.emit('instanceGone', removed, { domain, agent, className })
215
- if (added.length !== 0) this.emit('instanceNew', added, { domain, agent, className })
216
- this.emit(
217
- 'class',
218
- {
219
- domain,
220
- agent,
221
- className,
222
- instances,
223
- memberFunctions,
224
- staticFunctions,
225
- meta
226
- }
227
- )
228
- // 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
229
246
  } else {
230
- const { id, data } = JSON.parse(message.toString())
231
- this._eventEmitter.emit(id, data)
247
+ const { i, a, e, r } = JSON.parse(message.toString())
248
+ this._eventEmitter.emit(i, { a, e, r })
232
249
  }
233
250
  })
234
251
  await new Promise((resolve, reject) => {
@@ -242,11 +259,6 @@ class VrpcRemote extends EventEmitter {
242
259
  })
243
260
  }
244
261
 
245
- async connected () {
246
- this._indicateDeprecation('VrpcRemote.connected(): This API usage is deprecated, use `connect()` instead.')
247
- return this.connect()
248
- }
249
-
250
262
  /**
251
263
  * Creates a new remote instance and provides a proxy to it.
252
264
  *
@@ -272,6 +284,8 @@ class VrpcRemote extends EventEmitter {
272
284
  * @param {Array} [options.args] Positional arguments for the constructor call
273
285
  * @param {String} [options.agent] Agent name. If not provided class default
274
286
  * is used
287
+ * @param {bool} [options.cacheProxy=false] If true the proxy object for a given
288
+ * instance is cached and used in subsequent calls
275
289
  * @returns {Promise<Proxy>} Object reflecting a proxy to the original one
276
290
  * handled by the agent
277
291
  * @example
@@ -293,24 +307,24 @@ class VrpcRemote extends EventEmitter {
293
307
  */
294
308
  async create ({
295
309
  className,
296
- instance,
310
+ instance = nanoid(8),
297
311
  args = [],
298
- agent = this._agent
312
+ agent = this._agent,
313
+ cacheProxy = false,
314
+ isIsolated = false
299
315
  } = {}) {
300
316
  if (agent === '*') throw new Error('Agent must be specified')
301
- const data = instance ? { _1: instance } : {}
302
- const offset = instance ? 2 : 1
303
- args.forEach((value, index) => {
304
- data[`_${index + offset}`] = value
305
- })
306
317
  const json = {
307
- context: className,
308
- method: instance ? '__createNamed__' : '__create__',
309
- id: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
310
- sender: `${this._domain}/${os.hostname()}/${this._instance}`,
311
- 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
312
324
  }
313
- return this._getProxy(agent, className, json)
325
+ const proxy = await this._getProxy(agent, className, json)
326
+ if (instance && cacheProxy) this._proxies[instance] = proxy
327
+ return proxy
314
328
  }
315
329
 
316
330
  /**
@@ -328,6 +342,7 @@ class VrpcRemote extends EventEmitter {
328
342
  * @returns {Promise<Proxy>} Proxy object reflecting the remotely existing instance
329
343
  */
330
344
  async getInstance (instance, options) {
345
+ if (this._proxies[instance]) return this._proxies[instance]
331
346
  if (typeof instance === 'string') {
332
347
  const {
333
348
  agent,
@@ -335,11 +350,12 @@ class VrpcRemote extends EventEmitter {
335
350
  instance: instanceString
336
351
  } = await this._getInstanceData(instance, options)
337
352
  return this._createProxy(agent, className, instanceString)
338
- } else { // deprecate this
353
+ } else {
354
+ // deprecate this
339
355
  this._indicateDeprecation(
340
- 'VrpcRemote.getInstance(): ' +
341
- 'Using an object as single argument is deprecated, ' +
342
- 'use "getInstance(instance, options)" instead.'
356
+ 'VrpcClient.getInstance(): ' +
357
+ 'Using an object as single argument is deprecated, ' +
358
+ 'use "getInstance(instance, options)" instead.'
343
359
  )
344
360
  const fakedOptions = { ...instance }
345
361
  const fakedInstance = fakedOptions.instance
@@ -370,24 +386,27 @@ class VrpcRemote extends EventEmitter {
370
386
  if (typeof instance === 'string') {
371
387
  instanceData = await this._getInstanceData(instance)
372
388
  if (options) instanceData = { ...options, ...instanceData }
373
- } else { // deprecate this
389
+ } else {
390
+ // deprecate this
374
391
  this._indicateDeprecation(
375
- 'VrpcRemote.delete(): ' +
376
- 'Using an object as single argument is deprecated, ' +
377
- 'use "delete(instance, options)" instead.'
392
+ 'VrpcClient.delete(): ' +
393
+ 'Using an object as single argument is deprecated, ' +
394
+ 'use "delete(instance, options)" instead.'
378
395
  )
379
396
  instanceData = { ...instanceData, ...instance }
380
397
  }
381
398
  const { agent, className, instance: instanceString } = instanceData
382
399
  const json = {
383
- context: className,
384
- method: '__delete__',
385
- id: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
386
- sender: `${this._domain}/${os.hostname()}/${this._instance}`,
387
- 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
388
406
  }
389
407
  const topic = `${this._domain}/${agent}/${className}/__static__/__delete__`
390
408
  this._mqttPublish(topic, JSON.stringify(json))
409
+ if (this._proxies[instance]) delete this._proxies[instance]
391
410
  return this._handleAgentAnswer(json)
392
411
  }
393
412
 
@@ -407,12 +426,15 @@ class VrpcRemote extends EventEmitter {
407
426
  args = [],
408
427
  agent = this._agent
409
428
  } = {}) {
429
+ const wrapped = this._wrapArguments(className, functionName, ...args)
430
+ if (!wrapped) return // Skipping remote call -> handled locally
410
431
  const json = {
411
- context: className,
412
- method: functionName,
413
- id: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
414
- sender: this._vrpcClientId,
415
- 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
416
438
  }
417
439
  const topic = `${this._domain}/${agent}/${className}/__static__/${functionName}`
418
440
  await this._waitUntilClassIsOnline(agent, className)
@@ -422,19 +444,23 @@ class VrpcRemote extends EventEmitter {
422
444
 
423
445
  /**
424
446
  * Calls the same function on all instances of a given class and returns an
425
- * 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.
426
450
  *
427
451
  * NOTE: When no agent was specified as class default and no agent is
428
452
  * specified when calling this function, callAll will act on the requested
429
- * class across all available agents. The same is true when explicitly using
430
- * a wildcard (*) as agent value.
453
+ * class across all available agents. The same is true when explicitly using a
454
+ * wildcard (*) as agent value.
431
455
  *
432
456
  * @param {Object} options
433
457
  * @param {String} options.className Name of the static function's class
434
- * @param {Array} [options.args] Positional arguments of the static function call
435
- * @param {String} [options.agent] Agent name. If not provided class default is used
436
- * @returns {Promise<Object[]>} An array of objects `{ id, val, err }` carrying
437
- * 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
438
464
  */
439
465
  async callAll ({
440
466
  className,
@@ -442,20 +468,26 @@ class VrpcRemote extends EventEmitter {
442
468
  args = [],
443
469
  agent = this._agent
444
470
  } = {}) {
445
- const data = { _1: functionName }
446
- args.forEach((value, index) => (data[`_${index + 2}`] = value))
447
471
  const json = {
448
- context: className,
449
- method: functionName,
450
- id: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
451
- sender: this._vrpcClientId,
452
- 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
453
478
  }
454
479
  if (agent === '*') {
455
480
  const result = []
456
481
  const agents = this.getAvailableAgents()
457
482
  for (const x of agents) {
458
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]
459
491
  await this._waitUntilClassIsOnline(x, className)
460
492
  this._mqttPublish(topic, JSON.stringify(json))
461
493
  const tmp = await this._handleAgentAnswer(json)
@@ -464,41 +496,22 @@ class VrpcRemote extends EventEmitter {
464
496
  return result
465
497
  }
466
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]
467
506
  await this._waitUntilClassIsOnline(agent, className)
468
507
  this._mqttPublish(topic, JSON.stringify(json))
469
508
  return this._handleAgentAnswer(json)
470
509
  }
471
510
 
472
- /**
473
- * Retrieves all agents, instances, classes, member and static
474
- * functions potentially available for remote control.
475
- * @deprecated
476
- * @returns {Object} SystemInformation
477
- * ```ts
478
- * type SystemInformation = {
479
- * [domain].agents[agent].status: string, // 'offline' or 'online'
480
- * [domain].agents[agent].hostname: string,
481
- * [domain].agents[agent].version: string,
482
- * [domain].agents[agent].classes[className].instances: string[],
483
- * [domain].agents[agent].classes[className].memberFunctions: string[],
484
- * [domain].agents[agent].classes[className].staticFunctions: string[],
485
- * [domain].agents[agent].classes[className].meta?: MetaData
486
- * }
487
- * ```
488
- */
489
- getAvailabilities () {
490
- this._indicateDeprecation(
491
- 'VrpcRemote.getAvailabilities(): ' +
492
- 'This function is deprecated and will be removed soon, ' +
493
- 'use `getSystemInformation()` instead.'
494
- )
495
- return { [this._domain]: { agents: this._agents } }
496
- }
497
-
498
511
  /**
499
512
  * Retrieves all information about the currently available components.
500
513
  *
501
- * @returns {Object} SystemInformation
514
+ * @returns {Object} SystemInformation
502
515
  * ```ts
503
516
  * type SystemInformation = {
504
517
  * [agent].status: string, // 'offline' or 'online'
@@ -515,21 +528,6 @@ class VrpcRemote extends EventEmitter {
515
528
  return this._agents
516
529
  }
517
530
 
518
- /**
519
- * Retrieves all domains on which agents can be remote controlled
520
- *
521
- * @deprecated
522
- * @returns {Array} Array of domain names
523
- */
524
- getAvailableDomains () {
525
- this._indicateDeprecation(
526
- 'VrpcRemote.getAvailableDomains(): ' +
527
- 'this function will be removed in the next major release, as the client' +
528
- 'works with a single domain anyways'
529
- )
530
- return [this._domain]
531
- }
532
-
533
531
  /**
534
532
  * Retrieves all available agents.
535
533
  *
@@ -542,8 +540,8 @@ class VrpcRemote extends EventEmitter {
542
540
  // `options = {}` was `domain = this._domain` before
543
541
  if (typeof options === 'string') {
544
542
  this._indicateDeprecation(
545
- 'VrpcRemote.getAvailableAgents(): ' +
546
- '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.'
547
545
  )
548
546
  options = { mustBeOnline: true }
549
547
  }
@@ -570,16 +568,19 @@ class VrpcRemote extends EventEmitter {
570
568
  * @param {Boolean} [options.mustBeOnline=true] Only retrieve currently online classes
571
569
  * @returns {Array} Array of class names.
572
570
  */
573
- getAvailableClasses (options = { agent: this._agent, mustBeOnline: true }, domain) {
571
+ getAvailableClasses (
572
+ options = { agent: this._agent, mustBeOnline: true },
573
+ domain
574
+ ) {
574
575
  /// DEPRECATION INFORMATION AND WORKAROUND - TO BE REMOVED IN 3.x
575
576
  let agent = this._agent
576
577
  let mustBeOnline = true
577
578
  // `options = {}` was `agent = this._agent` before
578
579
  if (typeof options === 'string') {
579
580
  this._indicateDeprecation(
580
- 'VrpcRemote.getAvailableClasses(): ' +
581
- 'setting the agent as string argument is deprecated.`' +
582
- 'Use `{ agent: <myAgent> }` instead.'
581
+ 'VrpcClient.getAvailableClasses(): ' +
582
+ 'setting the agent as string argument is deprecated.`' +
583
+ 'Use `{ agent: <myAgent> }` instead.'
583
584
  )
584
585
  agent = options
585
586
  } else {
@@ -589,8 +590,8 @@ class VrpcRemote extends EventEmitter {
589
590
  // was `domain = this._domain` before
590
591
  if (domain) {
591
592
  this._indicateDeprecation(
592
- 'VrpcRemote.getAvailableClasses(): ' +
593
- '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.'
594
595
  )
595
596
  }
596
597
  ///
@@ -622,9 +623,9 @@ class VrpcRemote extends EventEmitter {
622
623
  // `options = {}` was `agent = this._agent` before
623
624
  if (typeof options === 'string') {
624
625
  this._indicateDeprecation(
625
- 'VrpcRemote.getAvailableInstances(): ' +
626
- 'Setting the agent as string argument is deprecated.`' +
627
- 'Use `{ agent: <myAgent> }` instead.'
626
+ 'VrpcClient.getAvailableInstances(): ' +
627
+ 'Setting the agent as string argument is deprecated.`' +
628
+ 'Use `{ agent: <myAgent> }` instead.'
628
629
  )
629
630
  agent = options
630
631
  } else {
@@ -634,8 +635,8 @@ class VrpcRemote extends EventEmitter {
634
635
  // was `domain = this._domain` before
635
636
  if (domain) {
636
637
  this._indicateDeprecation(
637
- 'VrpcRemote.getAvailableInstances(): ' +
638
- '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.'
639
640
  )
640
641
  }
641
642
  ///
@@ -667,9 +668,9 @@ class VrpcRemote extends EventEmitter {
667
668
  // `options = {}` was `agent = this._agent` before
668
669
  if (typeof options === 'string') {
669
670
  this._indicateDeprecation(
670
- 'VrpcRemote.getAvailableMemberFunctions(): ' +
671
- 'Setting the agent as string argument is deprecated.`' +
672
- 'Use `{ agent: <myAgent> }` instead.'
671
+ 'VrpcClient.getAvailableMemberFunctions(): ' +
672
+ 'Setting the agent as string argument is deprecated.`' +
673
+ 'Use `{ agent: <myAgent> }` instead.'
673
674
  )
674
675
  agent = options
675
676
  } else {
@@ -679,8 +680,8 @@ class VrpcRemote extends EventEmitter {
679
680
  // was `domain = this._domain` before
680
681
  if (domain) {
681
682
  this._indicateDeprecation(
682
- 'VrpcRemote.getAvailableMemberFunctions(): ' +
683
- '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.'
684
685
  )
685
686
  }
686
687
  ///
@@ -714,9 +715,9 @@ class VrpcRemote extends EventEmitter {
714
715
  // `options = {}` was `agent = this._agent` before
715
716
  if (typeof options === 'string') {
716
717
  this._indicateDeprecation(
717
- 'VrpcRemote.getAvailableStaticFunctions(): ' +
718
- 'Setting the agent as string argument is deprecated.`' +
719
- 'Use `{ agent: <myAgent> }` instead.'
718
+ 'VrpcClient.getAvailableStaticFunctions(): ' +
719
+ 'Setting the agent as string argument is deprecated.`' +
720
+ 'Use `{ agent: <myAgent> }` instead.'
720
721
  )
721
722
  agent = options
722
723
  } else {
@@ -726,8 +727,8 @@ class VrpcRemote extends EventEmitter {
726
727
  // was `domain = this._domain` before
727
728
  if (domain) {
728
729
  this._indicateDeprecation(
729
- 'VrpcRemote.getAvailableStaticFunctions(): ' +
730
- '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.'
731
732
  )
732
733
  }
733
734
  ///
@@ -749,10 +750,7 @@ class VrpcRemote extends EventEmitter {
749
750
  * @param {String} options.agent Agent name. If not provided class default is used
750
751
  * @returns {Promise} Promise that resolves once re-connected
751
752
  */
752
- async reconnectWithToken (
753
- token,
754
- { agent = this._agent } = {}
755
- ) {
753
+ async reconnectWithToken (token, { agent = this._agent } = {}) {
756
754
  this._token = token
757
755
  this._agent = agent
758
756
  this._client.end(() => this.connect())
@@ -791,19 +789,30 @@ class VrpcRemote extends EventEmitter {
791
789
  async end () {
792
790
  this._mqttPublish(
793
791
  `${this._vrpcClientId}/__clientInfo__`,
794
- JSON.stringify({ status: 'offline' })
792
+ JSON.stringify({ status: 'offline', v: VRPC_PROTOCOL_VERSION })
795
793
  )
796
794
  return new Promise(resolve => this._client.end(false, {}, resolve))
797
795
  }
798
796
 
799
797
  _createMqttClientId () {
800
- const clientInfo = os.arch() + JSON.stringify(os.cpus()) + os.homedir() +
801
- os.hostname() + JSON.stringify(os.networkInterfaces()) + os.platform() +
802
- 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()
803
808
  // console.log('ClientInfo:', clientInfo)
804
- const md5 = crypto.createHash('md5').update(clientInfo).digest('hex').substr(0, 13)
809
+ const md5 = crypto
810
+ .createHash('md5')
811
+ .update(clientInfo)
812
+ .digest('hex')
813
+ .substr(0, 13)
805
814
  // FIXME (3.x): use vrpcp -> vrpcc
806
- return `vrpcp${this._instance}X${md5}` // 5 + 4 + 1 + 13 = 23 (max clientId)
815
+ return `vc${this._instance}${md5}` // 2 + 8 + 13 = 23 (max clientId)
807
816
  }
808
817
 
809
818
  _createVrpcClientId () {
@@ -811,29 +820,44 @@ class VrpcRemote extends EventEmitter {
811
820
  }
812
821
 
813
822
  _mqttPublish (topic, message, options) {
814
- this._client.publish(topic, message, { qos: this._qos, ...options }, (err) => {
815
- if (err) {
816
- this._log.warn(`Could not publish MQTT message because: ${err.message}`)
823
+ this._client.publish(
824
+ topic,
825
+ message,
826
+ { qos: this._qos, ...options },
827
+ err => {
828
+ if (err) {
829
+ this._log.warn(
830
+ `Could not publish MQTT message because: ${err.message}`
831
+ )
832
+ }
817
833
  }
818
- })
834
+ )
819
835
  }
820
836
 
821
837
  _mqttSubscribe (topic, options) {
822
- this._client.subscribe(topic, { qos: this._qos, ...options }, (err, granted) => {
823
- if (err) {
824
- this._log.warn(`Could not subscribe to topic: ${topic} because: ${err.message}`)
825
- } else {
826
- if (granted.length === 0) {
827
- this._log.debug(`Already subscribed to: ${topic}`)
838
+ this._client.subscribe(
839
+ topic,
840
+ { qos: this._qos, ...options },
841
+ (err, granted) => {
842
+ if (err) {
843
+ this._log.warn(
844
+ `Could not subscribe to topic: ${topic} because: ${err.message}`
845
+ )
846
+ } else {
847
+ if (granted.length === 0) {
848
+ this._log.debug(`Already subscribed to: ${topic}`)
849
+ }
828
850
  }
829
851
  }
830
- })
852
+ )
831
853
  }
832
854
 
833
855
  _mqttUnsubscribe (topic, options) {
834
- this._client.unsubscribe(topic, options, (err) => {
856
+ this._client.unsubscribe(topic, options, err => {
835
857
  if (err) {
836
- this._log.warn(`Could not unsubscribe from topic: ${topic} because: ${err.message}`)
858
+ this._log.warn(
859
+ `Could not unsubscribe from topic: ${topic} because: ${err.message}`
860
+ )
837
861
  }
838
862
  })
839
863
  }
@@ -845,25 +869,22 @@ class VrpcRemote extends EventEmitter {
845
869
  }
846
870
 
847
871
  async _getProxy (agent, className, json) {
848
- const { method } = json
849
- const topic = `${this._domain}/${agent}/${className}/__static__/${method}`
872
+ const { f, i } = json
873
+ const topic = `${this._domain}/${agent}/${className}/__static__/${f}`
850
874
  await this._waitUntilClassIsOnline(agent, className)
851
875
  this._mqttPublish(topic, JSON.stringify(json))
852
876
  return new Promise((resolve, reject) => {
853
877
  const msg = `Proxy creation for class "${className}" on agent "${agent}" and domain "${this._domain}" timed out (> ${this._timeout} ms)`
854
- const timer = setTimeout(
855
- () => {
856
- this._eventEmitter.removeAllListeners(json.id)
857
- reject(new Error(msg))
858
- },
859
- this._timeout
860
- )
861
- this._eventEmitter.once(json.id, data => {
878
+ const timer = setTimeout(() => {
879
+ this._eventEmitter.removeAllListeners(i)
880
+ reject(new Error(msg))
881
+ }, this._timeout)
882
+ this._eventEmitter.once(i, ({ e, r }) => {
862
883
  clearTimeout(timer)
863
- if (data.e) {
864
- reject(new Error(data.e))
884
+ if (e) {
885
+ reject(new Error(e))
865
886
  } else {
866
- const proxy = this._createProxy(agent, className, data.r)
887
+ const proxy = this._createProxy(agent, className, r)
867
888
  resolve(proxy)
868
889
  }
869
890
  })
@@ -883,7 +904,11 @@ class VrpcRemote extends EventEmitter {
883
904
  }
884
905
  timer = setTimeout(() => {
885
906
  this.removeListener('class', checkClass)
886
- reject(new Error(`Proxy creation for class "${className}" on agent "${agent}" and domain "${this._domain}" timed out (> ${this._timeout} ms)`))
907
+ reject(
908
+ new Error(
909
+ `Proxy creation for class "${className}" on agent "${agent}" and domain "${this._domain}" timed out (> ${this._timeout} ms)`
910
+ )
911
+ )
887
912
  }, this._timeout)
888
913
  this.on('class', checkClass)
889
914
  })
@@ -899,10 +924,11 @@ class VrpcRemote extends EventEmitter {
899
924
 
900
925
  _createProxy (agent, className, instance) {
901
926
  const targetTopic = `${this._domain}/${agent}/${className}/${instance}`
902
- const proxyId = crypto.randomBytes(2).toString('hex')
927
+ const proxyId = `${this._instance}-${this._proxyId++}`
903
928
  const proxy = {
904
- _targetId: instance,
905
- _proxyId: proxyId
929
+ vrpcClientId: this._vrpcClientId,
930
+ vrpcInstanceId: instance,
931
+ vrpcProxyId: proxyId
906
932
  }
907
933
  let functions = this._agents[agent].classes[className].memberFunctions
908
934
  // Strip off argument signature
@@ -916,37 +942,37 @@ class VrpcRemote extends EventEmitter {
916
942
  // Build proxy
917
943
  uniqueFuncs.forEach(name => {
918
944
  proxy[name] = async (...args) => {
945
+ const wrapped = this._wrapArguments(proxyId, name, ...args)
946
+ if (!wrapped) return // Skipping remote call -> handled locally
919
947
  try {
920
948
  const json = {
921
- context: instance,
922
- method: name,
923
- id: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
924
- sender: this._vrpcClientId,
925
- data: this._packData(proxyId, name, ...args)
949
+ c: instance,
950
+ f: name,
951
+ i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
952
+ s: this._vrpcClientId,
953
+ a: wrapped,
954
+ v: VRPC_PROTOCOL_VERSION
926
955
  }
927
- // Skipping remote call -> handled locally
928
- if (json.data === null) return
929
956
  this._mqttPublish(`${targetTopic}/${name}`, JSON.stringify(json))
930
957
  return this._handleAgentAnswer(json)
931
958
  } catch (err) {
932
- throw new Error(`Could not remotely call "${name}" because: ${err.message}`)
959
+ throw new Error(
960
+ `Could not remotely call "${name}" because: ${err.message}`
961
+ )
933
962
  }
934
963
  }
935
964
  })
936
965
  return proxy
937
966
  }
938
967
 
939
- async _handleAgentAnswer ({ id, context, method }) {
968
+ async _handleAgentAnswer ({ i, c, f }) {
940
969
  return new Promise((resolve, reject) => {
941
- const msg = `Function call "${context}::${method}()" timed out (> ${this._timeout} ms)`
942
- const timer = setTimeout(
943
- () => {
944
- this._eventEmitter.removeAllListeners(id)
945
- reject(new Error(msg))
946
- },
947
- this._timeout
948
- )
949
- this._eventEmitter.once(id, data => {
970
+ const msg = `Function call "${c}::${f}()" timed out (> ${this._timeout} ms)`
971
+ const timer = setTimeout(() => {
972
+ this._eventEmitter.removeAllListeners(i)
973
+ reject(new Error(msg))
974
+ }, this._timeout)
975
+ this._eventEmitter.once(i, data => {
950
976
  clearTimeout(timer)
951
977
  if (data.e) {
952
978
  reject(new Error(data.e))
@@ -968,7 +994,7 @@ class VrpcRemote extends EventEmitter {
968
994
 
969
995
  async _waitForInstance (instance, options = {}) {
970
996
  return new Promise((resolve, reject) => {
971
- const handler = (timer) => (instances, { agent, className }) => {
997
+ const handler = timer => (instances, { agent, className }) => {
972
998
  if (instances.includes(instance)) {
973
999
  if (options.agent && agent !== options.agent) return
974
1000
  if (options.className && className !== options.className) return
@@ -977,75 +1003,64 @@ class VrpcRemote extends EventEmitter {
977
1003
  resolve({ agent, className, instance })
978
1004
  }
979
1005
  }
980
- const timer = setTimeout(
981
- () => {
982
- this.removeListener('instanceNew', handler(timer))
983
- const msg = `Could not find instance: ${instance} (> ${this._timeout} ms)`
984
- reject(new Error(msg))
985
- },
986
- this._timeout
987
- )
1006
+ const timer = setTimeout(() => {
1007
+ this.removeListener('instanceNew', handler(timer))
1008
+ const msg = `Could not find instance: ${instance} (> ${this._timeout} ms)`
1009
+ reject(new Error(msg))
1010
+ }, this._timeout)
988
1011
  this.on('instanceNew', handler(timer))
989
1012
  })
990
1013
  }
991
1014
 
992
- _packData (proxyId, functionName, ...args) {
993
- const data = {}
1015
+ _wrapArguments (remoteId, functionName, ...args) {
1016
+ const wrapped = []
994
1017
  let isHandledLocally = false
995
- args.forEach((value, index) => {
1018
+ args.forEach((x, i) => {
996
1019
  // Check whether provided argument is a function
997
- if (this._isFunction(value)) {
1020
+ if (this._isFunction(x)) {
998
1021
  // Check special case of an event emitter registration
999
1022
  // We test three conditions:
1000
1023
  // 1) functionName must be "on"
1001
1024
  // 2) callback is second argument
1002
1025
  // 3) first argument was string
1003
- if (functionName === 'on' &&
1004
- index === 1 &&
1005
- typeof args[0] === 'string'
1006
- ) {
1007
- const id = this._addEventSubscription(proxyId, args[0], value)
1026
+ if (functionName === 'on' && i === 1 && typeof args[0] === 'string') {
1027
+ const id = this._addEventSubscription(remoteId, args[0], x)
1008
1028
  if (!id) isHandledLocally = true
1009
- data[`_${index + 1}`] = id
1029
+ wrapped.push(id)
1010
1030
  } else if (
1011
1031
  (functionName === 'off' || functionName === 'removeListener') &&
1012
- index === 1 &&
1032
+ i === 1 &&
1013
1033
  typeof args[0] === 'string'
1014
1034
  ) {
1015
- const id = this._removeEventSubscription(proxyId, args[0], value)
1035
+ const id = this._removeEventSubscription(remoteId, args[0], x)
1016
1036
  if (!id) isHandledLocally = true
1017
- data[`_${index + 1}`] = id
1018
- // Regular function callback
1037
+ wrapped.push(id)
1038
+ // Regular function callback
1019
1039
  } else {
1020
- const id = `__f__${proxyId}-${functionName}-${index}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`
1021
- data[`_${index + 1}`] = id
1022
- this._eventEmitter.once(id, data => {
1023
- const args = Object.keys(data).sort()
1024
- .filter(x => x[0] === '_')
1025
- .map(x => data[x])
1026
- value.apply(null, args) // This is the actual function call
1040
+ const id = `__f__${remoteId}-${functionName}-${i}-${this._invokeId++ %
1041
+ Number.MAX_SAFE_INTEGER}`
1042
+ wrapped.push(id)
1043
+ this._eventEmitter.once(id, ({ a }) => {
1044
+ x.apply(null, a) // This is the actual function call
1027
1045
  })
1028
1046
  }
1029
- } else if (this._isEmitter(value)) {
1030
- const { emitter, event } = value
1031
- const id = `__f__${proxyId}-${functionName}-${index}-${event}`
1032
- data[`_${index + 1}`] = id
1033
- this._eventEmitter.on(id, data => {
1034
- const args = Object.keys(data).sort()
1035
- .filter(x => x[0] === '_')
1036
- .map(x => data[x])
1037
- emitter.emit(event, ...args)
1047
+ } else if (this._isEmitter(x)) {
1048
+ const { emitter, event } = x
1049
+ const id = `__f__${remoteId}-${functionName}-${i}-${event}`
1050
+ wrapped.push(id)
1051
+ this._eventEmitter.on(id, ({ a }) => {
1052
+ emitter.emit(event, ...a)
1038
1053
  })
1039
1054
  } else {
1040
- data[`_${index + 1}`] = value
1055
+ wrapped.push(x)
1041
1056
  }
1042
1057
  })
1043
1058
  if (isHandledLocally) return null
1044
- return data
1059
+ return wrapped
1045
1060
  }
1046
1061
 
1047
- _addEventSubscription (proxyId, event, callback) {
1048
- const id = `__f__${proxyId}-${event}`
1062
+ _addEventSubscription (remoteId, event, callback) {
1063
+ const id = `__f__${remoteId}-${event}`
1049
1064
  if (this._cachedSubscriptions.has(id)) {
1050
1065
  this._cachedSubscriptions.get(id).on(id, callback)
1051
1066
  return
@@ -1053,17 +1068,14 @@ class VrpcRemote extends EventEmitter {
1053
1068
  const emitter = new EventEmitter()
1054
1069
  emitter.on(id, callback)
1055
1070
  this._cachedSubscriptions.set(id, emitter)
1056
- this._eventEmitter.on(id, (data) => {
1057
- const args = Object.keys(data).sort()
1058
- .filter(x => x[0] === '_')
1059
- .map(x => data[x])
1060
- emitter.emit(id, ...args)
1071
+ this._eventEmitter.on(id, ({ a }) => {
1072
+ emitter.emit(id, ...a)
1061
1073
  })
1062
1074
  return id
1063
1075
  }
1064
1076
 
1065
- _removeEventSubscription (proxyId, event, callback) {
1066
- const id = `__f__${proxyId}-${event}`
1077
+ _removeEventSubscription (remoteId, event, callback) {
1078
+ const id = `__f__${remoteId}-${event}`
1067
1079
  if (this._cachedSubscriptions.has(id)) {
1068
1080
  const emitter = this._cachedSubscriptions.get(id)
1069
1081
  emitter.removeListener(id, callback)
@@ -1084,8 +1096,10 @@ class VrpcRemote extends EventEmitter {
1084
1096
  _isFunction (variable) {
1085
1097
  const getType = {}
1086
1098
  const type = getType.toString.call(variable)
1087
- return variable &&
1099
+ return (
1100
+ variable &&
1088
1101
  (type === '[object Function]' || type === '[object AsyncFunction]')
1102
+ )
1089
1103
  }
1090
1104
 
1091
1105
  _isEmitter (variable) {
@@ -1101,7 +1115,9 @@ class VrpcRemote extends EventEmitter {
1101
1115
  async _getInstanceData (instance, options = {}) {
1102
1116
  const instanceData = this._getInstanceFromCache(instance, options)
1103
1117
  if (!instanceData) {
1104
- if (options.noWait) throw new Error(`Could not find instance: ${instance}`)
1118
+ if (options.noWait) {
1119
+ throw new Error(`Could not find instance: ${instance}`)
1120
+ }
1105
1121
  return this._waitForInstance(instance, options)
1106
1122
  }
1107
1123
  return instanceData
@@ -1140,7 +1156,7 @@ class VrpcRemote extends EventEmitter {
1140
1156
  * This event is fired whenever an agent is added or removed, or whenever
1141
1157
  * an agent changes its status (switches between online or offline).
1142
1158
  *
1143
- * @event VrpcRemote#agent
1159
+ * @event VrpcClient#agent
1144
1160
  * @param {Object} info
1145
1161
  * @param {String} info.domain - Domain name
1146
1162
  * @param {String} info.agent - Agent name
@@ -1155,7 +1171,7 @@ class VrpcRemote extends EventEmitter {
1155
1171
  * Emitted whenever a class is added or removed, or when instances
1156
1172
  * or functions of this class have changed.
1157
1173
  *
1158
- * @event VrpcRemote#class
1174
+ * @event VrpcClient#class
1159
1175
  * @param {Object} info
1160
1176
  * @param {String} info.domain - Domain name
1161
1177
  * @param {String} info.agent - Agent name
@@ -1167,57 +1183,57 @@ class VrpcRemote extends EventEmitter {
1167
1183
  */
1168
1184
 
1169
1185
  /**
1170
- * Event 'instanceNew'
1171
- *
1172
- * Emitted whenever a new instance was created.
1173
- *
1174
- * @event VrpcRemote#instanceNew
1175
- *
1176
- * @param {Array.<String>} addedInstances - An array of newly added instances
1177
- * @param {Object} info
1178
- * @param {String} info.domain - Domain name
1179
- * @param {String} info.agent - Agent name
1180
- * @param {String} info.className - Class name
1181
- */
1186
+ * Event 'instanceNew'
1187
+ *
1188
+ * Emitted whenever a new instance was created.
1189
+ *
1190
+ * @event VrpcClient#instanceNew
1191
+ *
1192
+ * @param {Array.<String>} addedInstances - An array of newly added instances
1193
+ * @param {Object} info
1194
+ * @param {String} info.domain - Domain name
1195
+ * @param {String} info.agent - Agent name
1196
+ * @param {String} info.className - Class name
1197
+ */
1182
1198
 
1183
1199
  /**
1184
- * Event 'instanceGone'
1185
- *
1186
- * Emitted whenever a new instance was removed.
1187
- *
1188
- * @event VrpcRemote#instanceGone
1189
- *
1190
- * @param {Array.<String>} removedInstances - An array of removed instances
1191
- * @param {Object} info
1192
- * @param {String} info.domain - Domain name
1193
- * @param {String} info.agent - Agent name
1194
- * @param {String} info.className - Class name
1195
- */
1200
+ * Event 'instanceGone'
1201
+ *
1202
+ * Emitted whenever a new instance was removed.
1203
+ *
1204
+ * @event VrpcClient#instanceGone
1205
+ *
1206
+ * @param {Array.<String>} removedInstances - An array of removed instances
1207
+ * @param {Object} info
1208
+ * @param {String} info.domain - Domain name
1209
+ * @param {String} info.agent - Agent name
1210
+ * @param {String} info.className - Class name
1211
+ */
1196
1212
 
1197
1213
  /**
1198
1214
  * Event 'connect'
1199
1215
  *
1200
1216
  * Emitted on successful (re)connection (i.e. connack rc=0).
1201
1217
  *
1202
- * @event VrpcRemote#connect
1218
+ * @event VrpcClient#connect
1203
1219
  * @type {Object}
1204
1220
  * @property {Boolean} sessionPresent - A session from a previous connection is already present
1205
- */
1221
+ */
1206
1222
 
1207
1223
  /**
1208
1224
  * Event 'reconnect'
1209
1225
  *
1210
1226
  * Emitted when a reconnect starts.
1211
1227
  *
1212
- * @event VrpcRemote#reconnect
1213
- */
1228
+ * @event VrpcClient#reconnect
1229
+ */
1214
1230
 
1215
1231
  /**
1216
1232
  * Event 'close'
1217
1233
  *
1218
1234
  * Emitted after a disconnection.
1219
1235
  *
1220
- * @event VrpcRemote#close
1236
+ * @event VrpcClient#close
1221
1237
  */
1222
1238
 
1223
1239
  /**
@@ -1225,7 +1241,7 @@ class VrpcRemote extends EventEmitter {
1225
1241
  *
1226
1242
  * Emitted when the client goes offline.
1227
1243
  *
1228
- * @event VrpcRemote#offline
1244
+ * @event VrpcClient#offline
1229
1245
  */
1230
1246
 
1231
1247
  /**
@@ -1240,7 +1256,7 @@ class VrpcRemote extends EventEmitter {
1240
1256
  * - EADDRINUSE
1241
1257
  * - ENOTFOUND
1242
1258
  *
1243
- * @event VrpcRemote#error
1259
+ * @event VrpcClient#error
1244
1260
  * @type {Object} Error
1245
1261
  */
1246
1262
 
@@ -1250,7 +1266,7 @@ class VrpcRemote extends EventEmitter {
1250
1266
  * Emitted when mqtt.Client#end() is called. If a callback was passed to
1251
1267
  * mqtt.Client#end(), this event is emitted once the callback returns.
1252
1268
  *
1253
- * @event VrpcRemote#end
1269
+ * @event VrpcClient#end
1254
1270
  */
1255
1271
 
1256
- module.exports = VrpcRemote
1272
+ module.exports = VrpcClient