JSON-RPC 2.0 implementation over WebSockets for Node.js and browser with bidirectional communication support
—
JSON-RPC 2.0 server implementation with method registration, event management, namespace support, and authentication middleware for building real-time WebSocket applications.
Creates a WebSocket RPC server with configurable options and data serialization.
/**
* WebSocket RPC server implementation
* Extends EventEmitter for connection and error handling
*/
class Server extends EventEmitter {
/** WebSocket Server instance for direct access to underlying server */
wss: InstanceType<typeof WebSocketServer>;
/**
* Instantiate a Server class
* @param options - WebSocket server constructor parameters
* @param dataPack - custom data pack for encoding/decoding messages
* @returns new Server instance
*/
constructor(
options: NodeWebSocket.ServerOptions,
dataPack?: DataPack<object, string>
);
}Usage Examples:
import { Server } from "rpc-websockets";
// Basic server on specific port
const server = new Server({ port: 8080 });
// Server with host and port
const server = new Server({
host: "localhost",
port: 8080
});
// Server with custom options
const server = new Server({
port: 8080,
perMessageDeflate: false,
maxPayload: 16 * 1024 * 1024 // 16MB
});
// Server with external HTTP server
import { createServer } from "http";
const httpServer = createServer();
const server = new Server({ server: httpServer });
httpServer.listen(8080);Register RPC methods that clients can call, with support for protection levels.
/**
* Registers an RPC method
* @param name - method name that clients will call
* @param fn - handler function receiving parameters and socket ID
* @param ns - namespace identifier (default: "/")
* @returns IMethod object with protection control methods
* @throws TypeError for invalid parameters
*/
register(
name: string,
fn: (params: IRPCMethodParams, socket_id: string) => any,
ns?: string
): IMethod;
interface IMethod {
/** Mark method as protected (requires authentication) */
protected(): void;
/** Mark method as public (default, no authentication required) */
public(): void;
}
interface IRPCMethodParams {
[x: string]: any;
}Usage Examples:
// Simple public method
server.register("sum", (params) => {
return params[0] + params[1];
});
// Method with socket access
server.register("getUserInfo", (params, socket_id) => {
console.log(`Request from socket: ${socket_id}`);
return { id: params.id, name: "User Name" };
});
// Protected method requiring authentication
server.register("getSecretData", () => {
return { secret: "confidential information" };
}).protected();
// Method in custom namespace
server.register("adminCommand", (params) => {
return { status: "executed", command: params.command };
}, "/admin");
// Async method
server.register("fetchData", async (params) => {
const response = await fetch(params.url);
return await response.json();
});Configure authentication middleware for protecting methods and events.
/**
* Sets an auth method for login functionality
* @param fn - authentication function returning Promise<boolean>
* @param ns - namespace identifier (default: "/")
*/
setAuth(
fn: (params: IRPCMethodParams, socket_id: string) => Promise<boolean>,
ns?: string
): void;Usage Examples:
// Simple username/password authentication
server.setAuth(async (params, socket_id) => {
const { username, password } = params;
// Validate credentials (example)
if (username === "admin" && password === "secret") {
console.log(`User ${username} authenticated from socket ${socket_id}`);
return true;
}
return false;
});
// Database-based authentication
server.setAuth(async (params, socket_id) => {
try {
const user = await database.validateUser(params.token);
if (user) {
console.log(`User ${user.id} authenticated`);
return true;
}
} catch (error) {
console.error("Auth error:", error);
}
return false;
});
// Namespace-specific authentication
server.setAuth(async (params) => {
return params.adminKey === "admin-secret";
}, "/admin");Create and manage events that can be emitted to subscribed clients.
/**
* Creates a new event that can be emitted to clients
* @param name - event name that clients can subscribe to
* @param ns - namespace identifier (default: "/")
* @returns IEvent object with protection control methods
* @throws TypeError if event already exists
*/
event(name: string, ns?: string): IEvent;
/**
* Lists all created events in a given namespace
* @param ns - namespace identifier (default: "/")
* @returns array of event names
*/
eventList(ns?: string): string[];
interface IEvent {
/** Mark event as protected (requires authentication to subscribe) */
protected(): void;
/** Mark event as public (default, no authentication required) */
public(): void;
}Usage Examples:
// Create public events
server.event("userJoined");
server.event("messagePosted");
server.event("feedUpdated");
// Create protected event
server.event("adminAlert").protected();
// Emit events to subscribers
server.emit("userJoined", {
userId: 123,
username: "alice",
timestamp: Date.now()
});
server.emit("messagePosted", {
messageId: 456,
content: "Hello world!",
author: "alice"
});
// List available events
const events = server.eventList();
console.log("Available events:", events);
// Events in specific namespace
server.event("specialEvent", "/admin");
const adminEvents = server.eventList("/admin");Organize methods and events into separate namespaces for multi-tenant applications.
/**
* Returns a requested namespace object with convenience methods
* @param name - namespace identifier
* @returns namespace object with scoped methods
*/
of(name: string): {
/** Register method in this namespace */
register(fn_name: string, fn: (params: IRPCMethodParams) => any): IMethod;
/** Create event in this namespace */
event(ev_name: string): IEvent;
/** Get list of events in this namespace */
readonly eventList: string[];
/** Emit event to this namespace */
emit(event: string, ...params: Array<string>): void;
/** Get namespace name */
readonly name: string;
/** Get connected clients in this namespace */
connected(): {};
/** Get namespace client data */
clients(): {
rpc_methods: IRPCMethod;
clients: Map<string, IClientWebSocket>;
events: INamespaceEvent;
};
};
/**
* Removes a namespace and closes all connections
* @param ns - namespace identifier
*/
closeNamespace(ns: string): void;Usage Examples:
// Work with admin namespace
const adminNS = server.of("/admin");
// Register methods in namespace
adminNS.register("restart", () => {
return { status: "restarting" };
});
adminNS.register("getStats", () => {
return { users: 100, uptime: "2 days" };
});
// Create events in namespace
adminNS.event("systemAlert");
// Emit to namespace
adminNS.emit("systemAlert", "High memory usage detected");
// Get namespace info
console.log("Admin namespace events:", adminNS.eventList);
console.log("Connected admin clients:", Object.keys(adminNS.connected()));
// Close namespace when done
server.closeNamespace("/admin");Create JSON-RPC 2.0 compliant error responses and handle server errors.
/**
* Creates a JSON-RPC 2.0 compliant error
* @param code - indicates the error type that occurred
* @param message - provides a short description of the error
* @param data - details containing additional information about the error
* @returns JSON-RPC error object
*/
createError(code: number, message: string, data: string | object): {
code: number;
message: string;
data: string | object;
};Standard JSON-RPC Error Codes:
-32700: Parse error-32600: Invalid Request-32601: Method not found-32602: Invalid params-32603: Internal error-32000: Event not provided-32604: Params not found-32605: Method forbidden-32606: Event forbiddenUsage Examples:
// Method that returns custom error
server.register("validateData", (params) => {
if (!params.email) {
throw server.createError(-32602, "Invalid params", "Email is required");
}
if (!params.email.includes("@")) {
throw server.createError(-32000, "Validation failed", "Invalid email format");
}
return { valid: true };
});
// Handle server errors
server.on("error", (error) => {
console.error("Server error:", error);
});
// Handle socket errors
server.on("socket-error", (socket, error) => {
console.error(`Socket ${socket._id} error:`, error);
});Control server startup, shutdown, and connection handling.
/**
* Closes the server and terminates all clients
* @returns Promise resolving when server is closed
*/
close(): Promise<void>;Usage Examples:
// Server lifecycle events
server.on("listening", () => {
console.log("Server listening on port 8080");
});
server.on("connection", (socket, request) => {
console.log(`New connection: ${socket._id}`);
console.log(`URL: ${request.url}`);
});
server.on("disconnection", (socket) => {
console.log(`Client disconnected: ${socket._id}`);
});
// Graceful shutdown
process.on("SIGTERM", async () => {
console.log("Shutting down server...");
await server.close();
process.exit(0);
});interface IRPCMethod {
[x: string]: {
fn: (params: IRPCMethodParams, socket_id: string) => any;
protected: boolean;
};
}
interface INamespaceEvent {
[x: string]: {
sockets: Array<string>;
protected: boolean;
};
}
interface IClientWebSocket extends NodeWebSocket {
_id: string;
_authenticated: boolean;
}
interface IRPCError {
code: number;
message: string;
data?: string;
}The server emits the following events during its lifecycle:
listening: Fired when server starts listening for connectionsconnection: Fired when a new client connects (socket, request)disconnection: Fired when a client disconnects (socket)error: Fired when server errors occur (error)socket-error: Fired when individual socket errors occur (socket, error)close: Fired when server is closed