Keep a bounded per-tab logout intent and broadcast it to already open tabs so back navigation after a normal logout is not mistaken for renewal failure. Preserve real refresh and session expiry signals, and keep IdP navigation reachable when local cleanup fails.
55 lines
1.2 KiB
TypeScript
55 lines
1.2 KiB
TypeScript
import { markIntentionalLogout } from "@/lib/logoutIntent";
|
|
|
|
const KEYCLOAK_LOGOUT_PATH = "/api/auth/keycloak-logout";
|
|
|
|
type CompleteLogoutOptions = {
|
|
reportAudit: () => Promise<unknown>;
|
|
clearLocalState: () => void;
|
|
navigate: (path: string) => void;
|
|
};
|
|
|
|
type LogoutAuditOptions = {
|
|
endpoint: string;
|
|
accessToken?: string | null;
|
|
fetcher?: typeof fetch;
|
|
};
|
|
|
|
export const reportLogoutAudit = ({
|
|
endpoint,
|
|
accessToken,
|
|
fetcher = fetch,
|
|
}: LogoutAuditOptions) => {
|
|
const headers = new Headers({ "Content-Type": "application/json" });
|
|
if (accessToken) {
|
|
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
}
|
|
|
|
return fetcher(endpoint, {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify({ event: "logout" }),
|
|
keepalive: true,
|
|
});
|
|
};
|
|
|
|
export const completeLogout = async ({
|
|
reportAudit,
|
|
clearLocalState,
|
|
navigate,
|
|
}: CompleteLogoutOptions) => {
|
|
try {
|
|
void reportAudit().catch(() => undefined);
|
|
} catch {
|
|
// Synchronous reporting errors must not block logout either.
|
|
}
|
|
|
|
markIntentionalLogout();
|
|
try {
|
|
clearLocalState();
|
|
} catch {
|
|
// Browser storage cleanup failures must not prevent the IdP logout.
|
|
}
|
|
navigate(KEYCLOAK_LOGOUT_PATH);
|
|
return { success: true as const };
|
|
};
|