Skip to content
File

Blob: src/client/lib/classify-failure.ts

typescript45 lines
1import { toApiError } from "@/client/lib/api";
2 
3/**
4 * Normalized failure kind for workspace/share/page/reporting flows.
5 *
6 * Classification uses only structured error codes from the worker's JSON
7 * response (`toApiError(err).error`) plus the `online` flag. No
8 * `message.includes(...)`, no status code parsing from message strings.
9 *
10 * - `"auth-ambiguous"` — `error: "unauthorized"` survived the client
11 * auto-refresh cycle. Could be a true expired session, a localhost-only
12 * Miniflare 401→403 artifact, or a transient server issue. Callers that
13 * need definitive auth revocation must use the session store's state
14 * transitions (`markExpired`, `setSessionMode`), not this classifier.
15 * - `"forbidden"` — definitive access denial; not the 401 masquerade.
16 * - `"not-found"` — definitive not-found.
17 * - `"network"` — offline, transport failure, or fetch-level error.
18 * - `"server"` — `internal_error` or other server-side failure.
19 * - `"unknown"` — unclassifiable; ambiguity preserved.
20 */
21export type FailureKind = "auth-ambiguous" | "forbidden" | "not-found" | "network" | "server" | "unknown";
22 
23export function classifyFailure(err: unknown, context: { online: boolean }): FailureKind {
24 if (!context.online) return "network";
25 
26 const apiErr = toApiError(err);
27 
28 switch (apiErr.error) {
29 case "unauthorized":
30 return "auth-ambiguous";
31 case "forbidden":
32 return "forbidden";
33 case "not_found":
34 return "not-found";
35 case "internal_error":
36 return "server";
37 case "request_failed":
38 return "network";
39 }
40 
41 if (err instanceof TypeError) return "network";
42 
43 return "unknown";
44}