import { useEffect, useRef } from 'react'; import { withinDeadline } from '../utils/withinDeadline'; // How often, and how many times, a patient's open invitation is read again while the page is in view. export const INVITED_REREAD_MS = 20_000; export const INVITED_REREAD_LIMIT = 30; // How long one reread may take, every step of it; past it the slot is free and that flight is over. export const INVITED_REREAD_DEADLINE_MS = 15_000; /** * THE PERSON INVITED ANSWERS IN ANOTHER SESSION, and nothing tells this one. A patient's "Invited" card stayed * that way after the invitation was accepted, until a reload. While an invitation is open the patient's own list * is read again: when they come back to the page, and every twenty seconds while it is in view, one read at a * time, up to a bound (coming back starts the count again). Nothing is assumed: only a read changes the card. * * EACH READ IS ITS OWN FLIGHT, and bounded: one that never answers frees the slot at its deadline, and the flight * is over, so a step it had not reached yet is not taken (`reread` is told through `stillCurrent`). Its late answer * frees nothing that belongs to a later flight. */ export const useRereadWhileInvited = ( waiting: boolean, reread: (stillCurrent: () => boolean, signal: AbortSignal) => Promise, ): void => { const rereadRef = useRef(reread); rereadRef.current = reread; const reading = useRef(false); const flight = useRef(0); const inFlight = useRef(null); const count = useRef(0); useEffect(() => { if (!waiting) return undefined; count.current = 0; const run = (comingBack: boolean) => { if (document.visibilityState === 'hidden' || reading.current) return; if (comingBack) count.current = 0; else if (count.current >= INVITED_REREAD_LIMIT) return; else count.current += 1; const mine = ++flight.current; const controller = new AbortController(); inFlight.current = controller; reading.current = true; void withinDeadline(rereadRef.current(() => flight.current === mine, controller.signal), INVITED_REREAD_DEADLINE_MS, 'invitation reread') .catch(() => undefined) .finally(() => { if (flight.current !== mine) return; // Over: its requests are aborted and it is no longer admitted anywhere, then the slot is free. flight.current += 1; controller.abort(); inFlight.current = null; reading.current = false; }); }; const onSchedule = () => run(false); const onReturn = () => run(true); const timer = window.setInterval(onSchedule, INVITED_REREAD_MS); window.addEventListener('focus', onReturn); document.addEventListener('visibilitychange', onReturn); return () => { window.clearInterval(timer); window.removeEventListener('focus', onReturn); document.removeEventListener('visibilitychange', onReturn); // No longer waiting, or gone: the flight in the air is over, its requests aborted, its slot free. flight.current += 1; inFlight.current?.abort(); inFlight.current = null; reading.current = false; }; }, [waiting]); };