diff --git a/modules/frontend/src/api/AuthClient/AuthClient.ts b/modules/frontend/src/api/AuthClient/AuthClient.ts new file mode 100644 index 0000000..58f4887 --- /dev/null +++ b/modules/frontend/src/api/AuthClient/AuthClient.ts @@ -0,0 +1,55 @@ +import { createClient, createConfig } from "../client"; +import type { ClientOptions as ClientOptions2 } from '../types.gen'; +import type { Client, RequestOptions, RequestResult } from "../client"; +import { refreshTokens } from "../../auth"; +import type { ResponseStyle } from "../client"; + +let refreshPromise: Promise | null = null; + +async function getRefreshed(): Promise { + if (!refreshPromise) { + refreshPromise = (async () => { + try { + const res = await refreshTokens(); + // consider refresh successful if res.data exists + return !!res.data; + } catch (err) { + return false; // failed to refresh + } + })(); + } + + return refreshPromise; +} + +const baseClient = createClient(createConfig({ baseUrl: '/api/v1' })); + +export const authClient: Client = { + ...baseClient, + + request: function < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', + >( + options: Omit, 'method'> & + Pick>, 'method'> + ): RequestResult { + + // Wrap logic inside a Promise to satisfy RequestResult type + return baseClient.request(options).catch(async (err: any) => { + if (err?.status === 401) { + const refreshed = await getRefreshed(); + if (!refreshed) { + localStorage.clear(); + window.location.href = "/login"; + throw err; + } + // Retry original request + return baseClient.request(options); + } + throw err; + }) as RequestResult; + }, +}; diff --git a/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx b/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx index 727e072..b5fb266 100644 --- a/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx +++ b/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx @@ -9,6 +9,7 @@ import { getTitles, type CursorObj, type Title, type TitleSort } from "../../api import { LayoutSwitch } from "../../components/LayoutSwitch/LayoutSwitch"; import { Link } from "react-router-dom"; import { type TitlesFilter, TitlesFilterPanel } from "../../components/TitlesFilterPanel/TitlesFilterPanel"; +import { authClient } from "../../api/AuthClient/AuthClient"; const PAGE_SIZE = 10; @@ -51,7 +52,12 @@ export default function TitlesPage() { offset: PAGE_SIZE, fields: "all", }, - }); + client: authClient, + },); + + if (response.response.status === 403) { + + } return { items: response.data?.data ?? [],