host.collab
host.collab is the App SDK surface for realtime multi-user rooms backed by the Hocuspocus collab pod and Yjs CRDTs. The host checks the workspace ACL, then mints a short-lived JWT your app uses to dial the pod with the Yjs provider of your choice (@hocuspocus/provider). The host does not bundle a Y.Doc for you: you bring your own CRDT runtime and open the socket with the token.
Collab is gated by the manifest
collabflag. Only an app whosewidget.jsondeclarescollab: truemay mint aroom-scope (code-based lobby) token, and the registry uses that flag to authorize the mint. See the manifest reference.
API
interface CollabNamespace {
getToken(scope: 'workspace' | 'item' | 'room', scopeId: string): Promise<CollabTokenResult>;
getTurnCredentials(): Promise<{ turn: TurnCredentials | null }>;
room(contentId: string): CollabRoomApi;
}
interface CollabTokenResult {
token: string; // pass to new HocuspocusProvider({ token })
wsUrl: string; // absolute wss:// URL to dial
roomId: string; // Hocuspocus document name: new HocuspocusProvider({ name: roomId })
expiresAt: string; // ISO-8601 token expiry
identity: ParticipantIdentity; // write into awareness.setLocalStateField('user', identity)
}
interface ParticipantIdentity { id: string; displayName: string; color: string }
interface CollabRoomApi {
generateCode(len?: number): string; // fresh shareable code (default len 6, charset A-Z/2-9)
join(code: string): Promise<CollabTokenResult>; // token for the <contentId>:<code> lobby
}
interface TurnCredentials {
username: string;
credential: string;
urls: string[]; // pass straight into RTCIceServer.urls
expiresAt: number; // wall-clock unix-seconds
}Room scopes
getToken(scope, scopeId) keys off one of three scopes:
| Scope | scopeId | Use |
|---|---|---|
workspace | a workspace id | Workspace-wide presence and shared cursors across the canvas. |
item | a workspace item id | A per-item Yjs subdoc, one room per canvas item. |
room | <contentId>:<roomCode> | A code-based lobby under a collab-enabled app's content id. |
The document name on the wire is the returned roomId.
Join a room
import { HocuspocusProvider } from 'https://esm.sh/@hocuspocus/provider';
import * as Y from 'https://esm.sh/yjs';
await host.ready();
const { token, wsUrl, roomId, identity } = await host.collab.getToken(
'item',
host.workspace.itemId,
);
const ydoc = new Y.Doc();
const provider = new HocuspocusProvider({ url: wsUrl, name: roomId, token, document: ydoc });
// Identify yourself in awareness so peers see your name + cursor color.
provider.awareness.setLocalStateField('user', identity);Tokens are short-lived (expiresAt). Re-mint when you reconnect after an expiry.
Code-based lobby rooms
host.collab.room(contentId) is sugar for shareable lobby rooms under a collab-enabled app. Generate a code, share it, and join:
const lobby = host.collab.room(myContentId);
const code = lobby.generateCode(); // e.g. 'K7P2QX'
const { token, wsUrl, roomId } = await lobby.join(code); // code is uppercasedjoin(code) mints a token for the <contentId>:<code> room. This path is what the collab manifest flag gates: a room token cannot be used to reach arbitrary item or workspace rooms.
TURN credentials for WebRTC
If your app does WebRTC (a video huddle, a voice call), call getTurnCredentials only when the call actually starts, not on mount. It triggers a TURN-provider round-trip, so it is deliberately not bundled into getToken (which runs on every collab mount).
const { turn } = await host.collab.getTurnCredentials();
const pc = new RTCPeerConnection({
iceServers: turn
? [{ urls: turn.urls, username: turn.username, credential: turn.credential }]
: [], // null → no relay provider configured; run STUN-only
});turn is null when no relay provider is configured; fall back to STUN-only in that case.
Reserved awareness namespaces
When you publish awareness updates, do not spoof host-owned keys. The collab pod drops inbound updates from app origins that touch:
presence.*: canvas cursors and follow-me (host chrome)comment.*: thread typing indicatorswebrtc.*: multi-party RTC signalinguser: the top-level identity field (set it only with theidentityfromgetToken)
Errors
service_unavailable: the collab pod or Redis is briefly unavailable; retry with backoff.- ACL failures and token-reuse replay rejections surface per the error codes.
Related
- host.documents: seed a room's starting state and keep durable version history
- host.live: 1 to N broadcast (not N to N)
- widget.json manifest: the
collabflag that gates room tokens