TypeScript Client Library
The Virtufin WebSocketManager TypeScript client library (@virtufin/websocketmanager) provides a TypeScript interface for managing external WebSocket connections using gRPC-web and Connect-RPC.
Installation
From Gitea npm Registry
Map the @virtufin scope to the registry and add a token (scope: read:package):
cat >> ~/.npmrc <<'EOF'
@virtufin:registry=https://gitea.haenerconsulting.com/api/packages/virtufin/npm/
//gitea.haenerconsulting.com/api/packages/virtufin/npm/:_authToken=<your-gitea-token>
EOF
Use gitea.haenerconsulting.com, not the npm.* alias: Gitea advertises
dist.tarball on gitea.* whatever host you query, so the npm.* form fetches
metadata authenticated and then follows an unauthenticated redirect for the
payload — npm matches tokens by URL prefix.
Then install:
npm install @virtufin/websocketmanager
From Local Source
{
"dependencies": {
"@virtufin/websocketmanager": "file:path/to/src/typescript"
}
}
Overview
The client library provides the WebSocketManagerClient class as the main entry point for WebSocket connection management. Methods take plain positional arguments, not a request object — the request message is built internally.
Quick Start
import { WebSocketManagerClient } from "@virtufin/websocketmanager";
const client = new WebSocketManagerClient({ url: "http://localhost:5001" });
// Connect to a WebSocket server
const { id: connectionId, status } = await client.connect("wss://echo.websocket.org");
console.log(`Connected: ${connectionId} - ${status}`);
// List all connections
const { connections } = await client.list();
console.log(`Total connections: ${connections.length}`);
// Send a message
const encoder = new TextEncoder();
const { response } = await client.send(
connectionId,
encoder.encode(JSON.stringify({ type: "ping" })),
"application/json"
);
console.log(`Response: ${new TextDecoder().decode(response)}`);
// Disconnect
await client.disconnect(connectionId);
WebSocketManagerClient
Constructor
const client = new WebSocketManagerClient({ url: string, timeout?: number });
Parameters:
- url - The gRPC-web base URL (e.g., "http://localhost:5001")
- timeout - Request timeout in milliseconds (default: 30000)
Methods
Connection Management
// Connect to a WebSocket server
connect(url: string, autoReconnect?: boolean): Promise<ConnectResponse>
// Disconnect from a WebSocket server
disconnect(id: string): Promise<DisconnectResponse>
// List all managed connections
list(): Promise<ListResponse>
Example:
const { id: connectionId, status } = await client.connect("wss://echo.websocket.org", true);
const { connections } = await client.list();
for (const conn of connections) {
console.log(`${conn.id}: ${conn.url} [${conn.status}]`);
}
await client.disconnect(connectionId);
Pub/Sub Integration
// Start publishing WebSocket messages to a Dapr pub/sub topic
startPublish(id: string, topic: string): Promise<StartPublishResponse>
// Stop publishing to the topic
stopPublish(id: string): Promise<StopPublishResponse>
Example:
await client.startPublish(connectionId, "websocket-events");
await client.stopPublish(connectionId);
Messaging
Message payloads are raw bytes (Uint8Array) with an explicit content type — encode/decode text yourself.
// Send a message and wait for a correlated response
send(id: string, message: Uint8Array, contentType: string, timeoutMs?: number): Promise<SendResponse>
// Send a message without waiting for a response
sendRaw(id: string, message: Uint8Array, contentType: string): Promise<SendRawResponse>
Example:
const encoder = new TextEncoder();
const decoder = new TextDecoder();
// Send with correlation (expects response)
const { response } = await client.send(
connectionId,
encoder.encode(JSON.stringify({ type: "request", id: "123" })),
"application/json",
5000
);
console.log(`Response: ${decoder.decode(response)}`);
// Fire-and-forget
await client.sendRaw(
connectionId,
encoder.encode(JSON.stringify({ type: "notification" })),
"application/json"
);
Tags
// Set/replace a single tag on a connection (resource metadata — not forwarded to remote WS)
setTag(id: string, key: string, value: string): Promise<SetTagResponse>
// Get a single tag value
getTag(id: string, key: string): Promise<GetTagResponse>
// Replace all tags on a connection at once
setTags(id: string, tags: Record<string, string>): Promise<SetTagsResponse>
// Get all tags for a connection
getTags(id: string): Promise<GetTagsResponse>
Example:
// Set a single tag
await client.setTag(connectionId, "environment", "staging");
// Replace all tags
await client.setTags(connectionId, { environment: "staging", team: "payments" });
// Read back
const { tags } = await client.getTags(connectionId);
console.log(`Tags:`, tags);
Note: list() takes no arguments — there is currently no client-side support for filtering the connection list by tag. Filter the returned array yourself if you need this.
Data Models
ConnectResponse
interface ConnectResponse {
id: string;
status: string;
}
WebSocketConnection
interface WebSocketConnection {
id: string;
url: string;
status: string;
topic: string;
instanceId: string;
}
SendResponse
interface SendResponse {
response: Uint8Array;
}
SendRawResponse / StartPublishResponse / StopPublishResponse
Empty messages — no fields, just a resolved Promise on success.
Error Handling
Connection Not Found
try {
await client.disconnect("nonexistent");
} catch (error) {
if (error instanceof ConnectError) {
console.log(`Not found: ${error.message}`);
}
}
Send Timeout
try {
const { response } = await client.send(
connectionId,
encoder.encode("slow request"),
"text/plain",
100 // very short
);
} catch (error) {
if (error instanceof ConnectError && error.code === Code.DeadlineExceeded) {
console.log("Send timed out");
}
}
Connection Errors
try {
const client = new WebSocketManagerClient({ url: "http://invalid-host:5001" });
await client.connect("wss://invalid");
} catch (error) {
console.log(`Connection failed: ${error.message}`);
}
Complete Example
import { WebSocketManagerClient } from "@virtufin/websocketmanager";
async function main() {
console.log("WebSocketManager Client Demo");
console.log("==============================\n");
const client = new WebSocketManagerClient({ url: "http://localhost:5001" });
const encoder = new TextEncoder();
const decoder = new TextDecoder();
// Connect
console.log("Connecting to WebSocket server...");
const { id: connectionId, status } = await client.connect("wss://echo.websocket.org");
console.log(`Connected: ${connectionId} - ${status}`);
// List connections
console.log("\nAll connections:");
const { connections } = await client.list();
for (const conn of connections) {
console.log(` ${conn.id}: ${conn.url} [${conn.status}]`);
if (conn.topic) {
console.log(` Publishing to: ${conn.topic}`);
}
}
// Send a message
console.log("\nSending message...");
const { response } = await client.send(
connectionId,
encoder.encode(JSON.stringify({ type: "request", id: "123", data: "hello" })),
"application/json",
5000
);
console.log(`Response: ${decoder.decode(response)}`);
// Start pub/sub
console.log("\nStarting pub/sub...");
await client.startPublish(connectionId, "ws-events");
console.log("Publishing started");
// Stop pub/sub
console.log("\nStopping pub/sub...");
await client.stopPublish(connectionId);
console.log("Publishing stopped");
// Disconnect
console.log("\nDisconnecting...");
await client.disconnect(connectionId);
console.log("Disconnected");
}
main();
Protobuf Dependencies
The client library requires the peer dependency:
{
"peerDependencies": {
"@bufbuild/protobuf": "^2.12.0"
}
}
Ensure your project installs the peer dependency:
npm install @bufbuild/protobuf
The proto files are pre-compiled into the src/generated directory.
Generated Protos
The client includes generated protobuf classes from websocketmanager.proto:
| Type | Description |
|---|---|
ConnectRequest |
Request to connect to a WebSocket server |
ConnectResponse |
Response with connection ID and status |
ListRequest |
Request to list all connections |
ListResponse |
Response with list of connections |
DisconnectRequest |
Request to disconnect |
DisconnectResponse |
Empty response |
StartPublishRequest |
Request to start pub/sub publishing |
StartPublishResponse |
Empty response |
StopPublishRequest |
Request to stop publishing |
StopPublishResponse |
Empty response |
SendRequest |
Request to send a correlated message |
SendResponse |
Response with server reply |
SendRawRequest |
Request to send without waiting |
SendRawResponse |
Empty response |
WebSocketConnection |
Connection details |
These are the wire message types (used internally by the generated stub); the WebSocketManagerClient wrapper methods above take plain arguments, not these types directly.