> ## Documentation Index
> Fetch the complete documentation index at: https://docs.doconda.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Real-time co-editing

> Several people editing the same Word, Excel, PowerPoint or PDF at once: on Doconda's server or on yours.

With `collab`, everyone who opens the same document in the editor sees the others' changes as they type, with their
name and colour on the cursor. It works in Word, Excel, PowerPoint and PDF (in PDF, the annotations are shared). The
status bar shows whether the editor is connected and how many more people are editing.

You choose who carries the changes:

| | What you give the editor | Who runs the server |
| - | - | - |
| **Doconda's server** | `collab={{ token, user }}` | Doconda, included in the licence |
| **Your server** | `collab={{ url, user }}` | You: a WebSocket server that forwards messages |
| **Your transport** | `collab={{ provider, user }}` | You: any channel (WebRTC, your realtime service…) |

Pass only one of `token`, `url` or `provider`. `user` is `{ id, name }`: `name` shows on the others' screens
next to the cursor, and signs comments and tracked changes when you don't pass `author`. Each screen picks the others'
colours itself, as Word does: steady while they stay connected and never the blue of your own selection. In Word you
see their cursor and selection; in Excel, the cells they have selected; in PowerPoint, the shapes they have selected.

Tracked changes work like Word's: each person chooses **For everyone** (the document records every change) or **Just
mine** (only their own changes are recorded; the others keep editing freely). Changes and comments also show as cards
in the margin when the page leaves room, with accept and reject on each change.

Everything can be done while co-editing: adding, deleting or moving slides and sheets, images, charts, tables, pivot
tables, comments, notes, themes, layouts and document properties travel to the others, and each person undoes only
their own changes. The one thing that waits: in a Word document that has no list yet, lists can't be started while
co-editing. Two known limits when two people do the same thing at the very same moment: two new sheets with the same
name (both called "Sheet3") end up duplicated, and if both add the first chart or image to the same sheet, one is
kept.

## On Doconda's server

Your backend asks for a token for the document with your API key, and the editor connects with it. The **room** is
your id for the document: everyone with a token for the same room edits together.

```ts Your backend theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { Doconda } from "@doconda/sdk"

const doconda = new Doconda() // DOCONDA_API_KEY

app.post("/api/documents/:id/collab-token", async (req, res) => {
  // check that this user may open this document, then:
  const { token } = await doconda.editor.collabToken(`doc-${req.params.id}`)
  res.json({ token })
})
```

```python Your backend (Python) theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
from doconda import Doconda

token = Doconda().editor.collab_token(f"doc-{document_id}")["token"]
```

```tsx Your page theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
<DocondaEditor
  src={`/files/${id}.docx`}
  licenseKey="ek_eu_…"
  collab={{
    token: () => fetch(`/api/documents/${id}/collab-token`, { method: "POST" }).then((r) => r.json()).then((j) => j.token),
    user: { id: me.id, name: me.name },
  }}
/>
```

* The token is valid for **10 minutes** to connect, only in that room and only from your licence's domains (and
  `localhost` while you develop). A connection already open stays open.
* Pass `token` as a function: the editor calls it again every time it has to reconnect. A fixed string works too, but
  a reconnection after 10 minutes is refused.
* Without the SDK it's `POST /editor/collab/token` with `{ "room": "…" }` (see the API reference, **Editor**).
* Up to 50 people per room. Doconda stores nothing: the changes only travel between the people connected.

## On your server

The editor speaks a very small protocol over WebSocket, so the server only has to **forward each message to the other
people in the same room** (not back to the sender). This is a complete server in Node with
[`ws`](https://github.com/websockets/ws):

```ts server.ts theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { WebSocketServer } from "ws"

const rooms = new Map() // room → Set of sockets

new WebSocketServer({ port: 8080 }).on("connection", (socket, req) => {
  // authenticate here: who is this user, and may they open this document?
  const room = new URL(req.url, "http://x").searchParams.get("room")
  const peers = rooms.get(room) ?? rooms.set(room, new Set()).get(room)
  peers.add(socket)
  let peer // the last awareness id seen from this socket
  socket.on("message", (data, binary) => {
    if (!binary) return
    if (data[0] === 1) peer = JSON.parse(data.subarray(1)).peer
    for (const other of peers) if (other !== socket) other.send(data)
  })
  socket.on("close", () => {
    peers.delete(socket)
    if (!peers.size) rooms.delete(room)
    const left = Buffer.concat([Buffer.from([1]), Buffer.from(JSON.stringify({ peer, state: null }))])
    if (peer) for (const other of peers) other.send(left)
  })
})
```

```tsx Your page theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
<DocondaEditor src={url} collab={{ url: `wss://collab.acme.com/?room=doc-${id}`, user: { name: me.name } }} />
```

Each message is binary. Its first byte says what it is:

| Byte | Content | What the server does |
| - | - | - |
| `0` | A change to the document (a Yjs update) | Forward it |
| `1` | Presence, JSON `{ peer, state }` (name, cursor) | Forward it |
| `2` | Hello, empty: someone just connected and asks the others for their state | Forward it |

When a connection closes, send the others `1` + `{ "peer": <its last peer>, "state": null }` so its cursor
disappears (the example above does). Authenticate however you like: a cookie, or a token in the URL that your page
puts there. To refuse a connection for good, close it with a code from **4000 to 4999**: the editor doesn't retry.
Any other close (a restart, a network cut) makes it reconnect by itself, waiting a little longer each time.

## On your own transport

`provider` is any object with `send(update)`, `onUpdate(cb)`, `onConnect(cb)`, `setAwareness(state)` and
`onAwareness(cb)`. The editor exports `WebSocketProvider`, the one it uses for `url` and `token`, if you want to start
from it:

```ts theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { WebSocketProvider } from "@doconda/editor"

const provider = new WebSocketProvider(() => new WebSocket(myUrl()), crypto.randomUUID())
```

## Saving

The room only lives while someone is connected: when the last person leaves, the document is what each of you saved.
Keep saving as usual with `onSave` (or `ref.save()`): everyone in the room has the same content, so it doesn't matter
who saves. The next person to open the document opens the saved file. While `collab` is on, the browser recovery copy
(`autosave`) is off, so an old copy can't overwrite the others' work.

## Connection problems

* The editor opens the document once it has joined the room and received what the others have (so it never overwrites
  their work with an older file); until then it shows **Connecting…**.
* The status bar shows **Connecting…** (it lost the connection and is retrying), the number of people editing, or
  **Offline**.
* If the editor can't get in at all (an expired token, a domain that isn't in the licence, a full room), it opens the
  document without co-editing and `onError` receives `editor_collab_closed`.
* On Doconda's server the close codes are `4401` (invalid or expired token), `4403` (no licence for this domain),
  `4429` (room full) and `4413` (a message over 64 MB).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.