Skip to content

Commit beddb6a

Browse files
committed
add transferables support
1 parent 5552536 commit beddb6a

File tree

2 files changed

+60
-18
lines changed

2 files changed

+60
-18
lines changed

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import guest from "./guest";
22
import host from "./host";
3+
import { withTransferable } from "./rpc";
34

4-
export { host, guest };
5+
export { host, guest, withTransferable };
56

67
export * from "./types";

src/rpc.ts

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import {
1010
} from "./helpers";
1111
import { actions, events, IRPCRequestPayload, IRPCResolvePayload, ISchema } from "./types";
1212

13+
/** Private symbol to which we will assign transferable objects */
14+
const SYM_TRANSFERABLES = Symbol();
15+
1316
/**
1417
* for each function in the schema
1518
* 1. subscribe to an event that the remote can call
@@ -47,24 +50,19 @@ export function registerLocalMethods(
4750
};
4851

4952
// run function and return the results to the remote
53+
const cb = get(schema, methodName);
54+
const transferables = cb[SYM_TRANSFERABLES];
55+
5056
try {
51-
const result = await get(schema, methodName)(...args);
52-
53-
if (!result) {
54-
// if the result is falsy (null, undefined, "", etc), set it directly
55-
payload.result = result;
56-
} else {
57-
// otherwise parse a stringified version of it
58-
payload.result = JSON.parse(JSON.stringify(result));
59-
}
57+
payload.result = await cb(...args);
6058
} catch (error) {
6159
payload.action = actions.RPC_REJECT;
6260
payload.error = JSON.parse(JSON.stringify(error, Object.getOwnPropertyNames(error)));
6361
}
6462

65-
if (guest) guest.postMessage(payload);
66-
else if (isWorker()) (self as any).postMessage(payload);
67-
else event.source.postMessage(payload, event.origin);
63+
if (guest) guest.postMessage(payload, transferables);
64+
else if (isWorker()) (self as any).postMessage(payload, transferables);
65+
else event.source.postMessage(payload, event.origin, transferables);
6866
}
6967

7068
// subscribe to the call event
@@ -99,7 +97,7 @@ export function createRPC(
9997
listeners: Array<() => void> = [],
10098
guest?: Worker
10199
) {
102-
return (...args: any) => {
100+
return (...args: any[]) => {
103101
return new Promise((resolve, reject) => {
104102
const callID = generateId();
105103

@@ -120,7 +118,7 @@ export function createRPC(
120118
// send the RPC request with arguments
121119
const payload = {
122120
action: actions.RPC_REQUEST,
123-
args: JSON.parse(JSON.stringify(args)),
121+
args,
124122
callID,
125123
callName: _callName,
126124
connectionID: _connectionID,
@@ -134,9 +132,12 @@ export function createRPC(
134132
listeners.push(() => removeEventListener(self, events.MESSAGE, handleResponse));
135133
}
136134

137-
if (guest) guest.postMessage(payload);
138-
else if (isWorker() || isNodeEnv()) (self as any).postMessage(payload);
139-
else (event.source || event.target).postMessage(payload, event.origin);
135+
// @ts-expect-error: we know this is an array of transferables (if it exists)
136+
const transferables = args[SYM_TRANSFERABLES] ?? (args.length === 1 ? args[0][SYM_TRANSFERABLES] : []);
137+
138+
if (guest) guest.postMessage(payload, transferables);
139+
else if (isWorker() || isNodeEnv()) (self as any).postMessage(payload, transferables);
140+
else (event.source || event.target).postMessage(payload, event.origin, transferables);
140141
});
141142
};
142143
}
@@ -171,3 +172,43 @@ export function registerRemoteMethods(
171172
unregisterRemote: () => listeners.forEach((unregister) => unregister()),
172173
};
173174
}
175+
176+
/**
177+
* This function is used by API schema declarations and remote function calls alike to
178+
* indicate which variables should be declared as transferable over `postMessage` calls.
179+
*
180+
* @param cb a function that takes a transfer function as an argument and returns an object
181+
* (in the loose, `typeof foo === "object"` sense)
182+
* @return result the callback's return value, with an extra array of transferable objects
183+
* assigned to rimless' private symbol `SYM_TRANSFERABLES`
184+
*
185+
* The `transfer(...)` function can be called with one or more objects to transfer. When
186+
* called with one object, it returns that object. When called with zero or multiple objects,
187+
* it returns an array of the objects. Calling `transfer` will only modify the callback result,
188+
* not the original object itself (or objects themselves).
189+
*
190+
* @example
191+
* host.connect({
192+
* foo: withTransferable((transfer) => {
193+
* const foo = new ArrayBuffer(8);
194+
* const bar = new ArrayBuffer(8);
195+
* transfer(foo, bar);
196+
*
197+
* return { foo, bar };
198+
* }),
199+
* });
200+
*
201+
* @example
202+
* host.remote.foo(withTransferable((transfer) => transfer(new ArrayBuffer(8))));
203+
*/
204+
export const withTransferable = <T, V extends object>(cb: (transfer: (transferable: T) => void) => V) => {
205+
const transferables: T[] = [];
206+
const transfer = (...toTransfer: [T, ...T[]]) => {
207+
transferables.push(...toTransfer);
208+
return toTransfer.length === 1 ? toTransfer[0] : toTransfer;
209+
};
210+
211+
const result = cb(transfer);
212+
213+
return Object.assign(result, { [SYM_TRANSFERABLES]: transferables });
214+
};

0 commit comments

Comments
 (0)