176 أسطر
4.9 KiB
TypeScript
176 أسطر
4.9 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { usePathname, useRouter } from "next/navigation";
|
|
|
|
import { SuperAdminSessionProvider } from "@/components/auth/session-context";
|
|
import { getSuperAdminSession } from "@/lib/api/superadmin";
|
|
import { refreshSuperAdmin } from "@/lib/auth/client";
|
|
import type { SuperAdminSessionResponse } from "@/types/api";
|
|
|
|
const RETRY_DELAY_MS = 5000;
|
|
|
|
function isRecoverableSessionError(error: unknown) {
|
|
const message = String(error).toLowerCase();
|
|
return [
|
|
"502",
|
|
"503",
|
|
"504",
|
|
"fetch failed",
|
|
"failed to fetch",
|
|
"econnrefused",
|
|
"timeout",
|
|
"network",
|
|
"upstream",
|
|
].some((token) => message.includes(token));
|
|
}
|
|
|
|
export function AuthGuard({ children }: { children: React.ReactNode }) {
|
|
const [ready, setReady] = useState(false);
|
|
const [sessionError, setSessionError] = useState<string | null>(null);
|
|
const [session, setSession] = useState<SuperAdminSessionResponse | null>(null);
|
|
const [retryNonce, setRetryNonce] = useState(0);
|
|
const [retryCountdown, setRetryCountdown] = useState<number | null>(null);
|
|
const router = useRouter();
|
|
const pathname = usePathname();
|
|
|
|
useEffect(() => {
|
|
let active = true;
|
|
let retryTimeout: number | null = null;
|
|
let countdownInterval: number | null = null;
|
|
|
|
const clearRetryTimers = () => {
|
|
if (retryTimeout) {
|
|
window.clearTimeout(retryTimeout);
|
|
retryTimeout = null;
|
|
}
|
|
|
|
if (countdownInterval) {
|
|
window.clearInterval(countdownInterval);
|
|
countdownInterval = null;
|
|
}
|
|
};
|
|
|
|
const scheduleRetry = () => {
|
|
clearRetryTimers();
|
|
setRetryCountdown(RETRY_DELAY_MS / 1000);
|
|
|
|
countdownInterval = window.setInterval(() => {
|
|
setRetryCountdown((value) => (value && value > 1 ? value - 1 : 1));
|
|
}, 1000);
|
|
|
|
retryTimeout = window.setTimeout(() => {
|
|
clearRetryTimers();
|
|
setRetryCountdown(null);
|
|
if (active) {
|
|
setRetryNonce((value) => value + 1);
|
|
}
|
|
}, RETRY_DELAY_MS);
|
|
};
|
|
|
|
const ensureSession = async () => {
|
|
try {
|
|
const nextSession = await getSuperAdminSession();
|
|
if (!active) {
|
|
return;
|
|
}
|
|
|
|
clearRetryTimers();
|
|
setSession(nextSession);
|
|
setSessionError(null);
|
|
setRetryCountdown(null);
|
|
setReady(true);
|
|
} catch (initialError) {
|
|
try {
|
|
const refreshed = await refreshSuperAdmin();
|
|
if (!refreshed) {
|
|
router.replace("/login");
|
|
return;
|
|
}
|
|
|
|
const nextSession = await getSuperAdminSession();
|
|
if (!active) {
|
|
return;
|
|
}
|
|
|
|
clearRetryTimers();
|
|
setSession(nextSession);
|
|
setSessionError(null);
|
|
setRetryCountdown(null);
|
|
setReady(true);
|
|
} catch (error) {
|
|
if (String(initialError).includes("401") || String(error).includes("401")) {
|
|
router.replace("/login");
|
|
return;
|
|
}
|
|
|
|
if (!active) {
|
|
return;
|
|
}
|
|
|
|
setSession(null);
|
|
setSessionError(String(error));
|
|
setReady(true);
|
|
|
|
if (isRecoverableSessionError(initialError) || isRecoverableSessionError(error)) {
|
|
scheduleRetry();
|
|
} else {
|
|
clearRetryTimers();
|
|
setRetryCountdown(null);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void ensureSession();
|
|
|
|
return () => {
|
|
active = false;
|
|
clearRetryTimers();
|
|
};
|
|
}, [router, pathname, retryNonce]);
|
|
|
|
if (!ready) {
|
|
return (
|
|
<div className="frame-panel mx-auto mt-10 w-full max-w-2xl p-6 text-sm text-muted-foreground">
|
|
Checking the current SuperAdmin session...
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (sessionError && !session) {
|
|
return (
|
|
<SuperAdminSessionProvider value={{ session: null, permissions: [] }}>
|
|
<div className="frame-panel mx-auto mt-10 w-full max-w-2xl space-y-4 p-6 text-sm text-muted-foreground">
|
|
<div>
|
|
The dashboard could not verify the current session, so permissions were not loaded.
|
|
Reload the page after the backend connection is available again.
|
|
</div>
|
|
<div className="break-words text-xs text-red-300">{sessionError}</div>
|
|
{retryCountdown !== null ? (
|
|
<div className="text-xs text-muted-foreground">
|
|
Retrying automatically in {retryCountdown}s...
|
|
</div>
|
|
) : null}
|
|
<button
|
|
type="button"
|
|
className="rounded-full border border-border px-4 py-2 text-foreground transition hover:bg-secondary"
|
|
onClick={() => {
|
|
setRetryCountdown(null);
|
|
setRetryNonce((value) => value + 1);
|
|
}}
|
|
>
|
|
Retry now
|
|
</button>
|
|
</div>
|
|
</SuperAdminSessionProvider>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<SuperAdminSessionProvider value={{ session, permissions: session?.permissions ?? [] }}>
|
|
{children}
|
|
</SuperAdminSessionProvider>
|
|
);
|
|
}
|