chrome-in-iframe 2.0.1 → 2.1.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/dist/index.js CHANGED
@@ -1,6 +1,16 @@
1
1
  import { nanoid } from 'nanoid';
2
2
  import { LRUCache } from 'lru-cache';
3
3
 
4
+ const TRANSPORT_DETACHED = Symbol.for('chrome-in-iframe.transport-detached');
5
+ function isTransportDetachedError(err) {
6
+ return err instanceof Error && err[TRANSPORT_DETACHED] === true;
7
+ }
8
+ function createTransportDetachedError(message) {
9
+ const err = new Error(message);
10
+ Object.defineProperty(err, TRANSPORT_DETACHED, { value: true, enumerable: false });
11
+ return err;
12
+ }
13
+
4
14
  const PREFIX = 'chrome-in-iframe';
5
15
  const defaultLogger = (scope, ...args) => {
6
16
  if (typeof console !== 'undefined' && typeof console.warn === 'function') {
@@ -39,6 +49,34 @@ function getWellKnownSymbolName(value) {
39
49
  function getWellKnownSymbol(name) {
40
50
  return WELL_KNOWN_SYMBOLS[name];
41
51
  }
52
+ function encodeSymbolToken(value, context) {
53
+ const globalKey = Symbol.keyFor(value);
54
+ if (globalKey)
55
+ return `global:${globalKey}`;
56
+ const wellKnownName = getWellKnownSymbolName(value);
57
+ if (wellKnownName)
58
+ return `wellKnown:${wellKnownName}`;
59
+ throw new TypeError(context ? `Cannot serialize non-global symbol ${context}` : 'Cannot serialize non-global symbol');
60
+ }
61
+ function decodeSymbolToken(value, context) {
62
+ if (value.startsWith('global:')) {
63
+ return Symbol.for(value.slice('global:'.length));
64
+ }
65
+ if (value.startsWith('wellKnown:')) {
66
+ const symbol = getWellKnownSymbol(value.slice('wellKnown:'.length));
67
+ if (symbol)
68
+ return symbol;
69
+ }
70
+ throw new TypeError(context ? `Cannot deserialize symbol ${context}` : 'Cannot deserialize symbol');
71
+ }
72
+ function setOwnProperty(target, key, value) {
73
+ Object.defineProperty(target, key, {
74
+ value,
75
+ enumerable: true,
76
+ configurable: true,
77
+ writable: true,
78
+ });
79
+ }
42
80
  function serializeThrownError(err) {
43
81
  if (err instanceof Error) {
44
82
  return { message: err.message, stack: err.stack };
@@ -57,7 +95,7 @@ function serializePath(path) {
57
95
  return key;
58
96
  return {
59
97
  $type: 'symbol',
60
- value: serializeSymbol$1(key),
98
+ value: encodeSymbolToken(key, 'path key'),
61
99
  };
62
100
  });
63
101
  }
@@ -65,7 +103,7 @@ function deserializePath(path) {
65
103
  return path.map((key) => {
66
104
  if (typeof key === 'string')
67
105
  return key;
68
- return deserializeSymbol$1(key.value);
106
+ return decodeSymbolToken(key.value, 'path key');
69
107
  });
70
108
  }
71
109
  function isSerializedPath(value) {
@@ -79,26 +117,6 @@ function isSerializedPathKey(value) {
79
117
  const record = value;
80
118
  return record.$type === 'symbol' && typeof record.value === 'string';
81
119
  }
82
- function serializeSymbol$1(value) {
83
- const globalKey = Symbol.keyFor(value);
84
- if (globalKey)
85
- return `global:${globalKey}`;
86
- const wellKnownName = getWellKnownSymbolName(value);
87
- if (wellKnownName)
88
- return `wellKnown:${wellKnownName}`;
89
- throw new TypeError('Cannot serialize non-global symbol path key');
90
- }
91
- function deserializeSymbol$1(value) {
92
- if (value.startsWith('global:')) {
93
- return Symbol.for(value.slice('global:'.length));
94
- }
95
- if (value.startsWith('wellKnown:')) {
96
- const symbol = getWellKnownSymbol(value.slice('wellKnown:'.length));
97
- if (symbol)
98
- return symbol;
99
- }
100
- throw new TypeError('Cannot deserialize symbol path key');
101
- }
102
120
 
103
121
  function createMessageSender(poster, key, instanceId) {
104
122
  return {
@@ -109,7 +127,7 @@ function createMessageSender(poster, key, instanceId) {
109
127
  data: serializeMessageData(type, data),
110
128
  senderInstanceId: instanceId,
111
129
  };
112
- if (targetInstanceId)
130
+ if (targetInstanceId !== undefined)
113
131
  body.targetInstanceId = targetInstanceId;
114
132
  poster.postMessage(JSON.stringify(body));
115
133
  },
@@ -133,14 +151,47 @@ function serializeMessageData(type, data) {
133
151
  return data;
134
152
  }
135
153
 
154
+ const MAX_PATH_LENGTH = 64;
155
+ const MAX_INVOKE_ARGS = 1024;
156
+ const MAX_MESSAGE_LENGTH = 1000000;
157
+ const MAX_RELEASE_IDS = 10000;
158
+ const MAX_ID_LENGTH = 200;
159
+ const MAX_TYPE_LENGTH = 64;
160
+ const MESSAGE_SPECS = {
161
+ connectRequest: { validate: isConnectMessage },
162
+ connectResponse: { validate: isConnectMessage },
163
+ invokeRequest: {
164
+ validate: isInvokeRequest,
165
+ preDeserialize: (data) => ({ ...data, path: deserializePath(data.path) }),
166
+ errorResponse: { type: 'invokeResponse', extractId: (data) => data.id },
167
+ },
168
+ invokeResponse: { validate: isResponse },
169
+ accessPropertyRequest: {
170
+ validate: isAccessPropertyRequest,
171
+ preDeserialize: (data) => ({ ...data, path: deserializePath(data.path) }),
172
+ errorResponse: { type: 'accessPropertyResponse', extractId: (data) => data.id },
173
+ },
174
+ accessPropertyResponse: { validate: isResponse },
175
+ invokeFunctionByIdRequest: {
176
+ validate: isCallbackRequest,
177
+ errorResponse: { type: 'invokeFunctionByIdResponse', extractId: (data) => data.callId },
178
+ },
179
+ invokeFunctionByIdResponse: { validate: isResponse },
180
+ releaseCallbacks: { validate: isReleaseCallbacks },
181
+ destroyEndpoint: { validate: isDestroyEndpoint },
182
+ };
136
183
  function createMessageChannel(poster, key, instanceId, context, processorRegistry) {
137
184
  const sender = createMessageSender(poster, key, instanceId);
138
185
  const keyMatch = `"key":${JSON.stringify(key)}`;
139
186
  const listener = (event) => {
140
187
  if (typeof event.data !== 'string')
141
188
  return;
142
- if (event.data.length === 0 || event.data.charCodeAt(0) !== 123 /* '{' */)
189
+ if (event.data.length === 0 || event.data.charCodeAt(0) !== 123)
190
+ return;
191
+ if (event.data.length > MAX_MESSAGE_LENGTH) {
192
+ warn('createMessageChannel', `dropping oversized message (${event.data.length} > ${MAX_MESSAGE_LENGTH})`);
143
193
  return;
194
+ }
144
195
  if (event.data.indexOf(keyMatch) === -1)
145
196
  return;
146
197
  let body;
@@ -165,6 +216,7 @@ function createMessageChannel(poster, key, instanceId, context, processorRegistr
165
216
  if (!isValidMessagePayload(body))
166
217
  return;
167
218
  const meta = { senderInstanceId: body.senderInstanceId };
219
+ context.noteRemoteSeen(body.senderInstanceId);
168
220
  try {
169
221
  handler(deserializeMessageData(body), context, meta);
170
222
  }
@@ -185,46 +237,47 @@ function createMessageChannel(poster, key, instanceId, context, processorRegistr
185
237
  function isMessageEnvelope(value) {
186
238
  if (!isRecord(value))
187
239
  return false;
188
- if (typeof value.type !== 'string' || value.type.length === 0)
240
+ if (typeof value.type !== 'string' || value.type.length === 0 || value.type.length > MAX_TYPE_LENGTH)
189
241
  return false;
190
242
  if (typeof value.key !== 'string')
191
243
  return false;
192
- if (typeof value.senderInstanceId !== 'string' || value.senderInstanceId.length === 0)
244
+ if (typeof value.senderInstanceId !== 'string' ||
245
+ value.senderInstanceId.length === 0 ||
246
+ value.senderInstanceId.length > MAX_ID_LENGTH)
193
247
  return false;
194
- if ('targetInstanceId' in value && typeof value.targetInstanceId !== 'string' && value.targetInstanceId !== undefined)
248
+ if ('targetInstanceId' in value &&
249
+ value.targetInstanceId !== undefined &&
250
+ (typeof value.targetInstanceId !== 'string' || value.targetInstanceId.length > MAX_ID_LENGTH))
195
251
  return false;
196
252
  return true;
197
253
  }
198
254
  function isValidMessagePayload(value) {
199
- switch (value.type) {
200
- case 'invokeRequest':
201
- return isInvokeRequest(value.data);
202
- case 'invokeResponse':
203
- return isResponse(value.data);
204
- case 'accessPropertyRequest':
205
- return isAccessPropertyRequest(value.data);
206
- case 'accessPropertyResponse':
207
- return isResponse(value.data);
208
- case 'invokeFunctionByIdRequest':
209
- return isCallbackRequest(value.data);
210
- case 'invokeFunctionByIdResponse':
211
- return isResponse(value.data);
212
- case 'releaseCallbacks':
213
- return isReleaseCallbacks(value.data);
214
- case 'destroyEndpoint':
215
- return isDestroyEndpoint(value.data);
216
- default:
217
- return false;
218
- }
255
+ const spec = MESSAGE_SPECS[value.type];
256
+ return spec ? spec.validate(value.data) : false;
257
+ }
258
+ function isConnectMessage(value) {
259
+ return isRecord(value) && isMessageId(value.instanceId);
219
260
  }
220
261
  function isInvokeRequest(value) {
221
- return isRecord(value) && isMessageId(value.id) && isSerializedPath(value.path) && Array.isArray(value.args);
262
+ return (isRecord(value) &&
263
+ isMessageId(value.id) &&
264
+ isSerializedPath(value.path) &&
265
+ value.path.length <= MAX_PATH_LENGTH &&
266
+ Array.isArray(value.args) &&
267
+ value.args.length <= MAX_INVOKE_ARGS);
222
268
  }
223
269
  function isAccessPropertyRequest(value) {
224
- return isRecord(value) && isMessageId(value.id) && isSerializedPath(value.path);
270
+ return (isRecord(value) &&
271
+ isMessageId(value.id) &&
272
+ isSerializedPath(value.path) &&
273
+ value.path.length <= MAX_PATH_LENGTH);
225
274
  }
226
275
  function isCallbackRequest(value) {
227
- return isRecord(value) && isMessageId(value.id) && isMessageId(value.callId) && Array.isArray(value.args);
276
+ return (isRecord(value) &&
277
+ isMessageId(value.id) &&
278
+ isMessageId(value.callId) &&
279
+ Array.isArray(value.args) &&
280
+ value.args.length <= MAX_INVOKE_ARGS);
228
281
  }
229
282
  function isResponse(value) {
230
283
  return (isRecord(value) &&
@@ -233,31 +286,21 @@ function isResponse(value) {
233
286
  ('data' in value || 'error' in value));
234
287
  }
235
288
  function isReleaseCallbacks(value) {
236
- return isRecord(value) && Array.isArray(value.ids) && value.ids.every((id) => typeof id === 'string');
289
+ return (isRecord(value) && Array.isArray(value.ids) && value.ids.length <= MAX_RELEASE_IDS && value.ids.every(isMessageId));
237
290
  }
238
291
  function isDestroyEndpoint(value) {
239
- return isRecord(value) && typeof value.instanceId === 'string';
292
+ return isRecord(value) && isMessageId(value.instanceId);
240
293
  }
241
294
  function isSerializedError(value) {
242
295
  return (isRecord(value) && typeof value.message === 'string' && (!('stack' in value) || typeof value.stack === 'string'));
243
296
  }
244
297
  function isMessageId(value) {
245
- return typeof value === 'string' && value.length > 0;
298
+ return typeof value === 'string' && value.length > 0 && value.length <= MAX_ID_LENGTH;
246
299
  }
247
300
  function deserializeMessageData(body) {
248
- if (body.type === 'invokeRequest') {
249
- const data = body.data;
250
- return {
251
- ...data,
252
- path: deserializePath(data.path),
253
- };
254
- }
255
- if (body.type === 'accessPropertyRequest') {
256
- const data = body.data;
257
- return {
258
- ...data,
259
- path: deserializePath(data.path),
260
- };
301
+ const spec = MESSAGE_SPECS[body.type];
302
+ if (spec.preDeserialize) {
303
+ return spec.preDeserialize(body.data);
261
304
  }
262
305
  return body.data;
263
306
  }
@@ -265,38 +308,39 @@ function isRecord(value) {
265
308
  return typeof value === 'object' && value !== null;
266
309
  }
267
310
  function sendProcessorError(body, sender, err) {
268
- if (body.type === 'invokeRequest') {
269
- const data = body.data;
270
- sender.sendMessage('invokeResponse', {
271
- id: data.id,
272
- error: serializeThrownError(err),
273
- }, body.senderInstanceId);
311
+ const spec = MESSAGE_SPECS[body.type];
312
+ if (!spec.errorResponse)
274
313
  return;
275
- }
276
- if (body.type === 'accessPropertyRequest') {
277
- const data = body.data;
278
- sender.sendMessage('accessPropertyResponse', {
279
- id: data.id,
280
- error: serializeThrownError(err),
281
- }, body.senderInstanceId);
282
- return;
283
- }
314
+ const error = serializeThrownError(err);
284
315
  if (body.type === 'invokeFunctionByIdRequest') {
285
316
  const data = body.data;
286
- const error = serializeThrownError(err);
287
317
  warn('sendProcessorError', `callback '${data.id}' failed: ${error.message}`);
288
- sender.sendMessage('invokeFunctionByIdResponse', {
289
- id: data.callId,
290
- error,
291
- }, body.senderInstanceId);
292
318
  }
319
+ sender.sendMessage(spec.errorResponse.type, { id: spec.errorResponse.extractId(body.data), error }, body.senderInstanceId);
320
+ }
321
+
322
+ function isListenerRegistrationPath(path) {
323
+ const last = path[path.length - 1];
324
+ return last === 'addListener';
325
+ }
326
+ function isListenerRemovalPath(path) {
327
+ const last = path[path.length - 1];
328
+ return last === 'removeListener';
329
+ }
330
+ function isLikelyListenerPath(path) {
331
+ const last = path[path.length - 1];
332
+ return typeof last === 'string' && /^on[A-Z]/.test(last);
293
333
  }
294
334
 
295
335
  const TYPE_KEY = '$cii$';
336
+ const MAX_SERIALIZE_DEPTH = 200;
296
337
  function serialize(arg, registerFunction, options = {}) {
297
- return serialize0(arg, undefined, registerFunction, options, new WeakSet());
338
+ return serialize0(arg, undefined, registerFunction, options, new WeakSet(), 0);
298
339
  }
299
- function serialize0(arg, owner, registerFunction, options, seen) {
340
+ function serialize0(arg, owner, registerFunction, options, seen, depth) {
341
+ if (depth > MAX_SERIALIZE_DEPTH) {
342
+ throw new TypeError(`Serialization exceeded maximum depth of ${MAX_SERIALIZE_DEPTH}`);
343
+ }
300
344
  if (arg === undefined)
301
345
  return { [TYPE_KEY]: 'undef' };
302
346
  if (arg === null)
@@ -315,15 +359,22 @@ function serialize0(arg, owner, registerFunction, options, seen) {
315
359
  return options.persistent ? { [TYPE_KEY]: 'fn', id, persistent: true } : { [TYPE_KEY]: 'fn', id };
316
360
  }
317
361
  if (typeOf === 'symbol')
318
- return { [TYPE_KEY]: 'sym', v: serializeSymbol(arg) };
362
+ return { [TYPE_KEY]: 'sym', v: encodeSymbolToken(arg) };
319
363
  if (typeOf === 'object') {
320
364
  if (seen.has(arg)) {
321
365
  throw new TypeError('Cannot serialize circular structure');
322
366
  }
323
367
  seen.add(arg);
324
368
  try {
369
+ if (Array.isArray(arg)) {
370
+ return arg.map((item) => serialize0(item, arg, registerFunction, options, seen, depth + 1));
371
+ }
372
+ const proto = Object.getPrototypeOf(arg);
373
+ if (proto === Object.prototype || proto === null) {
374
+ return serializePlainObject(arg, registerFunction, options, seen, depth);
375
+ }
325
376
  if (arg instanceof Error)
326
- return serializeError(arg, owner, registerFunction, options, seen);
377
+ return serializeError(arg, owner, registerFunction, options, seen, depth);
327
378
  if (arg instanceof Date)
328
379
  return { [TYPE_KEY]: 'date', v: arg.getTime() };
329
380
  if (arg instanceof RegExp)
@@ -332,8 +383,8 @@ function serialize0(arg, owner, registerFunction, options, seen) {
332
383
  const entries = [];
333
384
  for (const [k, v] of arg.entries()) {
334
385
  entries.push([
335
- serialize0(k, arg, registerFunction, options, seen),
336
- serialize0(v, arg, registerFunction, options, seen),
386
+ serialize0(k, arg, registerFunction, options, seen, depth + 1),
387
+ serialize0(v, arg, registerFunction, options, seen, depth + 1),
337
388
  ]);
338
389
  }
339
390
  return { [TYPE_KEY]: 'map', entries };
@@ -341,14 +392,11 @@ function serialize0(arg, owner, registerFunction, options, seen) {
341
392
  if (arg instanceof Set) {
342
393
  const values = [];
343
394
  for (const v of arg.values()) {
344
- values.push(serialize0(v, arg, registerFunction, options, seen));
395
+ values.push(serialize0(v, arg, registerFunction, options, seen, depth + 1));
345
396
  }
346
397
  return { [TYPE_KEY]: 'set', values };
347
398
  }
348
- if (Array.isArray(arg)) {
349
- return arg.map((item) => serialize0(item, arg, registerFunction, options, seen));
350
- }
351
- return serializePlainObject(arg, registerFunction, options, seen);
399
+ return serializePlainObject(arg, registerFunction, options, seen, depth);
352
400
  }
353
401
  finally {
354
402
  seen.delete(arg);
@@ -356,7 +404,7 @@ function serialize0(arg, owner, registerFunction, options, seen) {
356
404
  }
357
405
  return null;
358
406
  }
359
- function serializeError(err, owner, registerFunction, options, seen) {
407
+ function serializeError(err, owner, registerFunction, options, seen, depth) {
360
408
  const result = { [TYPE_KEY]: 'err', message: err.message };
361
409
  if (err.name && err.name !== 'Error')
362
410
  result.name = err.name;
@@ -364,7 +412,7 @@ function serializeError(err, owner, registerFunction, options, seen) {
364
412
  result.stack = err.stack;
365
413
  const cause = err.cause;
366
414
  if (cause !== undefined) {
367
- result.cause = serialize0(cause, owner, registerFunction, options, seen);
415
+ result.cause = serialize0(cause, owner, registerFunction, options, seen, depth + 1);
368
416
  }
369
417
  const RESERVED_ERROR_KEYS = new Set(['message', 'name', 'stack', 'cause']);
370
418
  const extras = [];
@@ -373,14 +421,16 @@ function serializeError(err, owner, registerFunction, options, seen) {
373
421
  continue;
374
422
  if (RESERVED_ERROR_KEYS.has(k))
375
423
  continue;
424
+ if (!isEnumerable(err, k))
425
+ continue;
376
426
  const value = err[k];
377
- extras.push([k, serialize0(value, owner, registerFunction, options, seen)]);
427
+ extras.push([k, serialize0(value, owner, registerFunction, options, seen, depth + 1)]);
378
428
  }
379
429
  if (extras.length > 0)
380
430
  result.extras = extras;
381
431
  return result;
382
432
  }
383
- function serializePlainObject(source, registerFunction, options, seen) {
433
+ function serializePlainObject(source, registerFunction, options, seen, depth) {
384
434
  const record = source;
385
435
  const ownKeys = Reflect.ownKeys(source).filter((k) => isEnumerable(source, k));
386
436
  const symbolKeys = [];
@@ -398,19 +448,25 @@ function serializePlainObject(source, registerFunction, options, seen) {
398
448
  if (symbolKeys.length > 0 || hasReservedKey) {
399
449
  const entries = [];
400
450
  for (const k of stringKeys) {
401
- entries.push([k, serialize0(record[k], source, registerFunction, options, seen)]);
451
+ entries.push([k, serialize0(record[k], source, registerFunction, options, seen, depth + 1)]);
402
452
  }
403
453
  for (const k of symbolKeys) {
404
454
  entries.push([
405
- { [TYPE_KEY]: 'sym', v: serializeSymbol(k) },
406
- serialize0(record[k], source, registerFunction, options, seen),
455
+ { [TYPE_KEY]: 'sym', v: encodeSymbolToken(k) },
456
+ serialize0(record[k], source, registerFunction, options, seen, depth + 1),
407
457
  ]);
408
458
  }
409
459
  return { [TYPE_KEY]: 'obj', entries };
410
460
  }
411
461
  const result = {};
412
462
  for (const k of stringKeys) {
413
- setOwnProperty$1(result, k, serialize0(record[k], source, registerFunction, options, seen));
463
+ const value = serialize0(record[k], source, registerFunction, options, seen, depth + 1);
464
+ if (k === '__proto__') {
465
+ setOwnProperty(result, k, value);
466
+ }
467
+ else {
468
+ result[k] = value;
469
+ }
414
470
  }
415
471
  return result;
416
472
  }
@@ -434,56 +490,61 @@ function serializeNumber(value) {
434
490
  return { [TYPE_KEY]: 'num', v: '-0' };
435
491
  return value;
436
492
  }
437
- function setOwnProperty$1(target, key, value) {
438
- Object.defineProperty(target, key, {
439
- value,
440
- enumerable: true,
441
- configurable: true,
442
- writable: true,
443
- });
444
- }
445
- function serializeSymbol(value) {
446
- const globalKey = Symbol.keyFor(value);
447
- if (globalKey)
448
- return `global:${globalKey}`;
449
- const wellKnownName = getWellKnownSymbolName(value);
450
- if (wellKnownName)
451
- return `wellKnown:${wellKnownName}`;
452
- throw new TypeError('Cannot serialize non-global symbol');
453
- }
454
493
 
455
- const ERROR_CONSTRUCTORS = {
456
- Error,
457
- TypeError: TypeError,
458
- RangeError: RangeError,
459
- SyntaxError: SyntaxError,
460
- ReferenceError: ReferenceError,
461
- URIError: URIError,
462
- EvalError: EvalError,
494
+ const MAX_DESERIALIZE_DEPTH = 200;
495
+ const ERROR_FACTORIES = {
496
+ Error: (m) => new Error(m),
497
+ TypeError: (m) => new TypeError(m),
498
+ RangeError: (m) => new RangeError(m),
499
+ SyntaxError: (m) => new SyntaxError(m),
500
+ ReferenceError: (m) => new ReferenceError(m),
501
+ URIError: (m) => new URIError(m),
502
+ EvalError: (m) => new EvalError(m),
463
503
  };
504
+ const globalScope = globalThis;
505
+ const AggregateErrorCtorRef = globalScope.AggregateError;
506
+ if (typeof AggregateErrorCtorRef === 'function') {
507
+ ERROR_FACTORIES.AggregateError = (m) => new AggregateErrorCtorRef([], m);
508
+ }
509
+ const DOMExceptionCtorRef = globalScope.DOMException;
510
+ if (typeof DOMExceptionCtorRef === 'function') {
511
+ ERROR_FACTORIES.DOMException = (m) => new DOMExceptionCtorRef(m);
512
+ }
464
513
  function deserialize(arg, generateCallback, getRemoteCallback) {
465
- return deserialize0(arg, generateCallback, getRemoteCallback);
514
+ return deserialize0(arg, generateCallback, getRemoteCallback, 0);
466
515
  }
467
- function deserialize0(arg, generateCallback, getRemoteCallback) {
516
+ function deserialize0(arg, generateCallback, getRemoteCallback, depth) {
517
+ if (depth > MAX_DESERIALIZE_DEPTH) {
518
+ throw new TypeError(`Deserialization exceeded maximum depth of ${MAX_DESERIALIZE_DEPTH}`);
519
+ }
468
520
  if (arg === null)
469
521
  return null;
470
522
  if (typeof arg !== 'object')
471
523
  return arg;
472
524
  if (Array.isArray(arg)) {
473
- return arg.map((item) => deserialize0(item, generateCallback, getRemoteCallback));
525
+ return arg.map((item) => deserialize0(item, generateCallback, getRemoteCallback, depth + 1));
474
526
  }
475
527
  const source = arg;
476
528
  const tag = source[TYPE_KEY];
477
529
  if (typeof tag === 'string') {
478
- return deserializeTagged(tag, source, generateCallback, getRemoteCallback);
530
+ return deserializeTagged(tag, source, generateCallback, getRemoteCallback, depth);
479
531
  }
532
+ return deserializePlainObject(source, generateCallback, getRemoteCallback, depth);
533
+ }
534
+ function deserializePlainObject(source, generateCallback, getRemoteCallback, depth) {
480
535
  const result = {};
481
536
  for (const key of Object.keys(source)) {
482
- setOwnProperty(result, key, deserialize0(source[key], generateCallback, getRemoteCallback));
537
+ const value = deserialize0(source[key], generateCallback, getRemoteCallback, depth + 1);
538
+ if (key === '__proto__') {
539
+ setOwnProperty(result, key, value);
540
+ }
541
+ else {
542
+ result[key] = value;
543
+ }
483
544
  }
484
545
  return result;
485
546
  }
486
- function deserializeTagged(tag, source, generateCallback, getRemoteCallback) {
547
+ function deserializeTagged(tag, source, generateCallback, getRemoteCallback, depth) {
487
548
  switch (tag) {
488
549
  case 'undef':
489
550
  return undefined;
@@ -494,26 +555,21 @@ function deserializeTagged(tag, source, generateCallback, getRemoteCallback) {
494
555
  case 'num':
495
556
  return deserializeNumber(expectString(source.v, 'number'));
496
557
  case 'err':
497
- return deserializeError(source, generateCallback, getRemoteCallback);
558
+ return deserializeError(source, generateCallback, getRemoteCallback, depth);
498
559
  case 'date':
499
560
  return deserializeDate(source.v);
500
561
  case 're':
501
562
  return deserializeRegExp(source.source, source.flags);
502
563
  case 'map':
503
- return deserializeMap(source.entries, generateCallback, getRemoteCallback);
564
+ return deserializeMap(source.entries, generateCallback, getRemoteCallback, depth);
504
565
  case 'set':
505
- return deserializeSet(source.values, generateCallback, getRemoteCallback);
566
+ return deserializeSet(source.values, generateCallback, getRemoteCallback, depth);
506
567
  case 'fn':
507
568
  return deserializeFunction(source, generateCallback, getRemoteCallback);
508
569
  case 'obj':
509
- return deserializeWrappedObject(source.entries, generateCallback, getRemoteCallback);
510
- default: {
511
- const result = {};
512
- for (const key of Object.keys(source)) {
513
- setOwnProperty(result, key, deserialize0(source[key], generateCallback, getRemoteCallback));
514
- }
515
- return result;
516
- }
570
+ return deserializeWrappedObject(source.entries, generateCallback, getRemoteCallback, depth);
571
+ default:
572
+ return deserializePlainObject(source, generateCallback, getRemoteCallback, depth);
517
573
  }
518
574
  }
519
575
  function deserializeBigInt(raw) {
@@ -544,25 +600,25 @@ function deserializeRegExp(rawSource, rawFlags) {
544
600
  throw new TypeError(`Invalid regexp payload: ${err instanceof Error ? err.message : String(err)}`);
545
601
  }
546
602
  }
547
- function deserializeMap(entries, generateCallback, getRemoteCallback) {
603
+ function deserializeMap(entries, generateCallback, getRemoteCallback, depth) {
548
604
  const result = new Map();
549
605
  if (!Array.isArray(entries))
550
606
  return result;
551
607
  for (const entry of entries) {
552
608
  if (!Array.isArray(entry) || entry.length !== 2)
553
609
  continue;
554
- const k = deserialize0(entry[0], generateCallback, getRemoteCallback);
555
- const v = deserialize0(entry[1], generateCallback, getRemoteCallback);
610
+ const k = deserialize0(entry[0], generateCallback, getRemoteCallback, depth + 1);
611
+ const v = deserialize0(entry[1], generateCallback, getRemoteCallback, depth + 1);
556
612
  result.set(k, v);
557
613
  }
558
614
  return result;
559
615
  }
560
- function deserializeSet(values, generateCallback, getRemoteCallback) {
616
+ function deserializeSet(values, generateCallback, getRemoteCallback, depth) {
561
617
  const result = new Set();
562
618
  if (!Array.isArray(values))
563
619
  return result;
564
620
  for (const v of values) {
565
- result.add(deserialize0(v, generateCallback, getRemoteCallback));
621
+ result.add(deserialize0(v, generateCallback, getRemoteCallback, depth + 1));
566
622
  }
567
623
  return result;
568
624
  }
@@ -580,23 +636,35 @@ function deserializeFunction(source, generateCallback, getRemoteCallback) {
580
636
  };
581
637
  return getRemoteCallback ? getRemoteCallback(id, invoke, fnOptions) : (...args) => invoke(args);
582
638
  }
583
- function deserializeError(source, generateCallback, getRemoteCallback) {
584
- const message = typeof source.message === 'string' ? source.message : '';
585
- const name = typeof source.name === 'string' ? source.name : 'Error';
586
- const Ctor = ERROR_CONSTRUCTORS[name] ?? Error;
587
- const error = new Ctor(message);
588
- if (name && name !== Ctor.name) {
639
+ function rebuildErrorFromSerialized(serialized) {
640
+ const name = typeof serialized.name === 'string' && serialized.name ? serialized.name : 'Error';
641
+ const factory = ERROR_FACTORIES[name] ?? ERROR_FACTORIES.Error;
642
+ const error = factory(serialized.message);
643
+ if (name && error.name !== name) {
589
644
  try {
590
645
  error.name = name;
591
646
  }
592
- catch (err) {
593
- warn('deserializeError', 'failed to set error.name', name, err);
647
+ catch {
648
+ try {
649
+ Object.defineProperty(error, 'name', { value: name, configurable: true, writable: true });
650
+ }
651
+ catch (err) {
652
+ warn('rebuildErrorFromSerialized', 'failed to set error.name', name, err);
653
+ }
594
654
  }
595
655
  }
596
- if (typeof source.stack === 'string')
597
- error.stack = source.stack;
656
+ if (typeof serialized.stack === 'string')
657
+ error.stack = serialized.stack;
658
+ return error;
659
+ }
660
+ function deserializeError(source, generateCallback, getRemoteCallback, depth) {
661
+ const error = rebuildErrorFromSerialized({
662
+ message: typeof source.message === 'string' ? source.message : '',
663
+ name: typeof source.name === 'string' ? source.name : undefined,
664
+ stack: typeof source.stack === 'string' ? source.stack : undefined,
665
+ });
598
666
  if (source.cause !== undefined) {
599
- error.cause = deserialize0(source.cause, generateCallback, getRemoteCallback);
667
+ error.cause = deserialize0(source.cause, generateCallback, getRemoteCallback, depth + 1);
600
668
  }
601
669
  if (Array.isArray(source.extras)) {
602
670
  for (const entry of source.extras) {
@@ -606,7 +674,7 @@ function deserializeError(source, generateCallback, getRemoteCallback) {
606
674
  if (typeof rawKey !== 'string')
607
675
  continue;
608
676
  try {
609
- setOwnProperty(error, rawKey, deserialize0(rawValue, generateCallback, getRemoteCallback));
677
+ setOwnProperty(error, rawKey, deserialize0(rawValue, generateCallback, getRemoteCallback, depth + 1));
610
678
  }
611
679
  catch (err) {
612
680
  warn('deserializeError', 'failed to set error property', rawKey, err);
@@ -615,7 +683,7 @@ function deserializeError(source, generateCallback, getRemoteCallback) {
615
683
  }
616
684
  return error;
617
685
  }
618
- function deserializeWrappedObject(entries, generateCallback, getRemoteCallback) {
686
+ function deserializeWrappedObject(entries, generateCallback, getRemoteCallback, depth) {
619
687
  const result = {};
620
688
  if (!Array.isArray(entries))
621
689
  return result;
@@ -623,18 +691,13 @@ function deserializeWrappedObject(entries, generateCallback, getRemoteCallback)
623
691
  if (!Array.isArray(entry) || entry.length !== 2)
624
692
  continue;
625
693
  const rawKey = entry[0];
626
- const value = deserialize0(entry[1], generateCallback, getRemoteCallback);
694
+ const value = deserialize0(entry[1], generateCallback, getRemoteCallback, depth + 1);
627
695
  const key = resolveObjectKey(rawKey);
628
696
  if (key === undefined) {
629
697
  warn('deserializeWrappedObject', 'dropping wrapped-object entry with unresolvable key', rawKey);
630
698
  continue;
631
699
  }
632
- Object.defineProperty(result, key, {
633
- value,
634
- enumerable: true,
635
- configurable: true,
636
- writable: true,
637
- });
700
+ setOwnProperty(result, key, value);
638
701
  }
639
702
  return result;
640
703
  }
@@ -672,39 +735,88 @@ function deserializeNumber(raw) {
672
735
  return Number(raw);
673
736
  }
674
737
  function deserializeSymbol(value) {
675
- if (value.startsWith('global:')) {
676
- return Symbol.for(value.slice('global:'.length));
677
- }
678
- if (value.startsWith('wellKnown:')) {
679
- const symbol = getWellKnownSymbol(value.slice('wellKnown:'.length));
680
- if (symbol)
681
- return symbol;
682
- }
683
- throw new TypeError('Cannot deserialize symbol');
684
- }
685
- function setOwnProperty(target, key, value) {
686
- Object.defineProperty(target, key, {
687
- value,
688
- enumerable: true,
689
- configurable: true,
690
- writable: true,
691
- });
738
+ return decodeSymbolToken(value);
692
739
  }
693
740
 
694
- function isListenerRegistrationPath(path) {
695
- const last = path[path.length - 1];
696
- return last === 'addListener';
741
+ function createResponder(ctx, targetInstanceId, responseType, id) {
742
+ const sender = () => ctx.getMessageChannel().getSender();
743
+ const sendError = (error) => {
744
+ sender().sendMessage(responseType, { id, error }, targetInstanceId);
745
+ };
746
+ return {
747
+ respondError(message) {
748
+ sendError({ message });
749
+ },
750
+ respondThrown(err) {
751
+ sendError(serializeThrownError(err));
752
+ },
753
+ respondSuccess(value) {
754
+ try {
755
+ sender().sendMessage(responseType, { id, data: ctx.serializeForRemote(value, targetInstanceId) }, targetInstanceId);
756
+ }
757
+ catch (err) {
758
+ sendError(serializeThrownError(err));
759
+ }
760
+ },
761
+ };
697
762
  }
698
- function isListenerRemovalPath(path) {
699
- const last = path[path.length - 1];
700
- return last === 'removeListener';
763
+ function createScopedRemoteCallback(ctx, remoteInstanceId) {
764
+ return (id, invoke, options) => ctx.getRemoteCallback(id, invoke, options, remoteInstanceId);
701
765
  }
702
- function isLikelyListenerPath(path) {
703
- const last = path[path.length - 1];
704
- return typeof last === 'string' && /^on[A-Z]/.test(last);
766
+ function createGenerateCallback(ctx, remoteInstanceId) {
767
+ return (id, args, options) => ctx.invokeFunctionById(id, args, options, remoteInstanceId);
768
+ }
769
+ function safeInvokeReject(callbacks, scope, warn) {
770
+ if (!callbacks.onReject)
771
+ return;
772
+ try {
773
+ callbacks.onReject();
774
+ }
775
+ catch (err) {
776
+ warn(scope, 'onReject hook threw', err);
777
+ }
778
+ }
779
+ function safeInvokeResolve(callbacks, scope, warn) {
780
+ if (!callbacks.onResolve)
781
+ return;
782
+ try {
783
+ callbacks.onResolve();
784
+ }
785
+ catch (err) {
786
+ warn(scope, 'onResolve hook threw', err);
787
+ }
788
+ }
789
+ function createResponseHandler(scope, warn) {
790
+ return function handleResponse(data, ctx, meta) {
791
+ const pending = ctx.getAndRemovePendingPromise(data.id, meta.senderInstanceId);
792
+ if (!pending)
793
+ return;
794
+ if (data.error) {
795
+ const err = rebuildErrorFromSerialized(data.error);
796
+ safeInvokeReject(pending, scope, warn);
797
+ pending.reject(err);
798
+ return;
799
+ }
800
+ try {
801
+ safeInvokeResolve(pending, scope, warn);
802
+ pending.resolve(deserialize(data.data, createGenerateCallback(ctx, meta.senderInstanceId), createScopedRemoteCallback(ctx, meta.senderInstanceId)));
803
+ }
804
+ catch (err) {
805
+ safeInvokeReject(pending, scope, warn);
806
+ pending.reject(err instanceof Error ? err : new Error(String(err)));
807
+ }
808
+ };
705
809
  }
706
810
 
707
- const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
811
+ const DANGEROUS_KEYS = new Set([
812
+ '__proto__',
813
+ 'constructor',
814
+ 'prototype',
815
+ '__defineGetter__',
816
+ '__defineSetter__',
817
+ '__lookupGetter__',
818
+ '__lookupSetter__',
819
+ ]);
708
820
  function readProperty(target, key) {
709
821
  if (typeof key === 'string' && DANGEROUS_KEYS.has(key)) {
710
822
  throw new Error(`Access to dangerous property '${key}' is denied`);
@@ -715,20 +827,16 @@ function readProperty(target, key) {
715
827
  }
716
828
 
717
829
  function handleAccessPropertyRequest(data, ctx, meta) {
830
+ if (ctx.isDestroyed())
831
+ return;
718
832
  const target = ctx.getDelegateTarget();
719
- const channel = ctx.getMessageChannel();
833
+ const { respondError, respondThrown } = createResponder(ctx, meta.senderInstanceId, 'accessPropertyResponse', data.id);
720
834
  if (data.path.length === 0) {
721
- channel.getSender().sendMessage('accessPropertyResponse', {
722
- id: data.id,
723
- error: { message: 'Property path must not be empty' },
724
- }, meta.senderInstanceId);
835
+ respondError('Property path must not be empty');
725
836
  return;
726
837
  }
727
838
  if (target === undefined || target === null) {
728
- channel.getSender().sendMessage('accessPropertyResponse', {
729
- id: data.id,
730
- error: { message: 'No delegate target is configured' },
731
- }, meta.senderInstanceId);
839
+ respondError('No delegate target is configured');
732
840
  return;
733
841
  }
734
842
  try {
@@ -737,115 +845,70 @@ function handleAccessPropertyRequest(data, ctx, meta) {
737
845
  for (let i = 0; i < data.path.length; i++) {
738
846
  const key = data.path[i];
739
847
  if (current === undefined || current === null) {
740
- channel.getSender().sendMessage('accessPropertyResponse', {
741
- id: data.id,
742
- error: {
743
- message: `Cannot read property '${String(key)}' of ${current === null ? 'null' : 'undefined'} (at '${data.path
744
- .slice(0, i)
745
- .map(String)
746
- .join('.')}')`,
747
- },
748
- }, meta.senderInstanceId);
848
+ respondError(`Cannot read property '${String(key)}' of ${current === null ? 'null' : 'undefined'} (at '${data.path
849
+ .slice(0, i)
850
+ .map(String)
851
+ .join('.')}')`);
749
852
  return;
750
853
  }
751
854
  owner = current;
752
855
  current = readProperty(current, key);
753
856
  }
754
857
  const value = typeof current === 'function' ? ctx.bindMethod(current, owner) : current;
755
- channel.getSender().sendMessage('accessPropertyResponse', {
858
+ ctx
859
+ .getMessageChannel()
860
+ .getSender()
861
+ .sendMessage('accessPropertyResponse', {
756
862
  id: data.id,
757
- data: serialize(value, ctx.registerFunction, { persistent: isLikelyListenerPath(data.path) }),
863
+ data: ctx.serializeForRemote(value, meta.senderInstanceId, {
864
+ persistent: isLikelyListenerPath(data.path),
865
+ }),
758
866
  }, meta.senderInstanceId);
759
867
  }
760
868
  catch (err) {
761
- channel.getSender().sendMessage('accessPropertyResponse', {
762
- id: data.id,
763
- error: serializeThrownError(err),
764
- }, meta.senderInstanceId);
869
+ respondThrown(err);
765
870
  }
766
871
  }
767
- function handleAccessPropertyResponse(data, ctx, meta) {
768
- const pending = ctx.getAndRemovePendingPromise(data.id);
769
- if (!pending)
770
- return;
771
- if (data.error) {
772
- const err = new Error(data.error.message);
773
- if (data.error.stack)
774
- err.stack = data.error.stack;
775
- pending.onReject?.();
776
- pending.reject(err);
777
- return;
778
- }
779
- const scopedRemoteCallback = createScopedRemoteCallback$2(ctx, meta.senderInstanceId);
780
- try {
781
- pending.onResolve?.();
782
- pending.resolve(deserialize(data.data, (id, args, options) => {
783
- return ctx.invokeFunctionById(id, args, options);
784
- }, scopedRemoteCallback));
785
- }
786
- catch (err) {
787
- pending.onReject?.();
788
- pending.reject(err instanceof Error ? err : new Error(String(err)));
789
- }
790
- }
791
- function createScopedRemoteCallback$2(ctx, remoteInstanceId) {
792
- return (id, invoke, options) => ctx.getRemoteCallback(id, invoke, options, remoteInstanceId);
793
- }
872
+ const handleAccessPropertyResponse = createResponseHandler('handleAccessPropertyResponse', warn);
794
873
 
795
874
  function handleInvokeRequest(data, ctx, meta) {
875
+ if (ctx.isDestroyed())
876
+ return;
796
877
  const target = ctx.getDelegateTarget();
797
- const channel = ctx.getMessageChannel();
798
- const scopedRemoteCallback = createScopedRemoteCallback$1(ctx, meta.senderInstanceId);
878
+ const scopedRemoteCallback = createScopedRemoteCallback(ctx, meta.senderInstanceId);
879
+ const generateCallback = createGenerateCallback(ctx, meta.senderInstanceId);
880
+ const { respondError, respondThrown, respondSuccess } = createResponder(ctx, meta.senderInstanceId, 'invokeResponse', data.id);
799
881
  if (target === undefined || target === null) {
800
- channel.getSender().sendMessage('invokeResponse', {
801
- id: data.id,
802
- error: { message: 'No delegate target is configured' },
803
- }, meta.senderInstanceId);
882
+ respondError('No delegate target is configured');
804
883
  return;
805
884
  }
806
885
  if (data.path.length === 0) {
807
- channel.getSender().sendMessage('invokeResponse', {
808
- id: data.id,
809
- error: { message: 'Invocation path must not be empty' },
810
- }, meta.senderInstanceId);
886
+ respondError('Invocation path must not be empty');
811
887
  return;
812
888
  }
813
889
  let current = target;
814
890
  for (let i = 0; i < data.path.length - 1; i++) {
815
891
  current = readProperty(current, data.path[i]);
816
892
  if (current === undefined || current === null) {
817
- channel.getSender().sendMessage('invokeResponse', {
818
- id: data.id,
819
- error: {
820
- message: `Cannot read property '${String(data.path[i + 1])}' of ${current === null ? 'null' : 'undefined'} (at '${data.path
821
- .slice(0, i + 1)
822
- .map(String)
823
- .join('.')}')`,
824
- },
825
- }, meta.senderInstanceId);
893
+ respondError(`Cannot read property '${String(data.path[i + 1])}' of ${current === null ? 'null' : 'undefined'} (at '${data.path
894
+ .slice(0, i + 1)
895
+ .map(String)
896
+ .join('.')}')`);
826
897
  return;
827
898
  }
828
899
  }
829
900
  const lastKey = data.path[data.path.length - 1];
830
901
  const fn = readProperty(current, lastKey);
831
902
  if (typeof fn !== 'function') {
832
- channel.getSender().sendMessage('invokeResponse', {
833
- id: data.id,
834
- error: { message: `'${String(lastKey)}' is not a function` },
835
- }, meta.senderInstanceId);
903
+ respondError(`'${String(lastKey)}' is not a function`);
836
904
  return;
837
905
  }
838
906
  let deserializedArgs;
839
907
  try {
840
- deserializedArgs = data.args.map((arg) => deserialize(arg, (id, args, options) => {
841
- return ctx.invokeFunctionById(id, args, options);
842
- }, scopedRemoteCallback));
908
+ deserializedArgs = data.args.map((arg) => deserialize(arg, generateCallback, scopedRemoteCallback));
843
909
  }
844
910
  catch (err) {
845
- channel.getSender().sendMessage('invokeResponse', {
846
- id: data.id,
847
- error: serializeThrownError(err),
848
- }, meta.senderInstanceId);
911
+ respondThrown(err);
849
912
  return;
850
913
  }
851
914
  let result;
@@ -853,93 +916,43 @@ function handleInvokeRequest(data, ctx, meta) {
853
916
  result = Reflect.apply(fn, current, deserializedArgs);
854
917
  }
855
918
  catch (err) {
856
- channel.getSender().sendMessage('invokeResponse', {
857
- id: data.id,
858
- error: serializeThrownError(err),
859
- }, meta.senderInstanceId);
919
+ respondThrown(err);
860
920
  return;
861
921
  }
862
922
  if (isPromiseLike(result)) {
863
923
  Promise.resolve(result)
864
924
  .then((value) => {
865
- sendInvokeSuccess(data.id, value, ctx, meta.senderInstanceId);
925
+ respondSuccess(value);
866
926
  })
867
927
  .catch((err) => {
868
- channel.getSender().sendMessage('invokeResponse', {
869
- id: data.id,
870
- error: serializeThrownError(err),
871
- }, meta.senderInstanceId);
928
+ respondThrown(err);
872
929
  });
873
930
  return;
874
931
  }
875
- sendInvokeSuccess(data.id, result, ctx, meta.senderInstanceId);
876
- }
877
- function handleInvokeResponse(data, ctx, meta) {
878
- const pending = ctx.getAndRemovePendingPromise(data.id);
879
- if (!pending)
880
- return;
881
- if (data.error) {
882
- const err = new Error(data.error.message);
883
- if (data.error.stack)
884
- err.stack = data.error.stack;
885
- pending.onReject?.();
886
- pending.reject(err);
887
- }
888
- else {
889
- const scopedRemoteCallback = createScopedRemoteCallback$1(ctx, meta.senderInstanceId);
890
- try {
891
- pending.onResolve?.();
892
- pending.resolve(deserialize(data.data, (id, args, options) => {
893
- return ctx.invokeFunctionById(id, args, options);
894
- }, scopedRemoteCallback));
895
- }
896
- catch (err) {
897
- pending.onReject?.();
898
- pending.reject(err instanceof Error ? err : new Error(String(err)));
899
- }
900
- }
901
- }
902
- function sendInvokeSuccess(id, value, ctx, targetInstanceId) {
903
- const channel = ctx.getMessageChannel();
904
- try {
905
- channel.getSender().sendMessage('invokeResponse', {
906
- id,
907
- data: serialize(value, ctx.registerFunction),
908
- }, targetInstanceId);
909
- }
910
- catch (err) {
911
- channel.getSender().sendMessage('invokeResponse', {
912
- id,
913
- error: serializeThrownError(err),
914
- }, targetInstanceId);
915
- }
916
- }
917
- function createScopedRemoteCallback$1(ctx, remoteInstanceId) {
918
- return (id, invoke, options) => ctx.getRemoteCallback(id, invoke, options, remoteInstanceId);
932
+ respondSuccess(result);
919
933
  }
934
+ const handleInvokeResponse = createResponseHandler('handleInvokeResponse', warn);
920
935
 
921
936
  function handleCallbackInvoke(data, ctx, meta) {
922
- const channel = ctx.getMessageChannel();
937
+ if (ctx.isDestroyed())
938
+ return;
939
+ const { respondError, respondThrown, respondSuccess } = createResponder(ctx, meta.senderInstanceId, 'invokeFunctionByIdResponse', data.callId);
923
940
  const persistentEntry = ctx.getPersistentFunctionCache().get(data.id);
924
941
  const entry = persistentEntry ?? ctx.getFunctionCache().get(data.id);
925
942
  if (!entry) {
926
- const error = { message: `Callback '${data.id}' is not available or has expired` };
927
- warn('handleCallbackInvoke', error.message);
928
- channel.getSender().sendMessage('invokeFunctionByIdResponse', {
929
- id: data.callId,
930
- error,
931
- }, meta.senderInstanceId);
943
+ const message = `Callback '${data.id}' is not available or has expired`;
944
+ warn('handleCallbackInvoke', message);
945
+ respondError(message);
932
946
  return;
933
947
  }
934
948
  const scopedRemoteCallback = createScopedRemoteCallback(ctx, meta.senderInstanceId);
949
+ const generateCallback = createGenerateCallback(ctx, meta.senderInstanceId);
935
950
  let deserializedArgs;
936
951
  try {
937
- deserializedArgs = data.args.map((arg) => deserialize(arg, (id, args, options) => {
938
- return ctx.invokeFunctionById(id, args, options);
939
- }, scopedRemoteCallback));
952
+ deserializedArgs = data.args.map((arg) => deserialize(arg, generateCallback, scopedRemoteCallback));
940
953
  }
941
954
  catch (err) {
942
- sendCallbackError(data.callId, err, ctx, meta.senderInstanceId);
955
+ respondThrown(err);
943
956
  return;
944
957
  }
945
958
  try {
@@ -947,82 +960,54 @@ function handleCallbackInvoke(data, ctx, meta) {
947
960
  if (isPromiseLike(result)) {
948
961
  Promise.resolve(result)
949
962
  .then((value) => {
950
- sendCallbackSuccess(data.callId, value, ctx, meta.senderInstanceId);
963
+ respondSuccess(value);
951
964
  })
952
965
  .catch((err) => {
953
- sendCallbackError(data.callId, err, ctx, meta.senderInstanceId);
966
+ respondThrown(err);
954
967
  });
955
968
  return;
956
969
  }
957
- sendCallbackSuccess(data.callId, result, ctx, meta.senderInstanceId);
958
- }
959
- catch (err) {
960
- sendCallbackError(data.callId, err, ctx, meta.senderInstanceId);
961
- }
962
- }
963
- function handleCallbackInvokeResponse(data, ctx, meta) {
964
- const pending = ctx.getAndRemovePendingPromise(data.id);
965
- if (!pending)
966
- return;
967
- if (data.error) {
968
- const err = new Error(data.error.message);
969
- if (data.error.stack)
970
- err.stack = data.error.stack;
971
- pending.onReject?.();
972
- pending.reject(err);
973
- return;
974
- }
975
- const scopedRemoteCallback = createScopedRemoteCallback(ctx, meta.senderInstanceId);
976
- try {
977
- pending.onResolve?.();
978
- pending.resolve(deserialize(data.data, (id, args, options) => {
979
- return ctx.invokeFunctionById(id, args, options);
980
- }, scopedRemoteCallback));
970
+ respondSuccess(result);
981
971
  }
982
972
  catch (err) {
983
- pending.onReject?.();
984
- pending.reject(err instanceof Error ? err : new Error(String(err)));
973
+ respondThrown(err);
985
974
  }
986
975
  }
987
- function sendCallbackSuccess(id, value, ctx, targetInstanceId) {
988
- const channel = ctx.getMessageChannel();
989
- try {
990
- channel.getSender().sendMessage('invokeFunctionByIdResponse', {
991
- id,
992
- data: serialize(value, ctx.registerFunction),
993
- }, targetInstanceId);
994
- }
995
- catch (err) {
996
- sendCallbackError(id, err, ctx, targetInstanceId);
997
- }
998
- }
999
- function sendCallbackError(id, err, ctx, targetInstanceId) {
1000
- ctx
1001
- .getMessageChannel()
1002
- .getSender()
1003
- .sendMessage('invokeFunctionByIdResponse', {
1004
- id,
1005
- error: serializeThrownError(err),
1006
- }, targetInstanceId);
976
+ const handleCallbackInvokeResponse = createResponseHandler('handleCallbackInvokeResponse', warn);
977
+
978
+ const RELEASE_CHUNK_SIZE = 1000;
979
+ const scheduleNextChunk = typeof queueMicrotask === 'function' ? (cb) => queueMicrotask(cb) : (cb) => setTimeout(cb, 0);
980
+ function handleConnectRequest(_data, ctx, meta) {
981
+ ctx.noteFreshConnect(meta.senderInstanceId);
982
+ ctx.getMessageChannel().getSender().sendMessage('connectResponse', {
983
+ instanceId: ctx.getInstanceId(),
984
+ }, meta.senderInstanceId);
1007
985
  }
1008
- function createScopedRemoteCallback(ctx, remoteInstanceId) {
1009
- return (id, invoke, options) => ctx.getRemoteCallback(id, invoke, options, remoteInstanceId);
986
+ function handleConnectResponse(_data, ctx, meta) {
987
+ ctx.bindRemote(meta.senderInstanceId);
1010
988
  }
1011
-
1012
- const MAX_RELEASE_IDS = 100000;
1013
989
  function handleReleaseCallbacks(data, ctx, meta) {
1014
990
  const ids = data.ids;
1015
- const total = ids.length > MAX_RELEASE_IDS ? MAX_RELEASE_IDS : ids.length;
1016
- if (ids.length > MAX_RELEASE_IDS) {
1017
- warn('handleReleaseCallbacks', `releaseCallbacks payload exceeds cap (${ids.length} > ${MAX_RELEASE_IDS}); only the first ${MAX_RELEASE_IDS} ids will be released`);
1018
- }
1019
- const effectiveIds = total === ids.length ? ids : ids.slice(0, total);
1020
- ctx.releaseRemoteCallbacks(meta.senderInstanceId, effectiveIds);
1021
- for (let i = 0; i < total; i++) {
1022
- ctx.dropLocalCallback(ids[i]);
1023
- }
991
+ const total = ids.length;
992
+ ctx.releaseRemoteCallbacks(meta.senderInstanceId, ids);
993
+ let cursor = 0;
994
+ const sender = meta.senderInstanceId;
995
+ const processChunk = () => {
996
+ if (ctx.isDestroyed())
997
+ return;
998
+ const end = Math.min(cursor + RELEASE_CHUNK_SIZE, total);
999
+ for (let i = cursor; i < end; i++) {
1000
+ ctx.dropLocalCallback(ids[i], sender);
1001
+ }
1002
+ cursor = end;
1003
+ if (cursor < total)
1004
+ scheduleNextChunk(processChunk);
1005
+ };
1006
+ processChunk();
1024
1007
  }
1025
- function handleDestroyEndpoint(_data, ctx, meta) {
1008
+ function handleDestroyEndpoint(data, ctx, meta) {
1009
+ if (data.instanceId !== meta.senderInstanceId)
1010
+ return;
1026
1011
  ctx.handleRemoteDestroy(meta.senderInstanceId);
1027
1012
  }
1028
1013
 
@@ -1043,10 +1028,16 @@ const DEFAULT_FUNCTION_CACHE_MAX = 100;
1043
1028
  const DEFAULT_FUNCTION_CACHE_TTL = 5 * 60 * 1000;
1044
1029
  const DEFAULT_REMOTE_CALLBACK_CACHE_MAX = 500;
1045
1030
  const DEFAULT_REMOTE_CALLBACK_CACHE_TTL = 5 * 60 * 1000;
1031
+ const RELEASE_CALLBACKS_CHUNK_SIZE = 10000;
1032
+ const BROADCAST_OWNER = Symbol('chrome-in-iframe.broadcast-owner');
1046
1033
  function resolveTimeout(raw) {
1047
1034
  if (raw === undefined)
1048
1035
  return DEFAULT_TIMEOUT;
1049
- if (typeof raw !== 'number' || !Number.isFinite(raw) || raw <= 0)
1036
+ if (typeof raw !== 'number')
1037
+ return DEFAULT_TIMEOUT;
1038
+ if (raw === Infinity)
1039
+ return Infinity;
1040
+ if (!Number.isFinite(raw) || raw <= 0)
1050
1041
  return DEFAULT_TIMEOUT;
1051
1042
  return raw;
1052
1043
  }
@@ -1055,7 +1046,7 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
1055
1046
  const remoteCacheTtl = options.remoteCallbackCacheTtl ?? DEFAULT_REMOTE_CALLBACK_CACHE_TTL;
1056
1047
  const callTimeout = resolveTimeout(options.timeout);
1057
1048
  const pending = new Map();
1058
- const functionIds = new Map();
1049
+ const functionIds = new WeakMap();
1059
1050
  const persistentFunctionCache = new Map();
1060
1051
  const persistentRefcount = new Map();
1061
1052
  const functionCache = new LRUCache({
@@ -1072,7 +1063,71 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
1072
1063
  const remoteCallbacksByOwner = new Map();
1073
1064
  const persistentRemoteCallbacksByOwner = new Map();
1074
1065
  const boundMethodCache = new WeakMap();
1066
+ const callbackOwners = new Map();
1067
+ const knownRemotes = new Set();
1068
+ const remoteTargetWaiters = new Set();
1069
+ let boundRemoteInstanceId;
1070
+ let remoteTargetWaitDisabled = false;
1075
1071
  let destroyed = false;
1072
+ const armTimer = (onTimeout) => {
1073
+ if (callTimeout === Infinity)
1074
+ return null;
1075
+ return setTimeout(onTimeout, callTimeout);
1076
+ };
1077
+ const cancelTimer = (timer) => {
1078
+ if (timer !== null)
1079
+ clearTimeout(timer);
1080
+ };
1081
+ const getPreferredRemote = () => {
1082
+ if (boundRemoteInstanceId !== undefined)
1083
+ return boundRemoteInstanceId;
1084
+ if (knownRemotes.size !== 1)
1085
+ return undefined;
1086
+ return knownRemotes.values().next().value;
1087
+ };
1088
+ const resolveRemoteTargetWaiters = (remoteInstanceId) => {
1089
+ if (remoteTargetWaiters.size === 0)
1090
+ return;
1091
+ const waiters = Array.from(remoteTargetWaiters);
1092
+ remoteTargetWaiters.clear();
1093
+ for (const resolve of waiters)
1094
+ resolve(remoteInstanceId);
1095
+ };
1096
+ const waitForRemoteTarget = () => {
1097
+ const preferred = getPreferredRemote();
1098
+ if (preferred !== undefined)
1099
+ return Promise.resolve(preferred);
1100
+ if (destroyed)
1101
+ return Promise.resolve(undefined);
1102
+ return new Promise((resolve) => {
1103
+ remoteTargetWaiters.add(resolve);
1104
+ });
1105
+ };
1106
+ const recordCallbackOwners = (ids, owner) => {
1107
+ if (ids.length === 0)
1108
+ return;
1109
+ if (owner === 'broadcast') {
1110
+ for (const id of ids)
1111
+ callbackOwners.set(id, BROADCAST_OWNER);
1112
+ return;
1113
+ }
1114
+ for (const id of ids) {
1115
+ const cur = callbackOwners.get(id);
1116
+ if (cur === BROADCAST_OWNER)
1117
+ continue;
1118
+ if (cur === undefined) {
1119
+ callbackOwners.set(id, owner);
1120
+ continue;
1121
+ }
1122
+ if (typeof cur === 'string') {
1123
+ if (cur === owner)
1124
+ continue;
1125
+ callbackOwners.set(id, new Set([cur, owner]));
1126
+ continue;
1127
+ }
1128
+ cur.add(owner);
1129
+ }
1130
+ };
1076
1131
  const bindMethod = (fn, owner) => {
1077
1132
  if (owner === null || typeof owner !== 'object') {
1078
1133
  return (...args) => Reflect.apply(fn, owner, args);
@@ -1151,7 +1206,44 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
1151
1206
  removeFunctionIdMapping(entry.fn, entry.thisArg);
1152
1207
  return id;
1153
1208
  };
1154
- const dropLocalCallback = (id) => {
1209
+ const dropLocalCallback = (id, requesterInstanceId) => {
1210
+ if (requesterInstanceId !== undefined) {
1211
+ const cur = callbackOwners.get(id);
1212
+ if (cur === BROADCAST_OWNER) {
1213
+ const remaining = new Set();
1214
+ for (const r of knownRemotes) {
1215
+ if (r !== requesterInstanceId)
1216
+ remaining.add(r);
1217
+ }
1218
+ if (remaining.size > 0) {
1219
+ callbackOwners.set(id, remaining.size === 1 ? remaining.values().next().value : remaining);
1220
+ return;
1221
+ }
1222
+ }
1223
+ else if (typeof cur === 'string') {
1224
+ if (cur !== requesterInstanceId)
1225
+ return;
1226
+ }
1227
+ else if (cur instanceof Set) {
1228
+ if (!cur.has(requesterInstanceId))
1229
+ return;
1230
+ cur.delete(requesterInstanceId);
1231
+ if (cur.size > 1)
1232
+ return;
1233
+ if (cur.size === 1) {
1234
+ callbackOwners.set(id, cur.values().next().value);
1235
+ return;
1236
+ }
1237
+ }
1238
+ }
1239
+ if (persistentRefcount.has(id)) {
1240
+ const next = (persistentRefcount.get(id) ?? 0) - 1;
1241
+ if (next > 0) {
1242
+ persistentRefcount.set(id, next);
1243
+ return;
1244
+ }
1245
+ }
1246
+ callbackOwners.delete(id);
1155
1247
  const entry = persistentFunctionCache.get(id) ?? functionCache.get(id);
1156
1248
  persistentRefcount.delete(id);
1157
1249
  persistentFunctionCache.delete(id);
@@ -1183,19 +1275,32 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
1183
1275
  if (ids.length === 0)
1184
1276
  return;
1185
1277
  try {
1186
- getMessageChannel().getSender().sendMessage('releaseCallbacks', { ids });
1278
+ const sender = getMessageChannel().getSender();
1279
+ for (let cursor = 0; cursor < ids.length; cursor += RELEASE_CALLBACKS_CHUNK_SIZE) {
1280
+ sender.sendMessage('releaseCallbacks', { ids: ids.slice(cursor, cursor + RELEASE_CALLBACKS_CHUNK_SIZE) });
1281
+ }
1187
1282
  }
1188
1283
  catch (err) {
1189
1284
  warn('notifyReleaseCallbacks', 'failed to release callbacks', err);
1190
1285
  }
1191
1286
  };
1287
+ const safeOnReject = (callbacks) => {
1288
+ if (!callbacks.onReject)
1289
+ return;
1290
+ try {
1291
+ callbacks.onReject();
1292
+ }
1293
+ catch (err) {
1294
+ warn('rejectPending', 'onReject hook threw', err);
1295
+ }
1296
+ };
1192
1297
  const rejectPending = (id, error) => {
1193
1298
  const callbacks = pending.get(id);
1194
1299
  if (!callbacks)
1195
1300
  return;
1196
- clearTimeout(callbacks.timer);
1301
+ cancelTimer(callbacks.timer);
1197
1302
  pending.delete(id);
1198
- callbacks.onReject?.();
1303
+ safeOnReject(callbacks);
1199
1304
  callbacks.reject(error);
1200
1305
  };
1201
1306
  return {
@@ -1217,15 +1322,31 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
1217
1322
  registerFunction(fn, thisArg, callbackOptions) {
1218
1323
  return registerFunction(fn, thisArg, callbackOptions);
1219
1324
  },
1325
+ serializeForRemote(value, target, callbackOptions) {
1326
+ const ids = [];
1327
+ const tracking = (fn, thisArg, opts) => {
1328
+ const id = registerFunction(fn, thisArg, opts);
1329
+ ids.push(id);
1330
+ return id;
1331
+ };
1332
+ const result = serialize(value, tracking, callbackOptions);
1333
+ recordCallbackOwners(ids, target);
1334
+ return result;
1335
+ },
1220
1336
  getRemoteCallback(id, invoke, callbackOptions, remoteInstanceId) {
1221
1337
  return getRemoteCallback(id, invoke, callbackOptions, remoteInstanceId);
1222
1338
  },
1223
- getAndRemovePendingPromise(id) {
1339
+ getAndRemovePendingPromise(id, senderInstanceId) {
1224
1340
  const callbacks = pending.get(id);
1225
- if (callbacks) {
1226
- clearTimeout(callbacks.timer);
1227
- pending.delete(id);
1341
+ if (!callbacks)
1342
+ return undefined;
1343
+ if (callbacks.expectedRemote !== undefined &&
1344
+ senderInstanceId !== undefined &&
1345
+ callbacks.expectedRemote !== senderInstanceId) {
1346
+ return undefined;
1228
1347
  }
1348
+ cancelTimer(callbacks.timer);
1349
+ pending.delete(id);
1229
1350
  return callbacks;
1230
1351
  },
1231
1352
  invoke(path, args) {
@@ -1251,46 +1372,97 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
1251
1372
  }
1252
1373
  return released;
1253
1374
  };
1254
- const timer = setTimeout(() => {
1375
+ const cleanupRegisteredOnFailure = () => {
1376
+ const released = [];
1377
+ for (const cbId of registeredIds) {
1378
+ if (persistentRefcount.has(cbId)) {
1379
+ const releasedId = releasePersistentRegistrationById(cbId);
1380
+ if (releasedId)
1381
+ released.push(releasedId);
1382
+ }
1383
+ else {
1384
+ const entry = functionCache.get(cbId);
1385
+ if (entry) {
1386
+ functionCache.delete(cbId);
1387
+ removeFunctionIdMapping(entry.fn, entry.thisArg);
1388
+ }
1389
+ }
1390
+ callbackOwners.delete(cbId);
1391
+ }
1392
+ return released;
1393
+ };
1394
+ const timer = armTimer(() => {
1255
1395
  rejectPending(id, new Error(`Call timed out: ${String(path.join('.'))}`));
1256
- }, callTimeout);
1396
+ });
1397
+ let serializedArgs;
1257
1398
  try {
1258
- const channel = getMessageChannel();
1259
- const serializedArgs = args.map((arg) => serialize(arg, collectingRegister, { persistent }));
1260
- pending.set(id, {
1261
- resolve,
1262
- reject,
1263
- timer,
1264
- onResolve: () => {
1265
- if (isListenerRemovalPath(path)) {
1266
- const released = releaseRegisteredPersistents();
1267
- if (released.length > 0)
1268
- notifyReleaseCallbacks(released);
1269
- }
1270
- },
1271
- onReject: () => {
1272
- if (isListenerRegistrationPath(path)) {
1273
- const released = releaseRegisteredPersistents();
1274
- if (released.length > 0)
1275
- notifyReleaseCallbacks(released);
1276
- }
1277
- },
1278
- });
1279
- channel.getSender().sendMessage('invokeRequest', {
1280
- id,
1281
- path,
1282
- args: serializedArgs,
1283
- });
1399
+ serializePath(path);
1400
+ serializedArgs = args.map((arg) => serialize(arg, collectingRegister, { persistent }));
1284
1401
  }
1285
1402
  catch (err) {
1286
- clearTimeout(timer);
1287
- pending.delete(id);
1288
- if (isListenerRegistrationPath(path)) {
1289
- const released = releaseRegisteredPersistents();
1403
+ cancelTimer(timer);
1404
+ const released = cleanupRegisteredOnFailure();
1405
+ if (released.length > 0)
1406
+ notifyReleaseCallbacks(released);
1407
+ reject(err);
1408
+ return;
1409
+ }
1410
+ pending.set(id, {
1411
+ resolve,
1412
+ reject,
1413
+ timer,
1414
+ onResolve: () => {
1415
+ if (isListenerRemovalPath(path)) {
1416
+ const released = releaseRegisteredPersistents();
1417
+ if (released.length > 0)
1418
+ notifyReleaseCallbacks(released);
1419
+ }
1420
+ },
1421
+ onReject: () => {
1422
+ if (isListenerRegistrationPath(path)) {
1423
+ const released = releaseRegisteredPersistents();
1424
+ if (released.length > 0)
1425
+ notifyReleaseCallbacks(released);
1426
+ }
1427
+ },
1428
+ });
1429
+ const sendRequest = (targetInstanceId, requireTarget = true) => {
1430
+ const callbacks = pending.get(id);
1431
+ if (!callbacks)
1432
+ return;
1433
+ if (targetInstanceId === undefined && requireTarget) {
1434
+ rejectPending(id, new Error('No remote endpoint is available'));
1435
+ return;
1436
+ }
1437
+ if (targetInstanceId !== undefined)
1438
+ callbacks.expectedRemote = targetInstanceId;
1439
+ try {
1440
+ const channel = getMessageChannel();
1441
+ recordCallbackOwners(registeredIds, targetInstanceId ?? 'broadcast');
1442
+ channel.getSender().sendMessage('invokeRequest', {
1443
+ id,
1444
+ path,
1445
+ args: serializedArgs,
1446
+ }, targetInstanceId);
1447
+ }
1448
+ catch (err) {
1449
+ cancelTimer(timer);
1450
+ pending.delete(id);
1451
+ const released = cleanupRegisteredOnFailure();
1290
1452
  if (released.length > 0)
1291
1453
  notifyReleaseCallbacks(released);
1454
+ reject(err);
1292
1455
  }
1293
- reject(err);
1456
+ };
1457
+ const targetInstanceId = getPreferredRemote();
1458
+ if (targetInstanceId !== undefined) {
1459
+ sendRequest(targetInstanceId);
1460
+ }
1461
+ else if (remoteTargetWaitDisabled) {
1462
+ sendRequest(undefined, false);
1463
+ }
1464
+ else {
1465
+ void waitForRemoteTarget().then(sendRequest);
1294
1466
  }
1295
1467
  });
1296
1468
  },
@@ -1301,46 +1473,100 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
1301
1473
  return;
1302
1474
  }
1303
1475
  const id = createId('req');
1304
- const timer = setTimeout(() => {
1476
+ const timer = armTimer(() => {
1305
1477
  rejectPending(id, new Error(`Property access timed out: ${String(path.join('.'))}`));
1306
- }, callTimeout);
1478
+ });
1307
1479
  pending.set(id, { resolve, reject, timer });
1480
+ let pathSerializationError;
1308
1481
  try {
1309
- getMessageChannel().getSender().sendMessage('accessPropertyRequest', {
1310
- id,
1311
- path,
1312
- });
1482
+ serializePath(path);
1313
1483
  }
1314
1484
  catch (err) {
1315
- clearTimeout(timer);
1485
+ pathSerializationError = err;
1486
+ }
1487
+ if (pathSerializationError !== undefined) {
1488
+ cancelTimer(timer);
1316
1489
  pending.delete(id);
1317
- reject(err);
1490
+ reject(pathSerializationError);
1491
+ return;
1492
+ }
1493
+ const sendRequest = (targetInstanceId, requireTarget = true) => {
1494
+ const callbacks = pending.get(id);
1495
+ if (!callbacks)
1496
+ return;
1497
+ if (targetInstanceId === undefined && requireTarget) {
1498
+ rejectPending(id, new Error('No remote endpoint is available'));
1499
+ return;
1500
+ }
1501
+ if (targetInstanceId !== undefined)
1502
+ callbacks.expectedRemote = targetInstanceId;
1503
+ try {
1504
+ getMessageChannel().getSender().sendMessage('accessPropertyRequest', {
1505
+ id,
1506
+ path,
1507
+ }, targetInstanceId);
1508
+ }
1509
+ catch (err) {
1510
+ cancelTimer(timer);
1511
+ pending.delete(id);
1512
+ reject(err);
1513
+ }
1514
+ };
1515
+ const targetInstanceId = getPreferredRemote();
1516
+ if (targetInstanceId !== undefined) {
1517
+ sendRequest(targetInstanceId);
1518
+ }
1519
+ else if (remoteTargetWaitDisabled) {
1520
+ sendRequest(undefined, false);
1521
+ }
1522
+ else {
1523
+ void waitForRemoteTarget().then(sendRequest);
1318
1524
  }
1319
1525
  });
1320
1526
  },
1321
- invokeFunctionById(callbackId, args, callbackOptions = {}) {
1527
+ invokeFunctionById(callbackId, args, callbackOptions = {}, remoteInstanceId) {
1322
1528
  return new Promise((resolve, reject) => {
1323
1529
  if (destroyed) {
1324
1530
  reject(new Error('Channel has been destroyed'));
1325
1531
  return;
1326
1532
  }
1327
1533
  const callId = createId('cbcall');
1328
- const timer = setTimeout(() => {
1534
+ const timer = armTimer(() => {
1329
1535
  rejectPending(callId, new Error(`Callback call timed out: ${callbackId}`));
1330
- }, callTimeout);
1331
- pending.set(callId, { resolve, reject, timer });
1536
+ });
1537
+ pending.set(callId, { resolve, reject, timer, expectedRemote: remoteInstanceId });
1538
+ const registeredIds = [];
1539
+ const collectingRegister = (fn, thisArg, opts) => {
1540
+ const cbId = registerFunction(fn, thisArg, opts);
1541
+ registeredIds.push(cbId);
1542
+ return cbId;
1543
+ };
1332
1544
  try {
1333
- getMessageChannel()
1334
- .getSender()
1335
- .sendMessage('invokeFunctionByIdRequest', {
1545
+ const serializedArgs = args.map((arg) => serialize(arg, collectingRegister, callbackOptions));
1546
+ const targetInstanceId = remoteInstanceId ?? getPreferredRemote();
1547
+ recordCallbackOwners(registeredIds, targetInstanceId ?? 'broadcast');
1548
+ getMessageChannel().getSender().sendMessage('invokeFunctionByIdRequest', {
1336
1549
  id: callbackId,
1337
1550
  callId,
1338
- args: args.map((arg) => serialize(arg, registerFunction, callbackOptions)),
1339
- });
1551
+ args: serializedArgs,
1552
+ }, targetInstanceId);
1340
1553
  }
1341
1554
  catch (err) {
1342
- clearTimeout(timer);
1555
+ cancelTimer(timer);
1343
1556
  pending.delete(callId);
1557
+ for (const cbId of registeredIds) {
1558
+ if (persistentRefcount.has(cbId)) {
1559
+ releasePersistentRegistrationById(cbId);
1560
+ }
1561
+ else {
1562
+ const entry = functionCache.get(cbId);
1563
+ if (entry) {
1564
+ functionCache.delete(cbId);
1565
+ removeFunctionIdMapping(entry.fn, entry.thisArg);
1566
+ }
1567
+ }
1568
+ callbackOwners.delete(cbId);
1569
+ }
1344
1570
  reject(err);
1345
1571
  }
1346
1572
  });
@@ -1348,6 +1574,62 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
1348
1574
  handleRemoteDestroy(remoteInstanceId) {
1349
1575
  remoteCallbacksByOwner.delete(remoteInstanceId);
1350
1576
  persistentRemoteCallbacksByOwner.delete(remoteInstanceId);
1577
+ knownRemotes.delete(remoteInstanceId);
1578
+ if (boundRemoteInstanceId === remoteInstanceId) {
1579
+ boundRemoteInstanceId = undefined;
1580
+ const preferred = getPreferredRemote();
1581
+ if (preferred !== undefined)
1582
+ boundRemoteInstanceId = preferred;
1583
+ }
1584
+ for (const [id, owners] of callbackOwners) {
1585
+ if (owners === remoteInstanceId) {
1586
+ callbackOwners.delete(id);
1587
+ }
1588
+ else if (owners instanceof Set && owners.has(remoteInstanceId)) {
1589
+ owners.delete(remoteInstanceId);
1590
+ if (owners.size === 0)
1591
+ callbackOwners.delete(id);
1592
+ else if (owners.size === 1)
1593
+ callbackOwners.set(id, owners.values().next().value);
1594
+ }
1595
+ }
1596
+ const reason = new Error(`Remote endpoint ${remoteInstanceId} was destroyed`);
1597
+ const noKnownRemotesLeft = knownRemotes.size === 0;
1598
+ for (const [id, callbacks] of pending) {
1599
+ const targeted = callbacks.expectedRemote === remoteInstanceId;
1600
+ const stranded = callbacks.expectedRemote === undefined && noKnownRemotesLeft;
1601
+ if (!targeted && !stranded)
1602
+ continue;
1603
+ cancelTimer(callbacks.timer);
1604
+ pending.delete(id);
1605
+ safeOnReject(callbacks);
1606
+ callbacks.reject(reason);
1607
+ }
1608
+ },
1609
+ bindRemote(remoteInstanceId) {
1610
+ if (!remoteInstanceId || remoteInstanceId === instanceId)
1611
+ return;
1612
+ knownRemotes.add(remoteInstanceId);
1613
+ if (boundRemoteInstanceId === undefined) {
1614
+ boundRemoteInstanceId = remoteInstanceId;
1615
+ resolveRemoteTargetWaiters(remoteInstanceId);
1616
+ }
1617
+ },
1618
+ noteFreshConnect(remoteInstanceId) {
1619
+ if (!remoteInstanceId || remoteInstanceId === instanceId)
1620
+ return;
1621
+ const isNew = !knownRemotes.has(remoteInstanceId);
1622
+ knownRemotes.add(remoteInstanceId);
1623
+ if (boundRemoteInstanceId === remoteInstanceId)
1624
+ return;
1625
+ if (boundRemoteInstanceId === undefined || isNew) {
1626
+ boundRemoteInstanceId = remoteInstanceId;
1627
+ resolveRemoteTargetWaiters(remoteInstanceId);
1628
+ }
1629
+ },
1630
+ disableRemoteTargetWait() {
1631
+ remoteTargetWaitDisabled = true;
1632
+ resolveRemoteTargetWaiters(undefined);
1351
1633
  },
1352
1634
  releaseRemoteCallbacks(remoteInstanceId, ids) {
1353
1635
  const lru = remoteCallbacksByOwner.get(remoteInstanceId);
@@ -1359,8 +1641,16 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
1359
1641
  pmap?.delete(id);
1360
1642
  }
1361
1643
  },
1362
- dropLocalCallback(id) {
1363
- dropLocalCallback(id);
1644
+ dropLocalCallback(id, requesterInstanceId) {
1645
+ dropLocalCallback(id, requesterInstanceId);
1646
+ },
1647
+ noteRemoteSeen(remoteInstanceId) {
1648
+ if (remoteInstanceId && remoteInstanceId !== instanceId) {
1649
+ knownRemotes.add(remoteInstanceId);
1650
+ const preferred = getPreferredRemote();
1651
+ if (preferred !== undefined)
1652
+ resolveRemoteTargetWaiters(preferred);
1653
+ }
1364
1654
  },
1365
1655
  bindMethod(fn, owner) {
1366
1656
  return bindMethod(fn, owner);
@@ -1371,42 +1661,48 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
1371
1661
  persistent: persistentRemoteCallbacksByOwner.get(remoteInstanceId)?.size ?? 0,
1372
1662
  };
1373
1663
  },
1664
+ isDestroyed() {
1665
+ return destroyed;
1666
+ },
1374
1667
  destroy(notifyRemote = true) {
1375
1668
  if (destroyed)
1376
1669
  return;
1377
1670
  destroyed = true;
1378
1671
  if (notifyRemote) {
1379
- const heldRemoteIds = [];
1672
+ const heldRemoteIdSet = new Set();
1380
1673
  for (const lru of remoteCallbacksByOwner.values()) {
1381
1674
  for (const id of lru.keys())
1382
- heldRemoteIds.push(id);
1675
+ heldRemoteIdSet.add(id);
1383
1676
  }
1384
1677
  for (const map of persistentRemoteCallbacksByOwner.values()) {
1385
1678
  for (const id of map.keys())
1386
- heldRemoteIds.push(id);
1679
+ heldRemoteIdSet.add(id);
1680
+ }
1681
+ if (heldRemoteIdSet.size > 0) {
1682
+ notifyReleaseCallbacks(Array.from(heldRemoteIdSet));
1387
1683
  }
1388
1684
  try {
1389
- const sender = getMessageChannel().getSender();
1390
- if (heldRemoteIds.length > 0) {
1391
- sender.sendMessage('releaseCallbacks', { ids: heldRemoteIds });
1392
- }
1393
- sender.sendMessage('destroyEndpoint', { instanceId });
1685
+ getMessageChannel().getSender().sendMessage('destroyEndpoint', { instanceId });
1394
1686
  }
1395
1687
  catch (err) {
1396
- if (!isExpectedDestroyNotifyError(err)) {
1688
+ if (!isTransportDetachedError(err)) {
1397
1689
  warn('destroy', 'failed to notify remote of destroy', err);
1398
1690
  }
1399
1691
  }
1400
1692
  }
1401
1693
  for (const [id, callbacks] of pending) {
1402
- clearTimeout(callbacks.timer);
1694
+ cancelTimer(callbacks.timer);
1695
+ safeOnReject(callbacks);
1403
1696
  callbacks.reject(new Error(`Channel has been destroyed before call ${id} completed`));
1404
1697
  }
1405
1698
  pending.clear();
1406
1699
  functionCache.clear();
1407
1700
  persistentFunctionCache.clear();
1408
1701
  persistentRefcount.clear();
1409
- functionIds.clear();
1702
+ callbackOwners.clear();
1703
+ knownRemotes.clear();
1704
+ boundRemoteInstanceId = undefined;
1705
+ resolveRemoteTargetWaiters(undefined);
1410
1706
  remoteCallbacksByOwner.clear();
1411
1707
  persistentRemoteCallbacksByOwner.clear();
1412
1708
  },
@@ -1415,9 +1711,6 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
1415
1711
  function createId(prefix) {
1416
1712
  return `${prefix}-${nanoid()}`;
1417
1713
  }
1418
- function isExpectedDestroyNotifyError(err) {
1419
- return err instanceof Error && err.message.includes('contentWindow is not available');
1420
- }
1421
1714
 
1422
1715
  const EMPTY_TARGET = () => undefined;
1423
1716
  const INTERCEPTED_STRING_PROPS = new Set([
@@ -1481,38 +1774,55 @@ function createClientProxy(invoke, accessProperty) {
1481
1774
  });
1482
1775
  return [...basePath, ...extraPath];
1483
1776
  };
1484
- function createHandler(node) {
1485
- return {
1777
+ function createNode(node) {
1778
+ const stringChildren = new Map();
1779
+ const symbolChildren = new Map();
1780
+ let getCache;
1781
+ const handler = {
1486
1782
  get(_target, prop) {
1487
1783
  if (typeof prop === 'string') {
1488
1784
  if (INTERCEPTED_STRING_PROPS.has(prop))
1489
1785
  return undefined;
1490
1786
  if (prop === '$get') {
1491
- return (...parts) => {
1492
- if (!accessProperty) {
1493
- return Promise.reject(new Error('Property access is not configured'));
1494
- }
1495
- const basePath = node ? resolvePath(node) : [];
1496
- return accessProperty(normalizePropertyPath(basePath, parts));
1497
- };
1787
+ if (!getCache) {
1788
+ getCache = (...parts) => {
1789
+ if (!accessProperty) {
1790
+ return Promise.reject(new Error('Property access is not configured'));
1791
+ }
1792
+ const basePath = node ? resolvePath(node) : [];
1793
+ return accessProperty(normalizePropertyPath(basePath, parts));
1794
+ };
1795
+ }
1796
+ return getCache;
1498
1797
  }
1798
+ const cached = stringChildren.get(prop);
1799
+ if (cached !== undefined)
1800
+ return cached;
1801
+ const childNode = node ? { parent: node, key: prop } : { key: prop };
1802
+ const child = createNode(childNode);
1803
+ stringChildren.set(prop, child);
1804
+ return child;
1499
1805
  }
1500
- else if (prop === Symbol.toPrimitive) {
1806
+ if (prop === Symbol.toPrimitive)
1501
1807
  return toPrimitiveDescriptor();
1502
- }
1503
- else if (INTERCEPTED_NONE_SYMBOLS.has(prop)) {
1808
+ if (INTERCEPTED_NONE_SYMBOLS.has(prop))
1504
1809
  return undefined;
1505
- }
1810
+ const cached = symbolChildren.get(prop);
1811
+ if (cached !== undefined)
1812
+ return cached;
1506
1813
  const childNode = node ? { parent: node, key: prop } : { key: prop };
1507
- return new Proxy(EMPTY_TARGET, createHandler(childNode));
1814
+ const child = createNode(childNode);
1815
+ symbolChildren.set(prop, child);
1816
+ return child;
1508
1817
  },
1509
1818
  apply(_target, _thisArg, argArray) {
1510
1819
  const path = node ? resolvePath(node) : [];
1511
1820
  return invoke(path, argArray);
1512
1821
  },
1513
1822
  };
1823
+ return new Proxy(EMPTY_TARGET, handler);
1514
1824
  }
1515
- return new Proxy(EMPTY_TARGET, createHandler());
1825
+ return createNode();
1516
1826
  }
1517
1827
  function isPathKey(value) {
1518
1828
  return typeof value === 'string' || typeof value === 'symbol';
@@ -1531,6 +1841,8 @@ function createEndpoint(options) {
1531
1841
  const context = createClientContext(getChannel, instanceId, options.contextOptions);
1532
1842
  const channel = createMessageChannel(options.poster, options.key ?? 'default', instanceId, context, processorRegistry);
1533
1843
  channelRef.current = channel;
1844
+ processorRegistry.register('connectRequest', handleConnectRequest);
1845
+ processorRegistry.register('connectResponse', handleConnectResponse);
1534
1846
  processorRegistry.register('invokeRequest', handleInvokeRequest);
1535
1847
  processorRegistry.register('invokeResponse', handleInvokeResponse);
1536
1848
  processorRegistry.register('accessPropertyRequest', handleAccessPropertyRequest);
@@ -1540,67 +1852,64 @@ function createEndpoint(options) {
1540
1852
  processorRegistry.register('releaseCallbacks', handleReleaseCallbacks);
1541
1853
  processorRegistry.register('destroyEndpoint', handleDestroyEndpoint);
1542
1854
  const proxy = createClientProxy((path, args) => context.invoke(path, args), (path) => context.accessProperty(path));
1855
+ try {
1856
+ channel.getSender().sendMessage('connectRequest', { instanceId });
1857
+ }
1858
+ catch {
1859
+ context.disableRemoteTargetWait();
1860
+ }
1543
1861
  let destroyed = false;
1544
1862
  return {
1545
1863
  proxy,
1546
1864
  getContext: () => context,
1865
+ isDestroyed: () => destroyed,
1547
1866
  destroy() {
1548
1867
  if (destroyed)
1549
1868
  return;
1550
1869
  destroyed = true;
1551
- context.destroy(true);
1552
1870
  channel.destroy();
1871
+ context.destroy(true);
1553
1872
  },
1554
1873
  };
1555
1874
  }
1556
1875
 
1557
1876
  function setupInMainWindow(options) {
1558
- const resolveTargetOrigin = createTargetOriginResolver(() => options.iframe, options.targetOrigin);
1559
- const allowedOrigin = createAllowedOrigin(options.allowedOrigin, resolveTargetOrigin);
1560
- const poster = createWindowPoster({
1877
+ const resolveTargetOrigin = createTargetOriginResolver(() => options.iframe, options.targetOrigin, options.strictOrigin === true);
1878
+ return setupConnection(options, {
1879
+ resolveTargetOrigin,
1561
1880
  postMessage: (message) => {
1562
1881
  const contentWindow = options.iframe.contentWindow;
1563
1882
  if (!contentWindow) {
1564
- throw new Error('chrome-in-iframe: iframe contentWindow is not available');
1883
+ throw createTransportDetachedError('chrome-in-iframe: iframe contentWindow is not available');
1565
1884
  }
1566
1885
  contentWindow.postMessage(message, resolveTargetOrigin());
1567
1886
  },
1568
1887
  getExpectedSource: () => options.iframe.contentWindow,
1569
- allowedOrigin,
1888
+ delegateTarget: options.delegateTarget,
1570
1889
  });
1571
- const endpoint = createEndpoint({
1572
- poster,
1573
- key: options.key,
1574
- contextOptions: {
1575
- delegateTarget: options.delegateTarget,
1576
- timeout: options.timeout,
1577
- functionCacheMax: options.functionCacheMax,
1578
- functionCacheTtl: options.functionCacheTtl,
1579
- remoteCallbackCacheMax: options.remoteCallbackCacheMax,
1580
- remoteCallbackCacheTtl: options.remoteCallbackCacheTtl,
1581
- },
1582
- });
1583
- const proxy = endpoint.proxy;
1584
- return {
1585
- proxy,
1586
- get: proxy.$get,
1587
- destroy: endpoint.destroy,
1588
- };
1589
1890
  }
1590
1891
  function setupInIframe(options = {}) {
1591
- const resolveTargetOrigin = createParentTargetOriginResolver(options.targetOrigin);
1592
- const allowedOrigin = createAllowedOrigin(options.allowedOrigin, resolveTargetOrigin);
1593
- const poster = createWindowPoster({
1892
+ const resolveTargetOrigin = createParentTargetOriginResolver(options.targetOrigin, options.strictOrigin === true);
1893
+ return setupConnection(options, {
1894
+ resolveTargetOrigin,
1594
1895
  postMessage: (message) => {
1595
1896
  window.parent.postMessage(message, resolveTargetOrigin());
1596
1897
  },
1597
1898
  getExpectedSource: () => window.parent,
1899
+ });
1900
+ }
1901
+ function setupConnection(options, sides) {
1902
+ const allowedOrigin = createAllowedOrigin(options.allowedOrigin, sides.resolveTargetOrigin);
1903
+ const poster = createWindowPoster({
1904
+ postMessage: sides.postMessage,
1905
+ getExpectedSource: sides.getExpectedSource,
1598
1906
  allowedOrigin,
1599
1907
  });
1600
1908
  const endpoint = createEndpoint({
1601
1909
  poster,
1602
1910
  key: options.key,
1603
1911
  contextOptions: {
1912
+ delegateTarget: sides.delegateTarget,
1604
1913
  timeout: options.timeout,
1605
1914
  functionCacheMax: options.functionCacheMax,
1606
1915
  functionCacheTtl: options.functionCacheTtl,
@@ -1613,6 +1922,7 @@ function setupInIframe(options = {}) {
1613
1922
  proxy,
1614
1923
  get: proxy.$get,
1615
1924
  destroy: endpoint.destroy,
1925
+ isDestroyed: endpoint.isDestroyed,
1616
1926
  };
1617
1927
  }
1618
1928
  function exposeChromeInIframe(options) {
@@ -1629,46 +1939,85 @@ function connectChromeInIframe(options = {}) {
1629
1939
  };
1630
1940
  }
1631
1941
  function createWindowPoster(options) {
1632
- const listenerMap = new Map();
1633
- const getOrCreatePerName = (name) => {
1634
- let perName = listenerMap.get(name);
1635
- if (!perName) {
1636
- perName = new Map();
1637
- listenerMap.set(name, perName);
1638
- }
1639
- return perName;
1640
- };
1942
+ const subscriptions = new Map();
1641
1943
  return {
1642
1944
  postMessage(message) {
1643
1945
  options.postMessage(message);
1644
1946
  },
1645
1947
  addEventListener(name, callback) {
1646
- const perName = getOrCreatePerName(name);
1647
- const existing = perName.get(callback);
1948
+ const existing = subscriptions.get(callback);
1648
1949
  if (existing)
1649
- window.removeEventListener(name, existing);
1650
- const listener = (event) => {
1651
- const messageEvent = event;
1652
- if (messageEvent.source !== options.getExpectedSource())
1653
- return;
1654
- if (!isAllowedOrigin(messageEvent.origin, options.allowedOrigin))
1655
- return;
1656
- callback(messageEvent);
1950
+ windowDispatcher.remove(name, existing);
1951
+ const sub = {
1952
+ isExpectedSource: (source) => source === options.getExpectedSource(),
1953
+ isAllowedOrigin: (origin) => isAllowedOrigin(origin, options.allowedOrigin),
1954
+ deliver: callback,
1657
1955
  };
1658
- perName.set(callback, listener);
1659
- window.addEventListener(name, listener);
1956
+ subscriptions.set(callback, sub);
1957
+ windowDispatcher.add(name, sub);
1660
1958
  },
1661
1959
  removeEventListener(name, callback) {
1662
- const perName = listenerMap.get(name);
1663
- if (!perName)
1960
+ const sub = subscriptions.get(callback);
1961
+ if (!sub)
1962
+ return;
1963
+ windowDispatcher.remove(name, sub);
1964
+ subscriptions.delete(callback);
1965
+ },
1966
+ };
1967
+ }
1968
+ const windowDispatcher = createWindowDispatcher();
1969
+ function createWindowDispatcher() {
1970
+ const perName = new Map();
1971
+ const domListeners = new Map();
1972
+ const ensureDomListener = (name) => {
1973
+ if (domListeners.has(name))
1974
+ return;
1975
+ if (typeof window === 'undefined')
1976
+ return;
1977
+ const handler = (event) => {
1978
+ const messageEvent = event;
1979
+ const subs = perName.get(name);
1980
+ if (!subs || subs.size === 0)
1981
+ return;
1982
+ for (const sub of subs) {
1983
+ if (!sub.isExpectedSource(messageEvent.source))
1984
+ continue;
1985
+ if (!sub.isAllowedOrigin(messageEvent.origin))
1986
+ continue;
1987
+ try {
1988
+ sub.deliver({ data: messageEvent.data });
1989
+ }
1990
+ catch (err) {
1991
+ warn('windowDispatcher', 'subscriber threw while delivering message', err);
1992
+ }
1993
+ }
1994
+ };
1995
+ domListeners.set(name, handler);
1996
+ window.addEventListener(name, handler);
1997
+ };
1998
+ return {
1999
+ add(name, sub) {
2000
+ let subs = perName.get(name);
2001
+ if (!subs) {
2002
+ subs = new Set();
2003
+ perName.set(name, subs);
2004
+ }
2005
+ subs.add(sub);
2006
+ ensureDomListener(name);
2007
+ },
2008
+ remove(name, sub) {
2009
+ const subs = perName.get(name);
2010
+ if (!subs)
1664
2011
  return;
1665
- const listener = perName.get(callback);
1666
- if (!listener)
2012
+ subs.delete(sub);
2013
+ if (subs.size > 0)
1667
2014
  return;
1668
- window.removeEventListener(name, listener);
1669
- perName.delete(callback);
1670
- if (perName.size === 0)
1671
- listenerMap.delete(name);
2015
+ perName.delete(name);
2016
+ const handler = domListeners.get(name);
2017
+ if (handler && typeof window !== 'undefined') {
2018
+ window.removeEventListener(name, handler);
2019
+ }
2020
+ domListeners.delete(name);
1672
2021
  },
1673
2022
  };
1674
2023
  }
@@ -1686,27 +2035,46 @@ function createAllowedOrigin(explicit, resolveTargetOrigin) {
1686
2035
  return explicit;
1687
2036
  return (origin) => {
1688
2037
  const targetOrigin = resolveTargetOrigin();
1689
- return targetOrigin === '*' || targetOrigin === origin;
2038
+ if (targetOrigin === '*') {
2039
+ if (typeof location !== 'undefined' && location.origin && location.origin !== 'null') {
2040
+ return origin === location.origin;
2041
+ }
2042
+ return false;
2043
+ }
2044
+ return targetOrigin === origin;
1690
2045
  };
1691
2046
  }
1692
2047
  function getGlobalChrome() {
1693
2048
  const chromeApi = globalThis.chrome;
1694
2049
  if (!chromeApi) {
1695
- throw new Error('chrome-in-iframe: global chrome API is not available');
2050
+ throw new Error('chrome-in-iframe: global `chrome` API is not available. ' +
2051
+ 'Make sure this runs inside a Chrome extension page, or pass a custom ' +
2052
+ '`chromeApi` option (e.g. for tests / non-extension environments).');
1696
2053
  }
1697
2054
  return chromeApi;
1698
2055
  }
1699
- function createTargetOriginResolver(getIframe, explicit) {
2056
+ function createTargetOriginResolver(getIframe, explicit, strict) {
1700
2057
  if (explicit)
1701
2058
  return () => explicit;
1702
- return () => deriveOriginFromIframe(getIframe());
2059
+ return freezeResolvedOrigin(() => deriveOriginFromIframe(getIframe(), strict === true));
1703
2060
  }
1704
- function createParentTargetOriginResolver(explicit) {
2061
+ function createParentTargetOriginResolver(explicit, strict) {
1705
2062
  if (explicit)
1706
2063
  return () => explicit;
1707
- return () => deriveParentOrigin();
2064
+ return freezeResolvedOrigin(() => deriveParentOrigin(strict === true));
2065
+ }
2066
+ function freezeResolvedOrigin(resolve) {
2067
+ let frozen;
2068
+ return () => {
2069
+ if (frozen !== undefined)
2070
+ return frozen;
2071
+ const resolved = resolve();
2072
+ if (resolved !== '*')
2073
+ frozen = resolved;
2074
+ return resolved;
2075
+ };
1708
2076
  }
1709
- function deriveOriginFromIframe(iframe) {
2077
+ function deriveOriginFromIframe(iframe, strict) {
1710
2078
  const src = iframe.src || iframe.getAttribute('src') || '';
1711
2079
  const origin = parseOrigin(src);
1712
2080
  if (origin)
@@ -1721,11 +2089,14 @@ function deriveOriginFromIframe(iframe) {
1721
2089
  catch (err) {
1722
2090
  warn('deriveOriginFromIframe', 'unable to access cross-origin contentWindow.location', err);
1723
2091
  }
2092
+ if (strict) {
2093
+ throw new Error('chrome-in-iframe: unable to derive iframe origin automatically; pass `targetOrigin` explicitly or set `strictOrigin: false`.');
2094
+ }
1724
2095
  warn('deriveOriginFromIframe', "unable to derive iframe origin automatically; falling back to '*'. " +
1725
2096
  'Pass `targetOrigin` explicitly to lock the destination.');
1726
2097
  return '*';
1727
2098
  }
1728
- function deriveParentOrigin() {
2099
+ function deriveParentOrigin(strict) {
1729
2100
  if (typeof document !== 'undefined') {
1730
2101
  const referrer = document.referrer;
1731
2102
  const origin = parseOrigin(referrer);
@@ -1737,6 +2108,9 @@ function deriveParentOrigin() {
1737
2108
  if (first && first !== 'null')
1738
2109
  return first;
1739
2110
  }
2111
+ if (strict) {
2112
+ throw new Error('chrome-in-iframe: unable to derive parent origin automatically; pass `targetOrigin` explicitly or set `strictOrigin: false`.');
2113
+ }
1740
2114
  warn('deriveParentOrigin', "unable to derive parent origin automatically; falling back to '*'. " +
1741
2115
  'Pass `targetOrigin` explicitly to lock the destination.');
1742
2116
  return '*';
@@ -1756,4 +2130,4 @@ function parseOrigin(url) {
1756
2130
  return null;
1757
2131
  }
1758
2132
 
1759
- export { connectChromeInIframe, exposeChromeInIframe, setLogger, setupInIframe, setupInMainWindow };
2133
+ export { TRANSPORT_DETACHED, connectChromeInIframe, exposeChromeInIframe, isTransportDetachedError, setLogger, setupInIframe, setupInMainWindow };