Browse topics
WebSocket API
Persistent WebSocket connections to external real-time services.
WebSocket API
The WebSocket API lets custom resources open persistent connections to external services — real-time APIs, chat protocols, streaming data feeds, and more. Connections are managed by the native Tauri layer, bypassing browser restrictions on iframe WebSocket origins.
Capability required: network:ws
Quick Start
Frontend (iframe)
import { createResourceClient } from "@rightplace/sdk";
const rp = createResourceClient();
await rp.ready();
const ws = rp.ws.connect("wss://echo.websocket.org");
ws.on("open", () => {
console.log("Connected!");
ws.send("Hello, server!");
});
ws.on("message", (data) => {
console.log("Received:", data);
});
ws.on("close", (code, reason) => {
console.log("Closed:", code, reason);
});
ws.on("error", (err) => {
console.error("Error:", err);
});
// When done:
ws.close();
Backend (Node.js)
The backend SDK exposes connect, send, and close as async methods. Connection events are handled natively by the Rust layer. For full event-driven WebSocket usage from a backend, use the Node.js ws module directly.
import { createResourceServer } from "@rightplace/sdk/server";
const server = createResourceServer({
methods: {
startStream: async (params, { rp }) => {
const connectionId = `stream-${Date.now()}`;
await rp.ws.connect(connectionId, params.url, {
headers: { "Authorization": `Bearer ${params.token}` },
});
await rp.ws.send(connectionId, JSON.stringify({ subscribe: "prices" }));
return { connectionId };
},
stopStream: async (params, { rp }) => {
await rp.ws.close(params.connectionId);
},
},
});
server.start();
Connect Options
const ws = rp.ws.connect(url, options);
| Option | Type | Default | Description |
|---|---|---|---|
url | string | (required) | WebSocket URL (ws:// or wss://) |
headers | Record<string, string> | {} | Custom headers sent with the upgrade request |
protocols | string[] | [] | WebSocket subprotocols to negotiate |
WsConnection Events
Register event handlers with ws.on(event, callback):
| Event | Callback | Description |
|---|---|---|
open | () => void | Connection established |
message | (data: string) => void | Text message received |
close | (code: number, reason: string) => void | Connection closed (includes close code and reason) |
error | (err: string) => void | Connection error |
WsConnection Methods
| Method | Description |
|---|---|
send(data: string) | Send a text message through the connection |
close() | Close the connection gracefully |
Manifest Configuration
Declare the network:ws capability in your resource.json:
{
"id": "com.example.realtime-dashboard",
"name": "Realtime Dashboard",
"version": "1.0.0",
"apiVersion": 1,
"capabilities": [
"network:ws"
],
"frontend": {
"entry": "dist/index.html"
}
}
Users will see that your resource requests WebSocket access when they install it.
Without the SDK
If you’re not using the @rightplace/sdk npm package, you can use the raw postMessage protocol:
const connectionId = `ws-${Date.now()}-${Math.random().toString(36).slice(2)}`;
// Connect
window.parent.postMessage({
type: "rp:request",
id: "req-1",
method: "ws.connect",
params: { connectionId, url: "wss://echo.websocket.org" },
}, "*");
// Listen for events
window.addEventListener("message", (e) => {
const d = e.data;
if (d.type === "rp:ws" && d.connectionId === connectionId) {
switch (d.event) {
case "open": console.log("Connected"); break;
case "message": console.log("Data:", d.data); break;
case "close": console.log("Closed:", d.code, d.reason); break;
case "error": console.error("Error:", d.error); break;
}
}
});
// Send a message
window.parent.postMessage({
type: "rp:request",
id: "req-2",
method: "ws.send",
params: { connectionId, data: "Hello!" },
}, "*");
// Close the connection
window.parent.postMessage({
type: "rp:request",
id: "req-3",
method: "ws.close",
params: { connectionId },
}, "*");
Examples
Real-Time Price Feed
const ws = rp.ws.connect("wss://stream.binance.com:9443/ws/btcusdt@trade");
ws.on("message", (data) => {
const trade = JSON.parse(data);
updatePriceDisplay(trade.p, trade.q);
});
ws.on("error", (err) => {
showNotification("Price feed disconnected: " + err);
});
Chat with Authentication
const token = await rp.credentials.get("chat-token");
const ws = rp.ws.connect("wss://chat.example.com/ws", {
headers: { "Authorization": `Bearer ${token}` },
protocols: ["chat-v2"],
});
ws.on("open", () => {
ws.send(JSON.stringify({ type: "join", room: "general" }));
});
ws.on("message", (data) => {
const msg = JSON.parse(data);
appendMessage(msg.user, msg.text);
});
Reconnect on Disconnect
function connectWithRetry(url, maxRetries = 5) {
let retries = 0;
function connect() {
const ws = rp.ws.connect(url);
ws.on("open", () => {
retries = 0; // Reset on successful connect
});
ws.on("close", (code, reason) => {
if (retries < maxRetries) {
retries++;
const delay = Math.min(1000 * Math.pow(2, retries), 30000);
setTimeout(connect, delay);
}
});
ws.on("message", (data) => {
handleMessage(JSON.parse(data));
});
return ws;
}
return connect();
}