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