@farcaster/frame-host 0.0.7 → 0.0.9

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