Appearance
PulseClient API
When Do You Need This?
Most users only need <pulse-widget>. The PulseClient API is for building your own custom UI instead of using the built-in widget.
PulseClient is the low-level client that connects to the Pulse server. The widget uses it internally.
Setup
typescript
import { PulseClient } from '@gamention/pulse-core';
const client = new PulseClient({
apiKey: 'pk_your_key',
token: userToken,
room: 'dashboard',
endpoint: 'wss://pulse.hire.rest'
});
client.connect();| Option | Type | Required | Description |
|---|---|---|---|
apiKey | string | Yes | Publishable key (pk_...) |
token | string | Yes | User JWT from /auth/token |
room | string | Yes | Room identifier |
endpoint | string | Yes | Pulse server WebSocket URL |
Connection
typescript
client.connect(); // Connect
client.disconnect(); // Disconnect and clean up
console.log(client.connectionState); // "connected" | "connecting" | "disconnected"
client.on('connection', (state) => { ... }); // Listen for changesReconnection
Pulse automatically reconnects with exponential backoff:
- Base delay: 1 second
- Maximum delay: 30 seconds
- Presence heartbeat: every 30 seconds
- Users appear offline after: 60 seconds of no heartbeat
On reconnect, the client re-authenticates and receives the full current state from the server. You do not need to handle reconnection manually.
Threads & Comments
typescript
// Create a thread (returns the thread ID)
const threadId = client.createThread('This button looks off', {
mentions: ['user-2'], // optional @mentions
position: { x: 0.45, y: 0.72 }, // optional pin position (0-1 range)
attachmentIds: ['attach-uuid'], // optional pre-uploaded attachment IDs
});
// Reply to a thread (returns the comment ID)
// Fourth argument is an optional array of attachment IDs
const commentId = client.reply('thread-id', 'Agreed, fixing now', ['user-1'], ['attach-uuid']);
// Edit your own comment
client.editComment('comment-id', 'Updated text', []);
// Delete your own comment — applies optimistic local removal immediately
client.deleteComment('comment-id');
// Resolve / reopen a thread
client.resolveThread('thread-id', true); // resolve
client.resolveThread('thread-id', false); // reopenReactions
typescript
client.addReaction('comment-id', 'comment', '👍');
client.removeReaction('reaction-id');Presence & Cursors
typescript
client.moveCursor({ x: 0.5, y: 0.3, pageX: 500, pageY: 300 });
client.updatePresence('idle'); // 'online' | 'idle'
client.setAppearOffline(true); // hide from presenceOther Interactions
typescript
client.sendTyping('thread-id'); // typing indicator
client.performClick({ x: 0.5, y: 0.3, pageX: 500, pageY: 300 });
// Send an emoji drop visible to all users in the room
client.dropEmoji('🎉', { x: 0.5, y: 0.3, pageX: 500, pageY: 300 });
client.drawStroke(
[{ x: 100, y: 200 }, { x: 150, y: 250 }],
'#ff0000', 3
);
client.clearDrawing();Notifications
typescript
client.markRead('notification-id');
// Mark all as read — optimistic local update, no server round-trip wait
client.markAllRead();File Uploads
typescript
// Upload a file — returns attachment metadata
const attachment = await client.uploadFile(file);
// { id, type, filename, url, thumbnailUrl? }
// Then pass the ID when creating a thread or reply
client.createThread('See attached', { attachmentIds: [attachment.id] });Uploads go to POST /api/v1/upload using the same pk_ + JWT credentials as the WebSocket connection. Errors (file too large, type disabled) are thrown as Error with the server's message.
See Upload API for the full endpoint reference.
Accessing State
typescript
client.state.user; // Current user (PulseUser)
client.state.threads; // All threads (Thread[])
client.state.presence; // Online users (PresenceUser[])
client.state.notifications; // User notifications (Notification[])
client.state.reactions; // All reactions (Reaction[])
client.state.users; // All known users in the room (PulseUser[])
client.state.config; // Environment feature flags (EnvironmentConfig)
client.state.getUser('id'); // Look up any user by IDSee State & Events for subscribing to changes.