import { useCallback, useEffect, useRef, useState } from 'react'; import { useAuthServiceContext } from '../services/AuthService'; import { useBAAService } from '../services/BAAService'; import { useOrganizationOwnerService } from '../services/OrganizationOwnerService'; import { referencesPractitioner } from '../components/OrganizationOwnerNotice/ownerReference'; import { msaEntry, type MsaEntry } from '../utils/msaAgreement'; import { authSessionId } from '../services/authStorage'; import { SaveRetiredError, useSaveScope } from '../utils/saveOperation'; /** Fired by an MSA acceptance, so the owner's entry reads its standing again. */ export const MSA_ACCEPTED_EVENT = 'akashic:msa-accepted'; /** Admit each read for the original practice and viewer, including the waits inside authenticated transport. */ export const useMsaReadScope = (organizationId: string | undefined) => { const auth = useAuthServiceContext(); const live = useRef(auth); live.current = auth; const actor = auth.currentUser?.cognito?.sub; const practitioner = auth.currentUser?.fhir?.resourceType === 'Practitioner' ? auth.currentUser.fhir.id : undefined; const session = authSessionId(); const viewer = auth.actingAsSessionRef.current; const key = [organizationId, actor, practitioner, session, viewer].join('|'); const scope = useSaveScope(key); const start = useCallback(() => { const operation = scope.start(); const belongs = () => operation.belongs() && live.current.wpOrganization.current?.id === organizationId && live.current.currentUser?.cognito?.sub === actor && authSessionId() === session && (live.current.currentUser?.fhir?.resourceType === 'Practitioner' ? live.current.currentUser.fhir.id : undefined) === practitioner && live.current.actingAsSessionRef.current === viewer; const admit = () => { if (!belongs()) { operation.retire(); throw new SaveRetiredError(); } operation.admit(); }; return { operation, belongs, admit, transport: { admit, signal: operation.transport.signal, organizationId } }; }, [scope, key, organizationId, actor, practitioner, session, viewer]); return { key, start }; }; /** * What the current practice's MSA standing means for the signed-in person's practice entry. Only the owner's standing * is read: everyone else is open without asking, so staff never meet the owner's update. */ export const useMsaOwnerEntry = (enabled: boolean): { entry: MsaEntry; retry: () => void } => { const { currentUser, currentOrganization } = useAuthServiceContext(); const { fetchOwner } = useOrganizationOwnerService(); const { getBaa } = useBAAService(); const organizationId = enabled ? currentOrganization?.id : undefined; const practitionerId = currentUser?.fhir?.resourceType === 'Practitioner' ? currentUser.fhir.id : undefined; const { key: viewerKey, start } = useMsaReadScope(organizationId); const key = organizationId && practitionerId ? viewerKey + '|' + practitionerId : undefined; const [reads, setReads] = useState(0); const [read, setRead] = useState<{ key: string; entry: MsaEntry } | undefined>(undefined); const retry = useCallback(() => setReads((n) => n + 1), []); useEffect(() => { window.addEventListener(MSA_ACCEPTED_EVENT, retry); return () => window.removeEventListener(MSA_ACCEPTED_EVENT, retry); }, [retry]); useEffect(() => { if (!key || !organizationId) return undefined; const { operation, admit, belongs, transport } = start(); (async () => { const owner = await operation.step(() => fetchOwner(organizationId, transport)); admit(); const isOwner = owner ? owner.status === 'accepted' && referencesPractitioner(owner.ownerPractitionerId, practitionerId) : undefined; if (isOwner !== true) return { kind: 'open' } as MsaEntry; const record = await operation.step(() => getBaa(transport)); admit(); return record?.msa ? msaEntry(record.msa, true) : ({ kind: 'retry' } as MsaEntry); })().then((entry) => { if (belongs()) setRead({ key, entry }); }) .catch(() => { if (belongs()) setRead({ key, entry: { kind: 'retry' } }); }) .finally(() => operation.retire()); return () => operation.retire(); }, [key, organizationId, practitionerId, fetchOwner, getBaa, reads, start]); // A verdict read for another practice or person never applies here; until this one answers, entry stays open. return { entry: read && read.key === key ? read.entry : { kind: 'open' }, retry }; };