TPTruPDF

@trupdf/collaboration

WebSocket real-time sync. Exponential reconnect, presence, awaitable acks, stop-on-destroy.

CollaborationEngine

import { CollaborationEngine } from '@trupdf/viewer';
 
const collab = new CollaborationEngine({
  url: 'wss://collab.example.com/v1',
  documentId: 'doc-123',
  name: 'Alice',
  authToken: sessionToken, // optional; sent in the join message
});
 
collab.connect();

Options

OptionDefaultPurpose
urlWebSocket URL (required)
documentIdRoom to join (required)
nameDisplay name for cursor / badge (required)
authTokenBearer token validated server-side
initialBackoffMs500First reconnect delay
maxBackoffMs30000Reconnect delay cap
heartbeatMs15000Ping interval
websocketCtorglobalInjectable WebSocket constructor (tests)

Methods

MethodPurpose
connect()Open the socket and send the join message
send(msg)Send any wire message (protocol field added automatically)
sendOp(op)Send an annotation operation; resolves with the server-assigned seq on ack
getPeers()Snapshot of remote peers (PresenceState[])
getPeerId()This client's server-assigned peer id (null before join)
destroy()Close the socket, stop reconnect + heartbeat timers, clear listeners

Events

collab.on('open', () => /* joined */);
collab.on('close', ({ code, reason, clean }) => /* … */);
collab.on('message', (msg) => /* any wire message */);
collab.on('presence', (peers) => /* readonly PresenceState[] */);
collab.on('error', (err) => /* … */);

Wire protocol

COLLAB_PROTOCOL_VERSION = 1. Every message carries a protocol field. Message types:

TypeDirectionPurpose
joinclient → server{ documentId, name, token? }
joinedserver → client{ peerId, peers[], lastSeq } — room snapshot
leaveserver → client{ peerId }
presenceboth{ peerId, page, cursor?, selection? } — cursor in PDF user space
opboth{ peerId, seq, op }op.kind: 'add' | 'update' | 'remove'
ackserver → client{ requestId, seq } — resolves the matching sendOp promise
errorserver → client{ code, message, requestId? }
ping / pongclient ⇄ serverKeepalive

Typed message interfaces (JoinMessage, JoinedMessage, PresenceMessage, OperationMessage, AckMessage, ErrorMessage, …) are exported from the package. Bring your own server — any WebSocket backend that speaks this protocol works.

Reconnect

Exponential backoff from initialBackoffMs doubling to the maxBackoffMs cap. Heartbeat ping every 15 s detects dead links faster than TCP. Stops on destroy — no zombie sockets.

License gate

The constructor calls gate.require('collab'). Trial cannot connect.