Collaboration
Connect DOCX, XLSX, and PPTX editors through a shared sync-v1 relay
BetterOffice exposes the same collaboration boundary for DOCX, XLSX, and PPTX: an editor-owned Yrs replica, a sync-v1 provider, and a caller-owned binary transport. The backend routes frames by room and never parses Office files.
Architecture
- The editor publishes its replica through
collaboration.onReplica. - The format provider converts replica updates to Yjs sync-v1 frames.
- Your transport moves those frames between peers in the same document room.
Use a distinct room ID for every document and format. Updates from different documents or formats are not interoperable.
Deploy the Cloudflare relay
The reference relay
uses one Durable Object per room. It accepts WebSockets at /room/:roomId,
replays retained frames to new peers, and broadcasts new binary frames. Its
wrangler.jsonc
contains the required ROOMS binding and SQLite migration.
From a BetterOffice checkout:
bun install
bun run --filter @betteroffice/collaboration-relay dev
bun run --filter @betteroffice/collaboration-relay deployPoint your browser transport at the deployed Worker origin. The complete demo
adapter is createRoomTransport;
it handles WebSocket reconnects, binary ownership, and backpressure. If you write
your own adapter, implement the CollaborationTransport exported by the format
package. A false return from send means the adapter must emit drain when it
can accept another frame.
Connect an editor
The provider import differs by format; the transport contract does not.
| Format | Provider import |
|---|---|
| DOCX | @betteroffice/docx/collaboration |
| XLSX | @betteroffice/xlsx/collaboration |
| PPTX | @betteroffice/pptx |
Create the provider when the editor publishes its replica:
const onReplica = (replica) => {
provider?.destroy();
transport?.disconnect();
if (!replica) return;
transport = createRoomTransport(relayOrigin, roomId);
provider = new CollaborationProvider(replica, transport);
provider.connect();
};Pass the same collaboration shape to any React editor:
const collaboration = { clientId, initialUpdate, onReplica };
<DocxEditor documentBuffer={docxBytes} collaboration={collaboration} />
<XlsxEditor file={xlsxBytes} collaboration={collaboration} />
<PptxEditor file={pptxBytes} fonts={fonts} collaboration={collaboration} />clientId must be unique per open peer. initialUpdate must represent the
shared starting state for that document. The demo generates these states with
build-collaboration-seeds.ts.
Call provider.destroy() and disconnect the transport when the editor unmounts
or changes rooms.
Production boundary
The reference relay is intentionally small. Add authentication before resolving a room, authorize each room ID, enforce connection and storage quotas, and define a snapshot or compaction policy for long-lived documents. The included relay retains at most 512 frames or 16 MiB per room.