Developer

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);
OptionTypeDefaultDescription
urlstring(required)WebSocket URL (ws:// or wss://)
headersRecord<string, string>{}Custom headers sent with the upgrade request
protocolsstring[][]WebSocket subprotocols to negotiate

WsConnection Events

Register event handlers with ws.on(event, callback):

EventCallbackDescription
open() => voidConnection established
message(data: string) => voidText message received
close(code: number, reason: string) => voidConnection closed (includes close code and reason)
error(err: string) => voidConnection error

WsConnection Methods

MethodDescription
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();
}