@scrypted/server 0.0.106 → 0.0.110

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.

Potentially problematic release.


This version of @scrypted/server might be problematic. Click here for more details.

Files changed (57) hide show
  1. package/dist/event-registry.js +2 -2
  2. package/dist/event-registry.js.map +1 -1
  3. package/dist/http-interfaces.js +2 -2
  4. package/dist/http-interfaces.js.map +1 -1
  5. package/dist/plugin/listen-zero.js +13 -1
  6. package/dist/plugin/listen-zero.js.map +1 -1
  7. package/dist/plugin/plugin-api.js +2 -2
  8. package/dist/plugin/plugin-api.js.map +1 -1
  9. package/dist/plugin/plugin-device.js +2 -1
  10. package/dist/plugin/plugin-device.js.map +1 -1
  11. package/dist/plugin/plugin-host-api.js +8 -7
  12. package/dist/plugin/plugin-host-api.js.map +1 -1
  13. package/dist/plugin/plugin-host.js +36 -87
  14. package/dist/plugin/plugin-host.js.map +1 -1
  15. package/dist/plugin/plugin-http.js +100 -0
  16. package/dist/plugin/plugin-http.js.map +1 -0
  17. package/dist/plugin/plugin-lazy-remote.js +73 -0
  18. package/dist/plugin/plugin-lazy-remote.js.map +1 -0
  19. package/dist/plugin/plugin-npm-dependencies.js +16 -16
  20. package/dist/plugin/plugin-npm-dependencies.js.map +1 -1
  21. package/dist/plugin/plugin-remote-websocket.js +40 -34
  22. package/dist/plugin/plugin-remote-websocket.js.map +1 -1
  23. package/dist/plugin/plugin-remote.js +35 -26
  24. package/dist/plugin/plugin-remote.js.map +1 -1
  25. package/dist/plugin/system.js +11 -3
  26. package/dist/plugin/system.js.map +1 -1
  27. package/dist/rpc.js +57 -27
  28. package/dist/rpc.js.map +1 -1
  29. package/dist/runtime.js +45 -111
  30. package/dist/runtime.js.map +1 -1
  31. package/dist/scrypted-main.js +3 -0
  32. package/dist/scrypted-main.js.map +1 -1
  33. package/dist/services/plugin.js +2 -2
  34. package/dist/services/plugin.js.map +1 -1
  35. package/dist/state.js +2 -8
  36. package/dist/state.js.map +1 -1
  37. package/package.json +2 -2
  38. package/python/plugin-remote.py +4 -4
  39. package/python/rpc.py +68 -26
  40. package/src/event-registry.ts +5 -5
  41. package/src/http-interfaces.ts +3 -3
  42. package/src/plugin/listen-zero.ts +13 -0
  43. package/src/plugin/plugin-api.ts +5 -5
  44. package/src/plugin/plugin-device.ts +3 -2
  45. package/src/plugin/plugin-host-api.ts +9 -9
  46. package/src/plugin/plugin-host.ts +40 -95
  47. package/src/plugin/plugin-http.ts +117 -0
  48. package/src/plugin/plugin-lazy-remote.ts +70 -0
  49. package/src/plugin/plugin-npm-dependencies.ts +19 -18
  50. package/src/plugin/plugin-remote-websocket.ts +55 -60
  51. package/src/plugin/plugin-remote.ts +45 -38
  52. package/src/plugin/system.ts +15 -6
  53. package/src/rpc.ts +66 -26
  54. package/src/runtime.ts +55 -128
  55. package/src/scrypted-main.ts +4 -0
  56. package/src/services/plugin.ts +2 -2
  57. package/src/state.ts +4 -12
@@ -6,8 +6,7 @@ import { PluginAPI, PluginLogger, PluginRemote, PluginRemoteLoadZipOptions } fro
6
6
  import { SystemManagerImpl } from './system';
7
7
  import { RpcPeer, RPCResultError, PROPERTY_PROXY_ONEWAY_METHODS, PROPERTY_JSON_DISABLE_SERIALIZATION } from '../rpc';
8
8
  import { BufferSerializer } from './buffer-serializer';
9
- import { EventEmitter } from 'events';
10
- import { createWebSocketClass } from './plugin-remote-websocket';
9
+ import { createWebSocketClass, WebSocketConnectCallbacks, WebSocketMethods } from './plugin-remote-websocket';
11
10
 
12
11
  class DeviceLogger implements Logger {
13
12
  nativeId: ScryptedNativeId;
@@ -262,14 +261,7 @@ class StorageImpl implements Storage {
262
261
  }
263
262
  }
264
263
 
265
- interface WebSocketCallbacks {
266
- end: any;
267
- error: any;
268
- data: any;
269
- }
270
-
271
-
272
- export async function setupPluginRemote(peer: RpcPeer, api: PluginAPI, pluginId: string): Promise<PluginRemote> {
264
+ export async function setupPluginRemote(peer: RpcPeer, api: PluginAPI, pluginId: string, getSystemState: () => { [id: string]: { [property: string]: SystemDeviceState } }): Promise<PluginRemote> {
273
265
  try {
274
266
  // the host/remote connection can be from server to plugin (node to node),
275
267
  // core plugin to web (node to browser).
@@ -277,16 +269,46 @@ export async function setupPluginRemote(peer: RpcPeer, api: PluginAPI, pluginId:
277
269
  // but in plugin-host, mark Buffer as transport safe.
278
270
  peer.addSerializer(Buffer, 'Buffer', new BufferSerializer());
279
271
  const getRemote = await peer.getParam('getRemote');
280
- return await getRemote(api, pluginId);
272
+ const remote = await getRemote(api, pluginId);
273
+
274
+ await remote.setSystemState(getSystemState());
275
+ api.listen((id, eventDetails, eventData) => {
276
+ // ScryptedDevice events will be handled specially and repropagated by the remote.
277
+ if (eventDetails.eventInterface === ScryptedInterface.ScryptedDevice) {
278
+ if (eventDetails.property === ScryptedInterfaceProperty.id) {
279
+ // a change on the id property means device was deleted
280
+ remote.updateDeviceState(eventData, undefined);
281
+ }
282
+ else {
283
+ // a change on anything else is a descriptor update
284
+ remote.updateDeviceState(id, getSystemState()[id]);
285
+ }
286
+ return;
287
+ }
288
+
289
+ if (eventDetails.property) {
290
+ remote.notify(id, eventDetails.eventTime, eventDetails.eventInterface, eventDetails.property, getSystemState()[id]?.[eventDetails.property], eventDetails.changed);
291
+ }
292
+ else {
293
+ remote.notify(id, eventDetails.eventTime, eventDetails.eventInterface, eventDetails.property, eventData, eventDetails.changed);
294
+ }
295
+ });
296
+
297
+ return remote;
281
298
  }
282
299
  catch (e) {
283
300
  throw new RPCResultError(peer, 'error while retrieving PluginRemote', e);
284
301
  }
285
302
  }
286
303
 
304
+ export interface WebSocketCustomHandler {
305
+ id: string,
306
+ methods: WebSocketMethods;
307
+ }
308
+
287
309
  export interface PluginRemoteAttachOptions {
288
310
  createMediaManager?: (systemManager: SystemManager) => Promise<MediaManager>;
289
- getServicePort?: (name: string) => Promise<number>;
311
+ getServicePort?: (name: string, ...args: any[]) => Promise<number>;
290
312
  getDeviceConsole?: (nativeId?: ScryptedNativeId) => Console;
291
313
  getPluginConsole?: () => Console;
292
314
  getMixinConsole?: (id: string, nativeId?: ScryptedNativeId) => Console;
@@ -309,8 +331,9 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
309
331
  const systemManager = new SystemManagerImpl();
310
332
  const deviceManager = new DeviceManagerImpl(systemManager, getDeviceConsole, getMixinConsole);
311
333
  const endpointManager = new EndpointManagerImpl();
312
- const ioSockets: { [id: string]: WebSocketCallbacks } = {};
313
334
  const mediaManager = await api.getMediaManager() || await createMediaManager(systemManager);
335
+ peer.params['mediaManager'] = mediaManager;
336
+ const ioSockets: { [id: string]: WebSocketConnectCallbacks } = {};
314
337
 
315
338
  systemManager.api = api;
316
339
  deviceManager.api = api;
@@ -383,11 +406,11 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
383
406
  async updateDeviceState(id: string, state: { [property: string]: SystemDeviceState }) {
384
407
  if (!state) {
385
408
  delete systemManager.state[id];
386
- systemManager.events.notify(id, Date.now(), ScryptedInterface.ScryptedDevice, ScryptedInterfaceProperty.id, id, true);
409
+ systemManager.events.notify(undefined, undefined, ScryptedInterface.ScryptedDevice, ScryptedInterfaceProperty.id, id, true);
387
410
  }
388
411
  else {
389
412
  systemManager.state[id] = state;
390
- systemManager.events.notify(id, Date.now(), ScryptedInterface.ScryptedDevice, undefined, undefined, true);
413
+ systemManager.events.notify(id, undefined, ScryptedInterface.ScryptedDevice, undefined, state, true);
391
414
  }
392
415
  },
393
416
 
@@ -432,32 +455,16 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
432
455
  volume.writeFileSync(name, entry.getData());
433
456
  }
434
457
 
435
- function websocketConnect(url: string, protocols: any, connect: any, end: any, error: any, data: any) {
436
- if (url.startsWith('io://')) {
437
- const id = url.substring('io://'.length);
438
-
439
- ioSockets[id] = {
440
- data,
441
- error,
442
- end
443
- };
444
-
445
- connect(undefined, {
446
- close: () => api.ioClose(id),
447
- }, (message: string) => api.ioSend(id, message));
448
- }
449
- else if (url.startsWith('ws://')) {
450
- const id = url.substring('ws://'.length);
458
+ function websocketConnect(url: string, protocols: any, callbacks: WebSocketConnectCallbacks) {
459
+ if (url.startsWith('io://') || url.startsWith('ws://')) {
460
+ const id = url.substring('xx://'.length);
451
461
 
452
- ioSockets[id] = {
453
- data,
454
- error,
455
- end
456
- };
462
+ ioSockets[id] = callbacks;
457
463
 
458
- connect(undefined, {
464
+ callbacks.connect(undefined, {
459
465
  close: () => api.ioClose(id),
460
- }, (message: string) => api.ioSend(id, message));
466
+ send: (message: string) => api.ioSend(id, message),
467
+ });
461
468
  }
462
469
  else {
463
470
  throw new Error('unsupported websocket');
@@ -1,6 +1,6 @@
1
1
  import { EventListenerOptions, EventDetails, EventListenerRegister, ScryptedDevice, ScryptedInterface, ScryptedInterfaceDescriptors, SystemDeviceState, SystemManager, ScryptedInterfaceProperty, ScryptedDeviceType, Logger } from "@scrypted/sdk/types";
2
2
  import { PluginAPI } from "./plugin-api";
3
- import { handleFunctionInvocations } from '../rpc';
3
+ import { handleFunctionInvocations, PROPERTY_PROXY_ONEWAY_METHODS } from '../rpc';
4
4
  import { EventRegistry } from "../event-registry";
5
5
  import { allInterfaceProperties, isValidInterfaceMethod } from "./descriptor";
6
6
 
@@ -56,7 +56,7 @@ class DeviceProxyHandler implements ProxyHandler<any>, ScryptedDevice {
56
56
  return (this.device as any)[method](...argArray);
57
57
  }
58
58
 
59
- listen(event: string | EventListenerOptions, callback: (eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: object) => void): EventListenerRegister {
59
+ listen(event: string | EventListenerOptions, callback: (eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: any) => void): EventListenerRegister {
60
60
  return this.systemManager.listenDevice(this.id, event, callback);
61
61
  }
62
62
 
@@ -92,6 +92,15 @@ class EventListenerRegisterImpl implements EventListenerRegister {
92
92
  }
93
93
  }
94
94
 
95
+ function makeOneWayCallback<T>(input: T): T {
96
+ const f: any = input;
97
+ const oneways: string[] = f[PROPERTY_PROXY_ONEWAY_METHODS] || [];
98
+ if (!oneways.includes(null))
99
+ oneways.push(null);
100
+ f[PROPERTY_PROXY_ONEWAY_METHODS] = oneways;
101
+ return input;
102
+ }
103
+
95
104
  export class SystemManagerImpl implements SystemManager {
96
105
  api: PluginAPI;
97
106
  state: {[id: string]: {[property: string]: SystemDeviceState}};
@@ -122,10 +131,10 @@ export class SystemManagerImpl implements SystemManager {
122
131
  return this.getDeviceById(id);
123
132
  }
124
133
  }
125
- listen(EventListener: (eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: object) => void): EventListenerRegister {
126
- return this.events.listen((id, eventDetails, eventData) => EventListener(this.getDeviceById(id), eventDetails, eventData));
134
+ listen(callback: (eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: any) => void): EventListenerRegister {
135
+ return this.events.listen(makeOneWayCallback((id, eventDetails, eventData) => callback(this.getDeviceById(id), eventDetails, eventData)));
127
136
  }
128
- listenDevice(id: string, options: string | EventListenerOptions, callback: (eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: object) => void): EventListenerRegister {
137
+ listenDevice(id: string, options: string | EventListenerOptions, callback: (eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: any) => void): EventListenerRegister {
129
138
  let { event, watch } = (options || {}) as EventListenerOptions;
130
139
  if (!event && typeof options === 'string')
131
140
  event = options as string;
@@ -136,7 +145,7 @@ export class SystemManagerImpl implements SystemManager {
136
145
  if (watch)
137
146
  return this.events.listenDevice(id, event, (eventDetails, eventData) => callback(this.getDeviceById(id), eventDetails, eventData));
138
147
 
139
- return new EventListenerRegisterImpl(this.api.listenDevice(id, options, (eventDetails, eventData) => callback(this.getDeviceById(id), eventDetails, eventData)))
148
+ return new EventListenerRegisterImpl(this.api.listenDevice(id, options, makeOneWayCallback((eventDetails, eventData) => callback(this.getDeviceById(id), eventDetails, eventData))));
140
149
  }
141
150
 
142
151
  async removeDevice(id: string) {
package/src/rpc.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import vm from 'vm';
2
2
 
3
+ const finalizerIdSymbol = Symbol('rpcFinalizerId');
4
+
3
5
  function getDefaultTransportSafeArgumentTypes() {
4
6
  const jsonSerializable = new Set<string>();
5
7
  jsonSerializable.add(Number.name);
@@ -40,6 +42,7 @@ interface RpcOob extends RpcMessage {
40
42
 
41
43
  interface RpcRemoteProxyValue {
42
44
  __remote_proxy_id: string;
45
+ __remote_proxy_finalizer_id: string;
43
46
  __remote_constructor_name: string;
44
47
  __remote_proxy_props: any;
45
48
  __remote_proxy_oneway_methods: string[];
@@ -52,6 +55,7 @@ interface RpcLocalProxyValue {
52
55
 
53
56
  interface RpcFinalize extends RpcMessage {
54
57
  __local_proxy_id: string;
58
+ __local_proxy_finalizer_id: string;
55
59
  }
56
60
 
57
61
  interface Deferred {
@@ -78,20 +82,17 @@ export const PROPERTY_PROXY_PROPERTIES = '__proxy_props';
78
82
  export const PROPERTY_JSON_COPY_SERIALIZE_CHILDREN = '__json_copy_serialize_children';
79
83
 
80
84
  class RpcProxy implements ProxyHandler<any> {
85
+
81
86
  constructor(public peer: RpcPeer,
82
- public id: string,
87
+ public entry: LocalProxiedEntry,
83
88
  public constructorName: string,
84
89
  public proxyProps: any,
85
90
  public proxyOneWayMethods: string[]) {
86
- this.peer = peer;
87
- this.id = id;
88
- this.constructorName = constructorName;
89
- this.proxyProps = proxyProps;
90
91
  }
91
92
 
92
93
  get(target: any, p: PropertyKey, receiver: any): any {
93
94
  if (p === '__proxy_id')
94
- return this.id;
95
+ return this.entry.id;
95
96
  if (p === '__proxy_constructor')
96
97
  return this.constructorName;
97
98
  if (p === '__proxy_peer')
@@ -114,8 +115,17 @@ class RpcProxy implements ProxyHandler<any> {
114
115
  return new Proxy(() => p, this);
115
116
  }
116
117
 
118
+ set(target: any, p: string | symbol, value: any, receiver: any): boolean {
119
+ if (p === finalizerIdSymbol)
120
+ this.entry.finalizerId = value;
121
+ return true;
122
+ }
123
+
117
124
  apply(target: any, thisArg: any, argArray?: any): any {
118
- const method = target();
125
+ // rpc objects can be functions. if the function is a oneway method,
126
+ // it will have a null in the oneway method list. this is because
127
+ // undefined is not JSON serializable.
128
+ const method = target() || null;
119
129
  const args: any[] = [];
120
130
  for (const arg of (argArray || [])) {
121
131
  args.push(this.peer.serialize(arg));
@@ -124,7 +134,7 @@ class RpcProxy implements ProxyHandler<any> {
124
134
  const rpcApply: RpcApply = {
125
135
  type: "apply",
126
136
  id: undefined,
127
- proxyId: this.id,
137
+ proxyId: this.entry.id,
128
138
  args,
129
139
  method,
130
140
  };
@@ -187,16 +197,21 @@ export interface RpcSerializer {
187
197
  deserialize(serialized: any): any;
188
198
  }
189
199
 
200
+ interface LocalProxiedEntry {
201
+ id: string;
202
+ finalizerId: string;
203
+ }
204
+
190
205
  export class RpcPeer {
191
206
  idCounter = 1;
192
207
  onOob: (oob: any) => void;
193
208
  params: { [name: string]: any } = {};
194
209
  pendingResults: { [id: string]: Deferred } = {};
195
210
  proxyCounter = 1;
196
- localProxied = new Map<any, string>();
211
+ localProxied = new Map<any, LocalProxiedEntry>();
197
212
  localProxyMap: { [id: string]: any } = {};
198
213
  remoteWeakProxies: { [id: string]: WeakRef<any> } = {};
199
- finalizers = new FinalizationRegistry(id => this.finalize(id as string));
214
+ finalizers = new FinalizationRegistry(entry => this.finalize(entry as LocalProxiedEntry));
200
215
  nameDeserializerMap = new Map<string, RpcSerializer>();
201
216
  constructorSerializerMap = new Map<string, string>();
202
217
  transportSafeArgumentTypes = getDefaultTransportSafeArgumentTypes();
@@ -238,10 +253,11 @@ export class RpcPeer {
238
253
  this.constructorSerializerMap.set(ctr, name);
239
254
  }
240
255
 
241
- finalize(id: string) {
242
- delete this.remoteWeakProxies[id];
256
+ finalize(entry: LocalProxiedEntry) {
257
+ delete this.remoteWeakProxies[entry.id];
243
258
  const rpcFinalize: RpcFinalize = {
244
- __local_proxy_id: id,
259
+ __local_proxy_id: entry.id,
260
+ __local_proxy_finalizer_id: entry.finalizerId,
245
261
  type: 'finalize',
246
262
  }
247
263
  this.send(rpcFinalize);
@@ -294,9 +310,12 @@ export class RpcPeer {
294
310
  return ret;
295
311
  }
296
312
 
297
- const { __remote_proxy_id, __local_proxy_id, __remote_constructor_name, __serialized_value, __remote_proxy_props, __remote_proxy_oneway_methods } = value;
313
+ const { __remote_proxy_id, __remote_proxy_finalizer_id, __local_proxy_id, __remote_constructor_name, __serialized_value, __remote_proxy_props, __remote_proxy_oneway_methods } = value;
298
314
  if (__remote_proxy_id) {
299
- const proxy = this.remoteWeakProxies[__remote_proxy_id]?.deref() || this.newProxy(__remote_proxy_id, __remote_constructor_name, __remote_proxy_props, __remote_proxy_oneway_methods);
315
+ let proxy = this.remoteWeakProxies[__remote_proxy_id]?.deref();
316
+ if (!proxy)
317
+ proxy = this.newProxy(__remote_proxy_id, __remote_constructor_name, __remote_proxy_props, __remote_proxy_oneway_methods);
318
+ proxy[finalizerIdSymbol] = __remote_proxy_finalizer_id;
300
319
  return proxy;
301
320
  }
302
321
 
@@ -329,10 +348,13 @@ export class RpcPeer {
329
348
 
330
349
  let __remote_constructor_name = value.__proxy_constructor || value.constructor?.name?.toString();
331
350
 
332
- let proxyId = this.localProxied.get(value);
333
- if (proxyId) {
351
+ let proxiedEntry = this.localProxied.get(value);
352
+ if (proxiedEntry) {
353
+ const __remote_proxy_finalizer_id = (this.proxyCounter++).toString();
354
+ proxiedEntry.finalizerId = __remote_proxy_finalizer_id;
334
355
  const ret: RpcRemoteProxyValue = {
335
- __remote_proxy_id: proxyId,
356
+ __remote_proxy_id: proxiedEntry.id,
357
+ __remote_proxy_finalizer_id,
336
358
  __remote_constructor_name,
337
359
  __remote_proxy_props: value?.[PROPERTY_PROXY_PROPERTIES],
338
360
  __remote_proxy_oneway_methods: value?.[PROPERTY_PROXY_ONEWAY_METHODS],
@@ -355,6 +377,7 @@ export class RpcPeer {
355
377
  const serialized = serializer.serialize(value);
356
378
  const ret: RpcRemoteProxyValue = {
357
379
  __remote_proxy_id: undefined,
380
+ __remote_proxy_finalizer_id: undefined,
358
381
  __remote_constructor_name,
359
382
  __remote_proxy_props: value?.[PROPERTY_PROXY_PROPERTIES],
360
383
  __remote_proxy_oneway_methods: value?.[PROPERTY_PROXY_ONEWAY_METHODS],
@@ -363,12 +386,17 @@ export class RpcPeer {
363
386
  return ret;
364
387
  }
365
388
 
366
- proxyId = (this.proxyCounter++).toString();
367
- this.localProxied.set(value, proxyId);
368
- this.localProxyMap[proxyId] = value;
389
+ const __remote_proxy_id = (this.proxyCounter++).toString();
390
+ proxiedEntry = {
391
+ id: __remote_proxy_id,
392
+ finalizerId: __remote_proxy_id,
393
+ };
394
+ this.localProxied.set(value, proxiedEntry);
395
+ this.localProxyMap[__remote_proxy_id] = value;
369
396
 
370
397
  const ret: RpcRemoteProxyValue = {
371
- __remote_proxy_id: proxyId,
398
+ __remote_proxy_id,
399
+ __remote_proxy_finalizer_id: __remote_proxy_id,
372
400
  __remote_constructor_name,
373
401
  __remote_proxy_props: value?.[PROPERTY_PROXY_PROPERTIES],
374
402
  __remote_proxy_oneway_methods: value?.[PROPERTY_PROXY_ONEWAY_METHODS],
@@ -378,12 +406,16 @@ export class RpcPeer {
378
406
  }
379
407
 
380
408
  newProxy(proxyId: string, proxyConstructorName: string, proxyProps: any, proxyOneWayMethods: string[]) {
381
- const rpc = new RpcProxy(this, proxyId, proxyConstructorName, proxyProps, proxyOneWayMethods);
409
+ const localProxiedEntry: LocalProxiedEntry = {
410
+ id: proxyId,
411
+ finalizerId: undefined,
412
+ }
413
+ const rpc = new RpcProxy(this, localProxiedEntry, proxyConstructorName, proxyProps, proxyOneWayMethods);
382
414
  const target = proxyConstructorName === 'Function' || proxyConstructorName === 'AsyncFunction' ? function () { } : rpc;
383
415
  const proxy = new Proxy(target, rpc);
384
416
  const weakref = new WeakRef(proxy);
385
417
  this.remoteWeakProxies[proxyId] = weakref;
386
- this.finalizers.register(rpc, proxyId);
418
+ this.finalizers.register(rpc, localProxiedEntry);
387
419
  global.gc?.();
388
420
  return proxy;
389
421
  }
@@ -460,8 +492,16 @@ export class RpcPeer {
460
492
  case 'finalize': {
461
493
  const rpcFinalize = message as RpcFinalize;
462
494
  const local = this.localProxyMap[rpcFinalize.__local_proxy_id];
463
- delete this.localProxyMap[rpcFinalize.__local_proxy_id];
464
- this.localProxied.delete(local);
495
+ if (local) {
496
+ const localProxiedEntry = this.localProxied.get(local);
497
+ // if a finalizer id is specified, it must match.
498
+ if (rpcFinalize.__local_proxy_finalizer_id && rpcFinalize.__local_proxy_finalizer_id !== localProxiedEntry?.finalizerId) {
499
+ console.error(this.selfName, this.peerName, 'finalizer mismatch')
500
+ break;
501
+ }
502
+ delete this.localProxyMap[rpcFinalize.__local_proxy_id];
503
+ this.localProxied.delete(local);
504
+ }
465
505
  break;
466
506
  }
467
507
  case 'oob': {