chrome-in-iframe 1.0.0 → 2.0.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,5 +1,5 @@
1
- import { LRUCache } from 'lru-cache';
2
1
  import { nanoid } from 'nanoid';
2
+ import { LRUCache } from 'lru-cache';
3
3
 
4
4
  const WELL_KNOWN_SYMBOLS = {
5
5
  asyncIterator: Symbol.asyncIterator,
@@ -82,10 +82,17 @@ function deserializeSymbol$1(value) {
82
82
  throw new TypeError('Cannot deserialize symbol path key');
83
83
  }
84
84
 
85
- function createMessageSender(poster, key) {
85
+ function createMessageSender(poster, key, instanceId) {
86
86
  return {
87
- sendMessage(type, data) {
88
- const body = { type, key, data: serializeMessageData(type, data) };
87
+ sendMessage(type, data, targetInstanceId) {
88
+ const body = {
89
+ type,
90
+ key,
91
+ data: serializeMessageData(type, data),
92
+ senderInstanceId: instanceId,
93
+ };
94
+ if (targetInstanceId)
95
+ body.targetInstanceId = targetInstanceId;
89
96
  poster.postMessage(JSON.stringify(body));
90
97
  },
91
98
  };
@@ -108,8 +115,8 @@ function serializeMessageData(type, data) {
108
115
  return data;
109
116
  }
110
117
 
111
- function createMessageChannel(poster, key, context, processorRegistry) {
112
- const sender = createMessageSender(poster, key);
118
+ function createMessageChannel(poster, key, instanceId, context, processorRegistry) {
119
+ const sender = createMessageSender(poster, key, instanceId);
113
120
  const listener = (event) => {
114
121
  let body;
115
122
  try {
@@ -122,11 +129,16 @@ function createMessageChannel(poster, key, context, processorRegistry) {
122
129
  return;
123
130
  if (body.key !== key)
124
131
  return;
132
+ if (body.senderInstanceId === instanceId)
133
+ return;
134
+ if (body.targetInstanceId !== undefined && body.targetInstanceId !== instanceId)
135
+ return;
125
136
  const handler = processorRegistry.get(body.type);
126
137
  if (!handler)
127
138
  return;
139
+ const meta = { senderInstanceId: body.senderInstanceId };
128
140
  try {
129
- handler(deserializeMessageData(body), context);
141
+ handler(deserializeMessageData(body), context, meta);
130
142
  }
131
143
  catch (err) {
132
144
  sendProcessorError(body, sender, err);
@@ -146,6 +158,9 @@ function createMessageChannel(poster, key, context, processorRegistry) {
146
158
  getKey() {
147
159
  return key;
148
160
  },
161
+ getInstanceId() {
162
+ return instanceId;
163
+ },
149
164
  destroy() {
150
165
  poster.removeEventListener('message', listener);
151
166
  },
@@ -158,6 +173,10 @@ function isMessageBody(value) {
158
173
  return false;
159
174
  if (typeof value.key !== 'string')
160
175
  return false;
176
+ if (typeof value.senderInstanceId !== 'string')
177
+ return false;
178
+ if ('targetInstanceId' in value && typeof value.targetInstanceId !== 'string' && value.targetInstanceId !== undefined)
179
+ return false;
161
180
  switch (value.type) {
162
181
  case 'invokeRequest':
163
182
  return isInvokeRequest(value.data);
@@ -171,6 +190,10 @@ function isMessageBody(value) {
171
190
  return isCallbackRequest(value.data);
172
191
  case 'invokeFunctionByIdResponse':
173
192
  return isResponse(value.data);
193
+ case 'releaseCallbacks':
194
+ return isReleaseCallbacks(value.data);
195
+ case 'destroyEndpoint':
196
+ return isDestroyEndpoint(value.data);
174
197
  default:
175
198
  return false;
176
199
  }
@@ -182,7 +205,7 @@ function isAccessPropertyRequest(value) {
182
205
  return isRecord(value) && isMessageId(value.id) && isSerializedPath(value.path);
183
206
  }
184
207
  function isCallbackRequest(value) {
185
- return isRecord(value) && typeof value.id === 'string' && isMessageId(value.callId) && Array.isArray(value.args);
208
+ return isRecord(value) && isMessageId(value.id) && isMessageId(value.callId) && Array.isArray(value.args);
186
209
  }
187
210
  function isResponse(value) {
188
211
  return (isRecord(value) &&
@@ -190,6 +213,12 @@ function isResponse(value) {
190
213
  (!('error' in value) || isSerializedError(value.error)) &&
191
214
  ('data' in value || 'error' in value));
192
215
  }
216
+ function isReleaseCallbacks(value) {
217
+ return isRecord(value) && Array.isArray(value.ids) && value.ids.every((id) => typeof id === 'string');
218
+ }
219
+ function isDestroyEndpoint(value) {
220
+ return isRecord(value) && typeof value.instanceId === 'string';
221
+ }
193
222
  function isSerializedError(value) {
194
223
  return (isRecord(value) && typeof value.message === 'string' && (!('stack' in value) || typeof value.stack === 'string'));
195
224
  }
@@ -222,7 +251,7 @@ function sendProcessorError(body, sender, err) {
222
251
  sender.sendMessage('invokeResponse', {
223
252
  id: data.id,
224
253
  error: serializeThrownError(err),
225
- });
254
+ }, body.senderInstanceId);
226
255
  return;
227
256
  }
228
257
  if (body.type === 'accessPropertyRequest') {
@@ -230,7 +259,7 @@ function sendProcessorError(body, sender, err) {
230
259
  sender.sendMessage('accessPropertyResponse', {
231
260
  id: data.id,
232
261
  error: serializeThrownError(err),
233
- });
262
+ }, body.senderInstanceId);
234
263
  return;
235
264
  }
236
265
  if (body.type === 'invokeFunctionByIdRequest') {
@@ -240,103 +269,151 @@ function sendProcessorError(body, sender, err) {
240
269
  sender.sendMessage('invokeFunctionByIdResponse', {
241
270
  id: data.callId,
242
271
  error,
243
- });
272
+ }, body.senderInstanceId);
244
273
  }
245
274
  }
246
275
 
247
- function deserialize(arg, generateCallback, getRemoteCallback, options = {}) {
248
- return deserialize0(arg, generateCallback, getRemoteCallback, options);
276
+ const TYPE_KEY = '$cii$';
277
+ function serialize(arg, registerFunction, options = {}) {
278
+ return serialize0(arg, undefined, registerFunction, options, new WeakSet());
249
279
  }
250
- function deserialize0(arg, generateCallback, getRemoteCallback, options = {}) {
251
- if (typeof arg === 'string') {
252
- return deserializePrimitive(arg, generateCallback, getRemoteCallback, options);
280
+ function serialize0(arg, owner, registerFunction, options, seen) {
281
+ if (arg === undefined)
282
+ return { [TYPE_KEY]: 'undef' };
283
+ if (arg === null)
284
+ return null;
285
+ const typeOf = typeof arg;
286
+ if (typeOf === 'string')
287
+ return arg;
288
+ if (typeOf === 'boolean')
289
+ return arg;
290
+ if (typeOf === 'number')
291
+ return serializeNumber(arg);
292
+ if (typeOf === 'bigint')
293
+ return { [TYPE_KEY]: 'big', v: arg.toString() };
294
+ if (typeOf === 'function') {
295
+ const id = registerFunction(arg, owner, options);
296
+ return options.persistent ? { [TYPE_KEY]: 'fn', id, persistent: true } : { [TYPE_KEY]: 'fn', id };
253
297
  }
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));
298
+ if (typeOf === 'symbol')
299
+ return { [TYPE_KEY]: 'sym', v: serializeSymbol(arg) };
300
+ if (typeOf === 'object') {
301
+ if (seen.has(arg)) {
302
+ throw new TypeError('Cannot serialize circular structure');
262
303
  }
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;
304
+ seen.add(arg);
278
305
  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);
306
+ if (arg instanceof Error)
307
+ return serializeError(arg, owner, registerFunction, options, seen);
308
+ if (arg instanceof Date)
309
+ return { [TYPE_KEY]: 'date', v: arg.getTime() };
310
+ if (arg instanceof RegExp)
311
+ return { [TYPE_KEY]: 're', source: arg.source, flags: arg.flags };
312
+ if (arg instanceof Map) {
313
+ const entries = [];
314
+ for (const [k, v] of arg.entries()) {
315
+ entries.push([
316
+ serialize0(k, arg, registerFunction, options, seen),
317
+ serialize0(v, arg, registerFunction, options, seen),
318
+ ]);
319
+ }
320
+ return { [TYPE_KEY]: 'map', entries };
293
321
  }
294
- else {
295
- return generateCallback(id, args);
322
+ if (arg instanceof Set) {
323
+ const values = [];
324
+ for (const v of arg.values()) {
325
+ values.push(serialize0(v, arg, registerFunction, options, seen));
326
+ }
327
+ return { [TYPE_KEY]: 'set', values };
296
328
  }
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);
329
+ if (Array.isArray(arg)) {
330
+ return arg.map((item) => serialize0(item, arg, registerFunction, options, seen));
331
+ }
332
+ return serializePlainObject(arg, registerFunction, options, seen);
333
+ }
334
+ finally {
335
+ seen.delete(arg);
316
336
  }
317
337
  }
318
- return value;
338
+ return null;
319
339
  }
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
- };
340
+ function serializeError(err, owner, registerFunction, options, seen) {
341
+ const result = { [TYPE_KEY]: 'err', message: err.message };
342
+ if (err.name && err.name !== 'Error')
343
+ result.name = err.name;
344
+ if (err.stack !== undefined)
345
+ result.stack = err.stack;
346
+ const cause = err.cause;
347
+ if (cause !== undefined) {
348
+ result.cause = serialize0(cause, owner, registerFunction, options, seen);
349
+ }
350
+ const RESERVED_ERROR_KEYS = new Set(['message', 'name', 'stack', 'cause']);
351
+ const extras = [];
352
+ for (const k of Reflect.ownKeys(err)) {
353
+ if (typeof k === 'symbol')
354
+ continue;
355
+ if (RESERVED_ERROR_KEYS.has(k))
356
+ continue;
357
+ const value = err[k];
358
+ extras.push([k, serialize0(value, owner, registerFunction, options, seen)]);
327
359
  }
328
- return { id: value, options };
360
+ if (extras.length > 0)
361
+ result.extras = extras;
362
+ return result;
329
363
  }
330
- function deserializeSymbol(value) {
331
- if (value.startsWith('global:')) {
332
- return Symbol.for(value.slice('global:'.length));
364
+ function serializePlainObject(source, registerFunction, options, seen) {
365
+ const record = source;
366
+ const ownKeys = Reflect.ownKeys(source).filter((k) => isEnumerable(source, k));
367
+ const symbolKeys = [];
368
+ const stringKeys = [];
369
+ for (const k of ownKeys) {
370
+ if (typeof k === 'symbol') {
371
+ if (isSerializableSymbol(k))
372
+ symbolKeys.push(k);
373
+ }
374
+ else {
375
+ stringKeys.push(k);
376
+ }
333
377
  }
334
- if (value.startsWith('wellKnown:')) {
335
- const symbol = getWellKnownSymbol(value.slice('wellKnown:'.length));
336
- if (symbol)
337
- return symbol;
378
+ const hasReservedKey = Object.prototype.hasOwnProperty.call(record, TYPE_KEY);
379
+ if (symbolKeys.length > 0 || hasReservedKey) {
380
+ const entries = [];
381
+ for (const k of stringKeys) {
382
+ entries.push([k, serialize0(record[k], source, registerFunction, options, seen)]);
383
+ }
384
+ for (const k of symbolKeys) {
385
+ entries.push([
386
+ { [TYPE_KEY]: 'sym', v: serializeSymbol(k) },
387
+ serialize0(record[k], source, registerFunction, options, seen),
388
+ ]);
389
+ }
390
+ return { [TYPE_KEY]: 'obj', entries };
338
391
  }
339
- throw new TypeError('Cannot deserialize symbol');
392
+ const result = {};
393
+ for (const k of stringKeys) {
394
+ setOwnProperty$1(result, k, serialize0(record[k], source, registerFunction, options, seen));
395
+ }
396
+ return result;
397
+ }
398
+ function isSerializableSymbol(value) {
399
+ if (Symbol.keyFor(value) !== undefined)
400
+ return true;
401
+ return getWellKnownSymbolName(value) !== undefined;
402
+ }
403
+ function isEnumerable(target, key) {
404
+ const descriptor = Object.getOwnPropertyDescriptor(target, key);
405
+ return descriptor ? descriptor.enumerable === true : false;
406
+ }
407
+ function serializeNumber(value) {
408
+ if (Number.isNaN(value))
409
+ return { [TYPE_KEY]: 'num', v: 'NaN' };
410
+ if (value === Infinity)
411
+ return { [TYPE_KEY]: 'num', v: 'Infinity' };
412
+ if (value === -Infinity)
413
+ return { [TYPE_KEY]: 'num', v: '-Infinity' };
414
+ if (Object.is(value, -0))
415
+ return { [TYPE_KEY]: 'num', v: '-0' };
416
+ return value;
340
417
  }
341
418
  function setOwnProperty$1(target, key, value) {
342
419
  Object.defineProperty(target, key, {
@@ -346,58 +423,244 @@ function setOwnProperty$1(target, key, value) {
346
423
  writable: true,
347
424
  });
348
425
  }
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);
426
+ function serializeSymbol(value) {
427
+ const globalKey = Symbol.keyFor(value);
428
+ if (globalKey)
429
+ return `global:${globalKey}`;
430
+ const wellKnownName = getWellKnownSymbolName(value);
431
+ if (wellKnownName)
432
+ return `wellKnown:${wellKnownName}`;
433
+ throw new TypeError('Cannot serialize non-global symbol');
359
434
  }
360
435
 
361
- function serialize(arg, registerFunction, options = {}) {
362
- return serialize0(arg, registerFunction, options, new WeakSet());
436
+ const ERROR_CONSTRUCTORS = {
437
+ Error,
438
+ TypeError: TypeError,
439
+ RangeError: RangeError,
440
+ SyntaxError: SyntaxError,
441
+ ReferenceError: ReferenceError,
442
+ URIError: URIError,
443
+ EvalError: EvalError,
444
+ };
445
+ function deserialize(arg, generateCallback, getRemoteCallback) {
446
+ return deserialize0(arg, generateCallback, getRemoteCallback);
363
447
  }
364
- function serialize0(arg, registerFunction, options, seen) {
365
- if (arg === undefined) {
366
- return '$undefined$';
367
- }
448
+ function deserialize0(arg, generateCallback, getRemoteCallback) {
368
449
  if (arg === null)
369
- return '$null$';
370
- if (typeof arg === 'object') {
371
- if (seen.has(arg)) {
372
- throw new TypeError('Cannot serialize circular structure');
450
+ return null;
451
+ if (typeof arg !== 'object')
452
+ return arg;
453
+ if (Array.isArray(arg)) {
454
+ return arg.map((item) => deserialize0(item, generateCallback, getRemoteCallback));
455
+ }
456
+ const source = arg;
457
+ const tag = source[TYPE_KEY];
458
+ if (typeof tag === 'string') {
459
+ return deserializeTagged(tag, source, generateCallback, getRemoteCallback);
460
+ }
461
+ const result = {};
462
+ for (const key of Object.keys(source)) {
463
+ setOwnProperty(result, key, deserialize0(source[key], generateCallback, getRemoteCallback));
464
+ }
465
+ return result;
466
+ }
467
+ function deserializeTagged(tag, source, generateCallback, getRemoteCallback) {
468
+ switch (tag) {
469
+ case 'undef':
470
+ return undefined;
471
+ case 'big':
472
+ return deserializeBigInt(source.v);
473
+ case 'sym':
474
+ return deserializeSymbol(expectString(source.v, 'symbol'));
475
+ case 'num':
476
+ return deserializeNumber(expectString(source.v, 'number'));
477
+ case 'err':
478
+ return deserializeError(source, generateCallback, getRemoteCallback);
479
+ case 'date':
480
+ return deserializeDate(source.v);
481
+ case 're':
482
+ return deserializeRegExp(source.source, source.flags);
483
+ case 'map':
484
+ return deserializeMap(source.entries, generateCallback, getRemoteCallback);
485
+ case 'set':
486
+ return deserializeSet(source.values, generateCallback, getRemoteCallback);
487
+ case 'fn':
488
+ return deserializeFunction(source, generateCallback, getRemoteCallback);
489
+ case 'obj':
490
+ return deserializeWrappedObject(source.entries, generateCallback, getRemoteCallback);
491
+ default: {
492
+ const result = {};
493
+ for (const key of Object.keys(source)) {
494
+ setOwnProperty(result, key, deserialize0(source[key], generateCallback, getRemoteCallback));
495
+ }
496
+ return result;
373
497
  }
374
- seen.add(arg);
498
+ }
499
+ }
500
+ function deserializeBigInt(raw) {
501
+ if (typeof raw !== 'string')
502
+ throw new TypeError('Invalid bigint payload');
503
+ try {
504
+ return BigInt(raw);
505
+ }
506
+ catch {
507
+ throw new TypeError(`Invalid bigint payload: ${raw}`);
508
+ }
509
+ }
510
+ function deserializeDate(raw) {
511
+ if (typeof raw !== 'number' || !Number.isFinite(raw)) {
512
+ throw new TypeError('Invalid date payload');
513
+ }
514
+ return new Date(raw);
515
+ }
516
+ function deserializeRegExp(rawSource, rawFlags) {
517
+ if (typeof rawSource !== 'string')
518
+ throw new TypeError('Invalid regexp source');
519
+ if (typeof rawFlags !== 'string')
520
+ throw new TypeError('Invalid regexp flags');
521
+ try {
522
+ return new RegExp(rawSource, rawFlags);
523
+ }
524
+ catch (err) {
525
+ throw new TypeError(`Invalid regexp payload: ${err instanceof Error ? err.message : String(err)}`);
526
+ }
527
+ }
528
+ function deserializeMap(entries, generateCallback, getRemoteCallback) {
529
+ const result = new Map();
530
+ if (!Array.isArray(entries))
531
+ return result;
532
+ for (const entry of entries) {
533
+ if (!Array.isArray(entry) || entry.length !== 2)
534
+ continue;
535
+ const k = deserialize0(entry[0], generateCallback, getRemoteCallback);
536
+ const v = deserialize0(entry[1], generateCallback, getRemoteCallback);
537
+ result.set(k, v);
538
+ }
539
+ return result;
540
+ }
541
+ function deserializeSet(values, generateCallback, getRemoteCallback) {
542
+ const result = new Set();
543
+ if (!Array.isArray(values))
544
+ return result;
545
+ for (const v of values) {
546
+ result.add(deserialize0(v, generateCallback, getRemoteCallback));
547
+ }
548
+ return result;
549
+ }
550
+ function deserializeFunction(source, generateCallback, getRemoteCallback) {
551
+ const id = source.id;
552
+ if (typeof id !== 'string' || id.length === 0) {
553
+ throw new TypeError('Invalid function payload');
554
+ }
555
+ const persistent = source.persistent === true;
556
+ const fnOptions = { persistent };
557
+ const invoke = (args) => {
558
+ if (persistent)
559
+ return generateCallback(id, args, fnOptions);
560
+ return generateCallback(id, args);
561
+ };
562
+ return getRemoteCallback ? getRemoteCallback(id, invoke, fnOptions) : (...args) => invoke(args);
563
+ }
564
+ function deserializeError(source, generateCallback, getRemoteCallback) {
565
+ const message = typeof source.message === 'string' ? source.message : '';
566
+ const name = typeof source.name === 'string' ? source.name : 'Error';
567
+ const Ctor = ERROR_CONSTRUCTORS[name] ?? Error;
568
+ const error = new Ctor(message);
569
+ if (name && name !== Ctor.name) {
375
570
  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));
571
+ error.name = name;
572
+ }
573
+ catch {
574
+ // ignore non-writable name
575
+ }
576
+ }
577
+ if (typeof source.stack === 'string')
578
+ error.stack = source.stack;
579
+ if (source.cause !== undefined) {
580
+ error.cause = deserialize0(source.cause, generateCallback, getRemoteCallback);
581
+ }
582
+ if (Array.isArray(source.extras)) {
583
+ for (const entry of source.extras) {
584
+ if (!Array.isArray(entry) || entry.length !== 2)
585
+ continue;
586
+ const [rawKey, rawValue] = entry;
587
+ if (typeof rawKey !== 'string')
588
+ continue;
589
+ try {
590
+ error[rawKey] = deserialize0(rawValue, generateCallback, getRemoteCallback);
381
591
  }
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));
592
+ catch {
593
+ // ignore non-writable property
386
594
  }
387
- return result;
388
595
  }
389
- finally {
390
- seen.delete(arg);
596
+ }
597
+ return error;
598
+ }
599
+ function deserializeWrappedObject(entries, generateCallback, getRemoteCallback) {
600
+ const result = {};
601
+ if (!Array.isArray(entries))
602
+ return result;
603
+ for (const entry of entries) {
604
+ if (!Array.isArray(entry) || entry.length !== 2)
605
+ continue;
606
+ const rawKey = entry[0];
607
+ const value = deserialize0(entry[1], generateCallback, getRemoteCallback);
608
+ const key = resolveObjectKey(rawKey);
609
+ if (key === undefined) {
610
+ console.warn('chrome-in-iframe: dropping wrapped-object entry with unresolvable key', rawKey);
611
+ continue;
612
+ }
613
+ Object.defineProperty(result, key, {
614
+ value,
615
+ enumerable: true,
616
+ configurable: true,
617
+ writable: true,
618
+ });
619
+ }
620
+ return result;
621
+ }
622
+ function resolveObjectKey(raw) {
623
+ if (typeof raw === 'string')
624
+ return raw;
625
+ if (raw !== null && typeof raw === 'object') {
626
+ const tagged = raw;
627
+ if (tagged[TYPE_KEY] === 'sym' && typeof tagged.v === 'string') {
628
+ try {
629
+ return deserializeSymbol(tagged.v);
630
+ }
631
+ catch {
632
+ return undefined;
633
+ }
391
634
  }
392
635
  }
393
- if (typeof arg === 'function') {
394
- const id = registerFunction(arg, options);
395
- return '$function$' + (options.persistent ? `persistent:${id}` : id);
636
+ return undefined;
637
+ }
638
+ function expectString(raw, label) {
639
+ if (typeof raw !== 'string')
640
+ throw new TypeError(`Invalid ${label} payload`);
641
+ return raw;
642
+ }
643
+ function deserializeNumber(raw) {
644
+ if (raw === 'NaN')
645
+ return NaN;
646
+ if (raw === 'Infinity')
647
+ return Infinity;
648
+ if (raw === '-Infinity')
649
+ return -Infinity;
650
+ if (raw === '-0')
651
+ return -0;
652
+ return Number(raw);
653
+ }
654
+ function deserializeSymbol(value) {
655
+ if (value.startsWith('global:')) {
656
+ return Symbol.for(value.slice('global:'.length));
396
657
  }
397
- if (typeof arg === 'symbol') {
398
- return '$symbol$' + serializeSymbol(arg);
658
+ if (value.startsWith('wellKnown:')) {
659
+ const symbol = getWellKnownSymbol(value.slice('wellKnown:'.length));
660
+ if (symbol)
661
+ return symbol;
399
662
  }
400
- return `$${typeof arg}$${arg}`;
663
+ throw new TypeError('Cannot deserialize symbol');
401
664
  }
402
665
  function setOwnProperty(target, key, value) {
403
666
  Object.defineProperty(target, key, {
@@ -407,14 +670,18 @@ function setOwnProperty(target, key, value) {
407
670
  writable: true,
408
671
  });
409
672
  }
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');
673
+
674
+ function isListenerRegistrationPath(path) {
675
+ const last = path[path.length - 1];
676
+ return last === 'addListener' || last === 'hasListener';
677
+ }
678
+ function isListenerRemovalPath(path) {
679
+ const last = path[path.length - 1];
680
+ return last === 'removeListener';
681
+ }
682
+ function isLikelyListenerPath(path) {
683
+ const last = path[path.length - 1];
684
+ return typeof last === 'string' && /^on[A-Z]/.test(last);
418
685
  }
419
686
 
420
687
  const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
@@ -427,48 +694,68 @@ function readProperty(target, key) {
427
694
  return target[key];
428
695
  }
429
696
 
430
- function handleAccessPropertyRequest(data, ctx) {
697
+ const boundCache = new WeakMap();
698
+ function bindMethod(fn, owner) {
699
+ if (owner === null || typeof owner !== 'object') {
700
+ return (...args) => Reflect.apply(fn, owner, args);
701
+ }
702
+ let perOwner = boundCache.get(fn);
703
+ if (!perOwner) {
704
+ perOwner = new WeakMap();
705
+ boundCache.set(fn, perOwner);
706
+ }
707
+ const cached = perOwner.get(owner);
708
+ if (cached)
709
+ return cached;
710
+ const bound = (...args) => Reflect.apply(fn, owner, args);
711
+ perOwner.set(owner, bound);
712
+ return bound;
713
+ }
714
+ function handleAccessPropertyRequest(data, ctx, meta) {
431
715
  const target = ctx.getDelegateTarget();
432
716
  const channel = ctx.getMessageChannel();
433
717
  if (data.path.length === 0) {
434
718
  channel.getSender().sendMessage('accessPropertyResponse', {
435
719
  id: data.id,
436
720
  error: { message: 'Property path must not be empty' },
437
- });
721
+ }, meta.senderInstanceId);
438
722
  return;
439
723
  }
440
724
  if (!target) {
441
725
  channel.getSender().sendMessage('accessPropertyResponse', {
442
726
  id: data.id,
443
727
  error: { message: 'No delegate target is configured' },
444
- });
728
+ }, meta.senderInstanceId);
445
729
  return;
446
730
  }
447
731
  try {
448
732
  let current = target;
733
+ let owner = target;
449
734
  for (const key of data.path) {
450
735
  if (current === undefined || current === null) {
451
736
  channel.getSender().sendMessage('accessPropertyResponse', {
452
737
  id: data.id,
453
738
  data: serialize(current, ctx.registerFunction),
454
- });
739
+ }, meta.senderInstanceId);
455
740
  return;
456
741
  }
742
+ owner = current;
457
743
  current = readProperty(current, key);
458
744
  }
745
+ const value = typeof current === 'function' ? bindMethod(current, owner) : current;
459
746
  channel.getSender().sendMessage('accessPropertyResponse', {
460
747
  id: data.id,
461
- data: serialize(current, ctx.registerFunction, { persistent: isLikelyListenerPath(data.path, current) }),
462
- });
748
+ data: serialize(value, ctx.registerFunction, { persistent: isLikelyListenerPath(data.path) }),
749
+ }, meta.senderInstanceId);
463
750
  }
464
751
  catch (err) {
465
752
  channel.getSender().sendMessage('accessPropertyResponse', {
466
753
  id: data.id,
467
754
  error: serializeThrownError(err),
468
- });
755
+ }, meta.senderInstanceId);
469
756
  }
470
757
  }
471
- function handleAccessPropertyResponse(data, ctx) {
758
+ function handleAccessPropertyResponse(data, ctx, meta) {
472
759
  const pending = ctx.getAndRemovePendingPromise(data.id);
473
760
  if (!pending)
474
761
  return;
@@ -478,19 +765,29 @@ function handleAccessPropertyResponse(data, ctx) {
478
765
  pending.reject(err);
479
766
  return;
480
767
  }
481
- pending.resolve(deserialize(data.data, (id, args, options) => {
482
- return ctx.invokeFunctionById(id, args, options);
483
- }, ctx.getRemoteCallback));
768
+ const scopedRemoteCallback = createScopedRemoteCallback$2(ctx, meta.senderInstanceId);
769
+ try {
770
+ pending.resolve(deserialize(data.data, (id, args, options) => {
771
+ return ctx.invokeFunctionById(id, args, options);
772
+ }, scopedRemoteCallback));
773
+ }
774
+ catch (err) {
775
+ pending.reject(err instanceof Error ? err : new Error(String(err)));
776
+ }
777
+ }
778
+ function createScopedRemoteCallback$2(ctx, remoteInstanceId) {
779
+ return (id, invoke, options) => ctx.getRemoteCallback(id, invoke, options, remoteInstanceId);
484
780
  }
485
781
 
486
- function handleInvokeRequest(data, ctx) {
782
+ function handleInvokeRequest(data, ctx, meta) {
487
783
  const target = ctx.getDelegateTarget();
488
784
  const channel = ctx.getMessageChannel();
785
+ const scopedRemoteCallback = createScopedRemoteCallback$1(ctx, meta.senderInstanceId);
489
786
  if (!target) {
490
787
  channel.getSender().sendMessage('invokeResponse', {
491
788
  id: data.id,
492
789
  error: { message: 'No delegate target is configured' },
493
- });
790
+ }, meta.senderInstanceId);
494
791
  return;
495
792
  }
496
793
  let current = target;
@@ -502,7 +799,7 @@ function handleInvokeRequest(data, ctx) {
502
799
  error: {
503
800
  message: `Cannot read property '${String(data.path[i + 1])}' of ${String(data.path[i])}`,
504
801
  },
505
- });
802
+ }, meta.senderInstanceId);
506
803
  return;
507
804
  }
508
805
  }
@@ -512,12 +809,22 @@ function handleInvokeRequest(data, ctx) {
512
809
  channel.getSender().sendMessage('invokeResponse', {
513
810
  id: data.id,
514
811
  error: { message: `'${String(lastKey)}' is not a function` },
515
- });
812
+ }, meta.senderInstanceId);
813
+ return;
814
+ }
815
+ let deserializedArgs;
816
+ try {
817
+ deserializedArgs = data.args.map((arg) => deserialize(arg, (id, args, options) => {
818
+ return ctx.invokeFunctionById(id, args, options);
819
+ }, scopedRemoteCallback));
820
+ }
821
+ catch (err) {
822
+ channel.getSender().sendMessage('invokeResponse', {
823
+ id: data.id,
824
+ error: serializeThrownError(err),
825
+ }, meta.senderInstanceId);
516
826
  return;
517
827
  }
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
828
  let result;
522
829
  try {
523
830
  result = Reflect.apply(fn, current, deserializedArgs);
@@ -526,26 +833,26 @@ function handleInvokeRequest(data, ctx) {
526
833
  channel.getSender().sendMessage('invokeResponse', {
527
834
  id: data.id,
528
835
  error: serializeThrownError(err),
529
- });
836
+ }, meta.senderInstanceId);
530
837
  return;
531
838
  }
532
839
  if (result instanceof Promise) {
533
840
  result
534
841
  .then((value) => {
535
- sendInvokeSuccess(data.id, value, ctx);
842
+ sendInvokeSuccess(data.id, value, ctx, meta.senderInstanceId);
536
843
  })
537
844
  .catch((err) => {
538
845
  channel.getSender().sendMessage('invokeResponse', {
539
846
  id: data.id,
540
847
  error: serializeThrownError(err),
541
- });
848
+ }, meta.senderInstanceId);
542
849
  });
543
850
  }
544
851
  else {
545
- sendInvokeSuccess(data.id, result, ctx);
852
+ sendInvokeSuccess(data.id, result, ctx, meta.senderInstanceId);
546
853
  }
547
854
  }
548
- function handleInvokeResponse(data, ctx) {
855
+ function handleInvokeResponse(data, ctx, meta) {
549
856
  const pending = ctx.getAndRemovePendingPromise(data.id);
550
857
  if (!pending)
551
858
  return;
@@ -555,62 +862,79 @@ function handleInvokeResponse(data, ctx) {
555
862
  pending.reject(err);
556
863
  }
557
864
  else {
558
- pending.resolve(deserialize(data.data, (id, args, options) => {
559
- return ctx.invokeFunctionById(id, args, options);
560
- }, ctx.getRemoteCallback));
865
+ const scopedRemoteCallback = createScopedRemoteCallback$1(ctx, meta.senderInstanceId);
866
+ try {
867
+ pending.resolve(deserialize(data.data, (id, args, options) => {
868
+ return ctx.invokeFunctionById(id, args, options);
869
+ }, scopedRemoteCallback));
870
+ }
871
+ catch (err) {
872
+ pending.reject(err instanceof Error ? err : new Error(String(err)));
873
+ }
561
874
  }
562
875
  }
563
- function sendInvokeSuccess(id, value, ctx) {
876
+ function sendInvokeSuccess(id, value, ctx, targetInstanceId) {
564
877
  const channel = ctx.getMessageChannel();
565
878
  try {
566
879
  channel.getSender().sendMessage('invokeResponse', {
567
880
  id,
568
881
  data: serialize(value, ctx.registerFunction),
569
- });
882
+ }, targetInstanceId);
570
883
  }
571
884
  catch (err) {
572
885
  channel.getSender().sendMessage('invokeResponse', {
573
886
  id,
574
887
  error: serializeThrownError(err),
575
- });
888
+ }, targetInstanceId);
576
889
  }
577
890
  }
891
+ function createScopedRemoteCallback$1(ctx, remoteInstanceId) {
892
+ return (id, invoke, options) => ctx.getRemoteCallback(id, invoke, options, remoteInstanceId);
893
+ }
578
894
 
579
- function handleCallbackInvoke(data, ctx) {
895
+ function handleCallbackInvoke(data, ctx, meta) {
580
896
  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) {
897
+ const persistentEntry = ctx.getPersistentFunctionCache().get(data.id);
898
+ const entry = persistentEntry ?? ctx.getFunctionCache().get(data.id);
899
+ if (!entry) {
584
900
  const error = { message: `Callback '${data.id}' is not available or has expired` };
585
901
  console.warn(`chrome-in-iframe: ${error.message}`);
586
902
  channel.getSender().sendMessage('invokeFunctionByIdResponse', {
587
903
  id: data.callId,
588
904
  error,
589
- });
905
+ }, meta.senderInstanceId);
590
906
  return;
591
907
  }
592
- const deserializedArgs = data.args.map((arg) => deserialize(arg, (id, args, options) => {
593
- return ctx.invokeFunctionById(id, args, options);
594
- }, ctx.getRemoteCallback, { persistent: isPersistentFunction }));
908
+ const scopedRemoteCallback = createScopedRemoteCallback(ctx, meta.senderInstanceId);
909
+ let deserializedArgs;
595
910
  try {
596
- const result = fn(...deserializedArgs);
911
+ deserializedArgs = data.args.map((arg) => deserialize(arg, (id, args, options) => {
912
+ return ctx.invokeFunctionById(id, args, options);
913
+ }, scopedRemoteCallback));
914
+ }
915
+ catch (err) {
916
+ sendCallbackError(data.callId, err, ctx, meta.senderInstanceId);
917
+ return;
918
+ }
919
+ try {
920
+ const result = Reflect.apply(entry.fn, entry.thisArg, deserializedArgs);
597
921
  if (result instanceof Promise) {
598
922
  result
599
923
  .then((value) => {
600
- sendCallbackSuccess(data.callId, value, ctx);
924
+ sendCallbackSuccess(data.callId, value, ctx, meta.senderInstanceId);
601
925
  })
602
926
  .catch((err) => {
603
- sendCallbackError(data.callId, err, ctx);
927
+ sendCallbackError(data.callId, err, ctx, meta.senderInstanceId);
604
928
  });
605
929
  return;
606
930
  }
607
- sendCallbackSuccess(data.callId, result, ctx);
931
+ sendCallbackSuccess(data.callId, result, ctx, meta.senderInstanceId);
608
932
  }
609
933
  catch (err) {
610
- sendCallbackError(data.callId, err, ctx);
934
+ sendCallbackError(data.callId, err, ctx, meta.senderInstanceId);
611
935
  }
612
936
  }
613
- function handleCallbackInvokeResponse(data, ctx) {
937
+ function handleCallbackInvokeResponse(data, ctx, meta) {
614
938
  const pending = ctx.getAndRemovePendingPromise(data.id);
615
939
  if (!pending)
616
940
  return;
@@ -620,30 +944,52 @@ function handleCallbackInvokeResponse(data, ctx) {
620
944
  pending.reject(err);
621
945
  return;
622
946
  }
623
- pending.resolve(deserialize(data.data, (id, args, options) => {
624
- return ctx.invokeFunctionById(id, args, options);
625
- }, ctx.getRemoteCallback));
947
+ const scopedRemoteCallback = createScopedRemoteCallback(ctx, meta.senderInstanceId);
948
+ try {
949
+ pending.resolve(deserialize(data.data, (id, args, options) => {
950
+ return ctx.invokeFunctionById(id, args, options);
951
+ }, scopedRemoteCallback));
952
+ }
953
+ catch (err) {
954
+ pending.reject(err instanceof Error ? err : new Error(String(err)));
955
+ }
626
956
  }
627
- function sendCallbackSuccess(id, value, ctx) {
957
+ function sendCallbackSuccess(id, value, ctx, targetInstanceId) {
628
958
  const channel = ctx.getMessageChannel();
629
959
  try {
630
960
  channel.getSender().sendMessage('invokeFunctionByIdResponse', {
631
961
  id,
632
962
  data: serialize(value, ctx.registerFunction),
633
- });
963
+ }, targetInstanceId);
634
964
  }
635
965
  catch (err) {
636
- sendCallbackError(id, err, ctx);
966
+ sendCallbackError(id, err, ctx, targetInstanceId);
637
967
  }
638
968
  }
639
- function sendCallbackError(id, err, ctx) {
969
+ function sendCallbackError(id, err, ctx, targetInstanceId) {
640
970
  ctx
641
971
  .getMessageChannel()
642
972
  .getSender()
643
973
  .sendMessage('invokeFunctionByIdResponse', {
644
974
  id,
645
975
  error: serializeThrownError(err),
646
- });
976
+ }, targetInstanceId);
977
+ }
978
+ function createScopedRemoteCallback(ctx, remoteInstanceId) {
979
+ return (id, invoke, options) => ctx.getRemoteCallback(id, invoke, options, remoteInstanceId);
980
+ }
981
+
982
+ const MAX_RELEASE_BATCH = 10000;
983
+ function handleReleaseCallbacks(data, ctx, _meta) {
984
+ if (data.ids.length > MAX_RELEASE_BATCH)
985
+ return;
986
+ for (const id of data.ids) {
987
+ ctx.getFunctionCache().delete(id);
988
+ ctx.getPersistentFunctionCache().delete(id);
989
+ }
990
+ }
991
+ function handleDestroyEndpoint(_data, ctx, meta) {
992
+ ctx.handleRemoteDestroy(meta.senderInstanceId);
647
993
  }
648
994
 
649
995
  function createProcessorRegistry() {
@@ -659,56 +1005,90 @@ function createProcessorRegistry() {
659
1005
  }
660
1006
 
661
1007
  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 = {}) {
1008
+ const DEFAULT_FUNCTION_CACHE_MAX = 100;
1009
+ const DEFAULT_FUNCTION_CACHE_TTL = 5 * 60 * 1000;
1010
+ const DEFAULT_REMOTE_CALLBACK_CACHE_MAX = 500;
1011
+ const DEFAULT_REMOTE_CALLBACK_CACHE_TTL = 5 * 60 * 1000;
1012
+ function createClientContext(getMessageChannel, instanceId, options = {}) {
1013
+ const remoteCacheMax = options.remoteCallbackCacheMax ?? DEFAULT_REMOTE_CALLBACK_CACHE_MAX;
1014
+ const remoteCacheTtl = options.remoteCallbackCacheTtl ?? DEFAULT_REMOTE_CALLBACK_CACHE_TTL;
667
1015
  const pending = new Map();
1016
+ const functionIds = new Map();
668
1017
  const functionCache = new LRUCache({
669
- max: FUNCTION_CACHE_MAX,
670
- ttl: FUNCTION_CACHE_TTL,
1018
+ max: options.functionCacheMax ?? DEFAULT_FUNCTION_CACHE_MAX,
1019
+ ttl: options.functionCacheTtl ?? DEFAULT_FUNCTION_CACHE_TTL,
1020
+ dispose: (value, _key, reason) => {
1021
+ if (reason === 'set' || reason === 'delete')
1022
+ return;
1023
+ functionIds.delete(value.fn);
1024
+ },
671
1025
  });
672
1026
  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,
677
- });
678
- const persistentRemoteCallbacks = new Map();
1027
+ const remoteCallbacksByOwner = new Map();
1028
+ const persistentRemoteCallbacksByOwner = new Map();
679
1029
  let destroyed = false;
680
- const registerFunction = (fn, callbackOptions = {}) => {
1030
+ const getOrCreateRemoteLru = (remoteId) => {
1031
+ let lru = remoteCallbacksByOwner.get(remoteId);
1032
+ if (!lru) {
1033
+ lru = new LRUCache({ max: remoteCacheMax, ttl: remoteCacheTtl });
1034
+ remoteCallbacksByOwner.set(remoteId, lru);
1035
+ }
1036
+ return lru;
1037
+ };
1038
+ const getOrCreatePersistentRemoteMap = (remoteId) => {
1039
+ let map = persistentRemoteCallbacksByOwner.get(remoteId);
1040
+ if (!map) {
1041
+ map = new Map();
1042
+ persistentRemoteCallbacksByOwner.set(remoteId, map);
1043
+ }
1044
+ return map;
1045
+ };
1046
+ const registerFunction = (fn, thisArg, callbackOptions = {}) => {
681
1047
  let id = functionIds.get(fn);
682
1048
  if (!id) {
683
1049
  id = createId('cb');
684
1050
  functionIds.set(fn, id);
685
1051
  }
1052
+ const entry = { fn, thisArg };
686
1053
  if (callbackOptions.persistent) {
687
- persistentFunctionCache.set(id, fn);
1054
+ persistentFunctionCache.set(id, entry);
688
1055
  functionCache.delete(id);
689
1056
  }
690
1057
  else if (!persistentFunctionCache.has(id)) {
691
- functionCache.set(id, fn);
1058
+ functionCache.set(id, entry);
692
1059
  }
693
1060
  return id;
694
1061
  };
695
- const getRemoteCallback = (id, invoke, callbackOptions = {}) => {
696
- let callback = persistentRemoteCallbacks.get(id) ?? remoteCallbacks.get(id);
1062
+ const getRemoteCallback = (id, invoke, callbackOptions, remoteInstanceId) => {
1063
+ const opts = callbackOptions ?? {};
1064
+ const lru = remoteCallbacksByOwner.get(remoteInstanceId);
1065
+ const pmap = persistentRemoteCallbacksByOwner.get(remoteInstanceId);
1066
+ let callback = pmap?.get(id) ?? lru?.get(id);
697
1067
  if (!callback) {
698
1068
  callback = (...args) => invoke(args);
699
- if (callbackOptions.persistent) {
700
- persistentRemoteCallbacks.set(id, callback);
1069
+ if (opts.persistent) {
1070
+ getOrCreatePersistentRemoteMap(remoteInstanceId).set(id, callback);
701
1071
  }
702
1072
  else {
703
- remoteCallbacks.set(id, callback);
1073
+ getOrCreateRemoteLru(remoteInstanceId).set(id, callback);
704
1074
  }
705
1075
  }
706
- else if (callbackOptions.persistent) {
707
- remoteCallbacks.delete(id);
708
- persistentRemoteCallbacks.set(id, callback);
1076
+ else if (opts.persistent && !pmap?.has(id)) {
1077
+ lru?.delete(id);
1078
+ getOrCreatePersistentRemoteMap(remoteInstanceId).set(id, callback);
709
1079
  }
710
1080
  return callback;
711
1081
  };
1082
+ const notifyReleaseCallbacks = (ids) => {
1083
+ if (ids.length === 0)
1084
+ return;
1085
+ try {
1086
+ getMessageChannel().getSender().sendMessage('releaseCallbacks', { ids });
1087
+ }
1088
+ catch {
1089
+ // channel may not be available yet
1090
+ }
1091
+ };
712
1092
  return {
713
1093
  getDelegateTarget() {
714
1094
  return options.delegateTarget;
@@ -716,17 +1096,20 @@ function createClientContext(getMessageChannel, options = {}) {
716
1096
  getMessageChannel() {
717
1097
  return getMessageChannel();
718
1098
  },
1099
+ getInstanceId() {
1100
+ return instanceId;
1101
+ },
719
1102
  getFunctionCache() {
720
1103
  return functionCache;
721
1104
  },
722
1105
  getPersistentFunctionCache() {
723
1106
  return persistentFunctionCache;
724
1107
  },
725
- registerFunction(fn, callbackOptions) {
726
- return registerFunction(fn, callbackOptions);
1108
+ registerFunction(fn, thisArg, callbackOptions) {
1109
+ return registerFunction(fn, thisArg, callbackOptions);
727
1110
  },
728
- getRemoteCallback(id, invoke, callbackOptions) {
729
- return getRemoteCallback(id, invoke, callbackOptions);
1111
+ getRemoteCallback(id, invoke, callbackOptions, remoteInstanceId) {
1112
+ return getRemoteCallback(id, invoke, callbackOptions, remoteInstanceId);
730
1113
  },
731
1114
  getAndRemovePendingPromise(id) {
732
1115
  const callbacks = pending.get(id);
@@ -755,6 +1138,21 @@ function createClientContext(getMessageChannel, options = {}) {
755
1138
  const channel = getMessageChannel();
756
1139
  const persistent = isListenerRegistrationPath(path);
757
1140
  const serializedArgs = args.map((arg) => serialize(arg, registerFunction, { persistent }));
1141
+ if (isListenerRemovalPath(path)) {
1142
+ const releasedIds = [];
1143
+ for (const original of args) {
1144
+ if (typeof original !== 'function')
1145
+ continue;
1146
+ const fnId = functionIds.get(original);
1147
+ if (fnId) {
1148
+ releasedIds.push(fnId);
1149
+ functionCache.delete(fnId);
1150
+ persistentFunctionCache.delete(fnId);
1151
+ functionIds.delete(original);
1152
+ }
1153
+ }
1154
+ notifyReleaseCallbacks(releasedIds);
1155
+ }
758
1156
  channel.getSender().sendMessage('invokeRequest', {
759
1157
  id,
760
1158
  path,
@@ -803,10 +1201,22 @@ function createClientContext(getMessageChannel, options = {}) {
803
1201
  });
804
1202
  });
805
1203
  },
806
- destroy() {
1204
+ handleRemoteDestroy(remoteInstanceId) {
1205
+ remoteCallbacksByOwner.delete(remoteInstanceId);
1206
+ persistentRemoteCallbacksByOwner.delete(remoteInstanceId);
1207
+ },
1208
+ destroy(notifyRemote = true) {
807
1209
  if (destroyed)
808
1210
  return;
809
1211
  destroyed = true;
1212
+ if (notifyRemote) {
1213
+ try {
1214
+ getMessageChannel().getSender().sendMessage('destroyEndpoint', { instanceId });
1215
+ }
1216
+ catch {
1217
+ // channel may already be torn down
1218
+ }
1219
+ }
810
1220
  for (const [id, callbacks] of pending) {
811
1221
  clearTimeout(callbacks.timer);
812
1222
  callbacks.reject(new Error(`Channel has been destroyed before call ${id} completed`));
@@ -814,8 +1224,9 @@ function createClientContext(getMessageChannel, options = {}) {
814
1224
  pending.clear();
815
1225
  functionCache.clear();
816
1226
  persistentFunctionCache.clear();
817
- remoteCallbacks.clear();
818
- persistentRemoteCallbacks.clear();
1227
+ functionIds.clear();
1228
+ remoteCallbacksByOwner.clear();
1229
+ persistentRemoteCallbacksByOwner.clear();
819
1230
  },
820
1231
  };
821
1232
  }
@@ -823,6 +1234,43 @@ function createId(prefix) {
823
1234
  return `${prefix}-${nanoid()}`;
824
1235
  }
825
1236
 
1237
+ const EMPTY_TARGET = () => undefined;
1238
+ const INTERCEPTED_STRING_PROPS = new Set([
1239
+ 'then',
1240
+ 'catch',
1241
+ 'finally',
1242
+ 'toJSON',
1243
+ 'toString',
1244
+ 'valueOf',
1245
+ 'nodeType',
1246
+ 'tagName',
1247
+ 'asymmetricMatch',
1248
+ '$$typeof',
1249
+ '@@__IMMUTABLE_ITERABLE__@@',
1250
+ '@@__IMMUTABLE_RECORD__@@',
1251
+ 'length',
1252
+ 'name',
1253
+ 'prototype',
1254
+ 'arguments',
1255
+ 'caller',
1256
+ 'bind',
1257
+ 'call',
1258
+ 'apply',
1259
+ ]);
1260
+ const INTERCEPTED_NONE_SYMBOLS = new Set([
1261
+ Symbol.toStringTag,
1262
+ Symbol.iterator,
1263
+ Symbol.asyncIterator,
1264
+ Symbol.isConcatSpreadable,
1265
+ ]);
1266
+ const PROXY_DESCRIPTION = '[chrome-in-iframe proxy]';
1267
+ function toPrimitiveDescriptor() {
1268
+ return (hint) => {
1269
+ if (hint === 'number')
1270
+ return NaN;
1271
+ return PROXY_DESCRIPTION;
1272
+ };
1273
+ }
826
1274
  function resolvePath(node) {
827
1275
  const path = [];
828
1276
  let current = node;
@@ -834,11 +1282,14 @@ function resolvePath(node) {
834
1282
  }
835
1283
  function createClientProxy(invoke, accessProperty) {
836
1284
  const normalizePropertyPath = (basePath, parts) => {
1285
+ if (parts.length === 1 && typeof parts[0] === 'string') {
1286
+ return [...basePath, ...parts[0].split('.').filter(Boolean)];
1287
+ }
837
1288
  const extraPath = parts.flatMap((part) => {
838
1289
  if (Array.isArray(part))
839
1290
  return part.filter(isPathKey);
840
1291
  if (typeof part === 'string')
841
- return part.split('.').filter(Boolean);
1292
+ return [part];
842
1293
  if (typeof part === 'symbol')
843
1294
  return [part];
844
1295
  return [];
@@ -848,19 +1299,27 @@ function createClientProxy(invoke, accessProperty) {
848
1299
  function createHandler(node) {
849
1300
  return {
850
1301
  get(_target, prop) {
851
- if (prop === 'then')
1302
+ if (typeof prop === 'string') {
1303
+ if (INTERCEPTED_STRING_PROPS.has(prop))
1304
+ return undefined;
1305
+ if (prop === '$get') {
1306
+ return (...parts) => {
1307
+ if (!accessProperty) {
1308
+ return Promise.reject(new Error('Property access is not configured'));
1309
+ }
1310
+ const basePath = node ? resolvePath(node) : [];
1311
+ return accessProperty(normalizePropertyPath(basePath, parts));
1312
+ };
1313
+ }
1314
+ }
1315
+ else if (prop === Symbol.toPrimitive) {
1316
+ return toPrimitiveDescriptor();
1317
+ }
1318
+ else if (INTERCEPTED_NONE_SYMBOLS.has(prop)) {
852
1319
  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
1320
  }
862
1321
  const childNode = node ? { parent: node, key: prop } : { key: prop };
863
- return new Proxy(() => { }, createHandler(childNode));
1322
+ return new Proxy(EMPTY_TARGET, createHandler(childNode));
864
1323
  },
865
1324
  apply(_target, _thisArg, argArray) {
866
1325
  const path = node ? resolvePath(node) : [];
@@ -868,7 +1327,7 @@ function createClientProxy(invoke, accessProperty) {
868
1327
  },
869
1328
  };
870
1329
  }
871
- return new Proxy(() => { }, createHandler());
1330
+ return new Proxy(EMPTY_TARGET, createHandler());
872
1331
  }
873
1332
  function isPathKey(value) {
874
1333
  return typeof value === 'string' || typeof value === 'symbol';
@@ -883,8 +1342,9 @@ function createEndpoint(options) {
883
1342
  }
884
1343
  return channelRef.current;
885
1344
  };
886
- const context = createClientContext(getChannel, options.contextOptions);
887
- const channel = createMessageChannel(options.poster, options.key ?? 'default', context, processorRegistry);
1345
+ const instanceId = `ep-${nanoid()}`;
1346
+ const context = createClientContext(getChannel, instanceId, options.contextOptions);
1347
+ const channel = createMessageChannel(options.poster, options.key ?? 'default', instanceId, context, processorRegistry);
888
1348
  channelRef.current = channel;
889
1349
  processorRegistry.register('invokeRequest', handleInvokeRequest);
890
1350
  processorRegistry.register('invokeResponse', handleInvokeResponse);
@@ -892,24 +1352,28 @@ function createEndpoint(options) {
892
1352
  processorRegistry.register('accessPropertyResponse', handleAccessPropertyResponse);
893
1353
  processorRegistry.register('invokeFunctionByIdRequest', handleCallbackInvoke);
894
1354
  processorRegistry.register('invokeFunctionByIdResponse', handleCallbackInvokeResponse);
1355
+ processorRegistry.register('releaseCallbacks', handleReleaseCallbacks);
1356
+ processorRegistry.register('destroyEndpoint', handleDestroyEndpoint);
895
1357
  const proxy = createClientProxy((path, args) => context.invoke(path, args), (path) => context.accessProperty(path));
896
1358
  return {
897
1359
  proxy,
1360
+ getContext: () => context,
898
1361
  destroy() {
1362
+ context.destroy(true);
899
1363
  channel.destroy();
900
- context.destroy();
901
1364
  },
902
1365
  };
903
1366
  }
904
1367
 
905
1368
  function setupInMainWindow(options) {
1369
+ const resolveTargetOrigin = createTargetOriginResolver(() => options.iframe, options.targetOrigin);
906
1370
  const poster = createWindowPoster({
907
1371
  postMessage: (message) => {
908
1372
  const contentWindow = options.iframe.contentWindow;
909
1373
  if (!contentWindow) {
910
1374
  throw new Error('chrome-in-iframe: iframe contentWindow is not available');
911
1375
  }
912
- contentWindow.postMessage(message, options.targetOrigin ?? '*');
1376
+ contentWindow.postMessage(message, resolveTargetOrigin());
913
1377
  },
914
1378
  getExpectedSource: () => options.iframe.contentWindow,
915
1379
  allowedOrigin: options.allowedOrigin,
@@ -920,6 +1384,10 @@ function setupInMainWindow(options) {
920
1384
  contextOptions: {
921
1385
  delegateTarget: options.delegateTarget,
922
1386
  timeout: options.timeout,
1387
+ functionCacheMax: options.functionCacheMax,
1388
+ functionCacheTtl: options.functionCacheTtl,
1389
+ remoteCallbackCacheMax: options.remoteCallbackCacheMax,
1390
+ remoteCallbackCacheTtl: options.remoteCallbackCacheTtl,
923
1391
  },
924
1392
  });
925
1393
  const proxy = endpoint.proxy;
@@ -930,9 +1398,10 @@ function setupInMainWindow(options) {
930
1398
  };
931
1399
  }
932
1400
  function setupInIframe(options = {}) {
1401
+ const resolveTargetOrigin = createParentTargetOriginResolver(options.targetOrigin);
933
1402
  const poster = createWindowPoster({
934
1403
  postMessage: (message) => {
935
- window.parent.postMessage(message, options.targetOrigin ?? '*');
1404
+ window.parent.postMessage(message, resolveTargetOrigin());
936
1405
  },
937
1406
  getExpectedSource: () => window.parent,
938
1407
  allowedOrigin: options.allowedOrigin,
@@ -942,6 +1411,10 @@ function setupInIframe(options = {}) {
942
1411
  key: options.key,
943
1412
  contextOptions: {
944
1413
  timeout: options.timeout,
1414
+ functionCacheMax: options.functionCacheMax,
1415
+ functionCacheTtl: options.functionCacheTtl,
1416
+ remoteCallbackCacheMax: options.remoteCallbackCacheMax,
1417
+ remoteCallbackCacheTtl: options.remoteCallbackCacheTtl,
945
1418
  },
946
1419
  });
947
1420
  const proxy = endpoint.proxy;
@@ -1007,5 +1480,67 @@ function getGlobalChrome() {
1007
1480
  }
1008
1481
  return chromeApi;
1009
1482
  }
1483
+ function createTargetOriginResolver(getIframe, explicit) {
1484
+ if (explicit)
1485
+ return () => explicit;
1486
+ return () => deriveOriginFromIframe(getIframe());
1487
+ }
1488
+ function createParentTargetOriginResolver(explicit) {
1489
+ if (explicit)
1490
+ return () => explicit;
1491
+ return () => deriveParentOrigin();
1492
+ }
1493
+ function deriveOriginFromIframe(iframe) {
1494
+ const src = iframe.src || iframe.getAttribute('src') || '';
1495
+ const origin = parseOrigin(src);
1496
+ if (origin)
1497
+ return origin;
1498
+ try {
1499
+ if (iframe.contentWindow && iframe.contentWindow.location) {
1500
+ const loc = iframe.contentWindow.location;
1501
+ if (loc.origin && loc.origin !== 'null')
1502
+ return loc.origin;
1503
+ }
1504
+ }
1505
+ catch {
1506
+ // cross-origin contentWindow.location access throws — fall through
1507
+ }
1508
+ if (typeof console !== 'undefined') {
1509
+ console.warn("chrome-in-iframe: unable to derive iframe origin automatically; falling back to '*'. " +
1510
+ 'Pass `targetOrigin` explicitly to lock the destination.');
1511
+ }
1512
+ return '*';
1513
+ }
1514
+ function deriveParentOrigin() {
1515
+ if (typeof document !== 'undefined') {
1516
+ const referrer = document.referrer;
1517
+ const origin = parseOrigin(referrer);
1518
+ if (origin)
1519
+ return origin;
1520
+ }
1521
+ if (typeof location !== 'undefined' && location.ancestorOrigins && location.ancestorOrigins.length > 0) {
1522
+ const first = location.ancestorOrigins[0];
1523
+ if (first && first !== 'null')
1524
+ return first;
1525
+ }
1526
+ if (typeof console !== 'undefined') {
1527
+ console.warn("chrome-in-iframe: unable to derive parent origin automatically; falling back to '*'. " +
1528
+ 'Pass `targetOrigin` explicitly to lock the destination.');
1529
+ }
1530
+ return '*';
1531
+ }
1532
+ function parseOrigin(url) {
1533
+ if (!url)
1534
+ return null;
1535
+ try {
1536
+ const parsed = new URL(url, typeof location !== 'undefined' ? location.href : 'http://localhost');
1537
+ if (parsed.origin && parsed.origin !== 'null')
1538
+ return parsed.origin;
1539
+ }
1540
+ catch {
1541
+ return null;
1542
+ }
1543
+ return null;
1544
+ }
1010
1545
 
1011
1546
  export { connectChromeInIframe, exposeChromeInIframe, setupInIframe, setupInMainWindow };