Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/adapters/auth/signOut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,31 @@ type SignOutOptions = {
callbackUrl?: string;
};

export type SessionClearedPayload = {
redirecting?: boolean;
};

export async function signOut(options: SignOutOptions = {}) {
// Remove the persisted session first. This makes the user unauthenticated
// for the next page load and prevents the old access token from being used.
clearSession();
eventBus.emit(AppEvents.SessionCleared);
if (options.callbackUrl && typeof window !== "undefined") {
const currentPathname = window.location.pathname;

if (currentPathname.startsWith("/admin")) {
// Start the redirect before notifying the current page. The event is
// still emitted, but the redirect flag tells mounted listeners to wait
// for the new page instead of rendering the login screen once here.
window.location.href = options.callbackUrl;
console.log("[Auth] SessionCleared emitted from admin logout");
eventBus.emit<SessionClearedPayload>(AppEvents.SessionCleared, { redirecting: true });
return;
}
}

// When there is no full-page redirect, mounted components must receive this
// event so useSession can clear their in-memory user and status values.
console.log("[Auth] SessionCleared emitted from signOut");
eventBus.emit<SessionClearedPayload>(AppEvents.SessionCleared);
return;
}
13 changes: 12 additions & 1 deletion src/hooks/useSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
import { loadSession } from "../adapters/auth/storage";
import { getSession } from "../adapters/auth/getSession";
import type { Session } from "../adapters/auth/types";
import type { SessionClearedPayload } from "../adapters/auth/signOut";
import { eventBus, AppEvents } from "../helpers/eventBus";

type UseSessionResult = {
Expand Down Expand Up @@ -38,7 +39,17 @@ export function useSession(): UseSessionResult {
setData(session || null);
setStatus(session?.user?.accessToken ? "authenticated" : "unauthenticated");
});
const offClear = eventBus.on(AppEvents.SessionCleared, () => {
const offClear = eventBus.on<SessionClearedPayload>(AppEvents.SessionCleared, (payload) => {
// For a normal sign-out, clear the session held by this React tree so
// route guards update immediately. During a page redirect, storage is
// already cleared and the new page will check it, so updating this old
// tree would briefly render the login screen before the redirect.
console.log(
payload?.redirecting
? "[Auth] SessionCleared received; waiting for page redirect"
: "[Auth] SessionCleared listener received event"
);
if (payload?.redirecting) return;
setData(null);
setStatus("unauthenticated");
});
Expand Down