tsrpc
Version:
A TypeScript RPC Framework, with runtime type checking and built-in serialization, support both HTTP and WebSocket.
923 lines (875 loc) • 36.2 kB
TypeScript
/*!
* TSRPC v3.4.19
* -----------------------------------------
* Copyright (c) King Wang.
* MIT License
* https://github.com/k8w/tsrpc
*/
/// <reference types="node" />
/// <reference path="mongodb-polyfill.d.ts" />
import { ApiReturn } from 'tsrpc-proto';
import { ApiService } from 'tsrpc-base-client';
import { ApiServiceDef } from 'tsrpc-proto';
import { BaseHttpClient } from 'tsrpc-base-client';
import { BaseHttpClientOptions } from 'tsrpc-base-client';
import { BaseServiceType } from 'tsrpc-proto';
import { BaseWsClient } from 'tsrpc-base-client';
import { BaseWsClientOptions } from 'tsrpc-base-client';
import { Counter } from 'tsrpc-base-client';
import { CustomTypeSchema } from 'tsbuffer-schema';
import { Flow } from 'tsrpc-base-client';
import http from 'http';
import * as http_2 from 'http';
import https from 'https';
import { Logger } from 'tsrpc-proto';
import { LogLevel } from 'tsrpc-proto';
import { MsgService } from 'tsrpc-base-client';
import { ParsedServerInput } from 'tsrpc-base-client';
import { ServiceMap } from 'tsrpc-base-client';
import { ServiceProto } from 'tsrpc-proto';
import { TSBuffer } from 'tsbuffer';
import { TsrpcError } from 'tsrpc-proto';
import { TsrpcErrorData } from 'tsrpc-proto';
import * as WebSocket_2 from 'ws';
/**
* A call request by `client.callApi()`
* @typeParam Req - Type of request
* @typeParam Res - Type of response
* @typeParam ServiceType - The same `ServiceType` to server, it is used for code auto hint.
*/
export declare abstract class ApiCall<Req = any, Res = any, ServiceType extends BaseServiceType = any> extends BaseCall<ServiceType> {
readonly type: "api";
/**
* Which `ApiService` the request is calling for
*/
readonly service: ApiService;
/** Only exists in long connection, it is used to associate request and response.
* It is created by the client, and the server would return the same value in `ApiReturn`.
*/
readonly sn?: number;
/**
* Request data from the client, type of it is checked by the framework already.
*/
readonly req: Req;
constructor(options: ApiCallOptions<Req, ServiceType>, logger?: PrefixLogger);
protected _return?: ApiReturn<Res>;
/**
* Response Data that sent already.
* `undefined` means no return data is sent yet. (Never `call.succ()` and `call.error()`)
*/
get return(): ApiReturn<Res> | undefined;
protected _usedTime: number | undefined;
/** Time from received req to send return data */
get usedTime(): number | undefined;
/**
* Send a successful `ApiReturn` with response data
* @param res - Response data
* @returns Promise resolved means the buffer is sent to kernel
*/
succ(res: Res): Promise<void>;
/**
* Send a error `ApiReturn` with a `TsrpcError`
* @returns Promise resolved means the buffer is sent to kernel
*/
error(message: string, info?: Partial<TsrpcErrorData>): Promise<void>;
error(err: TsrpcError): Promise<void>;
protected _prepareReturn(ret: ApiReturn<Res>): Promise<void>;
protected _sendReturn(ret: ApiReturn<Res>): ReturnType<SendReturnMethod<Res>>;
static encodeApiReturn(tsbuffer: TSBuffer, service: ApiService, apiReturn: ApiReturn<any>, type: "text", sn?: number): EncodeApiReturnOutput<string>;
static encodeApiReturn(tsbuffer: TSBuffer, service: ApiService, apiReturn: ApiReturn<any>, type: "buffer", sn?: number): EncodeApiReturnOutput<Uint8Array>;
static encodeApiReturn(tsbuffer: TSBuffer, service: ApiService, apiReturn: ApiReturn<any>, type: "json", sn?: number): EncodeApiReturnOutput<object>;
static encodeApiReturn(tsbuffer: TSBuffer, service: ApiService, apiReturn: ApiReturn<any>, type: "text" | "buffer" | "json", sn?: number): EncodeApiReturnOutput<Uint8Array> | EncodeApiReturnOutput<string> | EncodeApiReturnOutput<object>;
}
export declare class ApiCallHttp<Req = any, Res = any, ServiceType extends BaseServiceType = any> extends ApiCall<Req, Res, ServiceType> {
readonly conn: HttpConnection<ServiceType>;
constructor(options: ApiCallHttpOptions<Req, ServiceType>);
protected _sendReturn(ret: ApiReturn<Res>): Promise<{
isSucc: true;
} | {
isSucc: false;
errMsg: string;
}>;
}
export declare interface ApiCallHttpOptions<Req, ServiceType extends BaseServiceType> extends ApiCallOptions<Req, ServiceType> {
conn: HttpConnection<ServiceType>;
}
export declare interface ApiCallOptions<Req, ServiceType extends BaseServiceType> extends BaseCallOptions<ServiceType> {
/** Which service the Call is belong to */
service: ApiService;
/** Only exists in long connection, it is used to associate request and response.
* It is created by the client, and the server would return the same value in `ApiReturn`.
*/
sn?: number;
/** Request Data */
req: Req;
}
export declare class ApiCallWs<Req = any, Res = any, ServiceType extends BaseServiceType = any> extends ApiCall<Req, Res, ServiceType> {
readonly conn: WsConnection<ServiceType>;
constructor(options: ApiCallWsOptions<Req, ServiceType>);
protected _prepareReturn(ret: ApiReturn<Res>): Promise<void>;
}
export declare interface ApiCallWsOptions<Req, ServiceType extends BaseServiceType> extends ApiCallOptions<Req, ServiceType> {
conn: WsConnection<ServiceType>;
}
export declare type ApiHandler<Call extends ApiCall = ApiCall> = (call: Call) => void | Promise<void>;
export declare abstract class BaseCall<ServiceType extends BaseServiceType> {
readonly conn: BaseConnection<ServiceType>;
readonly service: ApiService | MsgService;
/** Time that server created the call */
readonly startTime: number;
readonly logger: PrefixLogger;
constructor(options: BaseCallOptions<ServiceType>, logger: PrefixLogger);
get server(): this["conn"]["server"];
}
export declare interface BaseCallOptions<ServiceType extends BaseServiceType> {
/** Connection */
conn: BaseConnection<ServiceType>;
/** Which service the call is belong to */
service: ApiService | MsgService;
}
export declare abstract class BaseConnection<ServiceType extends BaseServiceType = any> {
/** It is long connection or short connection */
abstract readonly type: "LONG" | "SHORT";
protected abstract readonly ApiCallClass: {
new (options: any): ApiCall;
};
protected abstract readonly MsgCallClass: {
new (options: any): MsgCall;
};
/** Connection unique ID */
readonly id: string;
/** Client IP address */
readonly ip: string;
readonly server: BaseServer<ServiceType>;
readonly logger: PrefixLogger;
dataType: BaseConnectionOptions["dataType"];
constructor(options: BaseConnectionOptions<ServiceType>, logger: PrefixLogger);
abstract get status(): ConnectionStatus;
/** Close the connection */
abstract close(reason?: string): void;
/** Send buffer (with pre-flow and post-flow) */
sendData(data: string | Uint8Array | object, call?: ApiCall): Promise<{
isSucc: true;
} | {
isSucc: false;
errMsg: string;
canceledByFlow?: string;
}>;
protected abstract doSendData(data: string | Uint8Array | object, call?: ApiCall): Promise<{
isSucc: true;
} | {
isSucc: false;
errMsg: string;
}>;
makeCall(input: ParsedServerInput): ApiCall | MsgCall;
/**
* Send message to the client, only be available when it is long connection.
* @param msgName
* @param msg - Message body
* @returns Promise resolved when the buffer is sent to kernel, it not represents the server received it.
*/
sendMsg<T extends string & keyof ServiceType["msg"]>(msgName: T, msg: ServiceType["msg"][T]): ReturnType<BaseConnection["sendData"]>;
private _msgHandlers?;
/**
* Add a message handler,
* duplicate handlers to the same `msgName` would be ignored.
* @param msgName
* @param handler
*/
listenMsg<Msg extends string & keyof ServiceType["msg"], Call extends MsgCall<ServiceType["msg"][Msg]>>(msgName: Msg, handler: MsgHandler<Call>): MsgHandler<Call>;
/**
* Remove a message handler
*/
unlistenMsg<Msg extends string & keyof ServiceType["msg"], Call extends MsgCall<ServiceType["msg"][Msg]>>(msgName: Msg, handler: Function): void;
/**
* Remove all handlers from a message
*/
unlistenMsgAll<Msg extends string & keyof ServiceType["msg"], Call extends MsgCall<ServiceType["msg"][Msg]>>(msgName: Msg): void;
}
export declare interface BaseConnectionOptions<ServiceType extends BaseServiceType = any> {
/** Created by server, each Call has a unique id. */
id: string;
/** Client IP address */
ip: string;
server: BaseServer<ServiceType>;
dataType: "text" | "buffer" | "json";
}
/**
* Abstract base class for TSRPC Server.
* Implement on a transportation protocol (like HTTP WebSocket) by extend it.
* @typeParam ServiceType - `ServiceType` from generated `proto.ts`
*/
export declare abstract class BaseServer<ServiceType extends BaseServiceType = BaseServiceType> {
/**
* Start the server
* @throws
*/
abstract start(): Promise<void>;
/**
* Stop server immediately, not waiting for the requests ending.
*/
abstract stop(): Promise<void>;
protected _status: ServerStatus;
get status(): ServerStatus;
readonly proto: ServiceProto<ServiceType>;
readonly options: BaseServerOptions<ServiceType>;
readonly tsbuffer: TSBuffer;
readonly serviceMap: ServiceMap;
readonly logger: Logger;
protected _connIdCounter: Counter;
/**
* Flow is a specific concept created by TSRPC family.
* All pre-flow can interrupt latter behaviours.
* All post-flow can NOT interrupt latter behaviours.
*/
readonly flows: {
/** After the connection is created */
readonly postConnectFlow: Flow<BaseConnection<ServiceType>>;
/** After the connection is disconnected */
readonly postDisconnectFlow: Flow<{
conn: BaseConnection<ServiceType>;
reason?: string | undefined;
}>;
/**
* Before processing the received data, usually be used to encryption / decryption.
* Return `null | undefined` would ignore the buffer.
*/
readonly preRecvDataFlow: Flow<{
conn: BaseConnection<ServiceType>;
data: string | Uint8Array | object;
/**
* @deprecated use `serviceId` instead
*/
serviceName?: string | undefined;
/**
* Parsed service id, you can get this by `this.serviceMap.apiName2Service[serviceName].id`
*/
serviceId?: number | undefined;
}>;
/**
* Before send out data to network, usually be used to encryption / decryption.
* Return `null | undefined` would not send the buffer.
*/
readonly preSendDataFlow: Flow<{
conn: BaseConnection<ServiceType>;
data: string | Uint8Array | object;
call?: ApiCall<any, any, any> | undefined;
}>;
/**
* @deprecated Use `preRecvDataFlow` instead.
*/
readonly preRecvBufferFlow: Flow<{
conn: BaseConnection<ServiceType>;
buf: Uint8Array;
}>;
/**
* @deprecated Use `preSendDataFlow` instead.
*/
readonly preSendBufferFlow: Flow<{
conn: BaseConnection<ServiceType>;
buf: Uint8Array;
call?: ApiCall<any, any, any> | undefined;
}>;
/**
* Before a API request is send.
* Return `null | undefined` would cancel the request.
*/
readonly preApiCallFlow: Flow<ApiCall<any, any, any>>;
/**
* Before return the `ApiReturn` to the client.
* It may be used to change the return value, or return `null | undefined` to abort the request.
*/
readonly preApiReturnFlow: Flow<{
call: ApiCall;
return: ApiReturn<any>;
}>;
/**
* After the `ApiReturn` is send.
* return `null | undefined` would NOT interrupt latter behaviours.
*/
readonly postApiReturnFlow: Flow<{
call: ApiCall;
return: ApiReturn<any>;
}>;
/**
* After the api handler is executed.
* return `null | undefined` would NOT interrupt latter behaviours.
*/
readonly postApiCallFlow: Flow<ApiCall<any, any, any>>;
/**
* Before handle a `MsgCall`
*/
readonly preMsgCallFlow: Flow<MsgCall<any, any>>;
/**
* After handlers of a `MsgCall` are executed.
* return `null | undefined` would NOT interrupt latter behaviours.
*/
readonly postMsgCallFlow: Flow<MsgCall<any, any>>;
/**
* Before send out a message.
* return `null | undefined` would NOT interrupt latter behaviours.
*/
readonly preSendMsgFlow: Flow<{
conn: BaseConnection<ServiceType>;
service: MsgService;
msg: any;
}>;
/**
* After send out a message.
* return `null | undefined` would NOT interrupt latter behaviours.
*/
readonly postSendMsgFlow: Flow<{
conn: BaseConnection<ServiceType>;
service: MsgService;
msg: any;
}>;
};
private _apiHandlers;
private _msgHandlers;
private static _isUncaughtExceptionProcessed;
/**
* It makes the `uncaughtException` and `unhandledRejection` not lead to the server stopping.
* @param logger
* @returns
*/
static processUncaughtException(logger: Logger): void;
constructor(proto: ServiceProto<ServiceType>, options: BaseServerOptions<ServiceType>);
protected _setDefaultFlowOnError(): void;
protected _pendingApiCallNum: number;
/**
* Process the buffer, after the `preRecvBufferFlow`.
*/
_onRecvData(conn: BaseConnection<ServiceType>, data: string | Uint8Array | object, serviceId?: number): Promise<void>;
protected _handleApiCall(call: ApiCall): Promise<void>;
protected _onApiCall(call: ApiCall): Promise<void>;
protected _onMsgCall(call: MsgCall): Promise<void>;
/**
* Associate a `ApiHandler` to a specific `apiName`.
* So that when `ApiCall` is receiving, it can be handled correctly.
* @param apiName
* @param handler
*/
implementApi<Api extends string & keyof ServiceType["api"], Call extends ApiCall<ServiceType["api"][Api]["req"], ServiceType["api"][Api]["res"]>>(apiName: Api, handler: ApiHandler<Call>): void;
/** 用于延迟注册 API */
protected _delayImplementApiPath?: string;
/**
* Auto call `imeplementApi` by traverse the `apiPath` and find all matched `PtlXXX` and `ApiXXX`.
* It is matched by checking whether the relative path and name of an API is consistent to the service name in `serviceProto`.
* Notice that the name prefix of protocol is `Ptl`, of API is `Api`.
* For example, `protocols/a/b/c/PtlTest` is matched to `api/a/b/c/ApiTest`.
* @param apiPath Absolute path or relative path to `process.cwd()`.
* @returns
*/
autoImplementApi(apiPath: string, delay?: boolean): Promise<{
succ: string[];
fail: string[];
}>;
getApiHandler(svc: ApiServiceDef, apiPath?: string, logger?: Logger): Promise<{
handler: ApiHandler;
errMsg?: undefined;
} | {
handler?: undefined;
errMsg: string;
}>;
/**
* Add a message handler,
* duplicate handlers to the same `msgName` would be ignored.
* @param msgName
* @param handler
* @returns
*/
listenMsg<T extends keyof ServiceType["msg"], Call extends MsgCall<ServiceType["msg"][T]>>(msgName: T | RegExp, handler: MsgHandler<Call>): MsgHandler<Call>;
/**
* Remove a message handler
*/
unlistenMsg<T extends keyof ServiceType["msg"]>(msgName: T | RegExp, handler: Function): void;
/**
* Remove all handlers from a message
*/
unlistenMsgAll<T extends keyof ServiceType["msg"]>(msgName: T | RegExp): void;
/**
* Event when the server cannot parse input buffer to api/msg call.
* By default, it will return "Input Data Error" .
*/
onInputDataError(errMsg: string, conn: BaseConnection<ServiceType>, data: string | Uint8Array | object): Promise<void>;
/**
* Event when a uncaught error (except `TsrpcError`) is throwed.
* By default, it will return a `TsrpcError` with message "Internal server error".
* If `returnInnerError` is `true`, the original error would be returned as `innerErr` property.
*/
onInternalServerError(err: {
message: string;
stack?: string;
name?: string;
}, call: ApiCall): void;
protected _gracefulStop?: {
rs: () => void;
};
/**
* Stop the server gracefully.
* Wait all API requests finished and then stop the server.
* @param maxWaitTime - The max time(ms) to wait before force stop the server.
* `undefined` and `0` means unlimited time.
*/
gracefulStop(maxWaitTime?: number): Promise<void>;
/**
* Execute API function through the inner connection, which is useful for unit test.
*
* **NOTICE**
* The `req` and return value is native JavaScript object which is not compatible to JSON. (etc. ArrayBuffer, Date, ObjectId)
* If you are using pure JSON as transfering, you may need use `callApiByJSON`.
* @param apiName
* @param req
* @param options
*/
callApi<T extends string & keyof ServiceType["api"]>(apiName: T, req: ServiceType["api"][T]["req"]): Promise<ApiReturn<ServiceType["api"][T]["res"]>>;
/**
* Like `server.callApi`, but both input and output are pure JSON object,
* which can be `JSON.stringify()` and `JSON.parse()` directly.
* Types that not compatible to JSON, would be encoded and decoded automatically.
* @param apiName - The same with `server.callApi`, may be parsed from the URL.
* @param jsonReq - Request data in pure JSON
* @returns Encoded `ApiReturn<Res>` in pure JSON
*/
/**
* Process JSON request by inner proxy, this is useful when you are porting to cloud function services.
* Both the input and output is pure JSON, ArrayBuffer/Date/ObjectId are encoded to string automatically.
* @param apiName - Parsed from URL
* @param req - Pure JSON
* @param logger - Custom logger
* @returns - Pure JSON
*/
inputJSON(apiName: string, req: object, logger?: PrefixLogger): Promise<ApiReturn<object>>;
/**
* Process input buffer by inner proxy, this is useful when you are porting to cloud function services.
* @param buf Input buffer (may be sent by TSRPC client)
* @returns Response buffer
*/
inputBuffer(buf: Uint8Array): Promise<Uint8Array>;
protected _parseServerInput(tsbuffer: TSBuffer, serviceMap: ServiceMap, data: string | Uint8Array | object, serviceId?: number): {
isSucc: true;
result: ParsedServerInput;
} | {
isSucc: false;
errMsg: string;
};
}
export declare interface BaseServerOptions<ServiceType extends BaseServiceType> {
/**
* Whether to enable JSON compatible mode.
* When it is true, it can be compatible with typical HTTP JSON request (like RESTful API).
*
* @remarks
* The JSON request methods are:
*
* 1. Add `Content-type: application/json` to request header.
* 2. HTTP request is: `POST /{jsonUrlPath}/{apiName}`.
* 3. POST body is JSON string.
* 4. The response body is JSON string also.
*
* NOTICE: Buffer type are not supported due to JSON not support them.
* For security and efficient reason, we strongly recommend you use binary encoded transportation.
*
* @defaultValue `false`
*/
json: boolean;
/** @deprecated Use `json` instead. */
jsonEnabled?: boolean;
/**
* Whether to strictly distinguish between `null` and `undefined` when encoding, decoding, and type checking.
* @defaultValue false
*/
strictNullChecks: boolean;
/**
* Timeout for processing an `ApiCall`(ms)
* `0` and `undefined` means unlimited time
* @defaultValue 30000
*/
apiTimeout: number | undefined;
/**
* Logger for processing log
* @defaultValue `new TerminalColorLogger()` (print to console with color)
*/
logger: Logger;
/**
* The minimum log level of `logger`
* @defaultValue `debug`
*/
logLevel: LogLevel;
/**
* Whether to print API request body into log (may increase log size)
* @defaultValue `true`
*/
logReqBody: boolean;
/**
* Whether to print API response body into log (may increase log size)
* @defaultValue `true`
*/
logResBody: boolean;
/**
* Whether to print `[SendMsg]` and `[RecvMsg]` log into log
* @defaultValue `true`
*/
logMsg: boolean;
/**
* 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.
*/
debugBuf?: boolean;
/**
* When uncaught error throwed,
* whether to return the original error as a property `innerErr`.
* (May include some sensitive information, suggests set to `false` in production environment.)
* @defaultValue It depends on environment variable `NODE_ENV`.
* If `NODE_ENV` equals to `production`, the default value is `false`, otherwise is `true`.
*/
returnInnerError: boolean;
/**
* Customize the protocol data type
*/
customTypes?: {
[schemaId: string]: CustomTypeSchema;
};
}
export declare enum ConnectionStatus {
Opened = "OPENED",
Closing = "CLOSING",
Closed = "CLOSED"
}
export declare const defaultBaseServerOptions: BaseServerOptions<any>;
export declare const defaultHttpServerOptions: HttpServerOptions<any>;
export declare type EncodeApiReturnOutput<T> = {
isSucc: true;
/** Encoded binary buffer */
output: T;
errMsg?: undefined;
} | {
isSucc: false;
/** Error message */
errMsg: string;
output?: undefined;
};
/**
* Client for TSRPC HTTP Server.
* It uses native http module of NodeJS.
* @typeParam ServiceType - `ServiceType` from generated `proto.ts`
*/
export declare class HttpClient<ServiceType extends BaseServiceType> extends BaseHttpClient<ServiceType> {
readonly options: Readonly<HttpClientOptions>;
constructor(proto: ServiceProto<ServiceType>, options?: Partial<HttpClientOptions>);
}
export declare interface HttpClientOptions extends BaseHttpClientOptions {
/** NodeJS HTTP Agent */
agent?: http.Agent | https.Agent;
}
export declare class HttpConnection<ServiceType extends BaseServiceType = any> extends BaseConnection<ServiceType> {
readonly type = "SHORT";
protected readonly ApiCallClass: typeof ApiCallHttp;
protected readonly MsgCallClass: typeof MsgCallHttp;
readonly httpReq: http_2.IncomingMessage & {
rawBody?: Buffer;
};
readonly httpRes: http_2.ServerResponse;
readonly server: HttpServer<ServiceType>;
/**
* Whether the transportation of the connection is JSON encoded instead of binary encoded.
*/
readonly isJSON: boolean | undefined;
/**
* In short connection, one connection correspond one call.
* It may be `undefined` when the request data is not fully received yet.
*/
call?: ApiCallHttp | MsgCallHttp;
constructor(options: HttpConnectionOptions<ServiceType>);
get status(): ConnectionStatus;
protected doSendData(data: string | Uint8Array, call?: ApiCall): Promise<{
isSucc: true;
} | {
isSucc: false;
errMsg: string;
}>;
/**
* Close the connection, the reason would be attached to response header `X-TSRPC-Close-Reason`.
*/
close(reason?: string): void;
makeCall(input: ParsedServerInput): ApiCallHttp | MsgCallHttp;
}
export declare interface HttpConnectionOptions<ServiceType extends BaseServiceType> extends BaseConnectionOptions<ServiceType> {
server: HttpServer<ServiceType>;
httpReq: http_2.IncomingMessage;
httpRes: http_2.ServerResponse;
}
/**
* TSRPC Server, based on HTTP connection.
* @typeParam ServiceType - `ServiceType` from generated `proto.ts`
*/
export declare class HttpServer<ServiceType extends BaseServiceType = any> extends BaseServer<ServiceType> {
readonly options: HttpServerOptions<ServiceType>;
constructor(proto: ServiceProto<ServiceType>, options?: Partial<HttpServerOptions<ServiceType>>);
/** Native `http.Server` of NodeJS */
httpServer?: http_2.Server | https.Server;
/**
* {@inheritDoc BaseServer.start}
*/
start(): Promise<void>;
/**
* {@inheritDoc BaseServer.stop}
*/
stop(): Promise<void>;
}
export declare interface HttpServerOptions<ServiceType extends BaseServiceType> extends BaseServerOptions<ServiceType> {
/** Which port the HTTP server listen to */
port: number;
/**
* HTTPS options, the server would use https instead of http if this value is defined.
* NOTICE: Once you enabled https, you CANNOT visit the server via `http://` anymore.
* If you need visit the server via both `http://` and `https://`, you can start 2 HttpServer (one with `https` and another without).
* @defaultValue `undefined`
*/
https?: {
/**
* @example
* fs.readFileSync('xxx-key.pem');
*/
key: https.ServerOptions["key"];
/**
* @example
* fs.readFileSync('xxx-cert.pem');
*/
cert: https.ServerOptions["cert"];
};
/**
* Passed to the `timeout` property to the native `http.Server` of NodeJS, in milliseconds.
* `0` and `undefined` will disable the socket timeout behavior.
* NOTICE: this `socketTimeout` be `undefined` only means disabling of the socket timeout, the `apiTimeout` is still working.
* `socketTimeout` should always greater than `apiTimeout`.
* @defaultValue `undefined`
* @see {@link https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_server_timeout}
*/
socketTimeout?: number;
/**
* Passed to the `keepAliveTimeout` property to the native `http.Server` of NodeJS, in milliseconds.
* It means keep-alive timeout of HTTP socket connection.
* @defaultValue 5000 (from NodeJS)
* @see {@link https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_server_keepalivetimeout}
*/
keepAliveTimeout?: number;
/**
* Response header value of `Access-Control-Allow-Origin`.
* If this has any value, it would also set `Access-Control-Allow-Headers` as `*`.
* `undefined` means no CORS header.
* @defaultValue `*`
*/
cors?: string;
/**
* Response header value of `Access-Control-Allow-Origin`.
* @defaultValue `3600`
*/
corsMaxAge?: number;
/**
* Actual URL path is `${jsonHostPath}/${apiName}`.
* For example, if `jsonHostPath` is `'/api'`, then you can send `POST /api/a/b/c/Test` to call API `a/b/c/Test`.
* @defaultValue `'/'`
*/
jsonHostPath: string;
}
/**
* A call request by `client.sendMsg()`
* @typeParam Msg - Type of the message
* @typeParam ServiceType - The same `ServiceType` to server, it is used for code auto hint.
*/
export declare abstract class MsgCall<Msg = any, ServiceType extends BaseServiceType = any> extends BaseCall<ServiceType> {
readonly type: "msg";
readonly service: MsgService;
readonly msg: Msg;
constructor(options: MsgCallOptions<Msg, ServiceType>, logger?: PrefixLogger);
}
export declare class MsgCallHttp<Msg = any, ServiceType extends BaseServiceType = any> extends MsgCall<Msg, ServiceType> {
readonly conn: HttpConnection<ServiceType>;
constructor(options: MsgCallHttpOptions<Msg, ServiceType>);
}
export declare interface MsgCallHttpOptions<Msg, ServiceType extends BaseServiceType> extends MsgCallOptions<Msg, ServiceType> {
conn: HttpConnection<ServiceType>;
}
export declare interface MsgCallOptions<Msg, ServiceType extends BaseServiceType> extends BaseCallOptions<ServiceType> {
service: MsgService;
msg: Msg;
}
export declare class MsgCallWs<Msg = any, ServiceType extends BaseServiceType = any> extends MsgCall<Msg, ServiceType> {
readonly conn: WsConnection<ServiceType>;
constructor(options: MsgCallWsOptions<Msg, ServiceType>);
}
export declare interface MsgCallWsOptions<Msg, ServiceType extends BaseServiceType> extends MsgCallOptions<Msg, ServiceType> {
conn: WsConnection<ServiceType>;
}
export declare type MsgHandler<Call extends MsgCall = MsgCall> = (call: Call) => void | Promise<void>;
/**
* Auto add prefix using existed `Logger`
*/
export declare class PrefixLogger implements Logger {
readonly logger: PrefixLoggerOptions["logger"];
readonly prefixs: PrefixLoggerOptions["prefixs"];
constructor(options: PrefixLoggerOptions);
getPrefix(): string[];
log(...args: any[]): void;
debug(...args: any[]): void;
warn(...args: any[]): void;
error(...args: any[]): void;
}
export declare interface PrefixLoggerOptions {
logger: Logger;
prefixs: (string | (() => string))[];
}
export declare type SendReturnMethod<Res> = (ret: ApiReturn<Res>) => ReturnType<BaseConnection["sendData"]>;
export declare enum ServerStatus {
Opening = "OPENING",
Opened = "OPENED",
Closing = "CLOSING",
Closed = "CLOSED"
}
/**
* Print log to terminal, with color.
*/
export declare class TerminalColorLogger implements Logger {
options: TerminalColorLoggerOptions;
private _pid;
constructor(options?: Partial<TerminalColorLoggerOptions>);
private _time;
debug(...args: any[]): void;
log(...args: any[]): void;
warn(...args: any[]): void;
error(...args: any[]): void;
}
export declare interface TerminalColorLoggerOptions {
/**
* Process ID prefix
* @defaultValue `process.pid`
*/
pid: string;
/**
* `undefined` represents not print time
* @defaultValue 'yyyy-MM-dd hh:mm:ss'
*/
timeFormat?: string;
}
/** Version of TSRPC */
export declare const TSRPC_VERSION = "__TSRPC_VERSION__";
/**
* Client for TSRPC WebSocket Server.
* @typeParam ServiceType - `ServiceType` from generated `proto.ts`
*/
export declare class WsClient<ServiceType extends BaseServiceType> extends BaseWsClient<ServiceType> {
readonly options: Readonly<WsClientOptions>;
constructor(proto: ServiceProto<ServiceType>, options?: Partial<WsClientOptions>);
}
export declare interface WsClientOptions extends BaseWsClientOptions {
}
/**
* Connected client
*/
export declare class WsConnection<ServiceType extends BaseServiceType = any> extends BaseConnection<ServiceType> {
readonly type = "LONG";
protected readonly ApiCallClass: typeof ApiCallWs;
protected readonly MsgCallClass: typeof MsgCallWs;
readonly ws: WebSocket_2;
readonly httpReq: http_2.IncomingMessage;
readonly server: WsServer<ServiceType>;
dataType: "text" | "buffer";
isDataTypeConfirmed?: boolean;
constructor(options: WsConnectionOptions<ServiceType>);
private _lastHeartbeatTime;
private _heartbeatInterval?;
get status(): ConnectionStatus;
protected doSendData(data: string | Uint8Array, call?: ApiCallWs): Promise<{
isSucc: true;
} | {
isSucc: false;
errMsg: string;
}>;
protected _rsClose?: () => void;
/** Close WebSocket connection */
close(reason?: string, code?: number, closeTimeout?: number): Promise<void>;
}
export declare interface WsConnectionOptions<ServiceType extends BaseServiceType> extends BaseConnectionOptions<ServiceType> {
server: WsServer<ServiceType>;
ws: WebSocket_2;
httpReq: http_2.IncomingMessage;
onClose: (conn: WsConnection<ServiceType>, code: number, reason: string) => Promise<void>;
dataType: "text" | "buffer";
isDataTypeConfirmed?: boolean;
}
/**
* TSRPC Server, based on WebSocket connection.
* It can support realtime cases.
* @typeParam ServiceType - `ServiceType` from generated `proto.ts`
*/
export declare class WsServer<ServiceType extends BaseServiceType = any> extends BaseServer<ServiceType> {
readonly options: WsServerOptions<ServiceType>;
readonly connections: WsConnection<ServiceType>[];
private readonly _id2Conn;
constructor(proto: ServiceProto<ServiceType>, options?: Partial<WsServerOptions<ServiceType>>);
private _wsServer?;
private _httpServer?;
/**
* {@inheritDoc BaseServer.start}
*/
start(): Promise<void>;
/**
* {@inheritDoc BaseServer.stop}
*/
stop(): Promise<void>;
private _onClientConnect;
private _onClientClose;
/**
* Send the same message to many connections.
* No matter how many target connections are, the message would be only encoded once.
* @param msgName
* @param msg - Message body
* @param connIds - `id` of target connections, `undefined` means broadcast to every connections.
* @returns Send result, `isSucc: true` means the message buffer is sent to kernel, not represents the clients received.
*/
broadcastMsg<T extends string & keyof ServiceType["msg"]>(msgName: T, msg: ServiceType["msg"][T], conns?: WsConnection<ServiceType>[]): Promise<{
isSucc: true;
} | {
isSucc: false;
errMsg: string;
}>;
}
export declare interface WsServerOptions<ServiceType extends BaseServiceType> extends BaseServerOptions<ServiceType> {
/** Which port the WebSocket server is listen to */
port: number;
/** Whether to print `[Connected]` and `[Disconnected]` into log */
logConnect: boolean;
/**
* HTTPS options, the server would use wss instead of http if this value is defined.
* NOTICE: Once you enabled wss, you CANNOT visit the server via `ws://` anymore.
* If you need visit the server via both `ws://` and `wss://`, you can start 2 HttpServer (one with `wss` and another without).
* @defaultValue `undefined`
*/
wss?: {
/**
* @example
* fs.readFileSync('xxx-key.pem');
*/
key: https.ServerOptions["key"];
/**
* @example
* fs.readFileSync('xxx-cert.pem');
*/
cert: https.ServerOptions["cert"];
};
/**
* Close a connection if not receive heartbeat after the time (ms).
* This value should be greater than `client.heartbeat.interval`, for exmaple 2x of it.
* `undefined` or `0` represent disable this feature.
* @defaultValue `undefined`
*/
heartbeatWaitTime?: number;
/**
* WebSocket server options, transfer to 'ws' package.
*/
wsOptions?: WebSocket_2.ServerOptions;
}
export * from "tsrpc-base-client";
export * from "tsrpc-proto";
export { }