chrome-in-iframe 1.0.1 → 2.0.1

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,5 +1,21 @@
1
- import { LRUCache } from 'lru-cache';
2
1
  import { nanoid } from 'nanoid';
2
+ import { LRUCache } from 'lru-cache';
3
+
4
+ const PREFIX = 'chrome-in-iframe';
5
+ const defaultLogger = (scope, ...args) => {
6
+ if (typeof console !== 'undefined' && typeof console.warn === 'function') {
7
+ console.warn(`[${PREFIX}] ${scope}`, ...args);
8
+ }
9
+ };
10
+ let activeLogger = defaultLogger;
11
+ function setLogger(logger) {
12
+ activeLogger = logger;
13
+ }
14
+ function warn(scope, ...args) {
15
+ if (!activeLogger)
16
+ return;
17
+ activeLogger(scope, ...args);
18
+ }
3
19
 
4
20
  const WELL_KNOWN_SYMBOLS = {
5
21
  asyncIterator: Symbol.asyncIterator,
@@ -16,12 +32,9 @@ const WELL_KNOWN_SYMBOLS = {
16
32
  toStringTag: Symbol.toStringTag,
17
33
  unscopables: Symbol.unscopables,
18
34
  };
35
+ const WELL_KNOWN_SYMBOL_NAMES = new Map(Object.entries(WELL_KNOWN_SYMBOLS).map(([name, symbol]) => [symbol, name]));
19
36
  function getWellKnownSymbolName(value) {
20
- for (const [name, symbol] of Object.entries(WELL_KNOWN_SYMBOLS)) {
21
- if (symbol === value)
22
- return name;
23
- }
24
- return undefined;
37
+ return WELL_KNOWN_SYMBOL_NAMES.get(value);
25
38
  }
26
39
  function getWellKnownSymbol(name) {
27
40
  return WELL_KNOWN_SYMBOLS[name];
@@ -32,6 +45,11 @@ function serializeThrownError(err) {
32
45
  }
33
46
  return { message: String(err) };
34
47
  }
48
+ function isPromiseLike(value) {
49
+ return (value !== null &&
50
+ (typeof value === 'object' || typeof value === 'function') &&
51
+ typeof value.then === 'function');
52
+ }
35
53
 
36
54
  function serializePath(path) {
37
55
  return path.map((key) => {
@@ -82,10 +100,17 @@ function deserializeSymbol$1(value) {
82
100
  throw new TypeError('Cannot deserialize symbol path key');
83
101
  }
84
102
 
85
- function createMessageSender(poster, key) {
103
+ function createMessageSender(poster, key, instanceId) {
86
104
  return {
87
- sendMessage(type, data) {
88
- const body = { type, key, data: serializeMessageData(type, data) };
105
+ sendMessage(type, data, targetInstanceId) {
106
+ const body = {
107
+ type,
108
+ key,
109
+ data: serializeMessageData(type, data),
110
+ senderInstanceId: instanceId,
111
+ };
112
+ if (targetInstanceId)
113
+ body.targetInstanceId = targetInstanceId;
89
114
  poster.postMessage(JSON.stringify(body));
90
115
  },
91
116
  };
@@ -108,25 +133,40 @@ function serializeMessageData(type, data) {
108
133
  return data;
109
134
  }
110
135
 
111
- function createMessageChannel(poster, key, context, processorRegistry) {
112
- const sender = createMessageSender(poster, key);
136
+ function createMessageChannel(poster, key, instanceId, context, processorRegistry) {
137
+ const sender = createMessageSender(poster, key, instanceId);
138
+ const keyMatch = `"key":${JSON.stringify(key)}`;
113
139
  const listener = (event) => {
140
+ if (typeof event.data !== 'string')
141
+ return;
142
+ if (event.data.length === 0 || event.data.charCodeAt(0) !== 123 /* '{' */)
143
+ return;
144
+ if (event.data.indexOf(keyMatch) === -1)
145
+ return;
114
146
  let body;
115
147
  try {
116
148
  body = JSON.parse(event.data);
117
149
  }
118
- catch {
150
+ catch (err) {
151
+ warn('createMessageChannel', 'failed to parse incoming message as JSON', err);
119
152
  return;
120
153
  }
121
- if (!isMessageBody(body))
154
+ if (!isMessageEnvelope(body))
122
155
  return;
123
156
  if (body.key !== key)
124
157
  return;
158
+ if (body.senderInstanceId === instanceId)
159
+ return;
160
+ if (body.targetInstanceId !== undefined && body.targetInstanceId !== instanceId)
161
+ return;
125
162
  const handler = processorRegistry.get(body.type);
126
163
  if (!handler)
127
164
  return;
165
+ if (!isValidMessagePayload(body))
166
+ return;
167
+ const meta = { senderInstanceId: body.senderInstanceId };
128
168
  try {
129
- handler(deserializeMessageData(body), context);
169
+ handler(deserializeMessageData(body), context, meta);
130
170
  }
131
171
  catch (err) {
132
172
  sendProcessorError(body, sender, err);
@@ -137,27 +177,25 @@ function createMessageChannel(poster, key, context, processorRegistry) {
137
177
  getSender() {
138
178
  return sender;
139
179
  },
140
- getContext() {
141
- return context;
142
- },
143
- getPoster() {
144
- return poster;
145
- },
146
- getKey() {
147
- return key;
148
- },
149
180
  destroy() {
150
181
  poster.removeEventListener('message', listener);
151
182
  },
152
183
  };
153
184
  }
154
- function isMessageBody(value) {
185
+ function isMessageEnvelope(value) {
155
186
  if (!isRecord(value))
156
187
  return false;
157
- if (typeof value.type !== 'string')
188
+ if (typeof value.type !== 'string' || value.type.length === 0)
158
189
  return false;
159
190
  if (typeof value.key !== 'string')
160
191
  return false;
192
+ if (typeof value.senderInstanceId !== 'string' || value.senderInstanceId.length === 0)
193
+ return false;
194
+ if ('targetInstanceId' in value && typeof value.targetInstanceId !== 'string' && value.targetInstanceId !== undefined)
195
+ return false;
196
+ return true;
197
+ }
198
+ function isValidMessagePayload(value) {
161
199
  switch (value.type) {
162
200
  case 'invokeRequest':
163
201
  return isInvokeRequest(value.data);
@@ -171,6 +209,10 @@ function isMessageBody(value) {
171
209
  return isCallbackRequest(value.data);
172
210
  case 'invokeFunctionByIdResponse':
173
211
  return isResponse(value.data);
212
+ case 'releaseCallbacks':
213
+ return isReleaseCallbacks(value.data);
214
+ case 'destroyEndpoint':
215
+ return isDestroyEndpoint(value.data);
174
216
  default:
175
217
  return false;
176
218
  }
@@ -182,7 +224,7 @@ function isAccessPropertyRequest(value) {
182
224
  return isRecord(value) && isMessageId(value.id) && isSerializedPath(value.path);
183
225
  }
184
226
  function isCallbackRequest(value) {
185
- return isRecord(value) && typeof value.id === 'string' && isMessageId(value.callId) && Array.isArray(value.args);
227
+ return isRecord(value) && isMessageId(value.id) && isMessageId(value.callId) && Array.isArray(value.args);
186
228
  }
187
229
  function isResponse(value) {
188
230
  return (isRecord(value) &&
@@ -190,6 +232,12 @@ function isResponse(value) {
190
232
  (!('error' in value) || isSerializedError(value.error)) &&
191
233
  ('data' in value || 'error' in value));
192
234
  }
235
+ function isReleaseCallbacks(value) {
236
+ return isRecord(value) && Array.isArray(value.ids) && value.ids.every((id) => typeof id === 'string');
237
+ }
238
+ function isDestroyEndpoint(value) {
239
+ return isRecord(value) && typeof value.instanceId === 'string';
240
+ }
193
241
  function isSerializedError(value) {
194
242
  return (isRecord(value) && typeof value.message === 'string' && (!('stack' in value) || typeof value.stack === 'string'));
195
243
  }
@@ -222,7 +270,7 @@ function sendProcessorError(body, sender, err) {
222
270
  sender.sendMessage('invokeResponse', {
223
271
  id: data.id,
224
272
  error: serializeThrownError(err),
225
- });
273
+ }, body.senderInstanceId);
226
274
  return;
227
275
  }
228
276
  if (body.type === 'accessPropertyRequest') {
@@ -230,113 +278,161 @@ function sendProcessorError(body, sender, err) {
230
278
  sender.sendMessage('accessPropertyResponse', {
231
279
  id: data.id,
232
280
  error: serializeThrownError(err),
233
- });
281
+ }, body.senderInstanceId);
234
282
  return;
235
283
  }
236
284
  if (body.type === 'invokeFunctionByIdRequest') {
237
285
  const data = body.data;
238
286
  const error = serializeThrownError(err);
239
- console.warn(`chrome-in-iframe: callback '${data.id}' failed: ${error.message}`);
287
+ warn('sendProcessorError', `callback '${data.id}' failed: ${error.message}`);
240
288
  sender.sendMessage('invokeFunctionByIdResponse', {
241
289
  id: data.callId,
242
290
  error,
243
- });
291
+ }, body.senderInstanceId);
244
292
  }
245
293
  }
246
294
 
247
- function deserialize(arg, generateCallback, getRemoteCallback, options = {}) {
248
- return deserialize0(arg, generateCallback, getRemoteCallback, options);
295
+ const TYPE_KEY = '$cii$';
296
+ function serialize(arg, registerFunction, options = {}) {
297
+ return serialize0(arg, undefined, registerFunction, options, new WeakSet());
249
298
  }
250
- function deserialize0(arg, generateCallback, getRemoteCallback, options = {}) {
251
- if (typeof arg === 'string') {
252
- return deserializePrimitive(arg, generateCallback, getRemoteCallback, options);
299
+ function serialize0(arg, owner, registerFunction, options, seen) {
300
+ if (arg === undefined)
301
+ return { [TYPE_KEY]: 'undef' };
302
+ if (arg === null)
303
+ return null;
304
+ const typeOf = typeof arg;
305
+ if (typeOf === 'string')
306
+ return arg;
307
+ if (typeOf === 'boolean')
308
+ return arg;
309
+ if (typeOf === 'number')
310
+ return serializeNumber(arg);
311
+ if (typeOf === 'bigint')
312
+ return { [TYPE_KEY]: 'big', v: arg.toString() };
313
+ if (typeOf === 'function') {
314
+ const id = registerFunction(arg, owner, options);
315
+ return options.persistent ? { [TYPE_KEY]: 'fn', id, persistent: true } : { [TYPE_KEY]: 'fn', id };
253
316
  }
254
- if (typeof arg === 'object' && arg !== null) {
255
- if (Array.isArray(arg)) {
256
- return arg.map((item) => deserialize0(item, generateCallback, getRemoteCallback, options));
257
- }
258
- const source = arg;
259
- const result = {};
260
- for (const key of Object.keys(arg)) {
261
- setOwnProperty$1(result, key, deserialize0(source[key], generateCallback, getRemoteCallback, options));
317
+ if (typeOf === 'symbol')
318
+ return { [TYPE_KEY]: 'sym', v: serializeSymbol(arg) };
319
+ if (typeOf === 'object') {
320
+ if (seen.has(arg)) {
321
+ throw new TypeError('Cannot serialize circular structure');
262
322
  }
263
- return result;
264
- }
265
- return arg;
266
- }
267
- function deserializePrimitive(value, generateCallback, getRemoteCallback, options = {}) {
268
- const undefinedPrefix = '$undefined$';
269
- const nullPrefix = '$null$';
270
- const errorPrefix = '$error$';
271
- const functionPrefix = '$function$';
272
- if (value === undefinedPrefix)
273
- return undefined;
274
- if (value === nullPrefix)
275
- return null;
276
- if (value.startsWith(errorPrefix)) {
277
- let serialized;
323
+ seen.add(arg);
278
324
  try {
279
- serialized = JSON.parse(value.slice(errorPrefix.length));
280
- }
281
- catch {
282
- return new Error('Unable to deserialize remote error');
283
- }
284
- const error = new Error(serialized.message);
285
- error.stack = serialized.stack;
286
- return error;
287
- }
288
- if (value.startsWith(functionPrefix)) {
289
- const { id, options: functionOptions } = parseSerializedFunction(value.slice(functionPrefix.length), options);
290
- const invoke = (args) => {
291
- if (functionOptions.persistent) {
292
- return generateCallback(id, args, functionOptions);
325
+ if (arg instanceof Error)
326
+ return serializeError(arg, owner, registerFunction, options, seen);
327
+ if (arg instanceof Date)
328
+ return { [TYPE_KEY]: 'date', v: arg.getTime() };
329
+ if (arg instanceof RegExp)
330
+ return { [TYPE_KEY]: 're', source: arg.source, flags: arg.flags };
331
+ if (arg instanceof Map) {
332
+ const entries = [];
333
+ for (const [k, v] of arg.entries()) {
334
+ entries.push([
335
+ serialize0(k, arg, registerFunction, options, seen),
336
+ serialize0(v, arg, registerFunction, options, seen),
337
+ ]);
338
+ }
339
+ return { [TYPE_KEY]: 'map', entries };
293
340
  }
294
- else {
295
- return generateCallback(id, args);
341
+ if (arg instanceof Set) {
342
+ const values = [];
343
+ for (const v of arg.values()) {
344
+ values.push(serialize0(v, arg, registerFunction, options, seen));
345
+ }
346
+ return { [TYPE_KEY]: 'set', values };
296
347
  }
297
- };
298
- return getRemoteCallback
299
- ? getRemoteCallback(id, invoke, functionOptions)
300
- : (...args) => invoke(args);
301
- }
302
- const match = value.match(/^\$(\w+)\$(.*)$/s);
303
- if (match) {
304
- const [, type, raw] = match;
305
- switch (type) {
306
- case 'number':
307
- return Number(raw);
308
- case 'string':
309
- return raw;
310
- case 'boolean':
311
- return raw === 'true';
312
- case 'bigint':
313
- return BigInt(raw);
314
- case 'symbol':
315
- return deserializeSymbol(raw);
348
+ if (Array.isArray(arg)) {
349
+ return arg.map((item) => serialize0(item, arg, registerFunction, options, seen));
350
+ }
351
+ return serializePlainObject(arg, registerFunction, options, seen);
352
+ }
353
+ finally {
354
+ seen.delete(arg);
316
355
  }
317
356
  }
318
- return value;
357
+ return null;
319
358
  }
320
- function parseSerializedFunction(value, options) {
321
- const persistentPrefix = 'persistent:';
322
- if (value.startsWith(persistentPrefix)) {
323
- return {
324
- id: value.slice(persistentPrefix.length),
325
- options: { ...options, persistent: true },
326
- };
359
+ function serializeError(err, owner, registerFunction, options, seen) {
360
+ const result = { [TYPE_KEY]: 'err', message: err.message };
361
+ if (err.name && err.name !== 'Error')
362
+ result.name = err.name;
363
+ if (err.stack !== undefined)
364
+ result.stack = err.stack;
365
+ const cause = err.cause;
366
+ if (cause !== undefined) {
367
+ result.cause = serialize0(cause, owner, registerFunction, options, seen);
327
368
  }
328
- return { id: value, options };
369
+ const RESERVED_ERROR_KEYS = new Set(['message', 'name', 'stack', 'cause']);
370
+ const extras = [];
371
+ for (const k of Reflect.ownKeys(err)) {
372
+ if (typeof k === 'symbol')
373
+ continue;
374
+ if (RESERVED_ERROR_KEYS.has(k))
375
+ continue;
376
+ const value = err[k];
377
+ extras.push([k, serialize0(value, owner, registerFunction, options, seen)]);
378
+ }
379
+ if (extras.length > 0)
380
+ result.extras = extras;
381
+ return result;
329
382
  }
330
- function deserializeSymbol(value) {
331
- if (value.startsWith('global:')) {
332
- return Symbol.for(value.slice('global:'.length));
383
+ function serializePlainObject(source, registerFunction, options, seen) {
384
+ const record = source;
385
+ const ownKeys = Reflect.ownKeys(source).filter((k) => isEnumerable(source, k));
386
+ const symbolKeys = [];
387
+ const stringKeys = [];
388
+ for (const k of ownKeys) {
389
+ if (typeof k === 'symbol') {
390
+ if (isSerializableSymbol(k))
391
+ symbolKeys.push(k);
392
+ }
393
+ else {
394
+ stringKeys.push(k);
395
+ }
333
396
  }
334
- if (value.startsWith('wellKnown:')) {
335
- const symbol = getWellKnownSymbol(value.slice('wellKnown:'.length));
336
- if (symbol)
337
- return symbol;
397
+ const hasReservedKey = Object.prototype.hasOwnProperty.call(record, TYPE_KEY);
398
+ if (symbolKeys.length > 0 || hasReservedKey) {
399
+ const entries = [];
400
+ for (const k of stringKeys) {
401
+ entries.push([k, serialize0(record[k], source, registerFunction, options, seen)]);
402
+ }
403
+ for (const k of symbolKeys) {
404
+ entries.push([
405
+ { [TYPE_KEY]: 'sym', v: serializeSymbol(k) },
406
+ serialize0(record[k], source, registerFunction, options, seen),
407
+ ]);
408
+ }
409
+ return { [TYPE_KEY]: 'obj', entries };
338
410
  }
339
- throw new TypeError('Cannot deserialize symbol');
411
+ const result = {};
412
+ for (const k of stringKeys) {
413
+ setOwnProperty$1(result, k, serialize0(record[k], source, registerFunction, options, seen));
414
+ }
415
+ return result;
416
+ }
417
+ function isSerializableSymbol(value) {
418
+ if (Symbol.keyFor(value) !== undefined)
419
+ return true;
420
+ return getWellKnownSymbolName(value) !== undefined;
421
+ }
422
+ function isEnumerable(target, key) {
423
+ const descriptor = Object.getOwnPropertyDescriptor(target, key);
424
+ return descriptor ? descriptor.enumerable === true : false;
425
+ }
426
+ function serializeNumber(value) {
427
+ if (Number.isNaN(value))
428
+ return { [TYPE_KEY]: 'num', v: 'NaN' };
429
+ if (value === Infinity)
430
+ return { [TYPE_KEY]: 'num', v: 'Infinity' };
431
+ if (value === -Infinity)
432
+ return { [TYPE_KEY]: 'num', v: '-Infinity' };
433
+ if (Object.is(value, -0))
434
+ return { [TYPE_KEY]: 'num', v: '-0' };
435
+ return value;
340
436
  }
341
437
  function setOwnProperty$1(target, key, value) {
342
438
  Object.defineProperty(target, key, {
@@ -346,58 +442,245 @@ function setOwnProperty$1(target, key, value) {
346
442
  writable: true,
347
443
  });
348
444
  }
349
-
350
- function isListenerRegistrationPath(path) {
351
- const last = path[path.length - 1];
352
- return last === 'addListener' || last === 'removeListener' || last === 'hasListener';
353
- }
354
- function isLikelyListenerPath(path, value) {
355
- if (typeof value === 'function')
356
- return true;
357
- const last = path[path.length - 1];
358
- return typeof last === 'string' && /^on[A-Z]/.test(last);
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');
359
453
  }
360
454
 
361
- function serialize(arg, registerFunction, options = {}) {
362
- return serialize0(arg, registerFunction, options, new WeakSet());
455
+ const ERROR_CONSTRUCTORS = {
456
+ Error,
457
+ TypeError: TypeError,
458
+ RangeError: RangeError,
459
+ SyntaxError: SyntaxError,
460
+ ReferenceError: ReferenceError,
461
+ URIError: URIError,
462
+ EvalError: EvalError,
463
+ };
464
+ function deserialize(arg, generateCallback, getRemoteCallback) {
465
+ return deserialize0(arg, generateCallback, getRemoteCallback);
363
466
  }
364
- function serialize0(arg, registerFunction, options, seen) {
365
- if (arg === undefined) {
366
- return '$undefined$';
367
- }
467
+ function deserialize0(arg, generateCallback, getRemoteCallback) {
368
468
  if (arg === null)
369
- return '$null$';
370
- if (typeof arg === 'object') {
371
- if (seen.has(arg)) {
372
- throw new TypeError('Cannot serialize circular structure');
469
+ return null;
470
+ if (typeof arg !== 'object')
471
+ return arg;
472
+ if (Array.isArray(arg)) {
473
+ return arg.map((item) => deserialize0(item, generateCallback, getRemoteCallback));
474
+ }
475
+ const source = arg;
476
+ const tag = source[TYPE_KEY];
477
+ if (typeof tag === 'string') {
478
+ return deserializeTagged(tag, source, generateCallback, getRemoteCallback);
479
+ }
480
+ const result = {};
481
+ for (const key of Object.keys(source)) {
482
+ setOwnProperty(result, key, deserialize0(source[key], generateCallback, getRemoteCallback));
483
+ }
484
+ return result;
485
+ }
486
+ function deserializeTagged(tag, source, generateCallback, getRemoteCallback) {
487
+ switch (tag) {
488
+ case 'undef':
489
+ return undefined;
490
+ case 'big':
491
+ return deserializeBigInt(source.v);
492
+ case 'sym':
493
+ return deserializeSymbol(expectString(source.v, 'symbol'));
494
+ case 'num':
495
+ return deserializeNumber(expectString(source.v, 'number'));
496
+ case 'err':
497
+ return deserializeError(source, generateCallback, getRemoteCallback);
498
+ case 'date':
499
+ return deserializeDate(source.v);
500
+ case 're':
501
+ return deserializeRegExp(source.source, source.flags);
502
+ case 'map':
503
+ return deserializeMap(source.entries, generateCallback, getRemoteCallback);
504
+ case 'set':
505
+ return deserializeSet(source.values, generateCallback, getRemoteCallback);
506
+ case 'fn':
507
+ return deserializeFunction(source, generateCallback, getRemoteCallback);
508
+ 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;
373
516
  }
374
- seen.add(arg);
517
+ }
518
+ }
519
+ function deserializeBigInt(raw) {
520
+ if (typeof raw !== 'string')
521
+ throw new TypeError('Invalid bigint payload');
522
+ try {
523
+ return BigInt(raw);
524
+ }
525
+ catch {
526
+ throw new TypeError(`Invalid bigint payload: ${raw}`);
527
+ }
528
+ }
529
+ function deserializeDate(raw) {
530
+ if (typeof raw !== 'number' || !Number.isFinite(raw)) {
531
+ throw new TypeError('Invalid date payload');
532
+ }
533
+ return new Date(raw);
534
+ }
535
+ function deserializeRegExp(rawSource, rawFlags) {
536
+ if (typeof rawSource !== 'string')
537
+ throw new TypeError('Invalid regexp source');
538
+ if (typeof rawFlags !== 'string')
539
+ throw new TypeError('Invalid regexp flags');
540
+ try {
541
+ return new RegExp(rawSource, rawFlags);
542
+ }
543
+ catch (err) {
544
+ throw new TypeError(`Invalid regexp payload: ${err instanceof Error ? err.message : String(err)}`);
545
+ }
546
+ }
547
+ function deserializeMap(entries, generateCallback, getRemoteCallback) {
548
+ const result = new Map();
549
+ if (!Array.isArray(entries))
550
+ return result;
551
+ for (const entry of entries) {
552
+ if (!Array.isArray(entry) || entry.length !== 2)
553
+ continue;
554
+ const k = deserialize0(entry[0], generateCallback, getRemoteCallback);
555
+ const v = deserialize0(entry[1], generateCallback, getRemoteCallback);
556
+ result.set(k, v);
557
+ }
558
+ return result;
559
+ }
560
+ function deserializeSet(values, generateCallback, getRemoteCallback) {
561
+ const result = new Set();
562
+ if (!Array.isArray(values))
563
+ return result;
564
+ for (const v of values) {
565
+ result.add(deserialize0(v, generateCallback, getRemoteCallback));
566
+ }
567
+ return result;
568
+ }
569
+ function deserializeFunction(source, generateCallback, getRemoteCallback) {
570
+ const id = source.id;
571
+ if (typeof id !== 'string' || id.length === 0) {
572
+ throw new TypeError('Invalid function payload');
573
+ }
574
+ const persistent = source.persistent === true;
575
+ const fnOptions = { persistent };
576
+ const invoke = (args) => {
577
+ if (persistent)
578
+ return generateCallback(id, args, fnOptions);
579
+ return generateCallback(id, args);
580
+ };
581
+ return getRemoteCallback ? getRemoteCallback(id, invoke, fnOptions) : (...args) => invoke(args);
582
+ }
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) {
375
589
  try {
376
- if (arg instanceof Error) {
377
- return '$error$' + JSON.stringify({ message: arg.message, stack: arg.stack });
378
- }
379
- if (Array.isArray(arg)) {
380
- return arg.map((item) => serialize0(item, registerFunction, options, seen));
590
+ error.name = name;
591
+ }
592
+ catch (err) {
593
+ warn('deserializeError', 'failed to set error.name', name, err);
594
+ }
595
+ }
596
+ if (typeof source.stack === 'string')
597
+ error.stack = source.stack;
598
+ if (source.cause !== undefined) {
599
+ error.cause = deserialize0(source.cause, generateCallback, getRemoteCallback);
600
+ }
601
+ if (Array.isArray(source.extras)) {
602
+ for (const entry of source.extras) {
603
+ if (!Array.isArray(entry) || entry.length !== 2)
604
+ continue;
605
+ const [rawKey, rawValue] = entry;
606
+ if (typeof rawKey !== 'string')
607
+ continue;
608
+ try {
609
+ setOwnProperty(error, rawKey, deserialize0(rawValue, generateCallback, getRemoteCallback));
381
610
  }
382
- const result = {};
383
- const source = arg;
384
- for (const key of Object.keys(arg)) {
385
- setOwnProperty(result, key, serialize0(source[key], registerFunction, options, seen));
611
+ catch (err) {
612
+ warn('deserializeError', 'failed to set error property', rawKey, err);
386
613
  }
387
- return result;
388
614
  }
389
- finally {
390
- seen.delete(arg);
615
+ }
616
+ return error;
617
+ }
618
+ function deserializeWrappedObject(entries, generateCallback, getRemoteCallback) {
619
+ const result = {};
620
+ if (!Array.isArray(entries))
621
+ return result;
622
+ for (const entry of entries) {
623
+ if (!Array.isArray(entry) || entry.length !== 2)
624
+ continue;
625
+ const rawKey = entry[0];
626
+ const value = deserialize0(entry[1], generateCallback, getRemoteCallback);
627
+ const key = resolveObjectKey(rawKey);
628
+ if (key === undefined) {
629
+ warn('deserializeWrappedObject', 'dropping wrapped-object entry with unresolvable key', rawKey);
630
+ continue;
391
631
  }
632
+ Object.defineProperty(result, key, {
633
+ value,
634
+ enumerable: true,
635
+ configurable: true,
636
+ writable: true,
637
+ });
392
638
  }
393
- if (typeof arg === 'function') {
394
- const id = registerFunction(arg, options);
395
- return '$function$' + (options.persistent ? `persistent:${id}` : id);
639
+ return result;
640
+ }
641
+ function resolveObjectKey(raw) {
642
+ if (typeof raw === 'string')
643
+ return raw;
644
+ if (raw !== null && typeof raw === 'object') {
645
+ const tagged = raw;
646
+ if (tagged[TYPE_KEY] === 'sym' && typeof tagged.v === 'string') {
647
+ try {
648
+ return deserializeSymbol(tagged.v);
649
+ }
650
+ catch (err) {
651
+ warn('resolveObjectKey', 'failed to deserialize symbol key', tagged.v, err);
652
+ return undefined;
653
+ }
654
+ }
655
+ }
656
+ return undefined;
657
+ }
658
+ function expectString(raw, label) {
659
+ if (typeof raw !== 'string')
660
+ throw new TypeError(`Invalid ${label} payload`);
661
+ return raw;
662
+ }
663
+ function deserializeNumber(raw) {
664
+ if (raw === 'NaN')
665
+ return NaN;
666
+ if (raw === 'Infinity')
667
+ return Infinity;
668
+ if (raw === '-Infinity')
669
+ return -Infinity;
670
+ if (raw === '-0')
671
+ return -0;
672
+ return Number(raw);
673
+ }
674
+ function deserializeSymbol(value) {
675
+ if (value.startsWith('global:')) {
676
+ return Symbol.for(value.slice('global:'.length));
396
677
  }
397
- if (typeof arg === 'symbol') {
398
- return '$symbol$' + serializeSymbol(arg);
678
+ if (value.startsWith('wellKnown:')) {
679
+ const symbol = getWellKnownSymbol(value.slice('wellKnown:'.length));
680
+ if (symbol)
681
+ return symbol;
399
682
  }
400
- return `$${typeof arg}$${arg}`;
683
+ throw new TypeError('Cannot deserialize symbol');
401
684
  }
402
685
  function setOwnProperty(target, key, value) {
403
686
  Object.defineProperty(target, key, {
@@ -407,14 +690,18 @@ function setOwnProperty(target, key, value) {
407
690
  writable: true,
408
691
  });
409
692
  }
410
- function serializeSymbol(value) {
411
- const globalKey = Symbol.keyFor(value);
412
- if (globalKey)
413
- return `global:${globalKey}`;
414
- const wellKnownName = getWellKnownSymbolName(value);
415
- if (wellKnownName)
416
- return `wellKnown:${wellKnownName}`;
417
- throw new TypeError('Cannot serialize non-global symbol');
693
+
694
+ function isListenerRegistrationPath(path) {
695
+ const last = path[path.length - 1];
696
+ return last === 'addListener';
697
+ }
698
+ function isListenerRemovalPath(path) {
699
+ const last = path[path.length - 1];
700
+ return last === 'removeListener';
701
+ }
702
+ function isLikelyListenerPath(path) {
703
+ const last = path[path.length - 1];
704
+ return typeof last === 'string' && /^on[A-Z]/.test(last);
418
705
  }
419
706
 
420
707
  const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
@@ -427,70 +714,100 @@ function readProperty(target, key) {
427
714
  return target[key];
428
715
  }
429
716
 
430
- function handleAccessPropertyRequest(data, ctx) {
717
+ function handleAccessPropertyRequest(data, ctx, meta) {
431
718
  const target = ctx.getDelegateTarget();
432
719
  const channel = ctx.getMessageChannel();
433
720
  if (data.path.length === 0) {
434
721
  channel.getSender().sendMessage('accessPropertyResponse', {
435
722
  id: data.id,
436
723
  error: { message: 'Property path must not be empty' },
437
- });
724
+ }, meta.senderInstanceId);
438
725
  return;
439
726
  }
440
- if (!target) {
727
+ if (target === undefined || target === null) {
441
728
  channel.getSender().sendMessage('accessPropertyResponse', {
442
729
  id: data.id,
443
730
  error: { message: 'No delegate target is configured' },
444
- });
731
+ }, meta.senderInstanceId);
445
732
  return;
446
733
  }
447
734
  try {
448
735
  let current = target;
449
- for (const key of data.path) {
736
+ let owner = target;
737
+ for (let i = 0; i < data.path.length; i++) {
738
+ const key = data.path[i];
450
739
  if (current === undefined || current === null) {
451
740
  channel.getSender().sendMessage('accessPropertyResponse', {
452
741
  id: data.id,
453
- data: serialize(current, ctx.registerFunction),
454
- });
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);
455
749
  return;
456
750
  }
751
+ owner = current;
457
752
  current = readProperty(current, key);
458
753
  }
754
+ const value = typeof current === 'function' ? ctx.bindMethod(current, owner) : current;
459
755
  channel.getSender().sendMessage('accessPropertyResponse', {
460
756
  id: data.id,
461
- data: serialize(current, ctx.registerFunction, { persistent: isLikelyListenerPath(data.path, current) }),
462
- });
757
+ data: serialize(value, ctx.registerFunction, { persistent: isLikelyListenerPath(data.path) }),
758
+ }, meta.senderInstanceId);
463
759
  }
464
760
  catch (err) {
465
761
  channel.getSender().sendMessage('accessPropertyResponse', {
466
762
  id: data.id,
467
763
  error: serializeThrownError(err),
468
- });
764
+ }, meta.senderInstanceId);
469
765
  }
470
766
  }
471
- function handleAccessPropertyResponse(data, ctx) {
767
+ function handleAccessPropertyResponse(data, ctx, meta) {
472
768
  const pending = ctx.getAndRemovePendingPromise(data.id);
473
769
  if (!pending)
474
770
  return;
475
771
  if (data.error) {
476
772
  const err = new Error(data.error.message);
477
- err.stack = data.error.stack;
773
+ if (data.error.stack)
774
+ err.stack = data.error.stack;
775
+ pending.onReject?.();
478
776
  pending.reject(err);
479
777
  return;
480
778
  }
481
- pending.resolve(deserialize(data.data, (id, args, options) => {
482
- return ctx.invokeFunctionById(id, args, options);
483
- }, ctx.getRemoteCallback));
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);
484
793
  }
485
794
 
486
- function handleInvokeRequest(data, ctx) {
795
+ function handleInvokeRequest(data, ctx, meta) {
487
796
  const target = ctx.getDelegateTarget();
488
797
  const channel = ctx.getMessageChannel();
489
- if (!target) {
798
+ const scopedRemoteCallback = createScopedRemoteCallback$1(ctx, meta.senderInstanceId);
799
+ if (target === undefined || target === null) {
490
800
  channel.getSender().sendMessage('invokeResponse', {
491
801
  id: data.id,
492
802
  error: { message: 'No delegate target is configured' },
493
- });
803
+ }, meta.senderInstanceId);
804
+ return;
805
+ }
806
+ 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);
494
811
  return;
495
812
  }
496
813
  let current = target;
@@ -500,9 +817,12 @@ function handleInvokeRequest(data, ctx) {
500
817
  channel.getSender().sendMessage('invokeResponse', {
501
818
  id: data.id,
502
819
  error: {
503
- message: `Cannot read property '${String(data.path[i + 1])}' of ${String(data.path[i])}`,
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('.')}')`,
504
824
  },
505
- });
825
+ }, meta.senderInstanceId);
506
826
  return;
507
827
  }
508
828
  }
@@ -512,12 +832,22 @@ function handleInvokeRequest(data, ctx) {
512
832
  channel.getSender().sendMessage('invokeResponse', {
513
833
  id: data.id,
514
834
  error: { message: `'${String(lastKey)}' is not a function` },
515
- });
835
+ }, meta.senderInstanceId);
836
+ return;
837
+ }
838
+ let deserializedArgs;
839
+ try {
840
+ deserializedArgs = data.args.map((arg) => deserialize(arg, (id, args, options) => {
841
+ return ctx.invokeFunctionById(id, args, options);
842
+ }, scopedRemoteCallback));
843
+ }
844
+ catch (err) {
845
+ channel.getSender().sendMessage('invokeResponse', {
846
+ id: data.id,
847
+ error: serializeThrownError(err),
848
+ }, meta.senderInstanceId);
516
849
  return;
517
850
  }
518
- const deserializedArgs = data.args.map((arg) => deserialize(arg, (id, args, options) => {
519
- return ctx.invokeFunctionById(id, args, options);
520
- }, ctx.getRemoteCallback, { persistent: isListenerRegistrationPath(data.path) }));
521
851
  let result;
522
852
  try {
523
853
  result = Reflect.apply(fn, current, deserializedArgs);
@@ -526,124 +856,174 @@ function handleInvokeRequest(data, ctx) {
526
856
  channel.getSender().sendMessage('invokeResponse', {
527
857
  id: data.id,
528
858
  error: serializeThrownError(err),
529
- });
859
+ }, meta.senderInstanceId);
530
860
  return;
531
861
  }
532
- if (result instanceof Promise) {
533
- result
862
+ if (isPromiseLike(result)) {
863
+ Promise.resolve(result)
534
864
  .then((value) => {
535
- sendInvokeSuccess(data.id, value, ctx);
865
+ sendInvokeSuccess(data.id, value, ctx, meta.senderInstanceId);
536
866
  })
537
867
  .catch((err) => {
538
868
  channel.getSender().sendMessage('invokeResponse', {
539
869
  id: data.id,
540
870
  error: serializeThrownError(err),
541
- });
871
+ }, meta.senderInstanceId);
542
872
  });
873
+ return;
543
874
  }
544
- else {
545
- sendInvokeSuccess(data.id, result, ctx);
546
- }
875
+ sendInvokeSuccess(data.id, result, ctx, meta.senderInstanceId);
547
876
  }
548
- function handleInvokeResponse(data, ctx) {
877
+ function handleInvokeResponse(data, ctx, meta) {
549
878
  const pending = ctx.getAndRemovePendingPromise(data.id);
550
879
  if (!pending)
551
880
  return;
552
881
  if (data.error) {
553
882
  const err = new Error(data.error.message);
554
- err.stack = data.error.stack;
883
+ if (data.error.stack)
884
+ err.stack = data.error.stack;
885
+ pending.onReject?.();
555
886
  pending.reject(err);
556
887
  }
557
888
  else {
558
- pending.resolve(deserialize(data.data, (id, args, options) => {
559
- return ctx.invokeFunctionById(id, args, options);
560
- }, ctx.getRemoteCallback));
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
+ }
561
900
  }
562
901
  }
563
- function sendInvokeSuccess(id, value, ctx) {
902
+ function sendInvokeSuccess(id, value, ctx, targetInstanceId) {
564
903
  const channel = ctx.getMessageChannel();
565
904
  try {
566
905
  channel.getSender().sendMessage('invokeResponse', {
567
906
  id,
568
907
  data: serialize(value, ctx.registerFunction),
569
- });
908
+ }, targetInstanceId);
570
909
  }
571
910
  catch (err) {
572
911
  channel.getSender().sendMessage('invokeResponse', {
573
912
  id,
574
913
  error: serializeThrownError(err),
575
- });
914
+ }, targetInstanceId);
576
915
  }
577
916
  }
917
+ function createScopedRemoteCallback$1(ctx, remoteInstanceId) {
918
+ return (id, invoke, options) => ctx.getRemoteCallback(id, invoke, options, remoteInstanceId);
919
+ }
578
920
 
579
- function handleCallbackInvoke(data, ctx) {
921
+ function handleCallbackInvoke(data, ctx, meta) {
580
922
  const channel = ctx.getMessageChannel();
581
- const isPersistentFunction = ctx.getPersistentFunctionCache().has(data.id);
582
- const fn = ctx.getPersistentFunctionCache().get(data.id) ?? ctx.getFunctionCache().get(data.id);
583
- if (!fn) {
923
+ const persistentEntry = ctx.getPersistentFunctionCache().get(data.id);
924
+ const entry = persistentEntry ?? ctx.getFunctionCache().get(data.id);
925
+ if (!entry) {
584
926
  const error = { message: `Callback '${data.id}' is not available or has expired` };
585
- console.warn(`chrome-in-iframe: ${error.message}`);
927
+ warn('handleCallbackInvoke', error.message);
586
928
  channel.getSender().sendMessage('invokeFunctionByIdResponse', {
587
929
  id: data.callId,
588
930
  error,
589
- });
931
+ }, meta.senderInstanceId);
590
932
  return;
591
933
  }
592
- const deserializedArgs = data.args.map((arg) => deserialize(arg, (id, args, options) => {
593
- return ctx.invokeFunctionById(id, args, options);
594
- }, ctx.getRemoteCallback, { persistent: isPersistentFunction }));
934
+ const scopedRemoteCallback = createScopedRemoteCallback(ctx, meta.senderInstanceId);
935
+ let deserializedArgs;
595
936
  try {
596
- const result = fn(...deserializedArgs);
597
- if (result instanceof Promise) {
598
- result
937
+ deserializedArgs = data.args.map((arg) => deserialize(arg, (id, args, options) => {
938
+ return ctx.invokeFunctionById(id, args, options);
939
+ }, scopedRemoteCallback));
940
+ }
941
+ catch (err) {
942
+ sendCallbackError(data.callId, err, ctx, meta.senderInstanceId);
943
+ return;
944
+ }
945
+ try {
946
+ const result = Reflect.apply(entry.fn, entry.thisArg, deserializedArgs);
947
+ if (isPromiseLike(result)) {
948
+ Promise.resolve(result)
599
949
  .then((value) => {
600
- sendCallbackSuccess(data.callId, value, ctx);
950
+ sendCallbackSuccess(data.callId, value, ctx, meta.senderInstanceId);
601
951
  })
602
952
  .catch((err) => {
603
- sendCallbackError(data.callId, err, ctx);
953
+ sendCallbackError(data.callId, err, ctx, meta.senderInstanceId);
604
954
  });
605
955
  return;
606
956
  }
607
- sendCallbackSuccess(data.callId, result, ctx);
957
+ sendCallbackSuccess(data.callId, result, ctx, meta.senderInstanceId);
608
958
  }
609
959
  catch (err) {
610
- sendCallbackError(data.callId, err, ctx);
960
+ sendCallbackError(data.callId, err, ctx, meta.senderInstanceId);
611
961
  }
612
962
  }
613
- function handleCallbackInvokeResponse(data, ctx) {
963
+ function handleCallbackInvokeResponse(data, ctx, meta) {
614
964
  const pending = ctx.getAndRemovePendingPromise(data.id);
615
965
  if (!pending)
616
966
  return;
617
967
  if (data.error) {
618
968
  const err = new Error(data.error.message);
619
- err.stack = data.error.stack;
969
+ if (data.error.stack)
970
+ err.stack = data.error.stack;
971
+ pending.onReject?.();
620
972
  pending.reject(err);
621
973
  return;
622
974
  }
623
- pending.resolve(deserialize(data.data, (id, args, options) => {
624
- return ctx.invokeFunctionById(id, args, options);
625
- }, ctx.getRemoteCallback));
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));
981
+ }
982
+ catch (err) {
983
+ pending.onReject?.();
984
+ pending.reject(err instanceof Error ? err : new Error(String(err)));
985
+ }
626
986
  }
627
- function sendCallbackSuccess(id, value, ctx) {
987
+ function sendCallbackSuccess(id, value, ctx, targetInstanceId) {
628
988
  const channel = ctx.getMessageChannel();
629
989
  try {
630
990
  channel.getSender().sendMessage('invokeFunctionByIdResponse', {
631
991
  id,
632
992
  data: serialize(value, ctx.registerFunction),
633
- });
993
+ }, targetInstanceId);
634
994
  }
635
995
  catch (err) {
636
- sendCallbackError(id, err, ctx);
996
+ sendCallbackError(id, err, ctx, targetInstanceId);
637
997
  }
638
998
  }
639
- function sendCallbackError(id, err, ctx) {
999
+ function sendCallbackError(id, err, ctx, targetInstanceId) {
640
1000
  ctx
641
1001
  .getMessageChannel()
642
1002
  .getSender()
643
1003
  .sendMessage('invokeFunctionByIdResponse', {
644
1004
  id,
645
1005
  error: serializeThrownError(err),
646
- });
1006
+ }, targetInstanceId);
1007
+ }
1008
+ function createScopedRemoteCallback(ctx, remoteInstanceId) {
1009
+ return (id, invoke, options) => ctx.getRemoteCallback(id, invoke, options, remoteInstanceId);
1010
+ }
1011
+
1012
+ const MAX_RELEASE_IDS = 100000;
1013
+ function handleReleaseCallbacks(data, ctx, meta) {
1014
+ 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
+ }
1024
+ }
1025
+ function handleDestroyEndpoint(_data, ctx, meta) {
1026
+ ctx.handleRemoteDestroy(meta.senderInstanceId);
647
1027
  }
648
1028
 
649
1029
  function createProcessorRegistry() {
@@ -659,56 +1039,165 @@ function createProcessorRegistry() {
659
1039
  }
660
1040
 
661
1041
  const DEFAULT_TIMEOUT = 30000;
662
- const FUNCTION_CACHE_MAX = 100;
663
- const FUNCTION_CACHE_TTL = 5 * 60 * 1000;
664
- const REMOTE_CALLBACK_CACHE_MAX = 500;
665
- const REMOTE_CALLBACK_CACHE_TTL = 5 * 60 * 1000;
666
- function createClientContext(getMessageChannel, options = {}) {
1042
+ const DEFAULT_FUNCTION_CACHE_MAX = 100;
1043
+ const DEFAULT_FUNCTION_CACHE_TTL = 5 * 60 * 1000;
1044
+ const DEFAULT_REMOTE_CALLBACK_CACHE_MAX = 500;
1045
+ const DEFAULT_REMOTE_CALLBACK_CACHE_TTL = 5 * 60 * 1000;
1046
+ function resolveTimeout(raw) {
1047
+ if (raw === undefined)
1048
+ return DEFAULT_TIMEOUT;
1049
+ if (typeof raw !== 'number' || !Number.isFinite(raw) || raw <= 0)
1050
+ return DEFAULT_TIMEOUT;
1051
+ return raw;
1052
+ }
1053
+ function createClientContext(getMessageChannel, instanceId, options = {}) {
1054
+ const remoteCacheMax = options.remoteCallbackCacheMax ?? DEFAULT_REMOTE_CALLBACK_CACHE_MAX;
1055
+ const remoteCacheTtl = options.remoteCallbackCacheTtl ?? DEFAULT_REMOTE_CALLBACK_CACHE_TTL;
1056
+ const callTimeout = resolveTimeout(options.timeout);
667
1057
  const pending = new Map();
668
- const functionCache = new LRUCache({
669
- max: FUNCTION_CACHE_MAX,
670
- ttl: FUNCTION_CACHE_TTL,
671
- });
1058
+ const functionIds = new Map();
672
1059
  const persistentFunctionCache = new Map();
673
- const functionIds = new WeakMap();
674
- const remoteCallbacks = new LRUCache({
675
- max: REMOTE_CALLBACK_CACHE_MAX,
676
- ttl: REMOTE_CALLBACK_CACHE_TTL,
1060
+ const persistentRefcount = new Map();
1061
+ const functionCache = new LRUCache({
1062
+ max: options.functionCacheMax ?? DEFAULT_FUNCTION_CACHE_MAX,
1063
+ ttl: options.functionCacheTtl ?? DEFAULT_FUNCTION_CACHE_TTL,
1064
+ dispose: (value, key, reason) => {
1065
+ if (reason === 'set' || reason === 'delete')
1066
+ return;
1067
+ if (persistentFunctionCache.has(key))
1068
+ return;
1069
+ removeFunctionIdMapping(value.fn, value.thisArg);
1070
+ },
677
1071
  });
678
- const persistentRemoteCallbacks = new Map();
1072
+ const remoteCallbacksByOwner = new Map();
1073
+ const persistentRemoteCallbacksByOwner = new Map();
1074
+ const boundMethodCache = new WeakMap();
679
1075
  let destroyed = false;
680
- const registerFunction = (fn, callbackOptions = {}) => {
681
- let id = functionIds.get(fn);
1076
+ const bindMethod = (fn, owner) => {
1077
+ if (owner === null || typeof owner !== 'object') {
1078
+ return (...args) => Reflect.apply(fn, owner, args);
1079
+ }
1080
+ let perOwner = boundMethodCache.get(fn);
1081
+ if (!perOwner) {
1082
+ perOwner = new WeakMap();
1083
+ boundMethodCache.set(fn, perOwner);
1084
+ }
1085
+ const cached = perOwner.get(owner);
1086
+ if (cached)
1087
+ return cached;
1088
+ const bound = (...args) => Reflect.apply(fn, owner, args);
1089
+ perOwner.set(owner, bound);
1090
+ return bound;
1091
+ };
1092
+ const removeFunctionIdMapping = (fn, thisArg) => {
1093
+ const perOwner = functionIds.get(fn);
1094
+ if (!perOwner)
1095
+ return;
1096
+ perOwner.delete(thisArg);
1097
+ if (perOwner.size === 0)
1098
+ functionIds.delete(fn);
1099
+ };
1100
+ const getOrCreateRemoteLru = (remoteId) => {
1101
+ let lru = remoteCallbacksByOwner.get(remoteId);
1102
+ if (!lru) {
1103
+ lru = new LRUCache({ max: remoteCacheMax, ttl: remoteCacheTtl });
1104
+ remoteCallbacksByOwner.set(remoteId, lru);
1105
+ }
1106
+ return lru;
1107
+ };
1108
+ const getOrCreatePersistentRemoteMap = (remoteId) => {
1109
+ let map = persistentRemoteCallbacksByOwner.get(remoteId);
1110
+ if (!map) {
1111
+ map = new Map();
1112
+ persistentRemoteCallbacksByOwner.set(remoteId, map);
1113
+ }
1114
+ return map;
1115
+ };
1116
+ const registerFunction = (fn, thisArg, callbackOptions = {}) => {
1117
+ let perOwner = functionIds.get(fn);
1118
+ if (!perOwner) {
1119
+ perOwner = new Map();
1120
+ functionIds.set(fn, perOwner);
1121
+ }
1122
+ let id = perOwner.get(thisArg);
682
1123
  if (!id) {
683
1124
  id = createId('cb');
684
- functionIds.set(fn, id);
1125
+ perOwner.set(thisArg, id);
685
1126
  }
1127
+ const entry = { fn, thisArg };
686
1128
  if (callbackOptions.persistent) {
687
- persistentFunctionCache.set(id, fn);
1129
+ persistentFunctionCache.set(id, entry);
688
1130
  functionCache.delete(id);
1131
+ persistentRefcount.set(id, (persistentRefcount.get(id) ?? 0) + 1);
689
1132
  }
690
1133
  else if (!persistentFunctionCache.has(id)) {
691
- functionCache.set(id, fn);
1134
+ functionCache.set(id, entry);
692
1135
  }
693
1136
  return id;
694
1137
  };
695
- const getRemoteCallback = (id, invoke, callbackOptions = {}) => {
696
- let callback = persistentRemoteCallbacks.get(id) ?? remoteCallbacks.get(id);
1138
+ const releasePersistentRegistrationById = (id) => {
1139
+ if (!persistentRefcount.has(id))
1140
+ return undefined;
1141
+ const next = (persistentRefcount.get(id) ?? 0) - 1;
1142
+ if (next > 0) {
1143
+ persistentRefcount.set(id, next);
1144
+ return undefined;
1145
+ }
1146
+ persistentRefcount.delete(id);
1147
+ const entry = persistentFunctionCache.get(id);
1148
+ persistentFunctionCache.delete(id);
1149
+ functionCache.delete(id);
1150
+ if (entry)
1151
+ removeFunctionIdMapping(entry.fn, entry.thisArg);
1152
+ return id;
1153
+ };
1154
+ const dropLocalCallback = (id) => {
1155
+ const entry = persistentFunctionCache.get(id) ?? functionCache.get(id);
1156
+ persistentRefcount.delete(id);
1157
+ persistentFunctionCache.delete(id);
1158
+ functionCache.delete(id);
1159
+ if (entry)
1160
+ removeFunctionIdMapping(entry.fn, entry.thisArg);
1161
+ };
1162
+ const getRemoteCallback = (id, invoke, callbackOptions, remoteInstanceId) => {
1163
+ const opts = callbackOptions ?? {};
1164
+ const lru = remoteCallbacksByOwner.get(remoteInstanceId);
1165
+ const pmap = persistentRemoteCallbacksByOwner.get(remoteInstanceId);
1166
+ let callback = pmap?.get(id) ?? lru?.get(id);
697
1167
  if (!callback) {
698
1168
  callback = (...args) => invoke(args);
699
- if (callbackOptions.persistent) {
700
- persistentRemoteCallbacks.set(id, callback);
1169
+ if (opts.persistent) {
1170
+ getOrCreatePersistentRemoteMap(remoteInstanceId).set(id, callback);
701
1171
  }
702
1172
  else {
703
- remoteCallbacks.set(id, callback);
1173
+ getOrCreateRemoteLru(remoteInstanceId).set(id, callback);
704
1174
  }
705
1175
  }
706
- else if (callbackOptions.persistent) {
707
- remoteCallbacks.delete(id);
708
- persistentRemoteCallbacks.set(id, callback);
1176
+ else if (opts.persistent && !pmap?.has(id)) {
1177
+ lru?.delete(id);
1178
+ getOrCreatePersistentRemoteMap(remoteInstanceId).set(id, callback);
709
1179
  }
710
1180
  return callback;
711
1181
  };
1182
+ const notifyReleaseCallbacks = (ids) => {
1183
+ if (ids.length === 0)
1184
+ return;
1185
+ try {
1186
+ getMessageChannel().getSender().sendMessage('releaseCallbacks', { ids });
1187
+ }
1188
+ catch (err) {
1189
+ warn('notifyReleaseCallbacks', 'failed to release callbacks', err);
1190
+ }
1191
+ };
1192
+ const rejectPending = (id, error) => {
1193
+ const callbacks = pending.get(id);
1194
+ if (!callbacks)
1195
+ return;
1196
+ clearTimeout(callbacks.timer);
1197
+ pending.delete(id);
1198
+ callbacks.onReject?.();
1199
+ callbacks.reject(error);
1200
+ };
712
1201
  return {
713
1202
  getDelegateTarget() {
714
1203
  return options.delegateTarget;
@@ -716,17 +1205,20 @@ function createClientContext(getMessageChannel, options = {}) {
716
1205
  getMessageChannel() {
717
1206
  return getMessageChannel();
718
1207
  },
1208
+ getInstanceId() {
1209
+ return instanceId;
1210
+ },
719
1211
  getFunctionCache() {
720
1212
  return functionCache;
721
1213
  },
722
1214
  getPersistentFunctionCache() {
723
1215
  return persistentFunctionCache;
724
1216
  },
725
- registerFunction(fn, callbackOptions) {
726
- return registerFunction(fn, callbackOptions);
1217
+ registerFunction(fn, thisArg, callbackOptions) {
1218
+ return registerFunction(fn, thisArg, callbackOptions);
727
1219
  },
728
- getRemoteCallback(id, invoke, callbackOptions) {
729
- return getRemoteCallback(id, invoke, callbackOptions);
1220
+ getRemoteCallback(id, invoke, callbackOptions, remoteInstanceId) {
1221
+ return getRemoteCallback(id, invoke, callbackOptions, remoteInstanceId);
730
1222
  },
731
1223
  getAndRemovePendingPromise(id) {
732
1224
  const callbacks = pending.get(id);
@@ -736,9 +1228,6 @@ function createClientContext(getMessageChannel, options = {}) {
736
1228
  }
737
1229
  return callbacks;
738
1230
  },
739
- registerPendingPromise(id, callbacks) {
740
- pending.set(id, callbacks);
741
- },
742
1231
  invoke(path, args) {
743
1232
  return new Promise((resolve, reject) => {
744
1233
  if (destroyed) {
@@ -746,20 +1235,63 @@ function createClientContext(getMessageChannel, options = {}) {
746
1235
  return;
747
1236
  }
748
1237
  const id = createId('req');
749
- const timeout = options.timeout ?? DEFAULT_TIMEOUT;
1238
+ const persistent = isListenerRegistrationPath(path);
1239
+ const registeredIds = [];
1240
+ const collectingRegister = (fn, thisArg, opts) => {
1241
+ const cbId = registerFunction(fn, thisArg, opts);
1242
+ registeredIds.push(cbId);
1243
+ return cbId;
1244
+ };
1245
+ const releaseRegisteredPersistents = () => {
1246
+ const released = [];
1247
+ for (const cbId of registeredIds) {
1248
+ const releasedId = releasePersistentRegistrationById(cbId);
1249
+ if (releasedId)
1250
+ released.push(releasedId);
1251
+ }
1252
+ return released;
1253
+ };
750
1254
  const timer = setTimeout(() => {
1255
+ rejectPending(id, new Error(`Call timed out: ${String(path.join('.'))}`));
1256
+ }, callTimeout);
1257
+ 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
+ });
1284
+ }
1285
+ catch (err) {
1286
+ clearTimeout(timer);
751
1287
  pending.delete(id);
752
- reject(new Error(`Call timed out: ${String(path.join('.'))}`));
753
- }, timeout);
754
- pending.set(id, { resolve, reject, timer });
755
- const channel = getMessageChannel();
756
- const persistent = isListenerRegistrationPath(path);
757
- const serializedArgs = args.map((arg) => serialize(arg, registerFunction, { persistent }));
758
- channel.getSender().sendMessage('invokeRequest', {
759
- id,
760
- path,
761
- args: serializedArgs,
762
- });
1288
+ if (isListenerRegistrationPath(path)) {
1289
+ const released = releaseRegisteredPersistents();
1290
+ if (released.length > 0)
1291
+ notifyReleaseCallbacks(released);
1292
+ }
1293
+ reject(err);
1294
+ }
763
1295
  });
764
1296
  },
765
1297
  accessProperty(path) {
@@ -769,16 +1301,21 @@ function createClientContext(getMessageChannel, options = {}) {
769
1301
  return;
770
1302
  }
771
1303
  const id = createId('req');
772
- const timeout = options.timeout ?? DEFAULT_TIMEOUT;
773
1304
  const timer = setTimeout(() => {
774
- pending.delete(id);
775
- reject(new Error(`Property access timed out: ${String(path.join('.'))}`));
776
- }, timeout);
1305
+ rejectPending(id, new Error(`Property access timed out: ${String(path.join('.'))}`));
1306
+ }, callTimeout);
777
1307
  pending.set(id, { resolve, reject, timer });
778
- getMessageChannel().getSender().sendMessage('accessPropertyRequest', {
779
- id,
780
- path,
781
- });
1308
+ try {
1309
+ getMessageChannel().getSender().sendMessage('accessPropertyRequest', {
1310
+ id,
1311
+ path,
1312
+ });
1313
+ }
1314
+ catch (err) {
1315
+ clearTimeout(timer);
1316
+ pending.delete(id);
1317
+ reject(err);
1318
+ }
782
1319
  });
783
1320
  },
784
1321
  invokeFunctionById(callbackId, args, callbackOptions = {}) {
@@ -788,25 +1325,79 @@ function createClientContext(getMessageChannel, options = {}) {
788
1325
  return;
789
1326
  }
790
1327
  const callId = createId('cbcall');
791
- const timeout = options.timeout ?? DEFAULT_TIMEOUT;
792
1328
  const timer = setTimeout(() => {
793
- pending.delete(callId);
794
- reject(new Error(`Callback call timed out: ${callbackId}`));
795
- }, timeout);
1329
+ rejectPending(callId, new Error(`Callback call timed out: ${callbackId}`));
1330
+ }, callTimeout);
796
1331
  pending.set(callId, { resolve, reject, timer });
797
- getMessageChannel()
798
- .getSender()
799
- .sendMessage('invokeFunctionByIdRequest', {
800
- id: callbackId,
801
- callId,
802
- args: args.map((arg) => serialize(arg, registerFunction, callbackOptions)),
803
- });
1332
+ try {
1333
+ getMessageChannel()
1334
+ .getSender()
1335
+ .sendMessage('invokeFunctionByIdRequest', {
1336
+ id: callbackId,
1337
+ callId,
1338
+ args: args.map((arg) => serialize(arg, registerFunction, callbackOptions)),
1339
+ });
1340
+ }
1341
+ catch (err) {
1342
+ clearTimeout(timer);
1343
+ pending.delete(callId);
1344
+ reject(err);
1345
+ }
804
1346
  });
805
1347
  },
806
- destroy() {
1348
+ handleRemoteDestroy(remoteInstanceId) {
1349
+ remoteCallbacksByOwner.delete(remoteInstanceId);
1350
+ persistentRemoteCallbacksByOwner.delete(remoteInstanceId);
1351
+ },
1352
+ releaseRemoteCallbacks(remoteInstanceId, ids) {
1353
+ const lru = remoteCallbacksByOwner.get(remoteInstanceId);
1354
+ const pmap = persistentRemoteCallbacksByOwner.get(remoteInstanceId);
1355
+ if (!lru && !pmap)
1356
+ return;
1357
+ for (const id of ids) {
1358
+ lru?.delete(id);
1359
+ pmap?.delete(id);
1360
+ }
1361
+ },
1362
+ dropLocalCallback(id) {
1363
+ dropLocalCallback(id);
1364
+ },
1365
+ bindMethod(fn, owner) {
1366
+ return bindMethod(fn, owner);
1367
+ },
1368
+ getRemoteCallbackCounts(remoteInstanceId) {
1369
+ return {
1370
+ lru: remoteCallbacksByOwner.get(remoteInstanceId)?.size ?? 0,
1371
+ persistent: persistentRemoteCallbacksByOwner.get(remoteInstanceId)?.size ?? 0,
1372
+ };
1373
+ },
1374
+ destroy(notifyRemote = true) {
807
1375
  if (destroyed)
808
1376
  return;
809
1377
  destroyed = true;
1378
+ if (notifyRemote) {
1379
+ const heldRemoteIds = [];
1380
+ for (const lru of remoteCallbacksByOwner.values()) {
1381
+ for (const id of lru.keys())
1382
+ heldRemoteIds.push(id);
1383
+ }
1384
+ for (const map of persistentRemoteCallbacksByOwner.values()) {
1385
+ for (const id of map.keys())
1386
+ heldRemoteIds.push(id);
1387
+ }
1388
+ try {
1389
+ const sender = getMessageChannel().getSender();
1390
+ if (heldRemoteIds.length > 0) {
1391
+ sender.sendMessage('releaseCallbacks', { ids: heldRemoteIds });
1392
+ }
1393
+ sender.sendMessage('destroyEndpoint', { instanceId });
1394
+ }
1395
+ catch (err) {
1396
+ if (!isExpectedDestroyNotifyError(err)) {
1397
+ warn('destroy', 'failed to notify remote of destroy', err);
1398
+ }
1399
+ }
1400
+ }
810
1401
  for (const [id, callbacks] of pending) {
811
1402
  clearTimeout(callbacks.timer);
812
1403
  callbacks.reject(new Error(`Channel has been destroyed before call ${id} completed`));
@@ -814,15 +1405,57 @@ function createClientContext(getMessageChannel, options = {}) {
814
1405
  pending.clear();
815
1406
  functionCache.clear();
816
1407
  persistentFunctionCache.clear();
817
- remoteCallbacks.clear();
818
- persistentRemoteCallbacks.clear();
1408
+ persistentRefcount.clear();
1409
+ functionIds.clear();
1410
+ remoteCallbacksByOwner.clear();
1411
+ persistentRemoteCallbacksByOwner.clear();
819
1412
  },
820
1413
  };
821
1414
  }
822
1415
  function createId(prefix) {
823
1416
  return `${prefix}-${nanoid()}`;
824
1417
  }
1418
+ function isExpectedDestroyNotifyError(err) {
1419
+ return err instanceof Error && err.message.includes('contentWindow is not available');
1420
+ }
825
1421
 
1422
+ const EMPTY_TARGET = () => undefined;
1423
+ const INTERCEPTED_STRING_PROPS = new Set([
1424
+ 'then',
1425
+ 'catch',
1426
+ 'finally',
1427
+ 'toJSON',
1428
+ 'toString',
1429
+ 'valueOf',
1430
+ 'nodeType',
1431
+ 'tagName',
1432
+ 'asymmetricMatch',
1433
+ '$$typeof',
1434
+ '@@__IMMUTABLE_ITERABLE__@@',
1435
+ '@@__IMMUTABLE_RECORD__@@',
1436
+ 'length',
1437
+ 'name',
1438
+ 'prototype',
1439
+ 'arguments',
1440
+ 'caller',
1441
+ 'bind',
1442
+ 'call',
1443
+ 'apply',
1444
+ ]);
1445
+ const INTERCEPTED_NONE_SYMBOLS = new Set([
1446
+ Symbol.toStringTag,
1447
+ Symbol.iterator,
1448
+ Symbol.asyncIterator,
1449
+ Symbol.isConcatSpreadable,
1450
+ ]);
1451
+ const PROXY_DESCRIPTION = '[chrome-in-iframe proxy]';
1452
+ function toPrimitiveDescriptor() {
1453
+ return (hint) => {
1454
+ if (hint === 'number')
1455
+ return NaN;
1456
+ return PROXY_DESCRIPTION;
1457
+ };
1458
+ }
826
1459
  function resolvePath(node) {
827
1460
  const path = [];
828
1461
  let current = node;
@@ -834,11 +1467,14 @@ function resolvePath(node) {
834
1467
  }
835
1468
  function createClientProxy(invoke, accessProperty) {
836
1469
  const normalizePropertyPath = (basePath, parts) => {
1470
+ if (parts.length === 1 && typeof parts[0] === 'string') {
1471
+ return [...basePath, ...parts[0].split('.').filter(Boolean)];
1472
+ }
837
1473
  const extraPath = parts.flatMap((part) => {
838
1474
  if (Array.isArray(part))
839
1475
  return part.filter(isPathKey);
840
1476
  if (typeof part === 'string')
841
- return part.split('.').filter(Boolean);
1477
+ return [part];
842
1478
  if (typeof part === 'symbol')
843
1479
  return [part];
844
1480
  return [];
@@ -848,19 +1484,27 @@ function createClientProxy(invoke, accessProperty) {
848
1484
  function createHandler(node) {
849
1485
  return {
850
1486
  get(_target, prop) {
851
- if (prop === 'then')
1487
+ if (typeof prop === 'string') {
1488
+ if (INTERCEPTED_STRING_PROPS.has(prop))
1489
+ return undefined;
1490
+ 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
+ };
1498
+ }
1499
+ }
1500
+ else if (prop === Symbol.toPrimitive) {
1501
+ return toPrimitiveDescriptor();
1502
+ }
1503
+ else if (INTERCEPTED_NONE_SYMBOLS.has(prop)) {
852
1504
  return undefined;
853
- if (prop === '$get') {
854
- return (...parts) => {
855
- if (!accessProperty) {
856
- return Promise.reject(new Error('Property access is not configured'));
857
- }
858
- const basePath = node ? resolvePath(node) : [];
859
- return accessProperty(normalizePropertyPath(basePath, parts));
860
- };
861
1505
  }
862
1506
  const childNode = node ? { parent: node, key: prop } : { key: prop };
863
- return new Proxy(() => { }, createHandler(childNode));
1507
+ return new Proxy(EMPTY_TARGET, createHandler(childNode));
864
1508
  },
865
1509
  apply(_target, _thisArg, argArray) {
866
1510
  const path = node ? resolvePath(node) : [];
@@ -868,7 +1512,7 @@ function createClientProxy(invoke, accessProperty) {
868
1512
  },
869
1513
  };
870
1514
  }
871
- return new Proxy(() => { }, createHandler());
1515
+ return new Proxy(EMPTY_TARGET, createHandler());
872
1516
  }
873
1517
  function isPathKey(value) {
874
1518
  return typeof value === 'string' || typeof value === 'symbol';
@@ -883,8 +1527,9 @@ function createEndpoint(options) {
883
1527
  }
884
1528
  return channelRef.current;
885
1529
  };
886
- const context = createClientContext(getChannel, options.contextOptions);
887
- const channel = createMessageChannel(options.poster, options.key ?? 'default', context, processorRegistry);
1530
+ const instanceId = `ep-${nanoid()}`;
1531
+ const context = createClientContext(getChannel, instanceId, options.contextOptions);
1532
+ const channel = createMessageChannel(options.poster, options.key ?? 'default', instanceId, context, processorRegistry);
888
1533
  channelRef.current = channel;
889
1534
  processorRegistry.register('invokeRequest', handleInvokeRequest);
890
1535
  processorRegistry.register('invokeResponse', handleInvokeResponse);
@@ -892,27 +1537,36 @@ function createEndpoint(options) {
892
1537
  processorRegistry.register('accessPropertyResponse', handleAccessPropertyResponse);
893
1538
  processorRegistry.register('invokeFunctionByIdRequest', handleCallbackInvoke);
894
1539
  processorRegistry.register('invokeFunctionByIdResponse', handleCallbackInvokeResponse);
1540
+ processorRegistry.register('releaseCallbacks', handleReleaseCallbacks);
1541
+ processorRegistry.register('destroyEndpoint', handleDestroyEndpoint);
895
1542
  const proxy = createClientProxy((path, args) => context.invoke(path, args), (path) => context.accessProperty(path));
1543
+ let destroyed = false;
896
1544
  return {
897
1545
  proxy,
1546
+ getContext: () => context,
898
1547
  destroy() {
1548
+ if (destroyed)
1549
+ return;
1550
+ destroyed = true;
1551
+ context.destroy(true);
899
1552
  channel.destroy();
900
- context.destroy();
901
1553
  },
902
1554
  };
903
1555
  }
904
1556
 
905
1557
  function setupInMainWindow(options) {
1558
+ const resolveTargetOrigin = createTargetOriginResolver(() => options.iframe, options.targetOrigin);
1559
+ const allowedOrigin = createAllowedOrigin(options.allowedOrigin, resolveTargetOrigin);
906
1560
  const poster = createWindowPoster({
907
1561
  postMessage: (message) => {
908
1562
  const contentWindow = options.iframe.contentWindow;
909
1563
  if (!contentWindow) {
910
1564
  throw new Error('chrome-in-iframe: iframe contentWindow is not available');
911
1565
  }
912
- contentWindow.postMessage(message, options.targetOrigin ?? '*');
1566
+ contentWindow.postMessage(message, resolveTargetOrigin());
913
1567
  },
914
1568
  getExpectedSource: () => options.iframe.contentWindow,
915
- allowedOrigin: options.allowedOrigin,
1569
+ allowedOrigin,
916
1570
  });
917
1571
  const endpoint = createEndpoint({
918
1572
  poster,
@@ -920,6 +1574,10 @@ function setupInMainWindow(options) {
920
1574
  contextOptions: {
921
1575
  delegateTarget: options.delegateTarget,
922
1576
  timeout: options.timeout,
1577
+ functionCacheMax: options.functionCacheMax,
1578
+ functionCacheTtl: options.functionCacheTtl,
1579
+ remoteCallbackCacheMax: options.remoteCallbackCacheMax,
1580
+ remoteCallbackCacheTtl: options.remoteCallbackCacheTtl,
923
1581
  },
924
1582
  });
925
1583
  const proxy = endpoint.proxy;
@@ -930,18 +1588,24 @@ function setupInMainWindow(options) {
930
1588
  };
931
1589
  }
932
1590
  function setupInIframe(options = {}) {
1591
+ const resolveTargetOrigin = createParentTargetOriginResolver(options.targetOrigin);
1592
+ const allowedOrigin = createAllowedOrigin(options.allowedOrigin, resolveTargetOrigin);
933
1593
  const poster = createWindowPoster({
934
1594
  postMessage: (message) => {
935
- window.parent.postMessage(message, options.targetOrigin ?? '*');
1595
+ window.parent.postMessage(message, resolveTargetOrigin());
936
1596
  },
937
1597
  getExpectedSource: () => window.parent,
938
- allowedOrigin: options.allowedOrigin,
1598
+ allowedOrigin,
939
1599
  });
940
1600
  const endpoint = createEndpoint({
941
1601
  poster,
942
1602
  key: options.key,
943
1603
  contextOptions: {
944
1604
  timeout: options.timeout,
1605
+ functionCacheMax: options.functionCacheMax,
1606
+ functionCacheTtl: options.functionCacheTtl,
1607
+ remoteCallbackCacheMax: options.remoteCallbackCacheMax,
1608
+ remoteCallbackCacheTtl: options.remoteCallbackCacheTtl,
945
1609
  },
946
1610
  });
947
1611
  const proxy = endpoint.proxy;
@@ -966,11 +1630,23 @@ function connectChromeInIframe(options = {}) {
966
1630
  }
967
1631
  function createWindowPoster(options) {
968
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
+ };
969
1641
  return {
970
1642
  postMessage(message) {
971
1643
  options.postMessage(message);
972
1644
  },
973
1645
  addEventListener(name, callback) {
1646
+ const perName = getOrCreatePerName(name);
1647
+ const existing = perName.get(callback);
1648
+ if (existing)
1649
+ window.removeEventListener(name, existing);
974
1650
  const listener = (event) => {
975
1651
  const messageEvent = event;
976
1652
  if (messageEvent.source !== options.getExpectedSource())
@@ -979,20 +1655,25 @@ function createWindowPoster(options) {
979
1655
  return;
980
1656
  callback(messageEvent);
981
1657
  };
982
- listenerMap.set(callback, listener);
1658
+ perName.set(callback, listener);
983
1659
  window.addEventListener(name, listener);
984
1660
  },
985
1661
  removeEventListener(name, callback) {
986
- const listener = listenerMap.get(callback);
1662
+ const perName = listenerMap.get(name);
1663
+ if (!perName)
1664
+ return;
1665
+ const listener = perName.get(callback);
987
1666
  if (!listener)
988
1667
  return;
989
1668
  window.removeEventListener(name, listener);
990
- listenerMap.delete(callback);
1669
+ perName.delete(callback);
1670
+ if (perName.size === 0)
1671
+ listenerMap.delete(name);
991
1672
  },
992
1673
  };
993
1674
  }
994
1675
  function isAllowedOrigin(origin, allowedOrigin) {
995
- if (!allowedOrigin)
1676
+ if (allowedOrigin === undefined)
996
1677
  return true;
997
1678
  if (typeof allowedOrigin === 'function')
998
1679
  return allowedOrigin(origin);
@@ -1000,6 +1681,14 @@ function isAllowedOrigin(origin, allowedOrigin) {
1000
1681
  return allowedOrigin.includes(origin);
1001
1682
  return allowedOrigin === origin;
1002
1683
  }
1684
+ function createAllowedOrigin(explicit, resolveTargetOrigin) {
1685
+ if (explicit !== undefined)
1686
+ return explicit;
1687
+ return (origin) => {
1688
+ const targetOrigin = resolveTargetOrigin();
1689
+ return targetOrigin === '*' || targetOrigin === origin;
1690
+ };
1691
+ }
1003
1692
  function getGlobalChrome() {
1004
1693
  const chromeApi = globalThis.chrome;
1005
1694
  if (!chromeApi) {
@@ -1007,5 +1696,64 @@ function getGlobalChrome() {
1007
1696
  }
1008
1697
  return chromeApi;
1009
1698
  }
1699
+ function createTargetOriginResolver(getIframe, explicit) {
1700
+ if (explicit)
1701
+ return () => explicit;
1702
+ return () => deriveOriginFromIframe(getIframe());
1703
+ }
1704
+ function createParentTargetOriginResolver(explicit) {
1705
+ if (explicit)
1706
+ return () => explicit;
1707
+ return () => deriveParentOrigin();
1708
+ }
1709
+ function deriveOriginFromIframe(iframe) {
1710
+ const src = iframe.src || iframe.getAttribute('src') || '';
1711
+ const origin = parseOrigin(src);
1712
+ if (origin)
1713
+ return origin;
1714
+ try {
1715
+ if (iframe.contentWindow && iframe.contentWindow.location) {
1716
+ const loc = iframe.contentWindow.location;
1717
+ if (loc.origin && loc.origin !== 'null')
1718
+ return loc.origin;
1719
+ }
1720
+ }
1721
+ catch (err) {
1722
+ warn('deriveOriginFromIframe', 'unable to access cross-origin contentWindow.location', err);
1723
+ }
1724
+ warn('deriveOriginFromIframe', "unable to derive iframe origin automatically; falling back to '*'. " +
1725
+ 'Pass `targetOrigin` explicitly to lock the destination.');
1726
+ return '*';
1727
+ }
1728
+ function deriveParentOrigin() {
1729
+ if (typeof document !== 'undefined') {
1730
+ const referrer = document.referrer;
1731
+ const origin = parseOrigin(referrer);
1732
+ if (origin)
1733
+ return origin;
1734
+ }
1735
+ if (typeof location !== 'undefined' && location.ancestorOrigins && location.ancestorOrigins.length > 0) {
1736
+ const first = location.ancestorOrigins[0];
1737
+ if (first && first !== 'null')
1738
+ return first;
1739
+ }
1740
+ warn('deriveParentOrigin', "unable to derive parent origin automatically; falling back to '*'. " +
1741
+ 'Pass `targetOrigin` explicitly to lock the destination.');
1742
+ return '*';
1743
+ }
1744
+ function parseOrigin(url) {
1745
+ if (!url)
1746
+ return null;
1747
+ try {
1748
+ const parsed = new URL(url, typeof location !== 'undefined' ? location.href : 'http://localhost');
1749
+ if (parsed.origin && parsed.origin !== 'null')
1750
+ return parsed.origin;
1751
+ }
1752
+ catch (err) {
1753
+ warn('parseOrigin', 'failed to parse URL', url, err);
1754
+ return null;
1755
+ }
1756
+ return null;
1757
+ }
1010
1758
 
1011
- export { connectChromeInIframe, exposeChromeInIframe, setupInIframe, setupInMainWindow };
1759
+ export { connectChromeInIframe, exposeChromeInIframe, setLogger, setupInIframe, setupInMainWindow };