vrpc 2.6.0 → 3.0.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,13 +10,13 @@ __\/\\\_______\/\\\__/\\\///////\\\___\/\\\/////////\\\____/\\\////////__
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>.
19
- Copyright (c) 2018 - 2021 Dr. Burkhard C. Heisen <burkhard.heisen@heisenware.com>.
19
+ Copyright (c) 2018 - 2022 Dr. Burkhard C. Heisen <burkhard.heisen@heisenware.com>.
20
20
 
21
21
  Permission is hereby granted, free of charge, to any person obtaining a copy
22
22
  of this software and associated documentation files (the "Software"), to deal
@@ -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 { nanoid } = require('nanoid')
48
48
 
49
49
  /**
50
50
  * Generates an adapter layer for existing code and enables further VRPC-based
@@ -101,17 +101,20 @@ class VrpcAdapter {
101
101
  * NOTE: This function currently only supports registration of classes (either
102
102
  * when provided as object or when exported on the provided module path)
103
103
  */
104
- static register (code, options = {}) {
104
+ static register (code, options = {}) {
105
105
  if (typeof code === 'string') {
106
106
  const absPath = path.resolve(caller(), '../', code)
107
107
  const relPath = path.relative(__dirname, absPath)
108
- const absJsdocPath = `${absPath}.js`
108
+ const absJsdocPath =
109
+ absPath && absPath.endsWith('.js') ? absPath : `${absPath}.js`
109
110
  const Klass = require(relPath)
110
111
  this._registerClass(Klass, absJsdocPath, options)
111
112
  } else {
112
113
  const { jsdocPath } = options
114
+ const sJsdocPath =
115
+ jsdocPath && jsdocPath.endsWith('.js') ? jsdocPath : `${jsdocPath}.js`
113
116
  if (jsdocPath) {
114
- const absJsdocPath = path.resolve(caller(), '../', `${jsdocPath}.js`)
117
+ const absJsdocPath = path.resolve(caller(), '../', sJsdocPath)
115
118
  this._registerClass(code, absJsdocPath, { ...options, jsdoc: true })
116
119
  } else {
117
120
  this._registerClass(code, null, options)
@@ -147,92 +150,73 @@ class VrpcAdapter {
147
150
  const content = fs.readFileSync(absJsdocPath).toString('utf8')
148
151
  meta = this._parseComments(content)
149
152
  }
150
- VrpcAdapter._functionRegistry.set(className, {
151
- memberFunctions,
152
- meta,
153
- Klass: {},
154
- withNew: false,
155
- staticFunctions: ['__getNamed__'],
156
- schema: null
153
+ if (!VrpcAdapter._functionRegistry.has(className)) {
154
+ VrpcAdapter._functionRegistry.set(className, {
155
+ memberFunctions,
156
+ meta,
157
+ Klass: {},
158
+ withNew: false,
159
+ staticFunctions: [],
160
+ schema: null
161
+ })
162
+ }
163
+ VrpcAdapter._instances.set(instance, {
164
+ className,
165
+ instance: obj,
166
+ isIsolated: false
157
167
  })
158
- VrpcAdapter._instances.set(instance, { className, instance: obj })
159
- }
160
-
161
- static getClasses () {
162
- return JSON.stringify(VrpcAdapter._getClassesArray())
163
- }
164
-
165
- static getMemberFunctions (className) {
166
- return JSON.stringify(VrpcAdapter._getMemberFunctionsArray(className))
167
- }
168
-
169
- static getStaticFunctions (className) {
170
- return JSON.stringify(VrpcAdapter._getStaticFunctionsArray(className))
171
- }
172
-
173
- static getMetaData (className) {
174
- return JSON.stringify(VrpcAdapter._getMetaData(className))
175
- }
176
-
177
- static getInstances (className) {
178
- return JSON.stringify(VrpcAdapter._getInstancesArray(className))
179
- }
180
-
181
- static call (jsonString) {
182
- const json = JSON.parse(jsonString)
183
- VrpcAdapter._call(json)
184
- return JSON.stringify(json)
185
168
  }
186
169
 
187
- static onCallback (callback) {
188
- VrpcAdapter._callback = callback
189
- }
190
-
191
- // convenience interface if used in parallel - as local factory and from remote
192
-
193
- /**
194
- * Creates an un-managed, anonymous instance
195
- *
196
- * @param {String} className Name of the class to create an instance of
197
- * @param {...any} args Arguments to provide to the constructor
198
- * @returns {Object} The real instance (not a proxy!)
199
- */
200
- static create (className, ...args) {
201
- if (typeof className === 'string') {
202
- return VrpcAdapter._create(className, ...args)
203
- }
204
- if (typeof className === 'object') {
205
- if (className.instance) {
206
- return VrpcAdapter._createNamed(
207
- className.className,
208
- className.instance,
209
- ...className.args
210
- )
211
- }
212
- return VrpcAdapter._create(className.className, ...className.args)
213
- }
214
- }
170
+ /*****************************************************
171
+ * convenience interface if used as plug-able factory *
172
+ ******************************************************/
215
173
 
216
174
  /**
217
- * Creates a managed named instance
175
+ * Creates a new instance
218
176
  *
219
- * @param {String} className Name of the class to create an instance of
220
- * @param {String} instance Name of the instance
221
- * @param {...any} args Arguments to provide to the constructor
177
+ * @param {Object} options
178
+ * @param {String} options.className Name of the class which should be
179
+ * instantiated
180
+ * @param {String} [options.instance] Name of the created instance. If not
181
+ * provided an id will be generated
182
+ * @param {Array} [options.args] Positional arguments for the constructor call
183
+ * @param {bool} [options.isIsolated=false] If true the created instance will
184
+ * be visible only to the client who created it
222
185
  * @returns {Object} The real instance (not a proxy!)
223
186
  */
224
- static createNamed (className, instance, ...args) {
225
- return VrpcAdapter._createNamed(className, instance, ...args)
187
+ static create ({
188
+ className,
189
+ instance = nanoid(8),
190
+ args = [],
191
+ isIsolated = false
192
+ } = {}) {
193
+ const json = { c: className, a: [instance, ...args] }
194
+ const obj = isIsolated
195
+ ? VrpcAdapter._handleCreateIsolated(json)
196
+ : VrpcAdapter._handleCreateShared(json)
197
+ if (json.e) throw new Error(json.e)
198
+ return obj
226
199
  }
227
200
 
228
201
  /**
229
- * Deletes a managed instance
202
+ * Deletes an instance
230
203
  *
231
- * @param {String} instance Name of the instance to be deleted
204
+ * @param {String|Object} instance Instance (name or object itself) to be deleted
232
205
  * @returns {Boolean} True in case of success, false otherwise
233
206
  */
234
207
  static delete (instance) {
235
- return VrpcAdapter._delete(instance)
208
+ if (typeof instance === 'string') {
209
+ return VrpcAdapter._delete(instance)
210
+ }
211
+ if (typeof instance === 'object') {
212
+ for (const [k, v] of VrpcAdapter._instances) {
213
+ if (v.instance === instance) {
214
+ VrpcAdapter._instances.delete(k)
215
+ return true
216
+ }
217
+ }
218
+ return false
219
+ }
236
220
  }
237
221
 
238
222
  /**
@@ -266,34 +250,14 @@ class VrpcAdapter {
266
250
  return VrpcAdapter._getInstancesArray(className)
267
251
  }
268
252
 
269
- /**
270
- * Provides all available member functions of the specified class.
271
- *
272
- * @param {String} className Name of class to provide member functions for
273
- * @return {Array.<String>} Array of member function names
274
- */
275
- static getAvailableMemberFunctions (className) {
276
- return VrpcAdapter._getMemberFunctionsArray(className)
277
- }
278
-
279
- /**
280
- * Provides all available static functions of a registered class.
281
- *
282
- * @param {String} className Name of class to provide static functions for
283
- * @returns {Array.<String>} Array of static function names
284
- */
285
- static getAvailableStaticFunctions (className) {
286
- return VrpcAdapter._getStaticFunctionsArray(className)
253
+ static call (jsonString) {
254
+ const json = JSON.parse(jsonString)
255
+ VrpcAdapter._call(json)
256
+ return JSON.stringify(json)
287
257
  }
288
258
 
289
- /**
290
- * Provides all available meta data of the registered class.
291
- *
292
- * @param {String} className Name of class to provide meta data for
293
- * @returns {MetaData} Meta Data
294
- */
295
- static getAvailableMetaData (className) {
296
- return VrpcAdapter._getMetaData(className)
259
+ static onCallback (callback) {
260
+ VrpcAdapter._callback = callback
297
261
  }
298
262
 
299
263
  /**
@@ -351,11 +315,10 @@ class VrpcAdapter {
351
315
  staticFunctions = staticFunctions.filter(f => !f.startsWith('_'))
352
316
  }
353
317
  // Inject constructor and destructor
354
- staticFunctions.push('__create__')
355
- staticFunctions.push('__delete__')
356
- staticFunctions.push('__createNamed__')
357
- staticFunctions.push('__getNamed__')
318
+ staticFunctions.push('__createIsolated__')
319
+ staticFunctions.push('__createShared__')
358
320
  staticFunctions.push('__callAll__')
321
+ staticFunctions.push('__delete__')
359
322
  let memberFunctions = VrpcAdapter._extractMemberFunctions(Klass)
360
323
  if (onlyPublic) {
361
324
  memberFunctions = memberFunctions.filter(f => {
@@ -416,7 +379,7 @@ class VrpcAdapter {
416
379
  },
417
380
  ...params
418
381
  ]
419
- meta.__createNamed__ = { description, params: modified, ret }
382
+ meta.__createShared__ = { description, params: modified, ret }
420
383
  } else {
421
384
  meta[functionName] = { description, params, ret }
422
385
  }
@@ -427,99 +390,98 @@ class VrpcAdapter {
427
390
  }
428
391
 
429
392
  static _call (json) {
430
- const { method } = json
431
- switch (method) {
432
- case '__create__':
433
- VrpcAdapter._handleCreate(json)
393
+ const before = Object.keys(VrpcAdapter._listeners).length
394
+ switch (json.f) {
395
+ case '__createIsolated__':
396
+ VrpcAdapter._handleCreateIsolated(json)
434
397
  break
435
- case '__delete__':
436
- VrpcAdapter._handleDelete(json)
437
- break
438
- case '__createNamed__':
439
- VrpcAdapter._handleCreateNamed(json)
440
- break
441
- case '__getNamed__':
442
- VrpcAdapter._handleGetNamed(json)
398
+ case '__createShared__':
399
+ VrpcAdapter._handleCreateShared(json)
443
400
  break
444
401
  case '__callAll__':
445
402
  VrpcAdapter._handleCallAll(json)
446
403
  break
404
+ case '__delete__':
405
+ VrpcAdapter._handleDelete(json)
406
+ break
447
407
  default:
408
+ // this call may have the side-effect of taking a listener on board
448
409
  VrpcAdapter._handleCall(json)
449
410
  }
411
+ const after = Object.keys(VrpcAdapter._listeners).length
412
+ // if we took a listener on board we must tell our caller such that
413
+ // things can be cleaned up later.
414
+ return after > before
450
415
  }
451
416
 
452
- static _handleCreate (json) {
453
- const { context, data } = json
417
+ static _handleCreateIsolated (json) {
418
+ let instance
454
419
  try {
455
- const wrappedArgs = VrpcAdapter._wrapArguments(json)
456
- const instance = VrpcAdapter._create(context, ...wrappedArgs)
457
- const instanceId = nanoid(9)
458
- VrpcAdapter._instances.set(instanceId, { context, instance })
459
- data.r = instanceId
460
- } catch (err) {
461
- data.e = err.message
462
- }
463
- }
464
-
465
- static _handleDelete (json) {
466
- const { context, data } = json
467
- try {
468
- const wrappedArgs = VrpcAdapter._wrapArguments(json)
469
- const instanceId = wrappedArgs[0]
470
- data.r = VrpcAdapter._delete(instanceId)
471
- VrpcAdapter._emitter.emit('delete', {
472
- className: context,
473
- instance: wrappedArgs[0]
420
+ const className = json.c
421
+ const [instanceId, ...args] = json.a
422
+ instance = VrpcAdapter._create(className, instanceId, ...args)
423
+ VrpcAdapter._instances.set(instanceId, {
424
+ instance,
425
+ className,
426
+ isIsolated: true
474
427
  })
475
- } catch (err) {
476
- data.e = err.message
477
- }
478
- }
479
-
480
- static _handleCreateNamed (json) {
481
- const { context, data } = json
482
- try {
483
- const wrappedArgs = VrpcAdapter._wrapArguments(json)
484
- VrpcAdapter._createNamed(context, ...wrappedArgs)
485
- data.r = wrappedArgs[0] // First argument is instanceId
486
428
  VrpcAdapter._emitter.emit('create', {
487
- className: context,
488
- instance: wrappedArgs[0],
489
- args: wrappedArgs.slice(1)
429
+ args,
430
+ className,
431
+ isIsolated: true,
432
+ instance: instanceId
490
433
  })
434
+ json.r = instanceId
491
435
  } catch (err) {
492
- data.e = err.message
436
+ json.e = err.message
493
437
  }
438
+ return instance
494
439
  }
495
440
 
496
- static _handleGetNamed (json) {
497
- const { data } = json
441
+ static _handleCreateShared (json) {
442
+ let instance
498
443
  try {
499
- const wrappedArgs = VrpcAdapter._wrapArguments(json)
500
- const instanceId = wrappedArgs[0]
501
- const entry = VrpcAdapter._instances.get(instanceId)
502
- if (entry) data.r = instanceId
503
- else data.e = `Instance with id: ${instanceId} does not exist`
444
+ const className = json.c
445
+ const [instanceId, ...args] = json.a
446
+ instance = VrpcAdapter._create(className, instanceId, ...args)
447
+ VrpcAdapter._instances.set(instanceId, {
448
+ instance,
449
+ className,
450
+ isIsolated: false
451
+ })
452
+ json.r = instanceId
453
+ VrpcAdapter._emitter.emit('create', {
454
+ args,
455
+ className,
456
+ isIsolated: false,
457
+ instance: instanceId
458
+ })
504
459
  } catch (err) {
505
- data.e = err.message
460
+ json.e = err.message
506
461
  }
462
+ return instance
507
463
  }
508
464
 
509
465
  static _handleCallAll (json) {
510
- const { context, method, data } = json
511
466
  try {
512
467
  const calls = []
513
- for (const [id, { className, instance }] of VrpcAdapter._instances) {
514
- if (className !== context) continue
515
- let v
468
+ for (const [
469
+ id,
470
+ { className, instance, isIsolated }
471
+ ] of VrpcAdapter._instances) {
472
+ // TODO Think about a configurable behavior w.r.t. isolated instances...
473
+ if (className !== json.c || isIsolated) continue
474
+ let v = true
516
475
  let e = null
517
476
  try {
518
- const wrappedArgs = VrpcAdapter._wrapArguments(json, id)
519
- const funcName = wrappedArgs[0]
520
- v = instance[funcName].apply(instance, wrappedArgs.slice(1))
477
+ const unwrapped = VrpcAdapter._unwrapArguments(json, id)
478
+ if (unwrapped) {
479
+ const funcName = unwrapped[0]
480
+ v = instance[funcName].apply(instance, unwrapped.slice(1))
481
+ v = VrpcAdapter.sanitizeEventListenerReturnValues(v)
482
+ }
521
483
  } catch (err) {
522
- e = err
484
+ e = err.message
523
485
  }
524
486
  if (VrpcAdapter._isPromise(v)) {
525
487
  calls.push(
@@ -531,17 +493,48 @@ class VrpcAdapter {
531
493
  calls.push({ id, val: v, err: e })
532
494
  }
533
495
  }
534
- this._handlePromise(json, method, Promise.all(calls))
496
+ this._handlePromise(json, Promise.all(calls))
497
+ } catch (err) {
498
+ json.e = err.message
499
+ }
500
+ }
501
+
502
+ static sanitizeEventListenerReturnValues (ret) {
503
+ return typeof ret === 'object' &&
504
+ ret._events !== undefined &&
505
+ ret._eventsCount !== undefined
506
+ ? true
507
+ : ret
508
+ }
509
+
510
+ static _handleDelete (json) {
511
+ try {
512
+ const instance = json.a[0] // first arg is instance to be deleted
513
+ json.r = VrpcAdapter._delete(instance)
514
+ VrpcAdapter._emitter.emit('delete', {
515
+ instance,
516
+ className: json.c
517
+ })
535
518
  } catch (err) {
536
- data.e = err.message
519
+ json.e = err.message
537
520
  }
538
521
  }
539
522
 
540
523
  static _handleCall (json) {
541
- const { context, method, data } = json
542
- const wrappedArgs = VrpcAdapter._wrapArguments(json)
524
+ // special case: removeAllListeners
525
+ if (json.f === 'removeAllListeners') {
526
+ VrpcAdapter._removeAllListeners(json.a[0], json.s, json.c)
527
+ json.r = true
528
+ return
529
+ }
530
+ const unwrapped = VrpcAdapter._unwrapArguments(json)
531
+ if (!unwrapped) {
532
+ // this call must be skipped (event emitter stuff)
533
+ json.r = true
534
+ return
535
+ }
543
536
  // Check whether context is a registered class
544
- const entry = VrpcAdapter._functionRegistry.get(context)
537
+ const entry = VrpcAdapter._functionRegistry.get(json.c)
545
538
  if (entry !== undefined) {
546
539
  // entry is class -> function is static
547
540
  const { Klass } = entry
@@ -549,39 +542,42 @@ class VrpcAdapter {
549
542
  // rather sticking to those functions registered before...
550
543
  // TODO This is even more important now as the agent does a wildcard
551
544
  // subscription against all functions
552
- if (VrpcAdapter._isFunction(Klass[method])) {
545
+ if (VrpcAdapter._isFunction(Klass[json.f])) {
553
546
  try {
554
- const ret = Klass[method].apply(null, wrappedArgs)
547
+ const ret = Klass[json.f].apply(null, unwrapped)
555
548
  // check if function returns promise
556
549
  if (VrpcAdapter._isPromise(ret)) {
557
- this._handlePromise(json, method, ret)
558
- } else data.r = ret
550
+ this._handlePromise(json, ret)
551
+ } else json.r = ret
559
552
  } catch (err) {
560
- data.e = err.message
553
+ json.e = err.message
561
554
  }
562
- } else throw new Error(`Could not find function: ${method}`)
555
+ } else throw new Error(`Could not find function: ${json.f}`)
563
556
  } else {
564
557
  // is not static
565
- const entry = VrpcAdapter._instances.get(context)
558
+ const entry = VrpcAdapter._instances.get(json.c)
566
559
  if (entry === undefined) {
567
- throw new Error(`Could not find context: ${context}`)
560
+ throw new Error(`Could not find context: ${json.c}`)
568
561
  }
569
562
  const { instance } = entry
570
- if (VrpcAdapter._isFunction(instance[method])) {
563
+ if (VrpcAdapter._isFunction(instance[json.f])) {
571
564
  try {
572
- const ret = instance[method].apply(instance, wrappedArgs)
565
+ let ret = instance[json.f].apply(instance, unwrapped)
566
+ ret = VrpcAdapter.sanitizeEventListenerReturnValues(ret)
573
567
  // check if function returns promise
574
568
  if (VrpcAdapter._isPromise(ret)) {
575
- this._handlePromise(json, method, ret)
576
- } else data.r = ret
569
+ this._handlePromise(json, ret)
570
+ } else json.r = ret
577
571
  } catch (err) {
578
- data.e = err.message
572
+ json.e = err.message
579
573
  }
580
- } else throw new Error(`Could not find function: ${method}`)
574
+ } else throw new Error(`Could not find function: ${json.f}`)
581
575
  }
582
576
  }
583
577
 
584
- static _create (className, ...args) {
578
+ static _create (className, instanceId, ...args) {
579
+ const entry = VrpcAdapter._instances.get(instanceId)
580
+ if (entry) return entry.instance
585
581
  const { Klass, withNew, schema } = VrpcAdapter._getClassEntry(className)
586
582
  if (schema !== null) {
587
583
  VrpcAdapter._validate(schema, ...args)
@@ -590,33 +586,39 @@ class VrpcAdapter {
590
586
  return Klass(...args)
591
587
  }
592
588
 
593
- static _createNamed (className, instanceId, ...args) {
594
- const entry = VrpcAdapter._instances.get(instanceId)
595
- if (!entry) {
596
- const instance = VrpcAdapter._create(className, ...args)
597
- VrpcAdapter._instances.set(instanceId, { className, instance })
598
- return instance
599
- }
600
- return entry.instance
601
- }
602
-
603
589
  static _delete (instanceId) {
604
590
  const entry = VrpcAdapter._instances.get(instanceId)
605
591
  if (entry) {
592
+ // in case the instance was an EventEmitter, free all registered listeners
593
+ if (VrpcAdapter._isFunction(entry.removeAllListeners)) {
594
+ entry.removeAllListeners()
595
+ }
596
+ for (const [key, { clients, instance }] of Object.entries(
597
+ VrpcAdapter._listeners
598
+ )) {
599
+ if (instance === instanceId) {
600
+ VrpcAdapter._listeners[key] = { clients }
601
+ }
602
+ }
606
603
  VrpcAdapter._instances.delete(instanceId)
607
604
  return true
608
605
  }
609
606
  return false
610
607
  }
611
608
 
612
- static _unregisterEventListeners (clientId) {
613
- const listeners = VrpcAdapter._listeners[clientId]
614
- if (listeners) {
615
- listeners.forEach(({ i, e, f }) => {
616
- VrpcAdapter.getInstance(i).removeListener(e, f)
617
- })
609
+ static _unregisterClient (clientId) {
610
+ for (const [key, value] of Object.entries(VrpcAdapter._listeners)) {
611
+ const { instance, event, listener, clients } = value
612
+ const filteredClients = clients.filter(x => x !== clientId)
613
+ if (filteredClients.length === 0) {
614
+ if (event) {
615
+ VrpcAdapter.getInstance(instance).removeListener(event, listener)
616
+ }
617
+ delete VrpcAdapter._listeners[key]
618
+ } else {
619
+ VrpcAdapter._listeners[key].clients = filteredClients
620
+ }
618
621
  }
619
- delete VrpcAdapter._listeners[clientId]
620
622
  }
621
623
 
622
624
  static _validate (schema, params) {
@@ -627,119 +629,99 @@ class VrpcAdapter {
627
629
  if (!valid) throw new Error(VrpcAdapter._ajv.errorsText())
628
630
  }
629
631
 
630
- static _wrapArguments (json, instanceId) {
631
- const { method, sender, data, context } = json
632
- const args = VrpcAdapter._extractDataToArray(data)
633
- const wrappedArgs = []
634
- args.forEach(arg => {
635
- // Find those args that actually need to be function callbacks
636
- if (typeof arg === 'string' && arg.substr(0, 5) === '__f__') {
637
- const f = this._generateWrapper(arg, json, instanceId)
638
- wrappedArgs.push(f)
639
- if (!sender) return
640
- // Register this client to later know when he is dead and skip callbacks
641
- if (!VrpcAdapter._listeners[sender]) VrpcAdapter._listeners[sender] = []
642
- // Check whether injected callback is an EventEmitter registration
643
- if (method === 'on' && typeof args[0] === 'string') {
644
- const e = args[0]
645
- const i = context
646
- const r = arg
647
- VrpcAdapter._listeners[sender].push({ i, e, f, r })
632
+ static _unwrapArguments (json, instanceId) {
633
+ const { f: func, s: sender, a: args, c: context } = json
634
+ const unwrapped = []
635
+ for (const arg of args) {
636
+ // find those args that actually need to be functions
637
+ if (typeof arg === 'string' && arg.slice(0, 5) === '__f__') {
638
+ const callback = this._generateCallback(arg, json, instanceId)
639
+ unwrapped.push(callback)
640
+ } else if (typeof arg === 'string' && arg.slice(0, 5) === '__e__') {
641
+ const listener = this._generateListener(arg, json, instanceId)
642
+ unwrapped.push(listener)
643
+ // start tracking clients that are registering event listeners
644
+ if (!VrpcAdapter._listeners[arg]) {
645
+ VrpcAdapter._listeners[arg] = {
646
+ listener,
647
+ clients: [sender],
648
+ instance: context,
649
+ event: null
650
+ }
651
+ } else {
652
+ VrpcAdapter._listeners[arg].clients.push(sender)
648
653
  }
649
- if (
650
- (method === 'off' || method === 'removeListener') &&
651
- typeof args[0] === 'string'
652
- ) {
653
- const r = arg
654
- const s = VrpcAdapter._listeners[sender]
655
- const entry = s && s.find(x => x.r === r)
656
- if (entry) {
657
- const { i, e, f } = entry
658
- VrpcAdapter.getInstance(i).removeListener(e, f)
654
+ // see whether the requested listener is an EventEmitter registration
655
+ if (func === 'on' || func === 'addListener') {
656
+ // if this event is already registered avoid a second registration
657
+ if (VrpcAdapter._listeners[arg].event) {
658
+ return null // means that this call won't be forwarded
659
659
  }
660
+ VrpcAdapter._listeners[arg].event = args[0]
661
+ }
662
+ if (func === 'off' || func === 'removeListener') {
663
+ const { clients, listener, event } = VrpcAdapter._listeners[arg]
664
+ const filteredClients = clients.filter(x => x !== sender)
665
+ // don't unregister this event, as other clients are still listening
666
+ if (filteredClients.length > 0) {
667
+ VrpcAdapter._listeners[arg].clients = filteredClients
668
+ } else {
669
+ delete VrpcAdapter._listeners[arg]
670
+ VrpcAdapter.getInstance(context).off(event, listener)
671
+ }
672
+ return null // means that this call won't be forwarded
660
673
  }
661
674
  } else {
662
- // Leave the others untouched
663
- wrappedArgs.push(arg)
675
+ // default behavior is to not touch
676
+ unwrapped.push(arg)
664
677
  }
665
- })
666
- return wrappedArgs
667
- }
668
-
669
- static _generateWrapper (id, json, instanceId) {
670
- const { sender } = json
671
- if (instanceId) {
672
- const wrapper = (...innerArgs) => {
673
- // do not even trigger a callback for already offline clients
674
- if (sender && !VrpcAdapter._listeners[sender]) return
675
- const data = { _1: instanceId }
676
- innerArgs.forEach((x, i) => {
677
- data[`_${i + 2}`] = x
678
- })
679
- // TODO Heal that crazy API !!
680
- const callbackJson = { ...json, data, id }
681
- const callbackJsonString = VrpcAdapter._stringifySafely(callbackJson)
682
- VrpcAdapter._callback(callbackJsonString, callbackJson)
678
+ }
679
+ return unwrapped
680
+ }
681
+
682
+ static _removeAllListeners (event, sender, context) {
683
+ for (const [key, value] of Object.entries(VrpcAdapter._listeners)) {
684
+ if (value.event === event) {
685
+ const filteredClients = value.clients.filter(x => x !== sender)
686
+ if (filteredClients.length > 0) {
687
+ VrpcAdapter._listeners[key].clients = filteredClients
688
+ } else {
689
+ delete VrpcAdapter._listeners[key]
690
+ VrpcAdapter.getInstance(context).off(event, value.listener)
691
+ }
683
692
  }
684
- return wrapper
685
693
  }
686
- const wrapper = (...innerArgs) => {
687
- // do not even trigger a callback for already offline clients
688
- if (sender && !VrpcAdapter._listeners[sender]) return
689
- const data = {}
690
- innerArgs.forEach((x, i) => {
691
- data[`_${i + 1}`] = x
692
- })
693
- // TODO Heal that crazy API !!
694
- const callbackJson = { ...json, data, id }
695
- const callbackJsonString = VrpcAdapter._stringifySafely(callbackJson)
696
- VrpcAdapter._callback(callbackJsonString, callbackJson)
694
+ }
695
+
696
+ static _generateCallback (id, json, instanceId) {
697
+ return (...innerArgs) => {
698
+ // callAll expects the instanceId as first argument
699
+ const a = instanceId ? [instanceId, ...innerArgs] : [...innerArgs]
700
+ VrpcAdapter._callback({ a, s: json.s, i: id })
697
701
  }
698
- return wrapper
699
702
  }
700
703
 
701
- static _handlePromise (json, method, promise) {
702
- try {
703
- const cid = VrpcAdapter._correlationId++ % Number.MAX_SAFE_INTEGER
704
- const id = `__p__${method}-${cid}`
705
- json.data.r = id
706
- promise
707
- .then(value => {
708
- const data = { r: value }
709
- const promiseJson = Object.assign({}, json, { data, id })
710
- const promiseJsonString = VrpcAdapter._stringifySafely(promiseJson)
711
- VrpcAdapter._callback(promiseJsonString, promiseJson)
712
- })
713
- .catch(err => {
714
- const data = { e: err.message }
715
- const promiseJson = Object.assign({}, json, { data, id })
716
- const promiseJsonString = VrpcAdapter._stringifySafely(promiseJson)
717
- VrpcAdapter._callback(promiseJsonString, promiseJson)
718
- })
719
- } catch (err) {
720
- json.data.e = err.message
704
+ static _generateListener (id, json, instanceId) {
705
+ return (...innerArgs) => {
706
+ // do not even emit for already offline clients
707
+ if (!VrpcAdapter._listeners[id]) return
708
+ // callAll expects the instanceId as first argument
709
+ const a = instanceId ? [instanceId, ...innerArgs] : [...innerArgs]
710
+ VrpcAdapter._callback({ a, s: json.s, i: id })
721
711
  }
722
712
  }
723
713
 
724
- static _stringifySafely (json) {
725
- let jsonString
714
+ static _handlePromise (json, promise) {
726
715
  try {
727
- jsonString = JSON.stringify(json)
716
+ const cid = VrpcAdapter._correlationId++ % Number.MAX_SAFE_INTEGER
717
+ const id = `__p__${json.f}-${cid}`
718
+ json.r = id
719
+ promise
720
+ .then(r => VrpcAdapter._callback({ ...json, r, i: id }))
721
+ .catch(err => VrpcAdapter._callback({ ...json, e: err.message, i: id }))
728
722
  } catch (err) {
729
- this._log.debug(
730
- `Failed serialization of return value for: ${json.context}::${json.method}, because: ${err.message}`
731
- )
732
- json.data.r = '__vrpc::not-serializable__'
733
- jsonString = JSON.stringify(json)
723
+ json.data.e = err.message
734
724
  }
735
- return jsonString
736
- }
737
-
738
- static _extractDataToArray (data) {
739
- return Object.keys(data)
740
- .sort()
741
- .filter(value => value[0] === '_')
742
- .map(key => data[key])
743
725
  }
744
726
 
745
727
  static _generateId (object) {
@@ -825,8 +807,8 @@ class VrpcAdapter {
825
807
 
826
808
  static _getInstancesArray (className) {
827
809
  const instances = []
828
- VrpcAdapter._instances.forEach((value, key) => {
829
- if (value.className === className) instances.push(key)
810
+ VrpcAdapter._instances.forEach((v, k) => {
811
+ if (v.className === className && !v.isIsolated) instances.push(k)
830
812
  })
831
813
  return instances
832
814
  }
@@ -853,7 +835,7 @@ class VrpcAdapter {
853
835
  /**
854
836
  * Event 'create'
855
837
  *
856
- * Emitted on creation of named instance
838
+ * Emitted on creation of shared instance
857
839
  *
858
840
  * @event VrpcAdapter#create
859
841
  * @type {Object}
@@ -865,7 +847,7 @@ class VrpcAdapter {
865
847
  /**
866
848
  * Event 'delete'
867
849
  *
868
- * Emitted on deletion of named instance
850
+ * Emitted on deletion of shared instance
869
851
  *
870
852
  * @event VrpcAdapter#delete
871
853
  * @type {Object}