Skip to content

Commit b2866ef

Browse files
maitrakhatrinadeem-fileversemritunjayz
authored
feat: Real Time Collaboration (#392)
* docs * install necessary packages * add sync-local * setup useSyncManager * setup collaboration awareness * collab status chip * webRTC cleanup * minor fixes * RTC on Demo * minor fix * fix: start and join RTC * fix: dsheetID in demo * debug logs * remove strict mode + clean up * E2E RTC Flow documentation * fix: flicker, sheet metadata events * Cursor UI * minor refactor * fix: cursor awareness * update merge on documentation * clean up * add spec for app identifier during collab session * minor update to spec * minor changes * add APP_TYPE in socket auth * update packages * fix: auto new sheet on refresh * minor changes for terminate sessoin * minor changes related to collaborator list and awarness * fix: cursor UX * fix editor ref related issue * disable comments on RTC * SC response sync * fix datablock new sheet addition * minor changes * merge with main * minor fix sync from peer to owner * fresh build * update with main * fix: DV changes in RTC * fix: flicker on using filters * fix: flicker on img & iframe position change * fix: filter flickering * wip: filter fix * fix formula over rtc * fix: filters * fix DV on peers side * revamp remote apply lock * safer termination * ens filed in collabUser * version + docs upgrade --------- Co-authored-by: Nadeem <nadeem@fileverse.io> Co-authored-by: Mritunjay Goutam <mritunjaygoutam2204@gmail.com>
1 parent 5f7083f commit b2866ef

53 files changed

Lines changed: 6129 additions & 644 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

APP_TYPE_IDENTIFIER_SPEC.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Spec: `appType` identifier for shared collaboration
2+
3+
**Status:** Design approved 2026-06-04. Server side implemented on `collaboration-server@feat/app-type-identifier`. This document is the implementation spec for the **dsheet client** change, and records the server contract it relies on.
4+
5+
**Related:** [`RTC_FLOW.md`](./RTC_FLOW.md) — end-to-end RTC flow for dsheet.
6+
7+
---
8+
9+
## 1. Problem
10+
11+
ddoc and dsheet share one collaboration server (custom Socket.IO + Yjs, MongoDB storage). Today every session/update/commit is keyed only by `documentId` (a UUID) and `sessionDid` (a DID derived from the room key). The two apps are **indistinguishable** in storage and on the wire — both send identical-shaped `/auth`, `/documents/update`, `/documents/commit` payloads.
12+
13+
We want to differentiate them, for three reasons (in priority order):
14+
15+
1. **Isolation guard** — reject a client of one app that tries to join/load the other app's room.
16+
2. **Observability** — know which app a session/connection belongs to (logs, dashboards).
17+
3. **Data lifecycle** — query / export / purge one app's updates & commits independently.
18+
19+
## 2. Decision (Approach A)
20+
21+
Add an optional identifier **`appType: 'ddoc' | 'dsheet'`**, declared **once** by the client at `/auth`. The **server is authoritative**: it stores `appType` on the session and **stamps it onto every update and commit** it persists. The client never re-sends it per event.
22+
23+
- `appType` is a **document-level** property — all sessions/updates/commits for a given `documentId` share one value.
24+
- **Missing ⇒ `'ddoc'`** (legacy). Every row in Mongo today is ddoc and dsheet is unreleased, so this default is already correct.
25+
- Naming chosen deliberately: field `appType`, values `'ddoc' | 'dsheet'` (singular — matches the repo/package names and the existing `dsheet-` IndexedDB key prefix).
26+
27+
See the **Decision log** (§7) for why Approach A over per-event tagging or documentId-prefixing.
28+
29+
## 3. Server contract (implemented — for reference)
30+
31+
What the dsheet client must satisfy / can rely on:
32+
33+
- **`/auth` accepts an optional `appType` field** in its args.
34+
- **New (owner) session:** server records `appType` (default `'ddoc'`) on the `Session`.
35+
- **Joining an existing session:** the server compares the client's declared `appType` (missing ⇒ `'ddoc'`) against the document's stored `appType` (missing ⇒ `'ddoc'`). On mismatch it rejects with:
36+
```
37+
{ status: false, statusCode: 403, error: "App type mismatch for this document", errorCode: "APP_MISMATCH" }
38+
```
39+
- **Stamping:** the server stamps the connection's resolved `appType` onto every `DocumentUpdate` and `DocumentCommit` it writes — the client does **not** send it on update/commit/awareness.
40+
- **Storage:** `appType` added to the `Session`, `DocumentUpdate`, and `DocumentCommit` collections (enum `['ddoc','dsheet']`, default `'ddoc'`); `DocumentUpdate` and `DocumentCommit` are indexed on `{ appType, createdAt }` for lifecycle/analytics queries.
41+
- **Backward compatible:** any client that omits `appType` (current ddoc production, legacy raw-WebSocket clients) is treated as `'ddoc'`.
42+
43+
**Server files changed** (`collaboration-server`): `src/types/index.ts` (field + `AppType` + `APP_MISMATCH`), `src/database/models/{session,document-update,document-commit}.ts`, `src/services/auth-middleware.ts`, `src/services/session-manager.ts`, `src/services/mongodb-store.ts`, `src/services/socket-handlers.ts`, plus `src/scripts/backfill-apptype.ts` and tests.
44+
45+
## 4. dsheet client changes (to implement)
46+
47+
Repo: `fileverse-dsheet` (branch `maitra/rtc-2` or successor). The change is intentionally tiny — `appType` is intrinsic to the editor, so it's a hardcoded constant, **not** consumer config.
48+
49+
### 4.1 Types — `src/sync-local/types/index.ts`
50+
51+
- Add an `AppType` and use it on the auth args (`IAuthArgs`, ~line 200):
52+
```ts
53+
export type AppType = 'ddoc' | 'dsheet';
54+
55+
export interface IAuthArgs {
56+
collaborationToken: string;
57+
documentId: string;
58+
ownerToken?: string;
59+
ownerAddress?: string;
60+
contractAddress?: string;
61+
sessionDid?: string;
62+
roomInfo?: string;
63+
appType?: AppType; // NEW
64+
}
65+
```
66+
- Mirror the new server error in `ServerErrorCode` (~line 125):
67+
```ts
68+
APP_MISMATCH = 'APP_MISMATCH',
69+
```
70+
71+
### 4.2 Send `appType` at `/auth``src/sync-local/socketClient.ts`
72+
73+
In `_handleHandShake` (~line 321) include the constant in the auth args. Add a module-level constant near the top of the file:
74+
75+
```ts
76+
const APP_TYPE = 'dsheet' as const;
77+
```
78+
79+
```ts
80+
const args: IAuthArgs = {
81+
collaborationToken: token,
82+
sessionDid: this.collaborationKeyPair?.did(),
83+
documentId: this.roomId,
84+
appType: APP_TYPE, // NEW
85+
};
86+
```
87+
88+
**No other change.** The `sendUpdate`, `commitUpdates`, and `broadcastAwareness` paths stay exactly as they are — the server stamps `appType` from the session.
89+
90+
### 4.3 (Optional) Friendlier `APP_MISMATCH` handling
91+
92+
The existing non-200 branch in `_handleHandShake` (~line 346) already surfaces a rejected handshake through `config.onHandShakeError`. Optionally special-case `response.errorCode === 'APP_MISMATCH'` to show a clearer message (e.g. "This link belongs to a different Fileverse app") instead of the generic error.
93+
94+
### 4.4 ddoc client — not in this change
95+
96+
The ddoc client need not change: the server defaults missing `appType` to `'ddoc'`, and the guard treats a non-declaring client as `'ddoc'` too. When convenient, mirror §4.2 in ddoc's `package/sync-local/socketClient.ts` with `APP_TYPE = 'ddoc'` for explicitness (cosmetic).
97+
98+
## 5. Rollout
99+
100+
Nothing is enforced as required, so ordering is low-risk:
101+
102+
1. Deploy the server (optional field + default + guard) — fully backward compatible; deployed ddoc clients keep working. **Operational note:** this adds `{ appType, createdAt }` indexes to the live, ddoc-populated `DocumentUpdate` and `DocumentCommit` collections. They are declared `{ background: true }`, so the build is non-blocking, but it is still an index build over production data — schedule/monitor accordingly.
103+
2. Run the backfill: `tsx src/scripts/backfill-apptype.ts` — sets `appType: 'ddoc'` on existing rows missing it (safe; all current data is ddoc).
104+
3. Ship the dsheet client (§4) sending `appType: 'dsheet'` — its first release.
105+
4. *(Optional, low priority)* ship the ddoc client (§4.4) sending `appType: 'ddoc'`.
106+
107+
> Ongoing legacy raw-WebSocket writes are safe without any client change: that handler persists through the same Mongoose models (`createUpdate` / `createCommit` / `createSession`), so the `default: 'ddoc'` tags them automatically.
108+
109+
## 6. Testing
110+
111+
**dsheet client**
112+
- Unit: `_handleHandShake` includes `appType: 'dsheet'` in the `/auth` args.
113+
- E2E: a dsheet session creates a `Session` plus updates & commits tagged `'dsheet'` (verify in Mongo); ddoc collaboration continues unaffected; feeding a ddoc room into the dsheet client (or vice-versa) is rejected with `APP_MISMATCH`.
114+
115+
**Server** (implemented alongside the change, in `src/tests/services/`)
116+
- New owner session stores the declared `appType` (defaults to `'ddoc'` when absent).
117+
- Join with a matching `appType` succeeds; mismatch (including a non-declaring `ddoc`-defaulted client joining a `dsheet` document) is rejected with `APP_MISMATCH` / 403.
118+
- `createUpdate` / `createCommit` receive the connection's `appType`.
119+
120+
## 7. Decision log
121+
122+
- **Approach A (auth-time tag, server stamps) over C (tag every event):** the isolation guard needs the app known at connect time (before joining the room); a single connection is always one app, so per-event tagging is pure redundancy; and A is actually *less* client code (only the `/auth` payload changes).
123+
- **Naming `appType: 'ddoc' | 'dsheet'`** (singular) — matches `fileverse-ddoc` / `fileverse-dsheet` package names and the `dsheet-` IndexedDB prefix.
124+
- **Missing ⇒ `'ddoc'`** — all current data is ddoc; dsheet is unreleased.
125+
- **Guard: hard-reject on mismatch**, treating missing as `ddoc` on both sides so it's robust immediately. Can be relaxed to log-only if a transition period is desired.
126+
- **Backfill: run it** — cheap and makes every row explicit, so queries are plain equality with no null-handling.
127+
128+
### Known limitation
129+
130+
The guard fires on the **join-existing-session** path. The **new-owner-session** path trusts the declared `appType` (it does not scan existing updates/commits for a conflicting app). Re-creating an existing ddoc document's session as dsheet therefore isn't blocked — but that requires the document's on-chain owner credentials, so it's not a cross-user isolation gap.

demo/.env.sample

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1-
NEXT_PUBLIC_PROXY_BASE_URL=
1+
NEXT_PUBLIC_PROXY_BASE_URL=
2+
3+
# Real-time collaboration (Socket.IO server)
4+
VITE_COLLAB_WS_URL=
5+
VITE_OWNER_ED_SECRET=
6+
VITE_COLLAB_CONTRACT_ADDRESS=
7+
VITE_COLLAB_OWNER_ADDRESS=

0 commit comments

Comments
 (0)