Last active
February 11, 2023 06:20
-
-
Save melbourne2991/b612aa0ceff1a397c44395991fbe6f05 to your computer and use it in GitHub Desktop.
typed rpc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export type ServiceDefinition = Record<string, (...args: any[]) => Promise<any>> | |
export interface Client<T extends ServiceDefinition> { | |
api: T; | |
handler: (e: MessageEvent) => void; | |
} | |
export type Send = (payload: any) => void; | |
export function Bridge<T extends ServiceDefinition>(serviceDefinition: T) { | |
const service = serviceDefinition | |
return { | |
createClient: (send: Send, clientId: string): Client<T> => { | |
let count = 0; | |
const api = {} as T; | |
let pending: Map<string, Resolvers<any, any>> = new Map(); | |
Object.keys(service).forEach((key) => { | |
(api as any)[key] = (...args: any[]) => { | |
return new Promise((resolve, reject) => { | |
const reqId = `${clientId}:${count}`; | |
const request: RpcRequest = { | |
__rpc_req: true, | |
reqId, | |
method: key, | |
args, | |
}; | |
send(request); | |
pending.set(reqId, { | |
resolve, | |
reject, | |
}); | |
count++; | |
}); | |
}; | |
}); | |
const handleMessage = (e: MessageEvent) => { | |
if (isRpcResponse(e.data)) { | |
const resolvers = pending.get(e.data.reqId); | |
if (!resolvers) { | |
throw new Error(`missing resolvers: ${e.data}`); | |
} | |
e.data.error | |
? resolvers.reject(e.data.value) | |
: resolvers.resolve(e.data.value); | |
pending.delete(e.data.reqId); | |
} | |
}; | |
return { | |
api, | |
handler: handleMessage, | |
}; | |
}, | |
createHandler: (send: Send) => { | |
return (e: MessageEvent) => { | |
const data = e.data; | |
if (isRpcRequest(data)) { | |
const method = service[e.data.method]; | |
if (!method) { | |
throw new Error(`missing method: ${e.data}`); | |
} | |
method(...e.data.args) | |
.then((value) => { | |
const response: RpcResponse = { | |
__rpc_res: true, | |
reqId: data.reqId, | |
error: false, | |
value, | |
}; | |
send(response); | |
}) | |
.catch((err) => { | |
const response: RpcResponse = { | |
__rpc_res: true, | |
reqId: data.reqId, | |
error: true, | |
value: err, | |
}; | |
send(response); | |
}); | |
} | |
}; | |
}, | |
}; | |
} | |
interface Resolvers<T, E> { | |
resolve: (value: T) => void; | |
reject: (value: E) => void; | |
} | |
interface RpcRequest { | |
__rpc_req: true; | |
reqId: string; | |
method: string; | |
args: any[]; | |
} | |
interface RpcResponse { | |
__rpc_res: true; | |
error: boolean; | |
reqId: string; | |
value: any; | |
} | |
function isRpcResponse(value: unknown): value is RpcResponse { | |
return Boolean( | |
value && typeof value === "object" && Object.hasOwn(value, "__rpc_res") | |
); | |
} | |
function isRpcRequest(value: unknown): value is RpcRequest { | |
return Boolean( | |
value && typeof value === "object" && Object.hasOwn(value, "__rpc_req") | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment