@open-secure-viewer/collab-server
v0.1.0
Published
Reference Node.js collaboration server for Open Secure Viewer — Yjs CRDT relay over WebSocket with pluggable S3/MinIO/LocalFS storage.
Maintainers
Readme
@open-secure-viewer/collab-server
Reference Node.js collaboration server for Open Secure Viewer.
- Yjs CRDT sync over WebSocket — implements
y-protocolssync + awareness directly on top ofws - Pluggable storage via
IStorageAdapter— built-in adapters for S3, MinIO, and local filesystem - Per-room JWT auth hook — reject unauthorized connections with
4401 - Debounced persistence — full Yjs state + XFDF snapshot saved every 500 ms
- Express HTTP API for health, document listing, presigned URLs and XFDF download
- Graceful shutdown — flushes pending saves, closes all sockets cleanly
Install
npm install @open-secure-viewer/collab-serverQuick start (LocalFs, zero-config)
import { createCollabServer, LocalFsStorageAdapter } from '@open-secure-viewer/collab-server';
const server = createCollabServer({
port: 4000,
storage: new LocalFsStorageAdapter('./data'),
});
await server.listen();Production (S3 + JWT)
import { createCollabServer, S3StorageAdapter } from '@open-secure-viewer/collab-server';
import jwt from 'jsonwebtoken';
const server = createCollabServer({
port: 4000,
storage: new S3StorageAdapter({
bucket: 'osv-collab',
region: 'us-east-1',
serverSideEncryption: 'aws:kms',
kmsKeyId: 'arn:aws:kms:us-east-1:111122223333:key/...',
}),
authenticate: async (token, _docId) => {
if (!token) return null;
try {
const claims = jwt.verify(token, process.env.JWT_PUBLIC_KEY!) as { sub: string };
return { userId: claims.sub, claims };
} catch {
return null;
}
},
});
await server.listen();Custom storage
import type { IStorageAdapter } from '@open-secure-viewer/collab-server';
class PostgresStorageAdapter implements IStorageAdapter {
async loadState(documentId: string) { /* SELECT yjs_state FROM ... */ }
async saveState(documentId, yjs, xfdf) { /* INSERT ... ON CONFLICT */ }
}Horizontal scaling (Redis pub/sub)
Multiple collab-server instances can share rooms by fanning out Yjs / awareness updates over Redis:
import {
createCollabServer, RedisStorageAdapter, createRedisRoomBroadcaster,
} from '@open-secure-viewer/collab-server';
const storage = new RedisStorageAdapter({ url: process.env.REDIS_URL });
await storage.connect();
const server = createCollabServer({
port: 4000,
storage,
broadcaster: createRedisRoomBroadcaster({ url: process.env.REDIS_URL }),
});
await server.listen();Rooms management REST API
| Method | Path | Purpose |
|--------|----------------------------|----------------------------------------|
| GET | /health | Health probe + room/client counts |
| GET | /rooms | Every active room + connected users |
| GET | /rooms/:id | One room's connected clients |
| POST | /rooms/:id/close | Kick all clients (admin) |
| POST | /rooms/:id/invite | Mint a signed invite token |
| GET | /documents/:id/url | Presigned source-doc URL (if supported)|
| GET | /documents/:id/xfdf | Latest XFDF snapshot |
| GET | /documents | List known document ids |
Docker / docker-compose
A reference Dockerfile and docker-compose.yml colocated with this
package make a one-command self-host possible:
docker compose -f packages/sdk-collab-server/docker-compose.yml upThe image is configured by OSV_* env vars (OSV_STORAGE,
OSV_DATA_DIR, OSV_BROADCASTER, REDIS_URL, …). See
docker/bootstrap.mjs for the full env-var contract.
License
Apache-2.0
