|
1 |
| -import { assert, Has, NotHas, IsAny } from "conditional-type-checks"; |
| 1 | +import { |
| 2 | + assert, |
| 3 | + Has, |
| 4 | + NotHas, |
| 5 | + IsAny, |
| 6 | + IsExact, |
| 7 | + IsNever |
| 8 | +} from "conditional-type-checks"; |
2 | 9 |
|
3 | 10 | import * as Comlink from "../src/comlink.js";
|
4 | 11 |
|
@@ -86,9 +93,263 @@ async function closureSoICanUseAwait() {
|
86 | 93 | assert<Has<typeof b, () => Promise<number>>>(true);
|
87 | 94 | assert<IsAny<typeof b>>(false);
|
88 | 95 | const subproxy = proxy.c;
|
89 |
| - assert<Has<typeof subproxy, { d: Promise<number> }>>(true); |
| 96 | + assert<Has<typeof subproxy, Promise<{ d: number }>>>(true); |
90 | 97 | assert<IsAny<typeof subproxy>>(false);
|
91 | 98 | const copy = await proxy.c;
|
92 | 99 | assert<Has<typeof copy, { d: number }>>(true);
|
93 | 100 | }
|
| 101 | + |
| 102 | + { |
| 103 | + Comlink.wrap(new MessageChannel().port1); |
| 104 | + Comlink.expose({}, new MessageChannel().port2); |
| 105 | + const connection = new RTCPeerConnection(); |
| 106 | + const channel = connection.createDataChannel("comlink"); |
| 107 | + |
| 108 | + interface Baz { |
| 109 | + baz: number; |
| 110 | + method(): number; |
| 111 | + } |
| 112 | + |
| 113 | + class Foo { |
| 114 | + constructor(cParam: string) { |
| 115 | + const self = this; |
| 116 | + assert<IsExact<typeof self.proxyProp, Bar & Comlink.ProxyMarked>>(true); |
| 117 | + } |
| 118 | + prop1: string = "abc"; |
| 119 | + proxyProp = Comlink.proxy(new Bar()); |
| 120 | + methodWithTupleParams(...args: [string] | [number, string]): number { |
| 121 | + return 123; |
| 122 | + } |
| 123 | + methodWithProxiedReturnValue(): Baz & Comlink.ProxyMarked { |
| 124 | + return Comlink.proxy({ baz: 123, method: () => 123 }); |
| 125 | + } |
| 126 | + methodWithProxyParameter(param: Baz & Comlink.ProxyMarked): void {} |
| 127 | + } |
| 128 | + |
| 129 | + class Bar { |
| 130 | + prop2: string | number = "abc"; |
| 131 | + method(param: string): number { |
| 132 | + return 123; |
| 133 | + } |
| 134 | + methodWithProxiedReturnValue(): Baz & Comlink.ProxyMarked { |
| 135 | + return Comlink.proxy({ baz: 123, method: () => 123 }); |
| 136 | + } |
| 137 | + } |
| 138 | + const proxy = Comlink.wrap<Foo>(Comlink.windowEndpoint(self)); |
| 139 | + assert<IsExact<typeof proxy, Comlink.Remote<Foo>>>(true); |
| 140 | + |
| 141 | + proxy[Comlink.releaseProxy](); |
| 142 | + const endp = proxy[Comlink.createEndpoint](); |
| 143 | + assert<IsExact<typeof endp, Promise<MessagePort>>>(true); |
| 144 | + |
| 145 | + assert<IsAny<typeof proxy.prop1>>(false); |
| 146 | + assert<Has<typeof proxy.prop1, Promise<string>>>(true); |
| 147 | + |
| 148 | + const r1 = proxy.methodWithTupleParams(123, "abc"); |
| 149 | + assert<IsExact<typeof r1, Promise<number>>>(true); |
| 150 | + |
| 151 | + const r2 = proxy.methodWithTupleParams("abc"); |
| 152 | + assert<IsExact<typeof r2, Promise<number>>>(true); |
| 153 | + |
| 154 | + assert< |
| 155 | + IsExact<typeof proxy.proxyProp, Comlink.Remote<Bar & Comlink.ProxyMarked>> |
| 156 | + >(true); |
| 157 | + |
| 158 | + assert<IsAny<typeof proxy.proxyProp.prop2>>(false); |
| 159 | + assert<Has<typeof proxy.proxyProp.prop2, Promise<string>>>(true); |
| 160 | + assert<Has<typeof proxy.proxyProp.prop2, Promise<number>>>(true); |
| 161 | + |
| 162 | + const r3 = proxy.proxyProp.method("param"); |
| 163 | + assert<IsAny<typeof r3>>(false); |
| 164 | + assert<Has<typeof r3, Promise<number>>>(true); |
| 165 | + |
| 166 | + // @ts-expect-error |
| 167 | + proxy.proxyProp.method(123); |
| 168 | + |
| 169 | + // @ts-expect-error |
| 170 | + proxy.proxyProp.method(); |
| 171 | + |
| 172 | + const r4 = proxy.methodWithProxiedReturnValue(); |
| 173 | + assert<IsAny<typeof r4>>(false); |
| 174 | + assert< |
| 175 | + IsExact<typeof r4, Promise<Comlink.Remote<Baz & Comlink.ProxyMarked>>> |
| 176 | + >(true); |
| 177 | + |
| 178 | + const r5 = proxy.proxyProp.methodWithProxiedReturnValue(); |
| 179 | + assert< |
| 180 | + IsExact<typeof r5, Promise<Comlink.Remote<Baz & Comlink.ProxyMarked>>> |
| 181 | + >(true); |
| 182 | + |
| 183 | + const r6 = (await proxy.methodWithProxiedReturnValue()).baz; |
| 184 | + assert<IsAny<typeof r6>>(false); |
| 185 | + assert<Has<typeof r6, Promise<number>>>(true); |
| 186 | + |
| 187 | + const r7 = (await proxy.methodWithProxiedReturnValue()).method(); |
| 188 | + assert<IsAny<typeof r7>>(false); |
| 189 | + assert<Has<typeof r7, Promise<number>>>(true); |
| 190 | + |
| 191 | + const ProxiedFooClass = Comlink.wrap<typeof Foo>( |
| 192 | + Comlink.windowEndpoint(self) |
| 193 | + ); |
| 194 | + const inst1 = await new ProxiedFooClass("test"); |
| 195 | + assert<IsExact<typeof inst1, Comlink.RemoteObject<Foo>>>(true); |
| 196 | + |
| 197 | + // @ts-expect-error |
| 198 | + await new ProxiedFooClass(123); |
| 199 | + |
| 200 | + // @ts-expect-error |
| 201 | + await new ProxiedFooClass(); |
| 202 | + |
| 203 | + // |
| 204 | + // Tests for advanced proxy use cases |
| 205 | + // |
| 206 | + |
| 207 | + // Type round trips |
| 208 | + assert< |
| 209 | + IsExact< |
| 210 | + Comlink.Local<Comlink.Remote<Comlink.ProxyMarked>>, |
| 211 | + Comlink.ProxyMarked |
| 212 | + > |
| 213 | + >(true); |
| 214 | + assert< |
| 215 | + IsExact< |
| 216 | + Comlink.Local<Comlink.Remote<(a: number) => string>>, |
| 217 | + (a: number) => string | Promise<string> |
| 218 | + > |
| 219 | + >(true); |
| 220 | + |
| 221 | + interface Subscriber<T> { |
| 222 | + closed?: boolean; |
| 223 | + next?: (value: T) => void; |
| 224 | + } |
| 225 | + interface Unsubscribable { |
| 226 | + unsubscribe(): void; |
| 227 | + } |
| 228 | + /** A Subscribable that can get proxied by Comlink */ |
| 229 | + interface ProxyableSubscribable<T> extends Comlink.ProxyMarked { |
| 230 | + subscribe( |
| 231 | + subscriber: Comlink.Remote<Subscriber<T> & Comlink.ProxyMarked> |
| 232 | + ): Unsubscribable & Comlink.ProxyMarked; |
| 233 | + } |
| 234 | + |
| 235 | + /** Simple parameter object that gets cloned (not proxied) */ |
| 236 | + interface Params { |
| 237 | + textDocument: string; |
| 238 | + } |
| 239 | + |
| 240 | + class Registry { |
| 241 | + async registerProvider( |
| 242 | + provider: Comlink.Remote< |
| 243 | + ((params: Params) => ProxyableSubscribable<string>) & |
| 244 | + Comlink.ProxyMarked |
| 245 | + > |
| 246 | + ) { |
| 247 | + const resultPromise = provider({ textDocument: "foo" }); |
| 248 | + assert< |
| 249 | + IsExact< |
| 250 | + typeof resultPromise, |
| 251 | + Promise<Comlink.Remote<ProxyableSubscribable<string>>> |
| 252 | + > |
| 253 | + >(true); |
| 254 | + const result = await resultPromise; |
| 255 | + |
| 256 | + const subscriptionPromise = result.subscribe({ |
| 257 | + [Comlink.proxyMarker]: true, |
| 258 | + next: value => { |
| 259 | + assert<IsExact<typeof value, string>>(true); |
| 260 | + } |
| 261 | + }); |
| 262 | + assert< |
| 263 | + IsExact< |
| 264 | + typeof subscriptionPromise, |
| 265 | + Promise<Comlink.Remote<Unsubscribable & Comlink.ProxyMarked>> |
| 266 | + > |
| 267 | + >(true); |
| 268 | + const subscriber = Comlink.proxy({ |
| 269 | + next: (value: string) => console.log(value) |
| 270 | + }); |
| 271 | + result.subscribe(subscriber); |
| 272 | + |
| 273 | + const r1 = (await subscriptionPromise).unsubscribe(); |
| 274 | + assert<IsExact<typeof r1, Promise<void>>>(true); |
| 275 | + } |
| 276 | + } |
| 277 | + const proxy2 = Comlink.wrap<Registry>(Comlink.windowEndpoint(self)); |
| 278 | + |
| 279 | + proxy2.registerProvider( |
| 280 | + // Synchronous callback |
| 281 | + Comlink.proxy(({ textDocument }: Params) => { |
| 282 | + const subscribable = Comlink.proxy({ |
| 283 | + subscribe( |
| 284 | + subscriber: Comlink.Remote<Subscriber<string> & Comlink.ProxyMarked> |
| 285 | + ): Unsubscribable & Comlink.ProxyMarked { |
| 286 | + // Important to test here is that union types (such as Function | undefined) distribute properly |
| 287 | + // when wrapped in Promises/proxied |
| 288 | + |
| 289 | + assert<IsAny<typeof subscriber.closed>>(false); |
| 290 | + assert< |
| 291 | + IsExact< |
| 292 | + typeof subscriber.closed, |
| 293 | + Promise<true> | Promise<false> | Promise<undefined> | undefined |
| 294 | + > |
| 295 | + >(true); |
| 296 | + |
| 297 | + assert<IsAny<typeof subscriber.next>>(false); |
| 298 | + assert< |
| 299 | + IsExact< |
| 300 | + typeof subscriber.next, |
| 301 | + | Comlink.Remote<(value: string) => void> |
| 302 | + | Promise<undefined> |
| 303 | + | undefined |
| 304 | + > |
| 305 | + >(true); |
| 306 | + |
| 307 | + // @ts-expect-error |
| 308 | + subscriber.next(); |
| 309 | + |
| 310 | + if (subscriber.next) { |
| 311 | + // Only checking for presence is not enough, since it could be a Promise |
| 312 | + |
| 313 | + // @ts-expect-error |
| 314 | + subscriber.next(); |
| 315 | + } |
| 316 | + |
| 317 | + if (typeof subscriber.next === "function") { |
| 318 | + subscriber.next("abc"); |
| 319 | + } |
| 320 | + |
| 321 | + return Comlink.proxy({ unsubscribe() {} }); |
| 322 | + } |
| 323 | + }); |
| 324 | + assert<Has<typeof subscribable, Comlink.ProxyMarked>>(true); |
| 325 | + return subscribable; |
| 326 | + }) |
| 327 | + ); |
| 328 | + proxy2.registerProvider( |
| 329 | + // Async callback |
| 330 | + Comlink.proxy(async ({ textDocument }: Params) => { |
| 331 | + return Comlink.proxy({ |
| 332 | + subscribe( |
| 333 | + subscriber: Comlink.Remote<Subscriber<string> & Comlink.ProxyMarked> |
| 334 | + ): Unsubscribable & Comlink.ProxyMarked { |
| 335 | + assert<IsAny<typeof subscriber.next>>(false); |
| 336 | + assert< |
| 337 | + IsExact< |
| 338 | + typeof subscriber.next, |
| 339 | + | Comlink.Remote<(value: string) => void> |
| 340 | + | Promise<undefined> |
| 341 | + | undefined |
| 342 | + > |
| 343 | + >(true); |
| 344 | + |
| 345 | + // Only checking for presence is not enough, since it could be a Promise |
| 346 | + if (typeof subscriber.next === "function") { |
| 347 | + subscriber.next("abc"); |
| 348 | + } |
| 349 | + return Comlink.proxy({ unsubscribe() {} }); |
| 350 | + } |
| 351 | + }); |
| 352 | + }) |
| 353 | + ); |
| 354 | + } |
94 | 355 | }
|
0 commit comments