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/CHANGELOG.md +304 -0
- package/CHANGELOG.zh-CN.md +203 -0
- package/LICENSE +15 -0
- package/README.md +105 -11
- package/README.zh-CN.md +108 -13
- package/dist/api.d.ts +4 -0
- package/dist/channel/channel.d.ts +1 -1
- package/dist/channel/deserializer.d.ts +3 -1
- package/dist/channel/listener.d.ts +3 -2
- package/dist/channel/sender.d.ts +1 -1
- package/dist/channel/serializer.d.ts +3 -1
- package/dist/channel/types.d.ts +34 -11
- package/dist/channel/utils.d.ts +1 -0
- package/dist/client/context.d.ts +5 -1
- package/dist/client/endpoint.d.ts +3 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1063 -315
- package/dist/log.d.ts +3 -0
- package/dist/processor/accessProperty.d.ts +3 -3
- package/dist/processor/invoke.d.ts +3 -3
- package/dist/processor/invokeCallback.d.ts +3 -3
- package/dist/processor/lifecycle.d.ts +3 -0
- package/package.json +19 -11
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
|
-
|
|
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 = {
|
|
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 (!
|
|
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
|
|
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) &&
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
|
|
295
|
+
const TYPE_KEY = '$cii$';
|
|
296
|
+
function serialize(arg, registerFunction, options = {}) {
|
|
297
|
+
return serialize0(arg, undefined, registerFunction, options, new WeakSet());
|
|
249
298
|
}
|
|
250
|
-
function
|
|
251
|
-
if (
|
|
252
|
-
return
|
|
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 (
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
295
|
-
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
|
357
|
+
return null;
|
|
319
358
|
}
|
|
320
|
-
function
|
|
321
|
-
const
|
|
322
|
-
if (
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
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
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
362
|
-
|
|
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
|
|
365
|
-
if (arg === undefined) {
|
|
366
|
-
return '$undefined$';
|
|
367
|
-
}
|
|
467
|
+
function deserialize0(arg, generateCallback, getRemoteCallback) {
|
|
368
468
|
if (arg === null)
|
|
369
|
-
return
|
|
370
|
-
if (typeof arg
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
383
|
-
|
|
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
|
-
|
|
390
|
-
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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 (
|
|
398
|
-
|
|
678
|
+
if (value.startsWith('wellKnown:')) {
|
|
679
|
+
const symbol = getWellKnownSymbol(value.slice('wellKnown:'.length));
|
|
680
|
+
if (symbol)
|
|
681
|
+
return symbol;
|
|
399
682
|
}
|
|
400
|
-
|
|
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
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
|
|
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 ${
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
|
582
|
-
const
|
|
583
|
-
if (!
|
|
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
|
-
|
|
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
|
|
593
|
-
|
|
594
|
-
}, ctx.getRemoteCallback, { persistent: isPersistentFunction }));
|
|
934
|
+
const scopedRemoteCallback = createScopedRemoteCallback(ctx, meta.senderInstanceId);
|
|
935
|
+
let deserializedArgs;
|
|
595
936
|
try {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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
|
-
|
|
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
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
|
663
|
-
const
|
|
664
|
-
const
|
|
665
|
-
const
|
|
666
|
-
function
|
|
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
|
|
669
|
-
max: FUNCTION_CACHE_MAX,
|
|
670
|
-
ttl: FUNCTION_CACHE_TTL,
|
|
671
|
-
});
|
|
1058
|
+
const functionIds = new Map();
|
|
672
1059
|
const persistentFunctionCache = new Map();
|
|
673
|
-
const
|
|
674
|
-
const
|
|
675
|
-
max:
|
|
676
|
-
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
|
|
1072
|
+
const remoteCallbacksByOwner = new Map();
|
|
1073
|
+
const persistentRemoteCallbacksByOwner = new Map();
|
|
1074
|
+
const boundMethodCache = new WeakMap();
|
|
679
1075
|
let destroyed = false;
|
|
680
|
-
const
|
|
681
|
-
|
|
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
|
-
|
|
1125
|
+
perOwner.set(thisArg, id);
|
|
685
1126
|
}
|
|
1127
|
+
const entry = { fn, thisArg };
|
|
686
1128
|
if (callbackOptions.persistent) {
|
|
687
|
-
persistentFunctionCache.set(id,
|
|
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,
|
|
1134
|
+
functionCache.set(id, entry);
|
|
692
1135
|
}
|
|
693
1136
|
return id;
|
|
694
1137
|
};
|
|
695
|
-
const
|
|
696
|
-
|
|
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 (
|
|
700
|
-
|
|
1169
|
+
if (opts.persistent) {
|
|
1170
|
+
getOrCreatePersistentRemoteMap(remoteInstanceId).set(id, callback);
|
|
701
1171
|
}
|
|
702
1172
|
else {
|
|
703
|
-
|
|
1173
|
+
getOrCreateRemoteLru(remoteInstanceId).set(id, callback);
|
|
704
1174
|
}
|
|
705
1175
|
}
|
|
706
|
-
else if (
|
|
707
|
-
|
|
708
|
-
|
|
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
|
|
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
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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
|
-
|
|
775
|
-
|
|
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
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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
|
-
|
|
794
|
-
|
|
795
|
-
}, timeout);
|
|
1329
|
+
rejectPending(callId, new Error(`Callback call timed out: ${callbackId}`));
|
|
1330
|
+
}, callTimeout);
|
|
796
1331
|
pending.set(callId, { resolve, reject, timer });
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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
|
-
|
|
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
|
-
|
|
818
|
-
|
|
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
|
|
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 === '
|
|
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(
|
|
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(
|
|
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
|
|
887
|
-
const
|
|
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,
|
|
1566
|
+
contentWindow.postMessage(message, resolveTargetOrigin());
|
|
913
1567
|
},
|
|
914
1568
|
getExpectedSource: () => options.iframe.contentWindow,
|
|
915
|
-
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,
|
|
1595
|
+
window.parent.postMessage(message, resolveTargetOrigin());
|
|
936
1596
|
},
|
|
937
1597
|
getExpectedSource: () => window.parent,
|
|
938
|
-
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
|
-
|
|
1658
|
+
perName.set(callback, listener);
|
|
983
1659
|
window.addEventListener(name, listener);
|
|
984
1660
|
},
|
|
985
1661
|
removeEventListener(name, callback) {
|
|
986
|
-
const
|
|
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
|
-
|
|
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 (
|
|
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 };
|