Sockudo
Realtime Clients

JavaScript and TypeScript

Use @sockudo/client in browsers, Node.js, workers, React, Vue, React Native, and NativeScript.

@sockudo/client is the official JavaScript and TypeScript realtime SDK. It preserves the Pusher subscribe/bind model and adds runtime-specific entrypoints.

The default is Protocol V1 compatibility. Set protocolVersion: 2 for Sockudo-native recovery, rewind, filters, deltas, binary wire formats, and versioned-message metadata.

Install

Install the published package from npm:

npm install @sockudo/client
# or: bun add @sockudo/client
# or: pnpm add @sockudo/client
# or: yarn add @sockudo/client

Runtime imports

import Sockudo from "@sockudo/client";
import { Filter } from "@sockudo/client/filter";
import SockudoEncrypted from "@sockudo/client/with-encryption";
import WorkerSockudo from "@sockudo/client/worker";
import { SockudoProvider, useChannel } from "@sockudo/client/react";
import { createSockudoPlugin, useChannel as useSockudoChannel } from "@sockudo/client/vue";
import ReactNativeSockudo from "@sockudo/client/react-native";
import NativeScriptSockudo from "@sockudo/client/nativescript";

Connect

import Sockudo from "@sockudo/client";

const client = new Sockudo("app-key", {
  wsHost: "127.0.0.1",
  wsPort: 6001,
  forceTLS: false,
  enabledTransports: ["ws"],
  protocolVersion: 2,
  connectionRecovery: true,
});

const channel = client.subscribe("public-updates");
channel.bind("price-updated", (payload) => {
  console.log(payload);
});

For self-hosted production, wsHost should be the public ingress or load balancer name, wssPort is normally 443, and forceTLS should be true. Specify enabledTransports: ["ws"] only when debugging a direct local connection; allowing both secure and fallback transports is safer across browser and mobile runtimes.

Connection lifecycle

Observe the connection separately from channel subscription state:

client.connection.bind("state_change", ({ previous, current }) => {
  console.log(`connection: ${previous} -> ${current}`);
});
client.connection.bind("error", (error) => {
  console.error("Sockudo connection error", error);
});

const channel = client.subscribe("orders");
channel.bind("sockudo:subscription_succeeded", () => {
  console.log("orders is live");
});
channel.bind("sockudo:subscription_error", (error) => {
  console.error("orders subscription failed", error);
});

A connected socket can still have an unauthorized or pending channel. Gate channel-dependent UI on subscription_succeeded, not only the global connected event.

Keep the binding callback when you need to remove one handler, or remove the whole subscription when its owning component or job ends:

const onOrder = (order: unknown) => console.log(order);
channel.bind("order.created", onOrder);

channel.unbind("order.created", onOrder);
client.unsubscribe("orders");
client.disconnect();

Framework hooks clean up their own bindings when components unmount. When using the core client directly, lifecycle cleanup is your responsibility.

Binary wire formats

Protocol V2 can negotiate JSON, MessagePack, or Protobuf with wireFormat. MessagePack and Protobuf preserve Uint8Array message data as native binary values without a JSON or base64 intermediate.

const client = new Sockudo("app-key", {
  wsHost: "127.0.0.1",
  wsPort: 6001,
  forceTLS: false,
  protocolVersion: 2,
  wireFormat: "messagepack",
});

const channel = client.subscribe("private-binary");
channel.trigger("client-binary", new Uint8Array([0xde, 0xad, 0xbe, 0xef]));

Native MessagePack uses the additive tagged data variant ["binary", <bin>]. Existing string, structured, and JSON variants retain their previous representation.

Private and presence auth

const client = new Sockudo("app-key", {
  wsHost: "realtime.example.com",
  forceTLS: true,
  channelAuthorization: {
    endpoint: "/sockudo/auth",
  },
  userAuthentication: {
    endpoint: "/sockudo/user-auth",
  },
});

const presence = client.subscribe("presence-lobby");
presence.bind("pusher:member_added", console.log);

The auth endpoint must validate the current application session and authorize the exact channel_name sent by the SDK before signing. For presence channels, derive user_id on the server; do not trust a client-supplied identity. The client bundle contains only the public app key.

presence.bind("pusher:subscription_succeeded", (members) => {
  console.log("initial members", members);
});
presence.bind("pusher:member_removed", (member) => {
  console.log("left", member);
});

Configure userAuthentication.endpoint and call client.signin() when using user-targeted events or watchlists. The backend signs the current socket_id and authenticated user data.

React

import Sockudo from "@sockudo/client";
import { SockudoProvider, useChannel } from "@sockudo/client/react";

const client = new Sockudo("app-key", {
  wsHost: "127.0.0.1",
  wsPort: 6001,
  forceTLS: false,
});

client.connect();

function Orders() {
  const { subscribed, events } = useChannel("orders", ["order.created"]);
  return <pre>{JSON.stringify({ subscribed, events }, null, 2)}</pre>;
}

export function App() {
  return (
    <SockudoProvider client={client}>
      <Orders />
    </SockudoProvider>
  );
}

Vue

import Sockudo from "@sockudo/client";
import { createSockudoPlugin, useChannel } from "@sockudo/client/vue";

const client = new Sockudo("app-key", {
  wsHost: "127.0.0.1",
  wsPort: 6001,
  forceTLS: false,
});

app.use(createSockudoPlugin(client));

const { subscribed, bind } = useChannel("orders");
bind("order.created", (payload) => console.log(payload));

Filters and deltas

const channel = client.subscribe("market:btc", {
  filter: Filter.and(
    Filter.eq("market", "spot"),
    Filter.gte("price", "100"),
  ),
  delta: {
    enabled: true,
    algorithm: "xdelta3",
  },
});

Recovery and rewind

const channel = client.subscribe("market:BTC", {
  rewind: { seconds: 30 },
});

channel.bind("message", () => {
  console.log(client.getRecoveryPosition("market:BTC"));
});

client.bind("sockudo:resume_success", (payload) => {
  console.log(payload.recovered, payload.failed);
});

Versioned messages

Configure a backend proxy for REST reads:

const client = new Sockudo("app-key", {
  wsHost: "127.0.0.1",
  wsPort: 6001,
  forceTLS: false,
  versionedMessages: {
    endpoint: "/sockudo/versioned",
  },
});

const channel = client.subscribe("chat:room-1");
const latest = await channel.getMessage("42");
const versions = await channel.getMessageVersions("42", {
  limit: 20,
  direction: "oldest_first",
});

The proxy endpoint must authenticate the caller, authorize access to the channel, and sign the upstream HTTP request with the app secret. Use until_attach channel history for a gap-free late join: first attach, then load history ending at the server-provided attach serial, then apply buffered live events.

Encrypted channels

Use the encryption entrypoint for private-encrypted-* channels:

import SockudoEncrypted from "@sockudo/client/with-encryption";

const encryptedClient = new SockudoEncrypted("app-key", {
  wsHost: "realtime.example.com",
  forceTLS: true,
  channelAuthorization: { endpoint: "/sockudo/auth" },
});

encryptedClient
  .subscribe("private-encrypted-documents")
  .bind("doc.updated", (payload) => console.log(payload));

Your auth response supplies the derived channel shared_secret. Keep the encryption master key on the trusted backend. End-to-end encryption complements TLS and authorization; it does not replace either.

Push proxy helpers

Browser push registration should flow through your backend. Keep app secrets and provider credentials server-side.

await fetch("/sockudo/push/devices", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    device_id: "browser-device-1",
    platform: "webpush",
    provider_token: pushSubscription,
  }),
});

Use a server SDK to publish push after validating user and tenant policy.

Error handling and production guidance

  • Reuse one client per browser tab, worker, or native application runtime.
  • Create the client outside React render functions and Vue component setup.
  • Use the runtime-specific entrypoint so bundlers do not pull in incompatible browser or Node transports.
  • Treat auth 401/403 as an application-permission problem, not a reconnect loop.
  • On sockudo:resume_failed, clear derived state and fetch an authoritative snapshot before applying new events.
  • Make handlers idempotent and validate unknown payloads at the application boundary.
  • Unbind handlers, unsubscribe unused channels, and disconnect during application shutdown.
  • Never expose the app secret, push credentials, or encryption master key in a browser or mobile bundle.

On this page