Skip to main content

Collaboration

Kritzel uses Yjs CRDTs under the hood for real-time collaboration and persistence. All canvas objects are stored in a Yjs document (Y.Doc), enabling conflict-free merging across multiple clients.

Configuring Sync Providers

Pass a syncConfig prop to <kritzel-editor> or <kritzel-engine>:

import { IndexedDBSyncProvider, WebSocketSyncProvider } from 'kritzel-stencil';

const editor = document.querySelector('kritzel-editor');
editor.syncConfig = {
providers: [
IndexedDBSyncProvider,
WebSocketSyncProvider.with({ url: 'wss://your-server.com' }),
],
};

Available Providers

IndexedDBSyncProvider

Persists canvas data locally in IndexedDB. Data survives page reloads and browser restarts.

import { IndexedDBSyncProvider } from 'kritzel-stencil';

editor.syncConfig = {
providers: [IndexedDBSyncProvider],
};

BroadcastSyncProvider

Syncs canvas data across browser tabs using the BroadcastChannel API. Useful for same-browser multi-tab editing.

import { BroadcastSyncProvider } from 'kritzel-stencil';

editor.syncConfig = {
providers: [BroadcastSyncProvider],
};

WebSocketSyncProvider

Real-time sync via a WebSocket server (compatible with y-websocket).

import { WebSocketSyncProvider } from 'kritzel-stencil';

editor.syncConfig = {
providers: [
WebSocketSyncProvider.with({ url: 'wss://your-server.com' }),
],
};

HocuspocusSyncProvider

Real-time sync via a Hocuspocus server. Supports authentication tokens and multiplexed WebSocket connections.

import { HocuspocusSyncProvider } from 'kritzel-stencil';

editor.syncConfig = {
providers: [
HocuspocusSyncProvider.with({
url: 'wss://your-hocuspocus-server.com',
token: 'your-auth-token',
}),
],
};

Combining Providers

Providers can be combined. A common pattern is local persistence + remote sync:

editor.syncConfig = {
providers: [
IndexedDBSyncProvider, // local persistence
BroadcastSyncProvider, // cross-tab sync
WebSocketSyncProvider.with({ url: 'wss://your-server.com' }), // remote sync
],
};

Hocuspocus Multiplexing

When working with multiple documents (e.g. multiple workspaces connecting to different rooms), the Hocuspocus provider supports multiplexing — sharing a single WebSocket connection across all documents.

Setup

import { HocuspocusSyncProvider } from 'kritzel-stencil';

// Step 1: Create a shared WebSocket connection (once)
HocuspocusSyncProvider.createSharedWebSocket({
url: 'ws://localhost:1234',
onConnect: () => console.log('WebSocket connected'),
onDisconnect: () => console.log('WebSocket disconnected'),
});

// Step 2: Create providers that share the connection
const provider1 = new HocuspocusSyncProvider('document-1', doc1, {
token: 'token-for-doc1',
});

const provider2 = new HocuspocusSyncProvider('document-2', doc2, {
token: 'token-for-doc2',
});

await Promise.all([provider1.connect(), provider2.connect()]);

Factory Pattern

const factory = HocuspocusSyncProvider.with({
quiet: false,
onConnect: () => console.log('Connected'),
});

const provider1 = factory.create('document-1', doc1);
const provider2 = factory.create('document-2', doc2);

Cleanup

provider1.destroy();
provider2.destroy();
HocuspocusSyncProvider.destroySharedWebSocket();

Benefits of Multiplexing

  • Single WebSocket connection for all documents
  • Reduced overhead — no multiple handshakes
  • Better resource usage — less memory and network
  • Faster document switching — connection already established
  • Shared authentication — authenticate once per connection

Hocuspocus Configuration Options

interface HocuspocusOptions {
url?: string; // Server URL (standalone mode only)
name?: string; // Override document name
token?: string | (() => string) | (() => Promise<string>); // Auth token
websocketProvider?: HocuspocusProviderWebsocket; // For multiplexing
quiet?: boolean; // Suppress console logs
forceSyncInterval?: false | number; // Force sync interval (ms)
WebSocketPolyfill?: any; // For Node.js environments

// Callbacks
onConnect?: () => void;
onDisconnect?: () => void;
onSynced?: () => void;
onAuthenticationFailed?: (data: any) => void;
onStatus?: (data: { status: string }) => void;
}