Files
MindSift/frontend/src/lib/docSync/push.ts
T
sandrodachandClaude Opus 5.5 8b7a53daab
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m16s
feat(notes): push, long polling and fallback detection for note sync
Phase 4 of the collaboration plan:
- The sync engine listens for DOC_SEQ in the note's realtime room and syncs
  right away. Without WebSocket/SSE it long-polls /docs/{id}/changes (25 s);
  after three failures it falls back to polling every 3 s and retries long
  polling when the browser comes back online.
- With push or long polling the engine only syncs every 7 s while holding
  leases (renewal), otherwise every 30 s as a safety net. Focusing a block
  announces its lease immediately (otherwise it could take up to 30 s).
- Realtime connection: SSE counts as connected only after its first event;
  a stream that stays silent for 5 s (buffering proxy) switches SSE off for
  the tab. App-level PING/PONG detects WebSockets that are accepted but
  forward nothing and switches to SSE after 35 s.
- The editor shows the active channel for shared notes.
- Tests: engine channel tests (push, heartbeat, lease on focus, long
  polling, interval fallback, switching), realtime fallback tests with fake
  WebSocket/EventSource, Go tests for PONG and DOC_SEQ delivery, and
  e2e/channels.e2e.mjs measuring latency behind six simulated proxy setups
  (0.2-0.3 s on all live channels, ~3 s with plain polling).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 14:10:29 +01:00

26 lines
1.0 KiB
TypeScript

// Push notifications for note sync: the server sends DOC_SEQ into the note's room whenever the note, its
// leases or its presence change. Delivery uses the app-wide realtime connection (WebSocket, else SSE).
import { createRealtimeConnection, getRealtimeTransport, isRealtimeConnected } from '../api';
export type PushKind = 'ws' | 'sse' | 'none';
export interface PushChannel {
/** Calls onPoke for changes of docId and onStatus whenever the channel goes up or down. */
subscribe(docId: string, onPoke: () => void, onStatus: (kind: PushKind) => void): () => void;
}
export const realtimePush: PushChannel = {
subscribe(docId, onPoke, onStatus) {
const current = (): PushKind => (isRealtimeConnected() ? (getRealtimeTransport() as PushKind) : 'none');
const conn = createRealtimeConnection({
docId,
onMessage: (msg: any) => {
if (msg?.event === 'DOC_SEQ' && msg.payload?.docId === docId) onPoke();
},
onStatusChange: () => onStatus(current()),
});
onStatus(current());
return () => conn.close();
},
};