
Security News
Risky Biz Podcast: Making Reachability Analysis Work in Real-World Codebases
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.
socketrpc-gen
Advanced tools
socket-rpc
is a powerful command-line tool that automatically generates a type-safe RPC (Remote Procedure Call) layer for your client and server applications using socket.io
. It takes a TypeScript interface as input and generates all the necessary code for you to communicate between your client and server with full type safety. It's unopinionated, meaning it only generates the function bindings and doesn't interfere with your existing socket.io
configuration.
socket.io
setup.RpcError
type.The example
directory in this repository is a great starting point and can be used as a template to bootstrap your own project. It demonstrates a practical project structure that you can adapt for your needs.
Create a TypeScript file (e.g., pkg/rpc/define.ts
) that defines the functions your server and client will expose.
// pkg/rpc/define.ts
/**
* Interface defining the functions available on the RPC server
* These functions can be called remotely by RPC clients
*/
interface ServerFunctions {
/**
* Generates text based on the provided prompt
*/
generateText: (prompt: string) => string;
}
/**
* Interface defining the functions available on the RPC client
* These functions can be called by the RPC server to interact with the client
*/
interface ClientFunctions {
/**
* Displays an error to the client user interface
*/
showError: (error: Error) => void;
/**
* Asks the client a question and expects a response.
* @param question The question to ask the client.
* @returns The client's answer to the question.
*/
askQuestion: (question: string) => string;
}
Important Note: Do not use Promise
in the return types when defining functions in your interfaces (ServerFunctions
, ClientFunctions
). The library automatically wraps the return types in Promise
. The implementation of these functions can be async
and return a Promise
, but the definition should specify the final resolved type. For example, use (prompt: string) => string
instead of (prompt: string) => Promise<string>
.
Use the socketrpc-gen
CLI to generate the RPC code. The generator automatically infers the output directory from the input file path.
bunx socketrpc-gen <path-to-your-interface-file> [options]
For example:
bunx socketrpc-gen ./example/pkg/rpc/define.ts
This will generate a new package in the example/pkg/rpc
directory containing the generated client and server code.
sequenceDiagram
participant ClientApp as "Your Client Application"
participant GenClient as "Generated Client-Side RPC"
participant GenServer as "Generated Server-Side RPC"
participant ServerApp as "Your Server Application"
title socket-rpc: Bidirectional Communication Flow
ClientApp->>GenClient: 1. Calls `generateText("hello")`
activate GenClient
GenClient->>GenServer: 2. Emits "rpc:generateText" event over network
deactivate GenClient
activate GenServer
GenServer->>ServerApp: 3. Invokes your `generateText` handler
activate ServerApp
Note over ServerApp: Server logic decides to<br/>call a function on the client
ServerApp->>GenServer: 4. Calls `askQuestion("Favorite color?")`
GenServer->>GenClient: 5. Emits "rpc:askQuestion" event over network
deactivate GenServer
activate GenClient
GenClient->>ClientApp: 6. Invokes your `askQuestion` handler
activate ClientApp
ClientApp-->>GenClient: 7. Returns answer: "blue"
deactivate ClientApp
GenClient-->>GenServer: 8. Sends response ("blue") back to server
deactivate GenClient
activate GenServer
GenServer-->>ServerApp: 9. `askQuestion` promise resolves with "blue"
Note over ServerApp: Server finishes its logic and<br/>returns the final result
ServerApp-->>GenServer: 10. Returns final result for `generateText`
deactivate ServerApp
GenServer-->>GenClient: 11. Sends final result back to client
deactivate GenServer
activate GenClient
GenClient-->>ClientApp: 12. Original `generateText` promise resolves
deactivate GenClient
Implement the server-side functions and use the generated handlers to process client requests.
// pkg/server/index.ts
import { createServer } from "http";
import { Server } from "socket.io";
import {
handleGenerateText,
showError,
askQuestion,
} from "@socket-rpc/rpc/server.generated";
import { RpcError, isRpcError } from "@socket-rpc/rpc";
const httpServer = createServer();
const io = new Server(httpServer);
io.on("connection", async (socket) => {
// Handle the `generateText` RPC call from the client
handleGenerateText(
socket,
async (prompt: string): Promise<string | RpcError> => {
// Example of server calling a client function and waiting for a response
try {
const clientResponse = await askQuestion(
socket,
"What is your favorite color?",
3000
); // 3s timeout
if (isRpcError(clientResponse)) {
console.error("Client returned an error:", clientResponse.message);
} else {
console.log(`Client's favorite color is: ${clientResponse}`);
}
} catch (e) {
console.error("Did not get a response from client for askQuestion", e);
}
// Example of server calling a fire-and-forget client function
showError(socket, new Error("This is a test error from the server!"));
if (prompt === "error") {
return {
code: "custom_error",
message: "This is a custom error",
data: { a: 1 },
} as RpcError;
} else if (prompt === "throw") {
throw new Error("This is a thrown error");
}
return `Server received: ${prompt}`;
}
);
});
httpServer.listen(8080, () => {
console.log("Server running on http://localhost:8080");
});
Use the generated functions to call server methods and handle server-initiated calls.
// pkg/client/index.ts
import { io } from "socket.io-client";
import {
generateText,
handleShowError,
handleAskQuestion,
} from "@socket-rpc/rpc/client.generated";
import { isRpcError } from "@socket-rpc/rpc";
const socket = io("http://localhost:8080");
// --- Best Practice: Đăng ký các trình xử lý sự kiện MỘT LẦN ở đây ---
// Xử lý RPC `showError` từ server
handleShowError(socket, async (error: Error): Promise<void> => {
console.error("Server sent an error:", error.message);
});
// Xử lý RPC `askQuestion` từ server (và gửi trả lời)
handleAskQuestion(socket, async (question: string) => {
console.log(`Server asked: ${question}`);
return "blue"; // Trả lời câu hỏi của server
});
// --- Logic chạy mỗi khi kết nối thành công (hoặc kết nối lại) sẽ nằm trong này ---
socket.on("connect", async () => {
console.log("Connected to the server!");
// Gọi hàm `generateText` trên server
const response = await generateText(socket, "Hello, server!", 10000); // 10s timeout
if (isRpcError(response)) {
console.error("RPC Error:", response);
} else {
console.log("Server responded:", response);
}
});
socket.on("disconnect", (reason) => {
console.log(`Disconnected from server: ${reason}`);
});
socketrpc-gen
Generates the RPC code from interface definitions.
Usage:
socketrpc-gen <path> [options]
Arguments:
<path>
: Path to the input TypeScript file containing interface definitions. (Required)Options:
-p, --package-name <name>
: The npm package name for the generated RPC code. (Default: "@socket-rpc/rpc")-t, --timeout <ms>
: Default timeout in milliseconds for RPC calls that expect a response. This can be overridden per-call. (Default: "5000")-w, --watch
: Watch for changes in the definition file and regenerate automatically. (Default: false)-h, --help
: Display help for command.The socket-rpc
tool works by parsing your TypeScript interface file and generating a set of functions and handlers that wrap the socket.io
communication layer.
ServerFunctions
interface, it generates:
handle<FunctionName>
function for the server to process incoming requests.<functionName>
function for the client to call the server method.ClientFunctions
interface, it generates:
handle<FunctionName>
function for the client to process incoming requests from the server.<functionName>
function for the server to call the client method.This approach provides a clean and type-safe way to communicate between your client and server, without having to write any boilerplate socket.io
code yourself. It automatically handles acknowledgments for functions that return values and uses fire-and-forget for void
functions.
Synchronous Pattern (Request-Response) Use this pattern when you need to wait for a response:
// define.ts
interface ServerFunctions {
getData: (id: string) => UserData;
}
Asynchronous Pattern (Fire-and-Forget with Callback)
Use this pattern for streaming or progressive updates. Declare the server function as void
and create a client callback to receive responses:
// define.ts
interface ServerFunctions {
startStreaming: (topic: string) => void; // Fire-and-forget
}
interface ClientFunctions {
onStreamData: (data: StreamChunk) => void; // Callback for receiving stream data
onStreamEnd: () => void; // Callback when stream ends
}
Here's a complete example showing how to simulate streaming data from server to client:
1. Interface Definition (pkg/rpc/define.ts
)
interface StreamChunk {
id: number;
content: string;
timestamp: number;
}
interface ServerFunctions {
startDataStream: (topic: string) => void; // Initiate streaming (fire-and-forget)
stopDataStream: () => void; // Stop streaming
}
interface ClientFunctions {
onStreamChunk: (chunk: StreamChunk) => void; // Receive stream data
onStreamComplete: (totalChunks: number) => void; // Stream finished
onStreamError: (error: string) => void; // Stream error
}
2. Server Implementation
import {
handleStartDataStream,
handleStopDataStream,
onStreamChunk,
onStreamComplete,
onStreamError
} from "@socket-rpc/rpc/server.generated";
io.on("connection", (socket) => {
let streamInterval: NodeJS.Timeout | null = null;
// Handle stream start request
handleStartDataStream(socket, async (topic: string) => {
console.log(`Starting stream for topic: ${topic}`);
let chunkId = 0;
const maxChunks = 10;
streamInterval = setInterval(async () => {
if (chunkId >= maxChunks) {
clearInterval(streamInterval!);
streamInterval = null;
// Notify client that stream is complete
onStreamComplete(socket, maxChunks);
return;
}
// Send stream chunk to client
const chunk: StreamChunk = {
id: chunkId++,
content: `Data chunk for ${topic} #${chunkId}`,
timestamp: Date.now()
};
onStreamChunk(socket, chunk);
}, 500); // Send chunk every 500ms
});
// Handle stream stop request
handleStopDataStream(socket, async () => {
if (streamInterval) {
clearInterval(streamInterval);
streamInterval = null;
console.log("Stream stopped by client request");
}
});
// Clean up on disconnect
socket.on("disconnect", () => {
if (streamInterval) {
clearInterval(streamInterval);
}
});
});
3. Client Implementation
import {
startDataStream,
stopDataStream,
handleOnStreamChunk,
handleOnStreamComplete,
handleOnStreamError
} from "@socket-rpc/rpc/client.generated";
const socket = io("http://localhost:8080");
// Set up stream handlers
handleOnStreamChunk(socket, async (chunk: StreamChunk) => {
console.log(`Received chunk ${chunk.id}: ${chunk.content} at ${new Date(chunk.timestamp).toISOString()}`);
});
handleOnStreamComplete(socket, async (totalChunks: number) => {
console.log(`Stream completed! Received ${totalChunks} chunks total.`);
});
handleOnStreamError(socket, async (error: string) => {
console.error("Stream error:", error);
});
socket.on("connect", () => {
// Start streaming data
startDataStream(socket, "user-activity");
// Stop stream after 8 seconds
setTimeout(() => {
stopDataStream(socket);
}, 8000);
});
This pattern enables real-time data streaming while maintaining type safety. The server uses fire-and-forget functions to initiate streams, then uses client callback functions to progressively send data chunks.
FAQs
Code generator for Socket.IO RPC packages using ts-morph.
The npm package socketrpc-gen receives a total of 0 weekly downloads. As such, socketrpc-gen popularity was classified as not popular.
We found that socketrpc-gen demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.
Security News
/Research
Malicious Nx npm versions stole secrets and wallet info using AI CLI tools; Socket’s AI scanner detected the supply chain attack and flagged the malware.
Security News
CISA’s 2025 draft SBOM guidance adds new fields like hashes, licenses, and tool metadata to make software inventories more actionable.