vrpc 2.6.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/vrpc/VrpcAgent.js CHANGED
@@ -6,6 +6,8 @@ const { ArgumentParser } = require('argparse')
6
6
  const VrpcAdapter = require('./VrpcAdapter')
7
7
  const EventEmitter = require('events')
8
8
 
9
+ const VRPC_PROTOCOL_VERSION = 3
10
+
9
11
  /**
10
12
  * Agent capable of making existing code available to remote control by clients.
11
13
  *
@@ -138,8 +140,8 @@ class VrpcAgent extends EventEmitter {
138
140
  this._baseTopic = `${this._domain}/${this._agent}`
139
141
  VrpcAdapter.onCallback(this._handleVrpcCallback.bind(this))
140
142
  // maps clientId to instanceId
141
- this._unnamedInstances = new Map()
142
- this._namedInstances = new Map()
143
+ this._isolatedInstances = new Map()
144
+ this._sharedInstances = new Map()
143
145
 
144
146
  // Handle the internal error event in case the user forgot to implement it
145
147
  this.on('error', err => {
@@ -167,7 +169,7 @@ class VrpcAgent extends EventEmitter {
167
169
  .createHash('md5')
168
170
  .update(this._domain + this._agent)
169
171
  .digest('hex')
170
- .substr(0, 18)
172
+ .substr(0, 20)
171
173
  let username = this._username
172
174
  let password = this._password
173
175
  if (this._token) {
@@ -179,7 +181,7 @@ class VrpcAgent extends EventEmitter {
179
181
  password,
180
182
  keepalive: 30,
181
183
  connectTimeout: 10 * 1000,
182
- clientId: `vrpca${md5}`,
184
+ clientId: `va3${md5}`,
183
185
  rejectUnauthorized: false,
184
186
  will: {
185
187
  topic: `${this._baseTopic}/__agentInfo__`,
@@ -274,7 +276,8 @@ class VrpcAgent extends EventEmitter {
274
276
  return JSON.stringify({
275
277
  status,
276
278
  hostname: os.hostname(),
277
- version: this._version
279
+ version: this._version,
280
+ v: VRPC_PROTOCOL_VERSION
278
281
  })
279
282
  }
280
283
 
@@ -343,23 +346,23 @@ class VrpcAgent extends EventEmitter {
343
346
  }
344
347
 
345
348
  _getClasses () {
346
- return VrpcAdapter.getAvailableClasses()
349
+ return VrpcAdapter._getClassesArray()
347
350
  }
348
351
 
349
352
  _getInstances (className) {
350
- return VrpcAdapter.getAvailableInstances(className)
353
+ return VrpcAdapter._getInstancesArray(className)
351
354
  }
352
355
 
353
356
  _getMemberFunctions (className) {
354
- return VrpcAdapter.getAvailableMemberFunctions(className)
357
+ return VrpcAdapter._getMemberFunctionsArray(className)
355
358
  }
356
359
 
357
360
  _getStaticFunctions (className) {
358
- return VrpcAdapter.getAvailableStaticFunctions(className)
361
+ return VrpcAdapter._getStaticFunctionsArray(className)
359
362
  }
360
363
 
361
364
  _getMetaData (className) {
362
- return VrpcAdapter.getAvailableMetaData(className)
365
+ return VrpcAdapter._getMetaData(className)
363
366
  }
364
367
 
365
368
  async _ensureConnected () {
@@ -372,22 +375,38 @@ class VrpcAgent extends EventEmitter {
372
375
  })
373
376
  }
374
377
 
375
- _handleVrpcCallback (jsonString, jsonObject) {
376
- const { sender } = jsonObject
378
+ _handleVrpcCallback (json) {
379
+ const { s, i, r, e, a } = json
380
+ const topic = i.startsWith('__e__') ? i.slice(5) : s
377
381
  try {
378
- this._log.debug(
379
- `Forwarding callback to: ${sender} with payload:`,
380
- jsonObject
382
+ this._log.debug(`Forwarding callback to: ${topic} with payload:`, json)
383
+ this._mqttPublish(
384
+ topic,
385
+ VrpcAgent._stringifySafely({ a, r, e, i, v: VRPC_PROTOCOL_VERSION })
381
386
  )
382
- this._mqttPublish(sender, jsonString)
383
387
  } catch (err) {
384
388
  this._log.warn(
385
389
  err,
386
- `Problem publishing vrpc callback to ${sender} because of: ${err.message}`
390
+ `Problem publishing vrpc callback to ${topic} because of: ${err.message}`
387
391
  )
388
392
  }
389
393
  }
390
394
 
395
+ static _stringifySafely (json) {
396
+ let str
397
+ const { c, f } = json
398
+ try {
399
+ str = JSON.stringify(json)
400
+ } catch (err) {
401
+ this._log.debug(
402
+ `Failed serialization of return value for: ${c}::${f}, because: ${err.message}`
403
+ )
404
+ json.r = '__vrpc::not-serializable__'
405
+ str = JSON.stringify(json)
406
+ }
407
+ return str
408
+ }
409
+
391
410
  _handleConnect () {
392
411
  this._log.info('[OK]')
393
412
  if (this._isReconnect) {
@@ -441,7 +460,8 @@ class VrpcAgent extends EventEmitter {
441
460
  instances: this._getInstances(className),
442
461
  memberFunctions: this._getMemberFunctions(className),
443
462
  staticFunctions: this._getStaticFunctions(className),
444
- meta: this._getMetaData(className)
463
+ meta: this._getMetaData(className),
464
+ v: VRPC_PROTOCOL_VERSION
445
465
  }
446
466
  try {
447
467
  this._mqttPublish(
@@ -494,69 +514,48 @@ class VrpcAgent extends EventEmitter {
494
514
  }
495
515
 
496
516
  // Prepare RPC json
497
- json.context = instance === '__static__' ? className : instance
498
- json.method = method
517
+ // SECURITY: take as much as possible params from topic structure as this
518
+ // can be authorized by the broker. Content can't be checked...
519
+ json.c = instance === '__static__' ? className : instance
520
+ json.f = method
499
521
 
500
522
  // Mutates json and adds return value
501
- VrpcAdapter._call(json)
523
+ const mustTrack = VrpcAdapter._call(json)
524
+
525
+ if (mustTrack) {
526
+ this._mqttSubscribe(`${json.s}/__clientInfo__`)
527
+ }
502
528
 
503
529
  // Intersecting life-cycle functions
504
- let publishClassInfo = false
505
530
  switch (method) {
506
- case '__create__': {
531
+ case '__createIsolated__': {
507
532
  // TODO handle instantiation errors
508
- const instanceId = json.data.r
533
+ const instanceId = json.r
509
534
  // TODO await this
510
535
  this._subscribeToMethodsOfNewInstance(className, instanceId)
511
- this._registerUnnamedInstance(instanceId, json.sender)
536
+ this._registerIsolatedInstance(instanceId, json.s)
512
537
  break
513
538
  }
514
- case '__createNamed__': {
539
+ case '__createShared__': {
515
540
  // TODO handle instantiation errors
516
- const instanceId = json.data.r
517
- if (!this._hasNamedInstance(instanceId)) {
518
- publishClassInfo = true
541
+ const instanceId = json.r
542
+ if (!this._hasSharedInstance(instanceId)) {
519
543
  this._subscribeToMethodsOfNewInstance(className, instanceId)
544
+ this._publishClassInfoMessage(className)
520
545
  }
521
- this._registerNamedInstance(instanceId, json.sender)
522
- break
523
- }
524
- case '__getNamed__': {
525
- const {
526
- data: { _1, e },
527
- sender
528
- } = json
529
- if (!e) this._registerNamedInstance(_1, sender)
546
+ this._registerSharedInstance(instanceId, json.s)
530
547
  break
531
548
  }
532
549
  case '__delete__': {
533
- const {
534
- data: { _1 },
535
- sender
536
- } = json
537
550
  this._unsubscribeMethodsOfDeletedInstance(className, instance)
538
- const wasNamed = this._unregisterInstance(_1, sender)
539
- if (wasNamed) publishClassInfo = true
551
+ const wasShared = this._unregisterInstance(json.a[0], json.s)
552
+ if (wasShared) this._publishClassInfoMessage(className)
540
553
  break
541
554
  }
542
555
  }
543
- let jsonString
544
- try {
545
- jsonString = JSON.stringify(json)
546
- } catch (err) {
547
- this._log.debug(
548
- `Failed serialization of return value for: ${json.context}::${json.method}, because: ${err.message}`
549
- )
550
- json.data.r = '__vrpc::not-serializable__'
551
- jsonString = JSON.stringify(json)
552
- }
553
- if (publishClassInfo && method === '__delete__') {
554
- this._publishClassInfoMessage(className)
555
- }
556
- this._mqttPublish(json.sender, jsonString)
557
- if (publishClassInfo && method === '__createNamed__') {
558
- this._publishClassInfoMessage(className)
559
- }
556
+ const { a, r, e, i, v } = json
557
+ const res = e ? { a, r, e, i, v } : { a, r, i, v }
558
+ this._mqttPublish(json.s, VrpcAgent._stringifySafely(res))
560
559
  } catch (err) {
561
560
  this._log.error(
562
561
  err,
@@ -567,80 +566,79 @@ class VrpcAgent extends EventEmitter {
567
566
 
568
567
  _handleClientInfoMessage (topic, json) {
569
568
  // Client went offline
570
- const clientId = topic.slice(0, -9)
569
+ const clientId = topic.slice(0, -15) // /__clientInfo__ = 15
571
570
  if (json.status === 'offline') {
572
- const entry = this._unnamedInstances.get(clientId)
571
+ VrpcAdapter._unregisterClient(clientId)
572
+ const entry = this._isolatedInstances.get(clientId)
573
573
  if (entry) {
574
- // anonymous
575
574
  entry.forEach(instanceId => {
576
- const json = { data: { _1: instanceId }, method: '__delete__' }
575
+ const json = { f: '__delete__', a: [instanceId], r: null }
577
576
  VrpcAdapter._call(json)
578
- const {
579
- data: { r }
580
- } = json
581
- if (r) this._log.debug(`Deleted unnamed instance: ${instanceId}`)
577
+ if (json.r) {
578
+ this._log.debug(`Auto-deleted isolated instance: ${instanceId}`)
579
+ }
582
580
  })
583
581
  }
584
- VrpcAdapter._unregisterEventListeners(clientId)
585
582
  this._mqttUnsubscribe(`${clientId}/__clientInfo__`)
583
+ this.emit('clientGone', clientId)
586
584
  }
587
585
  }
588
586
 
589
- _registerUnnamedInstance (instanceId, clientId) {
590
- const entry = this._unnamedInstances.get(clientId)
587
+ _registerIsolatedInstance (instanceId, clientId) {
588
+ const entry = this._isolatedInstances.get(clientId)
591
589
  if (entry) {
592
590
  // already seen
593
591
  entry.add(instanceId)
594
592
  } else {
595
593
  // new instance
596
- this._unnamedInstances.set(clientId, new Set([instanceId]))
597
- if (!this._namedInstances.has(clientId)) {
594
+ this._isolatedInstances.set(clientId, new Set([instanceId]))
595
+ if (!this._sharedInstances.has(clientId)) {
598
596
  this._mqttSubscribe(`${clientId}/__clientInfo__`)
599
597
  }
600
598
  this._log.info(`Tracking lifetime of client: ${clientId}`)
601
599
  }
602
600
  }
603
601
 
604
- _registerNamedInstance (instanceId, clientId) {
605
- const entry = this._namedInstances.get(clientId)
602
+ _registerSharedInstance (instanceId, clientId) {
603
+ const entry = this._sharedInstances.get(clientId)
606
604
  if (entry) {
607
605
  // already seen
608
606
  entry.add(instanceId)
609
607
  } else {
610
608
  // new instance
611
- this._namedInstances.set(clientId, new Set([instanceId]))
612
- if (!this._unnamedInstances.has(clientId)) {
609
+ this._sharedInstances.set(clientId, new Set([instanceId]))
610
+ if (!this._isolatedInstances.has(clientId)) {
613
611
  this._mqttSubscribe(`${clientId}/__clientInfo__`)
614
612
  }
615
613
  this._log.debug(`Tracking lifetime of client: ${clientId}`)
616
614
  }
617
615
  }
618
616
 
619
- _hasNamedInstance (instanceId) {
620
- for (const [, instances] of this._namedInstances) {
617
+ _hasSharedInstance (instanceId) {
618
+ for (const [, instances] of this._sharedInstances) {
621
619
  if (instances.has(instanceId)) return true
622
620
  }
623
621
  return false
624
622
  }
625
623
 
626
624
  _unregisterInstance (instanceId, clientId) {
627
- const entryUnnamed = this._unnamedInstances.get(clientId)
628
- if (entryUnnamed && entryUnnamed.has(instanceId)) {
629
- entryUnnamed.delete(instanceId)
630
- if (entryUnnamed.length === 0) {
631
- this._unnamedInstances.delete(clientId)
625
+ const entryIsolated = this._isolatedInstances.get(clientId)
626
+ if (entryIsolated && entryIsolated.has(instanceId)) {
627
+ entryIsolated.delete(instanceId)
628
+ if (entryIsolated.length === 0) {
629
+ this._isolatedInstances.delete(clientId)
632
630
  this._mqttUnsubscribe(`${clientId}/__clientInfo__`)
633
631
  this._log.debug(`Stopped tracking lifetime of client: ${clientId}`)
634
632
  }
635
633
  return false
636
634
  }
637
635
  let found = false
638
- this._namedInstances.forEach(async v => {
636
+ this._sharedInstances.forEach(async v => {
639
637
  if (v.has(instanceId)) {
640
638
  found = true
641
639
  v.delete(instanceId)
642
640
  if (v.length === 0) {
643
- this._namedInstances.delete(clientId)
641
+ this._sharedInstances.delete(clientId)
644
642
  this._mqttUnsubscribe(`${clientId}/__clientInfo__`)
645
643
  this._log.debug(`Stopped tracking lifetime of client: ${clientId}`)
646
644
  }
@@ -749,4 +747,13 @@ class VrpcAgent extends EventEmitter {
749
747
  * @event VrpcAgent#end
750
748
  */
751
749
 
750
+ /**
751
+ * Event 'clientGone'
752
+ *
753
+ * Emitted when a tracked VRPC client exited.
754
+ *
755
+ * @event VrpcAgent#clientGone
756
+ * @type {String} clientId
757
+ */
758
+
752
759
  module.exports = VrpcAgent