# How Real-Time Collaboration Is Implemented with WebSocket Room-Based Trip Channels in TREK

> Discover how TREK uses WebSocket room-based trip channels for real-time collaboration. Learn how the server broadcasts updates efficiently to users within specific trip rooms.

- Repository: [Maurice/TREK](https://github.com/mauriceboe/TREK)
- Tags: internals
- Published: 2026-06-26

---

**TREK implements real-time collaboration using a room-based WebSocket architecture where clients join specific trip rooms, and the server broadcasts updates to all sockets in that room while excluding the originator to prevent echo-back.**

The TREK repository (mauriceboe/TREK) uses a classic room-based WebSocket system to synchronize trip data across multiple users in real-time. This architecture ensures that every participant viewing the same trip receives instant updates when itinerary details, notes, or files change. Understanding how real-time collaboration WebSocket room-based trip channels work requires examining both the server-side room management and the client-side connection handling.

## Server-Side Room Management and Broadcasting

### WebSocket Server Setup and Authentication

The server initializes a single `WebSocketServer` that listens on the `/ws` endpoint through the `setupWebSocket` function in [`server/src/websocket.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/websocket.ts). When a client connects, it must present a one-time **ws-token** issued by the authentication service. After validation, the socket receives three critical pieces of metadata:

```typescript
// https://github.com/mauriceboe/TREK/blob/main/server/src/websocket.ts
socketId.set(nws, sid);
socketUser.set(nws, user);
socketRooms.set(nws, new Set());

```

These mappings track the unique `socketId`, the authenticated `User` object (`socketUser`), and an empty `Set<number>` called `socketRooms` that will store which trip rooms this connection has joined.

### Join and Leave Actions

Clients communicate room membership through JSON messages with types **join** or **leave**, including a `tripId` parameter. The server validates permissions via `canAccessTrip` before updating its internal room registry:

```typescript
if (msg.type === 'join' && msg.tripId) {
  const tripId = Number(msg.tripId);
  if (!canAccessTrip(tripId, user.id)) { /* handle unauthorized */ }
  if (!rooms.has(tripId)) rooms.set(tripId, new Set());
  rooms.get(tripId).add(nws);
  socketRooms.get(nws).add(tripId);
}

```

The system maintains two essential data structures:

- **`rooms: Map<number, Set<NomadWebSocket>>`** – Maps each trip ID to the set of connected sockets currently viewing that trip
- **`socketRooms: WeakMap<NomadWebSocket, Set<number>>`** – Tracks which rooms each specific socket has joined for efficient cleanup

When a socket closes, the server iterates through `socketRooms` to call `leaveRoom` for every trip, ensuring the `rooms` map stays synchronized and memory is properly managed.

### Broadcasting to Trip Rooms

The `broadcast` function in [`server/src/websocket.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/websocket.ts) distributes updates to all participants in a trip room:

```typescript
function broadcast(tripId, eventType, payload, excludeSid) { 
  // Iterates over rooms.get(tripId) and sends JSON payload
  // Skips socket matching excludeSid to prevent echo-back
}

```

Services that mutate trip data—such as note updates or itinerary changes—call `broadcast(tripId, eventType, payload, excludeSid)` where the `excludeSid` parameter typically represents the originating client. This prevents users from receiving their own changes back while ensuring all other participants see updates instantly.

## Client-Side WebSocket Manager

### Connection Lifecycle and Reconnection

All browser code communicates through a singleton WebSocket instance defined in [`client/src/api/websocket.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/api/websocket.ts). The connection process follows a specific sequence:

1. **Token acquisition** – `connect()` fetches a temporary ws-token from `/api/auth/ws-token`
2. **URL construction** – Builds `/ws?token=...` and opens the socket
3. **Automatic reconnection** – On disconnect, the client implements exponential back-off retry logic
4. **State resynchronization** – After reconnecting, the client re-joins all active trips and optionally runs a `preReconnectHook` to flush pending mutations before refetching data

### Active Trip Tracking

The client maintains an `activeTrips` set containing every trip ID the user is currently viewing. When the socket opens or reopens, it automatically transmits `join` messages for each active trip:

```typescript
// https://github.com/mauriceboe/TREK/blob/main/client/src/api/websocket.ts
export function joinTrip(tripId: number | string): void {
  activeTrips.add(String(tripId));
  if (socket && socket.readyState === WebSocket.OPEN) {
    socket.send(JSON.stringify({ type: 'join', tripId: String(tripId) }));
  }
}

```

UI components register event handlers via `addListener` and `removeListener` functions. Incoming messages are parsed and dispatched to all registered listeners, enabling reactive updates across the application.

## React Hook Integration

The `useTripWebSocket` hook in [`client/src/hooks/useTripWebSocket.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/hooks/useTripWebSocket.ts) provides a clean interface for React components to participate in real-time collaboration:

```typescript
// https://github.com/mauriceboe/TREK/blob/main/client/src/hooks/useTripWebSocket.ts
export function useTripWebSocket(tripId) {
  const tripStore = useTripStore();
  useEffect(() => {
    if (!tripId) return;
    const handler = useTripStore.getState().handleRemoteEvent;
    joinTrip(tripId);
    addListener(handler);
    
    const collabFileSync = (event) => {
      if (event?.type === 'collab:note:deleted' || event?.type === 'collab:note:updated') {
        tripStore.loadFiles?.(tripId);
      }
    };
    addListener(collabFileSync);
    
    return () => {
      leaveTrip(tripId);
      removeListener(handler);
      removeListener(collabFileSync);
    };
  }, [tripId]);
}

```

This hook manages the component lifecycle by joining the trip room on mount and leaving it on unmount. It also sets up specialized listeners for file synchronization events, ensuring that changes to shared notes or documents from other users trigger immediate UI updates.

## End-to-End Collaboration Flow

Understanding the complete real-time collaboration WebSocket room-based trip channels implementation requires seeing how the pieces interact:

1. **Application startup** – The client calls `connect()` to establish the WebSocket connection
2. **Trip navigation** – When a user opens a trip, `useTripWebSocket(tripId)` executes and sends a `join` message
3. **Server validation** – The server verifies permissions via `canAccessTrip` and adds the socket to the appropriate room in the `rooms` map
4. **Data mutation** – When any participant updates trip data, the relevant service calls `broadcast(tripId, eventType, payload, originatorSid)`
5. **Client update** – All other sockets in the room receive the payload, triggering `handleRemoteEvent` or file-sync callbacks to update the UI
6. **Network resilience** – If connectivity drops, automatic reconnection logic reestablishes the socket and re-joins all active trips, optionally refreshing data to ensure consistency

## Summary

- **TREK uses a room-based WebSocket architecture** where each trip represents a distinct room managed through [`server/src/websocket.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/websocket.ts)
- **Dual mapping strategy** uses `rooms` (trip-to-sockets) and `socketRooms` (socket-to-trips) for efficient membership tracking and cleanup
- **Authentication** requires one-time ws-tokens for connection establishment, with permission checks via `canAccessTrip` before allowing room entry
- **Client singleton pattern** in [`client/src/api/websocket.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/api/websocket.ts) handles connection management, automatic reconnection with exponential back-off, and active trip tracking
- **React integration** through `useTripWebSocket` provides declarative subscription management and automatic cleanup on component unmount
- **Broadcast exclusion** prevents echo-back by skipping the originator socket when distributing updates to room participants

## Frequently Asked Questions

### How does the server prevent unauthorized users from joining trip rooms?

The server validates every join request using the `canAccessTrip(tripId, user.id)` function before adding the socket to the room. This check occurs in [`server/src/websocket.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/websocket.ts) when processing join messages, ensuring only users with proper permissions can receive real-time updates for a specific trip.

### What happens when a user's network connection drops temporarily?

The client-side WebSocket manager implements automatic reconnection with exponential back-off. Upon reconnecting, it references the `activeTrips` set to automatically re-send `join` messages for all trips the user was viewing. An optional `preReconnectHook` can flush pending mutations and trigger data refetching to ensure consistency after reconnection.

### How does the system handle file synchronization between collaborators?

The `useTripWebSocket` hook registers specialized listeners for file-related events such as `collab:note:deleted` and `collab:note:updated`. When these events arrive from the server, they trigger `tripStore.loadFiles(tripId)` to refresh the file list, ensuring all participants see document changes immediately without manual refreshes.

### Why does the broadcast function exclude the originator socket?

The `broadcast` function accepts an `excludeSid` parameter that specifies a socket ID to skip when sending messages. This prevents users from receiving echoes of their own changes, reducing network traffic and eliminating the need for clients to filter out their own updates while maintaining instantaneous synchronization for all other room participants.