vrpc 2.5.1 → 3.0.0-alpha.3

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
 
@@ -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,46 @@ 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
- VrpcAdapter._call(json)
519
+ const mustTrack = VrpcAdapter._call(json)
520
+
521
+ if (mustTrack) {
522
+ this._mqttSubscribe(`${json.s}/__clientInfo__`)
523
+ }
502
524
 
503
525
  // Intersecting life-cycle functions
504
- let publishClassInfo = false
505
526
  switch (method) {
506
- case '__create__': {
527
+ case '__createIsolated__': {
507
528
  // TODO handle instantiation errors
508
- const instanceId = json.data.r
529
+ const instanceId = json.r
509
530
  // TODO await this
510
531
  this._subscribeToMethodsOfNewInstance(className, instanceId)
511
- this._registerUnnamedInstance(instanceId, json.sender)
532
+ this._registerIsolatedInstance(instanceId, json.s)
512
533
  break
513
534
  }
514
- case '__createNamed__': {
535
+ case '__createShared__': {
515
536
  // TODO handle instantiation errors
516
- const instanceId = json.data.r
517
- if (!this._hasNamedInstance(instanceId)) {
518
- publishClassInfo = true
537
+ const instanceId = json.r
538
+ if (!this._hasSharedInstance(instanceId)) {
519
539
  this._subscribeToMethodsOfNewInstance(className, instanceId)
540
+ this._publishClassInfoMessage(className)
520
541
  }
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)
542
+ this._registerSharedInstance(instanceId, json.s)
530
543
  break
531
544
  }
532
545
  case '__delete__': {
533
- const {
534
- data: { _1 },
535
- sender
536
- } = json
537
546
  this._unsubscribeMethodsOfDeletedInstance(className, instance)
538
- const wasNamed = this._unregisterInstance(_1, sender)
539
- if (wasNamed) publishClassInfo = true
547
+ const wasShared = this._unregisterInstance(json.a[0], json.s)
548
+ if (wasShared) this._publishClassInfoMessage(className)
540
549
  break
541
550
  }
542
551
  }
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
- }
552
+ this._mqttPublish(json.s, VrpcAgent._stringifySafely(json))
560
553
  } catch (err) {
561
554
  this._log.error(
562
555
  err,
@@ -567,80 +560,83 @@ class VrpcAgent extends EventEmitter {
567
560
 
568
561
  _handleClientInfoMessage (topic, json) {
569
562
  // Client went offline
570
- const clientId = topic.slice(0, -9)
563
+ const clientId = topic.slice(0, -15) // /__clientInfo__ = 15
571
564
  if (json.status === 'offline') {
572
- const entry = this._unnamedInstances.get(clientId)
565
+ const entry = this._isolatedInstances.get(clientId)
573
566
  if (entry) {
574
567
  // anonymous
575
568
  entry.forEach(instanceId => {
576
- const json = { data: { _1: instanceId }, method: '__delete__' }
569
+ const json = { f: '__delete__', a: [instanceId], r: null }
570
+ // TODO Do not mutate!
577
571
  VrpcAdapter._call(json)
578
- const {
579
- data: { r }
580
- } = json
581
- if (r) this._log.debug(`Deleted unnamed instance: ${instanceId}`)
572
+ if (json.r) {
573
+ this._log.debug(`Auto-deleted isolated instance: ${instanceId}`)
574
+ }
582
575
  })
583
576
  }
584
577
  VrpcAdapter._unregisterEventListeners(clientId)
585
578
  this._mqttUnsubscribe(`${clientId}/__clientInfo__`)
579
+ this.emit('clientGone', clientId)
586
580
  }
587
581
  }
588
582
 
589
- _registerUnnamedInstance (instanceId, clientId) {
590
- const entry = this._unnamedInstances.get(clientId)
583
+ _registerIsolatedInstance (instanceId, clientId) {
584
+ const entry = this._isolatedInstances.get(clientId)
591
585
  if (entry) {
592
586
  // already seen
593
587
  entry.add(instanceId)
594
588
  } else {
595
589
  // new instance
596
- this._unnamedInstances.set(clientId, new Set([instanceId]))
597
- if (!this._namedInstances.has(clientId)) {
590
+ this._isolatedInstances.set(clientId, new Set([instanceId]))
591
+ if (!this._sharedInstances.has(clientId)) {
598
592
  this._mqttSubscribe(`${clientId}/__clientInfo__`)
599
593
  }
600
594
  this._log.info(`Tracking lifetime of client: ${clientId}`)
601
595
  }
602
596
  }
603
597
 
604
- _registerNamedInstance (instanceId, clientId) {
605
- const entry = this._namedInstances.get(clientId)
598
+ _registerSharedInstance (instanceId, clientId) {
599
+ const entry = this._sharedInstances.get(clientId)
606
600
  if (entry) {
607
601
  // already seen
608
602
  entry.add(instanceId)
609
603
  } else {
610
604
  // new instance
611
- this._namedInstances.set(clientId, new Set([instanceId]))
612
- if (!this._unnamedInstances.has(clientId)) {
605
+ this._sharedInstances.set(clientId, new Set([instanceId]))
606
+ if (!this._isolatedInstances.has(clientId)) {
613
607
  this._mqttSubscribe(`${clientId}/__clientInfo__`)
614
608
  }
615
609
  this._log.debug(`Tracking lifetime of client: ${clientId}`)
616
610
  }
617
611
  }
618
612
 
619
- _hasNamedInstance (instanceId) {
620
- for (const [, instances] of this._namedInstances) {
613
+ _hasSharedInstance (instanceId) {
614
+ for (const [, instances] of this._sharedInstances) {
621
615
  if (instances.has(instanceId)) return true
622
616
  }
623
617
  return false
624
618
  }
625
619
 
626
620
  _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)
621
+ const entryIsolated = this._isolatedInstances.get(clientId)
622
+ if (entryIsolated && entryIsolated.has(instanceId)) {
623
+ VrpcAdapter._unregisterEventListenersOfInstance(instanceId)
624
+ entryIsolated.delete(instanceId)
625
+ if (entryIsolated.length === 0) {
626
+ this._isolatedInstances.delete(clientId)
632
627
  this._mqttUnsubscribe(`${clientId}/__clientInfo__`)
633
628
  this._log.debug(`Stopped tracking lifetime of client: ${clientId}`)
634
629
  }
635
630
  return false
636
631
  }
637
632
  let found = false
638
- this._namedInstances.forEach(async v => {
633
+ this._sharedInstances.forEach(async v => {
639
634
  if (v.has(instanceId)) {
640
635
  found = true
636
+ VrpcAdapter._unregisterEventListenersOfInstance(instanceId)
641
637
  v.delete(instanceId)
642
638
  if (v.length === 0) {
643
- this._namedInstances.delete(clientId)
639
+ this._sharedInstances.delete(clientId)
644
640
  this._mqttUnsubscribe(`${clientId}/__clientInfo__`)
645
641
  this._log.debug(`Stopped tracking lifetime of client: ${clientId}`)
646
642
  }
@@ -749,4 +745,14 @@ class VrpcAgent extends EventEmitter {
749
745
  * @event VrpcAgent#end
750
746
  */
751
747
 
748
+ /**
749
+ * Event 'clientGone'
750
+ *
751
+ * Emitted when a VRPC client exited that before produced state (e.g. created
752
+ * an anonymous or named instance).
753
+ *
754
+ * @event VrpcAgent#clientGone
755
+ * @type {String} clientId
756
+ */
757
+
752
758
  module.exports = VrpcAgent