vrpc 2.6.0 → 3.0.0

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