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/clientRuntime 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/403as 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.