Skip to content

State & Events

Subscribe to real-time state changes from PulseClient.

When Do You Need This?

Only if you're building custom UI instead of using <pulse-widget>.

Subscribing to Changes

Use client.state.on() to get notified when data changes. It returns an unsubscribe function.

typescript
// Fires when auth completes — safe point to read config and initial state
client.state.on('auth', (user) => {
  console.log('Signed in as', user.name);
  console.log('Max file size:', client.state.config.maxFileSizeMb, 'MB');
  renderUI();
});

// Threads updated (created, resolved, deleted, new comment, etc.)
const unsub = client.state.on('threads', (threads) => {
  renderThreadList(threads);
});

// Presence changed (user joined, left, or changed status)
client.state.on('presence', (users) => {
  renderAvatars(users);
});

// Notifications
client.state.on('notifications', (notifs) => {
  const unread = notifs.filter(n => !n.read).length;
  updateBadge(unread);
});

// Reactions
client.state.on('reactions', ({ targetId, reactions }) => {
  updateReactionCounts(targetId, reactions);
});

// Activity log
client.state.on('activity-logs', (logs) => {
  renderActivityFeed(logs);
});

// Unsubscribe when done
unsub();

Real-Time Events

Individual events for live interactions:

typescript
// Someone's cursor moved
client.state.on('cursor', ({ userId, position }) => { ... });

// Someone clicked
client.state.on('click', ({ userId, position }) => { ... });

// Someone is typing
client.state.on('typing', ({ userId, threadId }) => { ... });

// Viewport update
client.state.on('viewport', ({ userId }) => { ... });

// Text selection changed
client.state.on('selection', ({ userId, selection }) => { ... });

// Drawing stroke received
client.state.on('draw-stroke', ({ userId, points, color, width }) => { ... });

// Drawing cleared
client.state.on('draw-clear', ({ userId }) => { ... });

// Emoji drop
client.state.on('emoji-drop', ({ userId, emoji, position }) => { ... });

Connection Events

These fire on client directly:

typescript
client.on('connection', (state) => {
  // 'connected' | 'connecting' | 'disconnected'
});

client.on('auth:ok', (data) => {
  console.log('Authenticated as:', data.user.name);
});

client.on('auth:error', (data) => {
  console.error('Auth failed:', data.message);
});

Full Event Reference

EventFires OnPayloadWhen
authclient.statePulseUserAuthentication completed; initial state populated
threadsclient.stateThread[]Any thread or comment change
presenceclient.statePresenceUser[]User joined, left, or changed status
notificationsclient.stateNotification[]New notification or read state changed
reactionsclient.state{ targetId, reactions }Reaction added or removed
activity-logsclient.stateActivityLog[]New activity logged
cursorclient.state{ userId, position }Cursor moved
clickclient.state{ userId, position }Click indicator
typingclient.state{ userId, threadId }Typing indicator
viewportclient.state{ userId }Viewport scrolled
selectionclient.state{ userId, selection }Text selection changed
draw-strokeclient.state{ userId, points, color, width }Drawing stroke received
draw-clearclient.state{ userId }Drawing cleared
emoji-dropclient.state{ userId, emoji, position }Emoji drop received
connectionclient'connected' | 'connecting' | 'disconnected'WebSocket state changed
auth:okclientauth dataServer accepted credentials
auth:errorclient{ message }Server rejected credentials

State Properties

Direct accessors on client.state:

PropertyTypeDescription
userPulseUser | nullCurrent authenticated user
configEnvironmentConfigEnvironment feature flags (set on auth, read-only)
usersPulseUser[]All known users in the room (from presence + comment authors)
threadsThread[]All threads in the room
presencePresenceUser[]Currently online users
notificationsNotification[]Notifications for the current user
unreadCountnumberCount of unread notifications
activityLogsActivityLog[]Recent room activity (max 100)

Key Types

typescript
interface PulseUser {
  id: string;
  name: string;
  avatar?: string;
  color: string;        // auto-assigned by Pulse
}

interface Thread {
  id: string;
  roomId: string;
  resolved: boolean;
  position: PinPosition | null;
  comments: Comment[];
  createdAt: string;
  updatedAt: string;
}

interface PinPosition {
  x: number;
  y: number;
  selector?: string;
  path?: string;
  elementOffsetX?: number;
  elementOffsetY?: number;
  scrollX?: number;
  scrollY?: number;
}

interface Comment {
  id: string;
  threadId: string;
  userId: string;
  body: string;
  mentions: string[];
  attachments: Attachment[];
  createdAt: string;
  editedAt: string | null;
}

interface Attachment {
  id: string;
  commentId: string;
  type: 'image' | 'audio' | 'video';
  filename: string;
  mimeType: string;
  sizeBytes: number;
  url: string;
  thumbnailUrl?: string;
  durationMs?: number;
  width?: number;
  height?: number;
  createdAt: string;
}

interface PresenceUser {
  user: PulseUser;
  cursor: CursorPosition | null;
  status: 'online' | 'idle';
  lastSeen: string;
  deviceType?: 'desktop' | 'mobile' | 'tablet';
}

interface CursorPosition {
  x: number;
  y: number;
  pageX: number;
  pageY: number;
}

interface Notification {
  id: string;
  userId: string;
  type: 'comment:created' | 'comment:mention' | 'comment:reply'
      | 'thread:resolved' | 'reaction:added';
  roomId: string;
  threadId?: string;
  commentId?: string;
  actorId: string;
  read: boolean;
  createdAt: string;
}

interface Reaction {
  id: string;
  targetId: string;
  targetType: 'comment' | 'thread';
  userId: string;
  emoji: string;
  createdAt: string;
}

interface ActivityLog {
  id: string;
  roomId: string;
  userId: string;
  type: 'join' | 'leave' | 'comment' | 'thread' | 'resolve';
  description: string;
  createdAt: string;
}

interface SelectionRange {
  startSelector: string;
  startOffset: number;
  endSelector: string;
  endOffset: number;
}

interface EnvironmentConfig {
  allowImages: boolean;
  allowAudio: boolean;
  allowVideo: boolean;
  maxFileSizeMb: number;
  maxAttachmentsPerComment: number;
  allowReactions: boolean;
  allowDrawing: boolean;
  allowMentions: boolean;
  showCursors: boolean;
  showPresence: boolean;
  showTypingIndicators: boolean;
}

Pulse Collaboration SDK