Skip to content

Instantly share code, notes, and snippets.

@melbourne2991
Last active February 11, 2023 06:20
Show Gist options
  • Save melbourne2991/b612aa0ceff1a397c44395991fbe6f05 to your computer and use it in GitHub Desktop.
Save melbourne2991/b612aa0ceff1a397c44395991fbe6f05 to your computer and use it in GitHub Desktop.
typed rpc
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