feat: implement cross-tab logout synchronization using storage event broadcasting
This commit is contained in:
@@ -13,6 +13,9 @@ export default function LogoutPage() {
|
|||||||
localStorage.setItem('isLoggingOut', 'true');
|
localStorage.setItem('isLoggingOut', 'true');
|
||||||
localStorage.removeItem('accessToken');
|
localStorage.removeItem('accessToken');
|
||||||
localStorage.removeItem('refreshToken');
|
localStorage.removeItem('refreshToken');
|
||||||
|
// Broadcast to other open tabs so they also log out.
|
||||||
|
// 'storage' events only fire in OTHER tabs, not the one that wrote the value.
|
||||||
|
localStorage.setItem('re-quest-logout-broadcast', String(Date.now()));
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -4,6 +4,37 @@ import { SessionProvider as NextAuthSessionProvider, useSession } from "next-aut
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { HeaderProvider } from "./header-provider";
|
import { HeaderProvider } from "./header-provider";
|
||||||
|
|
||||||
|
const LOGOUT_BROADCAST_KEY = "re-quest-logout-broadcast";
|
||||||
|
|
||||||
|
// Listens for logout broadcasts from OTHER tabs and forces this tab to reload.
|
||||||
|
// When the logout page clears auth, it writes a timestamp to LOGOUT_BROADCAST_KEY;
|
||||||
|
// every other open tab receives a `storage` event and reloads into an unauthed state.
|
||||||
|
function CrossTabLogoutListener() {
|
||||||
|
useEffect(() => {
|
||||||
|
const onStorage = (e: StorageEvent) => {
|
||||||
|
if (e.key !== LOGOUT_BROADCAST_KEY || !e.newValue) return;
|
||||||
|
// Skip auth pages — they're already unauthed.
|
||||||
|
const path = window.location.pathname;
|
||||||
|
const onAuthPage =
|
||||||
|
path === "/login" ||
|
||||||
|
path === "/signup" ||
|
||||||
|
path === "/logout" ||
|
||||||
|
path === "/" ||
|
||||||
|
path.startsWith("/forgot-password") ||
|
||||||
|
path.startsWith("/reset-password");
|
||||||
|
if (onAuthPage) return;
|
||||||
|
// Clear local auth and hard-redirect to /logout so this tab runs the full cleanup too.
|
||||||
|
localStorage.removeItem("accessToken");
|
||||||
|
localStorage.removeItem("refreshToken");
|
||||||
|
localStorage.removeItem("user");
|
||||||
|
window.location.replace("/logout");
|
||||||
|
};
|
||||||
|
window.addEventListener("storage", onStorage);
|
||||||
|
return () => window.removeEventListener("storage", onStorage);
|
||||||
|
}, []);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Component that syncs tokens from NextAuth session to localStorage
|
// Component that syncs tokens from NextAuth session to localStorage
|
||||||
function TokenSync() {
|
function TokenSync() {
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status } = useSession();
|
||||||
@@ -63,6 +94,7 @@ export function SessionProvider({ children }: { children: React.ReactNode }) {
|
|||||||
return (
|
return (
|
||||||
<NextAuthSessionProvider>
|
<NextAuthSessionProvider>
|
||||||
<TokenSync />
|
<TokenSync />
|
||||||
|
<CrossTabLogoutListener />
|
||||||
<HeaderProvider>
|
<HeaderProvider>
|
||||||
{children}
|
{children}
|
||||||
</HeaderProvider>
|
</HeaderProvider>
|
||||||
|
|||||||
Reference in New Issue
Block a user