@@ -10,6 +10,9 @@ import {
1010} from "./helpers" ;
1111import { 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