vrpc 3.13.0 → 3.15.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.
- package/LICENSE +1 -1
- package/README.md +100 -77
- package/browser/vrpc.js +5 -5
- package/index.js +7 -7
- package/package.json +25 -15
- package/vrpc/ReconnectBackoff.js +79 -0
- package/vrpc/Registrations.js +850 -0
- package/vrpc/SubscribeRetrier.js +33 -0
- package/vrpc/VrpcAdapter.js +552 -294
- package/vrpc/VrpcAgent.js +242 -154
- package/vrpc/VrpcBrowserAdapter.js +417 -285
- package/vrpc/VrpcBrowserAgent.js +239 -128
- package/vrpc/VrpcClient.js +1221 -233
- package/vrpc/VrpcNative.js +10 -5
- package/vrpc/VrpcPersistor.js +1 -1
- package/vrpc/adapter.hpp +1 -1
- package/vrpc/addon.cpp +1 -1
- package/vrpc/comment-parser/parser.js +23 -0
- package/vrpc/namePattern.js +86 -0
- package/vrpc/vrpc-agent-js +3 -11
package/vrpc/VrpcAdapter.js
CHANGED
|
@@ -14,7 +14,7 @@ procedure calls (RPC).
|
|
|
14
14
|
Author: Dr. Burkhard C. Heisen (https://github.com/heisenware/vrpc)
|
|
15
15
|
|
|
16
16
|
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
|
17
|
-
Copyright (c) 2018
|
|
17
|
+
Copyright (c) 2018 Dr. Burkhard C. Heisen <burkhard.heisen@heisenware.com>.
|
|
18
18
|
|
|
19
19
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
20
20
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -42,6 +42,8 @@ const caller = require('caller')
|
|
|
42
42
|
const commentParser = require('./comment-parser')
|
|
43
43
|
const EventEmitter = require('events')
|
|
44
44
|
const { nanoid } = require('nanoid')
|
|
45
|
+
const Registrations = require('./Registrations')
|
|
46
|
+
const NamePattern = require('./namePattern')
|
|
45
47
|
|
|
46
48
|
// Node before 18.18 / 20.4 has no dispose symbols, and a class's
|
|
47
49
|
// [Symbol.asyncDispose]() would become a method named 'undefined'. Node's
|
|
@@ -97,14 +99,16 @@ class VrpcAdapter {
|
|
|
97
99
|
* @param {Any} code Existing code to be registered, can be a class
|
|
98
100
|
* or function object or a relative path to a module
|
|
99
101
|
* @param {Object} [options]
|
|
100
|
-
* @param {Boolean} [options.onlyPublic=true] If true, only registers
|
|
101
|
-
* functions
|
|
102
|
+
* @param {Boolean} [options.onlyPublic=true] If true, only registers public
|
|
103
|
+
* functions: none whose name begins with an underscore, and - when the
|
|
104
|
+
* JSDoc is read - none tagged `@private` or `@access private`
|
|
102
105
|
* @param {Boolean} [options.withNew=true] If true, class will be constructed
|
|
103
106
|
* using the `new` operator
|
|
104
107
|
* @param {Object} [options.schema=null] If provided is used to validate ctor
|
|
105
108
|
* parameters (only works if registered code reflects a single class)
|
|
106
|
-
* @param {String} options.jsdocPath if provided, parses documentation and
|
|
107
|
-
* provides it as meta information
|
|
109
|
+
* @param {String} [options.jsdocPath] if provided, parses documentation and
|
|
110
|
+
* provides it as meta information; a code given by path has its JSDoc read
|
|
111
|
+
* without
|
|
108
112
|
*
|
|
109
113
|
* NOTE: This function currently only supports registration of classes (either
|
|
110
114
|
* when provided as object or when exported on the provided module path)
|
|
@@ -135,8 +139,9 @@ class VrpcAdapter {
|
|
|
135
139
|
* @param {Object} options
|
|
136
140
|
* @param {String} options.className Class name of the instance
|
|
137
141
|
* @param {String} options.instance Name of the instance
|
|
138
|
-
* @param {Boolean} [options.onlyPublic=true] If true, only registers
|
|
139
|
-
* functions
|
|
142
|
+
* @param {Boolean} [options.onlyPublic=true] If true, only registers public
|
|
143
|
+
* functions: none whose name begins with an underscore, and - when the
|
|
144
|
+
* JSDoc is read - none tagged `@private` or `@access private`
|
|
140
145
|
* @param {String} [options.jsdocPath] if provided, parses documentation and
|
|
141
146
|
* provides it as meta information
|
|
142
147
|
*/
|
|
@@ -144,18 +149,17 @@ class VrpcAdapter {
|
|
|
144
149
|
obj,
|
|
145
150
|
{ className, instance, jsdocPath, onlyPublic = true } = {}
|
|
146
151
|
) {
|
|
147
|
-
let
|
|
148
|
-
if (onlyPublic) {
|
|
149
|
-
memberFunctions = memberFunctions.filter(f => {
|
|
150
|
-
return !f.startsWith('_')
|
|
151
|
-
})
|
|
152
|
-
}
|
|
153
|
-
let meta = null
|
|
152
|
+
let docs = null
|
|
154
153
|
if (jsdocPath) {
|
|
155
154
|
const absJsdocPath = path.resolve(caller(), '../', jsdocPath)
|
|
156
155
|
const content = fs.readFileSync(absJsdocPath).toString('utf8')
|
|
157
|
-
|
|
156
|
+
docs = VrpcAdapter._readDocs(content, obj.constructor && obj.constructor.name)
|
|
157
|
+
}
|
|
158
|
+
let memberFunctions = VrpcAdapter._extractMemberFunctions(obj)
|
|
159
|
+
if (onlyPublic) {
|
|
160
|
+
memberFunctions = VrpcAdapter._onlyPublic(memberFunctions, docs, 'member')
|
|
158
161
|
}
|
|
162
|
+
const meta = docs ? VrpcAdapter._metaOf(docs.meta, memberFunctions) : null
|
|
159
163
|
if (!VrpcAdapter._functionRegistry.has(className)) {
|
|
160
164
|
VrpcAdapter._functionRegistry.set(className, {
|
|
161
165
|
memberFunctions,
|
|
@@ -304,6 +308,13 @@ class VrpcAdapter {
|
|
|
304
308
|
* @param {String} [type] Return value type
|
|
305
309
|
*/
|
|
306
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Listens to an event of the adapter: `create`, `beforeDelete`,
|
|
313
|
+
* `delete`, `disposeFailed`, `releaseFailed` or `greetFailed`
|
|
314
|
+
*
|
|
315
|
+
* @param {String} eventName The event
|
|
316
|
+
* @param {Function} listener Called with the event's payload
|
|
317
|
+
*/
|
|
307
318
|
static on (eventName, listener) {
|
|
308
319
|
VrpcAdapter._emitter.on(eventName, listener)
|
|
309
320
|
}
|
|
@@ -328,26 +339,30 @@ class VrpcAdapter {
|
|
|
328
339
|
{ onlyPublic = true, withNew = true, schema = null, jsdoc = true } = {}
|
|
329
340
|
) {
|
|
330
341
|
// Get all static static functions
|
|
342
|
+
const docs =
|
|
343
|
+
jsdoc && absJsdocPath
|
|
344
|
+
? VrpcAdapter._readDocs(
|
|
345
|
+
fs.readFileSync(absJsdocPath).toString('utf8'),
|
|
346
|
+
Klass.name
|
|
347
|
+
)
|
|
348
|
+
: null
|
|
331
349
|
let staticFunctions = VrpcAdapter._extractStaticFunctions(Klass)
|
|
332
350
|
if (onlyPublic) {
|
|
333
|
-
staticFunctions =
|
|
351
|
+
staticFunctions = VrpcAdapter._onlyPublic(staticFunctions, docs, 'static')
|
|
334
352
|
}
|
|
335
353
|
// Inject constructor and destructor
|
|
336
354
|
staticFunctions.push('__createIsolated__')
|
|
337
355
|
staticFunctions.push('__createShared__')
|
|
338
356
|
staticFunctions.push('__callAll__')
|
|
357
|
+
staticFunctions.push('__callMatching__')
|
|
339
358
|
staticFunctions.push('__delete__')
|
|
340
359
|
let memberFunctions = VrpcAdapter._extractMemberFunctions(Klass)
|
|
341
360
|
if (onlyPublic) {
|
|
342
|
-
memberFunctions =
|
|
343
|
-
return !f.startsWith('_')
|
|
344
|
-
})
|
|
345
|
-
}
|
|
346
|
-
let meta = null
|
|
347
|
-
if (jsdoc && absJsdocPath) {
|
|
348
|
-
const content = fs.readFileSync(absJsdocPath).toString('utf8')
|
|
349
|
-
meta = this._parseComments(content)
|
|
361
|
+
memberFunctions = VrpcAdapter._onlyPublic(memberFunctions, docs, 'member')
|
|
350
362
|
}
|
|
363
|
+
const meta = docs
|
|
364
|
+
? VrpcAdapter._metaOf(docs.meta, [...staticFunctions, ...memberFunctions])
|
|
365
|
+
: null
|
|
351
366
|
VrpcAdapter._functionRegistry.set(Klass.name, {
|
|
352
367
|
Klass,
|
|
353
368
|
withNew,
|
|
@@ -358,8 +373,72 @@ class VrpcAdapter {
|
|
|
358
373
|
})
|
|
359
374
|
}
|
|
360
375
|
|
|
361
|
-
|
|
362
|
-
|
|
376
|
+
/**
|
|
377
|
+
* The public of some functions: none beginning with an underscore, and
|
|
378
|
+
* none the class's JSDoc tags `@private` (when it was read)
|
|
379
|
+
*/
|
|
380
|
+
static _onlyPublic (functions, docs, kind) {
|
|
381
|
+
return functions.filter(
|
|
382
|
+
f => !f.startsWith('_') && !(docs && docs.privates[kind].has(f))
|
|
383
|
+
)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* The meta data of the published functions only: a hidden function's
|
|
388
|
+
* documentation stays inside the agent as well
|
|
389
|
+
*/
|
|
390
|
+
static _metaOf (meta, published) {
|
|
391
|
+
return Object.fromEntries(
|
|
392
|
+
Object.entries(meta).filter(
|
|
393
|
+
([name]) => name === '__createShared__' || published.includes(name)
|
|
394
|
+
)
|
|
395
|
+
)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* The meta data of the functions a source documents (see `_readDocs`)
|
|
400
|
+
*/
|
|
401
|
+
static _parseComments (content, className) {
|
|
402
|
+
return VrpcAdapter._readDocs(content, className).meta
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Reads the JSDoc of a class's source: the meta data of its functions, and
|
|
407
|
+
* the functions tagged `@private` (or `@access private`), which stay off
|
|
408
|
+
* its class info and so cannot be called. A comment counts for the class
|
|
409
|
+
* whose body it is in; in a source that declares no class, and when no
|
|
410
|
+
* class name is given, every comment counts.
|
|
411
|
+
*
|
|
412
|
+
* @param {String} content the source
|
|
413
|
+
* @param {String} [className] the class to read the documentation of
|
|
414
|
+
* @returns {{ meta: Object, privates: { member: Set, static: Set } }}
|
|
415
|
+
*/
|
|
416
|
+
static _readDocs (content, className) {
|
|
417
|
+
const declaresClasses = content
|
|
418
|
+
.split('\n')
|
|
419
|
+
.some(line => commentParser.CLASS_DECLARATION.test(line))
|
|
420
|
+
const comments = commentParser(content, { assocFunctions: true }).filter(
|
|
421
|
+
({ functionName, className: owner }) =>
|
|
422
|
+
!functionName ||
|
|
423
|
+
className === undefined ||
|
|
424
|
+
owner === className ||
|
|
425
|
+
(!owner && !declaresClasses)
|
|
426
|
+
)
|
|
427
|
+
const privates = { member: new Set(), static: new Set() }
|
|
428
|
+
comments.forEach(({ tags, functionName, isStatic, className: owner }) => {
|
|
429
|
+
if (!functionName || functionName === 'constructor' || !tags) return
|
|
430
|
+
const hidden = tags.some(
|
|
431
|
+
({ tag, name }) =>
|
|
432
|
+
tag === 'private' || (tag === 'access' && name === 'private')
|
|
433
|
+
)
|
|
434
|
+
if (!hidden) return
|
|
435
|
+
if (owner) {
|
|
436
|
+
privates[isStatic ? 'static' : 'member'].add(functionName)
|
|
437
|
+
} else {
|
|
438
|
+
privates.member.add(functionName)
|
|
439
|
+
privates.static.add(functionName)
|
|
440
|
+
}
|
|
441
|
+
})
|
|
363
442
|
const meta = {}
|
|
364
443
|
const paramsOf = tags =>
|
|
365
444
|
tags
|
|
@@ -418,11 +497,38 @@ class VrpcAdapter {
|
|
|
418
497
|
}
|
|
419
498
|
}
|
|
420
499
|
})
|
|
421
|
-
return meta
|
|
500
|
+
return { meta, privates }
|
|
422
501
|
}
|
|
423
502
|
|
|
503
|
+
/**
|
|
504
|
+
* Handles one request in place: the answer lands on `json` as `r` or `e`.
|
|
505
|
+
* Every request is answered - a malformed one, an unknown context or
|
|
506
|
+
* function with an error - so no caller waits for its timeout.
|
|
507
|
+
*
|
|
508
|
+
* @returns {Boolean} Whether the call took subscriptions of its sender,
|
|
509
|
+
* whose presence the agent then watches
|
|
510
|
+
*/
|
|
424
511
|
static _call (json) {
|
|
425
512
|
this._mustTrackClient = false // reset client tracking flag
|
|
513
|
+
// the answer is what the call yields, never what the request brought
|
|
514
|
+
delete json.r
|
|
515
|
+
delete json.e
|
|
516
|
+
try {
|
|
517
|
+
// a request without arguments has none
|
|
518
|
+
if (json.a === undefined) json.a = []
|
|
519
|
+
if (!Array.isArray(json.a)) {
|
|
520
|
+
throw new Error('The arguments of a request must be an array')
|
|
521
|
+
}
|
|
522
|
+
VrpcAdapter._dispatch(json)
|
|
523
|
+
} catch (err) {
|
|
524
|
+
json.e = VrpcAdapter._error(err)
|
|
525
|
+
}
|
|
526
|
+
// if we took a listener on board we must tell our caller such that
|
|
527
|
+
// things can be cleaned up later.
|
|
528
|
+
return this._mustTrackClient
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
static _dispatch (json) {
|
|
426
532
|
switch (json.f) {
|
|
427
533
|
case '__createIsolated__':
|
|
428
534
|
VrpcAdapter._handleCreateIsolated(json)
|
|
@@ -433,16 +539,48 @@ class VrpcAdapter {
|
|
|
433
539
|
case '__callAll__':
|
|
434
540
|
VrpcAdapter._handleCallAll(json)
|
|
435
541
|
break
|
|
542
|
+
case '__callMatching__':
|
|
543
|
+
VrpcAdapter._handleCallMatching(json)
|
|
544
|
+
break
|
|
436
545
|
case '__delete__':
|
|
437
546
|
VrpcAdapter._handleDelete(json)
|
|
438
547
|
break
|
|
548
|
+
case '__unsubscribe__':
|
|
549
|
+
VrpcAdapter._handleUnsubscribe(json)
|
|
550
|
+
break
|
|
551
|
+
case '__registrations__':
|
|
552
|
+
VrpcAdapter._handleRegistrations(json)
|
|
553
|
+
break
|
|
554
|
+
case '__end__':
|
|
555
|
+
VrpcAdapter._handleEnd(json)
|
|
556
|
+
break
|
|
439
557
|
default:
|
|
440
558
|
// this call may have the side-effect of taking a listener on board
|
|
441
559
|
VrpcAdapter._handleCall(json)
|
|
442
560
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// an error as it travels: `{ message, cause? }`, whatever was thrown
|
|
564
|
+
static _error (err) {
|
|
565
|
+
if (err && typeof err === 'object' && 'message' in err) {
|
|
566
|
+
const e = { message: String(err.message) }
|
|
567
|
+
if (err.cause !== undefined) e.cause = err.cause
|
|
568
|
+
return e
|
|
569
|
+
}
|
|
570
|
+
return { message: String(err) }
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Whether a request may call a function: only what the class info lists
|
|
575
|
+
* is reachable - a member function of the instance's class, or a static
|
|
576
|
+
* function of the class. Anything else an object happens to have (an
|
|
577
|
+
* EventEmitter's `emit`, a private helper) stays inside the agent.
|
|
578
|
+
*/
|
|
579
|
+
static _isPublished (className, func, isStatic) {
|
|
580
|
+
const entry = VrpcAdapter._functionRegistry.get(className)
|
|
581
|
+
if (!entry) return false
|
|
582
|
+
const published = isStatic ? entry.staticFunctions : entry.memberFunctions
|
|
583
|
+
return published.includes(func)
|
|
446
584
|
}
|
|
447
585
|
|
|
448
586
|
/**
|
|
@@ -461,11 +599,28 @@ class VrpcAdapter {
|
|
|
461
599
|
)
|
|
462
600
|
}
|
|
463
601
|
|
|
602
|
+
/**
|
|
603
|
+
* An instance name is one topic level of its own: a non-empty string
|
|
604
|
+
* without `/`, `+`, `#` or `*` (which would read as a pattern), and never
|
|
605
|
+
* `__static__`, which stands for the class in a request topic.
|
|
606
|
+
*/
|
|
607
|
+
static _assertInstanceName (name) {
|
|
608
|
+
if (
|
|
609
|
+
typeof name !== 'string' ||
|
|
610
|
+
name.length === 0 ||
|
|
611
|
+
/[/+#*]/.test(name) ||
|
|
612
|
+
name === '__static__'
|
|
613
|
+
) {
|
|
614
|
+
throw new Error(`Invalid instance name: ${JSON.stringify(name)}`)
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
464
618
|
static _handleCreateIsolated (json) {
|
|
465
619
|
let instance
|
|
466
620
|
try {
|
|
467
621
|
const className = json.c
|
|
468
622
|
const [instanceId, ...args] = json.a
|
|
623
|
+
VrpcAdapter._assertInstanceName(instanceId)
|
|
469
624
|
const existing = VrpcAdapter._instances.get(instanceId)
|
|
470
625
|
if (existing) {
|
|
471
626
|
// an id is never re-homed: a shared instance stays shared, an
|
|
@@ -504,6 +659,7 @@ class VrpcAdapter {
|
|
|
504
659
|
try {
|
|
505
660
|
const className = json.c
|
|
506
661
|
const [instanceId, ...args] = json.a
|
|
662
|
+
VrpcAdapter._assertInstanceName(instanceId)
|
|
507
663
|
const existing = VrpcAdapter._instances.get(instanceId)
|
|
508
664
|
if (existing && existing.isIsolated) {
|
|
509
665
|
throw new Error(`Instance '${instanceId}' exists as an isolated instance`)
|
|
@@ -528,35 +684,75 @@ class VrpcAdapter {
|
|
|
528
684
|
return instance
|
|
529
685
|
}
|
|
530
686
|
|
|
687
|
+
// `a` = [function, ...arguments]: every shared instance of the class (ALL-1)
|
|
531
688
|
static _handleCallAll (json) {
|
|
689
|
+
const [func, ...args] = json.a
|
|
690
|
+
VrpcAdapter._callAll(json, func, args, () => true)
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// `a` = [selection, function, ...arguments]: the shared instances whose
|
|
694
|
+
// name the selection matches, a pattern or a list of them (ALL-4, ALL-5)
|
|
695
|
+
static _handleCallMatching (json) {
|
|
696
|
+
const [selection, func, ...args] = json.a
|
|
697
|
+
if (!NamePattern.isSelection(selection)) {
|
|
698
|
+
json.e = {
|
|
699
|
+
message: `Invalid selection: ${JSON.stringify(selection)} (a pattern or a list of them)`
|
|
700
|
+
}
|
|
701
|
+
return
|
|
702
|
+
}
|
|
703
|
+
VrpcAdapter._callAll(json, func, args, id =>
|
|
704
|
+
NamePattern.selects(selection, id)
|
|
705
|
+
)
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// calls a function on the shared instances of a class `chosen` takes, and
|
|
709
|
+
// answers one entry per instance (ALL-2)
|
|
710
|
+
static _callAll (json, func, args, chosen) {
|
|
532
711
|
try {
|
|
533
712
|
const calls = []
|
|
713
|
+
const subscribes = Registrations.carriesEventIds(args)
|
|
714
|
+
// a remover carrying ids never reaches the class (see Registrations)
|
|
715
|
+
const withdraws = subscribes && Registrations.isRemover(func, json.v)
|
|
716
|
+
if (!withdraws && !VrpcAdapter._isPublished(json.c, func, false)) {
|
|
717
|
+
throw new Error(`Could not find function: ${func}`)
|
|
718
|
+
}
|
|
719
|
+
// what the instance's callbacks unwrap from: the function and its
|
|
720
|
+
// arguments, as a __callAll__ request carries them
|
|
721
|
+
const asCallAll = { ...json, a: [func, ...args] }
|
|
534
722
|
for (const [
|
|
535
723
|
id,
|
|
536
724
|
{ className, instance, isIsolated }
|
|
537
725
|
] of VrpcAdapter._instances) {
|
|
538
|
-
//
|
|
539
|
-
if (className !== json.c || isIsolated) continue
|
|
540
|
-
|
|
541
|
-
|
|
726
|
+
// isolated instances never take part
|
|
727
|
+
if (className !== json.c || isIsolated || !chosen(id)) continue
|
|
728
|
+
// one answer per instance: `{ id, val }`, or `{ id, err }` with the
|
|
729
|
+
// error as it travels
|
|
542
730
|
try {
|
|
543
|
-
|
|
544
|
-
if (
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
731
|
+
let val
|
|
732
|
+
if (subscribes) {
|
|
733
|
+
val = VrpcAdapter._subscribe(json, {
|
|
734
|
+
contextKey: id,
|
|
735
|
+
contextName: id,
|
|
736
|
+
target: instance,
|
|
737
|
+
func,
|
|
738
|
+
args,
|
|
739
|
+
isCallAll: true
|
|
740
|
+
}).value
|
|
741
|
+
} else {
|
|
742
|
+
const unwrapped = VrpcAdapter._unwrapArguments(asCallAll, id)
|
|
743
|
+
val = instance[func].apply(instance, unwrapped.slice(1))
|
|
744
|
+
val = VrpcAdapter.sanitizeEventListenerReturnValues(val)
|
|
548
745
|
}
|
|
549
|
-
} catch (err) {
|
|
550
|
-
e = err.message
|
|
551
|
-
}
|
|
552
|
-
if (VrpcAdapter._isPromise(v)) {
|
|
553
746
|
calls.push(
|
|
554
|
-
|
|
555
|
-
.then(
|
|
556
|
-
|
|
747
|
+
VrpcAdapter._isPromise(val)
|
|
748
|
+
? val.then(
|
|
749
|
+
x => ({ id, val: x }),
|
|
750
|
+
err => ({ id, err: VrpcAdapter._error(err) })
|
|
751
|
+
)
|
|
752
|
+
: { id, val }
|
|
557
753
|
)
|
|
558
|
-
}
|
|
559
|
-
calls.push({ id,
|
|
754
|
+
} catch (err) {
|
|
755
|
+
calls.push({ id, err: VrpcAdapter._error(err) })
|
|
560
756
|
}
|
|
561
757
|
}
|
|
562
758
|
this._handlePromise(
|
|
@@ -572,18 +768,117 @@ class VrpcAdapter {
|
|
|
572
768
|
}
|
|
573
769
|
|
|
574
770
|
static sanitizeEventListenerReturnValues (ret) {
|
|
575
|
-
return ret
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
771
|
+
return Registrations.sanitize(ret)
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* The book key of a request's context: an instance id, or the class for
|
|
776
|
+
* static functions (kept apart from an instance of the same name).
|
|
777
|
+
*/
|
|
778
|
+
static _contextKey (context, isStatic) {
|
|
779
|
+
return isStatic ? `\u0000${context}` : context
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// a class name addresses the class, as in _handleCall
|
|
783
|
+
static _isStaticContext (context) {
|
|
784
|
+
return VrpcAdapter._functionRegistry.has(context)
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* A call carrying subscription ids: one registration per context,
|
|
789
|
+
* function and arguments, shared by every subscription (see
|
|
790
|
+
* Registrations).
|
|
791
|
+
*/
|
|
792
|
+
static _subscribe (
|
|
793
|
+
json,
|
|
794
|
+
{ contextKey, contextName, target, func, args, isCallAll = false }
|
|
795
|
+
) {
|
|
796
|
+
const result = VrpcAdapter._registrations.call({
|
|
797
|
+
json,
|
|
798
|
+
contextKey,
|
|
799
|
+
contextName,
|
|
800
|
+
target,
|
|
801
|
+
func,
|
|
802
|
+
args,
|
|
803
|
+
isCallAll,
|
|
804
|
+
speaks: VrpcAdapter._speaksFor(contextName),
|
|
805
|
+
makeCallback: eventId =>
|
|
806
|
+
VrpcAdapter._generateCallback({
|
|
807
|
+
clientId: json.s,
|
|
808
|
+
isCallAll,
|
|
809
|
+
instanceId: contextName,
|
|
810
|
+
eventId,
|
|
811
|
+
receiverVersion: json.v
|
|
812
|
+
})
|
|
813
|
+
})
|
|
814
|
+
// a subscriber is tracked: its going withdraws what it holds
|
|
815
|
+
if (result.ids.length > 0) VrpcAdapter._mustTrackClient = true
|
|
816
|
+
return result
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* `__unsubscribe__ [id, ...]`: withdraws the sender's subscriptions
|
|
821
|
+
* wherever they are held; an entry `{ id, context }` withdraws that id
|
|
822
|
+
* on that instance only (one instance of a callAll subscription).
|
|
823
|
+
* Answers true.
|
|
824
|
+
*/
|
|
825
|
+
static _handleUnsubscribe (json) {
|
|
826
|
+
VrpcAdapter._registrations.unsubscribe(json.a || [], json.s)
|
|
827
|
+
json.r = true
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* `__registrations__`: lists the registrations of an instance or (on the
|
|
832
|
+
* static topic) a class.
|
|
833
|
+
*/
|
|
834
|
+
static _handleRegistrations (json) {
|
|
835
|
+
try {
|
|
836
|
+
const isStatic = VrpcAdapter._isStaticContext(json.c)
|
|
837
|
+
if (!isStatic) {
|
|
838
|
+
VrpcAdapter._assertOwner(VrpcAdapter._instances.get(json.c), json, json.c)
|
|
839
|
+
}
|
|
840
|
+
json.r = VrpcAdapter._registrations.list(
|
|
841
|
+
VrpcAdapter._contextKey(json.c, isStatic)
|
|
842
|
+
)
|
|
843
|
+
} catch (err) {
|
|
844
|
+
const { message, cause } = err
|
|
845
|
+
json.e = { message, cause }
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* `__end__ [function, args, by]`: ends one registration for all its
|
|
851
|
+
* subscriptions, which are told who ended it. Answers whether it existed.
|
|
852
|
+
*/
|
|
853
|
+
static _handleEnd (json) {
|
|
854
|
+
try {
|
|
855
|
+
const isStatic = VrpcAdapter._isStaticContext(json.c)
|
|
856
|
+
if (!isStatic) {
|
|
857
|
+
VrpcAdapter._assertOwner(VrpcAdapter._instances.get(json.c), json, json.c)
|
|
858
|
+
}
|
|
859
|
+
const [func, args = [], by = null] = json.a || []
|
|
860
|
+
json.r = VrpcAdapter._registrations.end(
|
|
861
|
+
VrpcAdapter._contextKey(json.c, isStatic),
|
|
862
|
+
func,
|
|
863
|
+
args,
|
|
864
|
+
by
|
|
865
|
+
)
|
|
866
|
+
} catch (err) {
|
|
867
|
+
const { message, cause } = err
|
|
868
|
+
json.e = { message, cause }
|
|
869
|
+
}
|
|
581
870
|
}
|
|
582
871
|
|
|
583
872
|
static _handleDelete (json) {
|
|
584
873
|
try {
|
|
585
874
|
const instance = json.a[0] // first arg is instance to be deleted
|
|
586
|
-
VrpcAdapter.
|
|
875
|
+
const entry = VrpcAdapter._instances.get(instance)
|
|
876
|
+
// a delete names the instance's own class: the class info of that
|
|
877
|
+
// class is what announces it gone
|
|
878
|
+
if (entry && entry.className !== json.c && json.s !== VrpcAdapter.LOCAL_SENDER) {
|
|
879
|
+
throw new Error(`Instance '${instance}' is not of class '${json.c}'`)
|
|
880
|
+
}
|
|
881
|
+
VrpcAdapter._assertOwner(entry, json, instance)
|
|
587
882
|
VrpcAdapter._emitter.emit('beforeDelete', {
|
|
588
883
|
instance,
|
|
589
884
|
className: json.c
|
|
@@ -614,9 +909,16 @@ class VrpcAdapter {
|
|
|
614
909
|
json.e = { message, cause }
|
|
615
910
|
return
|
|
616
911
|
}
|
|
617
|
-
// special case: removeAllListeners
|
|
912
|
+
// special case: removeAllListeners withdraws the caller's subscriptions
|
|
913
|
+
// of that event - the emitter itself is never touched, it holds other
|
|
914
|
+
// clients' and its own listeners
|
|
618
915
|
if (json.f === 'removeAllListeners') {
|
|
619
|
-
VrpcAdapter.
|
|
916
|
+
const isStatic = VrpcAdapter._isStaticContext(json.c)
|
|
917
|
+
VrpcAdapter._registrations.withdrawEvent(
|
|
918
|
+
VrpcAdapter._contextKey(json.c, isStatic),
|
|
919
|
+
json.a[0],
|
|
920
|
+
json.s
|
|
921
|
+
)
|
|
620
922
|
json.r = true
|
|
621
923
|
return
|
|
622
924
|
}
|
|
@@ -629,6 +931,49 @@ class VrpcAdapter {
|
|
|
629
931
|
) {
|
|
630
932
|
throw new Error(`Could not find context: ${json.c}`)
|
|
631
933
|
}
|
|
934
|
+
// Check whether context is a registered class (function is static)
|
|
935
|
+
const classEntry = VrpcAdapter._functionRegistry.get(json.c)
|
|
936
|
+
const isStatic = classEntry !== undefined
|
|
937
|
+
let target
|
|
938
|
+
let className = json.c
|
|
939
|
+
if (isStatic) {
|
|
940
|
+
target = classEntry.Klass
|
|
941
|
+
} else {
|
|
942
|
+
const entry = VrpcAdapter._instances.get(json.c)
|
|
943
|
+
if (entry === undefined) {
|
|
944
|
+
throw new Error(`Could not find context: ${json.c}`)
|
|
945
|
+
}
|
|
946
|
+
target = entry.instance
|
|
947
|
+
className = entry.className
|
|
948
|
+
}
|
|
949
|
+
const carriesIds = Registrations.carriesEventIds(json.a)
|
|
950
|
+
// only what the class info lists is called; a remover carrying ids never
|
|
951
|
+
// reaches the class (see Registrations)
|
|
952
|
+
if (
|
|
953
|
+
!(carriesIds && Registrations.isRemover(json.f, json.v)) &&
|
|
954
|
+
!VrpcAdapter._isPublished(className, json.f, isStatic)
|
|
955
|
+
) {
|
|
956
|
+
throw new Error(`Could not find function: ${json.f}`)
|
|
957
|
+
}
|
|
958
|
+
// a call carrying subscription ids subscribes (or withdraws)
|
|
959
|
+
if (carriesIds) {
|
|
960
|
+
try {
|
|
961
|
+
const { value } = VrpcAdapter._subscribe(json, {
|
|
962
|
+
contextKey: VrpcAdapter._contextKey(json.c, isStatic),
|
|
963
|
+
contextName: json.c,
|
|
964
|
+
target,
|
|
965
|
+
func: json.f,
|
|
966
|
+
args: json.a
|
|
967
|
+
})
|
|
968
|
+
if (VrpcAdapter._isPromise(value)) {
|
|
969
|
+
this._handlePromise(json, value)
|
|
970
|
+
} else json.r = value
|
|
971
|
+
} catch (err) {
|
|
972
|
+
const { message, cause } = err
|
|
973
|
+
json.e = { message, cause }
|
|
974
|
+
}
|
|
975
|
+
return
|
|
976
|
+
}
|
|
632
977
|
const unwrapped = VrpcAdapter._unwrapArguments(json)
|
|
633
978
|
// special case: removing a listener the adapter does not hold (the
|
|
634
979
|
// client's subscription outlived a restart of this agent, or it sent
|
|
@@ -641,47 +986,23 @@ class VrpcAdapter {
|
|
|
641
986
|
json.r = true
|
|
642
987
|
return
|
|
643
988
|
}
|
|
644
|
-
//
|
|
645
|
-
|
|
646
|
-
if (
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
if (VrpcAdapter.
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
const { message, cause } = err
|
|
662
|
-
json.e = { message, cause }
|
|
663
|
-
}
|
|
664
|
-
} else throw new Error(`Could not find function: ${json.f}`)
|
|
665
|
-
} else {
|
|
666
|
-
// is not static
|
|
667
|
-
const entry = VrpcAdapter._instances.get(json.c)
|
|
668
|
-
if (entry === undefined) {
|
|
669
|
-
throw new Error(`Could not find context: ${json.c}`)
|
|
670
|
-
}
|
|
671
|
-
const { instance } = entry
|
|
672
|
-
if (VrpcAdapter._isFunction(instance[json.f])) {
|
|
673
|
-
try {
|
|
674
|
-
let ret = instance[json.f].apply(instance, unwrapped)
|
|
675
|
-
ret = VrpcAdapter.sanitizeEventListenerReturnValues(ret)
|
|
676
|
-
// check if function returns promise
|
|
677
|
-
if (VrpcAdapter._isPromise(ret)) {
|
|
678
|
-
this._handlePromise(json, ret)
|
|
679
|
-
} else json.r = ret
|
|
680
|
-
} catch (err) {
|
|
681
|
-
const { message, cause } = err
|
|
682
|
-
json.e = { message, cause }
|
|
683
|
-
}
|
|
684
|
-
} else throw new Error(`Could not find function: ${json.f}`)
|
|
989
|
+
// TODO Think about whether to do live checking (like here) or
|
|
990
|
+
// rather sticking to those functions registered before...
|
|
991
|
+
if (!VrpcAdapter._isFunction(target[json.f])) {
|
|
992
|
+
throw new Error(`Could not find function: ${json.f}`)
|
|
993
|
+
}
|
|
994
|
+
try {
|
|
995
|
+
let ret = isStatic
|
|
996
|
+
? target[json.f].apply(null, unwrapped)
|
|
997
|
+
: target[json.f].apply(target, unwrapped)
|
|
998
|
+
if (!isStatic) ret = VrpcAdapter.sanitizeEventListenerReturnValues(ret)
|
|
999
|
+
// check if function returns promise
|
|
1000
|
+
if (VrpcAdapter._isPromise(ret)) {
|
|
1001
|
+
this._handlePromise(json, ret)
|
|
1002
|
+
} else json.r = ret
|
|
1003
|
+
} catch (err) {
|
|
1004
|
+
const { message, cause } = err
|
|
1005
|
+
json.e = { message, cause }
|
|
685
1006
|
}
|
|
686
1007
|
}
|
|
687
1008
|
|
|
@@ -707,10 +1028,13 @@ class VrpcAdapter {
|
|
|
707
1028
|
* clock or holds a connection lives on as long as anything reaches it;
|
|
708
1029
|
* ending it takes three steps:
|
|
709
1030
|
* 1. its name is free at once: no call reaches the object any more;
|
|
710
|
-
* 2. the adapter
|
|
711
|
-
* silent from now on
|
|
1031
|
+
* 2. the adapter forgets the instance's registrations (their listeners
|
|
1032
|
+
* are silent from now on, see Registrations); it releases them itself
|
|
1033
|
+
* only for an object without a dispose method (legacy), and always
|
|
1034
|
+
* takes back what it attached through Node's own `on`;
|
|
712
1035
|
* 3. the object releases what it runs through the ECMAScript dispose
|
|
713
1036
|
* protocol, `[Symbol.asyncDispose]` or else `[Symbol.dispose]`.
|
|
1037
|
+
* Its subscribers learn the loss from the class info (instance gone).
|
|
714
1038
|
*
|
|
715
1039
|
* @returns {Object} `{ ended, disposed }`: whether the name was known, and
|
|
716
1040
|
* a promise when an asynchronous dispose is under way (it never rejects)
|
|
@@ -719,33 +1043,16 @@ class VrpcAdapter {
|
|
|
719
1043
|
const entry = VrpcAdapter._instances.get(instanceId)
|
|
720
1044
|
if (!entry) return { ended: false, disposed: null }
|
|
721
1045
|
VrpcAdapter._instances.delete(instanceId)
|
|
722
|
-
|
|
1046
|
+
const { instance } = entry
|
|
1047
|
+
VrpcAdapter._registrations.forgetContext(instanceId, {
|
|
1048
|
+
release: !(
|
|
1049
|
+
VrpcAdapter._isFunction(instance[Symbol.asyncDispose]) ||
|
|
1050
|
+
VrpcAdapter._isFunction(instance[Symbol.dispose])
|
|
1051
|
+
)
|
|
1052
|
+
})
|
|
723
1053
|
return { ended: true, disposed: VrpcAdapter._dispose(instanceId, entry) }
|
|
724
1054
|
}
|
|
725
1055
|
|
|
726
|
-
static _detachListeners (instanceId, instance) {
|
|
727
|
-
const registrations = VrpcAdapter._listeners[instanceId]
|
|
728
|
-
delete VrpcAdapter._listeners[instanceId]
|
|
729
|
-
if (!registrations || !VrpcAdapter._isFunction(instance.removeListener)) {
|
|
730
|
-
return
|
|
731
|
-
}
|
|
732
|
-
for (const { event, listener, clients } of Object.values(registrations)) {
|
|
733
|
-
try {
|
|
734
|
-
VrpcAdapter._removeTimes(instance, event, listener, clients.length)
|
|
735
|
-
} catch (err) {
|
|
736
|
-
// a class whose removeListener fails keeps a silent listener
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
// a subscription repeated n times may hold n attachments of one listener
|
|
742
|
-
// (EventEmitter semantics): each removeListener takes one of them
|
|
743
|
-
static _removeTimes (instance, event, listener, times) {
|
|
744
|
-
for (let n = 0; n < Math.max(1, times); n++) {
|
|
745
|
-
instance.removeListener(event, listener)
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
1056
|
/**
|
|
750
1057
|
* Runs the object's dispose method. A dispose that fails or outlasts
|
|
751
1058
|
* VrpcAdapter.DISPOSE_TIMEOUT is reported (event 'disposeFailed') and
|
|
@@ -790,25 +1097,12 @@ class VrpcAdapter {
|
|
|
790
1097
|
return Promise.race([settled, bound]).then(() => clearTimeout(timer))
|
|
791
1098
|
}
|
|
792
1099
|
|
|
1100
|
+
/**
|
|
1101
|
+
* A client connection went: every subscription it held is withdrawn, and
|
|
1102
|
+
* a registration whose last subscription that was is released.
|
|
1103
|
+
*/
|
|
793
1104
|
static _unregisterClient (clientId) {
|
|
794
|
-
|
|
795
|
-
Object.entries(iv).forEach(([ek, ev]) => {
|
|
796
|
-
const { listener, event } = ev
|
|
797
|
-
// a client is listed once per subscription: its going ends them all
|
|
798
|
-
const remaining = ev.clients.filter(x => x !== clientId)
|
|
799
|
-
const gone = ev.clients.length - remaining.length
|
|
800
|
-
if (gone === 0) return
|
|
801
|
-
ev.clients = remaining
|
|
802
|
-
if (remaining.length === 0) {
|
|
803
|
-
const entry = VrpcAdapter._instances.get(ik)
|
|
804
|
-
const instance = entry && entry.instance
|
|
805
|
-
if (instance && instance.removeListener) {
|
|
806
|
-
VrpcAdapter._removeTimes(instance, event, listener, gone)
|
|
807
|
-
}
|
|
808
|
-
delete VrpcAdapter._listeners[ik][ek]
|
|
809
|
-
}
|
|
810
|
-
})
|
|
811
|
-
})
|
|
1105
|
+
VrpcAdapter._registrations.withdrawClient(clientId)
|
|
812
1106
|
}
|
|
813
1107
|
|
|
814
1108
|
static _validate (schema, params) {
|
|
@@ -819,6 +1113,7 @@ class VrpcAdapter {
|
|
|
819
1113
|
if (!valid) throw new Error(VrpcAdapter._ajv.errorsText())
|
|
820
1114
|
}
|
|
821
1115
|
|
|
1116
|
+
// the arguments of a plain call: `__f__` ids become one-shot callbacks
|
|
822
1117
|
static _unwrapArguments (json, instanceId) {
|
|
823
1118
|
const clientId = json.s
|
|
824
1119
|
// if we came from a "callAll" we will have an instanceId defined
|
|
@@ -829,53 +1124,17 @@ class VrpcAdapter {
|
|
|
829
1124
|
const func = isCallAll ? json.a[0] : json.f
|
|
830
1125
|
const unwrapped = isCallAll ? [func] : []
|
|
831
1126
|
for (const arg of args) {
|
|
832
|
-
if (typeof arg === 'string') {
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
)
|
|
843
|
-
} else if (arg.startsWith('__e__')) {
|
|
844
|
-
// event registration
|
|
845
|
-
if (func === 'on' || func === 'addListener') {
|
|
846
|
-
this._mustTrackClient = true
|
|
847
|
-
const listener = VrpcAdapter._registerListener({
|
|
848
|
-
clientId,
|
|
849
|
-
isCallAll,
|
|
850
|
-
instanceId: context,
|
|
851
|
-
eventId: arg,
|
|
852
|
-
event: args[0]
|
|
853
|
-
})
|
|
854
|
-
unwrapped.push(listener)
|
|
855
|
-
// event un-registration
|
|
856
|
-
} else if (func === 'off' || func === 'removeListener') {
|
|
857
|
-
const listener = VrpcAdapter._unregisterListener({
|
|
858
|
-
clientId,
|
|
859
|
-
instanceId: context,
|
|
860
|
-
eventId: arg
|
|
861
|
-
})
|
|
862
|
-
unwrapped.push(listener)
|
|
863
|
-
} else {
|
|
864
|
-
this._mustTrackClient = true
|
|
865
|
-
const listener = VrpcAdapter._registerListener({
|
|
866
|
-
clientId,
|
|
867
|
-
isCallAll,
|
|
868
|
-
instanceId: context,
|
|
869
|
-
eventId: arg,
|
|
870
|
-
event: null
|
|
871
|
-
})
|
|
872
|
-
unwrapped.push(listener)
|
|
873
|
-
}
|
|
874
|
-
} else {
|
|
875
|
-
unwrapped.push(arg)
|
|
876
|
-
}
|
|
1127
|
+
if (typeof arg === 'string' && arg.startsWith('__f__')) {
|
|
1128
|
+
unwrapped.push(
|
|
1129
|
+
this._generateCallback({
|
|
1130
|
+
clientId,
|
|
1131
|
+
isCallAll,
|
|
1132
|
+
instanceId: context,
|
|
1133
|
+
eventId: arg,
|
|
1134
|
+
receiverVersion: json.v
|
|
1135
|
+
})
|
|
1136
|
+
)
|
|
877
1137
|
} else {
|
|
878
|
-
// regular argument
|
|
879
1138
|
unwrapped.push(arg)
|
|
880
1139
|
}
|
|
881
1140
|
}
|
|
@@ -899,112 +1158,38 @@ class VrpcAdapter {
|
|
|
899
1158
|
}
|
|
900
1159
|
}
|
|
901
1160
|
|
|
902
|
-
static _generateCallback ({
|
|
903
|
-
const speaks = VrpcAdapter._speaksFor(instanceId)
|
|
904
|
-
return (...innerArgs) => {
|
|
905
|
-
if (!speaks()) return
|
|
906
|
-
// "callAll" event notifications expect the instanceId as first argument
|
|
907
|
-
const a = isCallAll ? [instanceId, ...innerArgs] : [...innerArgs]
|
|
908
|
-
VrpcAdapter._callback({ a, s: clientId, i: eventId })
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
static _registerListener ({
|
|
1161
|
+
static _generateCallback ({
|
|
913
1162
|
eventId,
|
|
914
1163
|
clientId,
|
|
915
1164
|
instanceId,
|
|
916
|
-
|
|
917
|
-
|
|
1165
|
+
isCallAll,
|
|
1166
|
+
receiverVersion
|
|
918
1167
|
}) {
|
|
919
|
-
if (!VrpcAdapter._listeners[instanceId]) {
|
|
920
|
-
VrpcAdapter._listeners[instanceId] = {
|
|
921
|
-
[eventId]: {
|
|
922
|
-
event,
|
|
923
|
-
clients: [clientId],
|
|
924
|
-
listener: VrpcAdapter._generateListener({
|
|
925
|
-
eventId,
|
|
926
|
-
instanceId,
|
|
927
|
-
isCallAll
|
|
928
|
-
})
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
return VrpcAdapter._listeners[instanceId][eventId].listener
|
|
932
|
-
}
|
|
933
|
-
if (!VrpcAdapter._listeners[instanceId][eventId]) {
|
|
934
|
-
VrpcAdapter._listeners[instanceId][eventId] = {
|
|
935
|
-
event,
|
|
936
|
-
clients: [clientId],
|
|
937
|
-
listener: VrpcAdapter._generateListener({
|
|
938
|
-
eventId,
|
|
939
|
-
instanceId,
|
|
940
|
-
isCallAll
|
|
941
|
-
})
|
|
942
|
-
}
|
|
943
|
-
return VrpcAdapter._listeners[instanceId][eventId].listener
|
|
944
|
-
}
|
|
945
|
-
VrpcAdapter._listeners[instanceId][eventId].clients.push(clientId)
|
|
946
|
-
return VrpcAdapter._listeners[instanceId][eventId].listener
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
static _generateListener ({ eventId, instanceId, isCallAll }) {
|
|
950
1168
|
const speaks = VrpcAdapter._speaksFor(instanceId)
|
|
951
1169
|
return (...innerArgs) => {
|
|
952
1170
|
if (!speaks()) return
|
|
1171
|
+
// "callAll" event notifications expect the instanceId as first argument
|
|
953
1172
|
const a = isCallAll ? [instanceId, ...innerArgs] : [...innerArgs]
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
static _unregisterListener ({ eventId, clientId, instanceId }) {
|
|
959
|
-
if (
|
|
960
|
-
VrpcAdapter._listeners[instanceId] &&
|
|
961
|
-
VrpcAdapter._listeners[instanceId][eventId]
|
|
962
|
-
) {
|
|
963
|
-
const { listener, clients } = VrpcAdapter._listeners[instanceId][eventId]
|
|
964
|
-
const index = clients.indexOf(clientId)
|
|
965
|
-
if (index !== -1) clients.splice(index, 1)
|
|
966
|
-
if (clients.length === 0) {
|
|
967
|
-
delete VrpcAdapter._listeners[instanceId][eventId]
|
|
968
|
-
if (Object.keys(VrpcAdapter._listeners[instanceId]).length === 0) {
|
|
969
|
-
delete VrpcAdapter._listeners[instanceId]
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
return listener
|
|
1173
|
+
// the receiver's protocol version: whether it can be told of a
|
|
1174
|
+
// callback that cannot travel (CALL-10)
|
|
1175
|
+
VrpcAdapter._callback({ a, s: clientId, i: eventId, receiverVersion })
|
|
973
1176
|
}
|
|
974
1177
|
}
|
|
975
1178
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
ev.clients = ev.clients.filter(x => x !== clientId)
|
|
982
|
-
VrpcAdapter.getInstance(instanceId).removeAllListeners(ev.event)
|
|
983
|
-
if (ev.clients.length === 0) {
|
|
984
|
-
delete VrpcAdapter._listeners[instanceId][ek]
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
})
|
|
988
|
-
}
|
|
989
|
-
|
|
1179
|
+
/**
|
|
1180
|
+
* Answers a promise twice: at once with `r: '__p__<f>-<n>'`, and once it
|
|
1181
|
+
* settled with a second answer under that id, `{ s, i, r }` or
|
|
1182
|
+
* `{ s, i, e }`.
|
|
1183
|
+
*/
|
|
990
1184
|
static _handlePromise (json, promise) {
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
...json,
|
|
1000
|
-
e: { message: err.message, cause: err.cause },
|
|
1001
|
-
i: id
|
|
1002
|
-
})
|
|
1003
|
-
)
|
|
1004
|
-
} catch (err) {
|
|
1005
|
-
const { message, cause } = err
|
|
1006
|
-
json.data.e = { message, cause }
|
|
1007
|
-
}
|
|
1185
|
+
const cid = VrpcAdapter._correlationId++ % Number.MAX_SAFE_INTEGER
|
|
1186
|
+
const id = `__p__${json.f}-${cid}`
|
|
1187
|
+
const { s } = json
|
|
1188
|
+
json.r = id
|
|
1189
|
+
promise.then(
|
|
1190
|
+
r => VrpcAdapter._callback({ s, i: id, r }),
|
|
1191
|
+
err => VrpcAdapter._callback({ s, i: id, e: VrpcAdapter._error(err) })
|
|
1192
|
+
)
|
|
1008
1193
|
}
|
|
1009
1194
|
|
|
1010
1195
|
static _getClassEntry (className) {
|
|
@@ -1040,6 +1225,18 @@ class VrpcAdapter {
|
|
|
1040
1225
|
static _extractMemberFunctions (klass) {
|
|
1041
1226
|
let klass_ = klass
|
|
1042
1227
|
let fs = []
|
|
1228
|
+
// Node's EventEmitter offers subscribing, withdrawing and once to
|
|
1229
|
+
// clients; emit (anyone could fake an event to every subscriber, or emit
|
|
1230
|
+
// 'error' and end the agent) and its internals stay inside the class
|
|
1231
|
+
const isEmitter =
|
|
1232
|
+
klass instanceof EventEmitter ||
|
|
1233
|
+
(!!klass.prototype && klass.prototype instanceof EventEmitter)
|
|
1234
|
+
const published = (names, level) => {
|
|
1235
|
+
if (level === EventEmitter.prototype) {
|
|
1236
|
+
return names.filter(x => VrpcAdapter._emitterPublic.has(x))
|
|
1237
|
+
}
|
|
1238
|
+
return isEmitter ? names.filter(x => x !== 'emit') : names
|
|
1239
|
+
}
|
|
1043
1240
|
do {
|
|
1044
1241
|
if (klass_.prototype) {
|
|
1045
1242
|
const funcs = Object.getOwnPropertyNames(klass_.prototype).filter(
|
|
@@ -1047,12 +1244,12 @@ class VrpcAdapter {
|
|
|
1047
1244
|
!VrpcAdapter._blackList.has(x) &&
|
|
1048
1245
|
this._isFunction(klass_.prototype[x])
|
|
1049
1246
|
)
|
|
1050
|
-
fs = [...fs, ...funcs]
|
|
1247
|
+
fs = [...fs, ...published(funcs, klass_.prototype)]
|
|
1051
1248
|
} else {
|
|
1052
1249
|
const funcs = Object.getOwnPropertyNames(klass_).filter(
|
|
1053
1250
|
x => !VrpcAdapter._blackList.has(x) && this._isFunction(klass_[x])
|
|
1054
1251
|
)
|
|
1055
|
-
fs = [...fs, ...funcs]
|
|
1252
|
+
fs = [...fs, ...published(funcs, klass_)]
|
|
1056
1253
|
}
|
|
1057
1254
|
klass_ = Object.getPrototypeOf(klass_)
|
|
1058
1255
|
} while (klass_)
|
|
@@ -1064,13 +1261,16 @@ class VrpcAdapter {
|
|
|
1064
1261
|
let klass_ = klass
|
|
1065
1262
|
let fs = []
|
|
1066
1263
|
do {
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1264
|
+
// EventEmitter's own statics are Node helpers, not the class's API
|
|
1265
|
+
if (klass_ !== EventEmitter) {
|
|
1266
|
+
fs = [
|
|
1267
|
+
...fs,
|
|
1268
|
+
...Object.getOwnPropertyNames(klass_).filter(prop => {
|
|
1269
|
+
const desc = Object.getOwnPropertyDescriptor(klass_, prop)
|
|
1270
|
+
return !!desc && typeof desc.value === 'function'
|
|
1271
|
+
})
|
|
1272
|
+
]
|
|
1273
|
+
}
|
|
1074
1274
|
klass_ = Object.getPrototypeOf(klass_)
|
|
1075
1275
|
} while (klass_ && klass_.name)
|
|
1076
1276
|
// Filter out all duplicates
|
|
@@ -1121,11 +1321,23 @@ class VrpcAdapter {
|
|
|
1121
1321
|
*/
|
|
1122
1322
|
|
|
1123
1323
|
/**
|
|
1124
|
-
* Event '
|
|
1324
|
+
* Event 'beforeDelete'
|
|
1125
1325
|
*
|
|
1126
|
-
* Emitted
|
|
1326
|
+
* Emitted when a deletion through the agent begins, while the instance can
|
|
1327
|
+
* still be reached
|
|
1127
1328
|
*
|
|
1128
1329
|
* @event VrpcAdapter#beforeDelete
|
|
1330
|
+
* @type {Object}
|
|
1331
|
+
* @property {String} className The class name of the instance
|
|
1332
|
+
* @property {String} instance The instance name
|
|
1333
|
+
*/
|
|
1334
|
+
|
|
1335
|
+
/**
|
|
1336
|
+
* Event 'delete'
|
|
1337
|
+
*
|
|
1338
|
+
* Emitted once an instance was deleted through the agent (a client's delete,
|
|
1339
|
+
* or an isolated instance whose owner went)
|
|
1340
|
+
*
|
|
1129
1341
|
* @event VrpcAdapter#delete
|
|
1130
1342
|
* @type {Object}
|
|
1131
1343
|
* @property {String} className The class name of the deleted instance
|
|
@@ -1146,15 +1358,59 @@ class VrpcAdapter {
|
|
|
1146
1358
|
* @property {Error} error What went wrong
|
|
1147
1359
|
*/
|
|
1148
1360
|
|
|
1361
|
+
/**
|
|
1362
|
+
* Event 'releaseFailed'
|
|
1363
|
+
*
|
|
1364
|
+
* Emitted when the release of an event registration threw or rejected; the
|
|
1365
|
+
* registration is gone anyway
|
|
1366
|
+
*
|
|
1367
|
+
* @event VrpcAdapter#releaseFailed
|
|
1368
|
+
* @type {Object}
|
|
1369
|
+
* @property {String} context The instance (or class) of the registration
|
|
1370
|
+
* @property {String} function The event function
|
|
1371
|
+
* @property {Array.<Any>} args Its arguments, without handlers
|
|
1372
|
+
* @property {Error} error What went wrong
|
|
1373
|
+
*/
|
|
1374
|
+
|
|
1375
|
+
/**
|
|
1376
|
+
* Event 'greetFailed'
|
|
1377
|
+
*
|
|
1378
|
+
* Emitted when a registration's greeting threw or rejected; the subscriber
|
|
1379
|
+
* stays subscribed
|
|
1380
|
+
*
|
|
1381
|
+
* @event VrpcAdapter#greetFailed
|
|
1382
|
+
* @type {Object}
|
|
1383
|
+
* @property {String} context The instance (or class) of the registration
|
|
1384
|
+
* @property {String} function The event function
|
|
1385
|
+
* @property {Array.<Any>} args Its arguments, without handlers
|
|
1386
|
+
* @property {Error} error What went wrong
|
|
1387
|
+
*/
|
|
1388
|
+
|
|
1149
1389
|
// Initialize static members
|
|
1150
1390
|
VrpcAdapter._functionRegistry = new Map()
|
|
1151
1391
|
VrpcAdapter._instances = new Map()
|
|
1152
1392
|
VrpcAdapter._emitter = new EventEmitter()
|
|
1153
1393
|
VrpcAdapter._correlationId = 0
|
|
1154
|
-
|
|
1394
|
+
// the event registrations of every context and their subscriptions
|
|
1395
|
+
VrpcAdapter._registrations = new Registrations({
|
|
1396
|
+
publish: message => VrpcAdapter._callback(message),
|
|
1397
|
+
emit: (event, payload) => VrpcAdapter._emitter.emit(event, payload)
|
|
1398
|
+
})
|
|
1155
1399
|
// the longest a delete waits for the dispose method of its instance
|
|
1156
1400
|
VrpcAdapter.DISPOSE_TIMEOUT = 10000
|
|
1401
|
+
// the EventEmitter functions published to clients
|
|
1402
|
+
VrpcAdapter._emitterPublic = new Set([
|
|
1403
|
+
'on',
|
|
1404
|
+
'addListener',
|
|
1405
|
+
'off',
|
|
1406
|
+
'removeListener',
|
|
1407
|
+
'once',
|
|
1408
|
+
'removeAllListeners'
|
|
1409
|
+
])
|
|
1157
1410
|
VrpcAdapter._blackList = new Set([
|
|
1411
|
+
// a class's constructor is reached through __createShared__ and
|
|
1412
|
+
// __createIsolated__, never as a member function
|
|
1413
|
+
'constructor',
|
|
1158
1414
|
'caller',
|
|
1159
1415
|
'callee',
|
|
1160
1416
|
'arguments',
|
|
@@ -1177,6 +1433,8 @@ VrpcAdapter._blackList = new Set([
|
|
|
1177
1433
|
* The sender the agent uses for its own housekeeping calls (deleting the
|
|
1178
1434
|
* isolated instances of a departed connection). A Symbol: no message from
|
|
1179
1435
|
* the wire can carry it, so it can never be claimed remotely.
|
|
1436
|
+
*
|
|
1437
|
+
* @private
|
|
1180
1438
|
*/
|
|
1181
1439
|
VrpcAdapter.LOCAL_SENDER = Symbol('vrpc.local-sender')
|
|
1182
1440
|
|