fix(frontend): logout state not shown after JWT expires #40
@@ -3,6 +3,7 @@ import type { ReactNode } from "react";
|
|||||||
import type { User } from "../utils/types.ts";
|
import type { User } from "../utils/types.ts";
|
||||||
import { login as apiLogin, logout as apiLogout, getUser } from "../utils/api/users.ts";
|
import { login as apiLogin, logout as apiLogout, getUser } from "../utils/api/users.ts";
|
||||||
import { AuthContext } from "./AuthContext.ts";
|
import { AuthContext } from "./AuthContext.ts";
|
||||||
|
import {registerUnauthorizedHandler} from "../utils/api/client.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Owns the single source of truth for the authenticated user. Everything that
|
* Owns the single source of truth for the authenticated user. Everything that
|
||||||
@@ -36,6 +37,10 @@ const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
setUser(null);
|
setUser(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
registerUnauthorizedHandler(logout);
|
||||||
|
}, [logout]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={{ user, loading, login, logout, refresh }}>
|
<AuthContext.Provider value={{ user, loading, login, logout, refresh }}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -6,9 +6,32 @@ class AuthError extends Error {
|
|||||||
constructor() { super("Not authenticated"); this.name = "AuthError"; }
|
constructor() { super("Not authenticated"); this.name = "AuthError"; }
|
||||||
}
|
}
|
||||||
|
|
||||||
const isThumbnailAvailable = async (thumbnailUrl: string): Promise<boolean> => {
|
let onUnauthorized: (() => void) | null = null;
|
||||||
const response = await fetch(thumbnailUrl, { credentials: 'include' });
|
|
||||||
return response.ok;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { API_URL, AuthError, isThumbnailAvailable };
|
export function registerUnauthorizedHandler(handler: () => void) {
|
||||||
|
onUnauthorized = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function apiFetch<T>(url: string, options: RequestInit = {}): Promise<T> {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
...options,
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status == 401 || response.status == 403) {
|
||||||
|
onUnauthorized?.();
|
||||||
|
throw new AuthError();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const text = await response.text();
|
||||||
|
let message = text;
|
||||||
|
try { message = JSON.parse(text).message ?? text; } catch { /* not JSON, use raw text */ }
|
||||||
|
throw new Error(message || `Request failed: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = await response.text();
|
||||||
|
return text ? JSON.parse(text) : (undefined as T);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { API_URL, AuthError };
|
||||||
|
|||||||
Reference in New Issue
Block a user