@stainless-api/playgrounds 0.0.1-beta.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 +15 -0
- package/README.md +23 -0
- package/eslint.config.js +2 -0
- package/package.json +69 -0
- package/src/Logs.tsx +216 -0
- package/src/Panel.tsx +21 -0
- package/src/PlaygroundPanelWrapper.tsx +5 -0
- package/src/build-py-types.ts +152 -0
- package/src/build-ts-types.ts +70 -0
- package/src/build.ts +97 -0
- package/src/codemirror/comlink.ts +698 -0
- package/src/codemirror/curl/curlconverter.vendor.js +7959 -0
- package/src/codemirror/curl.ts +108 -0
- package/src/codemirror/deps.ts +12 -0
- package/src/codemirror/fix-lsp-markdown.ts +50 -0
- package/src/codemirror/lsp.ts +87 -0
- package/src/codemirror/python/anser.ts +398 -0
- package/src/codemirror/python/pyodide.ts +180 -0
- package/src/codemirror/python.ts +160 -0
- package/src/codemirror/react.tsx +615 -0
- package/src/codemirror/sanitize-html.ts +12 -0
- package/src/codemirror/shiki.ts +65 -0
- package/src/codemirror/typescript/cdn-typescript.d.ts +1 -0
- package/src/codemirror/typescript/cdn-typescript.js +1 -0
- package/src/codemirror/typescript/console.ts +590 -0
- package/src/codemirror/typescript/get-signature.ts +94 -0
- package/src/codemirror/typescript/prettier-plugin-external-typescript.vendor.js +4968 -0
- package/src/codemirror/typescript/runner.ts +396 -0
- package/src/codemirror/typescript/special-info.ts +171 -0
- package/src/codemirror/typescript/worker.ts +292 -0
- package/src/codemirror/typescript.tsx +198 -0
- package/src/create.tsx +44 -0
- package/src/icon.tsx +21 -0
- package/src/index.ts +6 -0
- package/src/logs-context.ts +5 -0
- package/src/playground.css +359 -0
- package/src/sandbox-worker/in-frame.js +179 -0
- package/src/sandbox-worker/index.ts +202 -0
- package/src/use-storage.ts +54 -0
- package/src/util.ts +29 -0
- package/src/virtual-module.d.ts +45 -0
- package/src/vite-env.d.ts +1 -0
- package/test/get-signature.test.ts +73 -0
- package/test/use-storage.test.ts +60 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-misused-new */
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
5
|
+
/**
|
|
6
|
+
* @license
|
|
7
|
+
* Copyright 2019 Google LLC
|
|
8
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface EventSource {
|
|
12
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: {}): void;
|
|
13
|
+
|
|
14
|
+
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: {}): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PostMessageWithOrigin {
|
|
18
|
+
postMessage(message: any, targetOrigin: string, transfer?: Transferable[]): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Endpoint extends EventSource {
|
|
22
|
+
postMessage(message: any, transfer?: Transferable[]): void;
|
|
23
|
+
|
|
24
|
+
start?: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type WireValueType = 'RAW' | 'PROXY' | 'THROW' | 'HANDLER';
|
|
28
|
+
|
|
29
|
+
export interface RawWireValue {
|
|
30
|
+
id?: string;
|
|
31
|
+
type: 'RAW';
|
|
32
|
+
value: {};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface HandlerWireValue {
|
|
36
|
+
id?: string;
|
|
37
|
+
type: 'HANDLER';
|
|
38
|
+
name: string;
|
|
39
|
+
value: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type WireValue = RawWireValue | HandlerWireValue;
|
|
43
|
+
|
|
44
|
+
export type MessageID = string;
|
|
45
|
+
|
|
46
|
+
export type MessageType = 'GET' | 'SET' | 'APPLY' | 'CONSTRUCT' | 'ENDPOINT' | 'RELEASE';
|
|
47
|
+
|
|
48
|
+
export interface GetMessage {
|
|
49
|
+
id?: MessageID;
|
|
50
|
+
type: 'GET';
|
|
51
|
+
path: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface SetMessage {
|
|
55
|
+
id?: MessageID;
|
|
56
|
+
type: 'SET';
|
|
57
|
+
path: string[];
|
|
58
|
+
value: WireValue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface ApplyMessage {
|
|
62
|
+
id?: MessageID;
|
|
63
|
+
type: 'APPLY';
|
|
64
|
+
path: string[];
|
|
65
|
+
argumentList: WireValue[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ConstructMessage {
|
|
69
|
+
id?: MessageID;
|
|
70
|
+
type: 'CONSTRUCT';
|
|
71
|
+
path: string[];
|
|
72
|
+
argumentList: WireValue[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface EndpointMessage {
|
|
76
|
+
id?: MessageID;
|
|
77
|
+
type: 'ENDPOINT';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ReleaseMessage {
|
|
81
|
+
id?: MessageID;
|
|
82
|
+
type: 'RELEASE';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type Message =
|
|
86
|
+
| GetMessage
|
|
87
|
+
| SetMessage
|
|
88
|
+
| ApplyMessage
|
|
89
|
+
| ConstructMessage
|
|
90
|
+
| EndpointMessage
|
|
91
|
+
| ReleaseMessage;
|
|
92
|
+
|
|
93
|
+
export const proxyMarker = Symbol('Comlink.proxy');
|
|
94
|
+
export const createEndpoint = Symbol('Comlink.endpoint');
|
|
95
|
+
export const releaseProxy = Symbol('Comlink.releaseProxy');
|
|
96
|
+
export const finalizer = Symbol('Comlink.finalizer');
|
|
97
|
+
|
|
98
|
+
const throwMarker = Symbol('Comlink.thrown');
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Interface of values that were marked to be proxied with `comlink.proxy()`.
|
|
102
|
+
* Can also be implemented by classes.
|
|
103
|
+
*/
|
|
104
|
+
export interface ProxyMarked {
|
|
105
|
+
[proxyMarker]: true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Takes a type and wraps it in a Promise, if it not already is one.
|
|
110
|
+
* This is to avoid `Promise<Promise<T>>`.
|
|
111
|
+
*
|
|
112
|
+
* This is the inverse of `Unpromisify<T>`.
|
|
113
|
+
*/
|
|
114
|
+
type Promisify<T> = [T] extends [Promise<unknown>] ? T : Promise<T>;
|
|
115
|
+
/**
|
|
116
|
+
* Takes a type that may be Promise and unwraps the Promise type.
|
|
117
|
+
* If `P` is not a Promise, it returns `P`.
|
|
118
|
+
*
|
|
119
|
+
* This is the inverse of `Promisify<T>`.
|
|
120
|
+
*/
|
|
121
|
+
type Unpromisify<P> = P extends Promise<infer T> ? T : P;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Takes the raw type of a remote property and returns the type that is visible to the local thread on the proxy.
|
|
125
|
+
*
|
|
126
|
+
* Note: This needs to be its own type alias, otherwise it will not distribute over unions.
|
|
127
|
+
* See https://www.typescriptlang.org/docs/handbook/advanced-types.html#distributive-conditional-types
|
|
128
|
+
*/
|
|
129
|
+
type RemoteProperty<T> =
|
|
130
|
+
// If the value is a method, comlink will proxy it automatically.
|
|
131
|
+
// Objects are only proxied if they are marked to be proxied.
|
|
132
|
+
// Otherwise, the property is converted to a Promise that resolves the cloned value.
|
|
133
|
+
T extends Function | ProxyMarked ? Remote<T> : Promisify<T>;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Takes the raw type of a property as a remote thread would see it through a proxy (e.g. when passed in as a function
|
|
137
|
+
* argument) and returns the type that the local thread has to supply.
|
|
138
|
+
*
|
|
139
|
+
* This is the inverse of `RemoteProperty<T>`.
|
|
140
|
+
*
|
|
141
|
+
* Note: This needs to be its own type alias, otherwise it will not distribute over unions. See
|
|
142
|
+
* https://www.typescriptlang.org/docs/handbook/advanced-types.html#distributive-conditional-types
|
|
143
|
+
*/
|
|
144
|
+
type LocalProperty<T> = T extends Function | ProxyMarked ? Local<T> : Unpromisify<T>;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Proxies `T` if it is a `ProxyMarked`, clones it otherwise (as handled by structured cloning and transfer handlers).
|
|
148
|
+
*/
|
|
149
|
+
export type ProxyOrClone<T> = T extends ProxyMarked ? Remote<T> : T;
|
|
150
|
+
/**
|
|
151
|
+
* Inverse of `ProxyOrClone<T>`.
|
|
152
|
+
*/
|
|
153
|
+
export type UnproxyOrClone<T> = T extends RemoteObject<ProxyMarked> ? Local<T> : T;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Takes the raw type of a remote object in the other thread and returns the type as it is visible to the local thread
|
|
157
|
+
* when proxied with `Comlink.proxy()`.
|
|
158
|
+
*
|
|
159
|
+
* This does not handle call signatures, which is handled by the more general `Remote<T>` type.
|
|
160
|
+
*
|
|
161
|
+
* @template T The raw type of a remote object as seen in the other thread.
|
|
162
|
+
*/
|
|
163
|
+
export type RemoteObject<T> = { [P in keyof T]: RemoteProperty<T[P]> };
|
|
164
|
+
/**
|
|
165
|
+
* Takes the type of an object as a remote thread would see it through a proxy (e.g. when passed in as a function
|
|
166
|
+
* argument) and returns the type that the local thread has to supply.
|
|
167
|
+
*
|
|
168
|
+
* This does not handle call signatures, which is handled by the more general `Local<T>` type.
|
|
169
|
+
*
|
|
170
|
+
* This is the inverse of `RemoteObject<T>`.
|
|
171
|
+
*
|
|
172
|
+
* @template T The type of a proxied object.
|
|
173
|
+
*/
|
|
174
|
+
export type LocalObject<T> = { [P in keyof T]: LocalProperty<T[P]> };
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Additional special comlink methods available on each proxy returned by `Comlink.wrap()`.
|
|
178
|
+
*/
|
|
179
|
+
export interface ProxyMethods {
|
|
180
|
+
[createEndpoint]: () => Promise<MessagePort>;
|
|
181
|
+
[releaseProxy]: () => void;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Takes the raw type of a remote object, function or class in the other thread and returns the type as it is visible to
|
|
186
|
+
* the local thread from the proxy return value of `Comlink.wrap()` or `Comlink.proxy()`.
|
|
187
|
+
*/
|
|
188
|
+
export type Remote<T> =
|
|
189
|
+
// Handle properties
|
|
190
|
+
RemoteObject<T> & // Handle call signature (if present)
|
|
191
|
+
(T extends (...args: infer TArguments) => infer TReturn
|
|
192
|
+
? (
|
|
193
|
+
...args: { [I in keyof TArguments]: UnproxyOrClone<TArguments[I]> }
|
|
194
|
+
) => Promisify<ProxyOrClone<Unpromisify<TReturn>>>
|
|
195
|
+
: unknown) & // Handle construct signature (if present)
|
|
196
|
+
// The return of construct signatures is always proxied (whether marked or not)
|
|
197
|
+
(T extends { new (...args: infer TArguments): infer TInstance }
|
|
198
|
+
? {
|
|
199
|
+
new (
|
|
200
|
+
...args: {
|
|
201
|
+
[I in keyof TArguments]: UnproxyOrClone<TArguments[I]>;
|
|
202
|
+
}
|
|
203
|
+
): Promisify<Remote<TInstance>>;
|
|
204
|
+
}
|
|
205
|
+
: unknown) & // Include additional special comlink methods available on the proxy.
|
|
206
|
+
ProxyMethods;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Expresses that a type can be either a sync or async.
|
|
210
|
+
*/
|
|
211
|
+
type MaybePromise<T> = Promise<T> | T;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Takes the raw type of a remote object, function or class as a remote thread would see it through a proxy (e.g. when
|
|
215
|
+
* passed in as a function argument) and returns the type the local thread has to supply.
|
|
216
|
+
*
|
|
217
|
+
* This is the inverse of `Remote<T>`. It takes a `Remote<T>` and returns its original input `T`.
|
|
218
|
+
*/
|
|
219
|
+
export type Local<T> =
|
|
220
|
+
// Omit the special proxy methods (they don't need to be supplied, comlink adds them)
|
|
221
|
+
Omit<LocalObject<T>, keyof ProxyMethods> & // Handle call signatures (if present)
|
|
222
|
+
(T extends (...args: infer TArguments) => infer TReturn
|
|
223
|
+
? (
|
|
224
|
+
...args: { [I in keyof TArguments]: ProxyOrClone<TArguments[I]> }
|
|
225
|
+
) => // The raw function could either be sync or async, but is always proxied automatically
|
|
226
|
+
MaybePromise<UnproxyOrClone<Unpromisify<TReturn>>>
|
|
227
|
+
: unknown) & // Handle construct signature (if present)
|
|
228
|
+
// The return of construct signatures is always proxied (whether marked or not)
|
|
229
|
+
(T extends { new (...args: infer TArguments): infer TInstance }
|
|
230
|
+
? {
|
|
231
|
+
new (
|
|
232
|
+
...args: {
|
|
233
|
+
[I in keyof TArguments]: ProxyOrClone<TArguments[I]>;
|
|
234
|
+
}
|
|
235
|
+
): // The raw constructor could either be sync or async, but is always proxied automatically
|
|
236
|
+
MaybePromise<Local<Unpromisify<TInstance>>>;
|
|
237
|
+
}
|
|
238
|
+
: unknown);
|
|
239
|
+
|
|
240
|
+
const isObject = (val: unknown): val is object =>
|
|
241
|
+
(typeof val === 'object' && val !== null) || typeof val === 'function';
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Customizes the serialization of certain values as determined by `canHandle()`.
|
|
245
|
+
*
|
|
246
|
+
* @template T The input type being handled by this transfer handler.
|
|
247
|
+
* @template S The serialized type sent over the wire.
|
|
248
|
+
*/
|
|
249
|
+
export interface TransferHandler<T, S> {
|
|
250
|
+
/**
|
|
251
|
+
* Gets called for every value to determine whether this transfer handler
|
|
252
|
+
* should serialize the value, which includes checking that it is of the right
|
|
253
|
+
* type (but can perform checks beyond that as well).
|
|
254
|
+
*/
|
|
255
|
+
canHandle(value: unknown): value is T;
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Gets called with the value if `canHandle()` returned `true` to produce a
|
|
259
|
+
* value that can be sent in a message, consisting of structured-cloneable
|
|
260
|
+
* values and/or transferrable objects.
|
|
261
|
+
*/
|
|
262
|
+
serialize(value: T): [S, Transferable[]];
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Gets called to deserialize an incoming value that was serialized in the
|
|
266
|
+
* other thread with this transfer handler (known through the name it was
|
|
267
|
+
* registered under).
|
|
268
|
+
*/
|
|
269
|
+
deserialize(value: S): T;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Internal transfer handle to handle objects marked to proxy.
|
|
274
|
+
*/
|
|
275
|
+
const proxyTransferHandler: TransferHandler<object, MessagePort> = {
|
|
276
|
+
canHandle: (val): val is ProxyMarked => isObject(val) && (val as ProxyMarked)[proxyMarker],
|
|
277
|
+
serialize(obj) {
|
|
278
|
+
const { port1, port2 } = new MessageChannel();
|
|
279
|
+
expose(obj, port1);
|
|
280
|
+
return [port2, [port2]];
|
|
281
|
+
},
|
|
282
|
+
deserialize(port) {
|
|
283
|
+
port.start();
|
|
284
|
+
return wrap(port);
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
interface ThrownValue {
|
|
289
|
+
[throwMarker]: unknown; // just needs to be present
|
|
290
|
+
value: unknown;
|
|
291
|
+
}
|
|
292
|
+
type SerializedThrownValue = { isError: true; value: Error } | { isError: false; value: unknown };
|
|
293
|
+
type PendingListenersMap = Map<
|
|
294
|
+
string,
|
|
295
|
+
[(value: WireValue | PromiseLike<WireValue>) => void, (error: unknown) => void]
|
|
296
|
+
>;
|
|
297
|
+
type EndpointWithPendingListeners = {
|
|
298
|
+
endpoint: Endpoint;
|
|
299
|
+
pendingListeners: PendingListenersMap;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Internal transfer handler to handle thrown exceptions.
|
|
304
|
+
*/
|
|
305
|
+
const throwTransferHandler: TransferHandler<ThrownValue, SerializedThrownValue> = {
|
|
306
|
+
canHandle: (value): value is ThrownValue => isObject(value) && throwMarker in value,
|
|
307
|
+
serialize({ value }) {
|
|
308
|
+
let serialized: SerializedThrownValue;
|
|
309
|
+
if (value instanceof Error) {
|
|
310
|
+
serialized = {
|
|
311
|
+
isError: true,
|
|
312
|
+
value: {
|
|
313
|
+
message: value.message,
|
|
314
|
+
name: value.name,
|
|
315
|
+
stack: value.stack,
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
} else {
|
|
319
|
+
serialized = { isError: false, value };
|
|
320
|
+
}
|
|
321
|
+
return [serialized, []];
|
|
322
|
+
},
|
|
323
|
+
deserialize(serialized) {
|
|
324
|
+
if (serialized.isError) {
|
|
325
|
+
throw Object.assign(new Error(serialized.value.message), serialized.value);
|
|
326
|
+
}
|
|
327
|
+
throw serialized.value;
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Allows customizing the serialization of certain values.
|
|
333
|
+
*/
|
|
334
|
+
export const transferHandlers = new Map<string, TransferHandler<unknown, unknown>>([
|
|
335
|
+
['proxy', proxyTransferHandler],
|
|
336
|
+
['throw', throwTransferHandler],
|
|
337
|
+
]);
|
|
338
|
+
|
|
339
|
+
function isAllowedOrigin(allowedOrigins: (string | RegExp)[], origin: string): boolean {
|
|
340
|
+
for (const allowedOrigin of allowedOrigins) {
|
|
341
|
+
if (origin === allowedOrigin || allowedOrigin === '*') {
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
if (allowedOrigin instanceof RegExp && allowedOrigin.test(origin)) {
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function expose(
|
|
352
|
+
obj: any,
|
|
353
|
+
ep: Endpoint = globalThis as any,
|
|
354
|
+
allowedOrigins: (string | RegExp)[] = ['*'],
|
|
355
|
+
) {
|
|
356
|
+
ep.addEventListener('message', function callback(ev: MessageEvent) {
|
|
357
|
+
if (!ev || !ev.data) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (!isAllowedOrigin(allowedOrigins, ev.origin)) {
|
|
361
|
+
console.warn(`Invalid origin '${ev.origin}' for comlink proxy`);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const { id, type, path } = {
|
|
365
|
+
path: [] as string[],
|
|
366
|
+
...(ev.data as Message),
|
|
367
|
+
};
|
|
368
|
+
const argumentList = (ev.data.argumentList || []).map(fromWireValue);
|
|
369
|
+
let returnValue;
|
|
370
|
+
try {
|
|
371
|
+
const parent = path.slice(0, -1).reduce((obj, prop) => obj[prop], obj);
|
|
372
|
+
const rawValue = path.reduce((obj, prop) => obj[prop], obj);
|
|
373
|
+
switch (type) {
|
|
374
|
+
case 'GET':
|
|
375
|
+
{
|
|
376
|
+
returnValue = rawValue;
|
|
377
|
+
}
|
|
378
|
+
break;
|
|
379
|
+
case 'SET':
|
|
380
|
+
{
|
|
381
|
+
parent[path.slice(-1)[0]!] = fromWireValue(ev.data.value);
|
|
382
|
+
returnValue = true;
|
|
383
|
+
}
|
|
384
|
+
break;
|
|
385
|
+
case 'APPLY':
|
|
386
|
+
{
|
|
387
|
+
returnValue = rawValue.apply(parent, argumentList);
|
|
388
|
+
}
|
|
389
|
+
break;
|
|
390
|
+
case 'CONSTRUCT':
|
|
391
|
+
{
|
|
392
|
+
const value = new rawValue(...argumentList);
|
|
393
|
+
returnValue = proxy(value);
|
|
394
|
+
}
|
|
395
|
+
break;
|
|
396
|
+
case 'ENDPOINT':
|
|
397
|
+
{
|
|
398
|
+
const { port1, port2 } = new MessageChannel();
|
|
399
|
+
expose(obj, port2);
|
|
400
|
+
returnValue = transfer(port1, [port1]);
|
|
401
|
+
}
|
|
402
|
+
break;
|
|
403
|
+
case 'RELEASE':
|
|
404
|
+
{
|
|
405
|
+
returnValue = undefined;
|
|
406
|
+
}
|
|
407
|
+
break;
|
|
408
|
+
default:
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
} catch (value) {
|
|
412
|
+
returnValue = { value, [throwMarker]: 0 };
|
|
413
|
+
}
|
|
414
|
+
Promise.resolve(returnValue)
|
|
415
|
+
.catch((value) => {
|
|
416
|
+
return { value, [throwMarker]: 0 };
|
|
417
|
+
})
|
|
418
|
+
.then((returnValue) => {
|
|
419
|
+
const [wireValue, transferables] = toWireValue(returnValue);
|
|
420
|
+
ep.postMessage({ ...wireValue, id }, transferables);
|
|
421
|
+
if (type === 'RELEASE') {
|
|
422
|
+
// detach and deactivate after sending release response above.
|
|
423
|
+
ep.removeEventListener('message', callback as any);
|
|
424
|
+
closeEndPoint(ep);
|
|
425
|
+
if (finalizer in obj && typeof obj[finalizer] === 'function') {
|
|
426
|
+
obj[finalizer]();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
})
|
|
430
|
+
.catch(() => {
|
|
431
|
+
// Send Serialization Error To Caller
|
|
432
|
+
const [wireValue, transferables] = toWireValue({
|
|
433
|
+
value: new TypeError('Unserializable return value'),
|
|
434
|
+
[throwMarker]: 0,
|
|
435
|
+
});
|
|
436
|
+
ep.postMessage({ ...wireValue, id }, transferables);
|
|
437
|
+
});
|
|
438
|
+
} as any);
|
|
439
|
+
if (ep.start) {
|
|
440
|
+
ep.start();
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function isMessagePort(endpoint: Endpoint): endpoint is MessagePort {
|
|
445
|
+
return endpoint.constructor.name === 'MessagePort';
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function closeEndPoint(endpoint: Endpoint) {
|
|
449
|
+
if (isMessagePort(endpoint)) endpoint.close();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export function wrap<T>(ep: Endpoint, target?: any): Remote<T> {
|
|
453
|
+
const pendingListeners: PendingListenersMap = new Map();
|
|
454
|
+
|
|
455
|
+
ep.addEventListener('message', function handleMessage(ev: Event) {
|
|
456
|
+
const { data } = ev as MessageEvent;
|
|
457
|
+
if (!data || !data.id) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const resolver = pendingListeners.get(data.id);
|
|
461
|
+
if (!resolver) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
resolver[0](data);
|
|
467
|
+
} finally {
|
|
468
|
+
pendingListeners.delete(data.id);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
return createProxy<T>({ endpoint: ep, pendingListeners }, [], target) as any;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function throwIfProxyReleased(isReleased: boolean) {
|
|
476
|
+
if (isReleased) {
|
|
477
|
+
throw new Error('Proxy has been released and is not useable');
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function releaseEndpoint(epWithPendingListeners: EndpointWithPendingListeners) {
|
|
482
|
+
for (const [key, value] of epWithPendingListeners.pendingListeners.entries()) {
|
|
483
|
+
value[1](new Error('Proxy has been released and is not useable'));
|
|
484
|
+
epWithPendingListeners.pendingListeners.delete(key);
|
|
485
|
+
}
|
|
486
|
+
return requestResponseMessage(epWithPendingListeners, {
|
|
487
|
+
type: 'RELEASE',
|
|
488
|
+
}).then(() => {
|
|
489
|
+
closeEndPoint(epWithPendingListeners.endpoint);
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
interface FinalizationRegistry<T> {
|
|
494
|
+
new (cb: (heldValue: T) => void): FinalizationRegistry<T>;
|
|
495
|
+
register(weakItem: object, heldValue: T, unregisterToken?: object | undefined): void;
|
|
496
|
+
unregister(unregisterToken: object): void;
|
|
497
|
+
}
|
|
498
|
+
declare let FinalizationRegistry: FinalizationRegistry<EndpointWithPendingListeners>;
|
|
499
|
+
|
|
500
|
+
const proxyCounter = new WeakMap<EndpointWithPendingListeners, number>();
|
|
501
|
+
const proxyFinalizers =
|
|
502
|
+
'FinalizationRegistry' in globalThis &&
|
|
503
|
+
new FinalizationRegistry((epWithPendingListeners: EndpointWithPendingListeners) => {
|
|
504
|
+
const newCount = (proxyCounter.get(epWithPendingListeners) || 0) - 1;
|
|
505
|
+
proxyCounter.set(epWithPendingListeners, newCount);
|
|
506
|
+
if (newCount === 0) {
|
|
507
|
+
releaseEndpoint(epWithPendingListeners).finally(() => {
|
|
508
|
+
epWithPendingListeners.pendingListeners.clear();
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
function registerProxy(proxy: object, epWithPendingListeners: EndpointWithPendingListeners) {
|
|
514
|
+
const newCount = (proxyCounter.get(epWithPendingListeners) || 0) + 1;
|
|
515
|
+
proxyCounter.set(epWithPendingListeners, newCount);
|
|
516
|
+
if (proxyFinalizers) {
|
|
517
|
+
proxyFinalizers.register(proxy, epWithPendingListeners, proxy);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function unregisterProxy(proxy: object) {
|
|
522
|
+
if (proxyFinalizers) {
|
|
523
|
+
proxyFinalizers.unregister(proxy);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function createProxy<T>(
|
|
528
|
+
epWithPendingListeners: EndpointWithPendingListeners,
|
|
529
|
+
path: (string | number | symbol)[] = [],
|
|
530
|
+
target: object = function () {},
|
|
531
|
+
): Remote<T> {
|
|
532
|
+
let isProxyReleased = false;
|
|
533
|
+
const proxy = new Proxy(target, {
|
|
534
|
+
get(_target, prop) {
|
|
535
|
+
throwIfProxyReleased(isProxyReleased);
|
|
536
|
+
if (prop === releaseProxy) {
|
|
537
|
+
return () => {
|
|
538
|
+
unregisterProxy(proxy);
|
|
539
|
+
releaseEndpoint(epWithPendingListeners).finally(() => {
|
|
540
|
+
epWithPendingListeners.pendingListeners.clear();
|
|
541
|
+
});
|
|
542
|
+
isProxyReleased = true;
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
if (prop === 'then') {
|
|
546
|
+
if (path.length === 0) {
|
|
547
|
+
return { then: () => proxy };
|
|
548
|
+
}
|
|
549
|
+
const r = requestResponseMessage(epWithPendingListeners, {
|
|
550
|
+
type: 'GET',
|
|
551
|
+
path: path.map((p) => p.toString()),
|
|
552
|
+
}).then(fromWireValue);
|
|
553
|
+
return r.then.bind(r);
|
|
554
|
+
}
|
|
555
|
+
return createProxy(epWithPendingListeners, [...path, prop]);
|
|
556
|
+
},
|
|
557
|
+
set(_target, prop, rawValue) {
|
|
558
|
+
throwIfProxyReleased(isProxyReleased);
|
|
559
|
+
// FIXME: ES6 Proxy Handler `set` methods are supposed to return a
|
|
560
|
+
// boolean. To show good will, we return true asynchronously ¯\_(ツ)_/¯
|
|
561
|
+
const [value, transferables] = toWireValue(rawValue);
|
|
562
|
+
return requestResponseMessage(
|
|
563
|
+
epWithPendingListeners,
|
|
564
|
+
{
|
|
565
|
+
type: 'SET',
|
|
566
|
+
path: [...path, prop].map((p) => p.toString()),
|
|
567
|
+
value,
|
|
568
|
+
},
|
|
569
|
+
transferables,
|
|
570
|
+
).then(fromWireValue) as any;
|
|
571
|
+
},
|
|
572
|
+
apply(_target, _thisArg, rawArgumentList) {
|
|
573
|
+
throwIfProxyReleased(isProxyReleased);
|
|
574
|
+
const last = path[path.length - 1];
|
|
575
|
+
if ((last as any) === createEndpoint) {
|
|
576
|
+
return requestResponseMessage(epWithPendingListeners, {
|
|
577
|
+
type: 'ENDPOINT',
|
|
578
|
+
}).then(fromWireValue);
|
|
579
|
+
}
|
|
580
|
+
// We just pretend that `bind()` didn’t happen.
|
|
581
|
+
if (last === 'bind') {
|
|
582
|
+
return createProxy(epWithPendingListeners, path.slice(0, -1));
|
|
583
|
+
}
|
|
584
|
+
const [argumentList, transferables] = processArguments(rawArgumentList);
|
|
585
|
+
return requestResponseMessage(
|
|
586
|
+
epWithPendingListeners,
|
|
587
|
+
{
|
|
588
|
+
type: 'APPLY',
|
|
589
|
+
path: path.map((p) => p.toString()),
|
|
590
|
+
argumentList,
|
|
591
|
+
},
|
|
592
|
+
transferables,
|
|
593
|
+
).then(fromWireValue);
|
|
594
|
+
},
|
|
595
|
+
construct(_target, rawArgumentList) {
|
|
596
|
+
throwIfProxyReleased(isProxyReleased);
|
|
597
|
+
const [argumentList, transferables] = processArguments(rawArgumentList);
|
|
598
|
+
return requestResponseMessage(
|
|
599
|
+
epWithPendingListeners,
|
|
600
|
+
{
|
|
601
|
+
type: 'CONSTRUCT',
|
|
602
|
+
path: path.map((p) => p.toString()),
|
|
603
|
+
argumentList,
|
|
604
|
+
},
|
|
605
|
+
transferables,
|
|
606
|
+
).then(fromWireValue);
|
|
607
|
+
},
|
|
608
|
+
});
|
|
609
|
+
registerProxy(proxy, epWithPendingListeners);
|
|
610
|
+
return proxy as any;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function myFlat<T>(arr: (T | T[])[]): T[] {
|
|
614
|
+
return Array.prototype.concat.apply([], arr);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function processArguments(argumentList: any[]): [WireValue[], Transferable[]] {
|
|
618
|
+
const processed = argumentList.map(toWireValue);
|
|
619
|
+
return [processed.map((v) => v[0]), myFlat(processed.map((v) => v[1]))];
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const transferCache = new WeakMap<any, Transferable[]>();
|
|
623
|
+
export function transfer<T>(obj: T, transfers: Transferable[]): T {
|
|
624
|
+
transferCache.set(obj, transfers);
|
|
625
|
+
return obj;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export function proxy<T extends {}>(obj: T): T & ProxyMarked {
|
|
629
|
+
return Object.assign(obj, { [proxyMarker]: true }) as any;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
export function windowEndpoint(
|
|
633
|
+
w: PostMessageWithOrigin,
|
|
634
|
+
context: EventSource = globalThis,
|
|
635
|
+
targetOrigin = '*',
|
|
636
|
+
): Endpoint {
|
|
637
|
+
return {
|
|
638
|
+
postMessage: (msg: any, transferables: Transferable[]) => w.postMessage(msg, targetOrigin, transferables),
|
|
639
|
+
addEventListener: context.addEventListener.bind(context),
|
|
640
|
+
removeEventListener: context.removeEventListener.bind(context),
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function toWireValue(value: any): [WireValue, Transferable[]] {
|
|
645
|
+
for (const [name, handler] of transferHandlers) {
|
|
646
|
+
if (handler.canHandle(value)) {
|
|
647
|
+
const [serializedValue, transferables] = handler.serialize(value);
|
|
648
|
+
return [
|
|
649
|
+
{
|
|
650
|
+
type: 'HANDLER',
|
|
651
|
+
name,
|
|
652
|
+
value: serializedValue,
|
|
653
|
+
},
|
|
654
|
+
transferables,
|
|
655
|
+
];
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return [
|
|
659
|
+
{
|
|
660
|
+
type: 'RAW',
|
|
661
|
+
value,
|
|
662
|
+
},
|
|
663
|
+
transferCache.get(value) || [],
|
|
664
|
+
];
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function fromWireValue(value: WireValue): any {
|
|
668
|
+
switch (value.type) {
|
|
669
|
+
case 'HANDLER':
|
|
670
|
+
return transferHandlers.get(value.name)!.deserialize(value.value);
|
|
671
|
+
case 'RAW':
|
|
672
|
+
return value.value;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function requestResponseMessage(
|
|
677
|
+
epWithPendingListeners: EndpointWithPendingListeners,
|
|
678
|
+
msg: Message,
|
|
679
|
+
transfers?: Transferable[],
|
|
680
|
+
): Promise<WireValue> {
|
|
681
|
+
const ep = epWithPendingListeners.endpoint;
|
|
682
|
+
const pendingListeners = epWithPendingListeners.pendingListeners;
|
|
683
|
+
return new Promise((resolve, reject) => {
|
|
684
|
+
const id = generateUUID();
|
|
685
|
+
pendingListeners.set(id, [resolve, reject]);
|
|
686
|
+
if (ep.start) {
|
|
687
|
+
ep.start();
|
|
688
|
+
}
|
|
689
|
+
ep.postMessage({ id, ...msg }, transfers);
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function generateUUID(): string {
|
|
694
|
+
return new Array(4)
|
|
695
|
+
.fill(0)
|
|
696
|
+
.map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16))
|
|
697
|
+
.join('-');
|
|
698
|
+
}
|