Skip to main content

Overview

The subscribe endpoint is a public subscription procedure that establishes a real-time WebSocket connection to receive chat messages from a specified channel. Messages are streamed as they are published.

Procedure Type

Subscription - Public procedure (no authentication required)

Input Parameters

channel
string
required
The channel identifier to subscribe to

Response Stream

The subscription yields ChatMessage objects as they are published to the channel.

ChatMessage Structure

id
string
Unique identifier for the message (UUID v4)
author
JwtUser
The user who sent the message
author.robloxUserId
string
User’s Roblox user ID
author.username
string
User’s Roblox username
author.displayName
string
User’s Roblox display name
author.picture
string
URL to user’s profile picture
content
string
The message content
replyToId
string | null
Message ID this message is replying to, or null if not a reply

WebSocket Connection

The subscription uses tRPC’s WebSocket transport to establish a persistent connection. Messages are pushed from the server to the client in real-time as they are published via the globalPubSub event emitter.

Example Usage

Basic Subscription

import { trpc } from './trpc';

// Subscribe to messages in a channel
const subscription = trpc.chat.subscribe.subscribe(
  { channel: 'global' },
  {
    onData: (message) => {
      console.log(`New message from ${message.author.username}: ${message.content}`);
      
      if (message.replyToId) {
        console.log(`  (replying to ${message.replyToId})`);
      }
    },
    onError: (error) => {
      console.error('Subscription error:', error);
    },
  }
);

// Later: unsubscribe to close the connection
subscription.unsubscribe();

React Hook Example

import { useEffect, useState } from 'react';
import { trpc } from './trpc';
import type { ChatMessage } from '@bloxchat/api';

function ChatRoom({ channel }: { channel: string }) {
  const [messages, setMessages] = useState<ChatMessage[]>([]);

  useEffect(() => {
    const subscription = trpc.chat.subscribe.subscribe(
      { channel },
      {
        onData: (message) => {
          setMessages((prev) => [...prev, message]);
        },
        onError: (error) => {
          console.error('Chat subscription error:', error);
        },
      }
    );

    return () => {
      subscription.unsubscribe();
    };
  }, [channel]);

  return (
    <div>
      {messages.map((msg) => (
        <div key={msg.id}>
          <strong>{msg.author.displayName}:</strong> {msg.content}
        </div>
      ))}
    </div>
  );
}

Multiple Channel Subscriptions

import { trpc } from './trpc';

const channels = ['global', 'support', 'announcements'];

const subscriptions = channels.map(channel => 
  trpc.chat.subscribe.subscribe(
    { channel },
    {
      onData: (message) => {
        console.log(`[${channel}] ${message.author.username}: ${message.content}`);
      },
    }
  )
);

// Cleanup all subscriptions
const cleanup = () => {
  subscriptions.forEach(sub => sub.unsubscribe());
};

Example Message Stream

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "author": {
    "robloxUserId": "123456789",
    "username": "johndoe",
    "displayName": "John Doe",
    "picture": "https://tr.rbxcdn.com/..."
  },
  "content": "Hello everyone!",
  "replyToId": null
}
{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "author": {
    "robloxUserId": "987654321",
    "username": "janedoe",
    "displayName": "Jane Doe",
    "picture": "https://tr.rbxcdn.com/..."
  },
  "content": "Hi John!",
  "replyToId": "550e8400-e29b-41d4-a716-446655440000"
}

Implementation Details

The subscribe endpoint is implemented at chat.ts:70-78 as an async generator function:
  1. Creates an async iterable using on(globalPubSub, input.channel) (line 73)
  2. Iterates over events emitted to the specified channel (line 75)
  3. Yields each message as a ChatMessage type (line 76)
The subscription remains active until:
  • The client calls unsubscribe()
  • The WebSocket connection is closed
  • The server shuts down

Connection Management

Automatic Reconnection

Most tRPC clients support automatic reconnection when the WebSocket connection is lost. Configure this in your tRPC client setup:
import { createTRPCProxyClient, createWSClient, wsLink } from '@trpc/client';

const wsClient = createWSClient({
  url: 'ws://localhost:3000/trpc',
  retryDelayMs: () => 1000, // Retry after 1 second
});

const trpc = createTRPCProxyClient({
  links: [wsLink({ client: wsClient })],
});

Memory Management

Always clean up subscriptions when components unmount or when the subscription is no longer needed to prevent memory leaks.

Use Cases

  • Real-time chat interfaces
  • Live message feeds
  • Notification systems
  • Activity streams
  • Multi-channel chat applications

Build docs developers (and LLMs) love