chrome-in-iframe 1.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 ADDED
@@ -0,0 +1,1011 @@
1
+ import { LRUCache } from 'lru-cache';
2
+ import { nanoid } from 'nanoid';
3
+
4
+ const WELL_KNOWN_SYMBOLS = {
5
+ asyncIterator: Symbol.asyncIterator,
6
+ hasInstance: Symbol.hasInstance,
7
+ isConcatSpreadable: Symbol.isConcatSpreadable,
8
+ iterator: Symbol.iterator,
9
+ match: Symbol.match,
10
+ matchAll: Symbol.matchAll,
11
+ replace: Symbol.replace,
12
+ search: Symbol.search,
13
+ species: Symbol.species,
14
+ split: Symbol.split,
15
+ toPrimitive: Symbol.toPrimitive,
16
+ toStringTag: Symbol.toStringTag,
17
+ unscopables: Symbol.unscopables,
18
+ };
19
+ 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;
25
+ }
26
+ function getWellKnownSymbol(name) {
27
+ return WELL_KNOWN_SYMBOLS[name];
28
+ }
29
+ function serializeThrownError(err) {
30
+ if (err instanceof Error) {
31
+ return { message: err.message, stack: err.stack };
32
+ }
33
+ return { message: String(err) };
34
+ }
35
+
36
+ function serializePath(path) {
37
+ return path.map((key) => {
38
+ if (typeof key === 'string')
39
+ return key;
40
+ return {
41
+ $type: 'symbol',
42
+ value: serializeSymbol$1(key),
43
+ };
44
+ });
45
+ }
46
+ function deserializePath(path) {
47
+ return path.map((key) => {
48
+ if (typeof key === 'string')
49
+ return key;
50
+ return deserializeSymbol$1(key.value);
51
+ });
52
+ }
53
+ function isSerializedPath(value) {
54
+ return Array.isArray(value) && value.every(isSerializedPathKey);
55
+ }
56
+ function isSerializedPathKey(value) {
57
+ if (typeof value === 'string')
58
+ return true;
59
+ if (typeof value !== 'object' || value === null)
60
+ return false;
61
+ const record = value;
62
+ return record.$type === 'symbol' && typeof record.value === 'string';
63
+ }
64
+ function serializeSymbol$1(value) {
65
+ const globalKey = Symbol.keyFor(value);
66
+ if (globalKey)
67
+ return `global:${globalKey}`;
68
+ const wellKnownName = getWellKnownSymbolName(value);
69
+ if (wellKnownName)
70
+ return `wellKnown:${wellKnownName}`;
71
+ throw new TypeError('Cannot serialize non-global symbol path key');
72
+ }
73
+ function deserializeSymbol$1(value) {
74
+ if (value.startsWith('global:')) {
75
+ return Symbol.for(value.slice('global:'.length));
76
+ }
77
+ if (value.startsWith('wellKnown:')) {
78
+ const symbol = getWellKnownSymbol(value.slice('wellKnown:'.length));
79
+ if (symbol)
80
+ return symbol;
81
+ }
82
+ throw new TypeError('Cannot deserialize symbol path key');
83
+ }
84
+
85
+ function createMessageSender(poster, key) {
86
+ return {
87
+ sendMessage(type, data) {
88
+ const body = { type, key, data: serializeMessageData(type, data) };
89
+ poster.postMessage(JSON.stringify(body));
90
+ },
91
+ };
92
+ }
93
+ function serializeMessageData(type, data) {
94
+ if (type === 'invokeRequest') {
95
+ const invokeData = data;
96
+ return {
97
+ ...invokeData,
98
+ path: serializePath(invokeData.path),
99
+ };
100
+ }
101
+ if (type === 'accessPropertyRequest') {
102
+ const accessData = data;
103
+ return {
104
+ ...accessData,
105
+ path: serializePath(accessData.path),
106
+ };
107
+ }
108
+ return data;
109
+ }
110
+
111
+ function createMessageChannel(poster, key, context, processorRegistry) {
112
+ const sender = createMessageSender(poster, key);
113
+ const listener = (event) => {
114
+ let body;
115
+ try {
116
+ body = JSON.parse(event.data);
117
+ }
118
+ catch {
119
+ return;
120
+ }
121
+ if (!isMessageBody(body))
122
+ return;
123
+ if (body.key !== key)
124
+ return;
125
+ const handler = processorRegistry.get(body.type);
126
+ if (!handler)
127
+ return;
128
+ try {
129
+ handler(deserializeMessageData(body), context);
130
+ }
131
+ catch (err) {
132
+ sendProcessorError(body, sender, err);
133
+ }
134
+ };
135
+ poster.addEventListener('message', listener);
136
+ return {
137
+ getSender() {
138
+ return sender;
139
+ },
140
+ getContext() {
141
+ return context;
142
+ },
143
+ getPoster() {
144
+ return poster;
145
+ },
146
+ getKey() {
147
+ return key;
148
+ },
149
+ destroy() {
150
+ poster.removeEventListener('message', listener);
151
+ },
152
+ };
153
+ }
154
+ function isMessageBody(value) {
155
+ if (!isRecord(value))
156
+ return false;
157
+ if (typeof value.type !== 'string')
158
+ return false;
159
+ if (typeof value.key !== 'string')
160
+ return false;
161
+ switch (value.type) {
162
+ case 'invokeRequest':
163
+ return isInvokeRequest(value.data);
164
+ case 'invokeResponse':
165
+ return isResponse(value.data);
166
+ case 'accessPropertyRequest':
167
+ return isAccessPropertyRequest(value.data);
168
+ case 'accessPropertyResponse':
169
+ return isResponse(value.data);
170
+ case 'invokeFunctionByIdRequest':
171
+ return isCallbackRequest(value.data);
172
+ case 'invokeFunctionByIdResponse':
173
+ return isResponse(value.data);
174
+ default:
175
+ return false;
176
+ }
177
+ }
178
+ function isInvokeRequest(value) {
179
+ return isRecord(value) && isMessageId(value.id) && isSerializedPath(value.path) && Array.isArray(value.args);
180
+ }
181
+ function isAccessPropertyRequest(value) {
182
+ return isRecord(value) && isMessageId(value.id) && isSerializedPath(value.path);
183
+ }
184
+ function isCallbackRequest(value) {
185
+ return isRecord(value) && typeof value.id === 'string' && isMessageId(value.callId) && Array.isArray(value.args);
186
+ }
187
+ function isResponse(value) {
188
+ return (isRecord(value) &&
189
+ isMessageId(value.id) &&
190
+ (!('error' in value) || isSerializedError(value.error)) &&
191
+ ('data' in value || 'error' in value));
192
+ }
193
+ function isSerializedError(value) {
194
+ return (isRecord(value) && typeof value.message === 'string' && (!('stack' in value) || typeof value.stack === 'string'));
195
+ }
196
+ function isMessageId(value) {
197
+ return typeof value === 'string' && value.length > 0;
198
+ }
199
+ function deserializeMessageData(body) {
200
+ if (body.type === 'invokeRequest') {
201
+ const data = body.data;
202
+ return {
203
+ ...data,
204
+ path: deserializePath(data.path),
205
+ };
206
+ }
207
+ if (body.type === 'accessPropertyRequest') {
208
+ const data = body.data;
209
+ return {
210
+ ...data,
211
+ path: deserializePath(data.path),
212
+ };
213
+ }
214
+ return body.data;
215
+ }
216
+ function isRecord(value) {
217
+ return typeof value === 'object' && value !== null;
218
+ }
219
+ function sendProcessorError(body, sender, err) {
220
+ if (body.type === 'invokeRequest') {
221
+ const data = body.data;
222
+ sender.sendMessage('invokeResponse', {
223
+ id: data.id,
224
+ error: serializeThrownError(err),
225
+ });
226
+ return;
227
+ }
228
+ if (body.type === 'accessPropertyRequest') {
229
+ const data = body.data;
230
+ sender.sendMessage('accessPropertyResponse', {
231
+ id: data.id,
232
+ error: serializeThrownError(err),
233
+ });
234
+ return;
235
+ }
236
+ if (body.type === 'invokeFunctionByIdRequest') {
237
+ const data = body.data;
238
+ const error = serializeThrownError(err);
239
+ console.warn(`chrome-in-iframe: callback '${data.id}' failed: ${error.message}`);
240
+ sender.sendMessage('invokeFunctionByIdResponse', {
241
+ id: data.callId,
242
+ error,
243
+ });
244
+ }
245
+ }
246
+
247
+ function deserialize(arg, generateCallback, getRemoteCallback, options = {}) {
248
+ return deserialize0(arg, generateCallback, getRemoteCallback, options);
249
+ }
250
+ function deserialize0(arg, generateCallback, getRemoteCallback, options = {}) {
251
+ if (typeof arg === 'string') {
252
+ return deserializePrimitive(arg, generateCallback, getRemoteCallback, options);
253
+ }
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));
262
+ }
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;
278
+ 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);
293
+ }
294
+ else {
295
+ return generateCallback(id, args);
296
+ }
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);
316
+ }
317
+ }
318
+ return value;
319
+ }
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
+ };
327
+ }
328
+ return { id: value, options };
329
+ }
330
+ function deserializeSymbol(value) {
331
+ if (value.startsWith('global:')) {
332
+ return Symbol.for(value.slice('global:'.length));
333
+ }
334
+ if (value.startsWith('wellKnown:')) {
335
+ const symbol = getWellKnownSymbol(value.slice('wellKnown:'.length));
336
+ if (symbol)
337
+ return symbol;
338
+ }
339
+ throw new TypeError('Cannot deserialize symbol');
340
+ }
341
+ function setOwnProperty$1(target, key, value) {
342
+ Object.defineProperty(target, key, {
343
+ value,
344
+ enumerable: true,
345
+ configurable: true,
346
+ writable: true,
347
+ });
348
+ }
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);
359
+ }
360
+
361
+ function serialize(arg, registerFunction, options = {}) {
362
+ return serialize0(arg, registerFunction, options, new WeakSet());
363
+ }
364
+ function serialize0(arg, registerFunction, options, seen) {
365
+ if (arg === undefined) {
366
+ return '$undefined$';
367
+ }
368
+ if (arg === null)
369
+ return '$null$';
370
+ if (typeof arg === 'object') {
371
+ if (seen.has(arg)) {
372
+ throw new TypeError('Cannot serialize circular structure');
373
+ }
374
+ seen.add(arg);
375
+ 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));
381
+ }
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));
386
+ }
387
+ return result;
388
+ }
389
+ finally {
390
+ seen.delete(arg);
391
+ }
392
+ }
393
+ if (typeof arg === 'function') {
394
+ const id = registerFunction(arg, options);
395
+ return '$function$' + (options.persistent ? `persistent:${id}` : id);
396
+ }
397
+ if (typeof arg === 'symbol') {
398
+ return '$symbol$' + serializeSymbol(arg);
399
+ }
400
+ return `$${typeof arg}$${arg}`;
401
+ }
402
+ function setOwnProperty(target, key, value) {
403
+ Object.defineProperty(target, key, {
404
+ value,
405
+ enumerable: true,
406
+ configurable: true,
407
+ writable: true,
408
+ });
409
+ }
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');
418
+ }
419
+
420
+ const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
421
+ function readProperty(target, key) {
422
+ if (typeof key === 'string' && DANGEROUS_KEYS.has(key)) {
423
+ throw new Error(`Access to dangerous property '${key}' is denied`);
424
+ }
425
+ if (target === undefined || target === null)
426
+ return undefined;
427
+ return target[key];
428
+ }
429
+
430
+ function handleAccessPropertyRequest(data, ctx) {
431
+ const target = ctx.getDelegateTarget();
432
+ const channel = ctx.getMessageChannel();
433
+ if (data.path.length === 0) {
434
+ channel.getSender().sendMessage('accessPropertyResponse', {
435
+ id: data.id,
436
+ error: { message: 'Property path must not be empty' },
437
+ });
438
+ return;
439
+ }
440
+ if (!target) {
441
+ channel.getSender().sendMessage('accessPropertyResponse', {
442
+ id: data.id,
443
+ error: { message: 'No delegate target is configured' },
444
+ });
445
+ return;
446
+ }
447
+ try {
448
+ let current = target;
449
+ for (const key of data.path) {
450
+ if (current === undefined || current === null) {
451
+ channel.getSender().sendMessage('accessPropertyResponse', {
452
+ id: data.id,
453
+ data: serialize(current, ctx.registerFunction),
454
+ });
455
+ return;
456
+ }
457
+ current = readProperty(current, key);
458
+ }
459
+ channel.getSender().sendMessage('accessPropertyResponse', {
460
+ id: data.id,
461
+ data: serialize(current, ctx.registerFunction, { persistent: isLikelyListenerPath(data.path, current) }),
462
+ });
463
+ }
464
+ catch (err) {
465
+ channel.getSender().sendMessage('accessPropertyResponse', {
466
+ id: data.id,
467
+ error: serializeThrownError(err),
468
+ });
469
+ }
470
+ }
471
+ function handleAccessPropertyResponse(data, ctx) {
472
+ const pending = ctx.getAndRemovePendingPromise(data.id);
473
+ if (!pending)
474
+ return;
475
+ if (data.error) {
476
+ const err = new Error(data.error.message);
477
+ err.stack = data.error.stack;
478
+ pending.reject(err);
479
+ return;
480
+ }
481
+ pending.resolve(deserialize(data.data, (id, args, options) => {
482
+ return ctx.invokeFunctionById(id, args, options);
483
+ }, ctx.getRemoteCallback));
484
+ }
485
+
486
+ function handleInvokeRequest(data, ctx) {
487
+ const target = ctx.getDelegateTarget();
488
+ const channel = ctx.getMessageChannel();
489
+ if (!target) {
490
+ channel.getSender().sendMessage('invokeResponse', {
491
+ id: data.id,
492
+ error: { message: 'No delegate target is configured' },
493
+ });
494
+ return;
495
+ }
496
+ let current = target;
497
+ for (let i = 0; i < data.path.length - 1; i++) {
498
+ current = readProperty(current, data.path[i]);
499
+ if (current === undefined || current === null) {
500
+ channel.getSender().sendMessage('invokeResponse', {
501
+ id: data.id,
502
+ error: {
503
+ message: `Cannot read property '${String(data.path[i + 1])}' of ${String(data.path[i])}`,
504
+ },
505
+ });
506
+ return;
507
+ }
508
+ }
509
+ const lastKey = data.path[data.path.length - 1];
510
+ const fn = readProperty(current, lastKey);
511
+ if (typeof fn !== 'function') {
512
+ channel.getSender().sendMessage('invokeResponse', {
513
+ id: data.id,
514
+ error: { message: `'${String(lastKey)}' is not a function` },
515
+ });
516
+ return;
517
+ }
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
+ let result;
522
+ try {
523
+ result = Reflect.apply(fn, current, deserializedArgs);
524
+ }
525
+ catch (err) {
526
+ channel.getSender().sendMessage('invokeResponse', {
527
+ id: data.id,
528
+ error: serializeThrownError(err),
529
+ });
530
+ return;
531
+ }
532
+ if (result instanceof Promise) {
533
+ result
534
+ .then((value) => {
535
+ sendInvokeSuccess(data.id, value, ctx);
536
+ })
537
+ .catch((err) => {
538
+ channel.getSender().sendMessage('invokeResponse', {
539
+ id: data.id,
540
+ error: serializeThrownError(err),
541
+ });
542
+ });
543
+ }
544
+ else {
545
+ sendInvokeSuccess(data.id, result, ctx);
546
+ }
547
+ }
548
+ function handleInvokeResponse(data, ctx) {
549
+ const pending = ctx.getAndRemovePendingPromise(data.id);
550
+ if (!pending)
551
+ return;
552
+ if (data.error) {
553
+ const err = new Error(data.error.message);
554
+ err.stack = data.error.stack;
555
+ pending.reject(err);
556
+ }
557
+ else {
558
+ pending.resolve(deserialize(data.data, (id, args, options) => {
559
+ return ctx.invokeFunctionById(id, args, options);
560
+ }, ctx.getRemoteCallback));
561
+ }
562
+ }
563
+ function sendInvokeSuccess(id, value, ctx) {
564
+ const channel = ctx.getMessageChannel();
565
+ try {
566
+ channel.getSender().sendMessage('invokeResponse', {
567
+ id,
568
+ data: serialize(value, ctx.registerFunction),
569
+ });
570
+ }
571
+ catch (err) {
572
+ channel.getSender().sendMessage('invokeResponse', {
573
+ id,
574
+ error: serializeThrownError(err),
575
+ });
576
+ }
577
+ }
578
+
579
+ function handleCallbackInvoke(data, ctx) {
580
+ 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) {
584
+ const error = { message: `Callback '${data.id}' is not available or has expired` };
585
+ console.warn(`chrome-in-iframe: ${error.message}`);
586
+ channel.getSender().sendMessage('invokeFunctionByIdResponse', {
587
+ id: data.callId,
588
+ error,
589
+ });
590
+ return;
591
+ }
592
+ const deserializedArgs = data.args.map((arg) => deserialize(arg, (id, args, options) => {
593
+ return ctx.invokeFunctionById(id, args, options);
594
+ }, ctx.getRemoteCallback, { persistent: isPersistentFunction }));
595
+ try {
596
+ const result = fn(...deserializedArgs);
597
+ if (result instanceof Promise) {
598
+ result
599
+ .then((value) => {
600
+ sendCallbackSuccess(data.callId, value, ctx);
601
+ })
602
+ .catch((err) => {
603
+ sendCallbackError(data.callId, err, ctx);
604
+ });
605
+ return;
606
+ }
607
+ sendCallbackSuccess(data.callId, result, ctx);
608
+ }
609
+ catch (err) {
610
+ sendCallbackError(data.callId, err, ctx);
611
+ }
612
+ }
613
+ function handleCallbackInvokeResponse(data, ctx) {
614
+ const pending = ctx.getAndRemovePendingPromise(data.id);
615
+ if (!pending)
616
+ return;
617
+ if (data.error) {
618
+ const err = new Error(data.error.message);
619
+ err.stack = data.error.stack;
620
+ pending.reject(err);
621
+ return;
622
+ }
623
+ pending.resolve(deserialize(data.data, (id, args, options) => {
624
+ return ctx.invokeFunctionById(id, args, options);
625
+ }, ctx.getRemoteCallback));
626
+ }
627
+ function sendCallbackSuccess(id, value, ctx) {
628
+ const channel = ctx.getMessageChannel();
629
+ try {
630
+ channel.getSender().sendMessage('invokeFunctionByIdResponse', {
631
+ id,
632
+ data: serialize(value, ctx.registerFunction),
633
+ });
634
+ }
635
+ catch (err) {
636
+ sendCallbackError(id, err, ctx);
637
+ }
638
+ }
639
+ function sendCallbackError(id, err, ctx) {
640
+ ctx
641
+ .getMessageChannel()
642
+ .getSender()
643
+ .sendMessage('invokeFunctionByIdResponse', {
644
+ id,
645
+ error: serializeThrownError(err),
646
+ });
647
+ }
648
+
649
+ function createProcessorRegistry() {
650
+ const handlers = new Map();
651
+ return {
652
+ register(type, handler) {
653
+ handlers.set(type, { handler: handler });
654
+ },
655
+ get(type) {
656
+ return handlers.get(type)?.handler;
657
+ },
658
+ };
659
+ }
660
+
661
+ 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 = {}) {
667
+ const pending = new Map();
668
+ const functionCache = new LRUCache({
669
+ max: FUNCTION_CACHE_MAX,
670
+ ttl: FUNCTION_CACHE_TTL,
671
+ });
672
+ 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();
679
+ let destroyed = false;
680
+ const registerFunction = (fn, callbackOptions = {}) => {
681
+ let id = functionIds.get(fn);
682
+ if (!id) {
683
+ id = createId('cb');
684
+ functionIds.set(fn, id);
685
+ }
686
+ if (callbackOptions.persistent) {
687
+ persistentFunctionCache.set(id, fn);
688
+ functionCache.delete(id);
689
+ }
690
+ else if (!persistentFunctionCache.has(id)) {
691
+ functionCache.set(id, fn);
692
+ }
693
+ return id;
694
+ };
695
+ const getRemoteCallback = (id, invoke, callbackOptions = {}) => {
696
+ let callback = persistentRemoteCallbacks.get(id) ?? remoteCallbacks.get(id);
697
+ if (!callback) {
698
+ callback = (...args) => invoke(args);
699
+ if (callbackOptions.persistent) {
700
+ persistentRemoteCallbacks.set(id, callback);
701
+ }
702
+ else {
703
+ remoteCallbacks.set(id, callback);
704
+ }
705
+ }
706
+ else if (callbackOptions.persistent) {
707
+ remoteCallbacks.delete(id);
708
+ persistentRemoteCallbacks.set(id, callback);
709
+ }
710
+ return callback;
711
+ };
712
+ return {
713
+ getDelegateTarget() {
714
+ return options.delegateTarget;
715
+ },
716
+ getMessageChannel() {
717
+ return getMessageChannel();
718
+ },
719
+ getFunctionCache() {
720
+ return functionCache;
721
+ },
722
+ getPersistentFunctionCache() {
723
+ return persistentFunctionCache;
724
+ },
725
+ registerFunction(fn, callbackOptions) {
726
+ return registerFunction(fn, callbackOptions);
727
+ },
728
+ getRemoteCallback(id, invoke, callbackOptions) {
729
+ return getRemoteCallback(id, invoke, callbackOptions);
730
+ },
731
+ getAndRemovePendingPromise(id) {
732
+ const callbacks = pending.get(id);
733
+ if (callbacks) {
734
+ clearTimeout(callbacks.timer);
735
+ pending.delete(id);
736
+ }
737
+ return callbacks;
738
+ },
739
+ registerPendingPromise(id, callbacks) {
740
+ pending.set(id, callbacks);
741
+ },
742
+ invoke(path, args) {
743
+ return new Promise((resolve, reject) => {
744
+ if (destroyed) {
745
+ reject(new Error('Channel has been destroyed'));
746
+ return;
747
+ }
748
+ const id = createId('req');
749
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
750
+ const timer = setTimeout(() => {
751
+ 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
+ });
763
+ });
764
+ },
765
+ accessProperty(path) {
766
+ return new Promise((resolve, reject) => {
767
+ if (destroyed) {
768
+ reject(new Error('Channel has been destroyed'));
769
+ return;
770
+ }
771
+ const id = createId('req');
772
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
773
+ const timer = setTimeout(() => {
774
+ pending.delete(id);
775
+ reject(new Error(`Property access timed out: ${String(path.join('.'))}`));
776
+ }, timeout);
777
+ pending.set(id, { resolve, reject, timer });
778
+ getMessageChannel().getSender().sendMessage('accessPropertyRequest', {
779
+ id,
780
+ path,
781
+ });
782
+ });
783
+ },
784
+ invokeFunctionById(callbackId, args, callbackOptions = {}) {
785
+ return new Promise((resolve, reject) => {
786
+ if (destroyed) {
787
+ reject(new Error('Channel has been destroyed'));
788
+ return;
789
+ }
790
+ const callId = createId('cbcall');
791
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
792
+ const timer = setTimeout(() => {
793
+ pending.delete(callId);
794
+ reject(new Error(`Callback call timed out: ${callbackId}`));
795
+ }, timeout);
796
+ 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
+ });
804
+ });
805
+ },
806
+ destroy() {
807
+ if (destroyed)
808
+ return;
809
+ destroyed = true;
810
+ for (const [id, callbacks] of pending) {
811
+ clearTimeout(callbacks.timer);
812
+ callbacks.reject(new Error(`Channel has been destroyed before call ${id} completed`));
813
+ }
814
+ pending.clear();
815
+ functionCache.clear();
816
+ persistentFunctionCache.clear();
817
+ remoteCallbacks.clear();
818
+ persistentRemoteCallbacks.clear();
819
+ },
820
+ };
821
+ }
822
+ function createId(prefix) {
823
+ return `${prefix}-${nanoid()}`;
824
+ }
825
+
826
+ function resolvePath(node) {
827
+ const path = [];
828
+ let current = node;
829
+ while (current) {
830
+ path.unshift(current.key);
831
+ current = current.parent;
832
+ }
833
+ return path;
834
+ }
835
+ function createClientProxy(invoke, accessProperty) {
836
+ const normalizePropertyPath = (basePath, parts) => {
837
+ const extraPath = parts.flatMap((part) => {
838
+ if (Array.isArray(part))
839
+ return part.filter(isPathKey);
840
+ if (typeof part === 'string')
841
+ return part.split('.').filter(Boolean);
842
+ if (typeof part === 'symbol')
843
+ return [part];
844
+ return [];
845
+ });
846
+ return [...basePath, ...extraPath];
847
+ };
848
+ function createHandler(node) {
849
+ return {
850
+ get(_target, prop) {
851
+ if (prop === 'then')
852
+ 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
+ }
862
+ const childNode = node ? { parent: node, key: prop } : { key: prop };
863
+ return new Proxy(() => { }, createHandler(childNode));
864
+ },
865
+ apply(_target, _thisArg, argArray) {
866
+ const path = node ? resolvePath(node) : [];
867
+ return invoke(path, argArray);
868
+ },
869
+ };
870
+ }
871
+ return new Proxy(() => { }, createHandler());
872
+ }
873
+ function isPathKey(value) {
874
+ return typeof value === 'string' || typeof value === 'symbol';
875
+ }
876
+
877
+ function createEndpoint(options) {
878
+ const processorRegistry = createProcessorRegistry();
879
+ const channelRef = {};
880
+ const getChannel = () => {
881
+ if (!channelRef.current) {
882
+ throw new Error('Channel has not been initialized');
883
+ }
884
+ return channelRef.current;
885
+ };
886
+ const context = createClientContext(getChannel, options.contextOptions);
887
+ const channel = createMessageChannel(options.poster, options.key ?? 'default', context, processorRegistry);
888
+ channelRef.current = channel;
889
+ processorRegistry.register('invokeRequest', handleInvokeRequest);
890
+ processorRegistry.register('invokeResponse', handleInvokeResponse);
891
+ processorRegistry.register('accessPropertyRequest', handleAccessPropertyRequest);
892
+ processorRegistry.register('accessPropertyResponse', handleAccessPropertyResponse);
893
+ processorRegistry.register('invokeFunctionByIdRequest', handleCallbackInvoke);
894
+ processorRegistry.register('invokeFunctionByIdResponse', handleCallbackInvokeResponse);
895
+ const proxy = createClientProxy((path, args) => context.invoke(path, args), (path) => context.accessProperty(path));
896
+ return {
897
+ proxy,
898
+ destroy() {
899
+ channel.destroy();
900
+ context.destroy();
901
+ },
902
+ };
903
+ }
904
+
905
+ function setupInMainWindow(options) {
906
+ const poster = createWindowPoster({
907
+ postMessage: (message) => {
908
+ const contentWindow = options.iframe.contentWindow;
909
+ if (!contentWindow) {
910
+ throw new Error('chrome-in-iframe: iframe contentWindow is not available');
911
+ }
912
+ contentWindow.postMessage(message, options.targetOrigin ?? '*');
913
+ },
914
+ getExpectedSource: () => options.iframe.contentWindow,
915
+ allowedOrigin: options.allowedOrigin,
916
+ });
917
+ const endpoint = createEndpoint({
918
+ poster,
919
+ key: options.key,
920
+ contextOptions: {
921
+ delegateTarget: options.delegateTarget,
922
+ timeout: options.timeout,
923
+ },
924
+ });
925
+ const proxy = endpoint.proxy;
926
+ return {
927
+ proxy,
928
+ get: proxy.$get,
929
+ destroy: endpoint.destroy,
930
+ };
931
+ }
932
+ function setupInIframe(options = {}) {
933
+ const poster = createWindowPoster({
934
+ postMessage: (message) => {
935
+ window.parent.postMessage(message, options.targetOrigin ?? '*');
936
+ },
937
+ getExpectedSource: () => window.parent,
938
+ allowedOrigin: options.allowedOrigin,
939
+ });
940
+ const endpoint = createEndpoint({
941
+ poster,
942
+ key: options.key,
943
+ contextOptions: {
944
+ timeout: options.timeout,
945
+ },
946
+ });
947
+ const proxy = endpoint.proxy;
948
+ return {
949
+ proxy,
950
+ get: proxy.$get,
951
+ destroy: endpoint.destroy,
952
+ };
953
+ }
954
+ function exposeChromeInIframe(options) {
955
+ return setupInMainWindow({
956
+ ...options,
957
+ delegateTarget: options.chromeApi ?? getGlobalChrome(),
958
+ });
959
+ }
960
+ function connectChromeInIframe(options = {}) {
961
+ const handle = setupInIframe(options);
962
+ return {
963
+ ...handle,
964
+ chrome: handle.proxy,
965
+ };
966
+ }
967
+ function createWindowPoster(options) {
968
+ const listenerMap = new Map();
969
+ return {
970
+ postMessage(message) {
971
+ options.postMessage(message);
972
+ },
973
+ addEventListener(name, callback) {
974
+ const listener = (event) => {
975
+ const messageEvent = event;
976
+ if (messageEvent.source !== options.getExpectedSource())
977
+ return;
978
+ if (!isAllowedOrigin(messageEvent.origin, options.allowedOrigin))
979
+ return;
980
+ callback(messageEvent);
981
+ };
982
+ listenerMap.set(callback, listener);
983
+ window.addEventListener(name, listener);
984
+ },
985
+ removeEventListener(name, callback) {
986
+ const listener = listenerMap.get(callback);
987
+ if (!listener)
988
+ return;
989
+ window.removeEventListener(name, listener);
990
+ listenerMap.delete(callback);
991
+ },
992
+ };
993
+ }
994
+ function isAllowedOrigin(origin, allowedOrigin) {
995
+ if (!allowedOrigin)
996
+ return true;
997
+ if (typeof allowedOrigin === 'function')
998
+ return allowedOrigin(origin);
999
+ if (Array.isArray(allowedOrigin))
1000
+ return allowedOrigin.includes(origin);
1001
+ return allowedOrigin === origin;
1002
+ }
1003
+ function getGlobalChrome() {
1004
+ const chromeApi = globalThis.chrome;
1005
+ if (!chromeApi) {
1006
+ throw new Error('chrome-in-iframe: global chrome API is not available');
1007
+ }
1008
+ return chromeApi;
1009
+ }
1010
+
1011
+ export { connectChromeInIframe, exposeChromeInIframe, setupInIframe, setupInMainWindow };