mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 19:33:11 +02:00
Prettier 2: Electric Boogaloo (#6113)
# Description of Changes When I added Prettier formatting in #6052, my aim was to use just the default settings in Prettier. Turns out, Prettier looks _really hard_ for any config files if it's not explicitly given one, which means that if a developer has some sort of Prettier config file lying around on their system, Prettier might find it and use it. Also, Prettier changes its defaults based on stuff in `.editorconfig` without any good way of disabling that behaviour explicitly in its config file. To solve both of these issues, I've introduced a `.prettierrc` file which sets Prettier's defaults explicitly, and then reformatted all our code _again_ in Prettier's actual default settings. This should achieve the aim of #6052 and remove the possibility for it breaking on different dev computers.
This commit is contained in:
@@ -1,7 +1,19 @@
|
||||
import { createContext, useContext, useEffect, useState, ReactNode, useCallback } from "react";
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import { springAuth } from "@app/auth/springAuthClient";
|
||||
import { clearPlatformAuthOnLoginInit } from "@app/extensions/authSessionCleanup";
|
||||
import type { Session, User, AuthError, AuthChangeEvent } from "@app/auth/springAuthClient";
|
||||
import type {
|
||||
Session,
|
||||
User,
|
||||
AuthError,
|
||||
AuthChangeEvent,
|
||||
} from "@app/auth/springAuthClient";
|
||||
|
||||
/**
|
||||
* Auth Context Type
|
||||
@@ -54,7 +66,9 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
console.debug("[Auth] refreshSession: start", { path: window.location.pathname });
|
||||
console.debug("[Auth] refreshSession: start", {
|
||||
path: window.location.pathname,
|
||||
});
|
||||
console.debug("[Auth] Refreshing session...");
|
||||
|
||||
const { data, error } = await springAuth.refreshSession();
|
||||
@@ -110,9 +124,14 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const initializeAuth = async () => {
|
||||
try {
|
||||
console.debug(`[Auth:${mountId}] Initializing auth...`);
|
||||
console.debug(`[Auth:${mountId}] Path: ${window.location.pathname} Search: ${window.location.search}`);
|
||||
console.debug(
|
||||
`[Auth:${mountId}] Path: ${window.location.pathname} Search: ${window.location.search}`,
|
||||
);
|
||||
// Clear any platform-specific cached auth on login page init.
|
||||
if (typeof window !== "undefined" && window.location.pathname.startsWith("/login")) {
|
||||
if (
|
||||
typeof window !== "undefined" &&
|
||||
window.location.pathname.startsWith("/login")
|
||||
) {
|
||||
await clearPlatformAuthOnLoginInit();
|
||||
}
|
||||
|
||||
@@ -134,12 +153,17 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
setSession(data.session);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[Auth] Unexpected error during auth initialization:", err);
|
||||
console.error(
|
||||
"[Auth] Unexpected error during auth initialization:",
|
||||
err,
|
||||
);
|
||||
if (mounted) {
|
||||
setError(err as AuthError);
|
||||
}
|
||||
} finally {
|
||||
console.debug(`[Auth:${mountId}] Initialize auth complete. mounted=${mounted}`);
|
||||
console.debug(
|
||||
`[Auth:${mountId}] Initialize auth complete. mounted=${mounted}`,
|
||||
);
|
||||
if (mounted) {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -152,8 +176,12 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const handleJwtAvailable = () => {
|
||||
console.log(`[Auth:${mountId}] ════════════════════════════════════`);
|
||||
console.log(`[Auth:${mountId}] 🔄 JWT available event received`);
|
||||
console.log(`[Auth:${mountId}] Current state: loading=${loading}, hasSession=${!!session}`);
|
||||
console.log(`[Auth:${mountId}] Setting loading=true to stabilize auth state`);
|
||||
console.log(
|
||||
`[Auth:${mountId}] Current state: loading=${loading}, hasSession=${!!session}`,
|
||||
);
|
||||
console.log(
|
||||
`[Auth:${mountId}] Setting loading=true to stabilize auth state`,
|
||||
);
|
||||
setLoading(true); // Prevent unstable renders during auth state transition
|
||||
setError(null);
|
||||
console.log(`[Auth:${mountId}] Refreshing session...`);
|
||||
@@ -165,40 +193,52 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
// Subscribe to auth state changes
|
||||
const {
|
||||
data: { subscription },
|
||||
} = springAuth.onAuthStateChange(async (event: AuthChangeEvent, newSession: Session | null) => {
|
||||
if (!mounted) {
|
||||
console.log(`[Auth:${mountId}] ⚠️ Auth state change ignored (unmounted): ${event}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[Auth:${mountId}] ════════════════════════════════════`);
|
||||
console.log(`[Auth:${mountId}] 📢 Auth state change event: ${event}`);
|
||||
console.log(`[Auth:${mountId}] Has session: ${!!newSession}`);
|
||||
console.log(`[Auth:${mountId}] User: ${newSession?.user?.email || "none"}`);
|
||||
console.log(`[Auth:${mountId}] Timestamp: ${new Date().toISOString()}`);
|
||||
|
||||
// Schedule state update
|
||||
setTimeout(() => {
|
||||
if (mounted) {
|
||||
console.log(`[Auth:${mountId}] Applying session update (event: ${event})`);
|
||||
setSession(newSession);
|
||||
setError(null);
|
||||
|
||||
// Handle specific events
|
||||
if (event === "SIGNED_OUT") {
|
||||
console.log(`[Auth:${mountId}] ✓ User signed out, session cleared`);
|
||||
} else if (event === "SIGNED_IN") {
|
||||
console.log(`[Auth:${mountId}] ✓ User signed in successfully`);
|
||||
} else if (event === "TOKEN_REFRESHED") {
|
||||
console.log(`[Auth:${mountId}] ✓ Token refreshed`);
|
||||
} else if (event === "USER_UPDATED") {
|
||||
console.log(`[Auth:${mountId}] ✓ User updated`);
|
||||
}
|
||||
} else {
|
||||
console.log(`[Auth:${mountId}] ⚠️ Session update skipped (unmounted during timeout)`);
|
||||
} = springAuth.onAuthStateChange(
|
||||
async (event: AuthChangeEvent, newSession: Session | null) => {
|
||||
if (!mounted) {
|
||||
console.log(
|
||||
`[Auth:${mountId}] ⚠️ Auth state change ignored (unmounted): ${event}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
|
||||
console.log(`[Auth:${mountId}] ════════════════════════════════════`);
|
||||
console.log(`[Auth:${mountId}] 📢 Auth state change event: ${event}`);
|
||||
console.log(`[Auth:${mountId}] Has session: ${!!newSession}`);
|
||||
console.log(
|
||||
`[Auth:${mountId}] User: ${newSession?.user?.email || "none"}`,
|
||||
);
|
||||
console.log(`[Auth:${mountId}] Timestamp: ${new Date().toISOString()}`);
|
||||
|
||||
// Schedule state update
|
||||
setTimeout(() => {
|
||||
if (mounted) {
|
||||
console.log(
|
||||
`[Auth:${mountId}] Applying session update (event: ${event})`,
|
||||
);
|
||||
setSession(newSession);
|
||||
setError(null);
|
||||
|
||||
// Handle specific events
|
||||
if (event === "SIGNED_OUT") {
|
||||
console.log(
|
||||
`[Auth:${mountId}] ✓ User signed out, session cleared`,
|
||||
);
|
||||
} else if (event === "SIGNED_IN") {
|
||||
console.log(`[Auth:${mountId}] ✓ User signed in successfully`);
|
||||
} else if (event === "TOKEN_REFRESHED") {
|
||||
console.log(`[Auth:${mountId}] ✓ Token refreshed`);
|
||||
} else if (event === "USER_UPDATED") {
|
||||
console.log(`[Auth:${mountId}] ✓ User updated`);
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
`[Auth:${mountId}] ⚠️ Session update skipped (unmounted during timeout)`,
|
||||
);
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
console.log(`[Auth:${mountId}] 🔴 AuthProvider unmounting`);
|
||||
|
||||
@@ -2,7 +2,11 @@ import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
|
||||
import { springAuth } from "@app/auth/springAuthClient";
|
||||
import { startOAuthNavigation } from "@app/extensions/oauthNavigation";
|
||||
import apiClient from "@app/services/apiClient";
|
||||
import { AxiosError, type AxiosResponse, type InternalAxiosRequestConfig } from "axios";
|
||||
import {
|
||||
AxiosError,
|
||||
type AxiosResponse,
|
||||
type InternalAxiosRequestConfig,
|
||||
} from "axios";
|
||||
|
||||
// Mock apiClient
|
||||
vi.mock("@app/services/apiClient");
|
||||
@@ -64,13 +68,19 @@ describe("SpringAuthClient", () => {
|
||||
const mockToken = "invalid-jwt-token";
|
||||
localStorage.setItem("stirling_jwt", mockToken);
|
||||
|
||||
const mockError = new AxiosError("Unauthorized", "ERR_BAD_REQUEST", undefined, undefined, {
|
||||
status: 401,
|
||||
statusText: "Unauthorized",
|
||||
data: {},
|
||||
headers: {},
|
||||
config: {} as InternalAxiosRequestConfig,
|
||||
});
|
||||
const mockError = new AxiosError(
|
||||
"Unauthorized",
|
||||
"ERR_BAD_REQUEST",
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
status: 401,
|
||||
statusText: "Unauthorized",
|
||||
data: {},
|
||||
headers: {},
|
||||
config: {} as InternalAxiosRequestConfig,
|
||||
},
|
||||
);
|
||||
|
||||
vi.mocked(apiClient.get).mockRejectedValueOnce(mockError);
|
||||
|
||||
@@ -86,13 +96,19 @@ describe("SpringAuthClient", () => {
|
||||
const mockToken = "forbidden-jwt-token";
|
||||
localStorage.setItem("stirling_jwt", mockToken);
|
||||
|
||||
const mockError = new AxiosError("Forbidden", "ERR_BAD_REQUEST", undefined, undefined, {
|
||||
status: 403,
|
||||
statusText: "Forbidden",
|
||||
data: {},
|
||||
headers: {},
|
||||
config: {} as InternalAxiosRequestConfig,
|
||||
});
|
||||
const mockError = new AxiosError(
|
||||
"Forbidden",
|
||||
"ERR_BAD_REQUEST",
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
status: 403,
|
||||
statusText: "Forbidden",
|
||||
data: {},
|
||||
headers: {},
|
||||
config: {} as InternalAxiosRequestConfig,
|
||||
},
|
||||
);
|
||||
|
||||
vi.mocked(apiClient.get).mockRejectedValueOnce(mockError);
|
||||
|
||||
@@ -145,7 +161,9 @@ describe("SpringAuthClient", () => {
|
||||
{ withCredentials: true },
|
||||
);
|
||||
expect(localStorage.getItem("stirling_jwt")).toBe(mockToken);
|
||||
expect(dispatchEventSpy).toHaveBeenCalledWith(expect.objectContaining({ type: "jwt-available" }));
|
||||
expect(dispatchEventSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ type: "jwt-available" }),
|
||||
);
|
||||
expect(result.user).toEqual(mockUser);
|
||||
expect(result.session?.access_token).toBe(mockToken);
|
||||
expect(result.error).toBeNull();
|
||||
@@ -336,7 +354,9 @@ describe("SpringAuthClient", () => {
|
||||
options: { redirectTo: "/auth/callback" },
|
||||
});
|
||||
|
||||
expect(startOAuthNavigation).toHaveBeenCalledWith("/oauth2/authorization/github");
|
||||
expect(startOAuthNavigation).toHaveBeenCalledWith(
|
||||
"/oauth2/authorization/github",
|
||||
);
|
||||
expect(mockAssign).toHaveBeenCalledWith("/oauth2/authorization/github");
|
||||
expect(result.error).toBeNull();
|
||||
});
|
||||
@@ -355,7 +375,9 @@ describe("SpringAuthClient", () => {
|
||||
options: { redirectTo: "/auth/callback" },
|
||||
});
|
||||
|
||||
expect(startOAuthNavigation).toHaveBeenCalledWith("/oauth2/authorization/github");
|
||||
expect(startOAuthNavigation).toHaveBeenCalledWith(
|
||||
"/oauth2/authorization/github",
|
||||
);
|
||||
expect(mockAssign).not.toHaveBeenCalled();
|
||||
expect(result.error).toBeNull();
|
||||
});
|
||||
|
||||
@@ -39,7 +39,12 @@ function getHttpStatus(error: unknown): number | undefined {
|
||||
// Helper to extract error message from axios error
|
||||
function getErrorMessage(error: unknown, fallback: string): string {
|
||||
if (error instanceof AxiosError) {
|
||||
return error.response?.data?.error || error.response?.data?.message || error.message || fallback;
|
||||
return (
|
||||
error.response?.data?.error ||
|
||||
error.response?.data?.message ||
|
||||
error.message ||
|
||||
fallback
|
||||
);
|
||||
}
|
||||
return error instanceof Error ? error.message : fallback;
|
||||
}
|
||||
@@ -108,9 +113,16 @@ export interface AuthResponse {
|
||||
error: AuthError | null;
|
||||
}
|
||||
|
||||
export type AuthChangeEvent = "SIGNED_IN" | "SIGNED_OUT" | "TOKEN_REFRESHED" | "USER_UPDATED";
|
||||
export type AuthChangeEvent =
|
||||
| "SIGNED_IN"
|
||||
| "SIGNED_OUT"
|
||||
| "TOKEN_REFRESHED"
|
||||
| "USER_UPDATED";
|
||||
|
||||
type AuthChangeCallback = (event: AuthChangeEvent, session: Session | null) => void;
|
||||
type AuthChangeCallback = (
|
||||
event: AuthChangeEvent,
|
||||
session: Session | null,
|
||||
) => void;
|
||||
|
||||
class SpringAuthClient {
|
||||
private listeners: AuthChangeCallback[] = [];
|
||||
@@ -138,7 +150,9 @@ class SpringAuthClient {
|
||||
try {
|
||||
const payload = this.decodeJwtPayload(token);
|
||||
if (!payload) {
|
||||
console.warn("[SpringAuth] Cannot decode token for adaptive intervals, using defaults");
|
||||
console.warn(
|
||||
"[SpringAuth] Cannot decode token for adaptive intervals, using defaults",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -146,7 +160,9 @@ class SpringAuthClient {
|
||||
const iatSeconds = typeof payload?.iat === "number" ? payload.iat : 0;
|
||||
|
||||
if (expSeconds <= 0 || iatSeconds <= 0) {
|
||||
console.warn("[SpringAuth] Token missing exp/iat claims, using default intervals");
|
||||
console.warn(
|
||||
"[SpringAuth] Token missing exp/iat claims, using default intervals",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -155,12 +171,18 @@ class SpringAuthClient {
|
||||
// Check interval: check 6 times during token lifetime
|
||||
// Min: 5 seconds (for very short tokens)
|
||||
// Max: 60 seconds (don't check too infrequently)
|
||||
this.sessionCheckIntervalMs = Math.max(5000, Math.min(60000, tokenLifetimeMs / 6));
|
||||
this.sessionCheckIntervalMs = Math.max(
|
||||
5000,
|
||||
Math.min(60000, tokenLifetimeMs / 6),
|
||||
);
|
||||
|
||||
// Refresh threshold: refresh when 25% of lifetime remaining
|
||||
// Min: 30 seconds (give buffer for refresh to complete)
|
||||
// Max: 5 minutes (don't wait too long for long-lived tokens)
|
||||
this.tokenRefreshThresholdMs = Math.max(30000, Math.min(300000, tokenLifetimeMs / 4));
|
||||
this.tokenRefreshThresholdMs = Math.max(
|
||||
30000,
|
||||
Math.min(300000, tokenLifetimeMs / 4),
|
||||
);
|
||||
|
||||
console.log("[SpringAuth] 📊 Adaptive intervals calculated:", {
|
||||
tokenLifetime: Math.floor(tokenLifetimeMs / 1000) + "s",
|
||||
@@ -171,7 +193,10 @@ class SpringAuthClient {
|
||||
// Restart monitoring with new interval
|
||||
this.restartSessionMonitoring();
|
||||
} catch (error) {
|
||||
console.warn("[SpringAuth] Failed to calculate adaptive intervals:", error);
|
||||
console.warn(
|
||||
"[SpringAuth] Failed to calculate adaptive intervals:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +215,10 @@ class SpringAuthClient {
|
||||
return JSON.parse(atob(base64));
|
||||
}
|
||||
|
||||
private getTokenExpiry(token: string): { expiresIn: number; expiresAt: number } {
|
||||
private getTokenExpiry(token: string): {
|
||||
expiresIn: number;
|
||||
expiresAt: number;
|
||||
} {
|
||||
try {
|
||||
const payload = this.decodeJwtPayload(token);
|
||||
if (!payload) {
|
||||
@@ -198,8 +226,12 @@ class SpringAuthClient {
|
||||
}
|
||||
|
||||
const expSeconds = typeof payload?.exp === "number" ? payload.exp : 0;
|
||||
const expiresAt = expSeconds > 0 ? expSeconds * 1000 : Date.now() + 3600 * 1000;
|
||||
const expiresIn = Math.max(0, Math.floor((expiresAt - Date.now()) / 1000));
|
||||
const expiresAt =
|
||||
expSeconds > 0 ? expSeconds * 1000 : Date.now() + 3600 * 1000;
|
||||
const expiresIn = Math.max(
|
||||
0,
|
||||
Math.floor((expiresAt - Date.now()) / 1000),
|
||||
);
|
||||
|
||||
return { expiresIn, expiresAt };
|
||||
} catch {
|
||||
@@ -227,7 +259,10 @@ class SpringAuthClient {
|
||||
* Get current session
|
||||
* JWT is stored in localStorage and sent via Authorization header
|
||||
*/
|
||||
async getSession(): Promise<{ data: { session: Session | null }; error: AuthError | null }> {
|
||||
async getSession(): Promise<{
|
||||
data: { session: Session | null };
|
||||
error: AuthError | null;
|
||||
}> {
|
||||
try {
|
||||
// Get JWT from localStorage
|
||||
let token = localStorage.getItem("stirling_jwt");
|
||||
@@ -265,7 +300,10 @@ class SpringAuthClient {
|
||||
|
||||
const session: Session = {
|
||||
user: {
|
||||
id: platformUser?.email || platformUser?.username || "desktop-saas-user",
|
||||
id:
|
||||
platformUser?.email ||
|
||||
platformUser?.username ||
|
||||
"desktop-saas-user",
|
||||
email: platformUser?.email || "",
|
||||
username: platformUser?.username || platformUser?.email || "User",
|
||||
role: "USER",
|
||||
@@ -334,7 +372,11 @@ class SpringAuthClient {
|
||||
/**
|
||||
* Sign in with email and password
|
||||
*/
|
||||
async signInWithPassword(credentials: { email: string; password: string; mfaCode?: string }): Promise<AuthResponse> {
|
||||
async signInWithPassword(credentials: {
|
||||
email: string;
|
||||
password: string;
|
||||
mfaCode?: string;
|
||||
}): Promise<AuthResponse> {
|
||||
try {
|
||||
const response = await apiClient.post(
|
||||
"/api/v1/auth/login",
|
||||
@@ -379,7 +421,11 @@ class SpringAuthClient {
|
||||
console.error("[SpringAuth] signInWithPassword error:", error);
|
||||
if (error instanceof AxiosError) {
|
||||
const errorCode = error.response?.data?.error as string | undefined;
|
||||
const errorMessage = error.response?.data?.message || error.response?.data?.error || error.message || "Login failed";
|
||||
const errorMessage =
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.error ||
|
||||
error.message ||
|
||||
"Login failed";
|
||||
return {
|
||||
user: null,
|
||||
session: null,
|
||||
@@ -462,7 +508,10 @@ class SpringAuthClient {
|
||||
return { error: null };
|
||||
} catch (error) {
|
||||
return {
|
||||
error: { message: error instanceof Error ? error.message : "SSO redirect failed" },
|
||||
error: {
|
||||
message:
|
||||
error instanceof Error ? error.message : "SSO redirect failed",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -473,7 +522,10 @@ class SpringAuthClient {
|
||||
async signOut(): Promise<{ error: AuthError | null }> {
|
||||
try {
|
||||
if (typeof window !== "undefined") {
|
||||
window.sessionStorage.setItem("stirling_sso_auto_login_logged_out", "1");
|
||||
window.sessionStorage.setItem(
|
||||
"stirling_sso_auto_login_logged_out",
|
||||
"1",
|
||||
);
|
||||
}
|
||||
const response = await apiClient.post("/api/v1/auth/logout", null, {
|
||||
headers: {
|
||||
@@ -496,14 +548,18 @@ class SpringAuthClient {
|
||||
// Clear any cached OAuth redirect/session state
|
||||
resetOAuthState();
|
||||
} catch (err) {
|
||||
console.warn("[SpringAuth] Failed to clear Supabase/local auth tokens", err);
|
||||
console.warn(
|
||||
"[SpringAuth] Failed to clear Supabase/local auth tokens",
|
||||
err,
|
||||
);
|
||||
}
|
||||
|
||||
// Clear cookies that might hold refresh/session tokens
|
||||
try {
|
||||
document.cookie.split(";").forEach((cookie) => {
|
||||
const eqPos = cookie.indexOf("=");
|
||||
const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
|
||||
const name =
|
||||
eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
|
||||
if (name) {
|
||||
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;`;
|
||||
}
|
||||
@@ -515,7 +571,10 @@ class SpringAuthClient {
|
||||
try {
|
||||
await clearPlatformAuthAfterSignOut();
|
||||
} catch (cleanupError) {
|
||||
console.warn("[SpringAuth] Failed to run platform auth cleanup", cleanupError);
|
||||
console.warn(
|
||||
"[SpringAuth] Failed to run platform auth cleanup",
|
||||
cleanupError,
|
||||
);
|
||||
}
|
||||
|
||||
// Notify listeners
|
||||
@@ -529,7 +588,10 @@ class SpringAuthClient {
|
||||
try {
|
||||
await clearPlatformAuthAfterSignOut();
|
||||
} catch (cleanupError) {
|
||||
console.warn("[SpringAuth] Failed to run platform auth cleanup after error", cleanupError);
|
||||
console.warn(
|
||||
"[SpringAuth] Failed to run platform auth cleanup after error",
|
||||
cleanupError,
|
||||
);
|
||||
}
|
||||
return {
|
||||
error: { message: getErrorMessage(error, "Logout failed") },
|
||||
@@ -540,7 +602,10 @@ class SpringAuthClient {
|
||||
/**
|
||||
* Refresh JWT token
|
||||
*/
|
||||
async refreshSession(): Promise<{ data: { session: Session | null }; error: AuthError | null }> {
|
||||
async refreshSession(): Promise<{
|
||||
data: { session: Session | null };
|
||||
error: AuthError | null;
|
||||
}> {
|
||||
try {
|
||||
if (await isDesktopSaaSAuthMode()) {
|
||||
const refreshed = await refreshPlatformSession();
|
||||
@@ -556,7 +621,9 @@ class SpringAuthClient {
|
||||
if (error || !data.session) {
|
||||
return {
|
||||
data: { session: null },
|
||||
error: error || { message: "Token refresh failed - please log in again" },
|
||||
error: error || {
|
||||
message: "Token refresh failed - please log in again",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -610,7 +677,10 @@ class SpringAuthClient {
|
||||
// Handle different error statuses
|
||||
const status = getHttpStatus(error);
|
||||
if (status === 401 || status === 403) {
|
||||
return { data: { session: null }, error: { message: "Token refresh failed - please log in again" } };
|
||||
return {
|
||||
data: { session: null },
|
||||
error: { message: "Token refresh failed - please log in again" },
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -623,7 +693,9 @@ class SpringAuthClient {
|
||||
/**
|
||||
* Listen to auth state changes
|
||||
*/
|
||||
onAuthStateChange(callback: AuthChangeCallback): { data: { subscription: { unsubscribe: () => void } } } {
|
||||
onAuthStateChange(callback: AuthChangeCallback): {
|
||||
data: { subscription: { unsubscribe: () => void } };
|
||||
} {
|
||||
this.listeners.push(callback);
|
||||
|
||||
return {
|
||||
@@ -646,7 +718,10 @@ class SpringAuthClient {
|
||||
try {
|
||||
callback(event, session);
|
||||
} catch (error) {
|
||||
console.error("[SpringAuth] Error in auth state change listener:", error);
|
||||
console.error(
|
||||
"[SpringAuth] Error in auth state change listener:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
@@ -665,9 +740,14 @@ class SpringAuthClient {
|
||||
const timeUntilExpiry = (data.session.expires_at || 0) - Date.now();
|
||||
|
||||
// Refresh if token expires soon (threshold is adaptive)
|
||||
if (timeUntilExpiry > 0 && timeUntilExpiry < this.tokenRefreshThresholdMs) {
|
||||
if (
|
||||
timeUntilExpiry > 0 &&
|
||||
timeUntilExpiry < this.tokenRefreshThresholdMs
|
||||
) {
|
||||
console.log(
|
||||
"[SpringAuth] 🔄 Proactively refreshing token (expires in " + Math.floor(timeUntilExpiry / 1000) + "s)",
|
||||
"[SpringAuth] 🔄 Proactively refreshing token (expires in " +
|
||||
Math.floor(timeUntilExpiry / 1000) +
|
||||
"s)",
|
||||
);
|
||||
await this.refreshSession();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user