feat(pwa): usePairPoll hook (polls tg pairing, sets session)
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
// src/hooks/usePairPoll.test.tsx
|
||||||
|
import { renderHook, waitFor } from "@testing-library/react";
|
||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
import { usePairPoll } from "./usePairPoll";
|
||||||
|
import * as driver from "@/api/driver";
|
||||||
|
import { useAuth } from "@/store/auth";
|
||||||
|
|
||||||
|
beforeEach(() => { useAuth.getState().clear(); vi.restoreAllMocks(); });
|
||||||
|
|
||||||
|
describe("usePairPoll", () => {
|
||||||
|
it("sets session on authenticated", async () => {
|
||||||
|
vi.spyOn(driver, "tgPoll").mockResolvedValue({ status: "authenticated", token: "T", driver_id: 7 } as any);
|
||||||
|
const { result } = renderHook(() => usePairPoll("SESS"));
|
||||||
|
await waitFor(() => expect(result.current.status).toBe("authenticated"));
|
||||||
|
expect(useAuth.getState().token).toBe("T");
|
||||||
|
expect(useAuth.getState().driverId).toBe(7);
|
||||||
|
});
|
||||||
|
it("stays pending then expired", async () => {
|
||||||
|
vi.spyOn(driver, "tgPoll").mockResolvedValue({ status: "expired" } as any);
|
||||||
|
const { result } = renderHook(() => usePairPoll("SESS"));
|
||||||
|
await waitFor(() => expect(result.current.status).toBe("expired"));
|
||||||
|
});
|
||||||
|
it("does nothing when session is null", () => {
|
||||||
|
const spy = vi.spyOn(driver, "tgPoll");
|
||||||
|
const { result } = renderHook(() => usePairPoll(null));
|
||||||
|
expect(result.current.status).toBe("pending");
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// src/hooks/usePairPoll.ts
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { tgPoll } from "@/api/driver";
|
||||||
|
import { useAuth } from "@/store/auth";
|
||||||
|
|
||||||
|
type Status = "pending" | "authenticated" | "expired";
|
||||||
|
const INTERVAL_MS = 2000;
|
||||||
|
const TIMEOUT_MS = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
export function usePairPoll(pairSession: string | null): { status: Status } {
|
||||||
|
const [status, setStatus] = useState<Status>("pending");
|
||||||
|
const setSession = useAuth((s) => s.setSession);
|
||||||
|
const startedAt = useRef<number>(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!pairSession) return;
|
||||||
|
let alive = true;
|
||||||
|
let timer: ReturnType<typeof setTimeout>;
|
||||||
|
startedAt.current = Date.now();
|
||||||
|
|
||||||
|
const tick = async () => {
|
||||||
|
if (!alive) return;
|
||||||
|
try {
|
||||||
|
const r = await tgPoll(pairSession);
|
||||||
|
if (!alive) return;
|
||||||
|
if (r.status === "authenticated") {
|
||||||
|
setSession(r.token, r.driver_id);
|
||||||
|
setStatus("authenticated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (r.status === "expired") { setStatus("expired"); return; }
|
||||||
|
} catch {
|
||||||
|
// transient — keep polling until timeout
|
||||||
|
}
|
||||||
|
if (Date.now() - startedAt.current > TIMEOUT_MS) { setStatus("expired"); return; }
|
||||||
|
timer = setTimeout(tick, INTERVAL_MS);
|
||||||
|
};
|
||||||
|
tick();
|
||||||
|
return () => { alive = false; clearTimeout(timer); };
|
||||||
|
}, [pairSession, setSession]);
|
||||||
|
|
||||||
|
return { status };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user