diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..65ba810 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,7 @@ +import RPCClient, { IHandlersOption } from "./lib/client"; +import RPCServer from "./lib/server"; +export { createRPCError } from "./lib/util"; +export { createValidator } from "./lib/validator"; +export { NOREPLY } from "./lib/symbols"; +export { RPCError, RPCFormatViolationError, RPCFormationViolationError, RPCFrameworkError, RPCGenericError, RPCInternalError, RPCMessageTypeNotSupportedError, RPCNotImplementedError, RPCNotSupportedError, RPCOccurenceConstraintViolationError, RPCOccurrenceConstraintViolationError, RPCPropertyConstraintViolationError, RPCProtocolError, RPCSecurityError, RPCTypeConstraintViolationError, TimeoutError, UnexpectedHttpResponse, WebsocketUpgradeError, } from "./lib/errors"; +export { RPCServer, RPCClient, IHandlersOption }; diff --git a/lib/client.d.ts b/lib/client.d.ts new file mode 100644 index 0000000..3c2926c --- /dev/null +++ b/lib/client.d.ts @@ -0,0 +1,146 @@ +/// +import EventEmitter from "events"; +import { Validator } from "./validator"; +import WebSocket from "ws"; +import EventBuffer from "./event-buffer"; +export interface RPC_ClientOptions { + identity: string; + reconnect: boolean; + callTimeoutMs: number; + pingIntervalMs: number; + deferPingsOnActivity: boolean; + respondWithDetailedErrors: boolean; + callConcurrency: number; + strictMode: boolean | string[]; + strictModeValidators: Validator[]; + maxBadMessages: number; + protocols: string[]; + endpoint?: string; + password: string | null; + wsOpts: any; + headers: any; + maxReconnects: number; + query?: string | Record; + backoff: { + initialDelay: number; + maxDelay: number; + factor: number; + randomisationFactor: number; + }; +} +export interface IHandlersOption { + messageId?: string; + method?: string; + params?: Record; + signal?: AbortSignal; + reply?: unknown; +} +type IHandlers = ({ params, reply, method, signal, messageId, }: IHandlersOption) => Promise>; +declare class RPC_Client extends EventEmitter { + protected _identity?: string; + private _wildcardHandler; + private _handlers; + protected _state: number; + private _callQueue; + protected _ws?: WebSocket; + private _wsAbortController?; + private _keepAliveAbortController?; + private _pendingPingResponse; + private _lastPingTime; + private _closePromise?; + private _protocolOptions; + protected _protocol?: string; + private _strictProtocols; + private _strictValidators?; + private _pendingCalls; + private _pendingResponses; + private _outboundMsgBuffer; + private _connectedOnce; + private _backoffStrategy?; + private _badMessagesCount; + private _reconnectAttempt; + protected _options: RPC_ClientOptions; + private _connectionUrl; + private _connectPromise; + private _nextPingTimeout; + static OPEN: number; + static CONNECTING: number; + static CLOSING: number; + static CLOSED: number; + constructor({ ...options }: RPC_ClientOptions); + get identity(): string | undefined; + get protocol(): string | undefined; + get state(): number; + reconfigure(options: RPC_ClientOptions): void; + /** + * Attempt to connect to the RPCServer. + * @returns {Promise} Resolves when connected, rejects on failure + */ + connect(): Promise; + private _keepAlive; + private _tryReconnect; + private _beginConnect; + /** + * Start consuming from a WebSocket + * @param {WebSocket} ws - A WebSocket instance + * @param {EventBuffer} leadMsgBuffer - A buffer which traps all 'message' events + */ + protected _attachWebsocket(ws: WebSocket, leadMsgBuffer?: EventBuffer): void; + private _handleDisconnect; + private _rejectPendingCalls; + /** + * Call a method on a remote RPCClient or RPCServerClient. + * @param {string} method - The RPC method to call. + * @param {*} params - A value to be passed as params to the remote handler. + * @param {Object} options - Call options + * @param {number} options.callTimeoutMs - Call timeout (in milliseconds) + * @param {AbortSignal} options.signal - AbortSignal to cancel the call. + * @param {boolean} options.noReply - If set to true, the call will return immediately. + * @returns Promise<*> - Response value from the remote handler. + */ + call(method: any, params?: any, options?: Record): Promise; + private _call; + /** + * Closes the RPCClient. + * @param {Object} options - Close options + * @param {number} options.code - The websocket CloseEvent code. + * @param {string} options.reason - The websocket CloseEvent reason. + * @param {boolean} options.awaitPending - Wait for in-flight calls & responses to complete before closing. + * @param {boolean} options.force - Terminate websocket immediately without passing code, reason, or waiting. + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code CloseEvent codes} + * @returns Promise - The CloseEvent (code & reason) for closure. May be different from requested code & reason. + */ + close({ code, reason, awaitPending, force, }?: { + code?: number; + reason?: string; + awaitPending?: any; + force?: any; + }): Promise<{ + code: number | undefined; + reason: string | undefined; + } | undefined>; + private _awaitUntilPendingSettled; + private _deferNextPing; + private _onMessage; + private _onCall; + private _onCallResult; + private _onCallError; + /** + * Send a message to the RPCServer. While socket is connecting, the message is queued and send when open. + * @param {Buffer|String} message - String to send via websocket + */ + sendRaw(message: string): void; + /** + * + * @param {string} [method] - The name of the handled method. + */ + removeHandler(method: string): void; + removeAllHandlers(): void; + /** + * + * @param {string} [method] - The name of the RPC method to handle. + * @param {Function} handler - A function that can handle incoming calls for this method. + */ + handle(method: string | IHandlers, handler?: IHandlers): void; +} +export default RPC_Client; diff --git a/lib/errors.d.ts b/lib/errors.d.ts new file mode 100644 index 0000000..cb80b46 --- /dev/null +++ b/lib/errors.d.ts @@ -0,0 +1,92 @@ +export declare class TimeoutError extends Error { +} +export declare class UnexpectedHttpResponse extends Error { + code: any; + request: any; + response: any; +} +export declare class RPCError extends Error { + rpcErrorMessage: string; + rpcErrorCode: string; +} +export declare class RPCGenericError extends RPCError { + rpcErrorMessage: string; + rpcErrorCode: string; +} +export declare class RPCNotImplementedError extends RPCError { + rpcErrorMessage: string; + rpcErrorCode: string; +} +export declare class RPCNotSupportedError extends RPCError { + rpcErrorMessage: string; + rpcErrorCode: string; +} +export declare class RPCInternalError extends RPCError { + rpcErrorMessage: string; + rpcErrorCode: string; +} +export declare class RPCProtocolError extends RPCError { + rpcErrorMessage: string; + rpcErrorCode: string; +} +export declare class RPCSecurityError extends RPCError { + rpcErrorMessage: string; + rpcErrorCode: string; +} +export declare class RPCFormatViolationError extends RPCError { + rpcErrorMessage: string; + rpcErrorCode: string; +} +export declare class RPCFormationViolationError extends RPCError { + rpcErrorMessage: string; + rpcErrorCode: string; +} +export declare class RPCPropertyConstraintViolationError extends RPCError { + rpcErrorMessage: string; + rpcErrorCode: string; +} +export declare class RPCOccurenceConstraintViolationError extends RPCError { + rpcErrorMessage: string; + rpcErrorCode: string; +} +export declare class RPCOccurrenceConstraintViolationError extends RPCError { + rpcErrorMessage: string; + rpcErrorCode: string; +} +export declare class RPCTypeConstraintViolationError extends RPCError { + rpcErrorMessage: string; + rpcErrorCode: string; +} +export declare class RPCMessageTypeNotSupportedError extends RPCError { + rpcErrorMessage: string; + rpcErrorCode: string; +} +export declare class RPCFrameworkError extends RPCError { + rpcErrorMessage: string; + rpcErrorCode: string; +} +export declare class WebsocketUpgradeError extends Error { + code: any; + constructor(code: any, message: string | undefined); +} +declare const _default: { + WebsocketUpgradeError: typeof WebsocketUpgradeError; + TimeoutError: typeof TimeoutError; + UnexpectedHttpResponse: typeof UnexpectedHttpResponse; + RPCError: typeof RPCError; + RPCGenericError: typeof RPCGenericError; + RPCNotImplementedError: typeof RPCNotImplementedError; + RPCNotSupportedError: typeof RPCNotSupportedError; + RPCInternalError: typeof RPCInternalError; + RPCProtocolError: typeof RPCProtocolError; + RPCSecurityError: typeof RPCSecurityError; + RPCFormatViolationError: typeof RPCFormatViolationError; + RPCFormationViolationError: typeof RPCFormationViolationError; + RPCPropertyConstraintViolationError: typeof RPCPropertyConstraintViolationError; + RPCOccurrenceConstraintViolationError: typeof RPCOccurrenceConstraintViolationError; + RPCOccurenceConstraintViolationError: typeof RPCOccurenceConstraintViolationError; + RPCTypeConstraintViolationError: typeof RPCTypeConstraintViolationError; + RPCMessageTypeNotSupportedError: typeof RPCMessageTypeNotSupportedError; + RPCFrameworkError: typeof RPCFrameworkError; +}; +export default _default; diff --git a/lib/event-buffer.d.ts b/lib/event-buffer.d.ts new file mode 100644 index 0000000..8f78170 --- /dev/null +++ b/lib/event-buffer.d.ts @@ -0,0 +1,12 @@ +/// +/// +import { EventEmitter } from "stream"; +declare class EventBuffer { + private _emitter; + private _event; + private _collector; + private _buffer; + constructor(emitter: EventEmitter, event: string | symbol); + condense(): any; +} +export default EventBuffer; diff --git a/lib/queue.d.ts b/lib/queue.d.ts new file mode 100644 index 0000000..178b845 --- /dev/null +++ b/lib/queue.d.ts @@ -0,0 +1,10 @@ +declare class Queue { + private _pending; + private _concurrency; + private _queue; + constructor(); + setConcurrency(concurrency: number): void; + push(fn: any): Promise; + private _next; +} +export default Queue; diff --git a/lib/server.d.ts b/lib/server.d.ts new file mode 100644 index 0000000..b72a467 --- /dev/null +++ b/lib/server.d.ts @@ -0,0 +1,41 @@ +/// +/// +/// +/// +/// +import { IncomingMessage, Server } from "http"; +import { ServerOptions } from "ws"; +import { EventEmitter } from "stream"; +import { Validator } from "./validator"; +import { IHandshakeInterface } from "./serverClient"; +import { Socket } from "net"; +interface IOccpServiceOptions { + wssOptions?: ServerOptions; + protocols?: string[]; + callTimeoutMs?: number; + pingIntervalMs?: number; + deferPingsOnActivity?: boolean; + respondWithDetailedErrors?: boolean; + callConcurrency?: number; + maxBadMessages?: number; + strictMode?: boolean | string[]; + strictModeValidators?: Validator[]; +} +declare class RPCServer extends EventEmitter { + private _httpServerAbortControllers; + private _state; + private _clients; + private _pendingUpgrades; + private _options; + private _wss; + private _strictValidators; + authCallback: (accept: (session?: Record, protocol?: string | false) => void, reject: (code: number, message: string) => void, handshake: IHandshakeInterface, signal: AbortSignal) => void; + constructor({ ...options }: IOccpServiceOptions, _callback?: () => void); + reconfigure(options: any): void; + private _onConnection; + get handleUpgrade(): (request: IncomingMessage, socket: Socket, head: Buffer) => Promise; + auth(cb: (accept: (session?: Record, protocol?: string | false) => void, reject: (code: number, message: string) => void, handshake: IHandshakeInterface, signal?: AbortSignal) => void): void; + listen(port: any, host?: any, options?: Record): Promise>; + close({ code, reason, awaitPending, force }: Record): Promise; +} +export default RPCServer; diff --git a/lib/serverClient.d.ts b/lib/serverClient.d.ts new file mode 100644 index 0000000..fa61410 --- /dev/null +++ b/lib/serverClient.d.ts @@ -0,0 +1,28 @@ +/// +/// +import WebSocket from "ws"; +import RPC_Client, { RPC_ClientOptions } from "./client"; +import { IncomingHttpHeaders, IncomingMessage } from "http"; +export interface IHandshakeInterface { + remoteAddress: string | undefined; + headers: IncomingHttpHeaders; + protocols: Set; + endpoint: string; + identity: string; + query: URLSearchParams; + request: IncomingMessage; + password: Buffer | undefined; +} +declare class RpcServerClient extends RPC_Client { + private _session; + private _handshake; + constructor({ ...options }: RPC_ClientOptions, { ws, handshake, session, }: { + ws: WebSocket; + session: Record; + handshake: IHandshakeInterface; + }); + get handshake(): IHandshakeInterface; + get session(): Record; + connect(): Promise; +} +export default RpcServerClient; diff --git a/lib/standard-validators.d.ts b/lib/standard-validators.d.ts new file mode 100644 index 0000000..1e29ef0 --- /dev/null +++ b/lib/standard-validators.d.ts @@ -0,0 +1,2 @@ +declare const _default: readonly [import("./validator").Validator, import("./validator").Validator]; +export default _default; diff --git a/lib/symbols.d.ts b/lib/symbols.d.ts new file mode 100644 index 0000000..af3bbc8 --- /dev/null +++ b/lib/symbols.d.ts @@ -0,0 +1,4 @@ +export declare const NOREPLY: { + NOREPLY: symbol; +}; +export default NOREPLY; diff --git a/lib/util.d.ts b/lib/util.d.ts new file mode 100644 index 0000000..b14f0da --- /dev/null +++ b/lib/util.d.ts @@ -0,0 +1,9 @@ +export declare function getPackageIdent(): string; +export declare function getErrorPlainObject(err: Error): any; +export declare function createRPCError(type: string, message?: any, details?: {}): Record; +declare const _default: { + getErrorPlainObject: typeof getErrorPlainObject; + createRPCError: typeof createRPCError; + getPackageIdent: typeof getPackageIdent; +}; +export default _default; diff --git a/lib/validator.d.ts b/lib/validator.d.ts new file mode 100644 index 0000000..d2a3a2f --- /dev/null +++ b/lib/validator.d.ts @@ -0,0 +1,9 @@ +import Ajv, { AnySchema, AsyncSchema, SchemaObject } from "ajv"; +export declare class Validator { + _subprotocol: string; + _ajv: Ajv; + constructor(subprotocol: string, ajv: Ajv); + get subprotocol(): string; + validate(schemaId: string, params: any): boolean | Promise; +} +export declare function createValidator(subprotocol: string, json: boolean | SchemaObject | AsyncSchema | AnySchema[]): Validator; diff --git a/lib/ws-util.d.ts b/lib/ws-util.d.ts new file mode 100644 index 0000000..f6dbb08 --- /dev/null +++ b/lib/ws-util.d.ts @@ -0,0 +1,18 @@ +/// +import { Socket } from "net"; +export declare function abortHandshake(socket: Socket, code: number | string, message?: string, headers?: Record): void; +export declare function parseSubprotocols(header: string): Set; +/** + * Checks if a status code is allowed in a close frame. + * + * @param {Number} code The status code + * @return {Boolean} `true` if the status code is valid, else `false` + * @public + */ +export declare function isValidStatusCode(code: number): boolean; +declare const _default: { + abortHandshake: typeof abortHandshake; + parseSubprotocols: typeof parseSubprotocols; + isValidStatusCode: typeof isValidStatusCode; +}; +export default _default; diff --git a/package-lock.json b/package-lock.json index 23ac94b..d072206 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "nyc": "^15.1.0" }, "engines": { - "node": ">=17.2.0" + "node": ">=17.3.0" } }, "node_modules/@ampproject/remapping": {