import { useEffect, useLayoutEffect, useRef } from 'react'; import { Goal } from 'fhir/r4'; import { useGoalServiceContext, usePatientServiceContext, useWebSocketServiceContext } from '../services'; interface UseGoalWebSocketOptions { goal?: Goal; goalId?: string; isOpen?: boolean; onUpdate: (goal: Goal) => void; onDelete: () => void; /** The goal changed but could not be read again: what the detail shows is kept and may be out of date. */ onUnavailable?: () => void; } export const useFhirGoalTargetedBroadcast = ({ goal, goalId, isOpen = true, onUpdate, onDelete, onUnavailable }: UseGoalWebSocketOptions) => { const { subscribe, unsubscribe } = useWebSocketServiceContext(); const { admitGoalEvent, readGoalForEvent } = useGoalServiceContext(); const { patientState } = usePatientServiceContext(); const targetId = goalId || goal?.id; const hasGoal = Boolean(goal); // One opening of the detail: another goal, closing or another patient ends it in the render that installs the next // opening's callbacks, and unmounting ends it at commit. A listener answers only the opening it was installed for; // the list still takes a read's answer through the service. const identity = `${targetId ?? ''}|${isOpen}|${patientState?.id ?? ''}`; const opening = useRef({ identity, generation: 0 }); if (opening.current.identity !== identity) opening.current = { identity, generation: opening.current.generation + 1 }; const current = useRef({ generation: opening.current.generation, onUpdate, onDelete, onUnavailable }); current.current = { generation: opening.current.generation, onUpdate, onDelete, onUnavailable }; const mounted = useRef(false); useLayoutEffect(() => { mounted.current = true; return () => { mounted.current = false; }; }, []); useEffect(() => { if (!patientState || !hasGoal || !targetId || !isOpen) { return; } const installedFor = current.current.generation; const stillOpen = () => mounted.current && current.current.generation === installedFor; const handleGoalEvent = (cloudEvent: any) => { const goalPayload: Goal = cloudEvent.data; const eventType = cloudEvent.type; if (goalPayload.id !== targetId || !stillOpen()) return; if (eventType === 'com.akashichealthrecords.fhir.goal.deleted') { admitGoalEvent(goalPayload, 'deleted'); current.current.onDelete(); return; } // The detail shows the edition the gated read serves now; a goal that read does not find is closed. void readGoalForEvent(targetId, cloudEvent).then((read) => { if (!stillOpen()) return; const now = current.current; if ('goal' in read) now.onUpdate(read.goal); else if ('gone' in read) now.onDelete(); else if ('unavailable' in read) now.onUnavailable?.(); }); }; subscribe('com.akashichealthrecords.fhir.goal.updated', handleGoalEvent); subscribe('com.akashichealthrecords.fhir.goal.deleted', handleGoalEvent); return () => { unsubscribe('com.akashichealthrecords.fhir.goal.updated', handleGoalEvent); unsubscribe('com.akashichealthrecords.fhir.goal.deleted', handleGoalEvent); }; }, [patientState, hasGoal, targetId, isOpen, subscribe, unsubscribe, admitGoalEvent, readGoalForEvent]); };