File size: 3,222 Bytes
cd99321
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/**
 * Sync triggers β€” register event listeners that flush the mutation queue
 * and/or run a full trip sync based on the connectivity trigger source.
 *
 * Trigger matrix:
 *   window 'online'          β†’ flush mutations + full syncAll (network truly back)
 *   visibilitychange visible β†’ flush mutations only (avoid hammering server on tab switch)
 *   periodic 30s             β†’ flush mutations only
 *   WS reconnect             β†’ flush mutations only (no syncAll β€” avoids rate-limiter
 *                              on server restart / socket timeout while already online)
 *
 * Call `registerSyncTriggers()` once on app mount.
 * Call `unregisterSyncTriggers()` on unmount / logout.
 */
import { mutationQueue } from './mutationQueue'
import { tripSyncManager } from './tripSyncManager'
import { setPreReconnectHook, setRefetchCallback, getActiveTrips } from '../api/websocket'
import { useTripStore } from '../store/tripStore'

const PERIODIC_MS = 30_000

let _intervalId: ReturnType<typeof setInterval> | null = null
let _registered = false

/** Pull the latest server state for every open trip into the Zustand store. */
function rehydrateActiveTrips() {
  const store = useTripStore.getState()
  for (const tripId of getActiveTrips()) {
    store.hydrateActiveTrip(tripId).catch(console.error)
  }
}

/**
 * Network came back β€” flush local writes first, then re-seed Dexie for all
 * cacheable trips and re-hydrate the open trip's store so a collaborator's
 * edits made while we were offline appear without navigating away.
 */
function onOnline() {
  mutationQueue.flush()
    .catch(console.error)
    .finally(() => {
      tripSyncManager.syncAll().catch(console.error)
      rehydrateActiveTrips()
    })
}

/** Tab became visible β€” flush only; don't trigger a potentially expensive syncAll. */
function onVisibility() {
  if (!document.hidden && navigator.onLine) {
    mutationQueue.flush().catch(console.error)
  }
}

/** Periodic heartbeat β€” drain any lingering pending mutations. */
function onPeriodic() {
  if (navigator.onLine) {
    mutationQueue.flush().catch(console.error)
  }
}

export function registerSyncTriggers(): void {
  if (_registered) return
  _registered = true

  // WS reconnect: flush mutations only β€” no syncAll to avoid triggering rate
  // limiters when the socket drops and reconnects while the device is online.
  setPreReconnectHook(() => mutationQueue.flush())
  // After the reconnect flush, pull canonical state for the open trip back into
  // the store (the WS layer awaits the flush hook before invoking this).
  setRefetchCallback(tripId => {
    useTripStore.getState().hydrateActiveTrip(tripId).catch(console.error)
  })

  window.addEventListener('online', onOnline)
  document.addEventListener('visibilitychange', onVisibility)
  _intervalId = setInterval(onPeriodic, PERIODIC_MS)
}

export function unregisterSyncTriggers(): void {
  if (!_registered) return
  _registered = false

  setPreReconnectHook(null)
  setRefetchCallback(null)
  window.removeEventListener('online', onOnline)
  document.removeEventListener('visibilitychange', onVisibility)
  if (_intervalId !== null) {
    clearInterval(_intervalId)
    _intervalId = null
  }
}