Client API Reference

This document covers the client SDK for connecting to a Dialogue server from frontend applications.

1. Overview

The Dialogue client SDK provides a simple, type-safe API for connecting to a Dialogue server, joining rooms, and handling real-time events.

2. Installation

Install the package and its peer dependency:

bun add dialogue-ts zod

The client SDK is included in the Dialogue package:

import { createDialogueClient } from "dialogue-ts/client";

3. DialogueClient

The main client factory for connecting to a Dialogue server.

3.1 Creating a Client

import { createDialogueClient } from "./client";

const client = createDialogueClient({
  url: "ws://localhost:3000",
  auth: {
    userId: "user-123",
    token: "jwt-token-here",
  },
});

3.2 Configuration Options

PropertyTypeRequiredDefaultDescription
urlstringYes-WebSocket server URL
authobjectNo-Authentication data sent with connection
auth.userIdstringNo-User ID for identification
auth.tokenstringNo-JWT or other auth token
autoConnectbooleanNotrueAuto-connect on instantiation
reconnectionbooleanNotrueEnable automatic reconnection
reconnectionAttemptsnumberNo5Maximum reconnection attempts

3.3 Properties

PropertyTypeDescription
userIdstringUser ID assigned by the server
connectedbooleanWhether the client is connected
stateConnectionStateCurrent connection state: "disconnected", "connecting", or "connected"

3.4 client.connect()

Connects to the server. Returns a promise that resolves when connected.

Signature:

client.connect(): Promise<void>

Example:

try {
  await client.connect();
  console.log("Connected as:", client.userId);
} catch (error) {
  console.error("Connection failed:", error);
}

3.5 client.disconnect()

Disconnects from the server and leaves all joined rooms.

Signature:

client.disconnect(): void

Example:

// Cleanup on component unmount
useEffect(() => {
  return () => {
    client.disconnect();
  };
}, []);

3.6 client.join()

Joins a room and returns a RoomContext for interacting with it.

Signature:

client.join(roomId: string): Promise<RoomContext>

Example:

const chat = await client.join("chat");

console.log(`Joined ${chat.roomName}`);

// Listen for messages
chat.on("message", (msg) => {
  console.log(`${msg.from}: ${msg.data.text}`);
});

3.7 client.getRoom()

Gets a previously joined room context.

Signature:

client.getRoom(roomId: string): RoomContext | undefined

Example:

const chat = client.getRoom("chat");

if (chat) {
  chat.trigger("message", { text: "Hello!" });
}

3.8 client.listRooms()

Lists all available rooms on the server.

Signature:

client.listRooms(): Promise<RoomInfo[]>

Returns:

Array of RoomInfo objects:

interface RoomInfo {
  id: string;
  name: string;
  description?: string;
  size: number;
  maxSize?: number;
  createdById?: string; // User ID of room creator (for dynamic rooms)
}

Example:

const rooms = await client.listRooms();

for (const room of rooms) {
  console.log(`${room.name}: ${room.size}/${room.maxSize ?? "∞"} participants`);
}

3.9 client.onConnect()

Registers a handler called when connected.

Signature:

client.onConnect(handler: () => void): () => void

Returns: Unsubscribe function

Example:

const unsubscribe = client.onConnect(() => {
  console.log("Connected!");
});

3.10 client.onDisconnect()

Registers a handler called when disconnected.

Signature:

client.onDisconnect(handler: (reason: string) => void): () => void

Example:

client.onDisconnect((reason) => {
  console.log("Disconnected:", reason);
});

3.11 client.onError()

Registers an error handler.

Signature:

client.onError(handler: (error: Error) => void): () => void

Example:

client.onError((error) => {
  console.error("Error:", error.message);
});

3.12 client.createRoom()

Creates a new room on the server. Requires server to support dynamic room creation.

Signature:

client.createRoom(options: CreateRoomOptions): Promise<RoomInfo>

CreateRoomOptions:

interface CreateRoomOptions {
  id: string;
  name: string;
  description?: string;
  maxSize?: number;
}

Returns: RoomInfo object with the created room details

Example:

const roomInfo = await client.createRoom({
  id: "tech-talk",
  name: "Tech Talk",
  description: "Discuss the latest in technology",
  maxSize: 100,
});

console.log(`Created room: ${roomInfo.name}`);

// Now join the room
const room = await client.join(roomInfo.id);

3.13 client.deleteRoom()

Deletes a room from the server. Only the room creator can delete a room.

Signature:

client.deleteRoom(roomId: string): Promise<void>

Example:

try {
  await client.deleteRoom("tech-talk");
  console.log("Room deleted successfully");
} catch (error) {
  console.error("Failed to delete room:", error.message);
}

3.14 client.onRoomCreated()

Registers a handler called when a new room is created on the server.

Signature:

client.onRoomCreated(handler: (room: RoomInfo) => void): () => void

Returns: Unsubscribe function

Example:

client.onRoomCreated((room) => {
  console.log(`New room available: ${room.name}`);
  // Update UI to show new room in list
  updateRoomList();
});

3.15 client.onRoomDeleted()

Registers a handler called when a room is deleted from the server.

Signature:

client.onRoomDeleted(handler: (roomId: string) => void): () => void

Returns: Unsubscribe function

Example:

client.onRoomDeleted((roomId) => {
  console.log(`Room deleted: ${roomId}`);
  
  // If user was in this room, redirect them
  if (currentRoomId === roomId) {
    showMessage("This room has been deleted");
    navigateToRoomList();
  }
});

3.16 client.onHistory()

Registers a handler called when historical events are received on room join (when syncHistoryOnJoin is enabled).

Signature:

client.onHistory(handler: (roomId: string, events: EventMessage[]) => void): () => void

Returns: Unsubscribe function

Example:

client.onHistory((roomId, events) => {
  console.log(`Received ${events.length} historical events for ${roomId}`);
  
  // Render historical messages
  events.forEach((event) => {
    addMessage(event, { isHistory: true });
  });
});

4. RoomContext

Returned by client.join(), provides methods for interacting with a joined room.

4.1 Properties

PropertyTypeDescription
roomIdstringRoom identifier
roomNamestringHuman-readable room name

4.2 room.trigger()

Triggers an event to all subscribers in the room.

Signature:

room.trigger<T>(eventName: string, data: T): void

Example:

// Send a message
room.trigger("message", {
  text: "Hello, everyone!",
  senderId: client.userId,
});

// Send a typing indicator
room.trigger("typing", { isTyping: true });

4.3 room.on()

Listens for a specific event. Returns an unsubscribe function.

Signature:

room.on<T>(eventName: string, handler: (msg: EventMessage<T>) => void): () => void

EventMessage structure:

interface EventMessage<T> {
  event: string;
  roomId: string;
  data: T;
  from: string;
  timestamp: number;
  meta?: Record<string, unknown>;
}

Meta Field

The optional meta field can contain arbitrary contextual data:

room.trigger('action', { type: 'click' }, {
  sessionId: 'abc-123',
  variant: 'A',
  timestamp: Date.now()
});

// Receive meta in handlers
room.on('action', (msg) => {
  console.log(msg.data);      // { type: 'click' }
  console.log(msg.meta);      // { sessionId: '...', variant: 'A', ... }
});

Common use cases:

  • Analytics tracking (session IDs, A/B variants)
  • Request tracing (correlation IDs)
  • Debug information
  • Client-side state hints

Example:

// Listen for messages
const unsubscribe = room.on<{ text: string; senderId: string }>(
  "message",
  (msg) => {
    console.log(`[${msg.from}] ${msg.data.text}`);
    console.log(`Received at: ${new Date(msg.timestamp)}`);
  }
);

// Stop listening
unsubscribe();

4.4 room.onAny()

Listens for all events in the room. Useful for logging or debugging.

Signature:

room.onAny(
  handler: (eventName: string, msg: EventMessage<unknown>) => void
): () => void

Example:

room.onAny((eventName, msg) => {
  console.log(`Event: ${eventName}`, msg.data);
});

4.5 room.subscribe()

Subscribes to an additional event type.

Signature:

room.subscribe(eventName: string): void

Example:

// Subscribe to typing events (if not in defaultSubscriptions)
room.subscribe("typing");

4.6 room.subscribeAll()

Subscribe to all events in the room using the wildcard pattern:

Signature:

room.subscribeAll(): void

Example:

const room = await client.join("project-123");

// Subscribe to all events in this room
room.subscribeAll();
// Internally calls: room.subscribe('*')

// Now set up handlers
room.on("message", (msg) => {
  console.log(msg.data);
});

This subscribes to every event type in the room, including:

  • Events defined in room config
  • Dynamic events (if room uses wildcard)
  • Future events added to the room

Use cases:

  • Logging/debugging during development
  • Analytics collection
  • Admin dashboards showing all activity

Performance note: Only use when you genuinely need all events. Specific subscriptions are more efficient.

Note: If the room has defaultSubscriptions configured on the server, clients are automatically subscribed to those events when joining. Use subscribeAll() for rooms without default subscriptions or when you need to ensure you receive all events.

4.7 room.unsubscribe()

Unsubscribes from an event type.

Signature:

room.unsubscribe(eventName: string): void

Example:

// Stop receiving typing events
room.unsubscribe("typing");

4.8 room.leave()

Leaves the room and cleans up all event handlers.

Signature:

room.leave(): void

Example:

// Leave when closing a chat
room.leave();

4.8 room.getHistory()

Fetches historical events for a specific event type with pagination.

Signature:

room.getHistory(eventName: string, start?: number, end?: number): Promise<EventMessage[]>

Parameters:

  • eventName: The event type to fetch history for
  • start: Starting index (0 = most recent). Defaults to 0
  • end: Ending index (exclusive). Defaults to 50

Returns: Promise resolving to array of historical events (newest first)

Example:

// Get the last 20 messages
const recentMessages = await room.getHistory("message", 0, 20);

// Paginate: skip first 20, get next 20
const olderMessages = await room.getHistory("message", 20, 40);

// Load more on scroll
async function loadMore() {
  const currentCount = messages.length;
  const older = await room.getHistory("message", currentCount, currentCount + 20);
  setMessages((prev) => [...prev, ...older]);
}

5. Complete Example

import { createDialogueClient } from "./client";

async function main() {
  // Create client with authentication
  const client = createDialogueClient({
    url: "ws://localhost:3000",
    auth: {
      userId: "user-123",
      token: "my-jwt-token",
    },
  });

  // Set up connection handlers
  client.onConnect(() => {
    console.log("Connected as:", client.userId);
  });

  client.onDisconnect((reason) => {
    console.log("Disconnected:", reason);
  });

  client.onError((error) => {
    console.error("Error:", error.message);
  });

  // Connect to server
  await client.connect();

  // List available rooms
  const rooms = await client.listRooms();
  console.log("Available rooms:", rooms);

  // Join chat room
  const chat = await client.join("chat");
  console.log(`Joined: ${chat.roomName}`);

  // Listen for messages
  chat.on<{ text: string; senderId: string }>("message", (msg) => {
    console.log(`[${msg.from}] ${msg.data.text}`);
  });

  // Listen for typing indicators
  chat.on<{ isTyping: boolean }>("typing", (msg) => {
    if (msg.data.isTyping) {
      console.log(`${msg.from} is typing...`);
    }
  });

  // Send a message
  chat.trigger("message", {
    text: "Hello, everyone!",
    senderId: client.userId,
  });

  // Send typing indicator
  chat.trigger("typing", { isTyping: true });

  // Later: leave room and disconnect
  // chat.leave();
  // client.disconnect();
}

main().catch(console.error);

6. React Integration

6.1 Basic Hook

import { useEffect, useState } from "react";
import { createDialogueClient } from "./client";
import type { RoomContext } from "./client/types";

export function useDialogue(url: string, userId: string) {
  const [client] = useState(
    () =>
      createDialogueClient({
        url,
        auth: { userId },
        autoConnect: false,
      })
  );
  const [connected, setConnected] = useState(false);

  useEffect(() => {
    client.onConnect(() => setConnected(true));
    client.onDisconnect(() => setConnected(false));
    client.connect();

    return () => {
      client.disconnect();
    };
  }, [client]);

  return { client, connected };
}

export function useRoom(client: ReturnType<typeof createDialogueClient>, roomId: string) {
  const [room, setRoom] = useState<RoomContext | null>(null);

  useEffect(() => {
    if (!client.connected) return;

    client.join(roomId).then(setRoom);

    return () => {
      room?.leave();
    };
  }, [client, roomId, client.connected]);

  return room;
}

6.2 Usage in Component

function ChatRoom({ roomId }: { roomId: string }) {
  const { client, connected } = useDialogue("ws://localhost:3000", "user-123");
  const room = useRoom(client, roomId);
  const [messages, setMessages] = useState<Message[]>([]);

  useEffect(() => {
    if (!room) return;

    const unsubscribe = room.on<MessageData>("message", (msg) => {
      setMessages((prev) => [...prev, msg]);
    });

    return unsubscribe;
  }, [room]);

  if (!connected || !room) {
    return <div>Connecting...</div>;
  }

  return (
    <div>
      <h2>{room.roomName}</h2>
      {messages.map((msg) => (
        <div key={msg.timestamp}>
          <strong>{msg.from}:</strong> {msg.data.text}
        </div>
      ))}
    </div>
  );
}

This documentation reflects the current implementation and is subject to evolution. Contributions and feedback are welcome.