/*! * TSRPC Base Client v2.1.15 * ----------------------------------------- * Copyright (c) Kingworks Corporation. * MIT License * https://github.com/k8w/tsrpc-base-client */ import { ApiReturn } from 'tsrpc-proto'; import { ApiServiceDef } from 'tsrpc-proto'; import { BaseServiceType } from 'tsrpc-proto'; import { CustomTypeSchema } from 'tsbuffer-schema'; import { Logger } from 'tsrpc-proto'; import { LogLevel } from 'tsrpc-proto'; import { MsgServiceDef } from 'tsrpc-proto'; import { ServiceProto } from 'tsrpc-proto'; import { TSBuffer } from 'tsbuffer'; import { TsrpcError } from 'tsrpc-proto'; export declare type ApiReturnFlowData = { [K in keyof ServiceType['api']]: { apiName: K & string; req: ServiceType['api'][K]['req']; options?: TransportOptions; return: ApiReturn; }; }[keyof ServiceType['api']]; export declare interface ApiService extends ApiServiceDef { reqSchemaId: string; resSchemaId: string; } /** * An abstract base class for TSRPC Client, * which includes some common buffer process flows. * * @remarks * You can implement a client on a specific transportation protocol (like HTTP, WebSocket, QUIP) by extend this. * * @typeParam ServiceType - `ServiceType` from generated `proto.ts` * * @see * {@link https://github.com/k8w/tsrpc} * {@link https://github.com/k8w/tsrpc-browser} * {@link https://github.com/k8w/tsrpc-miniapp} */ export declare abstract class BaseClient { /** The connection is long connection or short connection */ abstract readonly type: 'SHORT' | 'LONG'; readonly dataType: 'json' | 'text' | 'buffer'; readonly options: Readonly; /** The map of all services */ readonly serviceMap: ServiceMap; /** The `TSBuffer` instance for encoding, decoding, and type checking */ readonly tsbuffer: TSBuffer; /** * `Logger` to process API Request/Response, send message, send buffer... * @defaultValue `console` */ readonly logger?: Logger; protected _msgHandlers: MsgHandlerManager; /** * {@link Flow} to process `callApi`, `sendMsg`, buffer input/output, etc... */ readonly flows: { readonly preCallApiFlow: Flow>; readonly preApiReturnFlow: Flow>; readonly postApiReturnFlow: Flow>; readonly preSendMsgFlow: Flow>; readonly postSendMsgFlow: Flow>; readonly preRecvMsgFlow: Flow>; readonly postRecvMsgFlow: Flow>; readonly preSendDataFlow: Flow<{ data: Uint8Array | string | object; sn?: number | undefined; }>; readonly preRecvDataFlow: Flow<{ data: Uint8Array | string | object; sn?: number | undefined; }>; /** * @deprecated Please use `preSendDataFlow` instead */ readonly preSendBufferFlow: Flow<{ buf: Uint8Array; sn?: number | undefined; }>; /** * @deprecated Please use `preRecvDataFlow` instead */ readonly preRecvBufferFlow: Flow<{ buf: Uint8Array; sn?: number | undefined; }>; /** Before connect to WebSocket server */ readonly preConnectFlow: Flow<{ /** Return `res` to `client.connect()`, without latter connect procedure */ return?: { isSucc: true; errMsg?: undefined; } | { isSucc: false; errMsg: string; } | undefined; }>; /** After WebSocket connect successfully */ readonly postConnectFlow: Flow<{}>; /** After WebSocket disconnected (from connected status) */ readonly postDisconnectFlow: Flow<{ /** reason parameter from server-side `conn.close(reason)` */ reason?: string | undefined; /** * Whether is is disconnected manually by `client.disconnect()`, * otherwise by accident. (e.g. network error, server closed...) */ isManual?: boolean | undefined; }>; }; protected _apiSnCounter: Counter; /** * The `SN` number of the last `callApi()`, * which can be passed to `abort()` to abort an API request. * @example * ```ts * client.callApi('xxx', { value: 'xxx' }) * .then(ret=>{ console.log('succ', ret) }); * let lastSN = client.lastSN; * client.abort(lastSN); * ``` */ get lastSN(): number; /** * The `SN` number of the next `callApi()`, * which can be passed to `abort()` to abort an API request. * @example * ```ts * let nextSN = client.nextSN; * client.callApi('xxx', { value: 'xxx' }) * ``` */ get nextSN(): number; /** * Pending API Requests */ protected _pendingApis: PendingApiItem[]; constructor(proto: ServiceProto, options: BaseClientOptions); /** * Send request and wait for the return * @param apiName * @param req - Request body * @param options - Transport options * @returns return a `ApiReturn`, all error (network error, business error, code exception...) is unified as `TsrpcError`. * The promise is never rejected, so you just need to process all error in one place. */ callApi(apiName: T, req: ServiceType['api'][T]['req'], options?: TransportOptions): Promise>; protected _doCallApi(apiName: T, req: ServiceType['api'][T]['req'], options: TransportOptions | undefined, pendingItem: PendingApiItem): Promise>; /** * Send message, without response, not ensuring the server is received and processed correctly. * @param msgName * @param msg - Message body * @param options - Transport options * @returns If the promise is resolved, it means the request is sent to system kernel successfully. * Notice that not means the server received and processed the message correctly. */ sendMsg(msgName: T, msg: ServiceType['msg'][T], options?: TransportOptions): Promise<{ isSucc: true; } | { isSucc: false; err: TsrpcError; }>; /** * Add a message handler, * duplicate handlers to the same `msgName` would be ignored. * @param msgName * @param handler * @returns */ listenMsg(msgName: T | RegExp, handler: ClientMsgHandler): ClientMsgHandler; /** * Remove a message handler */ unlistenMsg(msgName: T | RegExp, handler: Function): void; /** * Remove all handlers from a message */ unlistenMsgAll(msgName: T | RegExp): void; /** * Abort a pending API request, it makes the promise returned by `callApi()` neither resolved nor rejected forever. * @param sn - Every api request has a unique `sn` number, you can get it by `this.lastSN` */ abort(sn: number): void; /** * Abort all API requests that has the `abortKey`. * It makes the promise returned by `callApi` neither resolved nor rejected forever. * @param abortKey - The `abortKey` of options when `callApi()`, see {@link TransportOptions.abortKey}. * @example * ```ts * // Send API request many times * client.callApi('SendData', { data: 'AAA' }, { abortKey: 'Session#123' }); * client.callApi('SendData', { data: 'BBB' }, { abortKey: 'Session#123' }); * client.callApi('SendData', { data: 'CCC' }, { abortKey: 'Session#123' }); * * // And abort the at once * client.abortByKey('Session#123'); * ``` */ abortByKey(abortKey: string): void; /** * Abort all pending API requests. * It makes the promise returned by `callApi` neither resolved nor rejected forever. */ abortAll(): void; /** * Send data (binary or text) * @remarks * Long connection: wait res by listenning `conn.onmessage` * Short connection: wait res by waitting response * @param data * @param options * @param sn */ sendData(data: Uint8Array | string | object, options: TransportOptions, serviceId: number, pendingApiItem?: PendingApiItem): Promise<{ err?: TsrpcError; }>; protected abstract _sendData(data: Uint8Array | string | object, options: TransportOptions, serviceId: number, pendingApiItem?: PendingApiItem): Promise<{ err?: TsrpcError; }>; protected _onRecvData(data: Uint8Array | string | object, pendingApiItem?: PendingApiItem): Promise; /** @deprecated Please use `_onRecvData` instead */ protected _onRecvBuf: (buf: Uint8Array, pendingApiItem?: PendingApiItem) => Promise; /** * @param sn * @param timeout * @returns `undefined` 代表 canceled */ protected _waitApiReturn(pendingItem: PendingApiItem, timeout?: number): Promise>; } export declare interface BaseClientOptions { /** * `Logger` to process API Request/Response, send message, send buffer... * If it is assigned to `undefined`, all log would be hidden. (It may be useful when you want to encrypt the transportation) * @defaultValue `console` */ logger?: Logger; /** * The minimum log level of `logger` * @defaultValue `debug` */ logLevel: LogLevel; /** * Whether to log [ApiReq] and [ApiRes] by the `logger`. * NOTICE: if `logger` is `undefined`, no log would be printed. * @defaultValue `true` */ logApi: boolean; /** * Whether to log [SendMsg] and [RecvMsg] by the `logger`. * NOTICE: if `logger` is `undefined`, no log would be printed. * @defaultValue `true` */ logMsg: boolean; /** * Use JSON instead of binary as transfering format. * JSON transportation also support ArrayBuffer / Date / ObjectId. * @defaultValue `false` */ json: boolean; /** * Timeout time for `callApi` (ms) * `undefined` or `0` means unlimited * @defaultValue `15000` */ timeout: number; /** * If `true`, all sent and received raw buffer would be print into the log. * It may be useful when you do something for buffer encryption/decryption, and want to debug them. * @defaultValue `false` */ debugBuf: boolean; /** * 自定义 mongodb/ObjectId 的反序列化类型 * 传入 `String`,则会反序列化为字符串 * 传入 `ObjectId`, 则会反序列化为 `ObjectId` 实例 * 若为 `false`,则不会自动对 ObjectId 进行额外处理 * 将会针对 'mongodb/ObjectId' 'bson/ObjectId' 进行处理 */ customObjectIdClass?: { new (id?: any): any; } | false; } /** * Base HTTP Client */ export declare class BaseHttpClient extends BaseClient { readonly type = "SHORT"; private _http; private _jsonServer; readonly options: Readonly; constructor(proto: ServiceProto, http: IHttpProxy, options?: Partial); protected _sendData(data: Uint8Array | string, options: TransportOptions, serviceId: number, pendingApiItem?: PendingApiItem): Promise<{ err?: TsrpcError | undefined; }>; } export declare interface BaseHttpClientOptions extends BaseClientOptions { /** Server URL, starts with `http://` or `https://`. */ server: string; /** * Whether to automatically delete excess properties that not defined in the protocol. * @defaultValue `true` */ jsonPrune: boolean; } /** * WebSocket Client for TSRPC. * It uses native `WebSocket` of browser. * @typeParam ServiceType - `ServiceType` from generated `proto.ts` */ export declare class BaseWsClient extends BaseClient { readonly type = "LONG"; protected _wsp: IWebSocketProxy; readonly options: Readonly; constructor(proto: ServiceProto, wsp: IWebSocketProxy, options?: Partial); protected _onWsOpen: () => void; protected _onWsClose: (code: number, reason: string) => void; protected _onWsError: (e: unknown) => void; protected _onWsMessage: (data: Uint8Array | string) => void; protected _sendData(data: string | Uint8Array): Promise<{ err?: TsrpcError; }>; /** * Last latency time (ms) of heartbeat test */ lastHeartbeatLatency: number; private _pendingHeartbeat?; private _nextHeartbeatTimer?; /** * Send a heartbeat packet */ private _heartbeat; private _onHeartbeatAnswer; private _status; get status(): WsClientStatus; get isConnected(): boolean; private _connecting?; /** * Start connecting, you must connect first before `callApi()` and `sendMsg()`. * @throws never */ connect(): Promise<{ isSucc: true; errMsg?: undefined; } | { isSucc: false; errMsg: string; }>; private _rsDisconnecting?; /** * Disconnect immediately * @throws never */ disconnect(code?: number, reason?: string): Promise; private _wsClose; } export declare interface BaseWsClientOptions extends BaseClientOptions { /** Server URL, starts with `ws://` or `wss://`. */ server: string; /** * Heartbeat test * `undefined` represent disable heartbeat test * @defaultValue `undefined` */ heartbeat?: { /** Interval time between 2 heartbeat packet (unit: ms) */ interval: number; /** If a heartbeat packet not got reply during this time, the connection would be closed (unit: ms) */ timeout: number; }; } export declare type CallApiFlowData = { [K in keyof ServiceType['api']]: { apiName: K & string; req: ServiceType['api'][K]['req']; options?: TransportOptions; return?: ApiReturn; }; }[keyof ServiceType['api']]; export declare type ClientMsgHandler = (msg: ServiceType['msg'][MsgName], msgName: MsgName) => void | Promise; /** * An auto-increment counter */ export declare class Counter { private _min; private _max; private _last; constructor(min?: number, max?: number); /** * Reset the counter, makes `getNext()` restart from `0` */ reset(): void; /** * Get next counter value, and auto increment `1` * @param notInc - Just get the next possible value, not actually increasing the sequence */ getNext(notInc?: boolean): number; /** * Last return of `getNext()` */ get last(): number; } export declare const defaultBaseClientOptions: BaseClientOptions; export declare const defaultBaseHttpClientOptions: BaseHttpClientOptions; export declare const defaultBaseWsClientOptions: BaseWsClientOptions; export declare type EncodeOutput = { isSucc: true; /** Encoded binary buffer */ output: T; errMsg?: undefined; } | { isSucc: false; /** Error message */ errMsg: string; output?: undefined; }; /** * A `Flow` is consists of many `FlowNode`, which is function with the same input and output (like pipeline). * * @remarks * `Flow` is like a hook or event, executed at a specific time. * The difference to event is it can be used to **interrupt** an action, by return `undefined` or `null` in a node. */ export declare class Flow { /** * All node functions, if you want to adjust the sort you can modify this. */ nodes: FlowNode[]; /** * Event when error throwed from a `FlowNode` function. * By default, it does nothing except print a `Uncaught FlowError` error log. * @param e * @param last * @param input * @param logger */ onError: (e: Error | TsrpcError, last: T, input: T, logger: Logger | undefined) => void; /** * Execute all node function one by one, the previous output is the next input, * until the last output would be return to the caller. * * @remarks * If any node function return `null | undefined`, or throws an error, * the latter node functions would not be executed. * And it would return `null | undefined` immediately to the caller, * which tell the caller it means a interruption, * to let the caller stop latter behaviours. * * @param input The input of the first `FlowNode` * @param logger Logger to print log, `undefined` means to hide all log. * @returns */ exec(input: T, logger: Logger | undefined): Promise>; /** * Append a node function to the last * @param node * @returns */ push(node: FlowNode): FlowNode; /** * Remove a node function * @param node * @returns */ remove(node: FlowNode): FlowNode[]; } export declare type FlowData> = T extends Flow ? R : unknown; export declare type FlowNode = (item: T) => FlowNodeReturn | Promise>; /** * @returns * `T` represents succ & continue, * `null | undefined` represents interrupt. * If error is throwed, `Flow.onError` would be called. */ export declare type FlowNodeReturn = T | null | undefined; export declare function getCustomObjectIdTypes(classObjectId: { new (id?: any): any; }): { [schemaId: string]: CustomTypeSchema; }; export declare interface IHttpProxy { fetch(options: { url: string; data: string | Uint8Array; method: string; /** ms */ timeout?: number; headers?: { [key: string]: string; }; transportOptions: TransportOptions; responseType: 'text' | 'arraybuffer'; }): { abort: () => void; promise: Promise<{ isSucc: true; res: string | Uint8Array; } | { isSucc: false; err: TsrpcError; }>; }; } export declare interface IWebSocketProxy { options: { onOpen: () => void; onClose: (code: number, reason: string) => void; onError: (e: unknown) => void; onMessage: (data: Uint8Array | string) => void; logger?: Logger; }; connect(server: string, protocols?: string[]): void; close(code?: number, reason?: string): void; send(data: Uint8Array | string): Promise<{ err?: TsrpcError; }>; } /** * A manager for TSRPC receiving messages */ export declare class MsgHandlerManager { private _handlers; /** * Execute all handlers parallelly * @returns handlers count */ forEachHandler(msgName: string, logger: Logger | undefined, ...args: any[]): (any | Promise)[]; /** * Add message handler, duplicate handlers to the same `msgName` would be ignored. * @param msgName * @param handler * @returns */ addHandler(msgName: string, handler: Function): void; /** * Remove handler from the specific `msgName` * @param msgName * @param handler * @returns */ removeHandler(msgName: string, handler: Function): void; /** * Remove all handlers for the specific `msgName` * @param msgName */ removeAllHandlers(msgName: string): void; } export declare interface MsgService extends MsgServiceDef { msgSchemaId: string; } export declare type ParsedServerInput = { type: 'api'; service: ApiService; req: any; sn?: number; } | { type: 'msg'; service: MsgService; msg: any; }; export declare type ParsedServerOutput = { type: 'api'; service: ApiService; sn?: number; ret: ApiReturn; } | { type: 'msg'; service: MsgService; msg: any; }; export declare interface PendingApiItem { sn: number; abortKey: string | undefined; service: ApiService; isAborted?: boolean; onAbort?: () => void; onReturn?: (ret: ApiReturn) => void; } export declare type RecvMsgFlowData = { [K in keyof ServiceType['msg']]: { msgName: K & string; msg: ServiceType['msg'][K]; }; }[keyof ServiceType['msg']]; export declare type SendMsgFlowData = { [K in keyof ServiceType['msg']]: { msgName: K & string; msg: ServiceType['msg'][K]; options?: TransportOptions; }; }[keyof ServiceType['msg']]; export declare interface ServiceMap { id2Service: { [serviceId: number]: ApiService | MsgService; }; apiName2Service: { [apiName: string]: ApiService | undefined; }; msgName2Service: { [msgName: string]: MsgService | undefined; }; } /** A utility for generate `ServiceMap` */ export declare class ServiceMapUtil { static getServiceMap(proto: ServiceProto): ServiceMap; } export declare class TransportDataUtil { static readonly HeartbeatPacket: Readonly; private static _tsbuffer?; static get tsbuffer(): TSBuffer; static encodeClientMsg(tsbuffer: TSBuffer, service: MsgService, msg: any, type: 'buffer', connType: BaseClient['type']): EncodeOutput; static encodeClientMsg(tsbuffer: TSBuffer, service: MsgService, msg: any, type: 'text', connType: BaseClient['type']): EncodeOutput; static encodeClientMsg(tsbuffer: TSBuffer, service: MsgService, msg: any, type: 'json', connType: BaseClient['type']): EncodeOutput; static encodeClientMsg(tsbuffer: TSBuffer, service: MsgService, msg: any, type: 'text' | 'buffer' | 'json', connType: BaseClient['type']): EncodeOutput | EncodeOutput | EncodeOutput; static encodeApiReq(tsbuffer: TSBuffer, service: ApiService, req: any, type: 'buffer', sn?: number): EncodeOutput; static encodeApiReq(tsbuffer: TSBuffer, service: ApiService, req: any, type: 'text', sn?: number): EncodeOutput; static encodeApiReq(tsbuffer: TSBuffer, service: ApiService, req: any, type: 'json', sn?: number): EncodeOutput; static encodeApiReq(tsbuffer: TSBuffer, service: ApiService, req: any, type: 'text' | 'buffer' | 'json', sn?: number): EncodeOutput | EncodeOutput | EncodeOutput; static encodeServerMsg(tsbuffer: TSBuffer, service: MsgService, msg: any, type: 'buffer', connType: BaseClient['type']): EncodeOutput; static encodeServerMsg(tsbuffer: TSBuffer, service: MsgService, msg: any, type: 'text', connType: BaseClient['type']): EncodeOutput; static encodeServerMsg(tsbuffer: TSBuffer, service: MsgService, msg: any, type: 'json', connType: BaseClient['type']): EncodeOutput; static encodeServerMsg(tsbuffer: TSBuffer, service: MsgService, msg: any, type: 'text' | 'buffer' | 'json', connType: BaseClient['type']): EncodeOutput | EncodeOutput | EncodeOutput; static parseServerOutout(tsbuffer: TSBuffer, serviceMap: ServiceMap, data: Uint8Array | string | object, serviceId?: number): { isSucc: true; result: ParsedServerOutput; } | { isSucc: false; errMsg: string; }; } export declare interface TransportOptions { /** * Timeout of this request(ms) * `undefined` represents no timeout * @defaultValue `undefined` */ timeout?: number; /** * Which can be passed to `client.abortByKey(abortKey)`. * Many requests can share the same `abortKey`, so that they can be aborted at once. * @remarks * This may be useful in frontend within React or VueJS. * You can specify a unified `abortKey` to requests in a component, and abort them when the component is destroying. * @example * ```ts * // Send API request many times * client.callApi('SendData', { data: 'AAA' }, { abortKey: 'Session#123' }); * client.callApi('SendData', { data: 'BBB' }, { abortKey: 'Session#123' }); * client.callApi('SendData', { data: 'CCC' }, { abortKey: 'Session#123' }); * * // And abort the at once * client.abortByKey('Session#123'); * ``` */ abortKey?: string; } export declare enum WsClientStatus { Opening = "OPENING", Opened = "OPENED", Closing = "CLOSING", Closed = "CLOSED" } export { }