mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-28 21:49:40 +02:00
fix(web):update auth token consumers
This commit is contained in:
parent
411bb0019e
commit
71045e552d
4 changed files with 173 additions and 103 deletions
|
|
@ -3,17 +3,18 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useGlobalLoadingEffect } from "@/hooks/use-global-loading";
|
import { useGlobalLoadingEffect } from "@/hooks/use-global-loading";
|
||||||
import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
|
import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
|
||||||
import { getAndClearRedirectPath, setBearerToken, setRefreshToken } from "@/lib/auth-utils";
|
import { getAndClearRedirectPath } from "@/lib/auth-utils";
|
||||||
|
import { buildBackendUrl } from "@/lib/env-config";
|
||||||
import { trackLoginSuccess } from "@/lib/posthog/events";
|
import { trackLoginSuccess } from "@/lib/posthog/events";
|
||||||
|
|
||||||
interface TokenHandlerProps {
|
interface TokenHandlerProps {
|
||||||
redirectPath?: string; // Default path to redirect after storing token (if no saved path)
|
redirectPath?: string; // Default path to redirect after storing token (if no saved path)
|
||||||
tokenParamName?: string; // Name of the URL parameter containing the token
|
tokenParamName?: string; // Deprecated: tokens are no longer read from URLs
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client component that extracts a token from URL parameters and stores it in localStorage
|
* Client component that finalizes a cookie session after OAuth/local login.
|
||||||
* After storing the token, it redirects the user back to the page they were on before
|
* After confirming the session, it redirects the user back to the page they were on before
|
||||||
* being redirected to login (if available), or to the default redirectPath.
|
* being redirected to login (if available), or to the default redirectPath.
|
||||||
*
|
*
|
||||||
* @param redirectPath - Default path to redirect after storing token (default: '/dashboard')
|
* @param redirectPath - Default path to redirect after storing token (default: '/dashboard')
|
||||||
|
|
@ -21,7 +22,7 @@ interface TokenHandlerProps {
|
||||||
*/
|
*/
|
||||||
const TokenHandler = ({
|
const TokenHandler = ({
|
||||||
redirectPath = "/dashboard",
|
redirectPath = "/dashboard",
|
||||||
tokenParamName = "token",
|
tokenParamName: _tokenParamName = "token",
|
||||||
}: TokenHandlerProps) => {
|
}: TokenHandlerProps) => {
|
||||||
// Always show loading for this component - spinner animation won't reset
|
// Always show loading for this component - spinner animation won't reset
|
||||||
useGlobalLoadingEffect(true);
|
useGlobalLoadingEffect(true);
|
||||||
|
|
@ -30,51 +31,47 @@ const TokenHandler = ({
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
|
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
const params = new URLSearchParams(window.location.search);
|
try {
|
||||||
const token = params.get(tokenParamName);
|
const sessionResponse = await fetch(buildBackendUrl("/auth/session"), {
|
||||||
const refreshToken = params.get("refresh_token");
|
credentials: "include",
|
||||||
|
});
|
||||||
if (token) {
|
if (!sessionResponse.ok) {
|
||||||
try {
|
window.location.href = "/login";
|
||||||
const alreadyTracked = sessionStorage.getItem("login_success_tracked");
|
return;
|
||||||
if (!alreadyTracked) {
|
|
||||||
trackLoginSuccess("google");
|
|
||||||
}
|
|
||||||
sessionStorage.removeItem("login_success_tracked");
|
|
||||||
|
|
||||||
setBearerToken(token);
|
|
||||||
|
|
||||||
if (refreshToken) {
|
|
||||||
setRefreshToken(refreshToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-set active search space in desktop if not already set
|
|
||||||
if (window.electronAPI?.getActiveSearchSpace) {
|
|
||||||
try {
|
|
||||||
const stored = await window.electronAPI.getActiveSearchSpace();
|
|
||||||
if (!stored) {
|
|
||||||
const spaces = await searchSpacesApiService.getSearchSpaces();
|
|
||||||
if (spaces?.length) {
|
|
||||||
await window.electronAPI.setActiveSearchSpace?.(String(spaces[0].id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// non-critical
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const savedRedirectPath = getAndClearRedirectPath();
|
|
||||||
const finalRedirectPath = savedRedirectPath || redirectPath;
|
|
||||||
window.location.href = finalRedirectPath;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error storing token in localStorage:", error);
|
|
||||||
window.location.href = redirectPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const alreadyTracked = sessionStorage.getItem("login_success_tracked");
|
||||||
|
if (!alreadyTracked) {
|
||||||
|
trackLoginSuccess("google");
|
||||||
|
}
|
||||||
|
sessionStorage.removeItem("login_success_tracked");
|
||||||
|
|
||||||
|
// Auto-set active search space in desktop if not already set
|
||||||
|
if (window.electronAPI?.getActiveSearchSpace) {
|
||||||
|
try {
|
||||||
|
const stored = await window.electronAPI.getActiveSearchSpace();
|
||||||
|
if (!stored) {
|
||||||
|
const spaces = await searchSpacesApiService.getSearchSpaces();
|
||||||
|
if (spaces?.length) {
|
||||||
|
await window.electronAPI.setActiveSearchSpace?.(String(spaces[0].id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// non-critical
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedRedirectPath = getAndClearRedirectPath();
|
||||||
|
const finalRedirectPath = savedRedirectPath || redirectPath;
|
||||||
|
window.location.href = finalRedirectPath;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error finalizing session:", error);
|
||||||
|
window.location.href = redirectPath;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
run();
|
run();
|
||||||
}, [tokenParamName, redirectPath]);
|
}, [redirectPath]);
|
||||||
|
|
||||||
// Return null - the global provider handles the loading UI
|
// Return null - the global provider handles the loading UI
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,17 @@
|
||||||
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { getBearerToken } from "@/lib/auth-utils";
|
import { useSession } from "@/hooks/use-session";
|
||||||
|
|
||||||
export function AuthRedirect() {
|
export function AuthRedirect() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const session = useSession();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getBearerToken()) {
|
if (session.status === "authenticated") {
|
||||||
router.replace("/dashboard");
|
router.replace("/dashboard");
|
||||||
}
|
}
|
||||||
}, [router]);
|
}, [router, session.status]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
|
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
|
||||||
|
import { useSession } from "@/hooks/use-session";
|
||||||
|
import { isPublicRoute } from "@/lib/auth-utils";
|
||||||
import { identifyUser, resetUser } from "@/lib/posthog/events";
|
import { identifyUser, resetUser } from "@/lib/posthog/events";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -12,7 +15,15 @@ import { identifyUser, resetUser } from "@/lib/posthog/events";
|
||||||
*
|
*
|
||||||
* This should be rendered inside the PostHogProvider.
|
* This should be rendered inside the PostHogProvider.
|
||||||
*/
|
*/
|
||||||
export function PostHogIdentify() {
|
function PostHogReset() {
|
||||||
|
useEffect(() => {
|
||||||
|
resetUser();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PostHogUserIdentify() {
|
||||||
const { data: user, isSuccess, isError } = useAtomValue(currentUserAtom);
|
const { data: user, isSuccess, isError } = useAtomValue(currentUserAtom);
|
||||||
const previousUserIdRef = useRef<string | null>(null);
|
const previousUserIdRef = useRef<string | null>(null);
|
||||||
|
|
||||||
|
|
@ -47,3 +58,27 @@ export function PostHogIdentify() {
|
||||||
// This component doesn't render anything
|
// This component doesn't render anything
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SessionGatedPostHogIdentify() {
|
||||||
|
const session = useSession();
|
||||||
|
|
||||||
|
if (session.status === "loading") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.status === "unauthenticated") {
|
||||||
|
return <PostHogReset />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <PostHogUserIdentify />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PostHogIdentify() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
if (isPublicRoute(pathname)) {
|
||||||
|
return <PostHogReset />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <SessionGatedPostHogIdentify />;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,18 @@ const REDIRECT_PATH_KEY = "surfsense_redirect_path";
|
||||||
const BEARER_TOKEN_KEY = "surfsense_bearer_token";
|
const BEARER_TOKEN_KEY = "surfsense_bearer_token";
|
||||||
const REFRESH_TOKEN_KEY = "surfsense_refresh_token";
|
const REFRESH_TOKEN_KEY = "surfsense_refresh_token";
|
||||||
|
|
||||||
// Flag to prevent multiple simultaneous refresh attempts
|
let desktopBearerToken: string | null = null;
|
||||||
let isRefreshing = false;
|
let desktopRefreshToken: string | null = null;
|
||||||
let refreshPromise: Promise<string | null> | null = null;
|
|
||||||
|
function isDesktopClient(): boolean {
|
||||||
|
return typeof window !== "undefined" && !!window.electronAPI;
|
||||||
|
}
|
||||||
|
|
||||||
|
function purgeLegacyStoredTokens(): void {
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
|
localStorage.removeItem(BEARER_TOKEN_KEY);
|
||||||
|
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
/** Path prefixes for routes that do not require auth (no current-user fetch, no redirect on 401) */
|
/** Path prefixes for routes that do not require auth (no current-user fetch, no redirect on 401) */
|
||||||
const PUBLIC_ROUTE_PREFIXES = [
|
const PUBLIC_ROUTE_PREFIXES = [
|
||||||
|
|
@ -53,8 +62,9 @@ export function handleUnauthorized(): void {
|
||||||
const pathname = window.location.pathname;
|
const pathname = window.location.pathname;
|
||||||
|
|
||||||
// Always clear tokens
|
// Always clear tokens
|
||||||
localStorage.removeItem(BEARER_TOKEN_KEY);
|
purgeLegacyStoredTokens();
|
||||||
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
desktopBearerToken = null;
|
||||||
|
desktopRefreshToken = null;
|
||||||
|
|
||||||
// Only redirect on protected routes; stay on public pages (e.g. /docs)
|
// Only redirect on protected routes; stay on public pages (e.g. /docs)
|
||||||
if (!isPublicRoute(pathname)) {
|
if (!isPublicRoute(pathname)) {
|
||||||
|
|
@ -93,8 +103,8 @@ export function getAndClearRedirectPath(): string | null {
|
||||||
* Gets the bearer token from localStorage
|
* Gets the bearer token from localStorage
|
||||||
*/
|
*/
|
||||||
export function getBearerToken(): string | null {
|
export function getBearerToken(): string | null {
|
||||||
if (typeof window === "undefined") return null;
|
if (typeof window === "undefined" || !isDesktopClient()) return null;
|
||||||
return localStorage.getItem(BEARER_TOKEN_KEY);
|
return desktopBearerToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -102,7 +112,8 @@ export function getBearerToken(): string | null {
|
||||||
*/
|
*/
|
||||||
export function setBearerToken(token: string): void {
|
export function setBearerToken(token: string): void {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
localStorage.setItem(BEARER_TOKEN_KEY, token);
|
purgeLegacyStoredTokens();
|
||||||
|
desktopBearerToken = isDesktopClient() ? token : null;
|
||||||
syncTokensToElectron();
|
syncTokensToElectron();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,14 +123,15 @@ export function setBearerToken(token: string): void {
|
||||||
export function clearBearerToken(): void {
|
export function clearBearerToken(): void {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
localStorage.removeItem(BEARER_TOKEN_KEY);
|
localStorage.removeItem(BEARER_TOKEN_KEY);
|
||||||
|
desktopBearerToken = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the refresh token from localStorage
|
* Gets the refresh token from localStorage
|
||||||
*/
|
*/
|
||||||
export function getRefreshToken(): string | null {
|
export function getRefreshToken(): string | null {
|
||||||
if (typeof window === "undefined") return null;
|
if (typeof window === "undefined" || !isDesktopClient()) return null;
|
||||||
return localStorage.getItem(REFRESH_TOKEN_KEY);
|
return desktopRefreshToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -127,7 +139,8 @@ export function getRefreshToken(): string | null {
|
||||||
*/
|
*/
|
||||||
export function setRefreshToken(token: string): void {
|
export function setRefreshToken(token: string): void {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
localStorage.setItem(REFRESH_TOKEN_KEY, token);
|
purgeLegacyStoredTokens();
|
||||||
|
desktopRefreshToken = isDesktopClient() ? token : null;
|
||||||
syncTokensToElectron();
|
syncTokensToElectron();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,6 +150,7 @@ export function setRefreshToken(token: string): void {
|
||||||
export function clearRefreshToken(): void {
|
export function clearRefreshToken(): void {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
||||||
|
desktopRefreshToken = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -153,8 +167,8 @@ export function clearAllTokens(): void {
|
||||||
*/
|
*/
|
||||||
function syncTokensToElectron(): void {
|
function syncTokensToElectron(): void {
|
||||||
if (typeof window === "undefined" || !window.electronAPI?.setAuthTokens) return;
|
if (typeof window === "undefined" || !window.electronAPI?.setAuthTokens) return;
|
||||||
const bearer = localStorage.getItem(BEARER_TOKEN_KEY) || "";
|
const bearer = desktopBearerToken || "";
|
||||||
const refresh = localStorage.getItem(REFRESH_TOKEN_KEY) || "";
|
const refresh = desktopRefreshToken || "";
|
||||||
if (bearer) {
|
if (bearer) {
|
||||||
window.electronAPI.setAuthTokens(bearer, refresh);
|
window.electronAPI.setAuthTokens(bearer, refresh);
|
||||||
}
|
}
|
||||||
|
|
@ -171,11 +185,18 @@ export async function ensureTokensFromElectron(): Promise<boolean> {
|
||||||
if (getBearerToken()) return true;
|
if (getBearerToken()) return true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (window.electronAPI.getAccessToken) {
|
||||||
|
const token = await window.electronAPI.getAccessToken();
|
||||||
|
if (token) {
|
||||||
|
desktopBearerToken = token;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
const tokens = await window.electronAPI.getAuthTokens();
|
const tokens = await window.electronAPI.getAuthTokens();
|
||||||
if (tokens?.bearer) {
|
if (tokens?.bearer) {
|
||||||
localStorage.setItem(BEARER_TOKEN_KEY, tokens.bearer);
|
desktopBearerToken = tokens.bearer;
|
||||||
if (tokens.refresh) {
|
if (tokens.refresh) {
|
||||||
localStorage.setItem(REFRESH_TOKEN_KEY, tokens.refresh);
|
desktopRefreshToken = tokens.refresh;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -191,16 +212,24 @@ export async function ensureTokensFromElectron(): Promise<boolean> {
|
||||||
*/
|
*/
|
||||||
export async function logout(): Promise<boolean> {
|
export async function logout(): Promise<boolean> {
|
||||||
const refreshToken = getRefreshToken();
|
const refreshToken = getRefreshToken();
|
||||||
|
const isDesktop = isDesktopClient();
|
||||||
|
|
||||||
|
if (isDesktop && window.electronAPI?.logout) {
|
||||||
|
await window.electronAPI.logout();
|
||||||
|
clearAllTokens();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Call backend to revoke the refresh token
|
// Call backend to revoke the refresh token
|
||||||
if (refreshToken) {
|
if (refreshToken || !isDesktop) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(buildBackendUrl("/auth/jwt/revoke"), {
|
const response = await fetch(buildBackendUrl("/auth/jwt/revoke"), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ refresh_token: refreshToken }),
|
credentials: "include",
|
||||||
|
...(refreshToken ? { body: JSON.stringify({ refresh_token: refreshToken }) } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -221,7 +250,7 @@ export async function logout(): Promise<boolean> {
|
||||||
* Checks if the user is authenticated (has a token)
|
* Checks if the user is authenticated (has a token)
|
||||||
*/
|
*/
|
||||||
export function isAuthenticated(): boolean {
|
export function isAuthenticated(): boolean {
|
||||||
return !!getBearerToken();
|
return isDesktopClient() ? !!getBearerToken() : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -259,50 +288,56 @@ export function getAuthHeaders(additionalHeaders?: Record<string, string>): Reco
|
||||||
* Attempts to refresh the access token using the stored refresh token.
|
* Attempts to refresh the access token using the stored refresh token.
|
||||||
* Returns the new access token if successful, null otherwise.
|
* Returns the new access token if successful, null otherwise.
|
||||||
*/
|
*/
|
||||||
export async function refreshAccessToken(): Promise<string | null> {
|
async function doRefreshSession(): Promise<string | null> {
|
||||||
// If already refreshing, wait for that request to complete
|
|
||||||
if (isRefreshing && refreshPromise) {
|
|
||||||
return refreshPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentRefreshToken = getRefreshToken();
|
const currentRefreshToken = getRefreshToken();
|
||||||
if (!currentRefreshToken) {
|
if (isDesktopClient() && !currentRefreshToken) {
|
||||||
|
if (window.electronAPI?.refreshAccessToken) {
|
||||||
|
const token = await window.electronAPI.refreshAccessToken();
|
||||||
|
if (token) {
|
||||||
|
desktopBearerToken = token;
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
isRefreshing = true;
|
try {
|
||||||
refreshPromise = (async () => {
|
const response = await fetch(buildBackendUrl("/auth/jwt/refresh"), {
|
||||||
try {
|
method: "POST",
|
||||||
const response = await fetch(buildBackendUrl("/auth/jwt/refresh"), {
|
headers: {
|
||||||
method: "POST",
|
"Content-Type": "application/json",
|
||||||
headers: {
|
},
|
||||||
"Content-Type": "application/json",
|
credentials: "include",
|
||||||
},
|
...(currentRefreshToken ? { body: JSON.stringify({ refresh_token: currentRefreshToken }) } : {}),
|
||||||
body: JSON.stringify({ refresh_token: currentRefreshToken }),
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
// Refresh failed, clear tokens
|
clearAllTokens();
|
||||||
clearAllTokens();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
if (data.access_token && data.refresh_token) {
|
|
||||||
setBearerToken(data.access_token);
|
|
||||||
setRefreshToken(data.refresh_token);
|
|
||||||
return data.access_token;
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
isRefreshing = false;
|
|
||||||
refreshPromise = null;
|
|
||||||
}
|
}
|
||||||
})();
|
|
||||||
|
|
||||||
return refreshPromise;
|
const data = await response.json();
|
||||||
|
if (isDesktopClient() && data.access_token) {
|
||||||
|
setBearerToken(data.access_token);
|
||||||
|
if (data.refresh_token) {
|
||||||
|
setRefreshToken(data.refresh_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data.access_token ?? null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function refreshSession(): Promise<string | null> {
|
||||||
|
if (typeof navigator !== "undefined" && "locks" in navigator) {
|
||||||
|
return navigator.locks.request("ss-token-refresh", () => doRefreshSession());
|
||||||
|
}
|
||||||
|
return doRefreshSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function refreshAccessToken(): Promise<string | null> {
|
||||||
|
return refreshSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -321,6 +356,7 @@ export async function authenticatedFetch(
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
...fetchOptions,
|
...fetchOptions,
|
||||||
headers,
|
headers,
|
||||||
|
credentials: "include",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle 401 Unauthorized
|
// Handle 401 Unauthorized
|
||||||
|
|
@ -337,6 +373,7 @@ export async function authenticatedFetch(
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
...fetchOptions,
|
...fetchOptions,
|
||||||
headers: retryHeaders,
|
headers: retryHeaders,
|
||||||
|
credentials: "include",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue