eCourt-note/wordaddin/services/apiClient.ts

127 lines
4.9 KiB
TypeScript
Raw Normal View History

2026-10-16 12:07:25 +07:00
import { config } from "../config";
import * as authService from "./authService";
/**
* fetch wrapper ของ add-in — แนบ Bearer ทุก request (access_token ที่ authService
* ขอจาก /api/documents/token ด้วย ticket ของไฟล์ — PLAN-ADDIN-AUTH.md)
* โดน 401 → ลองขอ token ใหม่จาก ticket แล้ว retry 1 ครั้ง ยังไม่ผ่าน → AuthRequiredError:
* UI จับ error นี้แล้วพาไปหน้า no-access ให้ขอไฟล์ใหม่จาก e-Court
* พอร์ตจาก D:\word-addin-poc\addin\src\services\apiClient.ts
2026-10-16 12:07:25 +07:00
* (ช่วง mock ไม่มี caller จริง — เตรียมไว้ให้ versioningService วันปลด mock + AuthRequiredError
* ถูก TaskpaneApp ใช้อยู่แล้ว)
*/
/** บอกว่า ticket ใช้ไม่ได้แล้ว (หมดอายุ / token ขอไม่ได้) — ต้องได้ไฟล์ใหม่จาก e-Court */
2026-10-16 12:07:25 +07:00
export class AuthRequiredError extends Error {
constructor(message = "ticket ของ e-Court หมดอายุหรือไม่ถูกต้อง") {
2026-10-16 12:07:25 +07:00
super(message);
this.name = "AuthRequiredError";
}
}
/** backend ตอบมาแต่ status ไม่สำเร็จ — message อ่านได้จาก body ถ้ามี */
export class ApiError extends Error {
readonly status: number;
constructor(status: number, message: string) {
super(message);
this.name = "ApiError";
this.status = status;
}
}
export interface ApiRequestOptions {
method?: string;
headers?: Record<string, string>;
body?: BodyInit | null;
}
function toUrl(path: string): string {
return path.startsWith("http") ? path : `${config.apiBaseUrl}${path}`;
}
async function doFetch(
path: string,
options: ApiRequestOptions,
accessToken: string
): Promise<Response> {
const headers = new Headers(options.headers ?? {});
headers.set("Authorization", `Bearer ${accessToken}`);
return fetch(toUrl(path), {
method: options.method ?? "GET",
headers,
body: options.body,
});
}
/**
* request พร้อม Bearer — โดน 401 → ขอ token ใหม่จาก ticket ของเอกสารปัจจุบัน
* (renewTokenFromTicket — ticket ยังไม่หมดอายุเท่านั้น) แล้ว retry 1 ครั้ง
* ยัง 401 อีก / renew ไม่ได้ = AuthRequiredError (UI พาไป no-access ให้ขอไฟล์
* ใหม่จาก e-Court) · Response ที่ได้คืนไปให้ caller เช็ค ok เอง
2026-10-16 12:07:25 +07:00
*/
export async function apiFetch(path: string, options: ApiRequestOptions = {}): Promise<Response> {
const accessToken = await authService.getAccessToken();
if (!accessToken) {
throw new AuthRequiredError();
}
const response = await doFetch(path, options, accessToken);
if (response.status !== 401) {
return response;
}
if (await authService.renewTokenFromTicket()) {
const retryToken = await authService.getAccessToken();
if (retryToken) {
const retry = await doFetch(path, options, retryToken);
if (retry.status !== 401) {
return retry;
}
}
2026-10-16 12:07:25 +07:00
}
throw new AuthRequiredError();
2026-10-16 12:07:25 +07:00
}
/** อ่าน error message จาก response body — backend error ส่วนใหญ่เป็น plain text, บางทีเป็น JSON problem details */
export async function readErrorMessage(response: Response, fallback: string): Promise<string> {
try {
const text = (await response.text()).trim();
if (!text) {
return fallback;
}
try {
const parsed = JSON.parse(text) as { detail?: unknown; message?: unknown; title?: unknown };
const detail = parsed.detail ?? parsed.message ?? parsed.title;
if (typeof detail === "string" && detail) {
return detail;
}
} catch {
// plain text — ใช้ตรงๆ
}
return text;
} catch {
return fallback;
}
}
/** GET แล้ว parse JSON — fail โยน ApiError พร้อม message จาก backend */
export async function apiGetJson<T>(path: string, fallbackError: string): Promise<T> {
const response = await apiFetch(path);
if (!response.ok) {
throw new ApiError(response.status, await readErrorMessage(response, fallbackError));
}
return (await response.json()) as T;
}
/** POST (form/json) แล้ว parse JSON — ใช้กับ upload version ที่ตอบ 201 */
export async function apiSendExpectJson<T>(
path: string,
options: ApiRequestOptions,
fallbackError: string
): Promise<T> {
const response = await apiFetch(path, options);
if (!response.ok) {
throw new ApiError(response.status, await readErrorMessage(response, fallbackError));
}
return (await response.json()) as T;
}