wss-rpc2
WebSocket Server and Client implementation of the JSON RPC2 spec.
Powered by ws.
Basic functionality, according to the spec:
- Method call: client -> server (request/response)
- Notifications: client -> server (notification)
Extended functionality:
- Events: server -> client (to all / to some / to a single client)
- Reconnect and keep alive
- Stateful client connection
- The client works in both node and browser
Quick start
Installation:
npm i wss-rpc2
Backend / Server:
import { RPCServer, RPCEvent } from 'wss-rpc2'
const server = new RPCServer({ port: 3000 })
server.registerMethod('multiply', (params) => {
return params[0] * params[1]
})
Frontend / Client:
import { RPCClient } from 'wss-rpc2'
const client = new RPCClient('ws://localhost:3000')
const result = await client.call('multiply', [3, 5])
See more examples.
RPCServer API
new RPCServer(options: IRPCServerOptions)
Parameters:
-
options: IRPCServerOptions<State = any> extends ServerOptions
-
wss?: ws.WebSocketServer
- existing WebSocketServer (ws) instance -
keepAlive?: number
- ping clients every N ms (default: 300000 // 5 min) -
stateFactory?: () => State
- initial state factory of the connected client -
...ServerOptions
(see ws library)
-
RPCServer.registerMethod(name: string, method: IRPCMethod): void
Parameters:
-
name: string
- name of the RPC method -
method: IRPCMethod
- RPC method function, see types
RPCServer.callMethod(name: string, params: IRPCParams): Promise<any>
Call a method explicitly
Parameters:
-
name: string
- name of the RPC method -
params: IRPCParams
- params of the RPC method
RPCServer.getConnections(): RPCConnection[]
Returns active connection instances.
RPCServer.close(): Promise
Closes all active client's connections and stops the server.
RPCServer.on(eventName: string, listener: Function, once?: boolean): Function
Subscribe a listener to the event. Returns unsubscribe function.
RPCServer Events:
-
listening (params: empty)
- on server start -
connect (params: RPCConnection)
- on client connect -
disconnect (params: RPCConnection)
- on client disconnect -
request (params: RPCRequest)
- on after request received -
response (params: RPCResponse)
- on before response sent -
error (params: Error | unknown)
- on server error -
close (params: empty)
- on server stop
RPCClient API
new RPCClient(address: string, options?: IRPCClientOptions)
Parameters:
-
address: string
- URL of the RPCServer -
options: IRPCClientOptions
-
autoConnect?: boolean
- if false, then useclient.connect()
(default: true) -
reconnectIntervals?: number[]
- reconnect intervals sequence. Example: [1000, 1500, 2000] will wait between reconnect: 1000ms, 1500ms, 2000ms, ..., 2000ms, untilreconnectLimit
is reached (default: [1000] - every 1000 ms) -
reconnectLimit?: number
- 0 - unlimited, -1 - disabled (default: 1000) -
requestTimeout?: number
- request timeout (default: 10000)
-
RPCClient.state: 'init' | 'connecting' | 'connected' | 'stopped'
Client connection state:
-
init
- before connect() has been called -
connecting
- connection or reconnection is in process -
connected
- active connection state -
stopped
- if disconnect() has been called or reconnectLimit is reached
RPCClient.connected: Promise<void>
Connection promise. However, it is not necessary to wait for a connection, because requests will be queued up until the connection is established.
const client = new RPCClient()
await client.connected
// connected!
RPCClient.connect(): Promise<void>
Manual connect, if autoConnect = false
const client = new RPCClient({ autoConnect: false })
await client.connect()
// connected!
RPCClient.disconnect(): Promise<void>
Disconnects and stops a reconnect-observer.
RPCClient.call(method: string, params?: IRPCParams): Promise<RPCResponse>
Calls the method and waits for a response.
RPCClient.notify(method: string, params?: IRPCParams): void
Notifies the server without waiting for a response.
RPCClient.on(eventName: string, listener: Function, once?: boolean): Function
Subscribe a listener to the event. Returns unsubscribe function.
RPCClient Events:
-
connect (params: empty)
- on connect -
disconnect (params: empty)
- on disconnect -
error (params: Error | unknown)
- on error -
request (params: IRPCRequestObject)
- on before request sent -
response (params: IRPCResponseObject)
- on response received -
event (params: RPCEvent)
- on server event received
RPCConnection <State = unknown> API
id: string
Connection identifier
ws: WebSocket
Browser WebSocket object (isomorphic-ws
is used for the node client)
state?: State
Domain state of the connection, if defined by IRPCServerOptions.stateFactory (and State generic for TS).
lastActivity: number = 0
Last client's activity timestamp
RPCConnection.emit (event: RPCEvent, cb?: (e: Error) => void)
Emit an event to the client. Example:
const event = new RPCEvent({ event: 'hello', params: {} })
server.getConnections().forEach(connection => {
connection.emit(event, e => console.error(e))
})
Types
interface IRPCServerOptions extends ws.ServerOptions {
wss?: WebSocketServer
keepAlive?: number
stateFactory?: () => State
}
interface IRPCClientOptions {
autoConnect?: boolean
reconnectIntervals?: number[]
reconnectLimit?: number
requestTimeout?: number
}
type IRPCParams = { [key: string]: unknown } | unknown[]
type IRPCMethod<Req extends IRPCParams = any, Res = any, State = any>
= (params: Req, connection: RPCConnection<State>) => Res
interface IRPCRequestObject {
jsonrpc: '2.0'
id?: string | number
method: string
params?: IRPCParams
}
interface IRPCResponseObject {
jsonrpc: '2.0'
id: string | number | null
result?: any
error?: IRPCError
}
interface IRPCError {
code: number
message: string
data?: unknown
}
Typescript
Typescript support provides several useful features for typing request, responses and connection state.
import { RPCServer } from 'wss-rpc2'
const server = new RPCServer<IAppUserState>({
stateFactory: () => ({ authorized: false })
})
server.registerMethod<IRpcLogin['request'], IRpcLogin['response']>('login', async (params, connection) => {
const user = await auth(params)
if (user) {
connection.state.authorized = true
connection.state.email = user.email
connection.state.id = user.id
}
return user
})
interface IAppUserState {
authorized: boolean
email?: string
id?: string
}
interface IRpcLogin {
request: {
login: string
password: string
}
response: {
user?: {
id: string
email: string
}
}
}
import { RPCClient } from 'wss-rpc2'
const client = new RPCClient(url)
const { error, result } = await client.call<{ user: IUser | null }>('login', { email, password })
console.log(result?.user?.id)
Examples
To be done
Also see tests