File size: 1,994 Bytes
ec17af3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import { useEffect, useRef, useState } from "react";
import { apiFetch } from "@/lib/api";

export type JobStatus = "pending" | "queued" | "in_progress" | "complete" | "failed" | "not_found" | "unknown";

type JobState = {
  status: JobStatus;
  result: Record<string, unknown> | null;
  error: string | null;
};

type UseJobPollingOptions = {
  jobId: string | null;
  intervalMs?: number;
  onComplete?: (result: Record<string, unknown>) => void;
  onError?: (error: string) => void;
};

export function useJobPolling({
  jobId,
  intervalMs = 2000,
  onComplete,
  onError,
}: UseJobPollingOptions): JobState {
  const [state, setState] = useState<JobState>({ status: "pending", result: null, error: null });
  const onCompleteRef = useRef(onComplete);
  const onErrorRef = useRef(onError);

  useEffect(() => {
    onCompleteRef.current = onComplete;
    onErrorRef.current = onError;
  }, [onComplete, onError]);

  useEffect(() => {
    if (!jobId) return;
    let active = true;
    let interval: number | null = null;

    const poll = async () => {
      try {
        const data = await apiFetch<JobState>(`/jobs/${jobId}`);
        if (!active) return;
        setState(data);
        if (data.status === "complete") {
          if (interval) window.clearInterval(interval);
          onCompleteRef.current?.(data.result || {});
        }
        if (data.status === "failed") {
          if (interval) window.clearInterval(interval);
          onErrorRef.current?.(data.error || "Job failed.");
        }
      } catch (error) {
        if (!active) return;
        const message = error instanceof Error ? error.message : "Job polling failed.";
        setState({ status: "failed", result: null, error: message });
        onErrorRef.current?.(message);
      }
    };

    poll();
    interval = window.setInterval(poll, intervalMs);
    return () => {
      active = false;
      if (interval) window.clearInterval(interval);
    };
  }, [intervalMs, jobId]);

  return state;
}