vrpc 3.1.1 → 3.2.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,3 +10,5 @@
10
10
  /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
11
11
 
12
12
  /*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
13
+
14
+ /** @license URI.js v4.4.1 (c) 2011 Gary Court. License: http://github.com/garycourt/uri-js */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vrpc",
3
- "version": "3.1.1",
3
+ "version": "3.2.0",
4
4
  "description": "Variadic Remote Procedure Calls",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -10,8 +10,8 @@
10
10
  },
11
11
  "scripts": {
12
12
  "build:addon": "ln -sf binding.gyp.no-auto binding.gyp && node-gyp rebuild && rm binding.gyp",
13
- "build:browser": "./node_modules/.bin/webpack",
14
- "build:doc": "./node_modules/.bin/jsdoc2md -f vrpc/*.js --param-list-format=list --separators > docs/api.md",
13
+ "build:browser": "./node_modules/.bin/webpack --stats-error-details",
14
+ "build:doc": "./node_modules/.bin/jsdoc2md -f vrpc/VrpcAdapter.js vrpc/VrpcAgent.js vrpc/VrpcClient.js vrpc/VrpcNative.js --param-list-format=list --separators > docs/api.md",
15
15
  "test": "npm run test:adapter && npm run test:native && npm run test:agent && npm run test:client && npm run test:production",
16
16
  "test:adapter": "./node_modules/.bin/mocha tests/adapter/*.js --exit",
17
17
  "test:native": "./node_modules/.bin/mocha tests/native/*.js --exit",
@@ -69,6 +69,7 @@
69
69
  "argparse": "^2.0.1",
70
70
  "buffer": "^6.0.3",
71
71
  "caller": "^1.0.1",
72
+ "json-stringify-safe": "^5.0.1",
72
73
  "mqtt": "^4.2.6",
73
74
  "nanoid": "^3.1.23",
74
75
  "process": "^0.11.10"
@@ -111,10 +111,8 @@ class VrpcAdapter {
111
111
  this._registerClass(Klass, absJsdocPath, options)
112
112
  } else {
113
113
  const { jsdocPath } = options
114
- const sJsdocPath =
115
- jsdocPath && jsdocPath.endsWith('.js') ? jsdocPath : `${jsdocPath}.js`
116
114
  if (jsdocPath) {
117
- const absJsdocPath = path.resolve(caller(), '../', sJsdocPath)
115
+ const absJsdocPath = path.resolve(caller(), '../', jsdocPath)
118
116
  this._registerClass(code, absJsdocPath, { ...options, jsdoc: true })
119
117
  } else {
120
118
  this._registerClass(code, null, options)
@@ -146,7 +144,7 @@ class VrpcAdapter {
146
144
  }
147
145
  let meta = null
148
146
  if (jsdocPath) {
149
- const absJsdocPath = path.resolve(caller(), '../', `${jsdocPath}.js`)
147
+ const absJsdocPath = path.resolve(caller(), '../', jsdocPath)
150
148
  const content = fs.readFileSync(absJsdocPath).toString('utf8')
151
149
  meta = this._parseComments(content)
152
150
  }
@@ -194,7 +192,10 @@ class VrpcAdapter {
194
192
  const obj = isIsolated
195
193
  ? VrpcAdapter._handleCreateIsolated(json)
196
194
  : VrpcAdapter._handleCreateShared(json)
197
- if (json.e) throw new Error(json.e)
195
+ if (json.e) {
196
+ const { message, cause } = json.e
197
+ throw new Error(message, { cause })
198
+ }
198
199
  return obj
199
200
  }
200
201
 
@@ -369,10 +370,7 @@ class VrpcAdapter {
369
370
  )
370
371
  if (ret.length === 1) {
371
372
  const { source, type } = ret[0]
372
- const description = source
373
- .split(' ')
374
- .splice(2)
375
- .join(' ')
373
+ const description = source.split(' ').splice(2).join(' ')
376
374
  ret = { description, type }
377
375
  } else {
378
376
  ret = null
@@ -441,7 +439,8 @@ class VrpcAdapter {
441
439
  })
442
440
  json.r = instanceId
443
441
  } catch (err) {
444
- json.e = err.message
442
+ const { message, cause } = err
443
+ json.e = { message, cause }
445
444
  }
446
445
  return instance
447
446
  }
@@ -465,7 +464,8 @@ class VrpcAdapter {
465
464
  instance: instanceId
466
465
  })
467
466
  } catch (err) {
468
- json.e = err.message
467
+ const { message, cause } = err
468
+ json.e = { message, cause }
469
469
  }
470
470
  return instance
471
471
  }
@@ -501,9 +501,15 @@ class VrpcAdapter {
501
501
  calls.push({ id, val: v, err: e })
502
502
  }
503
503
  }
504
- this._handlePromise(json, Promise.all(calls))
504
+ this._handlePromise(
505
+ json,
506
+ Promise.allSettled(calls).then(result =>
507
+ result.filter(x => x.status === 'fulfilled').map(x => x.value)
508
+ )
509
+ )
505
510
  } catch (err) {
506
- json.e = err.message
511
+ const { message, cause } = err
512
+ json.e = { message, cause }
507
513
  }
508
514
  }
509
515
 
@@ -519,13 +525,18 @@ class VrpcAdapter {
519
525
  static _handleDelete (json) {
520
526
  try {
521
527
  const instance = json.a[0] // first arg is instance to be deleted
528
+ VrpcAdapter._emitter.emit('beforeDelete', {
529
+ instance,
530
+ className: json.c
531
+ })
522
532
  json.r = VrpcAdapter._delete(instance)
523
533
  VrpcAdapter._emitter.emit('delete', {
524
534
  instance,
525
535
  className: json.c
526
536
  })
527
537
  } catch (err) {
528
- json.e = err.message
538
+ const { message, cause } = err
539
+ json.e = { message, cause }
529
540
  }
530
541
  }
531
542
 
@@ -559,7 +570,8 @@ class VrpcAdapter {
559
570
  this._handlePromise(json, ret)
560
571
  } else json.r = ret
561
572
  } catch (err) {
562
- json.e = err.message
573
+ const { message, cause } = err
574
+ json.e = { message, cause }
563
575
  }
564
576
  } else throw new Error(`Could not find function: ${json.f}`)
565
577
  } else {
@@ -578,7 +590,8 @@ class VrpcAdapter {
578
590
  this._handlePromise(json, ret)
579
591
  } else json.r = ret
580
592
  } catch (err) {
581
- json.e = err.message
593
+ const { message, cause } = err
594
+ json.e = { message, cause }
582
595
  }
583
596
  } else throw new Error(`Could not find function: ${json.f}`)
584
597
  }
@@ -613,9 +626,10 @@ class VrpcAdapter {
613
626
  Object.entries(VrpcAdapter._listeners).forEach(([ik, iv]) => {
614
627
  Object.entries(iv).forEach(([ek, ev]) => {
615
628
  const { clients, listener, event } = ev
616
- if (clients.has(clientId)) {
617
- clients.delete(clientId)
618
- if (clients.size === 0) {
629
+ const index = clients.indexOf(clientId)
630
+ if (index !== -1) {
631
+ clients.splice(index, 1)
632
+ if (clients.length === 0) {
619
633
  const instance = VrpcAdapter.getInstance(ik)
620
634
  if (instance && instance.removeListener) {
621
635
  instance.removeListener(event, listener)
@@ -667,11 +681,7 @@ class VrpcAdapter {
667
681
  eventId: arg,
668
682
  event: args[0]
669
683
  })
670
- if (
671
- VrpcAdapter.getInstance(context)
672
- .listeners(arg)
673
- .includes(listener)
674
- ) {
684
+ if (!listener) {
675
685
  return null // skip call as listener is already registered
676
686
  }
677
687
  unwrapped.push(listener)
@@ -727,7 +737,7 @@ class VrpcAdapter {
727
737
  VrpcAdapter._listeners[instanceId] = {
728
738
  [eventId]: {
729
739
  event,
730
- clients: new Set([clientId]),
740
+ clients: [clientId],
731
741
  listener: VrpcAdapter._generateListener({
732
742
  eventId,
733
743
  instanceId,
@@ -735,20 +745,22 @@ class VrpcAdapter {
735
745
  })
736
746
  }
737
747
  }
738
- } else if (!VrpcAdapter._listeners[instanceId][eventId]) {
748
+ return VrpcAdapter._listeners[instanceId][eventId].listener
749
+ }
750
+ if (!VrpcAdapter._listeners[instanceId][eventId]) {
739
751
  VrpcAdapter._listeners[instanceId][eventId] = {
740
752
  event,
741
- clients: new Set([clientId]),
753
+ clients: [clientId],
742
754
  listener: VrpcAdapter._generateListener({
743
755
  eventId,
744
756
  instanceId,
745
757
  isCallAll
746
758
  })
747
759
  }
748
- } else {
749
- VrpcAdapter._listeners[instanceId][eventId].clients.add(clientId)
760
+ return VrpcAdapter._listeners[instanceId][eventId].listener
750
761
  }
751
- return VrpcAdapter._listeners[instanceId][eventId].listener
762
+ VrpcAdapter._listeners[instanceId][eventId].clients.push(clientId)
763
+ return null
752
764
  }
753
765
 
754
766
  static _generateListener ({ eventId, instanceId, isCallAll }) {
@@ -764,8 +776,9 @@ class VrpcAdapter {
764
776
  VrpcAdapter._listeners[instanceId][eventId]
765
777
  ) {
766
778
  const { listener, clients } = VrpcAdapter._listeners[instanceId][eventId]
767
- clients.delete(clientId)
768
- if (clients.size === 0) {
779
+ const index = clients.indexOf(clientId)
780
+ if (index !== -1) clients.splice(index, 1)
781
+ if (clients.length === 0) {
769
782
  delete VrpcAdapter._listeners[instanceId][eventId]
770
783
  if (Object.keys(VrpcAdapter._listeners[instanceId]).length === 0) {
771
784
  delete VrpcAdapter._listeners[instanceId]
@@ -779,9 +792,9 @@ class VrpcAdapter {
779
792
  const eventIds = VrpcAdapter._listeners[instanceId]
780
793
  if (!eventIds) return
781
794
  Object.entries(eventIds).forEach(([ek, ev]) => {
782
- if (ev.event === event && ev.clients.has(clientId)) {
783
- ev.clients.delete(clientId)
784
- if (ev.clients.size === 0) {
795
+ if (ev.event === event && ev.clients.includes(clientId)) {
796
+ ev.clients = ev.clients.filter(x => x !== clientId)
797
+ if (ev.clients.length === 0) {
785
798
  VrpcAdapter.getInstance(instanceId).removeListener(
786
799
  ev.event,
787
800
  ev.listener
@@ -799,17 +812,21 @@ class VrpcAdapter {
799
812
  json.r = id
800
813
  promise
801
814
  .then(r => VrpcAdapter._callback({ ...json, r, i: id }))
802
- .catch(err => VrpcAdapter._callback({ ...json, e: err.message, i: id }))
815
+ .catch(err =>
816
+ VrpcAdapter._callback({
817
+ ...json,
818
+ e: { message: err.message, cause: err.cause },
819
+ i: id
820
+ })
821
+ )
803
822
  } catch (err) {
804
- json.data.e = err.message
823
+ const { message, cause } = err
824
+ json.data.e = { message, cause }
805
825
  }
806
826
  }
807
827
 
808
828
  static _generateId (object) {
809
- return crypto
810
- .createHash('md5')
811
- .update(JSON.stringify(object))
812
- .digest('hex')
829
+ return crypto.createHash('md5').update(JSON.stringify(object)).digest('hex')
813
830
  }
814
831
 
815
832
  static _getClassEntry (className) {
@@ -930,6 +947,7 @@ class VrpcAdapter {
930
947
  *
931
948
  * Emitted on deletion of shared instance
932
949
  *
950
+ * @event VrpcAdapter#beforeDelete
933
951
  * @event VrpcAdapter#delete
934
952
  * @type {Object}
935
953
  * @property {String} className The class name of the deleted instance
package/vrpc/VrpcAgent.js CHANGED
@@ -3,8 +3,9 @@ const path = require('path')
3
3
  const mqtt = require('mqtt')
4
4
  const crypto = require('crypto')
5
5
  const { ArgumentParser } = require('argparse')
6
- const VrpcAdapter = require('./VrpcAdapter')
7
6
  const EventEmitter = require('events')
7
+ const jsonStringifySafe = require('json-stringify-safe')
8
+ const VrpcAdapter = require('./VrpcAdapter')
8
9
 
9
10
  const VRPC_PROTOCOL_VERSION = 3
10
11
 
@@ -67,13 +68,11 @@ class VrpcAgent extends EventEmitter {
67
68
  default: password
68
69
  })
69
70
  parser.add_argument('--bestEffort', {
70
- help:
71
- 'Calls function on best-effort. Improves performance but may fail under unstable network connections.',
71
+ help: 'Calls function on best-effort. Improves performance but may fail under unstable network connections.',
72
72
  action: 'store_true'
73
73
  })
74
74
  parser.add_argument('-V', '-v', '--userVersion', {
75
- help:
76
- 'User defined agent version. May be checked on the remote side for compatibility checks.',
75
+ help: 'User defined agent version. May be checked on the remote side for compatibility checks.',
77
76
  required: false,
78
77
  default: version,
79
78
  dest: 'version'
@@ -99,7 +98,7 @@ class VrpcAgent extends EventEmitter {
99
98
  * @param {String} [obj.agent='<user>-<pathId>@<hostname>-<platform>-js'] This agent's name
100
99
  * @param {String} [obj.broker='mqtts://vrpc.io:8883'] Broker url in form: `<scheme>://<host>:<port>`
101
100
  * @param {Object} [obj.log=console] Log object (must support debug, info, warn, and error level)
102
- * @param {String} [obj.bestEffort=false] If true, message will be sent with best effort, i.e. no caching if offline
101
+ * @param {String} [obj.bestEffort=true] If true, message will be sent with best effort, i.e. no caching if offline
103
102
  * @param {String} [obj.version=''] The (user-defined) version of this agent
104
103
  * @param {String} [obj.mqttClientId='<generated()>'] Explicitly set the mqtt client id.
105
104
  *
@@ -117,7 +116,7 @@ class VrpcAgent extends EventEmitter {
117
116
  agent = VrpcAgent._generateAgentName(),
118
117
  broker = 'mqtts://vrpc.io:8883',
119
118
  log = 'console',
120
- bestEffort = false,
119
+ bestEffort = true,
121
120
  version = '',
122
121
  mqttClientId = null
123
122
  } = {}) {
@@ -244,6 +243,32 @@ class VrpcAgent extends EventEmitter {
244
243
  }
245
244
  }
246
245
 
246
+ /**
247
+ * Creates a new instance locally
248
+ *
249
+ * NOTE: The instance must previously be registered by the local VrpcAdapter
250
+ *
251
+ * @param {Object} options
252
+ * @param {String} options.className Name of the class which should be
253
+ * instantiated
254
+ * @param {String} [options.instance] Name of the created instance. If not
255
+ * provided an id will be generated
256
+ * @param {Array} [options.args] Positional arguments for the constructor call
257
+ * @param {bool} [options.isIsolated=false] If true the created instance will
258
+ * be visible only to the client who created it
259
+ * @returns {Object} The real instance (not a proxy!)
260
+ */
261
+ create ({ className, instance = nanoid(8), args = [], isIsolated = false }) {
262
+ const obj = VrpcAdapter.create({ className, instance, args, isIsolated })
263
+ if (!this._hasSharedInstance(instance)) {
264
+ this._subscribeToMethodsOfNewInstance(className, instance)
265
+ this._publishClassInfoMessage(className)
266
+ this._publishClassInfoConciseMessage(className)
267
+ this._registerSharedInstance(instance, null)
268
+ }
269
+ return obj
270
+ }
271
+
247
272
  static _generateAgentName () {
248
273
  const { username } = os.userInfo()
249
274
  const pathId = crypto
@@ -269,10 +294,7 @@ class VrpcAgent extends EventEmitter {
269
294
  os.type() +
270
295
  JSON.stringify(os.networkInterfaces()) +
271
296
  JSON.stringify(os.cpus().map(({ model }) => model))
272
- return crypto
273
- .createHash('md5')
274
- .update(uid)
275
- .digest('hex')
297
+ return crypto.createHash('md5').update(uid).digest('hex')
276
298
  }
277
299
 
278
300
  _validateDomain (domain) {
@@ -427,9 +449,9 @@ class VrpcAgent extends EventEmitter {
427
449
  let str
428
450
  const { c, f } = json
429
451
  try {
430
- str = JSON.stringify(json)
452
+ str = jsonStringifySafe(json)
431
453
  } catch (err) {
432
- this._log.debug(
454
+ console.error(
433
455
  `Failed serialization of return value for: ${c}::${f}, because: ${err.message}`
434
456
  )
435
457
  json.r = '__vrpc::not-serializable__'
@@ -456,6 +478,7 @@ class VrpcAgent extends EventEmitter {
456
478
  const classes = this._getClasses()
457
479
  classes.forEach(className => {
458
480
  this._publishClassInfoMessage(className)
481
+ this._publishClassInfoConciseMessage(className)
459
482
  })
460
483
  // Register all pre-existing instances
461
484
  for (const [
@@ -477,7 +500,7 @@ class VrpcAgent extends EventEmitter {
477
500
 
478
501
  _publishClassInfoMessage (className) {
479
502
  const json = {
480
- className: className,
503
+ className,
481
504
  instances: this._getInstances(className),
482
505
  memberFunctions: this._getMemberFunctions(className),
483
506
  staticFunctions: this._getStaticFunctions(className),
@@ -490,6 +513,25 @@ class VrpcAgent extends EventEmitter {
490
513
  JSON.stringify(json),
491
514
  { retain: true }
492
515
  )
516
+ } catch (err) {
517
+ this._log.error(err, `Problem during publishing schema: ${err.message}`)
518
+ }
519
+ }
520
+
521
+ _publishClassInfoConciseMessage (className) {
522
+ const json = {
523
+ className,
524
+ instances: this._getInstances(className),
525
+ memberFunctions: this._getMemberFunctions(className),
526
+ staticFunctions: this._getStaticFunctions(className),
527
+ v: VRPC_PROTOCOL_VERSION
528
+ }
529
+ try {
530
+ this._mqttPublish(
531
+ `${this._baseTopic}/${className}/__classInfoConcise__`,
532
+ JSON.stringify(json),
533
+ { retain: true }
534
+ )
493
535
  } catch (err) {
494
536
  this._log.error(
495
537
  err,
@@ -563,6 +605,7 @@ class VrpcAgent extends EventEmitter {
563
605
  if (!this._hasSharedInstance(instanceId)) {
564
606
  this._subscribeToMethodsOfNewInstance(className, instanceId)
565
607
  this._publishClassInfoMessage(className)
608
+ this._publishClassInfoConciseMessage(className)
566
609
  }
567
610
  this._registerSharedInstance(instanceId, json.s)
568
611
  break
@@ -570,7 +613,10 @@ class VrpcAgent extends EventEmitter {
570
613
  case '__delete__': {
571
614
  this._unsubscribeMethodsOfDeletedInstance(className, instance)
572
615
  const wasShared = this._unregisterInstance(json.a[0], json.s)
573
- if (wasShared) this._publishClassInfoMessage(className)
616
+ if (wasShared) {
617
+ this._publishClassInfoMessage(className)
618
+ this._publishClassInfoConciseMessage(className)
619
+ }
574
620
  break
575
621
  }
576
622
  }
@@ -666,10 +712,10 @@ class VrpcAgent extends EventEmitter {
666
712
  }
667
713
  })
668
714
  if (!found) {
669
- this._log.warn(
670
- `Failed un-registering not registered instance: ${instanceId}`
715
+ this._log.info(
716
+ `Unregistering non-tracked (possibly by agent created) instance: ${instanceId}`
671
717
  )
672
- return false
718
+ return true
673
719
  }
674
720
  return true
675
721
  }