(opts: LiveStoreOptions<S>)
| 75 | } |
| 76 | |
| 77 | export function createLiveStore<S>(opts: LiveStoreOptions<S>): LiveStore<S> { |
| 78 | const inner = createStore<S>(() => opts.initialState) |
| 79 | const stale = opts.staleAfterMs ?? 60_000 |
| 80 | |
| 81 | let refCount = 0 |
| 82 | let cleanup: (() => void) | null = null |
| 83 | let lastEventTs = Date.now() |
| 84 | let visibilityHandler: (() => void) | null = null |
| 85 | |
| 86 | const apply: Apply<S> = (next) => { |
| 87 | if (typeof next === 'function') { |
| 88 | inner.setState((prev) => (next as (p: S) => S)(prev), true) |
| 89 | } else { |
| 90 | inner.setState(next, true) |
| 91 | } |
| 92 | lastEventTs = Date.now() |
| 93 | } |
| 94 | |
| 95 | function start() { |
| 96 | if (cleanup) return |
| 97 | lastEventTs = Date.now() |
| 98 | cleanup = opts.subscribe({ apply }) |
| 99 | |
| 100 | visibilityHandler = () => { |
| 101 | if (document.visibilityState !== 'visible') return |
| 102 | if (Date.now() - lastEventTs > stale) { |
| 103 | // Connection might have died silently while backgrounded — take |
| 104 | // the optimistic stance and reconnect. Worst case we drop a few |
| 105 | // duplicate events; best case we recover from a zombie socket. |
| 106 | reconnect() |
| 107 | } |
| 108 | } |
| 109 | document.addEventListener('visibilitychange', visibilityHandler) |
| 110 | } |
| 111 | |
| 112 | function stop() { |
| 113 | cleanup?.() |
| 114 | cleanup = null |
| 115 | if (visibilityHandler) { |
| 116 | document.removeEventListener('visibilitychange', visibilityHandler) |
| 117 | visibilityHandler = null |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | function reconnect() { |
| 122 | if (!cleanup) return |
| 123 | stop() |
| 124 | start() |
| 125 | } |
| 126 | |
| 127 | function bumpRef(delta: 1 | -1) { |
| 128 | refCount += delta |
| 129 | if (refCount === 1 && delta === 1) start() |
| 130 | else if (refCount === 0 && delta === -1) stop() |
| 131 | } |
| 132 | |
| 133 | function useStore<T>(selector: (s: S) => T): T { |
| 134 | useEffect(() => { |
no outgoing calls
no test coverage detected