My App
BoardKitBackend

CollaborationService

Realtime session management with state machine lifecycle

CollaborationService manages realtime collaboration sessions. Each board has at most one active session at a time.

Session State Machine

created → active → idle → archived
StateDescription
createdSession initialized, waiting for first participant
activeAt least one participant is connected
idleAll participants disconnected, idle timer running
archivedIdle timeout reached; session data flushed and cleaned up

Transitions:

  • created → active: First participant joins
  • active → idle: Last participant leaves
  • idle → active: A participant reconnects before timeout
  • idle → archived: Timeout expires (5 min for ephemeral, 15 min for persistent)

BoardSession

interface BoardSession {
    boardId: string;
    participants: Map<string, Participant>;
    pendingMutations: ElementMutation[];
    currentSequence: number;
    lastFlush: number;
    state: SessionState;
    idleSince: number | null;
    sessionType: 'ephemeral' | 'persistent';
}

type SessionState = 'created' | 'active' | 'idle' | 'archived';

Methods

joinSession

Joins or creates a session. Returns sync information for the client.

async joinSession(
    boardId: string,
    userId: string,
    displayName: string,
    lastSequence?: number,
    sessionType?: 'ephemeral' | 'persistent',
): Promise<JoinResult>;
interface JoinResult {
    session: BoardSession;
    syncType: 'full' | 'delta';
    missedMutations?: ElementMutation[];
}
  • If lastSequence is provided and fewer than 1000 events have occurred since, returns syncType: 'delta' with missedMutations.
  • Otherwise returns syncType: 'full' (client should fetch complete board state).

leaveSession

Removes a participant. Transitions to idle if no participants remain.

async leaveSession(boardId: string, userId: string): Promise<void>;

applyMutations

Applies element mutations, logs events, and increments the sequence counter. Returns the new sequence number.

async applyMutations(
    boardId: string,
    userId: string,
    mutations: ElementMutation[],
): Promise<number>;

flushPending

Persists all pending mutations to storage. Groups element upserts by page.

async flushPending(boardId: string): Promise<void>;

getSessionState

Returns the current session for a board, or null if no session exists.

async getSessionState(boardId: string): Promise<BoardSession | null>;

getSessionStats

Returns aggregate statistics across all active sessions.

getSessionStats(): SessionStats;
interface SessionStats {
    activeSessions: number;
    idleSessions: number;
    totalParticipants: number;
}

closeSession

Flushes pending mutations and removes the session.

async closeSession(boardId: string): Promise<void>;

Idle Timeout Behavior

The service runs a background interval (every 60 seconds) that checks for idle sessions:

Session TypeIdle TimeoutOn Archive
ephemeral5 minutesFlush pending mutations, delete session
persistent15 minutesFlush pending mutations, create snapshot, delete session

Persistent sessions create a snapshot of all page elements when archived, stored via EventLogStorage.createSnapshot().

Participant Colors

Participants are assigned colors from a rotating palette when they join:

#FF6B6B, #4ECDC4, #45B7D1, #96CEB4, #FFEAA7, #DDA0DD, #98D8C8, #F7DC6F

The color index is based on the number of existing participants modulo the palette size.

On this page