vrpc 3.16.0 → 3.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vrpc",
3
- "version": "3.16.0",
3
+ "version": "3.16.1",
4
4
  "description": "Remote objects over MQTT: call classes in other programs as if they were local - Node.js, browser, C++, Python, Arduino",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -661,8 +661,15 @@ class VrpcAdapter {
661
661
  const [instanceId, ...args] = json.a
662
662
  VrpcAdapter._assertInstanceName(instanceId)
663
663
  const existing = VrpcAdapter._instances.get(instanceId)
664
- if (existing && existing.isIsolated) {
665
- throw new Error(`Instance '${instanceId}' exists as an isolated instance`)
664
+ if (existing) {
665
+ // an id is never re-homed: an isolated instance stays isolated
666
+ if (existing.isIsolated) {
667
+ throw new Error(`Instance '${instanceId}' exists as an isolated instance`)
668
+ }
669
+ // a create of a name that exists returns it unchanged: its class
670
+ // and arguments stay, and nothing announces it (no 'create' event)
671
+ json.r = instanceId
672
+ return existing.instance
666
673
  }
667
674
  instance = VrpcAdapter._create(className, instanceId, ...args)
668
675
  VrpcAdapter._instances.set(instanceId, {
@@ -1313,7 +1320,8 @@ class VrpcAdapter {
1313
1320
  /**
1314
1321
  * Event 'create'
1315
1322
  *
1316
- * Emitted on creation of shared instance
1323
+ * Emitted once an instance was created. A create of a name that exists
1324
+ * returns that instance and emits nothing.
1317
1325
  *
1318
1326
  * @event VrpcAdapter#create
1319
1327
  * @type {Object}
package/vrpc/VrpcAgent.js CHANGED
@@ -646,6 +646,9 @@ class VrpcAgent extends EventEmitter {
646
646
  ] of VrpcAdapter._instances.entries()) {
647
647
  batches.push(this._methodsTopic(className, instanceId))
648
648
  }
649
+ // what the broker retains of this agent's classes: an earlier run's
650
+ // class that is no longer offered is cleared (_sweepClassInfo)
651
+ batches.push(`${this._baseTopic}/+/__classInfoConcise__`)
649
652
  let pending = batches.length
650
653
  const announce = () => {
651
654
  // a connection that ended meanwhile announces nothing: the next
@@ -659,10 +662,6 @@ class VrpcAgent extends EventEmitter {
659
662
  this._serving = true
660
663
  this.emit('connect')
661
664
  }
662
- if (pending === 0) {
663
- announce()
664
- return
665
- }
666
665
  for (const topic of batches) {
667
666
  this._mqttSubscribe(topic, undefined, () => {
668
667
  pending -= 1
@@ -743,7 +742,38 @@ class VrpcAgent extends EventEmitter {
743
742
  return topics
744
743
  }
745
744
 
746
- _handleMessage (topic, data) {
745
+ /**
746
+ * A class info under this agent's own name. What the broker retained of
747
+ * a class this agent does not offer is a ghost of an earlier run - a
748
+ * class info only `end({ unregister: true })` would have cleared - and
749
+ * clients would list the class forever: it is cleared, in full and
750
+ * concise. Everything else on the topic - the agent's own announcements
751
+ * and clears, routed back live - passes without a word.
752
+ *
753
+ * @returns {Boolean} whether the message was a class info
754
+ * @private
755
+ */
756
+ _sweepClassInfo (topic, data, packet) {
757
+ const prefix = `${this._baseTopic}/`
758
+ if (!topic.startsWith(prefix)) return false
759
+ const tokens = topic.slice(prefix.length).split('/')
760
+ if (tokens.length !== 2) return false
761
+ const [className, info] = tokens
762
+ if (info !== '__classInfoConcise__' && info !== '__classInfo__') {
763
+ return false
764
+ }
765
+ const retained = !!packet && packet.retain
766
+ if (!retained || data.length === 0) return true
767
+ if (this._getClasses().includes(className)) return true
768
+ this._log.info(`Clearing the class info of ${className}, no longer offered`)
769
+ for (const name of ['__classInfo__', '__classInfoConcise__']) {
770
+ this._mqttPublish(`${prefix}${className}/${name}`, null, { retain: true })
771
+ }
772
+ return true
773
+ }
774
+
775
+ _handleMessage (topic, data, packet) {
776
+ if (this._sweepClassInfo(topic, data, packet)) return
747
777
  let json
748
778
  try {
749
779
  json = JSON.parse(data.toString())
@@ -469,8 +469,15 @@ class VrpcAdapter {
469
469
  const [instanceId, ...args] = json.a
470
470
  VrpcAdapter._assertInstanceName(instanceId)
471
471
  const existing = VrpcAdapter._instances.get(instanceId)
472
- if (existing && existing.isIsolated) {
473
- throw new Error(`Instance '${instanceId}' exists as an isolated instance`)
472
+ if (existing) {
473
+ // an id is never re-homed: an isolated instance stays isolated
474
+ if (existing.isIsolated) {
475
+ throw new Error(`Instance '${instanceId}' exists as an isolated instance`)
476
+ }
477
+ // a create of a name that exists returns it unchanged: its class
478
+ // and arguments stay, and nothing announces it (no 'create' event)
479
+ json.r = instanceId
480
+ return existing.instance
474
481
  }
475
482
  instance = VrpcAdapter._create(className, instanceId, ...args)
476
483
  VrpcAdapter._instances.set(instanceId, {
@@ -1132,7 +1139,8 @@ class VrpcAdapter {
1132
1139
  /**
1133
1140
  * Event 'create'
1134
1141
  *
1135
- * Emitted on creation of shared instance
1142
+ * Emitted once an instance was created. A create of a name that exists
1143
+ * returns that instance and emits nothing.
1136
1144
  *
1137
1145
  * @event VrpcAdapter#create
1138
1146
  * @type {Object}
@@ -478,6 +478,9 @@ class VrpcAgent extends EventEmitter {
478
478
  ] of VrpcAdapter._instances.entries()) {
479
479
  batches.push(this._methodsTopic(className, instanceId))
480
480
  }
481
+ // what the broker retains of this agent's classes: an earlier run's
482
+ // class that is no longer offered is cleared (_sweepClassInfo)
483
+ batches.push(`${this._baseTopic}/+/__classInfoConcise__`)
481
484
  let pending = batches.length
482
485
  const announce = () => {
483
486
  // a connection that ended meanwhile announces nothing: the next
@@ -490,10 +493,6 @@ class VrpcAgent extends EventEmitter {
490
493
  this._serving = true
491
494
  this.emit('connect')
492
495
  }
493
- if (pending === 0) {
494
- announce()
495
- return
496
- }
497
496
  for (const topic of batches) {
498
497
  this._mqttSubscribe(topic, undefined, () => {
499
498
  pending -= 1
@@ -566,7 +565,38 @@ class VrpcAgent extends EventEmitter {
566
565
  return topics
567
566
  }
568
567
 
569
- _handleMessage (topic, data) {
568
+ /**
569
+ * A class info under this agent's own name. What the broker retained of
570
+ * a class this agent does not offer is a ghost of an earlier run - a
571
+ * class info only `end({ unregister: true })` would have cleared - and
572
+ * clients would list the class forever: it is cleared, in full and
573
+ * concise. Everything else on the topic - the agent's own announcements
574
+ * and clears, routed back live - passes without a word.
575
+ *
576
+ * @returns {Boolean} whether the message was a class info
577
+ * @private
578
+ */
579
+ _sweepClassInfo (topic, data, packet) {
580
+ const prefix = `${this._baseTopic}/`
581
+ if (!topic.startsWith(prefix)) return false
582
+ const tokens = topic.slice(prefix.length).split('/')
583
+ if (tokens.length !== 2) return false
584
+ const [className, info] = tokens
585
+ if (info !== '__classInfoConcise__' && info !== '__classInfo__') {
586
+ return false
587
+ }
588
+ const retained = !!packet && packet.retain
589
+ if (!retained || data.length === 0) return true
590
+ if (this._getClasses().includes(className)) return true
591
+ this._log.info(`Clearing the class info of ${className}, no longer offered`)
592
+ for (const name of ['__classInfo__', '__classInfoConcise__']) {
593
+ this._mqttPublish(`${prefix}${className}/${name}`, null, { retain: true })
594
+ }
595
+ return true
596
+ }
597
+
598
+ _handleMessage (topic, data, packet) {
599
+ if (this._sweepClassInfo(topic, data, packet)) return
570
600
  let json
571
601
  try {
572
602
  json = JSON.parse(data.toString())