@prabhask5/stellar-engine 1.1.18 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -1
- package/dist/bin/install-pwa.js +50 -0
- package/dist/bin/install-pwa.js.map +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -2
- package/dist/config.js.map +1 -1
- package/dist/crdt/awareness.d.ts +128 -0
- package/dist/crdt/awareness.d.ts.map +1 -0
- package/dist/crdt/awareness.js +284 -0
- package/dist/crdt/awareness.js.map +1 -0
- package/dist/crdt/channel.d.ts +165 -0
- package/dist/crdt/channel.d.ts.map +1 -0
- package/dist/crdt/channel.js +522 -0
- package/dist/crdt/channel.js.map +1 -0
- package/dist/crdt/config.d.ts +58 -0
- package/dist/crdt/config.d.ts.map +1 -0
- package/dist/crdt/config.js +123 -0
- package/dist/crdt/config.js.map +1 -0
- package/dist/crdt/helpers.d.ts +104 -0
- package/dist/crdt/helpers.d.ts.map +1 -0
- package/dist/crdt/helpers.js +116 -0
- package/dist/crdt/helpers.js.map +1 -0
- package/dist/crdt/offline.d.ts +58 -0
- package/dist/crdt/offline.d.ts.map +1 -0
- package/dist/crdt/offline.js +130 -0
- package/dist/crdt/offline.js.map +1 -0
- package/dist/crdt/persistence.d.ts +65 -0
- package/dist/crdt/persistence.d.ts.map +1 -0
- package/dist/crdt/persistence.js +171 -0
- package/dist/crdt/persistence.js.map +1 -0
- package/dist/crdt/provider.d.ts +109 -0
- package/dist/crdt/provider.d.ts.map +1 -0
- package/dist/crdt/provider.js +543 -0
- package/dist/crdt/provider.js.map +1 -0
- package/dist/crdt/store.d.ts +111 -0
- package/dist/crdt/store.d.ts.map +1 -0
- package/dist/crdt/store.js +158 -0
- package/dist/crdt/store.js.map +1 -0
- package/dist/crdt/types.d.ts +281 -0
- package/dist/crdt/types.d.ts.map +1 -0
- package/dist/crdt/types.js +26 -0
- package/dist/crdt/types.js.map +1 -0
- package/dist/database.d.ts +1 -1
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +28 -7
- package/dist/database.js.map +1 -1
- package/dist/diagnostics.d.ts +75 -0
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +114 -2
- package/dist/diagnostics.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +21 -1
- package/dist/engine.js.map +1 -1
- package/dist/entries/crdt.d.ts +32 -0
- package/dist/entries/crdt.d.ts.map +1 -0
- package/dist/entries/crdt.js +52 -0
- package/dist/entries/crdt.js.map +1 -0
- package/dist/entries/types.d.ts +1 -0
- package/dist/entries/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
- package/dist/operations.d.ts +0 -73
- package/dist/operations.d.ts.map +0 -1
- package/dist/operations.js +0 -227
- package/dist/operations.js.map +0 -1
- package/dist/reconnectHandler.d.ts +0 -16
- package/dist/reconnectHandler.d.ts.map +0 -1
- package/dist/reconnectHandler.js +0 -21
- package/dist/reconnectHandler.js.map +0 -1
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CRDT Broadcast Channel — Supabase Realtime Transport
|
|
3
|
+
*
|
|
4
|
+
* Manages one Supabase Broadcast + Presence channel per open CRDT document.
|
|
5
|
+
* Responsible for:
|
|
6
|
+
* - Distributing Yjs updates to remote peers via Broadcast
|
|
7
|
+
* - Receiving and applying remote updates (with echo suppression)
|
|
8
|
+
* - Debouncing outbound updates and merging via `Y.mergeUpdates()`
|
|
9
|
+
* - Chunking payloads that exceed the Broadcast size limit
|
|
10
|
+
* - Running the sync protocol on join (exchange state vectors, send deltas)
|
|
11
|
+
* - Cross-tab sync via browser `BroadcastChannel` API (avoids Supabase for same-device)
|
|
12
|
+
* - Reconnection with exponential backoff
|
|
13
|
+
*
|
|
14
|
+
* Channel naming convention: `crdt:${prefix}:${documentId}`
|
|
15
|
+
*
|
|
16
|
+
* @see {@link ./provider.ts} for the orchestrator that creates channels
|
|
17
|
+
* @see {@link ./types.ts} for message type definitions
|
|
18
|
+
* @see {@link ./awareness.ts} for Presence management (separate concern)
|
|
19
|
+
*/
|
|
20
|
+
import * as Y from 'yjs';
|
|
21
|
+
import { supabase } from '../supabase/client';
|
|
22
|
+
import { getDeviceId } from '../deviceId';
|
|
23
|
+
import { debugLog, debugWarn } from '../debug';
|
|
24
|
+
import { getCRDTConfig, getCRDTPrefix } from './config';
|
|
25
|
+
import { handlePresenceJoin, handlePresenceLeave, assignColor } from './awareness';
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Binary ↔ Base64 Encoding Utilities
|
|
28
|
+
// =============================================================================
|
|
29
|
+
/**
|
|
30
|
+
* Encode a `Uint8Array` to a base64 string for JSON transport.
|
|
31
|
+
*
|
|
32
|
+
* Uses the browser's built-in `btoa()` via a binary string intermediary.
|
|
33
|
+
* This is necessary because Supabase Broadcast payloads are JSON — binary
|
|
34
|
+
* data must be string-encoded.
|
|
35
|
+
*/
|
|
36
|
+
function uint8ToBase64(bytes) {
|
|
37
|
+
let binary = '';
|
|
38
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
39
|
+
binary += String.fromCharCode(bytes[i]);
|
|
40
|
+
}
|
|
41
|
+
return btoa(binary);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Decode a base64 string back to a `Uint8Array`.
|
|
45
|
+
*/
|
|
46
|
+
function base64ToUint8(base64) {
|
|
47
|
+
const binary = atob(base64);
|
|
48
|
+
const bytes = new Uint8Array(binary.length);
|
|
49
|
+
for (let i = 0; i < binary.length; i++) {
|
|
50
|
+
bytes[i] = binary.charCodeAt(i);
|
|
51
|
+
}
|
|
52
|
+
return bytes;
|
|
53
|
+
}
|
|
54
|
+
// =============================================================================
|
|
55
|
+
// CRDTChannel Class
|
|
56
|
+
// =============================================================================
|
|
57
|
+
/**
|
|
58
|
+
* Manages the Supabase Broadcast channel for a single CRDT document.
|
|
59
|
+
*
|
|
60
|
+
* Handles update distribution, echo suppression, debouncing, chunking,
|
|
61
|
+
* the sync protocol, cross-tab sync, and reconnection.
|
|
62
|
+
*
|
|
63
|
+
* @internal — consumers interact via {@link ./provider.ts}, not this class directly.
|
|
64
|
+
*/
|
|
65
|
+
export class CRDTChannel {
|
|
66
|
+
constructor(documentId, doc, onConnectionStateChange) {
|
|
67
|
+
/** Supabase Realtime channel instance. */
|
|
68
|
+
this.channel = null;
|
|
69
|
+
/** Browser BroadcastChannel for cross-tab sync (same device). */
|
|
70
|
+
this.localChannel = null;
|
|
71
|
+
/** Current connection state. */
|
|
72
|
+
this._connectionState = 'disconnected';
|
|
73
|
+
/** Callback invoked when connection state changes. */
|
|
74
|
+
this.onConnectionStateChange = null;
|
|
75
|
+
/** Initial presence info for Supabase Presence tracking. */
|
|
76
|
+
this.presenceInfo = null;
|
|
77
|
+
// --- Debounce state ---
|
|
78
|
+
this.pendingUpdates = [];
|
|
79
|
+
this.debounceTimer = null;
|
|
80
|
+
// --- Chunk reassembly state ---
|
|
81
|
+
this.chunkBuffers = new Map();
|
|
82
|
+
// --- Reconnection state ---
|
|
83
|
+
this.reconnectAttempts = 0;
|
|
84
|
+
this.reconnectTimer = null;
|
|
85
|
+
this.destroyed = false;
|
|
86
|
+
// --- Sync protocol state ---
|
|
87
|
+
this.syncResolvers = new Map();
|
|
88
|
+
this.documentId = documentId;
|
|
89
|
+
this.doc = doc;
|
|
90
|
+
this.deviceId = getDeviceId();
|
|
91
|
+
this.channelName = `crdt:${getCRDTPrefix()}:${documentId}`;
|
|
92
|
+
this.onConnectionStateChange = onConnectionStateChange ?? null;
|
|
93
|
+
}
|
|
94
|
+
// ===========================================================================
|
|
95
|
+
// Public API
|
|
96
|
+
// ===========================================================================
|
|
97
|
+
/** Current connection state of the channel. */
|
|
98
|
+
get connectionState() {
|
|
99
|
+
return this._connectionState;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Set the local user's presence info for Supabase Presence tracking.
|
|
103
|
+
*
|
|
104
|
+
* Call this before `join()` to announce presence immediately on channel subscribe,
|
|
105
|
+
* or after join to update the tracked presence.
|
|
106
|
+
*/
|
|
107
|
+
setPresenceInfo(info) {
|
|
108
|
+
this.presenceInfo = info;
|
|
109
|
+
/* If already connected, track immediately. */
|
|
110
|
+
if (this.channel && this._connectionState === 'connected') {
|
|
111
|
+
this.trackPresence();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Join the Broadcast channel and start receiving messages.
|
|
116
|
+
*
|
|
117
|
+
* After subscribing, initiates the sync protocol by sending a sync-step-1
|
|
118
|
+
* message with the local state vector so peers can respond with deltas.
|
|
119
|
+
*/
|
|
120
|
+
async join() {
|
|
121
|
+
if (this.destroyed)
|
|
122
|
+
return;
|
|
123
|
+
this.setConnectionState('connecting');
|
|
124
|
+
debugLog(`[CRDT] Channel ${this.channelName} joining`);
|
|
125
|
+
/* Create Supabase Broadcast channel. */
|
|
126
|
+
this.channel = supabase.channel(this.channelName, {
|
|
127
|
+
config: { broadcast: { self: false } }
|
|
128
|
+
});
|
|
129
|
+
/* Listen for Broadcast messages. */
|
|
130
|
+
this.channel.on('broadcast', { event: 'crdt' }, (payload) => {
|
|
131
|
+
this.handleBroadcastMessage(payload.payload);
|
|
132
|
+
});
|
|
133
|
+
/* Listen for Presence events (join/leave). */
|
|
134
|
+
this.channel.on('presence', { event: 'join' }, ({ newPresences }) => {
|
|
135
|
+
for (const presence of newPresences) {
|
|
136
|
+
const state = presence;
|
|
137
|
+
if (state.deviceId !== this.deviceId) {
|
|
138
|
+
handlePresenceJoin(this.documentId, state);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
this.channel.on('presence', { event: 'leave' }, ({ leftPresences }) => {
|
|
143
|
+
for (const presence of leftPresences) {
|
|
144
|
+
const state = presence;
|
|
145
|
+
handlePresenceLeave(this.documentId, state.userId, state.deviceId);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
/* Subscribe to the channel. */
|
|
149
|
+
this.channel.subscribe((status) => {
|
|
150
|
+
if (status === 'SUBSCRIBED') {
|
|
151
|
+
this.setConnectionState('connected');
|
|
152
|
+
this.reconnectAttempts = 0;
|
|
153
|
+
debugLog(`[CRDT] Channel ${this.channelName} subscribed`);
|
|
154
|
+
/* Track presence if info is available. */
|
|
155
|
+
this.trackPresence();
|
|
156
|
+
/* Initiate sync protocol — request missing updates from peers. */
|
|
157
|
+
this.sendSyncStep1();
|
|
158
|
+
}
|
|
159
|
+
else if (status === 'CHANNEL_ERROR') {
|
|
160
|
+
debugWarn(`[CRDT] Channel ${this.channelName} error`);
|
|
161
|
+
this.handleDisconnect();
|
|
162
|
+
}
|
|
163
|
+
else if (status === 'CLOSED') {
|
|
164
|
+
this.setConnectionState('disconnected');
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
/* Set up cross-tab sync via browser BroadcastChannel API. */
|
|
168
|
+
this.setupLocalChannel();
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Leave the channel and clean up all resources.
|
|
172
|
+
*/
|
|
173
|
+
async leave() {
|
|
174
|
+
this.destroyed = true;
|
|
175
|
+
debugLog(`[CRDT] Channel ${this.channelName} leaving`);
|
|
176
|
+
/* Clear debounce timer. */
|
|
177
|
+
if (this.debounceTimer) {
|
|
178
|
+
clearTimeout(this.debounceTimer);
|
|
179
|
+
this.debounceTimer = null;
|
|
180
|
+
}
|
|
181
|
+
/* Clear reconnect timer. */
|
|
182
|
+
if (this.reconnectTimer) {
|
|
183
|
+
clearTimeout(this.reconnectTimer);
|
|
184
|
+
this.reconnectTimer = null;
|
|
185
|
+
}
|
|
186
|
+
/* Flush any pending updates before leaving. */
|
|
187
|
+
if (this.pendingUpdates.length > 0) {
|
|
188
|
+
this.flushUpdates();
|
|
189
|
+
}
|
|
190
|
+
/* Unsubscribe from Supabase channel. */
|
|
191
|
+
if (this.channel) {
|
|
192
|
+
await supabase.removeChannel(this.channel);
|
|
193
|
+
this.channel = null;
|
|
194
|
+
}
|
|
195
|
+
/* Close browser BroadcastChannel. */
|
|
196
|
+
if (this.localChannel) {
|
|
197
|
+
this.localChannel.close();
|
|
198
|
+
this.localChannel = null;
|
|
199
|
+
}
|
|
200
|
+
this.setConnectionState('disconnected');
|
|
201
|
+
this.chunkBuffers.clear();
|
|
202
|
+
this.syncResolvers.clear();
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Queue a Yjs update for broadcasting to remote peers.
|
|
206
|
+
*
|
|
207
|
+
* Updates are debounced for `broadcastDebounceMs` (default 100ms) and merged
|
|
208
|
+
* via `Y.mergeUpdates()` before sending. This reduces network traffic for
|
|
209
|
+
* rapid keystrokes while keeping latency under 100ms.
|
|
210
|
+
*
|
|
211
|
+
* @param update - The Yjs update delta from `doc.on('update')`.
|
|
212
|
+
*/
|
|
213
|
+
broadcastUpdate(update) {
|
|
214
|
+
if (this.destroyed || this._connectionState !== 'connected')
|
|
215
|
+
return;
|
|
216
|
+
this.pendingUpdates.push(update);
|
|
217
|
+
/* Also broadcast to same-device tabs immediately (no debounce). */
|
|
218
|
+
this.localChannel?.postMessage({
|
|
219
|
+
type: 'update',
|
|
220
|
+
data: uint8ToBase64(update),
|
|
221
|
+
deviceId: this.deviceId
|
|
222
|
+
});
|
|
223
|
+
/* Debounce the Supabase Broadcast send. */
|
|
224
|
+
if (!this.debounceTimer) {
|
|
225
|
+
const config = getCRDTConfig();
|
|
226
|
+
this.debounceTimer = setTimeout(() => {
|
|
227
|
+
this.debounceTimer = null;
|
|
228
|
+
this.flushUpdates();
|
|
229
|
+
}, config.broadcastDebounceMs);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Wait for the sync protocol to complete after joining.
|
|
234
|
+
*
|
|
235
|
+
* Resolves when at least one peer responds with sync-step-2, or times out
|
|
236
|
+
* after `syncPeerTimeoutMs` (default 3s) if no peers are available.
|
|
237
|
+
*
|
|
238
|
+
* @returns `true` if a peer responded, `false` if timed out (no peers).
|
|
239
|
+
*/
|
|
240
|
+
waitForSync() {
|
|
241
|
+
const config = getCRDTConfig();
|
|
242
|
+
return new Promise((resolve) => {
|
|
243
|
+
const key = `sync-${Date.now()}`;
|
|
244
|
+
let resolved = false;
|
|
245
|
+
this.syncResolvers.set(key, () => {
|
|
246
|
+
if (!resolved) {
|
|
247
|
+
resolved = true;
|
|
248
|
+
resolve(true);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
setTimeout(() => {
|
|
252
|
+
this.syncResolvers.delete(key);
|
|
253
|
+
if (!resolved) {
|
|
254
|
+
resolved = true;
|
|
255
|
+
debugLog(`[CRDT] Document ${this.documentId}: no peers responded within ${config.syncPeerTimeoutMs}ms, fetching from Supabase`);
|
|
256
|
+
resolve(false);
|
|
257
|
+
}
|
|
258
|
+
}, config.syncPeerTimeoutMs);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
// ===========================================================================
|
|
262
|
+
// Message Handling
|
|
263
|
+
// ===========================================================================
|
|
264
|
+
/**
|
|
265
|
+
* Handle an incoming Broadcast message from a remote peer.
|
|
266
|
+
*
|
|
267
|
+
* Dispatches to type-specific handlers and performs echo suppression
|
|
268
|
+
* (skip messages from our own device).
|
|
269
|
+
*/
|
|
270
|
+
handleBroadcastMessage(message) {
|
|
271
|
+
/* Echo suppression — skip messages from our own device. */
|
|
272
|
+
if (message.deviceId === this.deviceId) {
|
|
273
|
+
debugLog(`[CRDT] Document ${this.documentId}: skipped own-device echo (deviceId=${this.deviceId})`);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
switch (message.type) {
|
|
277
|
+
case 'update':
|
|
278
|
+
this.handleRemoteUpdate(message);
|
|
279
|
+
break;
|
|
280
|
+
case 'sync-step-1':
|
|
281
|
+
this.handleSyncStep1(message);
|
|
282
|
+
break;
|
|
283
|
+
case 'sync-step-2':
|
|
284
|
+
this.handleSyncStep2(message);
|
|
285
|
+
break;
|
|
286
|
+
case 'chunk':
|
|
287
|
+
this.handleChunk(message);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Apply a remote Yjs update to the local document.
|
|
293
|
+
*/
|
|
294
|
+
handleRemoteUpdate(message) {
|
|
295
|
+
const update = base64ToUint8(message.data);
|
|
296
|
+
debugLog(`[CRDT] Document ${this.documentId}: received remote update from device ${message.deviceId} (${update.byteLength} bytes)`);
|
|
297
|
+
Y.applyUpdate(this.doc, update);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Handle sync-step-1: a peer is requesting missing updates.
|
|
301
|
+
*
|
|
302
|
+
* We compute the delta between our state and their state vector,
|
|
303
|
+
* then send it back as sync-step-2.
|
|
304
|
+
*/
|
|
305
|
+
handleSyncStep1(message) {
|
|
306
|
+
const remoteStateVector = base64ToUint8(message.stateVector);
|
|
307
|
+
const update = Y.encodeStateAsUpdate(this.doc, remoteStateVector);
|
|
308
|
+
if (update.byteLength > 0) {
|
|
309
|
+
debugLog(`[CRDT] Document ${this.documentId}: sync-step-2 sent to ${message.deviceId} (${update.byteLength} bytes)`);
|
|
310
|
+
this.sendMessage({
|
|
311
|
+
type: 'sync-step-2',
|
|
312
|
+
update: uint8ToBase64(update),
|
|
313
|
+
deviceId: this.deviceId
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Handle sync-step-2: a peer responded to our sync-step-1 with a delta.
|
|
319
|
+
*/
|
|
320
|
+
handleSyncStep2(message) {
|
|
321
|
+
const update = base64ToUint8(message.update);
|
|
322
|
+
debugLog(`[CRDT] Document ${this.documentId}: sync-step-2 received from ${message.deviceId} (${update.byteLength} bytes)`);
|
|
323
|
+
Y.applyUpdate(this.doc, update);
|
|
324
|
+
/* Resolve any pending sync waiters. */
|
|
325
|
+
for (const resolver of this.syncResolvers.values()) {
|
|
326
|
+
resolver();
|
|
327
|
+
}
|
|
328
|
+
this.syncResolvers.clear();
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Handle a chunk message — part of a large payload that was split.
|
|
332
|
+
*
|
|
333
|
+
* Buffers chunks until all parts arrive, then reassembles and processes
|
|
334
|
+
* the full payload as a regular message.
|
|
335
|
+
*/
|
|
336
|
+
handleChunk(message) {
|
|
337
|
+
const { chunkId, index, total, data } = message;
|
|
338
|
+
let buffer = this.chunkBuffers.get(chunkId);
|
|
339
|
+
if (!buffer) {
|
|
340
|
+
buffer = { total, chunks: new Map() };
|
|
341
|
+
this.chunkBuffers.set(chunkId, buffer);
|
|
342
|
+
}
|
|
343
|
+
buffer.chunks.set(index, data);
|
|
344
|
+
/* Check if all chunks have arrived. */
|
|
345
|
+
if (buffer.chunks.size === buffer.total) {
|
|
346
|
+
/* Reassemble in order. */
|
|
347
|
+
let fullBase64 = '';
|
|
348
|
+
for (let i = 0; i < buffer.total; i++) {
|
|
349
|
+
fullBase64 += buffer.chunks.get(i) ?? '';
|
|
350
|
+
}
|
|
351
|
+
this.chunkBuffers.delete(chunkId);
|
|
352
|
+
/* Process as an update message. */
|
|
353
|
+
const update = base64ToUint8(fullBase64);
|
|
354
|
+
debugLog(`[CRDT] Document ${this.documentId}: reassembled ${buffer.total} chunks (${update.byteLength} bytes)`);
|
|
355
|
+
Y.applyUpdate(this.doc, update);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// ===========================================================================
|
|
359
|
+
// Outbound Message Sending
|
|
360
|
+
// ===========================================================================
|
|
361
|
+
/**
|
|
362
|
+
* Flush all pending updates: merge, encode, and send via Broadcast.
|
|
363
|
+
*
|
|
364
|
+
* If the merged payload exceeds the max size, it is chunked.
|
|
365
|
+
*/
|
|
366
|
+
flushUpdates() {
|
|
367
|
+
if (this.pendingUpdates.length === 0)
|
|
368
|
+
return;
|
|
369
|
+
const updates = this.pendingUpdates;
|
|
370
|
+
this.pendingUpdates = [];
|
|
371
|
+
/* Merge all buffered updates into a single binary payload. */
|
|
372
|
+
const merged = updates.length === 1 ? updates[0] : Y.mergeUpdates(updates);
|
|
373
|
+
const config = getCRDTConfig();
|
|
374
|
+
debugLog(`[CRDT] Document ${this.documentId}: ${updates.length} updates buffered (${merged.byteLength} bytes), broadcasting`);
|
|
375
|
+
const base64Data = uint8ToBase64(merged);
|
|
376
|
+
if (base64Data.length > config.maxBroadcastPayloadBytes) {
|
|
377
|
+
/* Payload too large — chunk it. */
|
|
378
|
+
this.sendChunked(base64Data);
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
this.sendMessage({
|
|
382
|
+
type: 'update',
|
|
383
|
+
data: base64Data,
|
|
384
|
+
deviceId: this.deviceId
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Send sync-step-1 to request missing updates from connected peers.
|
|
390
|
+
*/
|
|
391
|
+
sendSyncStep1() {
|
|
392
|
+
const stateVector = Y.encodeStateVector(this.doc);
|
|
393
|
+
debugLog(`[CRDT] Document ${this.documentId}: sync-step-1 sent (stateVector ${stateVector.byteLength} bytes)`);
|
|
394
|
+
this.sendMessage({
|
|
395
|
+
type: 'sync-step-1',
|
|
396
|
+
stateVector: uint8ToBase64(stateVector),
|
|
397
|
+
deviceId: this.deviceId
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Send a message via the Supabase Broadcast channel.
|
|
402
|
+
*/
|
|
403
|
+
sendMessage(message) {
|
|
404
|
+
if (!this.channel || this._connectionState !== 'connected')
|
|
405
|
+
return;
|
|
406
|
+
this.channel.send({
|
|
407
|
+
type: 'broadcast',
|
|
408
|
+
event: 'crdt',
|
|
409
|
+
payload: message
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Split a large base64 payload into chunks and send each one.
|
|
414
|
+
*/
|
|
415
|
+
sendChunked(base64Data) {
|
|
416
|
+
const config = getCRDTConfig();
|
|
417
|
+
/* Use ~200KB per chunk to stay safely below the limit. */
|
|
418
|
+
const chunkSize = Math.floor(config.maxBroadcastPayloadBytes * 0.8);
|
|
419
|
+
const totalChunks = Math.ceil(base64Data.length / chunkSize);
|
|
420
|
+
const chunkId = `${this.deviceId}-${Date.now()}`;
|
|
421
|
+
debugWarn(`[CRDT] Document ${this.documentId}: chunking broadcast payload (${base64Data.length} bytes > ${config.maxBroadcastPayloadBytes} bytes, ${totalChunks} chunks)`);
|
|
422
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
423
|
+
const start = i * chunkSize;
|
|
424
|
+
const end = Math.min(start + chunkSize, base64Data.length);
|
|
425
|
+
this.sendMessage({
|
|
426
|
+
type: 'chunk',
|
|
427
|
+
chunkId,
|
|
428
|
+
index: i,
|
|
429
|
+
total: totalChunks,
|
|
430
|
+
data: base64Data.slice(start, end),
|
|
431
|
+
deviceId: this.deviceId
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// ===========================================================================
|
|
436
|
+
// Cross-Tab Sync (Browser BroadcastChannel)
|
|
437
|
+
// ===========================================================================
|
|
438
|
+
/**
|
|
439
|
+
* Set up the browser BroadcastChannel for same-device tab sync.
|
|
440
|
+
*
|
|
441
|
+
* This avoids Supabase Broadcast for updates between tabs on the same device,
|
|
442
|
+
* which is faster and doesn't consume any network bandwidth.
|
|
443
|
+
*/
|
|
444
|
+
setupLocalChannel() {
|
|
445
|
+
if (typeof BroadcastChannel === 'undefined')
|
|
446
|
+
return;
|
|
447
|
+
this.localChannel = new BroadcastChannel(this.channelName);
|
|
448
|
+
this.localChannel.onmessage = (event) => {
|
|
449
|
+
const message = event.data;
|
|
450
|
+
/* Skip our own messages (same tab). */
|
|
451
|
+
if (message.deviceId === this.deviceId)
|
|
452
|
+
return;
|
|
453
|
+
/* Apply update from another tab on the same device. */
|
|
454
|
+
if (message.type === 'update') {
|
|
455
|
+
const update = base64ToUint8(message.data);
|
|
456
|
+
Y.applyUpdate(this.doc, update);
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
// ===========================================================================
|
|
461
|
+
// Reconnection
|
|
462
|
+
// ===========================================================================
|
|
463
|
+
/**
|
|
464
|
+
* Handle a channel disconnect — attempt reconnection with exponential backoff.
|
|
465
|
+
*/
|
|
466
|
+
handleDisconnect() {
|
|
467
|
+
if (this.destroyed)
|
|
468
|
+
return;
|
|
469
|
+
this.setConnectionState('disconnected');
|
|
470
|
+
const config = getCRDTConfig();
|
|
471
|
+
if (this.reconnectAttempts >= config.maxReconnectAttempts) {
|
|
472
|
+
debugWarn(`[CRDT] Channel ${this.channelName} max reconnect attempts reached (${config.maxReconnectAttempts})`);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
this.reconnectAttempts++;
|
|
476
|
+
const delay = config.reconnectBaseDelayMs * Math.pow(2, this.reconnectAttempts - 1);
|
|
477
|
+
debugLog(`[CRDT] Channel ${this.channelName} reconnecting (attempt ${this.reconnectAttempts}/${config.maxReconnectAttempts}, delay ${delay}ms)`);
|
|
478
|
+
this.reconnectTimer = setTimeout(async () => {
|
|
479
|
+
this.reconnectTimer = null;
|
|
480
|
+
if (this.destroyed)
|
|
481
|
+
return;
|
|
482
|
+
/* Clean up old channel before rejoining. */
|
|
483
|
+
if (this.channel) {
|
|
484
|
+
await supabase.removeChannel(this.channel);
|
|
485
|
+
this.channel = null;
|
|
486
|
+
}
|
|
487
|
+
await this.join();
|
|
488
|
+
}, delay);
|
|
489
|
+
}
|
|
490
|
+
// ===========================================================================
|
|
491
|
+
// State Management
|
|
492
|
+
// ===========================================================================
|
|
493
|
+
/**
|
|
494
|
+
* Track the local user's presence on the Supabase Presence channel.
|
|
495
|
+
*
|
|
496
|
+
* Sends the user's name, avatar, color, and device ID so other collaborators
|
|
497
|
+
* can display cursor badges and avatar lists.
|
|
498
|
+
*/
|
|
499
|
+
trackPresence() {
|
|
500
|
+
if (!this.channel || !this.presenceInfo || this._connectionState !== 'connected')
|
|
501
|
+
return;
|
|
502
|
+
const presenceState = {
|
|
503
|
+
userId: this.deviceId, // Will be replaced with actual userId when auth is available
|
|
504
|
+
name: this.presenceInfo.name,
|
|
505
|
+
avatarUrl: this.presenceInfo.avatarUrl,
|
|
506
|
+
color: assignColor(this.deviceId),
|
|
507
|
+
deviceId: this.deviceId,
|
|
508
|
+
lastActiveAt: new Date().toISOString()
|
|
509
|
+
};
|
|
510
|
+
this.channel.track(presenceState);
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Update the connection state and notify the listener.
|
|
514
|
+
*/
|
|
515
|
+
setConnectionState(state) {
|
|
516
|
+
if (this._connectionState === state)
|
|
517
|
+
return;
|
|
518
|
+
this._connectionState = state;
|
|
519
|
+
this.onConnectionStateChange?.(state);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
//# sourceMappingURL=channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../../src/crdt/channel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CRDT Configuration Singleton
|
|
3
|
+
*
|
|
4
|
+
* Manages the resolved CRDT configuration singleton. The raw {@link CRDTConfig}
|
|
5
|
+
* (with optional fields) is provided by the consumer via `initEngine({ crdt: ... })`.
|
|
6
|
+
* This module applies sensible defaults and stores the result as a
|
|
7
|
+
* {@link ResolvedCRDTConfig} singleton, accessible to all other CRDT modules
|
|
8
|
+
* via {@link getCRDTConfig}.
|
|
9
|
+
*
|
|
10
|
+
* Initialization flow:
|
|
11
|
+
* 1. Consumer calls `initEngine({ crdt: { ... } })`
|
|
12
|
+
* 2. `config.ts#initEngine` calls `_initCRDT(rawConfig, prefix)`
|
|
13
|
+
* 3. This module merges defaults → stores singleton
|
|
14
|
+
* 4. Other CRDT modules call `getCRDTConfig()` to read the resolved config
|
|
15
|
+
*
|
|
16
|
+
* If `initEngine()` is called without a `crdt` field, the singleton remains
|
|
17
|
+
* `null` and `getCRDTConfig()` throws with a descriptive error message.
|
|
18
|
+
*
|
|
19
|
+
* @see {@link ../config.ts} for the `initEngine()` entry point
|
|
20
|
+
* @see {@link ./types.ts} for the config interfaces
|
|
21
|
+
*/
|
|
22
|
+
import type { CRDTConfig, ResolvedCRDTConfig } from './types';
|
|
23
|
+
/**
|
|
24
|
+
* Initialize the CRDT configuration singleton.
|
|
25
|
+
*
|
|
26
|
+
* Called internally by {@link ../config.ts#initEngine} when `config.crdt` is
|
|
27
|
+
* provided. Merges user-provided values with defaults and stores the result.
|
|
28
|
+
*
|
|
29
|
+
* @param rawConfig - The user-provided CRDT config (with optional fields).
|
|
30
|
+
* @param prefix - The application prefix from `SyncEngineConfig.prefix`.
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export declare function _initCRDT(rawConfig: CRDTConfig, prefix: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Get the resolved CRDT configuration.
|
|
36
|
+
*
|
|
37
|
+
* @throws {Error} If CRDT was not configured in `initEngine()`.
|
|
38
|
+
* @returns The fully resolved {@link ResolvedCRDTConfig} with all defaults applied.
|
|
39
|
+
*/
|
|
40
|
+
export declare function getCRDTConfig(): ResolvedCRDTConfig;
|
|
41
|
+
/**
|
|
42
|
+
* Get the application prefix for use in channel naming and storage keys.
|
|
43
|
+
*
|
|
44
|
+
* @throws {Error} If CRDT was not configured in `initEngine()`.
|
|
45
|
+
* @returns The prefix string (e.g., `'myapp'`).
|
|
46
|
+
*/
|
|
47
|
+
export declare function getCRDTPrefix(): string;
|
|
48
|
+
/**
|
|
49
|
+
* Check whether the CRDT subsystem has been initialized.
|
|
50
|
+
*
|
|
51
|
+
* Unlike {@link getCRDTConfig}, this does not throw — it returns a boolean.
|
|
52
|
+
* Used by conditional code paths that need to check CRDT availability
|
|
53
|
+
* without triggering an error (e.g., sign-out cleanup).
|
|
54
|
+
*
|
|
55
|
+
* @returns `true` if `_initCRDT()` has been called.
|
|
56
|
+
*/
|
|
57
|
+
export declare function isCRDTEnabled(): boolean;
|
|
58
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/crdt/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AA4C9D;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAgBrE;AAMD;;;;;GAKG;AACH,wBAAgB,aAAa,IAAI,kBAAkB,CAKlD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAKtC;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC"}
|