Skip to content

Presentation Sessions

Sessions enable real-time synchronization between presenters and audience members during live presentations.

When you present a deck, a session is created to coordinate state across all connected clients. The session tracks which slide is active, handles follow codes, and broadcasts updates via Server-Sent Events (SSE).

  1. Create - Presenter starts presenting, session created
  2. Active - Audience joins, state synchronized
  3. Update - Presenter navigates, state broadcast
  4. Expire - No activity for 30 minutes, session cleaned up

Each session stores:

{
sessionId: 'abc123',
presentationId: 'pres456',
state: {
slideId: 'slide789',
slideIndex: 3,
slideType: 'content-slide',
stepIdx: 0,
stepParagraphs: false,
updatedAt: 1705312200000
},
controlEnabled: false,
followCodes: {
nl: 'ABCD',
en: 'EFGH'
},
followCodesCreatedAt: 1705312200000,
createdAt: 1705312200000,
lastActivityAt: 1705312200000,
clients: Set(),
heartbeatTimers: Map()
}
POST /api/present-sessions
Content-Type: application/json
{
"presentationId": "abc123"
}
{
"sessionId": "xyz789",
"joinPath": "/notes/xyz789",
"followCodes": {
"nl": "ABCD",
"en": "EFGH"
}
}

If an active session exists for the presentation, it’s reused:

  • Same session ID returned
  • Follow codes refreshed if expired
  • Activity timestamp updated
PropertyDescription
slideIdCurrent slide’s unique ID
slideIndex0-based slide position
slideTypeType of current slide
stepIdxBuild step within slide
stepParagraphsParagraph-level stepping enabled
updatedAtLast state change timestamp

When presenter navigates:

POST /api/present-sessions/:sessionId/state
Content-Type: application/json
{
"slideId": "slide789",
"slideIndex": 5,
"stepIdx": 2
}

The current state can be read back with GET /api/present-sessions/:sessionId/state. State is broadcast to all connected clients via SSE.

Short, easy-to-share codes for audience to join.

https://your-instance.com/go
Code: ABCD
  • 4 uppercase letters
  • One code per language
  • Valid for 24 hours

When audience enters a code:

  1. Code looked up in database
  2. Target URL returned
  3. Viewer redirected to follow view
  4. SSE connection established

Codes are automatically refreshed:

  • When they expire (24 hours)
  • Session must still be active
  • Presenter sees updated codes

Clients connect to receive updates:

const eventSource = new EventSource(
'/api/present-sessions/ABC123/events'
);

On connection, clients receive:

  • Current state snapshot
  • Control enabled status
EventDescription
stateSlide navigation
controlEnabledAudience control toggle
deckUpdatedPresentation content changed
interactionStatePoll/Q&A updates
branchNavigate to branched slide

See Real-Time Updates for details.

Allow audience to self-navigate. Enable and disable are separate endpoints (no request body):

POST /api/present-sessions/:sessionId/control/enable
POST /api/present-sessions/:sessionId/control/disable

(POST /api/present-sessions/:sessionId/control is a different endpoint: it sends a remote-navigation command, not the audience-control toggle.)

  • Disabled (default) - Audience locked to presenter’s slide
  • Enabled - Audience can navigate freely

Control state changes are broadcast to all clients.

Sessions expire after 30 minutes of inactivity.

These actions reset the timer:

  • State updates
  • Client connections
  • Control toggles
  • Any API calls to session

Expired sessions are:

  • Removed from memory
  • Cleaned from disk storage
  • Follow codes invalidated

There is no explicit “close session” endpoint. A session ends by expiring after its inactivity window (see TTL below); stopping presenting simply lets it lapse.

Active sessions are stored in memory for performance.

Sessions are periodically saved to disk:

  • On state changes (debounced)
  • On graceful shutdown
  • Loaded on server restart
./server/data/present-sessions/

Multiple users can present the same deck:

  • Same session is reused
  • State updates from any presenter
  • All audience members see same state

Last write wins - the most recent navigation is broadcast.

Keep SSE connections alive through proxies.

Every 30 seconds:

: heartbeat

Each client has its own heartbeat timer:

  • Created on connection
  • Cleared on disconnect
  • Used to detect stale connections

Default: sessions stored in memory, persisted to disk.

For multiple servers:

  • Use Redis for session storage
  • Use Redis pub/sub for broadcasts
  • Sticky sessions or distributed coordination

All session endpoints are under the base path /api/present-sessions.

PurposeMethodPath
Create (or reuse) a sessionPOST/api/present-sessions
Read current stateGET/api/present-sessions/:sessionId/state
Update state (navigate)POST/api/present-sessions/:sessionId/state
Enable audience controlPOST/api/present-sessions/:sessionId/control/enable
Disable audience controlPOST/api/present-sessions/:sessionId/control/disable
Remote-navigation commandPOST/api/present-sessions/:sessionId/control
SSE event streamGET/api/present-sessions/:sessionId/events

There is no DELETE / close endpoint; sessions expire on inactivity.

  • Session may have expired
  • Check sessionId is correct
  • Verify session was created
  • Code may have expired (24 hours)
  • Check code was typed correctly
  • Verify session is still active
  • Check SSE connection is open
  • Verify no network issues
  • Look for errors in browser console
  • Many concurrent sessions
  • Large numbers of connected clients
  • Consider horizontal scaling