@pantheon-systems/css-client 0.4.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/dist/auth.d.ts +54 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +72 -0
- package/dist/auth.js.map +1 -0
- package/dist/broker.d.ts +41 -0
- package/dist/broker.d.ts.map +1 -0
- package/dist/broker.js +187 -0
- package/dist/broker.js.map +1 -0
- package/dist/client.d.ts +138 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +130 -0
- package/dist/client.js.map +1 -0
- package/dist/content.d.ts +45 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +65 -0
- package/dist/content.js.map +1 -0
- package/dist/endpoints/agent-edit.d.ts +79 -0
- package/dist/endpoints/agent-edit.d.ts.map +1 -0
- package/dist/endpoints/agent-edit.js +125 -0
- package/dist/endpoints/agent-edit.js.map +1 -0
- package/dist/endpoints/agent-registry.d.ts +59 -0
- package/dist/endpoints/agent-registry.d.ts.map +1 -0
- package/dist/endpoints/agent-registry.js +96 -0
- package/dist/endpoints/agent-registry.js.map +1 -0
- package/dist/endpoints/base.d.ts +49 -0
- package/dist/endpoints/base.d.ts.map +1 -0
- package/dist/endpoints/base.js +150 -0
- package/dist/endpoints/base.js.map +1 -0
- package/dist/endpoints/branches.d.ts +32 -0
- package/dist/endpoints/branches.d.ts.map +1 -0
- package/dist/endpoints/branches.js +69 -0
- package/dist/endpoints/branches.js.map +1 -0
- package/dist/endpoints/checkpoints.d.ts +36 -0
- package/dist/endpoints/checkpoints.d.ts.map +1 -0
- package/dist/endpoints/checkpoints.js +76 -0
- package/dist/endpoints/checkpoints.js.map +1 -0
- package/dist/endpoints/documents.d.ts +46 -0
- package/dist/endpoints/documents.d.ts.map +1 -0
- package/dist/endpoints/documents.js +101 -0
- package/dist/endpoints/documents.js.map +1 -0
- package/dist/endpoints/index.d.ts +15 -0
- package/dist/endpoints/index.d.ts.map +1 -0
- package/dist/endpoints/index.js +16 -0
- package/dist/endpoints/index.js.map +1 -0
- package/dist/endpoints/merge.d.ts +51 -0
- package/dist/endpoints/merge.d.ts.map +1 -0
- package/dist/endpoints/merge.js +104 -0
- package/dist/endpoints/merge.js.map +1 -0
- package/dist/endpoints/presence.d.ts +55 -0
- package/dist/endpoints/presence.d.ts.map +1 -0
- package/dist/endpoints/presence.js +72 -0
- package/dist/endpoints/presence.js.map +1 -0
- package/dist/endpoints/sites.d.ts +20 -0
- package/dist/endpoints/sites.d.ts.map +1 -0
- package/dist/endpoints/sites.js +38 -0
- package/dist/endpoints/sites.js.map +1 -0
- package/dist/endpoints/versions.d.ts +37 -0
- package/dist/endpoints/versions.d.ts.map +1 -0
- package/dist/endpoints/versions.js +64 -0
- package/dist/endpoints/versions.js.map +1 -0
- package/dist/errors.d.ts +61 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +87 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/jwt-utils.d.ts +13 -0
- package/dist/jwt-utils.d.ts.map +1 -0
- package/dist/jwt-utils.js +45 -0
- package/dist/jwt-utils.js.map +1 -0
- package/dist/oauth.d.ts +64 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +62 -0
- package/dist/oauth.js.map +1 -0
- package/dist/realtime.d.ts +314 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +615 -0
- package/dist/realtime.js.map +1 -0
- package/dist/types.d.ts +703 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +17 -0
- package/dist/utils.js.map +1 -0
- package/package.json +56 -0
package/dist/realtime.js
ADDED
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 2.1: RealtimeClient
|
|
3
|
+
*
|
|
4
|
+
* WebSocket-based real-time collaboration client using Yjs CRDT.
|
|
5
|
+
* Provides bidirectional sync between client and DocumentSession Durable Object.
|
|
6
|
+
* Uses ReconnectingWebSocket from partysocket for automatic reconnection with exponential backoff.
|
|
7
|
+
*/
|
|
8
|
+
import * as Y from 'yjs';
|
|
9
|
+
import { WebSocket as ReconnectingWebSocket } from 'partysocket';
|
|
10
|
+
/**
|
|
11
|
+
* Real-time collaboration client using Yjs CRDT over WebSocket.
|
|
12
|
+
* Uses PartySocket for automatic reconnection with exponential backoff.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const client = new RealtimeClient({
|
|
17
|
+
* baseUrl: 'wss://api.example.com',
|
|
18
|
+
* onUpdate: (snapshot) => {
|
|
19
|
+
* console.log('Document updated:', snapshot);
|
|
20
|
+
* },
|
|
21
|
+
* onReconnecting: (attempt) => {
|
|
22
|
+
* console.log(`Reconnecting... attempt ${attempt}`);
|
|
23
|
+
* },
|
|
24
|
+
* reconnection: {
|
|
25
|
+
* maxRetries: 10,
|
|
26
|
+
* minReconnectionDelay: 1000,
|
|
27
|
+
* maxReconnectionDelay: 30000,
|
|
28
|
+
* },
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* client.connect({
|
|
32
|
+
* siteId: 'site-123',
|
|
33
|
+
* branchId: 'branch-456',
|
|
34
|
+
* documentPath: 'pages/home',
|
|
35
|
+
* actorId: 'user-789',
|
|
36
|
+
* actorType: 'user',
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* // When done
|
|
40
|
+
* client.disconnect();
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export class RealtimeClient {
|
|
44
|
+
config;
|
|
45
|
+
ydoc;
|
|
46
|
+
ws = null;
|
|
47
|
+
connected = false;
|
|
48
|
+
intentionalDisconnect = false;
|
|
49
|
+
currentApiKey;
|
|
50
|
+
tokenRefreshInFlight = false;
|
|
51
|
+
hasConnectedOnce = false;
|
|
52
|
+
lastReportedRetryCount = 0;
|
|
53
|
+
reconnectCheckInterval = null;
|
|
54
|
+
visibilityHandler = null;
|
|
55
|
+
// Rate limiting state
|
|
56
|
+
sendTimestamps = [];
|
|
57
|
+
pendingUpdates = [];
|
|
58
|
+
flushTimer = null;
|
|
59
|
+
static RATE_THRESHOLD = 40; // Start buffering at this rate
|
|
60
|
+
static RATE_WINDOW_MS = 1000; // 1-second sliding window
|
|
61
|
+
// Echo suppression: track the last JSON snapshot sent to the DO.
|
|
62
|
+
// If a Y.Doc update produces the same JSON snapshot as we last sent,
|
|
63
|
+
// it's a no-op echo (typically from Puck's onChange after receiving
|
|
64
|
+
// a remote sync) and should NOT be sent to avoid overwriting newer data.
|
|
65
|
+
lastSentSnapshot = null;
|
|
66
|
+
// Delivery acknowledgment state
|
|
67
|
+
pendingDeliveryAcks = new Map();
|
|
68
|
+
static DELIVERY_ACK_TIMEOUT_MS = 5000;
|
|
69
|
+
// Publish request state
|
|
70
|
+
pendingPublishRequests = new Map();
|
|
71
|
+
static PUBLISH_TIMEOUT_MS = 30000;
|
|
72
|
+
// Action metadata state — best-effort metadata sent alongside CRDT updates
|
|
73
|
+
pendingActionMetadata = null;
|
|
74
|
+
constructor(config) {
|
|
75
|
+
this.config = config;
|
|
76
|
+
this.ydoc = new Y.Doc();
|
|
77
|
+
// Listen for local changes to broadcast
|
|
78
|
+
this.ydoc.on('update', (update, origin) => {
|
|
79
|
+
// Only broadcast if this update didn't come from remote
|
|
80
|
+
if (origin !== 'remote' && this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
81
|
+
const root = this.ydoc.getMap('root');
|
|
82
|
+
const currentSnapshot = JSON.stringify(root.toJSON());
|
|
83
|
+
// Echo suppression: if the Y.Doc's JSON snapshot hasn't changed since
|
|
84
|
+
// the last time we sent (or since the last remote update), this is a
|
|
85
|
+
// no-op full-rebuild echo. Don't send it — it would overwrite newer
|
|
86
|
+
// data on the DO if another client has edited since.
|
|
87
|
+
if (currentSnapshot === this.lastSentSnapshot) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.lastSentSnapshot = currentSnapshot;
|
|
91
|
+
this.rateLimitedSend(update);
|
|
92
|
+
// Send any pending action metadata after the CRDT update
|
|
93
|
+
this.sendPendingActionMetadata();
|
|
94
|
+
}
|
|
95
|
+
else if (origin === 'remote') {
|
|
96
|
+
// Track the remote snapshot so we can detect echoes
|
|
97
|
+
const root = this.ydoc.getMap('root');
|
|
98
|
+
this.lastSentSnapshot = JSON.stringify(root.toJSON());
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Connect to a document session via WebSocket.
|
|
104
|
+
* Uses PartySocket for automatic reconnection on connection loss.
|
|
105
|
+
*
|
|
106
|
+
* @param params - Connection parameters including site, branch, document, and actor info
|
|
107
|
+
*/
|
|
108
|
+
connect(params) {
|
|
109
|
+
if (this.ws) {
|
|
110
|
+
this.disconnect();
|
|
111
|
+
}
|
|
112
|
+
this.currentApiKey = this.config.apiKey;
|
|
113
|
+
this.intentionalDisconnect = false;
|
|
114
|
+
this.hasConnectedOnce = false;
|
|
115
|
+
this.lastReportedRetryCount = 0;
|
|
116
|
+
// Build WebSocket URL
|
|
117
|
+
const baseUrl = this.config.baseUrl.replace(/^http/, 'ws');
|
|
118
|
+
const encodedPath = encodeURIComponent(params.documentPath);
|
|
119
|
+
const url = new URL(`/api/sites/${params.siteId}/branches/${params.branchId}/documents/${encodedPath}/connect`, baseUrl);
|
|
120
|
+
// Add actor info as query params (WebSocket can't send headers)
|
|
121
|
+
url.searchParams.set('actorId', params.actorId);
|
|
122
|
+
url.searchParams.set('actorType', params.actorType);
|
|
123
|
+
// Note: apiKey is NOT added to the base URL here — it is injected inside
|
|
124
|
+
// urlProvider so that a refreshed token is picked up on each reconnect.
|
|
125
|
+
// Add session ID for agent authorization (obtained from startEdit())
|
|
126
|
+
if (params.sessionId) {
|
|
127
|
+
url.searchParams.set('sessionId', params.sessionId);
|
|
128
|
+
}
|
|
129
|
+
// Get reconnection config with defaults
|
|
130
|
+
const reconnection = this.config.reconnection ?? {};
|
|
131
|
+
const maxRetries = reconnection.maxRetries ?? Infinity;
|
|
132
|
+
const minReconnectionDelay = reconnection.minReconnectionDelay ?? 1000;
|
|
133
|
+
const maxReconnectionDelay = reconnection.maxReconnectionDelay ?? 30000;
|
|
134
|
+
const reconnectionDelayGrowFactor = reconnection.reconnectionDelayGrowFactor ?? 1.5;
|
|
135
|
+
// Build the base WebSocket URL (without state vector)
|
|
136
|
+
const baseWsUrl = url.toString();
|
|
137
|
+
// Create a URL provider function for PartySocket.
|
|
138
|
+
// On the initial connect the state vector is omitted so the server sends
|
|
139
|
+
// the full CRDT history. On every subsequent (re)connect the current Yjs
|
|
140
|
+
// state vector is included so the server can respond with only the delta.
|
|
141
|
+
// Also injects the current API key on every call so that a refreshed token
|
|
142
|
+
// (set by tokenRefresher after an unexpected close) is used for each
|
|
143
|
+
// reconnect attempt.
|
|
144
|
+
const urlProvider = () => {
|
|
145
|
+
const connectUrl = new URL(baseWsUrl);
|
|
146
|
+
// Inject current token — may have been refreshed after an unexpected disconnect
|
|
147
|
+
if (this.currentApiKey) {
|
|
148
|
+
connectUrl.searchParams.set('apiKey', this.currentApiKey);
|
|
149
|
+
}
|
|
150
|
+
// Include the state vector on every call after the first successful
|
|
151
|
+
// connection so the server can send a delta rather than full history.
|
|
152
|
+
if (this.hasConnectedOnce) {
|
|
153
|
+
const sv = Y.encodeStateVector(this.ydoc);
|
|
154
|
+
const svBase64 = btoa(String.fromCharCode(...sv));
|
|
155
|
+
connectUrl.searchParams.set('stateVector', svBase64);
|
|
156
|
+
}
|
|
157
|
+
return connectUrl.toString();
|
|
158
|
+
};
|
|
159
|
+
// Create ReconnectingWebSocket with URL provider and reconnection options
|
|
160
|
+
this.ws = new ReconnectingWebSocket(urlProvider, [], {
|
|
161
|
+
maxRetries,
|
|
162
|
+
minReconnectionDelay,
|
|
163
|
+
maxReconnectionDelay,
|
|
164
|
+
reconnectionDelayGrowFactor,
|
|
165
|
+
});
|
|
166
|
+
this.ws.binaryType = 'arraybuffer';
|
|
167
|
+
// Start monitoring for reconnection attempts
|
|
168
|
+
this.startReconnectMonitoring();
|
|
169
|
+
// Start visibility change monitoring to handle tab backgrounding
|
|
170
|
+
this.startVisibilityMonitoring();
|
|
171
|
+
this.ws.addEventListener('open', () => {
|
|
172
|
+
this.connected = true;
|
|
173
|
+
// Send local state when doc has content (seeded or from prior session).
|
|
174
|
+
// On initial connect with seeded data, this ensures bidirectional sync.
|
|
175
|
+
// On reconnect, this sends any edits made while disconnected.
|
|
176
|
+
if (this.ws) {
|
|
177
|
+
const root = this.ydoc.getMap('root');
|
|
178
|
+
if (root.size > 0) {
|
|
179
|
+
const localState = Y.encodeStateAsUpdate(this.ydoc);
|
|
180
|
+
this.ws.send(localState);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
this.hasConnectedOnce = true;
|
|
184
|
+
this.lastReportedRetryCount = 0;
|
|
185
|
+
this.config.onConnect?.();
|
|
186
|
+
});
|
|
187
|
+
this.ws.addEventListener('message', (event) => {
|
|
188
|
+
try {
|
|
189
|
+
// Distinguish text (presence JSON) from binary (Yjs CRDT) messages
|
|
190
|
+
if (typeof event.data === 'string') {
|
|
191
|
+
// Text frame: JSON presence message
|
|
192
|
+
this.handleTextMessage(event.data);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
// Binary frame: Yjs CRDT update
|
|
196
|
+
const data = event.data;
|
|
197
|
+
const update = new Uint8Array(data);
|
|
198
|
+
// Apply remote update to local Y.Doc
|
|
199
|
+
Y.applyUpdate(this.ydoc, update, 'remote');
|
|
200
|
+
// Notify listeners of new snapshot
|
|
201
|
+
const root = this.ydoc.getMap('root');
|
|
202
|
+
const snapshot = root.toJSON();
|
|
203
|
+
this.config.onUpdate?.(snapshot);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
console.error('[Realtime] Error processing message:', error);
|
|
207
|
+
this.config.onError?.(error instanceof Error ? error : new Error('Failed to process message'));
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
this.ws.addEventListener('close', (event) => {
|
|
211
|
+
this.connected = false;
|
|
212
|
+
// Check for authorization failure close codes (4401 Unauthorized, 4403 Forbidden)
|
|
213
|
+
const closeEvent = event;
|
|
214
|
+
if (closeEvent.code === 4401 || closeEvent.code === 4403) {
|
|
215
|
+
// Authorization failure - do not attempt to reconnect
|
|
216
|
+
this.intentionalDisconnect = true;
|
|
217
|
+
this.stopReconnectMonitoring();
|
|
218
|
+
this.ws?.close();
|
|
219
|
+
this.ws = null;
|
|
220
|
+
// Call authorization error callback
|
|
221
|
+
const reason = closeEvent.reason || 'Authorization failed';
|
|
222
|
+
this.config.onAuthorizationError?.(new Error(reason));
|
|
223
|
+
this.config.onDisconnect?.();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// Only call onDisconnect for intentional disconnects or when max retries exceeded
|
|
227
|
+
if (this.intentionalDisconnect) {
|
|
228
|
+
this.stopReconnectMonitoring();
|
|
229
|
+
this.ws = null;
|
|
230
|
+
this.config.onDisconnect?.();
|
|
231
|
+
}
|
|
232
|
+
// PartySocket will automatically attempt to reconnect otherwise
|
|
233
|
+
// Fire-and-forget token refresh on unexpected close so the next
|
|
234
|
+
// urlProvider call uses a fresh token for the reconnect URL.
|
|
235
|
+
// Guard prevents concurrent refreshes when the socket flaps rapidly.
|
|
236
|
+
if (!this.intentionalDisconnect && this.config.tokenRefresher && !this.tokenRefreshInFlight) {
|
|
237
|
+
this.tokenRefreshInFlight = true;
|
|
238
|
+
this.config.tokenRefresher().then((freshToken) => {
|
|
239
|
+
if (freshToken) {
|
|
240
|
+
this.currentApiKey = freshToken;
|
|
241
|
+
}
|
|
242
|
+
}).catch(() => {
|
|
243
|
+
// Ignore errors — the reconnect will proceed with the stale token
|
|
244
|
+
}).finally(() => {
|
|
245
|
+
this.tokenRefreshInFlight = false;
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
this.ws.addEventListener('error', () => {
|
|
250
|
+
this.config.onError?.(new Error('WebSocket error'));
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Start monitoring for reconnection attempts by polling retryCount.
|
|
255
|
+
* This is needed because PartySocket doesn't expose a reconnection callback.
|
|
256
|
+
*/
|
|
257
|
+
startReconnectMonitoring() {
|
|
258
|
+
this.stopReconnectMonitoring();
|
|
259
|
+
// Poll every 100ms to detect retry count changes
|
|
260
|
+
this.reconnectCheckInterval = setInterval(() => {
|
|
261
|
+
if (!this.ws || this.intentionalDisconnect) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const currentRetryCount = this.ws.retryCount;
|
|
265
|
+
// If retry count increased and we've connected before, we're reconnecting
|
|
266
|
+
if (currentRetryCount > this.lastReportedRetryCount &&
|
|
267
|
+
this.hasConnectedOnce) {
|
|
268
|
+
this.lastReportedRetryCount = currentRetryCount;
|
|
269
|
+
this.config.onReconnecting?.(currentRetryCount);
|
|
270
|
+
}
|
|
271
|
+
}, 100);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Stop the reconnection monitoring interval.
|
|
275
|
+
*/
|
|
276
|
+
stopReconnectMonitoring() {
|
|
277
|
+
if (this.reconnectCheckInterval) {
|
|
278
|
+
clearInterval(this.reconnectCheckInterval);
|
|
279
|
+
this.reconnectCheckInterval = null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Start monitoring for page visibility changes.
|
|
284
|
+
* When the page becomes visible after being hidden, check connection health
|
|
285
|
+
* and force a reconnection sync if needed.
|
|
286
|
+
*/
|
|
287
|
+
startVisibilityMonitoring() {
|
|
288
|
+
this.stopVisibilityMonitoring();
|
|
289
|
+
// Only set up if document is available (browser environment)
|
|
290
|
+
if (typeof document === 'undefined') {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
this.visibilityHandler = () => {
|
|
294
|
+
if (document.visibilityState === 'visible' && !this.intentionalDisconnect && this.ws) {
|
|
295
|
+
// Force a reconnection by calling reconnect() on PartySocket.
|
|
296
|
+
// This ensures we get a fresh connection even if the old one appears open
|
|
297
|
+
// but is actually stale (server closed it while tab was backgrounded).
|
|
298
|
+
// PartySocket's reconnect() will close the current connection and open a new one.
|
|
299
|
+
// The 'open' event handler will then send local state for bidirectional sync.
|
|
300
|
+
this.ws.reconnect();
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
document.addEventListener('visibilitychange', this.visibilityHandler);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Stop visibility change monitoring.
|
|
307
|
+
*/
|
|
308
|
+
stopVisibilityMonitoring() {
|
|
309
|
+
if (this.visibilityHandler && typeof document !== 'undefined') {
|
|
310
|
+
document.removeEventListener('visibilitychange', this.visibilityHandler);
|
|
311
|
+
this.visibilityHandler = null;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Disconnect from the current session.
|
|
316
|
+
* This permanently closes the connection and stops any reconnection attempts.
|
|
317
|
+
*/
|
|
318
|
+
disconnect() {
|
|
319
|
+
this.intentionalDisconnect = true;
|
|
320
|
+
this.stopReconnectMonitoring();
|
|
321
|
+
this.stopVisibilityMonitoring();
|
|
322
|
+
// Clean up rate limiting state
|
|
323
|
+
if (this.flushTimer !== null) {
|
|
324
|
+
clearTimeout(this.flushTimer);
|
|
325
|
+
this.flushTimer = null;
|
|
326
|
+
}
|
|
327
|
+
this.pendingUpdates = [];
|
|
328
|
+
this.sendTimestamps = [];
|
|
329
|
+
// Reject all pending delivery ack promises
|
|
330
|
+
for (const [requestId, pending] of this.pendingDeliveryAcks) {
|
|
331
|
+
clearTimeout(pending.timer);
|
|
332
|
+
pending.reject(new Error('Disconnected'));
|
|
333
|
+
this.pendingDeliveryAcks.delete(requestId);
|
|
334
|
+
}
|
|
335
|
+
// Reject all pending publish request promises
|
|
336
|
+
for (const [requestId, pending] of this.pendingPublishRequests) {
|
|
337
|
+
clearTimeout(pending.timer);
|
|
338
|
+
pending.reject(new Error('Disconnected'));
|
|
339
|
+
this.pendingPublishRequests.delete(requestId);
|
|
340
|
+
}
|
|
341
|
+
if (this.ws) {
|
|
342
|
+
this.ws.close();
|
|
343
|
+
this.ws = null;
|
|
344
|
+
this.connected = false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Apply a local Yjs update.
|
|
349
|
+
* This is typically called from Puck-Yjs binding when local edits occur.
|
|
350
|
+
* Uses rate-aware sending to avoid exceeding the server's rate limit.
|
|
351
|
+
*
|
|
352
|
+
* @param update - Raw Yjs update bytes
|
|
353
|
+
*/
|
|
354
|
+
applyLocalUpdate(update) {
|
|
355
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
356
|
+
this.rateLimitedSend(update);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Set pending action metadata to be sent after the next CRDT update.
|
|
361
|
+
* The metadata is best-effort — if the WebSocket is not open or the
|
|
362
|
+
* send fails, the CRDT update still goes through without metadata.
|
|
363
|
+
*
|
|
364
|
+
* @param meta - Action type and metadata from Puck's onAction callback
|
|
365
|
+
*/
|
|
366
|
+
setActionMetadata(meta) {
|
|
367
|
+
this.pendingActionMetadata = meta;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Send any pending action metadata as a text message and clear it.
|
|
371
|
+
* Called after a CRDT update is sent to associate the metadata with
|
|
372
|
+
* the most recent edit.
|
|
373
|
+
*/
|
|
374
|
+
sendPendingActionMetadata() {
|
|
375
|
+
if (this.pendingActionMetadata && this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
376
|
+
try {
|
|
377
|
+
this.ws.send(JSON.stringify({
|
|
378
|
+
type: 'action_metadata',
|
|
379
|
+
...this.pendingActionMetadata,
|
|
380
|
+
}));
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
// Best-effort — don't break the CRDT sync if metadata send fails
|
|
384
|
+
}
|
|
385
|
+
this.pendingActionMetadata = null;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Send a Yjs update with rate awareness.
|
|
390
|
+
* Sends immediately when under the threshold (40 msgs/sec).
|
|
391
|
+
* Buffers and coalesces updates when approaching the server's 50 msg/sec limit.
|
|
392
|
+
*/
|
|
393
|
+
rateLimitedSend(update) {
|
|
394
|
+
if (!this.ws)
|
|
395
|
+
return;
|
|
396
|
+
const now = Date.now();
|
|
397
|
+
// Prune timestamps outside the sliding window
|
|
398
|
+
this.sendTimestamps = this.sendTimestamps.filter((ts) => now - ts < RealtimeClient.RATE_WINDOW_MS);
|
|
399
|
+
if (this.sendTimestamps.length < RealtimeClient.RATE_THRESHOLD) {
|
|
400
|
+
// Under threshold — send immediately
|
|
401
|
+
this.sendTimestamps.push(now);
|
|
402
|
+
this.ws.send(update);
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
// At or above threshold — buffer for coalesced flush
|
|
406
|
+
this.pendingUpdates.push(update);
|
|
407
|
+
this.scheduleFlush();
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Schedule a flush of buffered updates after the rate window resets.
|
|
412
|
+
*/
|
|
413
|
+
scheduleFlush() {
|
|
414
|
+
if (this.flushTimer !== null)
|
|
415
|
+
return;
|
|
416
|
+
this.flushTimer = setTimeout(() => {
|
|
417
|
+
this.flushTimer = null;
|
|
418
|
+
this.flushPendingUpdates();
|
|
419
|
+
}, RealtimeClient.RATE_WINDOW_MS);
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Flush all buffered updates as a single coalesced message.
|
|
423
|
+
*/
|
|
424
|
+
flushPendingUpdates() {
|
|
425
|
+
if (this.pendingUpdates.length === 0)
|
|
426
|
+
return;
|
|
427
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
428
|
+
this.pendingUpdates = [];
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
// Merge all pending updates into a single Yjs update
|
|
432
|
+
const merged = Y.mergeUpdates(this.pendingUpdates);
|
|
433
|
+
this.pendingUpdates = [];
|
|
434
|
+
// Reset window and send
|
|
435
|
+
this.sendTimestamps = [Date.now()];
|
|
436
|
+
this.ws.send(merged);
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Get the current document snapshot as JSON.
|
|
440
|
+
*/
|
|
441
|
+
getSnapshot() {
|
|
442
|
+
const root = this.ydoc.getMap('root');
|
|
443
|
+
return root.toJSON();
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Get the underlying Y.Doc for direct manipulation.
|
|
447
|
+
* Use with caution - prefer using the Puck-Yjs binding utilities.
|
|
448
|
+
*/
|
|
449
|
+
getYDoc() {
|
|
450
|
+
return this.ydoc;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Check if currently connected.
|
|
454
|
+
*/
|
|
455
|
+
isConnected() {
|
|
456
|
+
return this.connected;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Check if presence updates are available via WebSocket.
|
|
460
|
+
* Returns true when connected and able to send/receive presence messages.
|
|
461
|
+
*/
|
|
462
|
+
get presenceViaWebSocket() {
|
|
463
|
+
return this.connected && this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Send focus regions update to the server.
|
|
467
|
+
* @param focusRegions - JSON paths the actor is focused on
|
|
468
|
+
* @returns true if message was sent, false if not connected
|
|
469
|
+
*/
|
|
470
|
+
sendFocusRegions(focusRegions) {
|
|
471
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
const message = {
|
|
475
|
+
type: 'focus_region_update',
|
|
476
|
+
focusRegions,
|
|
477
|
+
timestamp: Date.now(),
|
|
478
|
+
};
|
|
479
|
+
this.ws.send(JSON.stringify(message));
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Send a presence heartbeat to keep the connection alive.
|
|
484
|
+
* @param state - Optional state update (active, idle, editing)
|
|
485
|
+
*/
|
|
486
|
+
sendHeartbeat(state) {
|
|
487
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const message = {
|
|
491
|
+
type: 'presence_heartbeat',
|
|
492
|
+
timestamp: Date.now(),
|
|
493
|
+
...(state && { state }),
|
|
494
|
+
};
|
|
495
|
+
this.ws.send(JSON.stringify(message));
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Wait for the server to acknowledge that all preceding WebSocket messages
|
|
499
|
+
* have been processed. This uses TCP ordering guarantees: a text frame sent
|
|
500
|
+
* after binary CRDT updates is guaranteed to arrive after those updates.
|
|
501
|
+
* The server echoes back a delivery_ack with the matching requestId.
|
|
502
|
+
*
|
|
503
|
+
* Used before publish to ensure the Durable Object has received and applied
|
|
504
|
+
* the latest edits before the HTTP publish request arrives.
|
|
505
|
+
*
|
|
506
|
+
* @returns Promise that resolves when the server confirms delivery
|
|
507
|
+
* @throws Error if not connected or if timeout expires (5 seconds)
|
|
508
|
+
*/
|
|
509
|
+
waitForDelivery() {
|
|
510
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
511
|
+
return Promise.reject(new Error('Not connected'));
|
|
512
|
+
}
|
|
513
|
+
const ws = this.ws;
|
|
514
|
+
const requestId = crypto.randomUUID();
|
|
515
|
+
return new Promise((resolve, reject) => {
|
|
516
|
+
const timer = setTimeout(() => {
|
|
517
|
+
this.pendingDeliveryAcks.delete(requestId);
|
|
518
|
+
reject(new Error('Delivery acknowledgment timed out'));
|
|
519
|
+
}, RealtimeClient.DELIVERY_ACK_TIMEOUT_MS);
|
|
520
|
+
this.pendingDeliveryAcks.set(requestId, { resolve, reject, timer });
|
|
521
|
+
ws.send(JSON.stringify({
|
|
522
|
+
type: 'delivery_ack_request',
|
|
523
|
+
requestId,
|
|
524
|
+
timestamp: Date.now(),
|
|
525
|
+
}));
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Request the server to publish the current document via WebSocket.
|
|
530
|
+
* TCP ordering guarantees all preceding binary CRDT updates have been
|
|
531
|
+
* processed before this message is handled, eliminating stale-version races.
|
|
532
|
+
*
|
|
533
|
+
* The Durable Object handles the entire flow: flush to Postgres, then
|
|
534
|
+
* call /internal/publish to create the checkpoint.
|
|
535
|
+
*
|
|
536
|
+
* @returns Promise that resolves with the publish result
|
|
537
|
+
* @throws Error if not connected or if timeout expires (30 seconds)
|
|
538
|
+
*/
|
|
539
|
+
requestPublish() {
|
|
540
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
541
|
+
return Promise.reject(new Error('Not connected'));
|
|
542
|
+
}
|
|
543
|
+
const ws = this.ws;
|
|
544
|
+
const requestId = crypto.randomUUID();
|
|
545
|
+
return new Promise((resolve, reject) => {
|
|
546
|
+
const timer = setTimeout(() => {
|
|
547
|
+
this.pendingPublishRequests.delete(requestId);
|
|
548
|
+
reject(new Error('Publish request timed out'));
|
|
549
|
+
}, RealtimeClient.PUBLISH_TIMEOUT_MS);
|
|
550
|
+
this.pendingPublishRequests.set(requestId, { resolve, reject, timer });
|
|
551
|
+
ws.send(JSON.stringify({
|
|
552
|
+
type: 'publish_request',
|
|
553
|
+
requestId,
|
|
554
|
+
timestamp: Date.now(),
|
|
555
|
+
}));
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Handle incoming text (JSON) messages for presence protocol.
|
|
560
|
+
* @param data - Raw JSON string from WebSocket
|
|
561
|
+
*/
|
|
562
|
+
handleTextMessage(data) {
|
|
563
|
+
try {
|
|
564
|
+
const message = JSON.parse(data);
|
|
565
|
+
switch (message.type) {
|
|
566
|
+
case 'presence_update':
|
|
567
|
+
this.config.onPresenceUpdate?.(message.actors);
|
|
568
|
+
break;
|
|
569
|
+
case 'focus_region_broadcast':
|
|
570
|
+
this.config.onFocusRegionBroadcast?.(message.actorId, message.focusRegions);
|
|
571
|
+
break;
|
|
572
|
+
case 'focus_region_ack':
|
|
573
|
+
// Acknowledgment received
|
|
574
|
+
break;
|
|
575
|
+
case 'delivery_ack': {
|
|
576
|
+
const pending = this.pendingDeliveryAcks.get(message.requestId);
|
|
577
|
+
if (pending) {
|
|
578
|
+
clearTimeout(pending.timer);
|
|
579
|
+
this.pendingDeliveryAcks.delete(message.requestId);
|
|
580
|
+
pending.resolve();
|
|
581
|
+
}
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
case 'publish_result': {
|
|
585
|
+
const pendingPublish = this.pendingPublishRequests.get(message.requestId);
|
|
586
|
+
if (pendingPublish) {
|
|
587
|
+
clearTimeout(pendingPublish.timer);
|
|
588
|
+
this.pendingPublishRequests.delete(message.requestId);
|
|
589
|
+
pendingPublish.resolve({
|
|
590
|
+
success: message.success,
|
|
591
|
+
publishedVersionId: message.publishedVersionId,
|
|
592
|
+
checkpoint: message.checkpoint,
|
|
593
|
+
error: message.error,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
598
|
+
case 'presence_error':
|
|
599
|
+
if (message.code === 'RATE_LIMITED') {
|
|
600
|
+
this.config.onRateLimited?.();
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
console.error('[Realtime] Presence error:', message.code, message.message);
|
|
604
|
+
}
|
|
605
|
+
break;
|
|
606
|
+
default:
|
|
607
|
+
console.warn('[Realtime] Unknown message type:', message.type);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
console.error('[Realtime] Error parsing text message:', error);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
//# sourceMappingURL=realtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime.js","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"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"}
|