@onting/rpc 0.0.0-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.
@@ -0,0 +1,25 @@
1
+ import { BrowsingContext as BrowsingContext$1, WebDriver } from 'selenium-webdriver';
2
+
3
+ type Promisify<T> = T extends Promise<unknown> ? T : Promise<T>;
4
+
5
+ type InferClient<T extends Record<string, (...args: unknown[]) => unknown>> = {
6
+ readonly [P in keyof T]: (...args: Parameters<T[P]>) => Promisify<ReturnType<T[P]>>;
7
+ };
8
+
9
+ type BrowsingContext = Awaited<ReturnType<typeof BrowsingContext$1>>;
10
+
11
+ type StubEnvironment = {
12
+ readonly browsingContext: BrowsingContext;
13
+ readonly webDriver: WebDriver;
14
+ };
15
+
16
+ type StubImplementation = Record<string, (...args: unknown[]) => unknown>;
17
+
18
+ type StubDeclaration<T extends StubImplementation> = {
19
+ readonly keys: readonly (keyof T)[];
20
+ implement(environment: StubEnvironment): T;
21
+ };
22
+
23
+ declare function createClientStub<T extends StubDeclaration<S>, S extends StubImplementation>(declaration: Pick<T, 'keys'>, messagePort: MessagePort): InferClient<S>;
24
+
25
+ export { createClientStub };
@@ -0,0 +1,207 @@
1
+ // ../../node_modules/message-port-rpc/dist/message-port-rpc.mjs
2
+ function workthru_(target, transformer, walked) {
3
+ if (Array.isArray(target)) {
4
+ if (walked.has(target)) {
5
+ return walked.get(target);
6
+ }
7
+ let nextArray = transformer(target);
8
+ if (nextArray !== target) {
9
+ walked.set(target, nextArray);
10
+ return nextArray;
11
+ }
12
+ walked.set(target, target);
13
+ for (const index in target) {
14
+ const value = target[index];
15
+ const nextValue = workthru_(value, transformer, walked);
16
+ if (nextValue !== value) {
17
+ if (nextArray === target) {
18
+ nextArray = [...target];
19
+ }
20
+ nextArray[index] = nextValue;
21
+ }
22
+ }
23
+ walked.set(target, nextArray);
24
+ return nextArray;
25
+ }
26
+ if (typeof target === "object" && target !== null) {
27
+ if (walked.has(target)) {
28
+ return walked.get(target);
29
+ }
30
+ const prototype = Object.getPrototypeOf(target);
31
+ if (prototype === null || prototype === Object.prototype) {
32
+ const nextTarget = transformer(target);
33
+ if (nextTarget !== target) {
34
+ walked.set(target, nextTarget);
35
+ return nextTarget;
36
+ }
37
+ walked.set(target, target);
38
+ const entries = Object.entries(target);
39
+ let nextMap = void 0;
40
+ for (const [key, value] of entries) {
41
+ const nextValue = workthru_(value, transformer, walked);
42
+ if (nextValue !== value) {
43
+ if (!nextMap) {
44
+ nextMap = new Map(entries);
45
+ }
46
+ nextMap.set(key, nextValue);
47
+ }
48
+ }
49
+ const nextObject = nextMap ? Object.fromEntries(nextMap.entries()) : target;
50
+ walked.set(target, nextObject);
51
+ return nextObject;
52
+ }
53
+ }
54
+ return transformer(target);
55
+ }
56
+ function workthru(target, transformer) {
57
+ return workthru_(target, transformer, /* @__PURE__ */ new Map());
58
+ }
59
+ var workthru_default = workthru;
60
+ function isArrayBuffer(value) {
61
+ return !!("ArrayBuffer" in globalThis) && value instanceof ArrayBuffer;
62
+ }
63
+ function isAudioData(value) {
64
+ return !!("AudioData" in globalThis) && value instanceof AudioData;
65
+ }
66
+ function isImageBitmap(value) {
67
+ return !!("ImageBitmap" in globalThis) && value instanceof ImageBitmap;
68
+ }
69
+ function isMediaSourceHandle(value) {
70
+ return !!("MediaSourceHandle" in globalThis) && value instanceof MediaSourceHandle;
71
+ }
72
+ function isMediaStreamTrack(value) {
73
+ return !!("MediaStreamTrack" in globalThis) && value instanceof MediaStreamTrack;
74
+ }
75
+ function isMessagePort(value) {
76
+ return !!("MessagePort" in globalThis) && value instanceof MessagePort;
77
+ }
78
+ function isMIDIAccess(value) {
79
+ return !!("MIDIAccess" in globalThis) && value instanceof MIDIAccess;
80
+ }
81
+ function isOffscreenCanvas(value) {
82
+ return !!("OffscreenCanvas" in globalThis) && value instanceof OffscreenCanvas;
83
+ }
84
+ function isReadableStream(value) {
85
+ return !!("ReadableStream" in globalThis) && value instanceof ReadableStream;
86
+ }
87
+ function isRTCDataChannel(value) {
88
+ return !!("RTCDataChannel" in globalThis) && value instanceof RTCDataChannel;
89
+ }
90
+ function isTransformStream(value) {
91
+ return !!("TransformStream" in globalThis) && value instanceof TransformStream;
92
+ }
93
+ function isVideoFrame(value) {
94
+ return !!("VideoFrame" in globalThis) && value instanceof VideoFrame;
95
+ }
96
+ function isWebTransportReceiveStream(value) {
97
+ return !!("WebTransportReceiveStream" in globalThis && // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
+ typeof globalThis["WebTransportReceiveStream"] === "function") && value instanceof globalThis.WebTransportReceiveStream;
99
+ }
100
+ function isWebTransportSendStream(value) {
101
+ return !!// eslint-disable-next-line @typescript-eslint/no-explicit-any
102
+ ("WebTransportSendStream" in globalThis && typeof globalThis["WebTransportSendStream"] === "function") && value instanceof globalThis.WebTransportSendStream;
103
+ }
104
+ function isWritableStream(value) {
105
+ return !!("WritableStream" in globalThis) && value instanceof WritableStream;
106
+ }
107
+ function isTransferable(value) {
108
+ return isArrayBuffer(value) || isAudioData(value) || isImageBitmap(value) || isMediaSourceHandle(value) || isMediaStreamTrack(value) || isMessagePort(value) || isMIDIAccess(value) || isOffscreenCanvas(value) || isReadableStream(value) || isRTCDataChannel(value) || isTransformStream(value) || isVideoFrame(value) || isWebTransportReceiveStream(value) || isWebTransportSendStream(value) || isWritableStream(value);
109
+ }
110
+ var isTransferable_default = isTransferable;
111
+ function getAllTransfer(data) {
112
+ const transferSet = /* @__PURE__ */ new Set();
113
+ workthru_default(data, (value) => {
114
+ if (isTransferable_default(value)) {
115
+ transferSet.add(value);
116
+ }
117
+ return value;
118
+ });
119
+ return Array.from(transferSet);
120
+ }
121
+ var ABORT = "ABORT";
122
+ var CALL = "CALL";
123
+ var REJECT = "REJECT";
124
+ var RESOLVE = "RESOLVE";
125
+ function messagePortRPC(port, fn) {
126
+ const handleMessage = (event) => {
127
+ const data = event.data;
128
+ if (Array.isArray(data) && data[0] === CALL) {
129
+ event.stopImmediatePropagation();
130
+ const [returnPort] = event.ports;
131
+ if (!returnPort) {
132
+ throw new Error("RPCCallMessage must contains a port.");
133
+ }
134
+ if (fn) {
135
+ (async () => {
136
+ const abortController = new AbortController();
137
+ try {
138
+ returnPort.onmessage = ({ data: data2 }) => Array.isArray(data2) && data2[0] === ABORT && abortController.abort();
139
+ const result = await fn.call({ signal: abortController.signal }, ...data.slice(1));
140
+ returnPort.postMessage([RESOLVE, result], getAllTransfer(result));
141
+ } catch (error) {
142
+ returnPort.postMessage([REJECT, error]);
143
+ } finally {
144
+ returnPort.close();
145
+ }
146
+ })();
147
+ } else {
148
+ returnPort.postMessage([
149
+ REJECT,
150
+ new Error(
151
+ "No function was registered on this RPC. This is probably calling a client which do not implement the function."
152
+ )
153
+ ]);
154
+ returnPort.close();
155
+ }
156
+ }
157
+ };
158
+ port.addEventListener("message", handleMessage);
159
+ port.start();
160
+ const createWithOptions = (init) => (...args) => {
161
+ return new Promise((resolve, reject) => {
162
+ const { port1, port2 } = new MessageChannel();
163
+ port1.onmessage = (event) => {
164
+ const data = event.data;
165
+ if (data[0] === RESOLVE) {
166
+ resolve(data[1]);
167
+ } else {
168
+ reject(data[1]);
169
+ }
170
+ port1.close();
171
+ };
172
+ init?.signal?.addEventListener("abort", () => {
173
+ port1.postMessage([ABORT]);
174
+ port1.close();
175
+ reject(new Error("Aborted."));
176
+ });
177
+ const transfer = getAllTransfer(args);
178
+ port.postMessage([CALL, ...args], [port2, ...transfer]);
179
+ });
180
+ };
181
+ const stub = createWithOptions({});
182
+ stub.withOptions = createWithOptions;
183
+ return stub;
184
+ }
185
+ var messagePortRPC_default = messagePortRPC;
186
+
187
+ // src/client/createClientStub.ts
188
+ function lazyStub(fnFactory) {
189
+ let fnPromise;
190
+ return async (...args) => (await (fnPromise ?? (fnPromise = fnFactory())))(...args);
191
+ }
192
+ function createClientStub(declaration, messagePort) {
193
+ const clientStubMap = /* @__PURE__ */ new Map();
194
+ const handshakePromise = messagePortRPC_default(messagePort)();
195
+ for (const key of declaration.keys) {
196
+ clientStubMap.set(
197
+ key,
198
+ lazyStub(async () => messagePortRPC_default((await handshakePromise)[key]))
199
+ );
200
+ }
201
+ return Object.fromEntries(clientStubMap.entries());
202
+ }
203
+ var createClientStub_default = createClientStub;
204
+ export {
205
+ createClientStub_default as createClientStub
206
+ };
207
+ //# sourceMappingURL=client.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../node_modules/workthru/src/workthru.ts","../../../node_modules/message-port-rpc/src/isTransferable.ts","../../../node_modules/message-port-rpc/src/getAllTransfer.ts","../../../node_modules/message-port-rpc/src/messagePortRPC.ts","../../../node_modules/message-port-rpc/src/forGenerator.ts","../src/client/createClientStub.ts"],"sourcesContent":["/**\n * Internal recursive helper for {@link workthru}.\n *\n * For arrays and plain objects, calls transformer first. If transformer returns the same reference,\n * recursively walks children and rebuilds the container only when a child changes (structural sharing).\n * Class instances, primitives, functions, and null are passed directly to transformer without recursion.\n *\n * The `walked` map tracks already-visited nodes to handle circular references and avoid duplicate work.\n *\n * @param target - Value to transform.\n * @param transformer - Called on every visited value; return the original value to recurse into children.\n * @param walked - Cycle-detection map from original value to its transformed counterpart.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction workthru_(target: any, transformer: (value: any) => any, walked: Map<any, any>): any {\n if (Array.isArray(target)) {\n if (walked.has(target)) {\n return walked.get(target);\n }\n\n let nextArray = transformer(target);\n\n if (nextArray !== target) {\n walked.set(target, nextArray);\n\n return nextArray;\n }\n\n walked.set(target, target);\n\n // for-in loop can handle sparse array.\n for (const index in target) {\n const value = target[index];\n const nextValue = workthru_(value, transformer, walked);\n\n if (nextValue !== value) {\n if (nextArray === target) {\n nextArray = [...target];\n }\n\n nextArray[index] = nextValue;\n }\n }\n\n walked.set(target, nextArray);\n\n return nextArray;\n }\n\n if (typeof target === 'object' && target !== null) {\n if (walked.has(target)) {\n return walked.get(target);\n }\n\n const prototype = Object.getPrototypeOf(target);\n\n if (prototype === null || prototype === Object.prototype) {\n const nextTarget = transformer(target);\n\n if (nextTarget !== target) {\n walked.set(target, nextTarget);\n\n return nextTarget;\n }\n\n walked.set(target, target);\n\n const entries = Object.entries(target);\n let nextMap = undefined;\n\n for (const [key, value] of entries) {\n const nextValue = workthru_(value, transformer, walked);\n\n if (nextValue !== value) {\n if (!nextMap) {\n nextMap = new Map(entries);\n }\n\n nextMap.set(key, nextValue);\n }\n }\n\n const nextObject = nextMap ? Object.fromEntries(nextMap.entries()) : target;\n\n walked.set(target, nextObject);\n\n return nextObject;\n }\n }\n\n return transformer(target);\n}\n\n/**\n * Recursively walks the input in a depth-first search manner and transforms value as needed.\n *\n * Every traversed value will be passed to the `transformer` function.\n *\n * - If the `transformer` return the original value, the traversal for this branch will be continued\n * - If the `transformer` return a new value, the traversal for this branch will be ended\n *\n * The following data types are supported:\n *\n * - boolean, number, string\n * - array will be transformed on itself and every of the element\n * - plain object will be transformed on itself and every of its member value\n *\n * Notes:\n *\n * - Values with unsupported will be kept as-is;\n * - Values that are not transformed will be kept as-is\n *\n * @param target - The value to be worked through\n * @param transformer - The function to transform the value\n * @returns - The transformed value if the input has been transformed, otherwise, return the original value\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction workthru(target: any, transformer: (value: any) => any): any {\n return workthru_(target, transformer, new Map());\n}\n\nexport default workthru;\n","function isArrayBuffer(value: unknown): value is ArrayBuffer {\n return !!('ArrayBuffer' in globalThis) && value instanceof ArrayBuffer;\n}\n\nfunction isAudioData(value: unknown): value is AudioData {\n return !!('AudioData' in globalThis) && value instanceof AudioData;\n}\n\nfunction isImageBitmap(value: unknown): value is ImageBitmap {\n return !!('ImageBitmap' in globalThis) && value instanceof ImageBitmap;\n}\n\nfunction isMediaSourceHandle(value: unknown): value is MediaSourceHandle {\n return !!('MediaSourceHandle' in globalThis) && value instanceof MediaSourceHandle;\n}\n\nfunction isMediaStreamTrack(value: unknown): value is MediaStreamTrack {\n return !!('MediaStreamTrack' in globalThis) && value instanceof MediaStreamTrack;\n}\n\nfunction isMessagePort(value: unknown): value is MessagePort {\n return !!('MessagePort' in globalThis) && value instanceof MessagePort;\n}\n\nfunction isMIDIAccess(value: unknown): value is MIDIAccess {\n return !!('MIDIAccess' in globalThis) && value instanceof MIDIAccess;\n}\n\nfunction isOffscreenCanvas(value: unknown): value is OffscreenCanvas {\n return !!('OffscreenCanvas' in globalThis) && value instanceof OffscreenCanvas;\n}\n\nfunction isReadableStream(value: unknown): value is ReadableStream {\n return !!('ReadableStream' in globalThis) && value instanceof ReadableStream;\n}\n\nfunction isRTCDataChannel(value: unknown): value is RTCDataChannel {\n return !!('RTCDataChannel' in globalThis) && value instanceof RTCDataChannel;\n}\n\nfunction isTransformStream(value: unknown): value is TransformStream {\n return !!('TransformStream' in globalThis) && value instanceof TransformStream;\n}\n\nfunction isVideoFrame(value: unknown): value is VideoFrame {\n return !!('VideoFrame' in globalThis) && value instanceof VideoFrame;\n}\n\nfunction isWebTransportReceiveStream(value: unknown): boolean {\n return (\n !!(\n (\n 'WebTransportReceiveStream' in globalThis &&\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n typeof (globalThis as any)['WebTransportReceiveStream'] === 'function'\n )\n // @ts-expect-error WebTransportReceiveStream is not in TypeScript yet\n ) && value instanceof globalThis.WebTransportReceiveStream\n );\n}\n\nfunction isWebTransportSendStream(value: unknown): boolean {\n return (\n !!(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ('WebTransportSendStream' in globalThis && typeof (globalThis as any)['WebTransportSendStream'] === 'function')\n // @ts-expect-error WebTransportSendStream is not in TypeScript yet\n ) && value instanceof globalThis.WebTransportSendStream\n );\n}\n\nfunction isWritableStream(value: unknown): value is WritableStream {\n return !!('WritableStream' in globalThis) && value instanceof WritableStream;\n}\n\nfunction isTransferable(value: unknown): boolean {\n return (\n isArrayBuffer(value) ||\n isAudioData(value) ||\n isImageBitmap(value) ||\n isMediaSourceHandle(value) ||\n isMediaStreamTrack(value) ||\n isMessagePort(value) ||\n isMIDIAccess(value) ||\n isOffscreenCanvas(value) ||\n isReadableStream(value) ||\n isRTCDataChannel(value) ||\n isTransformStream(value) ||\n isVideoFrame(value) ||\n isWebTransportReceiveStream(value) ||\n isWebTransportSendStream(value) ||\n isWritableStream(value)\n );\n}\n\nexport default isTransferable;\n","import { workthru } from 'workthru';\nimport isTransferable from './isTransferable.ts';\n\nexport default function getAllTransfer(data: unknown): Transferable[] {\n const transferSet = new Set<Transferable>();\n\n workthru(data, value => {\n if (isTransferable(value)) {\n transferSet.add(value as Transferable);\n }\n\n return value;\n });\n\n return Array.from(transferSet);\n}\n","// Naming is from https://www.w3.org/History/1992/nfs_dxcern_mirror/rpc/doc/Introduction/HowItWorks.html.\n\nimport getAllTransfer from './getAllTransfer.ts';\nimport type { CallInit, ClientStub, ServerStub, Subroutine } from './types.ts';\n\nconst ABORT = 'ABORT';\nconst CALL = 'CALL';\nconst REJECT = 'REJECT';\nconst RESOLVE = 'RESOLVE';\n\ntype RPCCallMessage<T extends Subroutine> = [typeof CALL, ...Parameters<T>];\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype RPCRejectMessage = [typeof REJECT, any];\ntype RPCResolveMessage<T extends Subroutine> = [typeof RESOLVE, Awaited<ReturnType<T>>];\n\n/**\n * Binds a function to a `MessagePort` in RPC fashion and/or create a RPC function stub connected to a `MessagePort`.\n *\n * In a traditional RPC setting:\n *\n * - server should call this function with `fn` argument, the returned function should be ignored;\n * - client should call this function without `fn` argument, the returned function is the stub to call the server.\n *\n * This function supports bidirectional RPC when both sides are passing the `fn` argument.\n *\n * When calling the returned function stub, the arguments and return value are sent over `MessagePort`.\n * They would be sent by the underlying structured clone algorithm provided by the `MessagePort` implementation.\n * Transferable are auto-populated and will be passed to `MessagePort.postMessage` call.\n *\n * The returned stub has a variant `withOptions` for passing abort signal.\n *\n * @param {MessagePort} port - The `MessagePort` object to send the calls. The underlying `MessageChannel` must be exclusively used by this function only.\n * @param {Function} fn - The function to invoke. If not set, this RPC cannot be invoked by the other side of `MessagePort`.\n *\n * @returns An asynchronous function, when called, will invoke the function on the other side of `MessagePort`.\n */\nfunction messagePortRPC<C extends Subroutine>(port: MessagePort): ClientStub<C>;\n\nfunction messagePortRPC<C extends Subroutine, S extends Subroutine = C>(\n port: MessagePort,\n fn: ServerStub<S>\n): ClientStub<C>;\n\nfunction messagePortRPC<C extends Subroutine, S extends Subroutine = C>(\n port: MessagePort,\n fn: ServerStub<S>,\n options: { signal: AbortSignal }\n): ClientStub<C>;\n\nfunction messagePortRPC<C extends Subroutine, S extends Subroutine = C>(\n port: MessagePort,\n fn?: ServerStub<S>\n): ClientStub<C> {\n // We cannot neuter the input port because it would cause memory leak:\n // - We can neuter a port by passing it through Structured Clone Algorithm so the input port will become non-functional\n // - After a port is neutered, closing the neutered port will not close the cloned port\n // - Thus, the port owner will no longer able to close the port\n // - This defeated our philosophy: whoever pass a resources to a function, should own the resources unless it is intentional and no other workarounds\n\n type ClientSubroutineParameters = Parameters<C>;\n type ClientSubroutineReturnValue = Awaited<ReturnType<C>>;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const handleMessage = (event: MessageEvent<RPCCallMessage<S>>): void => {\n const data = event.data as RPCCallMessage<S> | undefined;\n\n if (Array.isArray(data) && data[0] === CALL) {\n event.stopImmediatePropagation();\n\n const [returnPort] = event.ports;\n\n if (!returnPort) {\n throw new Error('RPCCallMessage must contains a port.');\n }\n\n if (fn) {\n (async () => {\n const abortController = new AbortController();\n\n try {\n returnPort.onmessage = ({ data }) => Array.isArray(data) && data[0] === ABORT && abortController.abort();\n\n const result = await fn.call({ signal: abortController.signal }, ...(data.slice(1) as Parameters<S>));\n\n returnPort.postMessage([RESOLVE, result], getAllTransfer(result));\n } catch (error) {\n returnPort.postMessage([REJECT, error]);\n } finally {\n returnPort.close();\n }\n })();\n } else {\n returnPort.postMessage([\n REJECT,\n new Error(\n 'No function was registered on this RPC. This is probably calling a client which do not implement the function.'\n )\n ]);\n returnPort.close();\n }\n }\n };\n\n port.addEventListener('message', handleMessage);\n port.start();\n\n const createWithOptions =\n (init: CallInit): ((...args: ClientSubroutineParameters) => Promise<ClientSubroutineReturnValue>) =>\n (...args) => {\n return new Promise<ClientSubroutineReturnValue>((resolve, reject) => {\n const { port1, port2 } = new MessageChannel();\n\n port1.onmessage = event => {\n const data = event.data as RPCRejectMessage | RPCResolveMessage<C>;\n\n if (data[0] === RESOLVE) {\n resolve(data[1]);\n } else {\n reject(data[1]);\n }\n\n port1.close();\n };\n\n init?.signal?.addEventListener('abort', () => {\n port1.postMessage([ABORT]);\n port1.close();\n\n reject(new Error('Aborted.'));\n });\n\n const transfer = getAllTransfer(args);\n\n port.postMessage([CALL, ...args] satisfies RPCCallMessage<C>, [port2, ...transfer]);\n });\n };\n\n const stub = createWithOptions({}) as ClientStub<C>;\n\n stub.withOptions = createWithOptions;\n\n return stub;\n}\n\nexport default messagePortRPC;\n","// Naming is from https://www.w3.org/History/1992/nfs_dxcern_mirror/rpc/doc/Introduction/HowItWorks.html.\n\nimport messagePortRPC from './messagePortRPC.ts';\n\nconst GENERATE = 'GENERATOR_GENERATE';\n\ntype ClientDisposeReason = 'abort' | 'dispose' | 'done';\n\n// type GeneratorSubroutine<TArgs extends unknown[] = any[], T = unknown, TReturn = any, TNext = unknown> = (\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype GeneratorSubroutine = (...args: any[]) => AsyncGenerator | Generator | AsyncIterator<unknown> | Iterator<unknown>;\ntype RPCGeneratorGenerateMessage<T extends GeneratorSubroutine> = [\n typeof GENERATE,\n Readonly<{\n asyncDispose: MessagePort;\n next: MessagePort;\n return: MessagePort;\n throw: MessagePort;\n }>,\n ...Parameters<T>\n];\n\ntype CallInit = {\n signal?: AbortSignal;\n transfer?: Transferable[];\n};\n\ntype NextOfGenerator<T extends AsyncGenerator | Generator | AsyncIterator<unknown> | Iterator<unknown>> =\n T extends AsyncGenerator<unknown, unknown, infer U> ? U : T extends Generator<unknown, unknown, infer V> ? V : never;\ntype ReturnOfGenerator<T extends AsyncGenerator | Generator | AsyncIterator<unknown> | Iterator<unknown>> =\n T extends AsyncGenerator<unknown, infer U> ? U : T extends Generator<unknown, infer V> ? V : never;\ntype YieldOfGenerator<T extends AsyncGenerator | Generator | AsyncIterator<unknown> | Iterator<unknown>> =\n T extends AsyncGenerator<infer U>\n ? U\n : T extends Generator<infer U>\n ? U\n : T extends AsyncIterator<infer U>\n ? U\n : T extends Iterator<infer U>\n ? U\n : never;\n\n// Regardless whether T returns Promise or not, the client stub must return Promise.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ClientGeneratorStub<T extends GeneratorSubroutine> = (\n ...args: Parameters<T>\n) => AsyncGenerator<YieldOfGenerator<ReturnType<T>>, ReturnOfGenerator<ReturnType<T>>, NextOfGenerator<ReturnType<T>>>;\n\ntype ClientGeneratorStubWithExtra<T extends GeneratorSubroutine> = ClientGeneratorStub<T> & {\n /**\n * Creates a new stub with options.\n *\n * @param {AbortSignal} init.signal - Abort signal to abort the call to the stub.\n * @param {Transferable[]} init.transfer - Transfer ownership of objects specified in `args`.\n */\n withOptions: (init: CallInit) => ClientGeneratorStub<T>;\n};\n\ntype ServerStub<T extends GeneratorSubroutine> = (...args: Parameters<T>) => ReturnType<T>;\n\n/**\n * Binds a generator function to a `MessagePort` in RPC fashion and/or create a RPC function stub connected to a `MessagePort`.\n *\n * In a traditional RPC setting:\n *\n * - server should call this generator function with `fn` argument, the returned function should be ignored;\n * - client should call this generator function without `fn` argument, the returned function is the stub to call the server.\n *\n * This function supports bidirectional RPC when both sides are passing the `fn` argument.\n *\n * When calling the returned function stub, the arguments and return value are transferred over `MessagePort`.\n * Thus, they will be cloned by the underlying structured clone algorithm.\n *\n * The returned stub has a variant `withOptions` for passing transferables and abort signal.\n *\n * Notes: if `next()` is used on the client stub and did not iterate until `{ done: true }`, caller must use the `withOptions({ signal: AbortSignal })`\n * to release resources.\n *\n * @param {MessagePort} port - The `MessagePort` object to send the calls. The underlying `MessageChannel` must be exclusively used by this function only.\n * @param {Function} fn - The generator function to invoke. If not set, this RPC cannot be invoked by the other side of `MessagePort`.\n *\n * @returns An asynchronous generator function, when called, will invoke the generator function on the other side of `MessagePort`.\n */\nexport default function forGenerator<C extends GeneratorSubroutine>(port: MessagePort): ClientGeneratorStubWithExtra<C>;\n\nexport default function forGenerator<C extends GeneratorSubroutine, S extends GeneratorSubroutine = C>(\n port: MessagePort,\n fn: ServerStub<S>\n): ClientGeneratorStubWithExtra<C>;\n\nexport default function forGenerator<C extends GeneratorSubroutine, S extends GeneratorSubroutine = C>(\n port: MessagePort,\n fn: ServerStub<S>,\n options: { signal: AbortSignal }\n): ClientGeneratorStubWithExtra<C>;\n\nexport default function forGenerator<C extends GeneratorSubroutine, S extends GeneratorSubroutine = C>(\n port: MessagePort,\n fn?: ServerStub<S>\n): ClientGeneratorStubWithExtra<C> {\n // We cannot neuter the input port because it would cause memory leak:\n // - We can neuter a port by passing it through Structured Clone Algorithm so the input port will become non-functional\n // - After a port is neutered, closing the neutered port will not close the cloned port\n // - Thus, the port owner will no longer able to close the port\n // - This defeated our philosophy: whoever pass a resources to a function, should own the resources unless it is intentional and no other workarounds\n\n type ClientSubroutineParameters = Parameters<C>;\n type ClientSubroutineYield = YieldOfGenerator<ReturnType<C>>;\n type ClientSubroutineReturn = ReturnOfGenerator<ReturnType<C>>;\n type ClientSubroutineNext = NextOfGenerator<ReturnType<C>>;\n type ClientSubroutineIteratorResult = IteratorResult<ClientSubroutineYield, ClientSubroutineReturn>;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const handleMessage = (event: MessageEvent<RPCGeneratorGenerateMessage<S>>): void => {\n const data = event.data as RPCGeneratorGenerateMessage<S> | undefined;\n\n if (Array.isArray(data) && data[0] === GENERATE) {\n event.stopImmediatePropagation();\n\n const [_, messagePorts, ...args] = data;\n\n const serverClosePorts = () => {\n messagePorts.asyncDispose.close();\n messagePorts.next.close();\n messagePorts.return.close();\n messagePorts.throw.close();\n };\n\n if (!fn) {\n // TODO: Throwing exception in onMessage is no-op, need to fix.\n console.warn(\n '`message-port-rpc`: No function was registered on this RPC. This is probably calling a client which do not implement the function.'\n );\n\n serverClosePorts();\n\n return;\n }\n\n const generator = fn(...args);\n\n messagePortRPC(messagePorts.next, async (...args: ClientSubroutineNext) => {\n const result = await generator.next(...args);\n\n // Automatically close server ports when iteration is done.\n // Client will fake the next()/return()/throw() output after iteration is done.\n result.done && serverClosePorts();\n\n return result;\n });\n\n // This is a slight deviation from the actual `Iterator`.\n // In the original approach, `Iterator.return` is not defined.\n // In our approach, the client stub does not know if the server stub has `Iterator.return` defined or not, instead, we simply return `{ done: true }`.\n messagePortRPC(messagePorts.return, async value => {\n return (await generator.return?.(value)) ?? { done: true };\n });\n\n messagePortRPC(messagePorts.throw, async error => {\n return (await generator.throw?.(error)) ?? Promise.reject(error);\n });\n\n messagePortRPC(messagePorts.asyncDispose, async (): Promise<void> => {\n try {\n const symbolAsyncDispose: typeof Symbol.asyncDispose =\n Symbol.asyncDispose || Symbol.for('Symbol.asyncDispose');\n const symbolDispose: typeof Symbol.dispose = Symbol.dispose || Symbol.for('Symbol.dispose');\n\n symbolAsyncDispose in generator\n ? await generator[symbolAsyncDispose]()\n : symbolDispose in generator && generator[symbolDispose]();\n } finally {\n serverClosePorts();\n }\n });\n }\n };\n\n port.addEventListener('message', handleMessage);\n port.start();\n\n const createWithOptions = (\n init: CallInit\n ): ((\n ...args: ClientSubroutineParameters\n ) => AsyncGenerator<ClientSubroutineYield, ClientSubroutineReturn, ClientSubroutineNext>) => {\n return (...args: ClientSubroutineParameters) => {\n const { port1: asyncDisposePort1, port2: asyncDisposePort2 } = new MessageChannel();\n const { port1: nextPort1, port2: nextPort2 } = new MessageChannel();\n const { port1: returnPort1, port2: returnPort2 } = new MessageChannel();\n const { port1: throwPort1, port2: throwPort2 } = new MessageChannel();\n\n const subInit = { signal: init.signal };\n\n const asyncDisposeRPC = messagePortRPC<() => void>(asyncDisposePort1).withOptions(subInit);\n\n const nextRPC =\n messagePortRPC<(next: ClientSubroutineNext | void) => ClientSubroutineIteratorResult>(nextPort1).withOptions(\n subInit\n );\n\n const returnRPC =\n messagePortRPC<(returnValue: ClientSubroutineReturn) => ClientSubroutineIteratorResult>(\n returnPort1\n ).withOptions(subInit);\n\n const throwRPC =\n messagePortRPC<(error: unknown) => ClientSubroutineIteratorResult>(throwPort1).withOptions(subInit);\n\n const callAsyncDispose = async (disposeReason: ClientDisposeReason) => {\n if (isDone(false)) {\n return;\n }\n\n // AsyncDispose has never been called.\n if (disposeReason === 'abort' || disposeReason === 'dispose') {\n // Only call server for AsyncDispose if the user intentionally abort or dispose.\n // Otherwise, the server-side object should have already disposed due to \"done\" and \"error\".\n await asyncDisposeRPC();\n }\n\n if (disposeReason === 'done') {\n isDone = () => true;\n } else {\n disposeReason satisfies 'abort' | 'dispose';\n\n isDone = (throwOnDispose = true) => {\n if (throwOnDispose === true) {\n // Already disposed, the MessagePort are all closed, return previous result.\n throw new Error('This generator has been disposed.');\n }\n\n return true;\n };\n }\n\n asyncDisposePort1.close();\n asyncDisposePort2.close();\n nextPort1.close();\n nextPort2.close();\n returnPort1.close();\n returnPort2.close();\n throwPort1.close();\n throwPort2.close();\n };\n\n let isDone: (throwOnDispose?: boolean | undefined) => boolean = () => false;\n\n const generator: AsyncGenerator<ClientSubroutineYield, ClientSubroutineReturn, ClientSubroutineNext> = {\n next: async (value: NextOfGenerator<ReturnType<C>> | void) => {\n if (isDone()) {\n // Return type of any generators should allow `undefined`.\n // For example, after the generator is exhausted, the return value will be `undefined`.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return { done: true, value: undefined } satisfies IteratorReturnResult<undefined> as any;\n }\n\n const result = await nextRPC(value);\n\n if (result.done) {\n await callAsyncDispose('done');\n }\n\n return result;\n },\n return: async (value: ReturnOfGenerator<ReturnType<C>>) => {\n if (isDone()) {\n return { done: true, value };\n }\n\n return await returnRPC(value);\n },\n throw: async (error: unknown) => {\n if (isDone()) {\n throw error;\n }\n\n return await throwRPC(error);\n },\n // Ponyfills for Symbol.asyncDispose\n [Symbol.asyncDispose || Symbol.for('Symbol.asyncDispose')]: async () => {\n await callAsyncDispose('dispose');\n },\n [Symbol.asyncIterator]: () => generator\n };\n\n port.postMessage(\n [\n GENERATE,\n { asyncDispose: asyncDisposePort2, next: nextPort2, return: returnPort2, throw: throwPort2 },\n ...args\n ] satisfies RPCGeneratorGenerateMessage<C>,\n [...(init.transfer || []), asyncDisposePort2, nextPort2, returnPort2, throwPort2]\n );\n\n init.signal?.addEventListener('abort', () => callAsyncDispose('abort').catch(() => {}), { once: true });\n\n return generator;\n };\n };\n\n const stub = createWithOptions({}) as ClientGeneratorStubWithExtra<C>;\n\n stub.withOptions = createWithOptions;\n\n return stub;\n}\n","import { messagePortRPC as rpc } from 'message-port-rpc';\nimport type { InferClient } from '../types/internal/InferClient.ts';\nimport type { InferHandshake } from '../types/internal/InferHandshake.ts';\nimport type { StubDeclaration } from '../types/StubDeclaration.ts';\nimport type { StubImplementation } from '../types/StubImplementation.ts';\n\nfunction lazyStub<TArgs extends unknown[], TSyncReturn extends unknown>(\n fnFactory: () => Promise<(...args: TArgs) => Promise<TSyncReturn>>\n): (...args: TArgs) => Promise<TSyncReturn> {\n let fnPromise: Promise<(...args: TArgs) => Promise<TSyncReturn>> | undefined;\n\n return async (...args) => (await (fnPromise ?? (fnPromise = fnFactory())))(...args);\n}\n\nfunction createClientStub<T extends StubDeclaration<S>, S extends StubImplementation>(\n declaration: Pick<T, 'keys'>,\n messagePort: MessagePort\n): InferClient<S> {\n type Handshake = InferHandshake<S>;\n\n const clientStubMap = new Map<keyof S, (...args: unknown[]) => Promise<unknown>>();\n const handshakePromise = rpc<() => Handshake>(messagePort)();\n\n for (const key of declaration.keys) {\n clientStubMap.set(\n key,\n lazyStub(async () => rpc((await handshakePromise)[key]))\n );\n }\n\n return Object.fromEntries(clientStubMap.entries()) satisfies Record<\n string,\n (...args: unknown[]) => Promise<unknown>\n // TODO: We should not use \"as unknown\".\n > as unknown as InferClient<S>;\n}\n\nexport default createClientStub;\n"],"mappings":";AAcA,SAAS,UAAU,QAAa,aAAkC,QAA4B;AAC5F,MAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,QAAI,OAAO,IAAI,MAAM,GAAG;AACtB,aAAO,OAAO,IAAI,MAAM;IAC1B;AAEA,QAAI,YAAY,YAAY,MAAM;AAElC,QAAI,cAAc,QAAQ;AACxB,aAAO,IAAI,QAAQ,SAAS;AAE5B,aAAO;IACT;AAEA,WAAO,IAAI,QAAQ,MAAM;AAGzB,eAAW,SAAS,QAAQ;AAC1B,YAAM,QAAQ,OAAO,KAAK;AAC1B,YAAM,YAAY,UAAU,OAAO,aAAa,MAAM;AAEtD,UAAI,cAAc,OAAO;AACvB,YAAI,cAAc,QAAQ;AACxB,sBAAY,CAAC,GAAG,MAAM;QACxB;AAEA,kBAAU,KAAK,IAAI;MACrB;IACF;AAEA,WAAO,IAAI,QAAQ,SAAS;AAE5B,WAAO;EACT;AAEA,MAAI,OAAO,WAAW,YAAY,WAAW,MAAM;AACjD,QAAI,OAAO,IAAI,MAAM,GAAG;AACtB,aAAO,OAAO,IAAI,MAAM;IAC1B;AAEA,UAAM,YAAY,OAAO,eAAe,MAAM;AAE9C,QAAI,cAAc,QAAQ,cAAc,OAAO,WAAW;AACxD,YAAM,aAAa,YAAY,MAAM;AAErC,UAAI,eAAe,QAAQ;AACzB,eAAO,IAAI,QAAQ,UAAU;AAE7B,eAAO;MACT;AAEA,aAAO,IAAI,QAAQ,MAAM;AAEzB,YAAM,UAAU,OAAO,QAAQ,MAAM;AACrC,UAAI,UAAU;AAEd,iBAAW,CAAC,KAAK,KAAK,KAAK,SAAS;AAClC,cAAM,YAAY,UAAU,OAAO,aAAa,MAAM;AAEtD,YAAI,cAAc,OAAO;AACvB,cAAI,CAAC,SAAS;AACZ,sBAAU,IAAI,IAAI,OAAO;UAC3B;AAEA,kBAAQ,IAAI,KAAK,SAAS;QAC5B;MACF;AAEA,YAAM,aAAa,UAAU,OAAO,YAAY,QAAQ,QAAQ,CAAC,IAAI;AAErE,aAAO,IAAI,QAAQ,UAAU;AAE7B,aAAO;IACT;EACF;AAEA,SAAO,YAAY,MAAM;AAC3B;AA0BA,SAAS,SAAS,QAAa,aAAuC;AACpE,SAAO,UAAU,QAAQ,aAAa,oBAAI,IAAI,CAAC;AACjD;AAEA,IAAO,mBAAQ;ACzHf,SAAS,cAAc,OAAsC;AAC3D,SAAO,CAAC,EAAE,iBAAiB,eAAe,iBAAiB;AAC7D;AAEA,SAAS,YAAY,OAAoC;AACvD,SAAO,CAAC,EAAE,eAAe,eAAe,iBAAiB;AAC3D;AAEA,SAAS,cAAc,OAAsC;AAC3D,SAAO,CAAC,EAAE,iBAAiB,eAAe,iBAAiB;AAC7D;AAEA,SAAS,oBAAoB,OAA4C;AACvE,SAAO,CAAC,EAAE,uBAAuB,eAAe,iBAAiB;AACnE;AAEA,SAAS,mBAAmB,OAA2C;AACrE,SAAO,CAAC,EAAE,sBAAsB,eAAe,iBAAiB;AAClE;AAEA,SAAS,cAAc,OAAsC;AAC3D,SAAO,CAAC,EAAE,iBAAiB,eAAe,iBAAiB;AAC7D;AAEA,SAAS,aAAa,OAAqC;AACzD,SAAO,CAAC,EAAE,gBAAgB,eAAe,iBAAiB;AAC5D;AAEA,SAAS,kBAAkB,OAA0C;AACnE,SAAO,CAAC,EAAE,qBAAqB,eAAe,iBAAiB;AACjE;AAEA,SAAS,iBAAiB,OAAyC;AACjE,SAAO,CAAC,EAAE,oBAAoB,eAAe,iBAAiB;AAChE;AAEA,SAAS,iBAAiB,OAAyC;AACjE,SAAO,CAAC,EAAE,oBAAoB,eAAe,iBAAiB;AAChE;AAEA,SAAS,kBAAkB,OAA0C;AACnE,SAAO,CAAC,EAAE,qBAAqB,eAAe,iBAAiB;AACjE;AAEA,SAAS,aAAa,OAAqC;AACzD,SAAO,CAAC,EAAE,gBAAgB,eAAe,iBAAiB;AAC5D;AAEA,SAAS,4BAA4B,OAAyB;AAC5D,SACE,CAAC,EAEG,+BAA+B;EAE/B,OAAQ,WAAmB,2BAA2B,MAAM,eAG3D,iBAAiB,WAAW;AAErC;AAEA,SAAS,yBAAyB,OAAyB;AACzD,SACE,CAAC;GAEE,4BAA4B,cAAc,OAAQ,WAAmB,wBAAwB,MAAM,eAEjG,iBAAiB,WAAW;AAErC;AAEA,SAAS,iBAAiB,OAAyC;AACjE,SAAO,CAAC,EAAE,oBAAoB,eAAe,iBAAiB;AAChE;AAEA,SAAS,eAAe,OAAyB;AAC/C,SACE,cAAc,KAAK,KACnB,YAAY,KAAK,KACjB,cAAc,KAAK,KACnB,oBAAoB,KAAK,KACzB,mBAAmB,KAAK,KACxB,cAAc,KAAK,KACnB,aAAa,KAAK,KAClB,kBAAkB,KAAK,KACvB,iBAAiB,KAAK,KACtB,iBAAiB,KAAK,KACtB,kBAAkB,KAAK,KACvB,aAAa,KAAK,KAClB,4BAA4B,KAAK,KACjC,yBAAyB,KAAK,KAC9B,iBAAiB,KAAK;AAE1B;AAEA,IAAO,yBAAQ;AC5FA,SAAR,eAAgC,MAA+B;AACpE,QAAM,cAAc,oBAAI,IAAkB;AAE1C,mBAAS,MAAM,CAAA,UAAS;AACtB,QAAI,uBAAe,KAAK,GAAG;AACzB,kBAAY,IAAI,KAAqB;IACvC;AAEA,WAAO;EACT,CAAC;AAED,SAAO,MAAM,KAAK,WAAW;AAC/B;ACVA,IAAM,QAAQ;AACd,IAAM,OAAO;AACb,IAAM,SAAS;AACf,IAAM,UAAU;AAyChB,SAAS,eACP,MACA,IACe;AAWf,QAAM,gBAAgB,CAAC,UAAiD;AACtE,UAAM,OAAO,MAAM;AAEnB,QAAI,MAAM,QAAQ,IAAI,KAAK,KAAK,CAAC,MAAM,MAAM;AAC3C,YAAM,yBAAyB;AAE/B,YAAM,CAAC,UAAU,IAAI,MAAM;AAE3B,UAAI,CAAC,YAAY;AACf,cAAM,IAAI,MAAM,sCAAsC;MACxD;AAEA,UAAI,IAAI;AACN,SAAC,YAAY;AACX,gBAAM,kBAAkB,IAAI,gBAAgB;AAE5C,cAAI;AACF,uBAAW,YAAY,CAAC,EAAE,MAAAA,MAAK,MAAM,MAAM,QAAQA,KAAI,KAAKA,MAAK,CAAC,MAAM,SAAS,gBAAgB,MAAM;AAEvG,kBAAM,SAAS,MAAM,GAAG,KAAK,EAAE,QAAQ,gBAAgB,OAAO,GAAG,GAAI,KAAK,MAAM,CAAC,CAAmB;AAEpG,uBAAW,YAAY,CAAC,SAAS,MAAM,GAAG,eAAe,MAAM,CAAC;UAClE,SAAS,OAAO;AACd,uBAAW,YAAY,CAAC,QAAQ,KAAK,CAAC;UACxC,UAAA;AACE,uBAAW,MAAM;UACnB;QACF,GAAG;MACL,OAAO;AACL,mBAAW,YAAY;UACrB;UACA,IAAI;YACF;UACF;QACF,CAAC;AACD,mBAAW,MAAM;MACnB;IACF;EACF;AAEA,OAAK,iBAAiB,WAAW,aAAa;AAC9C,OAAK,MAAM;AAEX,QAAM,oBACJ,CAAC,SACD,IAAI,SAAS;AACX,WAAO,IAAI,QAAqC,CAAC,SAAS,WAAW;AACnE,YAAM,EAAE,OAAO,MAAM,IAAI,IAAI,eAAe;AAE5C,YAAM,YAAY,CAAA,UAAS;AACzB,cAAM,OAAO,MAAM;AAEnB,YAAI,KAAK,CAAC,MAAM,SAAS;AACvB,kBAAQ,KAAK,CAAC,CAAC;QACjB,OAAO;AACL,iBAAO,KAAK,CAAC,CAAC;QAChB;AAEA,cAAM,MAAM;MACd;AAEA,YAAM,QAAQ,iBAAiB,SAAS,MAAM;AAC5C,cAAM,YAAY,CAAC,KAAK,CAAC;AACzB,cAAM,MAAM;AAEZ,eAAO,IAAI,MAAM,UAAU,CAAC;MAC9B,CAAC;AAED,YAAM,WAAW,eAAe,IAAI;AAEpC,WAAK,YAAY,CAAC,MAAM,GAAG,IAAI,GAA+B,CAAC,OAAO,GAAG,QAAQ,CAAC;IACpF,CAAC;EACH;AAEF,QAAM,OAAO,kBAAkB,CAAC,CAAC;AAEjC,OAAK,cAAc;AAEnB,SAAO;AACT;AAEA,IAAO,yBAAQ;;;AE1If,SAAS,SACP,WAC0C;AAC1C,MAAI;AAEJ,SAAO,UAAU,UAAU,OAAO,cAAc,YAAY,UAAU,KAAK,GAAG,IAAI;AACpF;AAEA,SAAS,iBACP,aACA,aACgB;AAGhB,QAAM,gBAAgB,oBAAI,IAAuD;AACjF,QAAM,mBAAmB,uBAAqB,WAAW,EAAE;AAE3D,aAAW,OAAO,YAAY,MAAM;AAClC,kBAAc;AAAA,MACZ;AAAA,MACA,SAAS,YAAY,wBAAK,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAAA,IACzD;AAAA,EACF;AAEA,SAAO,OAAO,YAAY,cAAc,QAAQ,CAAC;AAKnD;AAEA,IAAO,2BAAQ;","names":["data"]}
@@ -0,0 +1,17 @@
1
+ import { BrowsingContext as BrowsingContext$1, WebDriver } from 'selenium-webdriver';
2
+
3
+ type BrowsingContext = Awaited<ReturnType<typeof BrowsingContext$1>>;
4
+
5
+ type StubEnvironment = {
6
+ readonly browsingContext: BrowsingContext;
7
+ readonly webDriver: WebDriver;
8
+ };
9
+
10
+ type StubImplementation = Record<string, (...args: unknown[]) => unknown>;
11
+
12
+ type StubDeclaration<T extends StubImplementation> = {
13
+ readonly keys: readonly (keyof T)[];
14
+ implement(environment: StubEnvironment): T;
15
+ };
16
+
17
+ export type { BrowsingContext, StubDeclaration, StubEnvironment, StubImplementation };
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/rpc.d.mts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/rpc.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/rpc.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ // src/index.ts
4
+ console.log("Hello, World!");
5
+ //# sourceMappingURL=rpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["console.log('Hello, World!');\n"],"mappings":";;;AAAA,QAAQ,IAAI,eAAe;","names":[]}
package/dist/rpc.mjs ADDED
@@ -0,0 +1,3 @@
1
+ // src/index.ts
2
+ console.log("Hello, World!");
3
+ //# sourceMappingURL=rpc.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["console.log('Hello, World!');\n"],"mappings":";AAAA,QAAQ,IAAI,eAAe;","names":[]}
@@ -0,0 +1,19 @@
1
+ import { BrowsingContext as BrowsingContext$1, WebDriver } from 'selenium-webdriver';
2
+
3
+ type BrowsingContext = Awaited<ReturnType<typeof BrowsingContext$1>>;
4
+
5
+ type StubEnvironment = {
6
+ readonly browsingContext: BrowsingContext;
7
+ readonly webDriver: WebDriver;
8
+ };
9
+
10
+ type StubImplementation = Record<string, (...args: unknown[]) => unknown>;
11
+
12
+ type StubDeclaration<T extends StubImplementation> = {
13
+ readonly keys: readonly (keyof T)[];
14
+ implement(environment: StubEnvironment): T;
15
+ };
16
+
17
+ declare function listen<T extends StubDeclaration<S>, S extends StubImplementation>(declaration: T, environment: StubEnvironment, messagePort: MessagePort): () => void;
18
+
19
+ export { listen as implementation };
@@ -0,0 +1,54 @@
1
+ // src/server/listen.ts
2
+ import { messagePortRPC as rpc2 } from "message-port-rpc";
3
+
4
+ // src/server/private/createHandshakeStub.ts
5
+ import { messagePortRPC as rpc } from "message-port-rpc";
6
+ function createHandshakeStub(declaration, implementation) {
7
+ const openedPorts = /* @__PURE__ */ new Set();
8
+ return {
9
+ fn() {
10
+ const handshakeResultMap = /* @__PURE__ */ new Map();
11
+ for (const key of declaration.keys) {
12
+ const value = implementation[key];
13
+ if (value) {
14
+ const { port1, port2 } = new MessageChannel();
15
+ rpc(port1, value);
16
+ handshakeResultMap.set(key, port2);
17
+ openedPorts.add(port1);
18
+ }
19
+ }
20
+ return Object.fromEntries(handshakeResultMap.entries());
21
+ },
22
+ teardown() {
23
+ for (const port of openedPorts) {
24
+ port.close();
25
+ }
26
+ }
27
+ };
28
+ }
29
+ var createHandshakeStub_default = createHandshakeStub;
30
+
31
+ // src/server/private/verifyImplementation.ts
32
+ function verifyImplementation(declaration, implementation) {
33
+ const declaredKeySet = new Set(declaration.keys);
34
+ const implementedKeySet = new Set(Object.getOwnPropertyNames(implementation));
35
+ const differences = declaredKeySet.symmetricDifference(implementedKeySet);
36
+ if (differences.size) {
37
+ throw new Error(`Keys in stub declaration does not match implementation: ${Array.from(differences).join(", ")}`);
38
+ }
39
+ }
40
+ var verifyImplementation_default = verifyImplementation;
41
+
42
+ // src/server/listen.ts
43
+ function listen(declaration, environment, messagePort) {
44
+ const implementation = declaration.implement(environment);
45
+ verifyImplementation_default(declaration, implementation);
46
+ const { fn, teardown } = createHandshakeStub_default(declaration, implementation);
47
+ rpc2(messagePort, fn);
48
+ return teardown;
49
+ }
50
+ var listen_default = listen;
51
+ export {
52
+ listen_default as implementation
53
+ };
54
+ //# sourceMappingURL=server.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/listen.ts","../src/server/private/createHandshakeStub.ts","../src/server/private/verifyImplementation.ts"],"sourcesContent":["import { messagePortRPC as rpc } from 'message-port-rpc';\nimport type { StubDeclaration } from '../types/StubDeclaration.ts';\nimport type { StubEnvironment } from '../types/StubEnvironment.ts';\nimport type { StubImplementation } from '../types/StubImplementation.ts';\nimport createHandshakeStub from './private/createHandshakeStub.ts';\nimport verifyImplementation from './private/verifyImplementation.ts';\n\nfunction listen<T extends StubDeclaration<S>, S extends StubImplementation>(\n declaration: T,\n environment: StubEnvironment,\n messagePort: MessagePort\n): () => void {\n const implementation: S = declaration.implement(environment);\n\n verifyImplementation(declaration, implementation);\n\n const { fn, teardown } = createHandshakeStub(declaration, implementation);\n\n rpc(messagePort, fn);\n\n return teardown;\n}\n\nexport default listen;\n","import { messagePortRPC as rpc } from 'message-port-rpc';\nimport type { InferHandshake } from '../../types/internal/InferHandshake.ts';\nimport type { StubDeclaration } from '../../types/StubDeclaration.ts';\nimport type { StubImplementation } from '../../types/StubImplementation.ts';\n\nfunction createHandshakeStub<T extends StubDeclaration<S>, S extends StubImplementation>(\n declaration: T,\n implementation: S\n): {\n readonly fn: () => InferHandshake<S>;\n readonly teardown: () => void;\n} {\n const openedPorts = new Set<MessagePort>();\n\n return {\n fn() {\n const handshakeResultMap = new Map<keyof S, MessagePort>();\n\n // Prefer StubDeclaration.keys over Object.getOwnPropertyNames(stubDeclaration).\n for (const key of declaration.keys) {\n const value = implementation[key];\n\n // We already verified the implementation in `listen()`.\n if (value) {\n const { port1, port2 } = new MessageChannel();\n\n rpc(port1, value);\n handshakeResultMap.set(key, port2);\n\n openedPorts.add(port1);\n }\n }\n\n return Object.fromEntries(handshakeResultMap.entries()) satisfies Record<\n string,\n MessagePort\n > as InferHandshake<S>;\n },\n teardown() {\n for (const port of openedPorts) {\n port.close();\n }\n }\n };\n}\n\nexport default createHandshakeStub;\n","import type { StubDeclaration } from '../../types/StubDeclaration';\nimport type { StubImplementation } from '../../types/StubImplementation';\n\nfunction verifyImplementation<T extends StubDeclaration<S>, S extends StubImplementation>(\n declaration: T,\n implementation: S\n) {\n const declaredKeySet = new Set(declaration.keys);\n const implementedKeySet = new Set(Object.getOwnPropertyNames(implementation));\n\n const differences = declaredKeySet.symmetricDifference(implementedKeySet);\n\n if (differences.size) {\n throw new Error(`Keys in stub declaration does not match implementation: ${Array.from(differences).join(', ')}`);\n }\n}\n\nexport default verifyImplementation;\n"],"mappings":";AAAA,SAAS,kBAAkBA,YAAW;;;ACAtC,SAAS,kBAAkB,WAAW;AAKtC,SAAS,oBACP,aACA,gBAIA;AACA,QAAM,cAAc,oBAAI,IAAiB;AAEzC,SAAO;AAAA,IACL,KAAK;AACH,YAAM,qBAAqB,oBAAI,IAA0B;AAGzD,iBAAW,OAAO,YAAY,MAAM;AAClC,cAAM,QAAQ,eAAe,GAAG;AAGhC,YAAI,OAAO;AACT,gBAAM,EAAE,OAAO,MAAM,IAAI,IAAI,eAAe;AAE5C,cAAI,OAAO,KAAK;AAChB,6BAAmB,IAAI,KAAK,KAAK;AAEjC,sBAAY,IAAI,KAAK;AAAA,QACvB;AAAA,MACF;AAEA,aAAO,OAAO,YAAY,mBAAmB,QAAQ,CAAC;AAAA,IAIxD;AAAA,IACA,WAAW;AACT,iBAAW,QAAQ,aAAa;AAC9B,aAAK,MAAM;AAAA,MACb;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,8BAAQ;;;AC3Cf,SAAS,qBACP,aACA,gBACA;AACA,QAAM,iBAAiB,IAAI,IAAI,YAAY,IAAI;AAC/C,QAAM,oBAAoB,IAAI,IAAI,OAAO,oBAAoB,cAAc,CAAC;AAE5E,QAAM,cAAc,eAAe,oBAAoB,iBAAiB;AAExE,MAAI,YAAY,MAAM;AACpB,UAAM,IAAI,MAAM,2DAA2D,MAAM,KAAK,WAAW,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,EACjH;AACF;AAEA,IAAO,+BAAQ;;;AFVf,SAAS,OACP,aACA,aACA,aACY;AACZ,QAAM,iBAAoB,YAAY,UAAU,WAAW;AAE3D,+BAAqB,aAAa,cAAc;AAEhD,QAAM,EAAE,IAAI,SAAS,IAAI,4BAAoB,aAAa,cAAc;AAExE,EAAAC,KAAI,aAAa,EAAE;AAEnB,SAAO;AACT;AAEA,IAAO,iBAAQ;","names":["rpc","rpc"]}
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@onting/rpc",
3
+ "version": "0.0.0-0",
4
+ "description": "",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "scripts": {
9
+ "build": "tsup",
10
+ "bump": "npm run bump:prod && npm run bump:dev",
11
+ "bump:dev": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localPeerDependencies // {}) as $L | (.devDependencies // {}) | to_entries | map(select(.key as $K | $L | has($K) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true",
12
+ "bump:prod": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localPeerDependencies // {}) as $L | (.dependencies // {}) | to_entries | map(select(.key as $K | $L | has($K) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true",
13
+ "init": "npm run init:dev --if-present && npm run init:prod --if-present",
14
+ "init:dev": "npm install --save-dev @onting/common@0.0.0-0 @happy-dom/global-registrator @testduet/given-when-then @tsconfig/recommended @tsconfig/strictest @types/node esbuild escape-string-regexp expect prettier publint tsup typescript",
15
+ "postscaffold": "npm run postscaffold:dev --if-present && npm run postscaffold:prod --if-present",
16
+ "postscaffold:dev": "npm install --save-dev @onting/test-harness@0.0.0-0 @tsconfig/recommended @tsconfig/strictest @types/node escape-string-regexp expect publint tsup typescript",
17
+ "precommit": "npm run precommit:eslint && npm run precommit:publint && npm run precommit:typescript:production && npm run precommit:typescript:test",
18
+ "precommit:eslint": "ESLINT_USE_FLAT_CONFIG=false eslint ./src/",
19
+ "precommit:publint": "publint",
20
+ "precommit:typescript:production": "tsc --noEmit --project ./src/tsconfig.precommit.production.json",
21
+ "precommit:typescript:test": "tsc --noEmit --project ./src/tsconfig.precommit.test.json",
22
+ "prepack": "cp ../../CHANGELOG.md . && cp ../../LICENSE . && cp ../../README.md .",
23
+ "start": "npm run build -- --onSuccess \"touch ../pages/package.json\" --watch",
24
+ "switch": "cat package.json | jq --arg SWITCH_NAME $SWITCH_NAME -r '(.[\"switch:\" + $SWITCH_NAME] // {}) as $TEMPLATE | .devDependencies += ($TEMPLATE.devDependencies // {}) | .dependencies += ($TEMPLATE.dependencies // {})' | tee ./package.json.tmp && mv ./package.json.tmp ./package.json",
25
+ "test": "npm run test:unit && npm run test:types",
26
+ "test:types": "node --experimental-strip-types --import @onting/test-harness/test-types.import --test **/*.test-types.ts",
27
+ "test:unit": "node --experimental-strip-types --experimental-test-coverage --experimental-test-isolation=none --test"
28
+ },
29
+ "files": [
30
+ "./*.js",
31
+ "./dist/"
32
+ ],
33
+ "main": "./dist/rpc.js",
34
+ "typings": "./dist/rpc.d.ts",
35
+ "exports": {
36
+ ".": {
37
+ "types": "./dist/rpc.js",
38
+ "default": "./dist/rpc.d.ts"
39
+ }
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/teamonting/rpc.git"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/teamonting/rpc/issues"
47
+ },
48
+ "homepage": "https://github.com/teamonting/rpc#readme",
49
+ "localPeerDependencies": {
50
+ "@onting/common": "0.0.0-0",
51
+ "@onting/test-harness": "0.0.0-0"
52
+ },
53
+ "author": "William Wong (https://github.com/teamonting)",
54
+ "devDependencies": {
55
+ "@happy-dom/global-registrator": "^20.10.3",
56
+ "@onting/common": "^0.0.0-0",
57
+ "@onting/test-harness": "^0.0.0-0",
58
+ "@testduet/given-when-then": "^0.1.0",
59
+ "@tsconfig/recommended": "^1.0.13",
60
+ "@tsconfig/strictest": "^2.0.8",
61
+ "@types/node": "^25.9.3",
62
+ "esbuild": "^0.28.1",
63
+ "escape-string-regexp": "^5.0.0",
64
+ "expect": "^30.4.1",
65
+ "prettier": "^3.8.4",
66
+ "publint": "^0.3.21",
67
+ "tsup": "^8.5.1",
68
+ "typescript": "^6.0.3"
69
+ },
70
+ "license": "MIT",
71
+ "pinDependencies": {}
72
+ }