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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
  module.exports = {
3
- VrpcLocal: require('./vrpc/VrpcLocal'),
4
- VrpcRemote: require('./vrpc/VrpcRemote'),
3
+ VrpcNative: require('./vrpc/VrpcNative'),
4
+ VrpcClient: require('./vrpc/VrpcClient'),
5
5
  VrpcAdapter: require('./vrpc/VrpcAdapter'),
6
6
  VrpcAgent: require('./vrpc/VrpcAgent')
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vrpc",
3
- "version": "2.5.1",
3
+ "version": "3.0.0-alpha.3",
4
4
  "description": "Variadic Remote Procedure Calls",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -9,18 +9,15 @@
9
9
  "test": "test"
10
10
  },
11
11
  "scripts": {
12
- "build": "ln -sf binding.gyp.no-auto binding.gyp && third_party/install.sh && node-gyp rebuild && rm binding.gyp",
13
- "test": "npm run test:cpp && npm run test:jsLocal && npm run test:jsRemote && npm run test:jsRemoteClient && npm run test:jsAgent",
14
- "test:cpp": "build/Release/vrpc-test",
15
- "test:jsLocal": "./node_modules/.bin/mocha test/js/local/*.js --exit",
16
- "test:jsRemote": "./node_modules/.bin/mocha test/js/remote/lifeCycleTest.js --timeout 30000 --exit && test/js/remote/test.sh",
17
- "test:jsRemoteClient": "test/js/remoteClient/test.sh",
18
- "test:jsAgent": "test/js/agent/test.sh",
19
- "test:jsPerformance": "test/js/performance/test.sh",
20
- "build:pydoc": "pydoc-markdown -I vrpc -m VrpcLocal --render-toc > docs/reference/python.md",
21
- "build:jsdoc": "./node_modules/.bin/jsdoc2md -f vrpc/*.js --param-list-format=list --separators > docs/reference/nodeJs.md",
22
- "build:doc": "npm run build:pydoc; npm run build:jsdoc",
23
- "build:browser": "./node_modules/.bin/webpack"
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",
15
+ "test": "npm run test:agent && npm run test:client && npm run test:native",
16
+ "test:agent": "tests/agent/test.sh",
17
+ "test:client": "tests/client/test.sh",
18
+ "test:native": "./node_modules/.bin/mocha tests/native/vrpc-native*.js --exit",
19
+ "test:performance": "tests/performance/test.sh",
20
+ "test:production": "./node_modules/.bin/mocha tests/production/lifeCycleTest.js --timeout 30000 --exit && tests/production/test.sh"
24
21
  },
25
22
  "bin": {
26
23
  "vrpc-agent-js": "./vrpc/vrpc-agent-js"
@@ -34,7 +31,7 @@
34
31
  "remote procedural calls",
35
32
  "language binding",
36
33
  "C++",
37
- "C++ binding",
34
+ "C++ embedding",
38
35
  "header-only"
39
36
  ],
40
37
  "repository": {
@@ -51,7 +48,7 @@
51
48
  "chai": "^4.2.0",
52
49
  "crypto-browserify": "^3.12.0",
53
50
  "dockerode": "^3.2.0",
54
- "jsdoc-to-markdown": "^6.0.1",
51
+ "jsdoc-to-markdown": "^7.0.1",
55
52
  "mocha": "^6.1.4",
56
53
  "os-browserify": "^0.3.0",
57
54
  "sinon": "^7.3.2",
@@ -62,7 +59,6 @@
62
59
  },
63
60
  "files": [
64
61
  "vrpc/",
65
- "third_party/",
66
62
  "browser/",
67
63
  "binding.gyp.no-auto"
68
64
  ],
@@ -10,9 +10,9 @@ __\/\\\_______\/\\\__/\\\///////\\\___\/\\\/////////\\\____/\\\////////__
10
10
  ________\///________\///________\///__\///___________________\/////////__
11
11
 
12
12
 
13
- Non-intrusively binds code and provides access in form of asynchronous remote
13
+ Non-intrusively adapts code and provides access in form of asynchronous remote
14
14
  procedure calls (RPC).
15
- Author: Dr. Burkhard C. Heisen (https://github.com/bheisen/vrpc)
15
+ Author: Dr. Burkhard C. Heisen (https://github.com/heisenware/vrpc)
16
16
 
17
17
 
18
18
  Licensed under the MIT License <http://opensource.org/licenses/MIT>.
@@ -42,9 +42,9 @@ const path = require('path')
42
42
  const fs = require('fs')
43
43
  const Ajv = require('ajv')
44
44
  const caller = require('caller')
45
- const { nanoid } = require('nanoid')
46
45
  const commentParser = require('./comment-parser')
47
46
  const EventEmitter = require('events')
47
+ const { type } = require('os')
48
48
 
49
49
  /**
50
50
  * Generates an adapter layer for existing code and enables further VRPC-based
@@ -152,10 +152,14 @@ class VrpcAdapter {
152
152
  meta,
153
153
  Klass: {},
154
154
  withNew: false,
155
- staticFunctions: ['__getNamed__'],
155
+ staticFunctions: [],
156
156
  schema: null
157
157
  })
158
- VrpcAdapter._instances.set(instance, { className, instance: obj })
158
+ VrpcAdapter._instances.set(instance, {
159
+ className,
160
+ instance: obj,
161
+ isIsolated: false
162
+ })
159
163
  }
160
164
 
161
165
  static getClasses () {
@@ -351,11 +355,10 @@ class VrpcAdapter {
351
355
  staticFunctions = staticFunctions.filter(f => !f.startsWith('_'))
352
356
  }
353
357
  // Inject constructor and destructor
354
- staticFunctions.push('__create__')
355
- staticFunctions.push('__delete__')
356
- staticFunctions.push('__createNamed__')
357
- staticFunctions.push('__getNamed__')
358
+ staticFunctions.push('__createIsolated__')
359
+ staticFunctions.push('__createShared__')
358
360
  staticFunctions.push('__callAll__')
361
+ staticFunctions.push('__delete__')
359
362
  let memberFunctions = VrpcAdapter._extractMemberFunctions(Klass)
360
363
  if (onlyPublic) {
361
364
  memberFunctions = memberFunctions.filter(f => {
@@ -416,7 +419,7 @@ class VrpcAdapter {
416
419
  },
417
420
  ...params
418
421
  ]
419
- meta.__createNamed__ = { description, params: modified, ret }
422
+ meta.__createShared__ = { description, params: modified, ret }
420
423
  } else {
421
424
  meta[functionName] = { description, params, ret }
422
425
  }
@@ -427,133 +430,170 @@ class VrpcAdapter {
427
430
  }
428
431
 
429
432
  static _call (json) {
430
- const { context, method, data } = json
431
- const wrappedArgs = VrpcAdapter._wrapCallbacks(json)
432
- // VrpcAdapter._log.debug(`Calling function: ${method} with payload: ${data}`)
433
- switch (method) {
434
- // Special case: ctor
435
- case '__create__':
436
- try {
437
- const instance = VrpcAdapter._create(context, ...wrappedArgs)
438
- const instanceId = nanoid(9)
439
- VrpcAdapter._instances.set(instanceId, { context, instance })
440
- data.r = instanceId
441
- } catch (err) {
442
- data.e = err.message
443
- }
433
+ const before = Object.keys(VrpcAdapter._listeners).length
434
+ switch (json.f) {
435
+ case '__createIsolated__':
436
+ VrpcAdapter._handleCreateIsolated(json)
437
+ break
438
+ case '__createShared__':
439
+ VrpcAdapter._handleCreateShared(json)
440
+ break
441
+ case '__callAll__':
442
+ VrpcAdapter._handleCallAll(json)
444
443
  break
445
- // Special case: dtor
446
444
  case '__delete__':
447
- try {
448
- const instanceId = wrappedArgs[0]
449
- data.r = VrpcAdapter._delete(instanceId)
450
- VrpcAdapter._emitter.emit('delete', {
451
- className: context,
452
- instance: wrappedArgs[0]
453
- })
454
- } catch (err) {
455
- data.e = err.message
456
- }
445
+ VrpcAdapter._handleDelete(json)
457
446
  break
458
- // Special case: named construction
459
- case '__createNamed__':
447
+ default:
448
+ VrpcAdapter._handleCall(json)
449
+ }
450
+ const after = Object.keys(VrpcAdapter._listeners).length
451
+ // if we took a listener on board we must tell our caller such that
452
+ // things can be cleaned up later.
453
+ return after > before
454
+ }
455
+
456
+ static _handleCreateIsolated (json) {
457
+ try {
458
+ const className = json.c
459
+ const [instanceId, ...args] = json.a
460
+ const instance = VrpcAdapter._create(className, instanceId, ...args)
461
+ VrpcAdapter._instances.set(instanceId, {
462
+ instance,
463
+ className,
464
+ isIsolated: true
465
+ })
466
+ json.r = instanceId
467
+ } catch (err) {
468
+ json.e = err.message
469
+ }
470
+ }
471
+
472
+ static _handleCreateShared (json) {
473
+ try {
474
+ const className = json.c
475
+ const [instanceId, ...args] = json.a
476
+ const instance = VrpcAdapter._create(className, instanceId, ...args)
477
+ VrpcAdapter._instances.set(instanceId, {
478
+ instance,
479
+ className,
480
+ isIsolated: false
481
+ })
482
+ json.r = instanceId
483
+ VrpcAdapter._emitter.emit('create', {
484
+ args,
485
+ className,
486
+ instance: instanceId
487
+ })
488
+ } catch (err) {
489
+ json.e = err.message
490
+ }
491
+ }
492
+
493
+ static _handleCallAll (json) {
494
+ try {
495
+ const calls = []
496
+ for (const [
497
+ id,
498
+ { className, instance, isIsolated }
499
+ ] of VrpcAdapter._instances) {
500
+ // TODO Think about a configurable behavior w.r.t. isolated instances...
501
+ if (className !== json.c || isIsolated) continue
502
+ let v
503
+ let e = null
460
504
  try {
461
- VrpcAdapter._createNamed(context, ...wrappedArgs)
462
- data.r = wrappedArgs[0] // First argument is instanceId
463
- VrpcAdapter._emitter.emit('create', {
464
- className: context,
465
- instance: wrappedArgs[0],
466
- args: wrappedArgs.slice(1)
467
- })
505
+ const wrappedArgs = VrpcAdapter._wrapArguments(json, id)
506
+ const funcName = wrappedArgs[0]
507
+ v = instance[funcName].apply(instance, wrappedArgs.slice(1))
508
+ v = VrpcAdapter.sanitizeEventListenerReturnValues(v)
468
509
  } catch (err) {
469
- data.e = err.message
510
+ e = err.message
470
511
  }
471
- break
472
- case '__getNamed__': // Special case: retrieval of known instance
512
+ if (VrpcAdapter._isPromise(v)) {
513
+ calls.push(
514
+ v
515
+ .then(val => ({ id, val, err: null }))
516
+ .catch(err => ({ id, err, val: null }))
517
+ )
518
+ } else {
519
+ calls.push({ id, val: v, err: e })
520
+ }
521
+ }
522
+ this._handlePromise(json, Promise.all(calls))
523
+ } catch (err) {
524
+ json.e = err.message
525
+ }
526
+ }
527
+
528
+ static sanitizeEventListenerReturnValues (ret) {
529
+ return typeof ret === 'object' &&
530
+ ret._events !== undefined &&
531
+ ret._eventsCount !== undefined
532
+ ? true
533
+ : ret
534
+ }
535
+
536
+ static _handleDelete (json) {
537
+ try {
538
+ const wrappedArgs = VrpcAdapter._wrapArguments(json)
539
+ const instanceId = wrappedArgs[0]
540
+ json.r = VrpcAdapter._delete(instanceId)
541
+ VrpcAdapter._emitter.emit('delete', {
542
+ className: json.c,
543
+ instance: wrappedArgs[0]
544
+ })
545
+ } catch (err) {
546
+ json.e = err.message
547
+ }
548
+ }
549
+
550
+ static _handleCall (json) {
551
+ const wrappedArgs = VrpcAdapter._wrapArguments(json)
552
+ // Check whether context is a registered class
553
+ const entry = VrpcAdapter._functionRegistry.get(json.c)
554
+ if (entry !== undefined) {
555
+ // entry is class -> function is static
556
+ const { Klass } = entry
557
+ // TODO Think about whether to do live checking (like here) or
558
+ // rather sticking to those functions registered before...
559
+ // TODO This is even more important now as the agent does a wildcard
560
+ // subscription against all functions
561
+ if (VrpcAdapter._isFunction(Klass[json.f])) {
473
562
  try {
474
- const instanceId = wrappedArgs[0]
475
- const entry = VrpcAdapter._instances.get(instanceId)
476
- if (entry) data.r = instanceId
477
- else data.e = `Instance with id: ${instanceId} does not exist`
563
+ const ret = Klass[json.f].apply(null, wrappedArgs)
564
+ // check if function returns promise
565
+ if (VrpcAdapter._isPromise(ret)) {
566
+ this._handlePromise(json, ret)
567
+ } else json.r = ret
478
568
  } catch (err) {
479
- data.e = err.message
569
+ json.e = err.message
480
570
  }
481
- break
482
- case '__callAll__': // Special case: call on all known instances
571
+ } else throw new Error(`Could not find function: ${json.f}`)
572
+ } else {
573
+ // is not static
574
+ const entry = VrpcAdapter._instances.get(json.c)
575
+ if (entry === undefined) {
576
+ throw new Error(`Could not find context: ${json.c}`)
577
+ }
578
+ const { instance } = entry
579
+ if (VrpcAdapter._isFunction(instance[json.f])) {
483
580
  try {
484
- const funcName = wrappedArgs[0]
485
- const calls = []
486
- for (const [id, { className, instance }] of VrpcAdapter._instances) {
487
- if (className !== context) continue
488
- let v
489
- let e = null
490
- try {
491
- v = instance[funcName].apply(instance, wrappedArgs.slice(1))
492
- } catch (err) {
493
- e = err
494
- }
495
- // check wether function returned a promise
496
- if (VrpcAdapter._isPromise(v)) {
497
- calls.push(
498
- v
499
- .then(val => ({ id, val, err: null }))
500
- .catch(err => ({ id, err, val: null }))
501
- )
502
- } else {
503
- calls.push({ id, val: v, err: e })
504
- }
505
- }
506
- this._handlePromise(json, method, Promise.all(calls))
581
+ let ret = instance[json.f].apply(instance, wrappedArgs)
582
+ ret = VrpcAdapter.sanitizeEventListenerReturnValues(ret)
583
+ // check if function returns promise
584
+ if (VrpcAdapter._isPromise(ret)) {
585
+ this._handlePromise(json, ret)
586
+ } else json.r = ret
507
587
  } catch (err) {
508
- data.e = err.message
588
+ json.e = err.message
509
589
  }
510
- break
511
- // Regular function call
512
- default: {
513
- // Check whether context is a registered class
514
- const entry = VrpcAdapter._functionRegistry.get(context)
515
- if (entry !== undefined) {
516
- // entry is class -> function is static
517
- const { Klass } = entry
518
- // TODO Think about whether to do live checking (like here) or
519
- // rather sticking to those functions registered before...
520
- // TODO This is even more important now as the agent does a wildcard
521
- // subscription against all functions
522
- if (VrpcAdapter._isFunction(Klass[method])) {
523
- try {
524
- const ret = Klass[method].apply(null, wrappedArgs)
525
- // check if function returns promise
526
- if (ret && this._isFunction(ret.then)) {
527
- this._handlePromise(json, method, ret)
528
- } else data.r = ret
529
- } catch (err) {
530
- data.e = err.message
531
- }
532
- } else throw new Error(`Could not find function: ${method}`)
533
- } else {
534
- // is not static
535
- const entry = VrpcAdapter._instances.get(context)
536
- if (entry === undefined) {
537
- throw new Error(`Could not find context: ${context}`)
538
- }
539
- const { instance } = entry
540
- if (VrpcAdapter._isFunction(instance[method])) {
541
- try {
542
- const ret = instance[method].apply(instance, wrappedArgs)
543
- // check if function returns promise
544
- if (VrpcAdapter._isPromise(ret)) {
545
- this._handlePromise(json, method, ret)
546
- } else data.r = ret
547
- } catch (err) {
548
- data.e = err.message
549
- }
550
- } else throw new Error(`Could not find function: ${method}`)
551
- }
552
- }
590
+ } else throw new Error(`Could not find function: ${json.f}`)
553
591
  }
554
592
  }
555
593
 
556
- static _create (className, ...args) {
594
+ static _create (className, instanceId, ...args) {
595
+ const entry = VrpcAdapter._instances.get(instanceId)
596
+ if (entry) return entry.instance
557
597
  const { Klass, withNew, schema } = VrpcAdapter._getClassEntry(className)
558
598
  if (schema !== null) {
559
599
  VrpcAdapter._validate(schema, ...args)
@@ -562,16 +602,6 @@ class VrpcAdapter {
562
602
  return Klass(...args)
563
603
  }
564
604
 
565
- static _createNamed (className, instanceId, ...args) {
566
- const entry = VrpcAdapter._instances.get(instanceId)
567
- if (!entry) {
568
- const instance = VrpcAdapter._create(className, ...args)
569
- VrpcAdapter._instances.set(instanceId, { className, instance })
570
- return instance
571
- }
572
- return entry.instance
573
- }
574
-
575
605
  static _delete (instanceId) {
576
606
  const entry = VrpcAdapter._instances.get(instanceId)
577
607
  if (entry) {
@@ -591,6 +621,18 @@ class VrpcAdapter {
591
621
  delete VrpcAdapter._listeners[clientId]
592
622
  }
593
623
 
624
+ static _unregisterEventListenersOfInstance (instance) {
625
+ for (const [key, value] of Object.entries(VrpcAdapter._listeners)) {
626
+ VrpcAdapter._listeners[key] = value.filter(({ i, e, f }) => {
627
+ if (instance === i) {
628
+ VrpcAdapter.getInstance(i).removeListener(e, f)
629
+ return false
630
+ }
631
+ return true
632
+ })
633
+ }
634
+ }
635
+
594
636
  static _validate (schema, params) {
595
637
  if (!VrpcAdapter._ajv) {
596
638
  VrpcAdapter._ajv = new Ajv({ useDefaults: true })
@@ -599,42 +641,30 @@ class VrpcAdapter {
599
641
  if (!valid) throw new Error(VrpcAdapter._ajv.errorsText())
600
642
  }
601
643
 
602
- static _wrapCallbacks (json) {
603
- const { method, sender, data, context } = json
604
- const args = VrpcAdapter._extractDataToArray(data)
605
- const wrappedArgs = []
644
+ static _wrapArguments (json, instanceId) {
645
+ const { f: func, s: sender, a: args, c: context } = json
646
+ const wrapped = []
606
647
  args.forEach(arg => {
607
648
  // Find those args that actually need to be function callbacks
608
649
  if (typeof arg === 'string' && arg.substr(0, 5) === '__f__') {
609
- const f = (...innerArgs) => {
610
- // do not even trigger a callback for already offline clients
611
- if (sender && !VrpcAdapter._listeners[sender]) return
612
- const data = {}
613
- innerArgs.forEach((value, index) => {
614
- data[`_${index + 1}`] = value
615
- })
616
- const callbackJson = Object.assign({}, json, { data, id: arg })
617
- const callbackJsonString = VrpcAdapter._stringifySafely(callbackJson)
618
- VrpcAdapter._callback(callbackJsonString, callbackJson)
619
- }
620
- wrappedArgs.push(f)
650
+ const f = this._generateWrapper(arg, json, instanceId)
651
+ wrapped.push(f)
621
652
  if (!sender) return
622
653
  // Register this client to later know when he is dead and skip callbacks
623
654
  if (!VrpcAdapter._listeners[sender]) VrpcAdapter._listeners[sender] = []
624
655
  // Check whether injected callback is an EventEmitter registration
625
- if (method === 'on' && typeof args[0] === 'string') {
656
+ if (func === 'on' && typeof args[0] === 'string') {
626
657
  const e = args[0]
627
658
  const i = context
628
659
  const r = arg
629
660
  VrpcAdapter._listeners[sender].push({ i, e, f, r })
630
661
  }
631
662
  if (
632
- (method === 'off' || method === 'removeListener') &&
663
+ (func === 'off' || func === 'removeListener') &&
633
664
  typeof args[0] === 'string'
634
665
  ) {
635
- const r = arg
636
- const s = VrpcAdapter._listeners[sender]
637
- const entry = s && s.find(x => x.r === r)
666
+ const listener = VrpcAdapter._listeners[sender]
667
+ const entry = listener && listener.find(x => x.r === arg)
638
668
  if (entry) {
639
669
  const { i, e, f } = entry
640
670
  VrpcAdapter.getInstance(i).removeListener(e, f)
@@ -642,54 +672,33 @@ class VrpcAdapter {
642
672
  }
643
673
  } else {
644
674
  // Leave the others untouched
645
- wrappedArgs.push(arg)
675
+ wrapped.push(arg)
646
676
  }
647
677
  })
648
- return wrappedArgs
678
+ return wrapped
649
679
  }
650
680
 
651
- static _handlePromise (json, method, promise) {
652
- try {
653
- const cid = VrpcAdapter._correlationId++ % Number.MAX_SAFE_INTEGER
654
- const id = `__p__${method}-${cid}`
655
- json.data.r = id
656
- promise
657
- .then(value => {
658
- const data = { r: value }
659
- const promiseJson = Object.assign({}, json, { data, id })
660
- const promiseJsonString = VrpcAdapter._stringifySafely(promiseJson)
661
- VrpcAdapter._callback(promiseJsonString, promiseJson)
662
- })
663
- .catch(err => {
664
- const data = { e: err.message }
665
- const promiseJson = Object.assign({}, json, { data, id })
666
- const promiseJsonString = VrpcAdapter._stringifySafely(promiseJson)
667
- VrpcAdapter._callback(promiseJsonString, promiseJson)
668
- })
669
- } catch (err) {
670
- json.data.e = err.message
681
+ static _generateWrapper (id, json, instanceId) {
682
+ const wrapper = (...innerArgs) => {
683
+ // do not even trigger a callback for already offline clients
684
+ if (json.s && !VrpcAdapter._listeners[json.s]) return
685
+ const a = instanceId ? [instanceId, ...innerArgs] : [...innerArgs]
686
+ VrpcAdapter._callback({ a, s: json.s, f: json.f, i: id })
671
687
  }
688
+ return wrapper
672
689
  }
673
690
 
674
- static _stringifySafely (json) {
675
- let jsonString
691
+ static _handlePromise (json, promise) {
676
692
  try {
677
- jsonString = JSON.stringify(json)
693
+ const cid = VrpcAdapter._correlationId++ % Number.MAX_SAFE_INTEGER
694
+ const id = `__p__${json.f}-${cid}`
695
+ json.r = id
696
+ promise
697
+ .then(r => VrpcAdapter._callback({ ...json, r, i: id }))
698
+ .catch(err => VrpcAdapter._callback({ ...json, e: err.message, i: id }))
678
699
  } catch (err) {
679
- this._log.debug(
680
- `Failed serialization of return value for: ${json.context}::${json.method}, because: ${err.message}`
681
- )
682
- json.data.r = '__vrpc::not-serializable__'
683
- jsonString = JSON.stringify(json)
700
+ json.data.e = err.message
684
701
  }
685
- return jsonString
686
- }
687
-
688
- static _extractDataToArray (data) {
689
- return Object.keys(data)
690
- .sort()
691
- .filter(value => value[0] === '_')
692
- .map(key => data[key])
693
702
  }
694
703
 
695
704
  static _generateId (object) {
@@ -775,8 +784,8 @@ class VrpcAdapter {
775
784
 
776
785
  static _getInstancesArray (className) {
777
786
  const instances = []
778
- VrpcAdapter._instances.forEach((value, key) => {
779
- if (value.className === className) instances.push(key)
787
+ VrpcAdapter._instances.forEach((v, k) => {
788
+ if (v.className === className && !v.isIsolated) instances.push(k)
780
789
  })
781
790
  return instances
782
791
  }