UNPKG

crx-rpc

Version:

A lightweight RPC framework for Chrome Extension (background <-> content <-> web)

112 lines (94 loc) 3.84 kB
import { OBSERVABLE_EVENT, RPC_EVENT_NAME, RPC_RESPONSE_EVENT_NAME, UNSUBSCRIBE_OBSERVABLE } from './const'; import type { RpcRequest, RpcResponse, RpcObservableUpdateMessage, IMessageAdapter } from './types'; import type { Identifier } from './id'; import { Disposable } from './disposable'; import { randomId } from './tool'; // 类型工具:提取函数类型的参数和返回值类型 type FunctionArgs<T> = T extends (...args: infer A) => any ? A : never; type FunctionReturnType<T> = T extends (...args: any[]) => infer R ? R : never; // 类型工具:将服务接口转换为客户端代理类型 type ServiceProxy<T> = { [K in keyof T]: T[K] extends (...args: any[]) => any ? (...args: FunctionArgs<T[K]>) => Promise<Awaited<FunctionReturnType<T[K]>>> : never; }; export class RPCClient extends Disposable { private pending: Map<string, { resolve: Function; reject: Function }> = new Map(); constructor( private messageAdapter: IMessageAdapter ) { super(); this.disposeWithMe(messageAdapter.onMessage<RpcResponse>(RPC_RESPONSE_EVENT_NAME, (event: RpcResponse) => { const { id, result, error } = event as RpcResponse; const promise = this.pending.get(id); if (!promise) return; this.pending.delete(id); if (error) { const err = new Error(error.message); err.name = error.name || 'RPCError'; err.stack = error.stack; promise.reject(err); } else { promise.resolve(result); } })); } call<T = any>(service: string, method: string, args: any[]): Promise<T> { const id = randomId(); return new Promise<T>((resolve, reject) => { this.pending.set(id, { resolve, reject }); const requestParam: RpcRequest = { method, args, id, service, }; this.messageAdapter.sendMessage(RPC_EVENT_NAME, requestParam); }); } createWebRPCService<T>(serviceIdentifier: Identifier<T>): ServiceProxy<T> { const serviceKey = serviceIdentifier.key; // 创建代理对象,拦截方法调用 return new Proxy({} as ServiceProxy<T>, { get: (target, prop: string | symbol) => { if (typeof prop === 'string') { // 返回一个代理函数 return (...args: any[]) => { return this.call(serviceKey, prop, args); }; } return (target as any)[prop]; }, }); } } export class BaseObservable<T> extends Disposable { private listeners = new Set<(value: T) => void>(); private completed = false; private get _finalKey() { return `${this.identifier.key}-${this.key}`; } constructor( private identifier: Identifier<T>, private key: string, private _callback: (value: T) => void, private _adapter: IMessageAdapter ) { super(); this.disposeWithMe(this._adapter.onMessage(OBSERVABLE_EVENT, (event: any) => { const msg = event.detail as RpcObservableUpdateMessage<T>; if (msg.key !== this._finalKey) return; if (msg.operation === 'next' && !this.completed && msg.value) { this._callback(msg.value); } if (msg.operation === 'complete') { this.completed = true; this.listeners.clear(); } })); this._adapter.sendMessage(OBSERVABLE_EVENT, { key: this._finalKey }); } unsubscribe(): void { this._adapter.sendMessage(UNSUBSCRIBE_OBSERVABLE, { key: this._finalKey }); } }