@open-ot/transport-websocket
v0.3.0
Published
WebSocket transport adapter for OpenOT. Provides low-latency, bidirectional real-time communication for collaborative applications.
Readme
@open-ot/transport-websocket
WebSocket transport adapter for OpenOT. Provides low-latency, bidirectional real-time communication for collaborative applications.
Installation
npm install @open-ot/transport-websocket wsOverview
This package provides a TransportAdapter implementation using WebSockets for both sending and receiving operations. It's ideal for:
- Traditional server environments (Node.js, VPS, Docker)
- Applications requiring the lowest possible latency
- Environments where long-lived connections are supported
Usage
Client-Side
import { WebSocketTransport } from "@open-ot/transport-websocket";
import { OTClient } from "@open-ot/client";
import { TextType } from "@open-ot/core";
const transport = new WebSocketTransport("ws://localhost:3000");
const client = new OTClient({
type: TextType,
initialSnapshot: "",
initialRevision: 0,
transport: transport,
});
// The client automatically connects and syncsServer-Side (Node.js)
import { WebSocketServer } from "ws";
import { Server, MemoryBackend } from "@open-ot/server";
import { TextType } from "@open-ot/core";
const backend = new MemoryBackend();
const otServer = new Server(backend);
otServer.registerType(TextType);
await backend.createDocument("doc-1", "text", "");
const wss = new WebSocketServer({ port: 3000 });
wss.on("connection", (ws) => {
ws.on("message", async (data) => {
const msg = JSON.parse(data.toString());
if (msg.type === "op") {
const result = await otServer.submitOperation(
"doc-1",
msg.op,
msg.revision
);
// Acknowledge sender
ws.send(JSON.stringify({ type: "ack" }));
// Broadcast to others
const update = JSON.stringify({
type: "op",
op: result.op,
revision: result.revision,
});
wss.clients.forEach((client) => {
if (client !== ws && client.readyState === 1) {
client.send(update);
}
});
}
});
});API Reference
WebSocketTransport
Constructor
new WebSocketTransport(url: string)Parameters:
url: WebSocket server URL (e.g.,"ws://localhost:3000"or"wss://api.example.com")
Methods
connect(onReceive: (msg: unknown) => void): Promise<void>
Establishes the WebSocket connection and starts listening for messages.
await transport.connect((message) => {
console.log("Received:", message);
});Returns: Promise that resolves when the connection is established.
send(msg: unknown): Promise<void>
Sends a message to the server.
await transport.send({
type: "op",
op: [{ i: "Hello" }],
revision: 0
});Throws: Error if the transport is disconnected.
disconnect(): Promise<void>
Closes the WebSocket connection.
await transport.disconnect();Message Protocol
Messages are JSON-encoded and follow this format:
Client → Server
{
"type": "op",
"op": [{ "i": "Hello" }],
"revision": 5
}Server → Client
{
"type": "ack"
}{
"type": "op",
"op": [{ "i": "World" }],
"revision": 6
}Connection Lifecycle
const transport = new WebSocketTransport("ws://localhost:3000");
// 1. Connect
await transport.connect((msg) => {
console.log("Message:", msg);
});
// 2. Send/Receive
await transport.send({ type: "op", ... });
// 3. Disconnect (cleanup)
await transport.disconnect();Error Handling
The transport handles connection errors and reconnection attempts:
try {
await transport.connect(onReceive);
} catch (err) {
console.error("Failed to connect:", err);
// Implement retry logic
}Production Deployment
Secure WebSockets (WSS)
Always use wss:// in production for encrypted connections:
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const url = `${protocol}//${window.location.host}`;
const transport = new WebSocketTransport(url);Load Balancing
WebSocket connections are stateful. Use sticky sessions or Redis Pub/Sub for multi-instance deployments:
import Redis from "ioredis";
const redis = new Redis();
const redisSub = new Redis();
redisSub.subscribe("ot:updates");
redisSub.on("message", (channel, message) => {
wss.clients.forEach((client) => {
if (client.readyState === 1) {
client.send(message);
}
});
});
// When processing an operation
const update = JSON.stringify({ type: "op", ... });
await redis.publish("ot:updates", update);Heartbeat / Ping-Pong
Prevent connection timeouts with periodic pings:
wss.on("connection", (ws) => {
const interval = setInterval(() => {
if (ws.readyState === 1) {
ws.ping();
}
}, 30000);
ws.on("close", () => {
clearInterval(interval);
});
});Next.js Integration
WebSockets require a custom server in Next.js:
// server.ts
import { createServer } from "http";
import { parse } from "url";
import next from "next";
import { WebSocketServer } from "ws";
const app = next({ dev: process.env.NODE_ENV !== "production" });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = createServer((req, res) => {
const parsedUrl = parse(req.url!, true);
handle(req, res, parsedUrl);
});
const wss = new WebSocketServer({ server });
// ... WebSocket logic
server.listen(3000);
});See the Next.js + WebSocket Integration Guide for a complete example.
Browser Compatibility
WebSockets are supported in all modern browsers. For Node.js environments, the ws package is required.
Comparison with SSE
| Feature | WebSocket | SSE | |---------|-----------|-----| | Bidirectional | ✅ Yes | ❌ No (client → server via HTTP) | | Latency | Lower | Slightly higher | | Serverless | ❌ Requires long-lived process | ✅ Works on Vercel/Lambda | | Browser Support | Excellent | Excellent | | Complexity | Moderate | Low |
Use WebSocket when:
- You need the lowest latency
- You have a traditional server environment
- You need bidirectional streaming
Use SSE when:
- Deploying to serverless platforms
- Simplicity is preferred
- Slightly higher latency is acceptable
See Also
- Next.js + WebSocket Integration Guide
- React + WebSocket Integration Guide
- @open-ot/transport-http-sse - SSE transport for serverless
