diff --git a/apps/Backend/src/queue/jobRunner.ts b/apps/Backend/src/queue/jobRunner.ts index 18399fc0..dc5ba5cf 100644 --- a/apps/Backend/src/queue/jobRunner.ts +++ b/apps/Backend/src/queue/jobRunner.ts @@ -196,6 +196,13 @@ export function enqueueSeleniumJob(data: SeleniumJobData): string { return jobId; } +// ── Job status query (HTTP polling fallback) ───────────────────────────────── +export function getSeleniumJobStatus(jobId: string) { + const job = seleniumQ.getJob(jobId); + if (!job) return null; + return { status: job.status, result: job.result ?? null, error: job.error ?? null }; +} + // ── OCR enqueue ────────────────────────────────────────────────────────────── export function enqueueOcrJob(data: OcrJobData): string { const { socketId } = data; diff --git a/apps/Backend/src/routes/insuranceStatus.ts b/apps/Backend/src/routes/insuranceStatus.ts index f6b4b182..e3f4646f 100755 --- a/apps/Backend/src/routes/insuranceStatus.ts +++ b/apps/Backend/src/routes/insuranceStatus.ts @@ -15,10 +15,18 @@ import { } from "../../../../packages/db/types/patient-types"; import { formatDobForAgent } from "../utils/dateUtils"; import { seleniumQueue } from "../queue/queues"; -import { enqueueSeleniumJob } from "../queue/jobRunner"; +import { enqueueSeleniumJob, getSeleniumJobStatus } from "../queue/jobRunner"; const router = Router(); +/** GET /job-status/:jobId — HTTP polling fallback when socket.io drops through Cloudflare */ +router.get("/job-status/:jobId", (req: Request, res: Response): any => { + const jobId = String(req.params.jobId ?? ""); + const status = getSeleniumJobStatus(jobId); + if (!status) return res.status(404).json({ error: "Job not found" }); + return res.json(status); +}); + /** Utility: naive name splitter */ function splitName(fullName?: string | null) { if (!fullName) return { firstName: "", lastName: "" }; diff --git a/apps/Frontend/src/pages/insurance-status-page.tsx b/apps/Frontend/src/pages/insurance-status-page.tsx index 01c67029..d1250881 100755 --- a/apps/Frontend/src/pages/insurance-status-page.tsx +++ b/apps/Frontend/src/pages/insurance-status-page.tsx @@ -42,6 +42,56 @@ import { TuftsSCOEligibilityButton } from "@/components/insurance-status/tufts-s import { UnitedSCOEligibilityButton } from "@/components/insurance-status/united-sco-button-modal"; import { CCAEligibilityButton } from "@/components/insurance-status/cca-button-modal"; +/** + * Waits for a Selenium job to complete by racing socket.io events against + * HTTP polling every 3 seconds. This ensures the result is received even + * when the socket reconnects through Cloudflare (changing socket.id). + */ +function waitForSeleniumJob( + jobId: string, + onActive?: (message: string) => void, +): Promise { + return new Promise((resolve, reject) => { + let settled = false; + + const settle = (fn: () => void) => { + if (settled) return; + settled = true; + clearInterval(pollInterval); + socket.off("job:update", socketHandler); + fn(); + }; + + const socketHandler = (payload: any) => { + if (String(payload.jobId) !== String(jobId)) return; + if (payload.status === "active") { + onActive?.(payload.message ?? "Selenium running..."); + } else if (payload.status === "completed") { + settle(() => resolve(payload.result ?? {})); + } else if (payload.status === "failed") { + settle(() => reject(new Error(payload.error ?? "Selenium job failed"))); + } + }; + socket.on("job:update", socketHandler); + + const pollInterval = setInterval(async () => { + if (settled) return; + try { + const res = await apiRequest("GET", `/api/insurance-status/job-status/${jobId}`); + if (!res.ok) return; + const data = await res.json(); + if (data.status === "completed") { + settle(() => resolve(data.result ?? {})); + } else if (data.status === "failed") { + settle(() => reject(new Error(data.error ?? "Selenium job failed"))); + } + } catch { + // ignore transient poll errors + } + }, 3000); + }); +} + export default function InsuranceStatusPage() { const { user } = useAuth(); const { toast } = useToast(); @@ -254,27 +304,9 @@ export default function InsuranceStatusPage() { }), ); - return new Promise((resolve, reject) => { - const handler = (payload: any) => { - if (String(payload.jobId) !== String(jobId)) return; - if (payload.status === "active") { - dispatch( - setTaskStatus({ - key: "eligibilityCheck", - status: "pending", - message: payload.message ?? "Selenium running...", - }), - ); - } else if (payload.status === "completed") { - socket.off("job:update", handler); - resolve(payload.result ?? {}); - } else if (payload.status === "failed") { - socket.off("job:update", handler); - reject(new Error(payload.error ?? "Selenium job failed")); - } - }; - socket.on("job:update", handler); - }); + return waitForSeleniumJob(jobId, (msg) => + dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: msg })) + ); }; const handleAddPatient = async () => { @@ -468,21 +500,9 @@ export default function InsuranceStatusPage() { dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: "Selenium browser starting..." })); - const jobResult = await new Promise((resolve, reject) => { - const handler = (payload: any) => { - if (String(payload.jobId) !== String(jobId)) return; - if (payload.status === "active") { - dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: payload.message ?? "Selenium running..." })); - } else if (payload.status === "completed") { - socket.off("job:update", handler); - resolve(payload.result ?? {}); - } else if (payload.status === "failed") { - socket.off("job:update", handler); - reject(new Error(payload.error ?? "Selenium job failed")); - } - }; - socket.on("job:update", handler); - }); + const jobResult = await waitForSeleniumJob(jobId, (msg) => + dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: msg })) + ); dispatch(setTaskStatus({ key: "eligibilityCheck", status: "success", message: "Eligibility and service history PDFs saved to Documents." })); toast({ @@ -549,21 +569,9 @@ export default function InsuranceStatusPage() { dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: "Selenium browser starting..." })); - const jobResult = await new Promise((resolve, reject) => { - const handler = (payload: any) => { - if (String(payload.jobId) !== String(jobId)) return; - if (payload.status === "active") { - dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: payload.message ?? "Selenium running..." })); - } else if (payload.status === "completed") { - socket.off("job:update", handler); - resolve(payload.result ?? {}); - } else if (payload.status === "failed") { - socket.off("job:update", handler); - reject(new Error(payload.error ?? "Selenium job failed")); - } - }; - socket.on("job:update", handler); - }); + const jobResult = await waitForSeleniumJob(jobId, (msg) => + dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: msg })) + ); dispatch(setTaskStatus({ key: "eligibilityCheck", status: "success", message: "CMSP PDFs saved to Documents." })); toast({