Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Introduced turbopack alias for canvas in next.config.mjs. - Updated package.json scripts for development and branding tasks. - Added new dependencies for Tiptap extensions. - Implemented new demo layouts for agenda, contacts, drive, and mail applications. - Enhanced globals.css for improved theming and splash screen animations. - Added OAuth callback handling for drive mounts. - Updated layout components to integrate new demo shells and improve structure.
95 lines
2.1 KiB
TypeScript
95 lines
2.1 KiB
TypeScript
import { useAuthStore } from "@/lib/api/auth-store"
|
|
import { ensureAccessToken } from "@/lib/auth/ensure-access-token"
|
|
import { fetchSession, tryRefreshSession } from "@/lib/auth/session-sync"
|
|
import {
|
|
isSessionExpired,
|
|
useSessionGuardStore,
|
|
} from "@/lib/auth/session-guard-store"
|
|
import { isDemoPublicPath } from "@/lib/auth/public-paths"
|
|
|
|
export type UnauthorizedResolution = "refreshed" | "offline" | "expired"
|
|
|
|
type HandleUnauthorizedOptions = {
|
|
/** API still returns 401 after a session refresh attempt. */
|
|
forceExpired?: boolean
|
|
}
|
|
|
|
let pending: Promise<UnauthorizedResolution> | null = null
|
|
|
|
function isBrowserOffline() {
|
|
return typeof navigator !== "undefined" && !navigator.onLine
|
|
}
|
|
|
|
function isDemoRoute() {
|
|
if (typeof window === "undefined") return false
|
|
return isDemoPublicPath(window.location.pathname)
|
|
}
|
|
|
|
function markSessionExpired() {
|
|
if (isDemoRoute()) return
|
|
useAuthStore.getState().logout()
|
|
useSessionGuardStore.getState().setExpired()
|
|
}
|
|
|
|
async function resolveUnauthorized(
|
|
opts?: HandleUnauthorizedOptions
|
|
): Promise<UnauthorizedResolution> {
|
|
if (isDemoRoute()) {
|
|
return "refreshed"
|
|
}
|
|
|
|
if (isSessionExpired()) {
|
|
return "expired"
|
|
}
|
|
|
|
if (opts?.forceExpired) {
|
|
markSessionExpired()
|
|
return "expired"
|
|
}
|
|
|
|
if (isBrowserOffline()) {
|
|
useSessionGuardStore.getState().setOffline()
|
|
return "offline"
|
|
}
|
|
|
|
if (await tryRefreshSession()) {
|
|
return "refreshed"
|
|
}
|
|
|
|
const session = await fetchSession()
|
|
if (session?.authenticated) {
|
|
return "refreshed"
|
|
}
|
|
|
|
if (await ensureAccessToken()) {
|
|
return "refreshed"
|
|
}
|
|
|
|
markSessionExpired()
|
|
return "expired"
|
|
}
|
|
|
|
/** Verify session after a 401; deduped across concurrent API calls. */
|
|
export function handleUnauthorized(
|
|
opts?: HandleUnauthorizedOptions
|
|
): Promise<UnauthorizedResolution> {
|
|
if (isDemoRoute()) {
|
|
return Promise.resolve("refreshed")
|
|
}
|
|
|
|
if (isSessionExpired()) {
|
|
return Promise.resolve("expired")
|
|
}
|
|
|
|
if (opts?.forceExpired) {
|
|
return resolveUnauthorized(opts)
|
|
}
|
|
|
|
if (!pending) {
|
|
pending = resolveUnauthorized().finally(() => {
|
|
pending = null
|
|
})
|
|
}
|
|
return pending
|
|
}
|