UNPKG

11.2 kBPlain TextView Raw
1import { assert, Has, NotHas, IsAny, IsExact } from "conditional-type-checks";
2
3import * as Comlink from "../src/comlink.js";
4
5async function closureSoICanUseAwait() {
6 {
7 function simpleNumberFunction() {
8 return 4;
9 }
10
11 const proxy = Comlink.wrap<typeof simpleNumberFunction>(0 as any);
12 assert<IsAny<typeof proxy>>(false);
13 const v = proxy();
14 assert<Has<typeof v, Promise<number>>>(true);
15 }
16
17 {
18 function simpleObjectFunction() {
19 return { a: 3 };
20 }
21
22 const proxy = Comlink.wrap<typeof simpleObjectFunction>(0 as any);
23 const v = await proxy();
24 assert<Has<typeof v, { a: number }>>(true);
25 }
26
27 {
28 async function simpleAsyncFunction() {
29 return { a: 3 };
30 }
31
32 const proxy = Comlink.wrap<typeof simpleAsyncFunction>(0 as any);
33 const v = await proxy();
34 assert<Has<typeof v, { a: number }>>(true);
35 }
36
37 {
38 function functionWithProxy() {
39 return Comlink.proxy({ a: 3 });
40 }
41
42 const proxy = Comlink.wrap<typeof functionWithProxy>(0 as any);
43 const subproxy = await proxy();
44 const prop = subproxy.a;
45 assert<Has<typeof prop, Promise<number>>>(true);
46 }
47
48 {
49 class X {
50 static staticFunc() {
51 return 4;
52 }
53 private f = 4;
54 public g = 9;
55 sayHi() {
56 return "hi";
57 }
58 }
59
60 const proxy = Comlink.wrap<typeof X>(0 as any);
61 assert<Has<typeof proxy, { staticFunc: () => Promise<number> }>>(true);
62 const instance = await new proxy();
63 assert<Has<typeof instance, { sayHi: () => Promise<string> }>>(true);
64 assert<Has<typeof instance, { g: Promise<number> }>>(true);
65 assert<NotHas<typeof instance, { f: Promise<number> }>>(true);
66 assert<IsAny<typeof instance>>(false);
67 }
68
69 {
70 const x = {
71 a: 4,
72 b() {
73 return 9;
74 },
75 c: {
76 d: 3,
77 },
78 };
79
80 const proxy = Comlink.wrap<typeof x>(0 as any);
81 assert<IsAny<typeof proxy>>(false);
82 const a = proxy.a;
83 assert<Has<typeof a, Promise<number>>>(true);
84 assert<IsAny<typeof a>>(false);
85 const b = proxy.b;
86 assert<Has<typeof b, () => Promise<number>>>(true);
87 assert<IsAny<typeof b>>(false);
88 const subproxy = proxy.c;
89 assert<Has<typeof subproxy, Promise<{ d: number }>>>(true);
90 assert<IsAny<typeof subproxy>>(false);
91 const copy = await proxy.c;
92 assert<Has<typeof copy, { d: number }>>(true);
93 }
94
95 {
96 Comlink.wrap(new MessageChannel().port1);
97 Comlink.expose({}, new MessageChannel().port2);
98
99 interface Baz {
100 baz: number;
101 method(): number;
102 }
103
104 class Foo {
105 constructor(cParam: string) {
106 const self = this;
107 assert<IsExact<typeof self.proxyProp, Bar & Comlink.ProxyMarked>>(true);
108 }
109 prop1: string = "abc";
110 proxyProp = Comlink.proxy(new Bar());
111 methodWithTupleParams(...args: [string] | [number, string]): number {
112 return 123;
113 }
114 methodWithProxiedReturnValue(): Baz & Comlink.ProxyMarked {
115 return Comlink.proxy({ baz: 123, method: () => 123 });
116 }
117 methodWithProxyParameter(param: Baz & Comlink.ProxyMarked): void {}
118 }
119
120 class Bar {
121 prop2: string | number = "abc";
122 method(param: string): number {
123 return 123;
124 }
125 methodWithProxiedReturnValue(): Baz & Comlink.ProxyMarked {
126 return Comlink.proxy({ baz: 123, method: () => 123 });
127 }
128 }
129 const proxy = Comlink.wrap<Foo>(Comlink.windowEndpoint(self));
130 assert<IsExact<typeof proxy, Comlink.Remote<Foo>>>(true);
131
132 proxy[Comlink.releaseProxy]();
133 const endp = proxy[Comlink.createEndpoint]();
134 assert<IsExact<typeof endp, Promise<MessagePort>>>(true);
135
136 assert<IsAny<typeof proxy.prop1>>(false);
137 assert<Has<typeof proxy.prop1, Promise<string>>>(true);
138
139 const r1 = proxy.methodWithTupleParams(123, "abc");
140 assert<IsExact<typeof r1, Promise<number>>>(true);
141
142 const r2 = proxy.methodWithTupleParams("abc");
143 assert<IsExact<typeof r2, Promise<number>>>(true);
144
145 assert<
146 IsExact<typeof proxy.proxyProp, Comlink.Remote<Bar & Comlink.ProxyMarked>>
147 >(true);
148
149 assert<IsAny<typeof proxy.proxyProp.prop2>>(false);
150 assert<Has<typeof proxy.proxyProp.prop2, Promise<string>>>(true);
151 assert<Has<typeof proxy.proxyProp.prop2, Promise<number>>>(true);
152
153 const r3 = proxy.proxyProp.method("param");
154 assert<IsAny<typeof r3>>(false);
155 assert<Has<typeof r3, Promise<number>>>(true);
156
157 // @ts-expect-error
158 proxy.proxyProp.method(123);
159
160 // @ts-expect-error
161 proxy.proxyProp.method();
162
163 const r4 = proxy.methodWithProxiedReturnValue();
164 assert<IsAny<typeof r4>>(false);
165 assert<
166 IsExact<typeof r4, Promise<Comlink.Remote<Baz & Comlink.ProxyMarked>>>
167 >(true);
168
169 const r5 = proxy.proxyProp.methodWithProxiedReturnValue();
170 assert<
171 IsExact<typeof r5, Promise<Comlink.Remote<Baz & Comlink.ProxyMarked>>>
172 >(true);
173
174 const r6 = (await proxy.methodWithProxiedReturnValue()).baz;
175 assert<IsAny<typeof r6>>(false);
176 assert<Has<typeof r6, Promise<number>>>(true);
177
178 const r7 = (await proxy.methodWithProxiedReturnValue()).method();
179 assert<IsAny<typeof r7>>(false);
180 assert<Has<typeof r7, Promise<number>>>(true);
181
182 const ProxiedFooClass = Comlink.wrap<typeof Foo>(
183 Comlink.windowEndpoint(self)
184 );
185 const inst1 = await new ProxiedFooClass("test");
186 assert<IsExact<typeof inst1, Comlink.Remote<Foo>>>(true);
187 inst1[Comlink.releaseProxy]();
188 inst1[Comlink.createEndpoint]();
189
190 // @ts-expect-error
191 await new ProxiedFooClass(123);
192
193 // @ts-expect-error
194 await new ProxiedFooClass();
195
196 //
197 // Tests for advanced proxy use cases
198 //
199
200 // Type round trips
201 // This tests that Local is the exact inverse of Remote for objects:
202 assert<
203 IsExact<
204 Comlink.Local<Comlink.Remote<Comlink.ProxyMarked>>,
205 Comlink.ProxyMarked
206 >
207 >(true);
208 // This tests that Local is the exact inverse of Remote for functions, with one difference:
209 // The local version of a remote function can be either implemented as a sync or async function,
210 // because Remote<T> always makes the function async.
211 assert<
212 IsExact<
213 Comlink.Local<Comlink.Remote<(a: number) => string>>,
214 (a: number) => string | Promise<string>
215 >
216 >(true);
217
218 interface Subscriber<T> {
219 closed?: boolean;
220 next?: (value: T) => void;
221 }
222 interface Unsubscribable {
223 unsubscribe(): void;
224 }
225 /** A Subscribable that can get proxied by Comlink */
226 interface ProxyableSubscribable<T> extends Comlink.ProxyMarked {
227 subscribe(
228 subscriber: Comlink.Remote<Subscriber<T> & Comlink.ProxyMarked>
229 ): Unsubscribable & Comlink.ProxyMarked;
230 }
231
232 /** Simple parameter object that gets cloned (not proxied) */
233 interface Params {
234 textDocument: string;
235 }
236
237 class Registry {
238 async registerProvider(
239 provider: Comlink.Remote<
240 ((params: Params) => ProxyableSubscribable<string>) &
241 Comlink.ProxyMarked
242 >
243 ) {
244 const resultPromise = provider({ textDocument: "foo" });
245 assert<
246 IsExact<
247 typeof resultPromise,
248 Promise<Comlink.Remote<ProxyableSubscribable<string>>>
249 >
250 >(true);
251 const result = await resultPromise;
252
253 const subscriptionPromise = result.subscribe({
254 [Comlink.proxyMarker]: true,
255 next: (value) => {
256 assert<IsExact<typeof value, string>>(true);
257 },
258 });
259 assert<
260 IsExact<
261 typeof subscriptionPromise,
262 Promise<Comlink.Remote<Unsubscribable & Comlink.ProxyMarked>>
263 >
264 >(true);
265 const subscriber = Comlink.proxy({
266 next: (value: string) => console.log(value),
267 });
268 result.subscribe(subscriber);
269
270 const r1 = (await subscriptionPromise).unsubscribe();
271 assert<IsExact<typeof r1, Promise<void>>>(true);
272 }
273 }
274 const proxy2 = Comlink.wrap<Registry>(Comlink.windowEndpoint(self));
275
276 proxy2.registerProvider(
277 // Synchronous callback
278 Comlink.proxy(({ textDocument }: Params) => {
279 const subscribable = Comlink.proxy({
280 subscribe(
281 subscriber: Comlink.Remote<Subscriber<string> & Comlink.ProxyMarked>
282 ): Unsubscribable & Comlink.ProxyMarked {
283 // Important to test here is that union types (such as Function | undefined) distribute properly
284 // when wrapped in Promises/proxied
285
286 assert<IsAny<typeof subscriber.closed>>(false);
287 assert<
288 IsExact<
289 typeof subscriber.closed,
290 Promise<true> | Promise<false> | Promise<undefined> | undefined
291 >
292 >(true);
293
294 assert<IsAny<typeof subscriber.next>>(false);
295 assert<
296 IsExact<
297 typeof subscriber.next,
298 | Comlink.Remote<(value: string) => void>
299 | Promise<undefined>
300 | undefined
301 >
302 >(true);
303
304 // @ts-expect-error
305 subscriber.next();
306
307 if (subscriber.next) {
308 // Only checking for presence is not enough, since it could be a Promise
309 // @ts-expect-error
310 subscriber.next();
311 }
312
313 if (typeof subscriber.next === "function") {
314 subscriber.next("abc");
315 }
316
317 return Comlink.proxy({ unsubscribe() {} });
318 },
319 });
320 assert<Has<typeof subscribable, Comlink.ProxyMarked>>(true);
321 return subscribable;
322 })
323 );
324 proxy2.registerProvider(
325 // Async callback
326 Comlink.proxy(async ({ textDocument }: Params) => {
327 const subscribable = Comlink.proxy({
328 subscribe(
329 subscriber: Comlink.Remote<Subscriber<string> & Comlink.ProxyMarked>
330 ): Unsubscribable & Comlink.ProxyMarked {
331 assert<IsAny<typeof subscriber.next>>(false);
332 assert<
333 IsExact<
334 typeof subscriber.next,
335 | Comlink.Remote<(value: string) => void>
336 | Promise<undefined>
337 | undefined
338 >
339 >(true);
340
341 // Only checking for presence is not enough, since it could be a Promise
342 if (typeof subscriber.next === "function") {
343 subscriber.next("abc");
344 }
345 return Comlink.proxy({ unsubscribe() {} });
346 },
347 });
348 return subscribable;
349 })
350 );
351 }
352
353 // Transfer handlers
354 {
355 const urlTransferHandler: Comlink.TransferHandler<URL, string> = {
356 canHandle: (val): val is URL => {
357 assert<IsExact<typeof val, unknown>>(true);
358 return val instanceof URL;
359 },
360 serialize: (url) => {
361 assert<IsExact<typeof url, URL>>(true);
362 return [url.href, []];
363 },
364 deserialize: (str) => {
365 assert<IsExact<typeof str, string>>(true);
366 return new URL(str);
367 },
368 };
369 Comlink.transferHandlers.set("URL", urlTransferHandler);
370 }
371}