vrpc 3.8.2 → 3.9.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/browser/vrpc.js +3 -3
- package/package.json +1 -1
- package/vrpc/VrpcAdapter.js +61 -3
- package/vrpc/VrpcAgent.js +8 -3
- package/vrpc/VrpcBrowserAdapter.js +50 -3
- package/vrpc/VrpcBrowserAgent.js +8 -3
- package/vrpc/VrpcClient.js +48 -18
package/package.json
CHANGED
package/vrpc/VrpcAdapter.js
CHANGED
|
@@ -9,12 +9,10 @@ __\/\\\_______\/\\\__/\\\///////\\\___\/\\\/////////\\\____/\\\////////__
|
|
|
9
9
|
_______\//\\\_______\/\\\______\//\\\_\/\\\_______________\////\\\\\\\\\_
|
|
10
10
|
________\///________\///________\///__\///___________________\/////////__
|
|
11
11
|
|
|
12
|
-
|
|
13
12
|
Non-intrusively adapts code and provides access in form of asynchronous remote
|
|
14
13
|
procedure calls (RPC).
|
|
15
14
|
Author: Dr. Burkhard C. Heisen (https://github.com/heisenware/vrpc)
|
|
16
15
|
|
|
17
|
-
|
|
18
16
|
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
|
19
17
|
Copyright (c) 2018 - 2022 Dr. Burkhard C. Heisen <burkhard.heisen@heisenware.com>.
|
|
20
18
|
|
|
@@ -419,16 +417,45 @@ class VrpcAdapter {
|
|
|
419
417
|
return this._mustTrackClient
|
|
420
418
|
}
|
|
421
419
|
|
|
420
|
+
/**
|
|
421
|
+
* Refuses a request on an isolated instance that belongs to another
|
|
422
|
+
* connection. An isolated instance created over the wire remembers the
|
|
423
|
+
* connection that created it (the request's sender) and answers that
|
|
424
|
+
* connection alone; one the agent created itself has no owner and is
|
|
425
|
+
* open. The agent's own housekeeping passes as VrpcAdapter.LOCAL_SENDER,
|
|
426
|
+
* which no wire message can carry.
|
|
427
|
+
*/
|
|
428
|
+
static _assertOwner (entry, json, instanceId) {
|
|
429
|
+
if (!entry || !entry.isIsolated || entry.owner === undefined) return
|
|
430
|
+
if (json.s === VrpcAdapter.LOCAL_SENDER || json.s === entry.owner) return
|
|
431
|
+
throw new Error(
|
|
432
|
+
`Instance '${instanceId}' is isolated to another connection`
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
|
|
422
436
|
static _handleCreateIsolated (json) {
|
|
423
437
|
let instance
|
|
424
438
|
try {
|
|
425
439
|
const className = json.c
|
|
426
440
|
const [instanceId, ...args] = json.a
|
|
441
|
+
const existing = VrpcAdapter._instances.get(instanceId)
|
|
442
|
+
if (existing) {
|
|
443
|
+
// an id is never re-homed: a shared instance stays shared, an
|
|
444
|
+
// isolated one keeps its owner
|
|
445
|
+
if (!existing.isIsolated) {
|
|
446
|
+
throw new Error(`Instance '${instanceId}' exists as a shared instance`)
|
|
447
|
+
}
|
|
448
|
+
VrpcAdapter._assertOwner(existing, json, instanceId)
|
|
449
|
+
json.r = instanceId
|
|
450
|
+
return existing.instance
|
|
451
|
+
}
|
|
427
452
|
instance = VrpcAdapter._create(className, instanceId, ...args)
|
|
428
453
|
VrpcAdapter._instances.set(instanceId, {
|
|
429
454
|
instance,
|
|
430
455
|
className,
|
|
431
|
-
isIsolated: true
|
|
456
|
+
isIsolated: true,
|
|
457
|
+
// undefined for instances the agent creates itself (open)
|
|
458
|
+
owner: typeof json.s === 'string' ? json.s : undefined
|
|
432
459
|
})
|
|
433
460
|
VrpcAdapter._emitter.emit('create', {
|
|
434
461
|
args,
|
|
@@ -449,6 +476,10 @@ class VrpcAdapter {
|
|
|
449
476
|
try {
|
|
450
477
|
const className = json.c
|
|
451
478
|
const [instanceId, ...args] = json.a
|
|
479
|
+
const existing = VrpcAdapter._instances.get(instanceId)
|
|
480
|
+
if (existing && existing.isIsolated) {
|
|
481
|
+
throw new Error(`Instance '${instanceId}' exists as an isolated instance`)
|
|
482
|
+
}
|
|
452
483
|
instance = VrpcAdapter._create(className, instanceId, ...args)
|
|
453
484
|
VrpcAdapter._instances.set(instanceId, {
|
|
454
485
|
instance,
|
|
@@ -524,6 +555,7 @@ class VrpcAdapter {
|
|
|
524
555
|
static _handleDelete (json) {
|
|
525
556
|
try {
|
|
526
557
|
const instance = json.a[0] // first arg is instance to be deleted
|
|
558
|
+
VrpcAdapter._assertOwner(VrpcAdapter._instances.get(instance), json, instance)
|
|
527
559
|
VrpcAdapter._emitter.emit('beforeDelete', {
|
|
528
560
|
instance,
|
|
529
561
|
className: json.c
|
|
@@ -540,6 +572,14 @@ class VrpcAdapter {
|
|
|
540
572
|
}
|
|
541
573
|
|
|
542
574
|
static _handleCall (json) {
|
|
575
|
+
// an isolated instance answers its creating connection only
|
|
576
|
+
try {
|
|
577
|
+
VrpcAdapter._assertOwner(VrpcAdapter._instances.get(json.c), json, json.c)
|
|
578
|
+
} catch (err) {
|
|
579
|
+
const { message, cause } = err
|
|
580
|
+
json.e = { message, cause }
|
|
581
|
+
return
|
|
582
|
+
}
|
|
543
583
|
// special case: removeAllListeners
|
|
544
584
|
if (json.f === 'removeAllListeners') {
|
|
545
585
|
VrpcAdapter._removeAllListeners(json.a[0], json.s, json.c)
|
|
@@ -547,6 +587,17 @@ class VrpcAdapter {
|
|
|
547
587
|
return
|
|
548
588
|
}
|
|
549
589
|
const unwrapped = VrpcAdapter._unwrapArguments(json)
|
|
590
|
+
// special case: removing a listener the adapter does not hold (the
|
|
591
|
+
// client's subscription outlived a restart of this agent, or it sent
|
|
592
|
+
// no listener at all) - nothing to remove, and the instance's
|
|
593
|
+
// EventEmitter would throw on a listener that is not a function
|
|
594
|
+
if (
|
|
595
|
+
(json.f === 'off' || json.f === 'removeListener') &&
|
|
596
|
+
typeof unwrapped[1] !== 'function'
|
|
597
|
+
) {
|
|
598
|
+
json.r = true
|
|
599
|
+
return
|
|
600
|
+
}
|
|
550
601
|
// Check whether context is a registered class
|
|
551
602
|
const entry = VrpcAdapter._functionRegistry.get(json.c)
|
|
552
603
|
if (entry !== undefined) {
|
|
@@ -960,4 +1011,11 @@ VrpcAdapter._blackList = new Set([
|
|
|
960
1011
|
'__lookupSetter__'
|
|
961
1012
|
])
|
|
962
1013
|
|
|
1014
|
+
/**
|
|
1015
|
+
* The sender the agent uses for its own housekeeping calls (deleting the
|
|
1016
|
+
* isolated instances of a departed connection). A Symbol: no message from
|
|
1017
|
+
* the wire can carry it, so it can never be claimed remotely.
|
|
1018
|
+
*/
|
|
1019
|
+
VrpcAdapter.LOCAL_SENDER = Symbol('vrpc.local-sender')
|
|
1020
|
+
|
|
963
1021
|
module.exports = VrpcAdapter
|
package/vrpc/VrpcAgent.js
CHANGED
|
@@ -663,7 +663,12 @@ class VrpcAgent extends EventEmitter {
|
|
|
663
663
|
const entry = this._isolatedInstances.get(connectionId)
|
|
664
664
|
if (entry) {
|
|
665
665
|
entry.forEach(instanceId => {
|
|
666
|
-
const json = {
|
|
666
|
+
const json = {
|
|
667
|
+
f: '__delete__',
|
|
668
|
+
a: [instanceId],
|
|
669
|
+
r: null,
|
|
670
|
+
s: VrpcAdapter.LOCAL_SENDER
|
|
671
|
+
}
|
|
667
672
|
VrpcAdapter._call(json)
|
|
668
673
|
if (json.r) {
|
|
669
674
|
this._log.debug(`Auto-deleted isolated instance: ${instanceId}`)
|
|
@@ -718,7 +723,7 @@ class VrpcAgent extends EventEmitter {
|
|
|
718
723
|
const entryIsolated = this._isolatedInstances.get(clientId)
|
|
719
724
|
if (entryIsolated && entryIsolated.has(instanceId)) {
|
|
720
725
|
entryIsolated.delete(instanceId)
|
|
721
|
-
if (entryIsolated.
|
|
726
|
+
if (entryIsolated.size === 0) {
|
|
722
727
|
this._isolatedInstances.delete(clientId)
|
|
723
728
|
this._mqttUnsubscribe(`${clientId}/__clientInfo__`)
|
|
724
729
|
this._log.debug(`Stopped tracking lifetime of client: ${clientId}`)
|
|
@@ -730,7 +735,7 @@ class VrpcAgent extends EventEmitter {
|
|
|
730
735
|
if (v.has(instanceId)) {
|
|
731
736
|
found = true
|
|
732
737
|
v.delete(instanceId)
|
|
733
|
-
if (v.
|
|
738
|
+
if (v.size === 0) {
|
|
734
739
|
this._sharedInstances.delete(clientId)
|
|
735
740
|
this._mqttUnsubscribe(`${clientId}/__clientInfo__`)
|
|
736
741
|
this._log.debug(`Stopped tracking lifetime of client: ${clientId}`)
|
|
@@ -9,12 +9,10 @@ __\/\\\_______\/\\\__/\\\///////\\\___\/\\\/////////\\\____/\\\////////__
|
|
|
9
9
|
_______\//\\\_______\/\\\______\//\\\_\/\\\_______________\////\\\\\\\\\_
|
|
10
10
|
________\///________\///________\///__\///___________________\/////////__
|
|
11
11
|
|
|
12
|
-
|
|
13
12
|
Non-intrusively adapts code and provides access in form of asynchronous remote
|
|
14
13
|
procedure calls (RPC).
|
|
15
14
|
Author: Dr. Burkhard C. Heisen (https://github.com/heisenware/vrpc)
|
|
16
15
|
|
|
17
|
-
|
|
18
16
|
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
|
19
17
|
Copyright (c) 2018 - 2022 Dr. Burkhard C. Heisen <burkhard.heisen@heisenware.com>.
|
|
20
18
|
|
|
@@ -319,16 +317,45 @@ class VrpcAdapter {
|
|
|
319
317
|
return VrpcAdapter._mustTrackClient
|
|
320
318
|
}
|
|
321
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Refuses a request on an isolated instance that belongs to another
|
|
322
|
+
* connection. An isolated instance created over the wire remembers the
|
|
323
|
+
* connection that created it (the request's sender) and answers that
|
|
324
|
+
* connection alone; one the agent created itself has no owner and is
|
|
325
|
+
* open. The agent's own housekeeping passes as VrpcAdapter.LOCAL_SENDER,
|
|
326
|
+
* which no wire message can carry.
|
|
327
|
+
*/
|
|
328
|
+
static _assertOwner (entry, json, instanceId) {
|
|
329
|
+
if (!entry || !entry.isIsolated || entry.owner === undefined) return
|
|
330
|
+
if (json.s === VrpcAdapter.LOCAL_SENDER || json.s === entry.owner) return
|
|
331
|
+
throw new Error(
|
|
332
|
+
`Instance '${instanceId}' is isolated to another connection`
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
|
|
322
336
|
static _handleCreateIsolated (json) {
|
|
323
337
|
let instance
|
|
324
338
|
try {
|
|
325
339
|
const className = json.c
|
|
326
340
|
const [instanceId, ...args] = json.a
|
|
341
|
+
const existing = VrpcAdapter._instances.get(instanceId)
|
|
342
|
+
if (existing) {
|
|
343
|
+
// an id is never re-homed: a shared instance stays shared, an
|
|
344
|
+
// isolated one keeps its owner
|
|
345
|
+
if (!existing.isIsolated) {
|
|
346
|
+
throw new Error(`Instance '${instanceId}' exists as a shared instance`)
|
|
347
|
+
}
|
|
348
|
+
VrpcAdapter._assertOwner(existing, json, instanceId)
|
|
349
|
+
json.r = instanceId
|
|
350
|
+
return existing.instance
|
|
351
|
+
}
|
|
327
352
|
instance = VrpcAdapter._create(className, instanceId, ...args)
|
|
328
353
|
VrpcAdapter._instances.set(instanceId, {
|
|
329
354
|
instance,
|
|
330
355
|
className,
|
|
331
|
-
isIsolated: true
|
|
356
|
+
isIsolated: true,
|
|
357
|
+
// undefined for instances the agent creates itself (open)
|
|
358
|
+
owner: typeof json.s === 'string' ? json.s : undefined
|
|
332
359
|
})
|
|
333
360
|
VrpcAdapter._emitter.emit('create', {
|
|
334
361
|
args,
|
|
@@ -348,6 +375,10 @@ class VrpcAdapter {
|
|
|
348
375
|
try {
|
|
349
376
|
const className = json.c
|
|
350
377
|
const [instanceId, ...args] = json.a
|
|
378
|
+
const existing = VrpcAdapter._instances.get(instanceId)
|
|
379
|
+
if (existing && existing.isIsolated) {
|
|
380
|
+
throw new Error(`Instance '${instanceId}' exists as an isolated instance`)
|
|
381
|
+
}
|
|
351
382
|
instance = VrpcAdapter._create(className, instanceId, ...args)
|
|
352
383
|
VrpcAdapter._instances.set(instanceId, {
|
|
353
384
|
instance,
|
|
@@ -421,6 +452,7 @@ class VrpcAdapter {
|
|
|
421
452
|
static _handleDelete (json) {
|
|
422
453
|
try {
|
|
423
454
|
const instance = json.a[0] // first arg is instance to be deleted
|
|
455
|
+
VrpcAdapter._assertOwner(VrpcAdapter._instances.get(instance), json, instance)
|
|
424
456
|
VrpcAdapter._emitter.emit('beforeDelete', {
|
|
425
457
|
instance,
|
|
426
458
|
className: json.c
|
|
@@ -436,6 +468,14 @@ class VrpcAdapter {
|
|
|
436
468
|
}
|
|
437
469
|
|
|
438
470
|
static _handleCall (json) {
|
|
471
|
+
// an isolated instance answers its creating connection only
|
|
472
|
+
try {
|
|
473
|
+
VrpcAdapter._assertOwner(VrpcAdapter._instances.get(json.c), json, json.c)
|
|
474
|
+
} catch (err) {
|
|
475
|
+
const { message, cause } = err
|
|
476
|
+
json.e = { message, cause }
|
|
477
|
+
return
|
|
478
|
+
}
|
|
439
479
|
// special case: removeAllListeners
|
|
440
480
|
if (json.f === 'removeAllListeners') {
|
|
441
481
|
VrpcAdapter._removeAllListeners(json.a[0], json.s, json.c)
|
|
@@ -877,4 +917,11 @@ VrpcAdapter._blackList = new Set([
|
|
|
877
917
|
'__lookupSetter__'
|
|
878
918
|
])
|
|
879
919
|
|
|
920
|
+
/**
|
|
921
|
+
* The sender the agent uses for its own housekeeping calls (deleting the
|
|
922
|
+
* isolated instances of a departed connection). A Symbol: no message from
|
|
923
|
+
* the wire can carry it, so it can never be claimed remotely.
|
|
924
|
+
*/
|
|
925
|
+
VrpcAdapter.LOCAL_SENDER = Symbol('vrpc.local-sender')
|
|
926
|
+
|
|
880
927
|
module.exports = VrpcAdapter
|
package/vrpc/VrpcBrowserAgent.js
CHANGED
|
@@ -504,7 +504,12 @@ class VrpcAgent extends EventEmitter {
|
|
|
504
504
|
const entry = this._isolatedInstances.get(connectionId)
|
|
505
505
|
if (entry) {
|
|
506
506
|
entry.forEach(instanceId => {
|
|
507
|
-
const json = {
|
|
507
|
+
const json = {
|
|
508
|
+
f: '__delete__',
|
|
509
|
+
a: [instanceId],
|
|
510
|
+
r: null,
|
|
511
|
+
s: VrpcAdapter.LOCAL_SENDER
|
|
512
|
+
}
|
|
508
513
|
VrpcAdapter._call(json)
|
|
509
514
|
if (json.r) {
|
|
510
515
|
this._log.debug(`Auto-deleted isolated instance: ${instanceId}`)
|
|
@@ -559,7 +564,7 @@ class VrpcAgent extends EventEmitter {
|
|
|
559
564
|
const entryIsolated = this._isolatedInstances.get(clientId)
|
|
560
565
|
if (entryIsolated && entryIsolated.has(instanceId)) {
|
|
561
566
|
entryIsolated.delete(instanceId)
|
|
562
|
-
if (entryIsolated.
|
|
567
|
+
if (entryIsolated.size === 0) {
|
|
563
568
|
this._isolatedInstances.delete(clientId)
|
|
564
569
|
this._mqttUnsubscribe(`${clientId}/__clientInfo__`)
|
|
565
570
|
this._log.debug(`Stopped tracking lifetime of client: ${clientId}`)
|
|
@@ -571,7 +576,7 @@ class VrpcAgent extends EventEmitter {
|
|
|
571
576
|
if (v.has(instanceId)) {
|
|
572
577
|
found = true
|
|
573
578
|
v.delete(instanceId)
|
|
574
|
-
if (v.
|
|
579
|
+
if (v.size === 0) {
|
|
575
580
|
this._sharedInstances.delete(clientId)
|
|
576
581
|
this._mqttUnsubscribe(`${clientId}/__clientInfo__`)
|
|
577
582
|
this._log.debug(`Stopped tracking lifetime of client: ${clientId}`)
|
package/vrpc/VrpcClient.js
CHANGED
|
@@ -9,12 +9,10 @@ __\/\\\_______\/\\\__/\\\///////\\\___\/\\\/////////\\\____/\\\////////__
|
|
|
9
9
|
_______\//\\\_______\/\\\______\//\\\_\/\\\_______________\////\\\\\\\\\_
|
|
10
10
|
________\///________\///________\///__\///___________________\/////////__
|
|
11
11
|
|
|
12
|
-
|
|
13
12
|
Non-intrusively adapts code and provides access in form of asynchronous remote
|
|
14
13
|
procedure calls (RPC).
|
|
15
14
|
Author: Dr. Burkhard C. Heisen (https://github.com/heisenware/vrpc)
|
|
16
15
|
|
|
17
|
-
|
|
18
16
|
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
|
19
17
|
Copyright (c) 2018 - 2022 Dr. Burkhard C. Heisen <burkhard.heisen@heisenware.com>.
|
|
20
18
|
|
|
@@ -113,6 +111,18 @@ class VrpcClient extends EventEmitter {
|
|
|
113
111
|
'The identity must NOT contain any of those characters: "+", "/", "#", "$"'
|
|
114
112
|
)
|
|
115
113
|
}
|
|
114
|
+
// mqtt client id sanity check: it becomes the second segment of every
|
|
115
|
+
// topic this client receives on, so it must be one plain segment
|
|
116
|
+
if (mqttClientId !== null && mqttClientId !== undefined) {
|
|
117
|
+
if (typeof mqttClientId !== 'string' || mqttClientId.length === 0) {
|
|
118
|
+
throw new Error('The mqttClientId must be a non-empty string')
|
|
119
|
+
}
|
|
120
|
+
if (mqttClientId.match(/[+/#]/)) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
'The mqttClientId must NOT contain any of those characters: "+", "/", "#"'
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
116
126
|
this._token = token
|
|
117
127
|
this._username = username
|
|
118
128
|
this._password = password
|
|
@@ -128,6 +138,11 @@ class VrpcClient extends EventEmitter {
|
|
|
128
138
|
this._instance = nanoid(8)
|
|
129
139
|
this._mqttClientId = mqttClientId || this._createMqttClientId()
|
|
130
140
|
this._vrpcClientId = this._createVrpcClientId()
|
|
141
|
+
// the part of the connection id nobody else can learn: a broker that
|
|
142
|
+
// confines a client to topics under its own mqtt client id keeps
|
|
143
|
+
// every response, event and presence message of this connection
|
|
144
|
+
// private, and a sender field nobody can read cannot be forged
|
|
145
|
+
this._connectionSecret = nanoid(16)
|
|
131
146
|
this._vrpcConnectionId = this._createVrpcConnectionId()
|
|
132
147
|
this._agents = {}
|
|
133
148
|
this._eventEmitter = new EventEmitter()
|
|
@@ -175,7 +190,12 @@ class VrpcClient extends EventEmitter {
|
|
|
175
190
|
* have distinct connection ids. Agents key all their bookkeeping by it
|
|
176
191
|
* (response routing, event listeners, isolated instances, presence), so
|
|
177
192
|
* one connection ending never disturbs the others of the same identity.
|
|
178
|
-
*
|
|
193
|
+
*
|
|
194
|
+
* It reads `<domain>/<mqttClientId>/<secret>`: the mqtt client id is
|
|
195
|
+
* what the broker knows this connection by, so a broker can confine the
|
|
196
|
+
* client to its own topics, and the secret is random per connection, so
|
|
197
|
+
* nobody who cannot read those topics can impersonate the connection
|
|
198
|
+
* towards an agent. Treat it as a credential: do not log or share it.
|
|
179
199
|
*
|
|
180
200
|
* @returns {String} connectionId
|
|
181
201
|
*/
|
|
@@ -866,11 +886,10 @@ class VrpcClient extends EventEmitter {
|
|
|
866
886
|
}
|
|
867
887
|
|
|
868
888
|
_createVrpcConnectionId () {
|
|
869
|
-
//
|
|
870
|
-
//
|
|
871
|
-
//
|
|
872
|
-
|
|
873
|
-
return `${this._vrpcClientId}:${this._instance}`
|
|
889
|
+
// Three segments, always: agents (this one, the C++ and the Python
|
|
890
|
+
// ports) recognise a presence message by `__clientInfo__` arriving as
|
|
891
|
+
// the fourth topic token, so the layout is part of the wire format.
|
|
892
|
+
return `${this._domain}/${this._mqttClientId}/${this._connectionSecret}`
|
|
874
893
|
}
|
|
875
894
|
|
|
876
895
|
_mqttPublish (topic, message, options) {
|
|
@@ -1225,15 +1244,15 @@ class VrpcClient extends EventEmitter {
|
|
|
1225
1244
|
return new Promise((resolve, reject) => {
|
|
1226
1245
|
const handler =
|
|
1227
1246
|
timer =>
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1247
|
+
(instances, { agent, className }) => {
|
|
1248
|
+
if (instances.includes(instance)) {
|
|
1249
|
+
if (options.agent && agent !== options.agent) return
|
|
1250
|
+
if (options.className && className !== options.className) return
|
|
1251
|
+
clearTimeout(timer)
|
|
1252
|
+
this.removeListener('instanceNew', handler)
|
|
1253
|
+
resolve({ agent, className, instance })
|
|
1254
|
+
}
|
|
1235
1255
|
}
|
|
1236
|
-
}
|
|
1237
1256
|
const timer = setTimeout(() => {
|
|
1238
1257
|
this.removeListener('instanceNew', handler(timer))
|
|
1239
1258
|
const msg = `Could not find instance: ${instance} (> ${this._timeout} ms)`
|
|
@@ -1284,6 +1303,17 @@ class VrpcClient extends EventEmitter {
|
|
|
1284
1303
|
isContinuousRemover
|
|
1285
1304
|
) {
|
|
1286
1305
|
const id = await this._offRemoteEvent({ callback })
|
|
1306
|
+
if (
|
|
1307
|
+
!id &&
|
|
1308
|
+
(functionName === 'off' || functionName === 'removeListener')
|
|
1309
|
+
) {
|
|
1310
|
+
// Nothing of ours to remove - the subscription was already
|
|
1311
|
+
// dropped (the instance or its agent went away and the cache
|
|
1312
|
+
// was cleared, or the listener was never registered). Sending
|
|
1313
|
+
// the call anyway would hand the agent a null listener, which
|
|
1314
|
+
// its EventEmitter rejects. Handled locally: nothing to do.
|
|
1315
|
+
return null
|
|
1316
|
+
}
|
|
1287
1317
|
wrapped.push(id || null)
|
|
1288
1318
|
continue
|
|
1289
1319
|
} else {
|
|
@@ -1443,8 +1473,8 @@ class VrpcClient extends EventEmitter {
|
|
|
1443
1473
|
return (
|
|
1444
1474
|
variable &&
|
|
1445
1475
|
typeof variable === 'object' &&
|
|
1446
|
-
|
|
1447
|
-
|
|
1476
|
+
Object.prototype.hasOwnProperty.call(variable, 'emitter') &&
|
|
1477
|
+
Object.prototype.hasOwnProperty.call(variable, 'event') &&
|
|
1448
1478
|
typeof variable.emitter === 'object' &&
|
|
1449
1479
|
typeof variable.emitter.emit === 'function'
|
|
1450
1480
|
)
|