vrpc 3.1.0 → 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.0",
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
 
@@ -350,9 +351,17 @@ class VrpcAdapter {
350
351
  ({ tag }) => tag === 'param' || tag === 'arg' || tag === 'argument'
351
352
  )
352
353
  if (params.length > 0) {
353
- params = params.map(({ name, optional, description, type }) => {
354
- return { name, optional, description, type }
355
- })
354
+ params = params.map(
355
+ ({
356
+ name,
357
+ optional,
358
+ description,
359
+ type,
360
+ default: defaultValue
361
+ }) => {
362
+ return { name, optional, description, type, defaultValue }
363
+ }
364
+ )
356
365
  } else {
357
366
  params = []
358
367
  }
@@ -361,10 +370,7 @@ class VrpcAdapter {
361
370
  )
362
371
  if (ret.length === 1) {
363
372
  const { source, type } = ret[0]
364
- const description = source
365
- .split(' ')
366
- .splice(2)
367
- .join(' ')
373
+ const description = source.split(' ').splice(2).join(' ')
368
374
  ret = { description, type }
369
375
  } else {
370
376
  ret = null
@@ -375,7 +381,8 @@ class VrpcAdapter {
375
381
  name: 'instanceName',
376
382
  optional: false,
377
383
  description: 'Name of the instance to be created',
378
- type: 'string'
384
+ type: 'string',
385
+ defaultValue: undefined
379
386
  },
380
387
  ...params
381
388
  ]
@@ -432,7 +439,8 @@ class VrpcAdapter {
432
439
  })
433
440
  json.r = instanceId
434
441
  } catch (err) {
435
- json.e = err.message
442
+ const { message, cause } = err
443
+ json.e = { message, cause }
436
444
  }
437
445
  return instance
438
446
  }
@@ -456,7 +464,8 @@ class VrpcAdapter {
456
464
  instance: instanceId
457
465
  })
458
466
  } catch (err) {
459
- json.e = err.message
467
+ const { message, cause } = err
468
+ json.e = { message, cause }
460
469
  }
461
470
  return instance
462
471
  }
@@ -492,14 +501,21 @@ class VrpcAdapter {
492
501
  calls.push({ id, val: v, err: e })
493
502
  }
494
503
  }
495
- 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
+ )
496
510
  } catch (err) {
497
- json.e = err.message
511
+ const { message, cause } = err
512
+ json.e = { message, cause }
498
513
  }
499
514
  }
500
515
 
501
516
  static sanitizeEventListenerReturnValues (ret) {
502
- return ret && typeof ret === 'object' &&
517
+ return ret &&
518
+ typeof ret === 'object' &&
503
519
  ret._events !== undefined &&
504
520
  ret._eventsCount !== undefined
505
521
  ? true
@@ -509,13 +525,18 @@ class VrpcAdapter {
509
525
  static _handleDelete (json) {
510
526
  try {
511
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
+ })
512
532
  json.r = VrpcAdapter._delete(instance)
513
533
  VrpcAdapter._emitter.emit('delete', {
514
534
  instance,
515
535
  className: json.c
516
536
  })
517
537
  } catch (err) {
518
- json.e = err.message
538
+ const { message, cause } = err
539
+ json.e = { message, cause }
519
540
  }
520
541
  }
521
542
 
@@ -549,7 +570,8 @@ class VrpcAdapter {
549
570
  this._handlePromise(json, ret)
550
571
  } else json.r = ret
551
572
  } catch (err) {
552
- json.e = err.message
573
+ const { message, cause } = err
574
+ json.e = { message, cause }
553
575
  }
554
576
  } else throw new Error(`Could not find function: ${json.f}`)
555
577
  } else {
@@ -568,7 +590,8 @@ class VrpcAdapter {
568
590
  this._handlePromise(json, ret)
569
591
  } else json.r = ret
570
592
  } catch (err) {
571
- json.e = err.message
593
+ const { message, cause } = err
594
+ json.e = { message, cause }
572
595
  }
573
596
  } else throw new Error(`Could not find function: ${json.f}`)
574
597
  }
@@ -603,9 +626,10 @@ class VrpcAdapter {
603
626
  Object.entries(VrpcAdapter._listeners).forEach(([ik, iv]) => {
604
627
  Object.entries(iv).forEach(([ek, ev]) => {
605
628
  const { clients, listener, event } = ev
606
- if (clients.has(clientId)) {
607
- clients.delete(clientId)
608
- if (clients.size === 0) {
629
+ const index = clients.indexOf(clientId)
630
+ if (index !== -1) {
631
+ clients.splice(index, 1)
632
+ if (clients.length === 0) {
609
633
  const instance = VrpcAdapter.getInstance(ik)
610
634
  if (instance && instance.removeListener) {
611
635
  instance.removeListener(event, listener)
@@ -657,11 +681,7 @@ class VrpcAdapter {
657
681
  eventId: arg,
658
682
  event: args[0]
659
683
  })
660
- if (
661
- VrpcAdapter.getInstance(context)
662
- .listeners(arg)
663
- .includes(listener)
664
- ) {
684
+ if (!listener) {
665
685
  return null // skip call as listener is already registered
666
686
  }
667
687
  unwrapped.push(listener)
@@ -717,7 +737,7 @@ class VrpcAdapter {
717
737
  VrpcAdapter._listeners[instanceId] = {
718
738
  [eventId]: {
719
739
  event,
720
- clients: new Set([clientId]),
740
+ clients: [clientId],
721
741
  listener: VrpcAdapter._generateListener({
722
742
  eventId,
723
743
  instanceId,
@@ -725,20 +745,22 @@ class VrpcAdapter {
725
745
  })
726
746
  }
727
747
  }
728
- } else if (!VrpcAdapter._listeners[instanceId][eventId]) {
748
+ return VrpcAdapter._listeners[instanceId][eventId].listener
749
+ }
750
+ if (!VrpcAdapter._listeners[instanceId][eventId]) {
729
751
  VrpcAdapter._listeners[instanceId][eventId] = {
730
752
  event,
731
- clients: new Set([clientId]),
753
+ clients: [clientId],
732
754
  listener: VrpcAdapter._generateListener({
733
755
  eventId,
734
756
  instanceId,
735
757
  isCallAll
736
758
  })
737
759
  }
738
- } else {
739
- VrpcAdapter._listeners[instanceId][eventId].clients.add(clientId)
760
+ return VrpcAdapter._listeners[instanceId][eventId].listener
740
761
  }
741
- return VrpcAdapter._listeners[instanceId][eventId].listener
762
+ VrpcAdapter._listeners[instanceId][eventId].clients.push(clientId)
763
+ return null
742
764
  }
743
765
 
744
766
  static _generateListener ({ eventId, instanceId, isCallAll }) {
@@ -754,8 +776,9 @@ class VrpcAdapter {
754
776
  VrpcAdapter._listeners[instanceId][eventId]
755
777
  ) {
756
778
  const { listener, clients } = VrpcAdapter._listeners[instanceId][eventId]
757
- clients.delete(clientId)
758
- if (clients.size === 0) {
779
+ const index = clients.indexOf(clientId)
780
+ if (index !== -1) clients.splice(index, 1)
781
+ if (clients.length === 0) {
759
782
  delete VrpcAdapter._listeners[instanceId][eventId]
760
783
  if (Object.keys(VrpcAdapter._listeners[instanceId]).length === 0) {
761
784
  delete VrpcAdapter._listeners[instanceId]
@@ -769,9 +792,9 @@ class VrpcAdapter {
769
792
  const eventIds = VrpcAdapter._listeners[instanceId]
770
793
  if (!eventIds) return
771
794
  Object.entries(eventIds).forEach(([ek, ev]) => {
772
- if (ev.event === event && ev.clients.has(clientId)) {
773
- ev.clients.delete(clientId)
774
- 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) {
775
798
  VrpcAdapter.getInstance(instanceId).removeListener(
776
799
  ev.event,
777
800
  ev.listener
@@ -789,17 +812,21 @@ class VrpcAdapter {
789
812
  json.r = id
790
813
  promise
791
814
  .then(r => VrpcAdapter._callback({ ...json, r, i: id }))
792
- .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
+ )
793
822
  } catch (err) {
794
- json.data.e = err.message
823
+ const { message, cause } = err
824
+ json.data.e = { message, cause }
795
825
  }
796
826
  }
797
827
 
798
828
  static _generateId (object) {
799
- return crypto
800
- .createHash('md5')
801
- .update(JSON.stringify(object))
802
- .digest('hex')
829
+ return crypto.createHash('md5').update(JSON.stringify(object)).digest('hex')
803
830
  }
804
831
 
805
832
  static _getClassEntry (className) {
@@ -920,6 +947,7 @@ class VrpcAdapter {
920
947
  *
921
948
  * Emitted on deletion of shared instance
922
949
  *
950
+ * @event VrpcAdapter#beforeDelete
923
951
  * @event VrpcAdapter#delete
924
952
  * @type {Object}
925
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
  }