Deploy a collaboration server to enable real-time document synchronization using our Yjs-based collaboration library.

Quick Start

1

Install the library

npm install @harbour-enterprises/superdoc-yjs-collaboration
2

Create server

import SuperDocCollaboration from '@harbour-enterprises/superdoc-yjs-collaboration';

const collab = new SuperDocCollaboration()
  .withDebounce(2000)
  .onAuthenticate(async ({ token }) => {
    const user = await validateToken(token);
    return user;
  })
  .onLoad(async ({ documentId }) => {
    return await loadDocument(documentId);
  })
  .onAutoSave(async ({ document, documentId }) => {
    await saveDocument(documentId, document);
  })
  .build();
3

Add WebSocket endpoint

app.ws('/collaboration/:documentId', (ws, req) => {
  collab.welcome(ws, req);
});

Required Hooks

onAuthenticate

Validate user access to documents.
token
string
Authentication token from client
documentId
string
required
Document being accessed
request
Request
required
Original HTTP request with headers
Returns: User object or throws error
.onAuthenticate(async ({ token, documentId, request }) => {
  // Option 1: JWT validation
  const payload = jwt.verify(token, SECRET);
  
  // Option 2: Session cookie
  const session = parseCookie(request.headers.cookie);
  
  // Verify permissions
  const canAccess = await checkPermissions(userId, documentId);
  if (!canAccess) {
    throw new Error('Access denied');
  }
  
  return {
    userId: payload.sub,
    name: payload.name,
    email: payload.email
  };
})
Always validate permissions - throwing an error prevents access

onLoad

Load document state from storage.
documentId
string
required
Document to load
Returns: Uint8Array | null - Document state or null for new
.onLoad(async ({ documentId }) => {
  const doc = await storage.get(documentId);
  
  if (!doc) {
    // New document
    return null;
  }
  
  // Must return Uint8Array
  return new Uint8Array(doc);
})

onAutoSave

Save document state (debounced).
document
Y.Doc
required
Yjs document instance
documentId
string
required
Document identifier
import * as Y from 'yjs';

.onAutoSave(async ({ document, documentId }) => {
  // Convert to Uint8Array for storage
  const state = Y.encodeStateAsUpdate(document);
  
  await storage.save(documentId, state);
  
  // Optional: Track metadata
  await db.updateMetadata(documentId, {
    lastModified: new Date(),
    size: state.byteLength
  });
})

Optional Hooks

onChange

Fires on every change - use sparingly!
document
Y.Doc
required
Yjs document
documentId
string
required
Document ID
.onChange(({ documentId }) => {
  // Light operations only
  metrics.increment('edits');
  updateLastActive(documentId);
})

onConfigure

Configure Yjs document on creation.
ydoc
Y.Doc
required
New Yjs document
.onConfigure(({ ydoc }) => {
  // Disable garbage collection
  ydoc.gc = false;
  
  // Add custom types
  ydoc.getMap('metadata');
})

Configuration

Builder Methods

withName
function
Set service identifier
.withName('collab-prod-1')
withDebounce
function
Autosave interval (milliseconds)
.withDebounce(2000) // Save every 2 seconds
withDocumentExpiryMs
function
Cache expiry when no users connected
.withDocumentExpiryMs(300000) // 5 minutes

Storage Implementations

import { Pool } from 'pg';
const pool = new Pool();

const storage = {
  async get(documentId) {
    const { rows } = await pool.query(
      'SELECT data FROM documents WHERE id = $1',
      [documentId]
    );
    return rows[0]?.data || null;
  },
  
  async save(documentId, data) {
    await pool.query(
      `INSERT INTO documents (id, data, updated_at) 
       VALUES ($1, $2, NOW()) 
       ON CONFLICT (id) 
       DO UPDATE SET data = $2, updated_at = NOW()`,
      [documentId, Buffer.from(data)]
    );
  }
};

Framework Integration

import express from 'express';
import expressWs from 'express-ws';

const app = express();
expressWs(app);

const collab = new SuperDocCollaboration()
  // ... hooks
  .build();

app.ws('/collaboration/:documentId', (ws, req) => {
  collab.welcome(ws, req);
});

app.listen(3000);

Production Deployment

Environment Variables

# .env
NODE_ENV=production
WS_PORT=3000
JWT_SECRET=your-secret-key
REDIS_URL=redis://localhost:6379
DATABASE_URL=postgres://user:pass@localhost/db

Docker Setup

FROM node:18-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --production

COPY . .

EXPOSE 3000
CMD ["node", "server.js"]

Scaling Considerations

For production scale:
  • Use Redis for document state (fast access)
  • Implement sticky sessions for WebSocket
  • Add health checks for load balancer
  • Monitor memory usage per document
  • Set connection limits per user

Security Checklist

1

Always use WSS in production

url: 'wss://collab.example.com' // Not ws://
2

Implement rate limiting

const rateLimit = new Map();

.onAuthenticate(async ({ token, request }) => {
  const ip = request.ip;
  if (rateLimit.get(ip) > 100) {
    throw new Error('Rate limit exceeded');
  }
  // ... rest of auth
})
3

Validate document IDs

.onLoad(async ({ documentId }) => {
  // Prevent path traversal
  if (!/^[a-zA-Z0-9-]+$/.test(documentId)) {
    throw new Error('Invalid document ID');
  }
  // ... load document
})
4

Set connection limits

const connections = new Map();

.onAuthenticate(async ({ token }) => {
  const user = validateToken(token);
  const userConnections = connections.get(user.id) || 0;
  
  if (userConnections >= 5) {
    throw new Error('Connection limit reached');
  }
  
  connections.set(user.id, userConnections + 1);
  return user;
})

Monitoring

Health Check Endpoint

app.get('/health', (req, res) => {
  res.json({
    status: 'healthy',
    documents: collab.getActiveDocumentCount(),
    connections: collab.getConnectionCount(),
    uptime: process.uptime()
  });
});

Metrics

// Track key metrics
.onChange(() => {
  metrics.increment('document.edits');
})

.onAutoSave(() => {
  metrics.increment('document.saves');
})

.onAuthenticate(() => {
  metrics.increment('auth.attempts');
})

Examples

API Reference

Full builder API documentation:
CollaborationBuilder
class
Fluent builder for configuration