Skip to main content

WebSocket Client

The WebSocket client provides real-time communication with the Heimdall API. It's designed for client-side use only.

createWebSocket

Create a WebSocket connection with auto-reconnect support.

import { createWebSocket } from "@/lib/api";

const ws = createWebSocket("/v1/ws/user", {
accessToken: session.accessToken,
});

Parameters

ParameterTypeRequiredDescription
endpointstringYesWebSocket endpoint (e.g., /v1/ws/user)
configWebSocketConfigNoConfiguration options

Config Options

interface WebSocketConfig {
accessToken?: string;
autoReconnect?: boolean;
reconnectDelay?: number;
maxReconnectAttempts?: number;
}
OptionTypeDefaultDescription
accessTokenstring-OAuth access token for auth
autoReconnectbooleantrueAuto-reconnect on disconnect
reconnectDelaynumber3000Delay between reconnects (ms)
maxReconnectAttemptsnumber5Max reconnection attempts

Returns

interface WebSocketClient {
socket: WebSocket;
send: (data: unknown) => void;
close: () => void;
onMessage: (handler: (data: unknown) => void) => void;
onError: (handler: (error: Event) => void) => void;
onClose: (handler: (event: CloseEvent) => void) => void;
}

Example

const ws = createWebSocket("/v1/ws/user", {
accessToken: session.accessToken,
autoReconnect: true,
reconnectDelay: 5000,
maxReconnectAttempts: 3,
});

ws.onMessage((data) => {
console.log("Received:", data);
});

ws.onError((error) => {
console.error("WebSocket error:", error);
});

ws.onClose((event) => {
console.log("Connection closed:", event.code);
});

// Send a message
ws.send({ type: "subscribe", channel: "updates" });

// Close when done
ws.close();

useWebSocket Hook

React hook for WebSocket connections with state management.

import { useWebSocket } from "@/lib/api";

function LiveUpdates() {
const { isConnected, lastMessage, send, connect, disconnect } = useWebSocket(
"/v1/ws/updates",
{ accessToken: session.accessToken }
);

return (
<div>
<p>Status: {isConnected ? "Connected" : "Disconnected"}</p>
<button onClick={() => send({ type: "ping" })}>Ping</button>
</div>
);
}

Options

interface UseWebSocketOptions extends WebSocketConfig {
immediate?: boolean; // Connect immediately (default: true)
}

Returns

interface UseWebSocketReturn {
isConnected: boolean;
lastMessage: unknown;
send: (data: unknown) => void;
connect: () => void;
disconnect: () => void;
}

Example with Message Handling

function NotificationListener() {
const { isConnected, lastMessage } = useWebSocket("/v1/ws/notifications", {
accessToken: session.accessToken,
});

useEffect(() => {
if (lastMessage) {
const msg = lastMessage as { type: string; payload: unknown };

switch (msg.type) {
case "notification":
showNotification(msg.payload);
break;
case "update":
refreshData();
break;
}
}
}, [lastMessage]);

return <ConnectionStatus connected={isConnected} />;
}

Server Message Types

The library includes TypeScript types for all server-sent WebSocket messages.

User Status Messages

Sent when user account status changes:

import type { UserStatusMessage } from "@/lib/api";

// Account deleted
interface AccountDeletedMessage {
type: "accountDeleted";
userId: string;
reason?: string;
timestamp: string;
}

// Account banned
interface AccountBannedMessage {
type: "accountBanned";
userId: string;
reason?: string;
expiresAt?: string;
isPermanent: boolean;
timestamp: string;
}

// Session revoked
interface SessionRevokedMessage {
type: "sessionRevoked";
userId: string;
sessionId?: string;
reason?: string;
timestamp: string;
}

// Force logout
interface ForceLogoutMessage {
type: "forceLogout";
userId: string;
reason?: string;
timestamp: string;
}

Permission Messages

Sent when user permissions change:

import type { PermissionMessage } from "@/lib/api";

// Roles updated
interface RolesUpdatedMessage {
type: "rolesUpdated";
userId: string;
roles: string[];
timestamp: string;
}

// Permissions updated
interface PermissionsUpdatedMessage {
type: "permissionsUpdated";
userId: string;
permissions: string[];
timestamp: string;
}

// Role permissions changed (system-wide)
interface RolePermissionsChangedMessage {
type: "rolePermissionsChanged";
roleId: string;
roleName: string;
timestamp: string;
}

Sent when account linking events occur:

import type { AccountLinkMessage } from "@/lib/api";

// Email link verified
interface EmailLinkVerifiedMessage {
type: "emailLinkVerified";
userId: string;
email: string;
platformAccountId: string;
timestamp: string;
}

// Email change verified
interface EmailChangeVerifiedMessage {
type: "emailChangeVerified";
userId: string;
oldEmail: string;
newEmail: string;
timestamp: string;
}

Handling Server Messages

import type { ServerMessage } from "@/lib/api";

ws.onMessage((data: unknown) => {
const message = data as ServerMessage;

switch (message.type) {
case "accountBanned":
handleBan(message);
break;
case "forceLogout":
signOut();
break;
case "rolesUpdated":
refreshPermissions();
break;
case "emailLinkVerified":
refreshAccounts();
break;
}
});

wsConfig

Configuration object for the WebSocket client.

import { wsConfig } from "@/lib/api";

console.log(wsConfig.wsUrl); // "ws://localhost:3000"

Best Practices

Cleanup on Unmount

Always close WebSocket connections when components unmount:

useEffect(() => {
const ws = createWebSocket("/v1/ws/updates", { accessToken });

ws.onMessage(handleMessage);

return () => {
ws.close(); // Important: cleanup
};
}, [accessToken]);

The useWebSocket hook handles this automatically.

Reconnection Handling

The client auto-reconnects by default. Customize behavior:

const ws = createWebSocket("/v1/ws/user", {
accessToken,
autoReconnect: true,
reconnectDelay: 5000, // Wait 5s between attempts
maxReconnectAttempts: 10, // Give up after 10 attempts
});

Connection State UI

function ConnectionIndicator() {
const { isConnected } = useWebSocket("/v1/ws/status", { accessToken });

return (
<div className={isConnected ? "text-green-500" : "text-red-500"}>
{isConnected ? "Connected" : "Reconnecting..."}
</div>
);
}