vrpc 2.6.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.
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 => {
@@ -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
 
@@ -372,14 +375,11 @@ class VrpcAgent extends EventEmitter {
372
375
  })
373
376
  }
374
377
 
375
- _handleVrpcCallback (jsonString, jsonObject) {
376
- const { sender } = jsonObject
378
+ _handleVrpcCallback (json) {
379
+ const { s } = json
377
380
  try {
378
- this._log.debug(
379
- `Forwarding callback to: ${sender} with payload:`,
380
- jsonObject
381
- )
382
- this._mqttPublish(sender, jsonString)
381
+ this._log.debug(`Forwarding callback to: ${s} with payload:`, json)
382
+ this._mqttPublish(s, VrpcAgent._stringifySafely(json))
383
383
  } catch (err) {
384
384
  this._log.warn(
385
385
  err,
@@ -388,6 +388,21 @@ class VrpcAgent extends EventEmitter {
388
388
  }
389
389
  }
390
390
 
391
+ static _stringifySafely (json) {
392
+ let str
393
+ const { c, f } = json
394
+ try {
395
+ str = JSON.stringify(json)
396
+ } catch (err) {
397
+ this._log.debug(
398
+ `Failed serialization of return value for: ${c}::${f}, because: ${err.message}`
399
+ )
400
+ json.r = '__vrpc::not-serializable__'
401
+ str = JSON.stringify(json)
402
+ }
403
+ return str
404
+ }
405
+
391
406
  _handleConnect () {
392
407
  this._log.info('[OK]')
393
408
  if (this._isReconnect) {
@@ -441,7 +456,8 @@ class VrpcAgent extends EventEmitter {
441
456
  instances: this._getInstances(className),
442
457
  memberFunctions: this._getMemberFunctions(className),
443
458
  staticFunctions: this._getStaticFunctions(className),
444
- meta: this._getMetaData(className)
459
+ meta: this._getMetaData(className),
460
+ v: VRPC_PROTOCOL_VERSION
445
461
  }
446
462
  try {
447
463
  this._mqttPublish(
@@ -494,69 +510,42 @@ class VrpcAgent extends EventEmitter {
494
510
  }
495
511
 
496
512
  // Prepare RPC json
497
- json.context = instance === '__static__' ? className : instance
498
- json.method = method
513
+ // SECURITY: take as much as possible params from topic structure as this
514
+ // can be authorized by the broker. Content can't be checked...
515
+ json.c = instance === '__static__' ? className : instance
516
+ json.f = method
499
517
 
500
518
  // Mutates json and adds return value
501
519
  VrpcAdapter._call(json)
502
520
 
503
521
  // Intersecting life-cycle functions
504
- let publishClassInfo = false
505
522
  switch (method) {
506
- case '__create__': {
523
+ case '__createIsolated__': {
507
524
  // TODO handle instantiation errors
508
- const instanceId = json.data.r
525
+ const instanceId = json.r
509
526
  // TODO await this
510
527
  this._subscribeToMethodsOfNewInstance(className, instanceId)
511
- this._registerUnnamedInstance(instanceId, json.sender)
528
+ this._registerIsolatedInstance(instanceId, json.s)
512
529
  break
513
530
  }
514
- case '__createNamed__': {
531
+ case '__createShared__': {
515
532
  // TODO handle instantiation errors
516
- const instanceId = json.data.r
517
- if (!this._hasNamedInstance(instanceId)) {
518
- publishClassInfo = true
533
+ const instanceId = json.r
534
+ if (!this._hasSharedInstance(instanceId)) {
519
535
  this._subscribeToMethodsOfNewInstance(className, instanceId)
536
+ this._publishClassInfoMessage(className)
520
537
  }
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)
538
+ this._registerSharedInstance(instanceId, json.s)
530
539
  break
531
540
  }
532
541
  case '__delete__': {
533
- const {
534
- data: { _1 },
535
- sender
536
- } = json
537
542
  this._unsubscribeMethodsOfDeletedInstance(className, instance)
538
- const wasNamed = this._unregisterInstance(_1, sender)
539
- if (wasNamed) publishClassInfo = true
543
+ const wasShared = this._unregisterInstance(json.a[0], json.s)
544
+ if (wasShared) this._publishClassInfoMessage(className)
540
545
  break
541
546
  }
542
547
  }
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
- }
548
+ this._mqttPublish(json.s, VrpcAgent._stringifySafely(json))
560
549
  } catch (err) {
561
550
  this._log.error(
562
551
  err,
@@ -567,80 +556,81 @@ class VrpcAgent extends EventEmitter {
567
556
 
568
557
  _handleClientInfoMessage (topic, json) {
569
558
  // Client went offline
570
- const clientId = topic.slice(0, -9)
559
+ const clientId = topic.slice(0, -15) // /__clientInfo__ = 15
571
560
  if (json.status === 'offline') {
572
- const entry = this._unnamedInstances.get(clientId)
561
+ const entry = this._isolatedInstances.get(clientId)
573
562
  if (entry) {
574
563
  // anonymous
575
564
  entry.forEach(instanceId => {
576
- const json = { data: { _1: instanceId }, method: '__delete__' }
565
+ const json = { f: '__delete__', a: [instanceId], r: null }
566
+ // TODO Do not mutate!
577
567
  VrpcAdapter._call(json)
578
- const {
579
- data: { r }
580
- } = json
581
- if (r) this._log.debug(`Deleted unnamed instance: ${instanceId}`)
568
+ if (json.r) {
569
+ this._log.debug(`Auto-deleted isolated instance: ${instanceId}`)
570
+ }
582
571
  })
583
572
  }
584
573
  VrpcAdapter._unregisterEventListeners(clientId)
585
574
  this._mqttUnsubscribe(`${clientId}/__clientInfo__`)
575
+ this.emit('clientGone', clientId)
586
576
  }
587
577
  }
588
578
 
589
- _registerUnnamedInstance (instanceId, clientId) {
590
- const entry = this._unnamedInstances.get(clientId)
579
+ _registerIsolatedInstance (instanceId, clientId) {
580
+ const entry = this._isolatedInstances.get(clientId)
591
581
  if (entry) {
592
582
  // already seen
593
583
  entry.add(instanceId)
594
584
  } else {
595
585
  // new instance
596
- this._unnamedInstances.set(clientId, new Set([instanceId]))
597
- if (!this._namedInstances.has(clientId)) {
586
+ this._isolatedInstances.set(clientId, new Set([instanceId]))
587
+ if (!this._sharedInstances.has(clientId)) {
598
588
  this._mqttSubscribe(`${clientId}/__clientInfo__`)
599
589
  }
600
590
  this._log.info(`Tracking lifetime of client: ${clientId}`)
601
591
  }
602
592
  }
603
593
 
604
- _registerNamedInstance (instanceId, clientId) {
605
- const entry = this._namedInstances.get(clientId)
594
+ _registerSharedInstance (instanceId, clientId) {
595
+ const entry = this._sharedInstances.get(clientId)
606
596
  if (entry) {
607
597
  // already seen
608
598
  entry.add(instanceId)
609
599
  } else {
610
600
  // new instance
611
- this._namedInstances.set(clientId, new Set([instanceId]))
612
- if (!this._unnamedInstances.has(clientId)) {
601
+ this._sharedInstances.set(clientId, new Set([instanceId]))
602
+ if (!this._isolatedInstances.has(clientId)) {
613
603
  this._mqttSubscribe(`${clientId}/__clientInfo__`)
614
604
  }
615
605
  this._log.debug(`Tracking lifetime of client: ${clientId}`)
616
606
  }
617
607
  }
618
608
 
619
- _hasNamedInstance (instanceId) {
620
- for (const [, instances] of this._namedInstances) {
609
+ _hasSharedInstance (instanceId) {
610
+ for (const [, instances] of this._sharedInstances) {
621
611
  if (instances.has(instanceId)) return true
622
612
  }
623
613
  return false
624
614
  }
625
615
 
626
616
  _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)
617
+ const entryIsolated = this._isolatedInstances.get(clientId)
618
+ if (entryIsolated && entryIsolated.has(instanceId)) {
619
+ entryIsolated.delete(instanceId)
620
+ if (entryIsolated.length === 0) {
621
+ this._isolatedInstances.delete(clientId)
632
622
  this._mqttUnsubscribe(`${clientId}/__clientInfo__`)
633
623
  this._log.debug(`Stopped tracking lifetime of client: ${clientId}`)
634
624
  }
635
625
  return false
636
626
  }
637
627
  let found = false
638
- this._namedInstances.forEach(async v => {
628
+ this._sharedInstances.forEach(async v => {
639
629
  if (v.has(instanceId)) {
640
630
  found = true
641
631
  v.delete(instanceId)
642
632
  if (v.length === 0) {
643
- this._namedInstances.delete(clientId)
633
+ this._sharedInstances.delete(clientId)
644
634
  this._mqttUnsubscribe(`${clientId}/__clientInfo__`)
645
635
  this._log.debug(`Stopped tracking lifetime of client: ${clientId}`)
646
636
  }
@@ -749,4 +739,14 @@ class VrpcAgent extends EventEmitter {
749
739
  * @event VrpcAgent#end
750
740
  */
751
741
 
742
+ /**
743
+ * Event 'clientGone'
744
+ *
745
+ * Emitted when a VRPC client exited that before produced state (e.g. created
746
+ * an anonymous or named instance).
747
+ *
748
+ * @event VrpcAgent#clientGone
749
+ * @type {String} clientId
750
+ */
751
+
752
752
  module.exports = VrpcAgent