More any type fixes

This commit is contained in:
James Brunton
2025-11-06 12:22:08 +00:00
parent 06c6b96992
commit aaf6c30413
15 changed files with 260 additions and 107 deletions

View File

@@ -1,22 +1,32 @@
import { ToastOptions } from '@app/components/toast/types';
import { ToastOptions, ToastApi } from '@app/components/toast/types';
import { useToast, ToastProvider } from '@app/components/toast/ToastContext';
import ToastRenderer from '@app/components/toast/ToastRenderer';
export { useToast, ToastProvider, ToastRenderer };
// Global imperative API via module singleton
let _api: ReturnType<typeof createImperativeApi> | null = null;
type ToastContextInstance = ReturnType<typeof useToast>;
function createImperativeApi() {
const subscribers: Array<(fn: any) => void> = [];
let api: any = null;
interface ImperativeApi {
provide(instance: ToastContextInstance): void;
get(): ToastContextInstance | null;
onReady(cb: (api: ToastContextInstance) => void): void;
}
// Global imperative API via module singleton
let _api: ImperativeApi | null = null;
function createImperativeApi(): ImperativeApi {
const subscribers: Array<(api: ToastContextInstance) => void> = [];
let api: ToastContextInstance | null = null;
return {
provide(instance: any) {
provide(instance: ToastContextInstance) {
api = instance;
subscribers.splice(0).forEach(cb => cb(api));
const queued = [...subscribers];
subscribers.length = 0;
queued.forEach(cb => cb(instance));
},
get(): any | null { return api; },
onReady(cb: (api: any) => void) {
get(): ToastContextInstance | null { return api; },
onReady(cb: (readyApi: ToastContextInstance) => void) {
if (api) cb(api); else subscribers.push(cb);
}
};
@@ -32,30 +42,33 @@ export function ToastPortalBinder() {
return null;
}
export function alert(options: ToastOptions) {
if (_api?.get()) {
return _api.get()!.show(options);
function getImperativeApi(): ToastApi | null {
return _api?.get() ?? null;
}
export function alert(options: ToastOptions): string {
const api = getImperativeApi();
if (api) {
return api.show(options);
}
// Queue until provider mounts
let id = '';
_api?.onReady((api) => { id = api.show(options); });
_api?.onReady((readyApi) => { id = readyApi.show(options); });
return id;
}
export function updateToast(id: string, options: Partial<ToastOptions>) {
_api?.get()?.update(id, options);
export function updateToast(id: string, options: Partial<ToastOptions>): void {
getImperativeApi()?.update(id, options);
}
export function updateToastProgress(id: string, progress: number) {
_api?.get()?.updateProgress(id, progress);
export function updateToastProgress(id: string, progress: number): void {
getImperativeApi()?.updateProgress(id, progress);
}
export function dismissToast(id: string) {
_api?.get()?.dismiss(id);
export function dismissToast(id: string): void {
getImperativeApi()?.dismiss(id);
}
export function dismissAllToasts() {
_api?.get()?.dismissAll();
export function dismissAllToasts(): void {
getImperativeApi()?.dismissAll();
}

View File

@@ -4,17 +4,23 @@ import { AdjustContrastParameters, defaultParameters } from '@app/hooks/tools/ad
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
import { applyAdjustmentsToCanvas } from '@app/components/tools/adjustContrast/utils';
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
import { createFileFromApiResponse } from '@app/utils/fileResponseUtils';
import { createFileFromApiResponse, toArrayBuffer } from '@app/utils/fileResponseUtils';
import type { PDFDocumentProxy, PDFPageProxy, RenderParameters } from 'pdfjs-dist/types/src/display/api';
async function renderPdfPageToCanvas(pdf: any, pageNumber: number, scale: number): Promise<HTMLCanvasElement> {
const page = await pdf.getPage(pageNumber);
async function renderPdfPageToCanvas(pdf: PDFDocumentProxy, pageNumber: number, scale: number): Promise<HTMLCanvasElement> {
const page: PDFPageProxy = await pdf.getPage(pageNumber);
const viewport = page.getViewport({ scale });
const canvas = document.createElement('canvas');
canvas.width = viewport.width;
canvas.height = viewport.height;
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('Canvas 2D context unavailable');
await page.render({ canvasContext: ctx, viewport }).promise;
const renderConfig: RenderParameters = {
canvasContext: ctx,
viewport,
canvas,
};
await page.render(renderConfig).promise;
return canvas;
}
@@ -41,7 +47,11 @@ async function buildAdjustedPdfForFile(file: File, params: AdjustContrastParamet
}
const pdfBytes = await newDoc.save();
const out = createFileFromApiResponse(pdfBytes, { 'content-type': 'application/pdf' }, file.name);
const out = createFileFromApiResponse(
toArrayBuffer(pdfBytes),
{ 'content-type': 'application/pdf' },
file.name
);
pdfWorkerManager.destroyDocument(pdf);
return out;
}
@@ -91,4 +101,3 @@ export const useAdjustContrastOperation = () => {
getErrorMessage: () => t('adjustContrast.error.failed', 'Failed to adjust colors/contrast')
});
};

View File

@@ -2,6 +2,7 @@ import { useState, useCallback } from 'react';
import { PDFDocument, PDFPage } from '@app/types/pageEditor';
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
import { createQuickKey } from '@app/types/fileContext';
import type { PDFDocumentProxy, RenderParameters } from 'pdfjs-dist/types/src/display/api';
export function usePDFProcessor() {
const [loading, setLoading] = useState(false);
@@ -27,7 +28,12 @@ export function usePDFProcessor() {
throw new Error('Could not get canvas context');
}
await page.render({ canvasContext: context, viewport, canvas }).promise;
const renderConfig: RenderParameters = {
canvasContext: context,
viewport,
canvas,
};
await page.render(renderConfig).promise;
const thumbnail = canvas.toDataURL();
// Clean up using worker manager
@@ -42,7 +48,7 @@ export function usePDFProcessor() {
// Internal function to generate thumbnail from already-opened PDF
const generateThumbnailFromPDF = useCallback(async (
pdf: any,
pdf: PDFDocumentProxy,
pageNumber: number,
scale: number = 0.5
): Promise<string> => {
@@ -58,7 +64,12 @@ export function usePDFProcessor() {
throw new Error('Could not get canvas context');
}
await page.render({ canvasContext: context, viewport }).promise;
const renderConfig: RenderParameters = {
canvasContext: context,
viewport,
canvas,
};
await page.render(renderConfig).promise;
return canvas.toDataURL();
}, []);

View File

@@ -2,13 +2,15 @@
* Service for managing automation configurations in IndexedDB
*/
import type { AutomationParameters } from '@app/types/automation';
export interface AutomationConfig {
id: string;
name: string;
description?: string;
operations: Array<{
operation: string;
parameters: any;
parameters: AutomationParameters;
}>;
createdAt: string;
updatedAt: string;
@@ -180,4 +182,4 @@ class AutomationStorage {
}
// Export singleton instance
export const automationStorage = new AutomationStorage();
export const automationStorage = new AutomationStorage();

View File

@@ -5,6 +5,7 @@ import { FileAnalyzer } from '@app/services/fileAnalyzer';
import { ProcessingErrorHandler } from '@app/services/processingErrorHandler';
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
import { createQuickKey } from '@app/types/fileContext';
import type { PDFPageProxy, RenderParameters } from 'pdfjs-dist/types/src/display/api';
export class EnhancedPDFProcessingService {
private static instance: EnhancedPDFProcessingService;
@@ -400,7 +401,7 @@ export class EnhancedPDFProcessingService {
/**
* Render a page thumbnail with specified quality
*/
private async renderPageThumbnail(page: any, quality: 'low' | 'medium' | 'high'): Promise<string> {
private async renderPageThumbnail(page: PDFPageProxy, quality: 'low' | 'medium' | 'high'): Promise<string> {
const scales = { low: 0.2, medium: 0.5, high: 0.8 }; // Reduced low quality for page editor
const scale = scales[quality];
@@ -414,7 +415,12 @@ export class EnhancedPDFProcessingService {
throw new Error('Could not get canvas context');
}
await page.render({ canvasContext: context, viewport }).promise;
const renderConfig: RenderParameters = {
canvasContext: context,
viewport,
canvas,
};
await page.render(renderConfig).promise;
return canvas.toDataURL('image/jpeg', 0.8); // Use JPEG for better compression
}

View File

@@ -4,7 +4,7 @@
import { FileId } from '@app/types/file';
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
import { PDFDocumentProxy } from 'pdfjs-dist';
import type { PDFDocumentProxy, RenderParameters } from 'pdfjs-dist/types/src/display/api';
interface ThumbnailResult {
pageNumber: number;
@@ -49,7 +49,7 @@ export class ThumbnailGenerationService {
/**
* Get or create a cached PDF document
*/
private async getCachedPDFDocument(fileId: FileId, pdfArrayBuffer: ArrayBuffer): Promise<any> {
private async getCachedPDFDocument(fileId: FileId, pdfArrayBuffer: ArrayBuffer): Promise<PDFDocumentProxy> {
const cached = this.pdfDocumentCache.get(fileId);
if (cached) {
cached.lastUsed = Date.now();
@@ -176,7 +176,12 @@ export class ThumbnailGenerationService {
throw new Error('Could not get canvas context');
}
await page.render({ canvasContext: context, viewport }).promise;
const renderConfig: RenderParameters = {
canvasContext: context,
viewport,
canvas,
};
await page.render(renderConfig).promise;
const thumbnail = canvas.toDataURL('image/jpeg', quality);
allResults.push({ pageNumber, thumbnail, success: true });

View File

@@ -1,4 +1,5 @@
import apiClient from '@app/services/apiClient';
import type { AxiosRequestConfig } from 'axios';
export interface User {
id: number;
@@ -29,10 +30,17 @@ export interface AdminSettingsData {
disabledUsers: number;
currentUsername?: string;
roleDetails?: Record<string, string>;
teams?: any[];
teams?: TeamSummary[];
maxPaidUsers?: number;
}
export interface TeamSummary {
id: number;
name: string;
memberCount?: number;
[key: string]: unknown;
}
export interface CreateUserRequest {
username: string;
password?: string;
@@ -100,9 +108,10 @@ export const userManagementService = {
if (data.forceChange !== undefined) {
formData.append('forceChange', data.forceChange.toString());
}
await apiClient.post('/api/v1/user/admin/saveUser', formData, {
suppressErrorToast: true, // Component will handle error display
} as any);
const config: AxiosRequestConfig & { suppressErrorToast?: boolean } = {
suppressErrorToast: true,
};
await apiClient.post('/api/v1/user/admin/saveUser', formData, config);
},
/**
@@ -115,9 +124,10 @@ export const userManagementService = {
if (data.teamId) {
formData.append('teamId', data.teamId.toString());
}
await apiClient.post('/api/v1/user/admin/changeRole', formData, {
const config: AxiosRequestConfig & { suppressErrorToast?: boolean } = {
suppressErrorToast: true,
} as any);
};
await apiClient.post('/api/v1/user/admin/changeRole', formData, config);
},
/**
@@ -126,18 +136,20 @@ export const userManagementService = {
async toggleUserEnabled(username: string, enabled: boolean): Promise<void> {
const formData = new FormData();
formData.append('enabled', enabled.toString());
await apiClient.post(`/api/v1/user/admin/changeUserEnabled/${username}`, formData, {
const config: AxiosRequestConfig & { suppressErrorToast?: boolean } = {
suppressErrorToast: true,
} as any);
};
await apiClient.post(`/api/v1/user/admin/changeUserEnabled/${username}`, formData, config);
},
/**
* Delete a user (admin only)
*/
async deleteUser(username: string): Promise<void> {
await apiClient.post(`/api/v1/user/admin/deleteUser/${username}`, null, {
const config: AxiosRequestConfig & { suppressErrorToast?: boolean } = {
suppressErrorToast: true,
} as any);
};
await apiClient.post(`/api/v1/user/admin/deleteUser/${username}`, null, config);
},
/**
@@ -153,12 +165,14 @@ export const userManagementService = {
formData.append('teamId', data.teamId.toString());
}
const config: AxiosRequestConfig & { suppressErrorToast?: boolean } = {
suppressErrorToast: true,
};
const response = await apiClient.post<InviteUsersResponse>(
'/api/v1/user/admin/inviteUsers',
formData,
{
suppressErrorToast: true, // Component will handle error display
} as any
config
);
return response.data;

View File

@@ -2,6 +2,7 @@ import JSZip, { JSZipObject } from 'jszip';
import { StirlingFileStub, createStirlingFile } from '@app/types/fileContext';
import { generateThumbnailForFile } from '@app/utils/thumbnailUtils';
import { fileStorage } from '@app/services/fileStorage';
import { toArrayBuffer } from '@app/utils/fileResponseUtils';
// Undocumented interface in JSZip for JSZipObject._data
interface CompressedObject {
@@ -13,7 +14,8 @@ interface CompressedObject {
}
const getData = (zipEntry: JSZipObject): CompressedObject | undefined => {
return (zipEntry as any)._data as CompressedObject;
const candidate = zipEntry as JSZipObject & { _data?: CompressedObject };
return candidate._data;
};
export interface ZipExtractionResult {
@@ -216,7 +218,7 @@ export class ZipFileService {
const content = await zipEntry.async('uint8array');
// Create File object
const extractedFile = new File([content as any], this.sanitizeFilename(filename), {
const extractedFile = new File([toArrayBuffer(content)], this.sanitizeFilename(filename), {
type: 'application/pdf',
lastModified: zipEntry.date?.getTime() || Date.now()
});

View File

@@ -1,7 +1,7 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useFileSelection } from "@app/contexts/FileContext";
import { createToolFlow } from "@app/components/tools/shared/createToolFlow";
import { createToolFlow, type MiddleStepConfig } from "@app/components/tools/shared/createToolFlow";
import { BaseToolProps, ToolComponent } from "@app/types/tool";
import { useEndpointEnabled } from "@app/hooks/useEndpointConfig";
import { useAddAttachmentsParameters } from "@app/hooks/tools/addAttachments/useAddAttachmentsParameters";
@@ -29,8 +29,12 @@ const AddAttachments = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =
if (operation.files && onComplete) {
onComplete(operation.files);
}
} catch (error: any) {
onError?.(error?.message || t("AddAttachmentsRequest.error.failed", "Add attachments operation failed"));
} catch (error: unknown) {
const message =
error instanceof Error
? error.message
: t("AddAttachmentsRequest.error.failed", "Add attachments operation failed");
onError?.(message);
}
};
@@ -56,7 +60,7 @@ const AddAttachments = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =
});
const getSteps = () => {
const steps: any[] = [];
const steps: MiddleStepConfig[] = [];
// Step 1: Attachments Selection
steps.push({

View File

@@ -1,7 +1,7 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useFileSelection } from "@app/contexts/FileContext";
import { createToolFlow } from "@app/components/tools/shared/createToolFlow";
import { createToolFlow, type MiddleStepConfig } from "@app/components/tools/shared/createToolFlow";
import { BaseToolProps, ToolComponent } from "@app/types/tool";
import { useEndpointEnabled } from "@app/hooks/useEndpointConfig";
import { useAddPageNumbersParameters } from "@app/components/tools/addPageNumbers/useAddPageNumbersParameters";
@@ -30,8 +30,12 @@ const AddPageNumbers = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =
if (operation.files && onComplete) {
onComplete(operation.files);
}
} catch (error: any) {
onError?.(error?.message || t("addPageNumbers.error.failed", "Add page numbers operation failed"));
} catch (error: unknown) {
const message =
error instanceof Error
? error.message
: t("addPageNumbers.error.failed", "Add page numbers operation failed");
onError?.(message);
}
};
@@ -58,7 +62,7 @@ const AddPageNumbers = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =
});
const getSteps = () => {
const steps: any[] = [];
const steps: MiddleStepConfig[] = [];
// Step 1: Position Selection & Pages/Starting Number
steps.push({
@@ -123,4 +127,4 @@ const AddPageNumbers = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =
AddPageNumbers.tool = () => useAddPageNumbersOperation;
export default AddPageNumbers as ToolComponent;
export default AddPageNumbers as ToolComponent;

View File

@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useFileSelection } from "@app/contexts/FileContext";
import { createToolFlow } from "@app/components/tools/shared/createToolFlow";
import { createToolFlow, type MiddleStepConfig } from "@app/components/tools/shared/createToolFlow";
import { BaseToolProps, ToolComponent } from "@app/types/tool";
import { useEndpointEnabled } from "@app/hooks/useEndpointConfig";
import { useAddStampParameters } from "@app/components/tools/addStamp/useAddStampParameters";
@@ -39,8 +39,12 @@ const AddStamp = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
if (operation.files && onComplete) {
onComplete(operation.files);
}
} catch (error: any) {
onError?.(error?.message || t("AddStampRequest.error.failed", "Add stamp operation failed"));
} catch (error: unknown) {
const message =
error instanceof Error
? error.message
: t("AddStampRequest.error.failed", "Add stamp operation failed");
onError?.(message);
}
};
@@ -67,7 +71,7 @@ const AddStamp = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
});
const getSteps = () => {
const steps: any[] = [];
const steps: MiddleStepConfig[] = [];
// Step 1: Stamp Setup
steps.push({
@@ -185,4 +189,3 @@ AddStamp.tool = () => useAddStampOperation;
export default AddStamp as ToolComponent;

View File

@@ -1,6 +1,6 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { createToolFlow } from "@app/components/tools/shared/createToolFlow";
import { createToolFlow, type MiddleStepConfig } from "@app/components/tools/shared/createToolFlow";
import { BaseToolProps, ToolComponent } from "@app/types/tool";
import { useEndpointEnabled } from "@app/hooks/useEndpointConfig";
import { useFileSelection } from "@app/contexts/FileContext";
@@ -29,8 +29,12 @@ const ReorganizePages = ({ onPreviewFile, onComplete, onError }: BaseToolProps)
if (operation.files && onComplete) {
onComplete(operation.files);
}
} catch (error: any) {
onError?.(error?.message || t("reorganizePages.error.failed", "Failed to reorganize pages"));
} catch (error: unknown) {
const message =
error instanceof Error
? error.message
: t("reorganizePages.error.failed", "Failed to reorganize pages");
onError?.(message);
}
};
@@ -55,7 +59,7 @@ const ReorganizePages = ({ onPreviewFile, onComplete, onError }: BaseToolProps)
}
});
const steps = [
const steps: MiddleStepConfig[] = [
{
title: t("reorganizePages.settings.title", "Settings"),
isCollapsed: accordion.getCollapsedState(Step.SETTINGS),
@@ -97,8 +101,8 @@ const ReorganizePages = ({ onPreviewFile, onComplete, onError }: BaseToolProps)
});
};
(ReorganizePages as any).tool = () => useReorganizePagesOperation;
export default ReorganizePages as ToolComponent;
const ReorganizePagesTool = ReorganizePages as ToolComponent;
ReorganizePagesTool.tool = () => useReorganizePagesOperation;
export default ReorganizePagesTool;

View File

@@ -30,16 +30,58 @@ export const getFilenameFromHeaders = (contentDisposition: string = ''): string
* @param fallbackFilename - Filename to use if none provided in headers
* @returns File object
*/
type HeaderSource =
| Record<string, string | null | undefined>
| Headers
| Array<[string, string]>
| undefined
| null;
function getHeaderValue(headers: HeaderSource, key: string): string | undefined {
if (!headers) return undefined;
if (headers instanceof Headers) {
const value = headers.get(key);
return value === null ? undefined : value;
}
if (Array.isArray(headers)) {
const entry = headers.find(([headerKey]) => headerKey.toLowerCase() === key.toLowerCase());
return entry?.[1];
}
const value = headers[key];
return value === null || value === undefined ? undefined : value;
}
export const createFileFromApiResponse = (
responseData: any,
headers: any,
responseData: BlobPart,
headers: HeaderSource,
fallbackFilename: string
): File => {
const contentType = headers?.['content-type'] || 'application/octet-stream';
const contentDisposition = headers?.['content-disposition'] || '';
const contentType = getHeaderValue(headers, 'content-type') || 'application/octet-stream';
const contentDisposition = getHeaderValue(headers, 'content-disposition') || '';
const filename = getFilenameFromHeaders(contentDisposition) || fallbackFilename;
const blob = new Blob([responseData], { type: contentType });
return new File([blob], filename, { type: contentType });
};
export function toArrayBuffer(view: ArrayBufferView): ArrayBuffer {
const { buffer, byteOffset, byteLength } = view;
const start = byteOffset;
const end = byteOffset + byteLength;
if (typeof SharedArrayBuffer !== 'undefined' && buffer instanceof SharedArrayBuffer) {
const out = new ArrayBuffer(byteLength);
new Uint8Array(out).set(new Uint8Array(buffer, start, byteLength));
return out;
}
if (buffer instanceof ArrayBuffer && typeof buffer.slice === 'function') {
return buffer.slice(start, end);
}
const out = new ArrayBuffer(byteLength);
new Uint8Array(out).set(new Uint8Array(buffer, start, byteLength));
return out;
}

View File

@@ -1,4 +1,5 @@
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
import type { PDFDocumentProxy, RenderParameters } from 'pdfjs-dist/types/src/display/api';
export interface ThumbnailWithMetadata {
thumbnail: string; // Always returns a thumbnail (placeholder if needed)
@@ -256,7 +257,7 @@ function drawLargeLockIcon(ctx: CanvasRenderingContext2D, centerX: number, cente
/**
* Generate standard PDF thumbnail by rendering first page
*/
async function generateStandardPDFThumbnail(pdf: any, scale: number): Promise<string> {
async function generateStandardPDFThumbnail(pdf: PDFDocumentProxy, scale: number): Promise<string> {
const page = await pdf.getPage(1);
const viewport = page.getViewport({ scale });
const canvas = document.createElement("canvas");
@@ -268,7 +269,12 @@ async function generateStandardPDFThumbnail(pdf: any, scale: number): Promise<st
throw new Error('Could not get canvas context');
}
await page.render({ canvasContext: context, viewport }).promise;
const renderConfig: RenderParameters = {
canvasContext: context,
viewport,
canvas,
};
await page.render(renderConfig).promise;
return canvas.toDataURL();
}

View File

@@ -7,6 +7,7 @@
* - No email confirmation flow (auto-confirmed on registration)
*/
import axios from 'axios';
import apiClient from '@app/services/apiClient';
// Auth types
@@ -17,7 +18,7 @@ export interface User {
role: string;
enabled?: boolean;
is_anonymous?: boolean;
app_metadata?: Record<string, any>;
app_metadata?: Record<string, unknown>;
}
export interface Session {
@@ -46,6 +47,35 @@ export type AuthChangeEvent =
type AuthChangeCallback = (event: AuthChangeEvent, session: Session | null) => void;
type OAuthQueryParams = Record<string, string | number | boolean | null | undefined>;
interface ErrorResponsePayload {
message?: string;
error?: string;
[key: string]: unknown;
}
function extractAxiosErrorMessage(error: unknown, fallback: string): string {
if (axios.isAxiosError<ErrorResponsePayload>(error)) {
const data = error.response?.data;
if (data && typeof data === 'object') {
if (typeof data.message === 'string' && data.message) return data.message;
if (typeof data.error === 'string' && data.error) return data.error;
}
if (typeof error.message === 'string' && error.message) {
return error.message;
}
}
if (error instanceof Error) {
return error.message;
}
return fallback;
}
function getAxiosStatus(error: unknown): number | undefined {
return axios.isAxiosError(error) ? error.response?.status : undefined;
}
class SpringAuthClient {
private listeners: AuthChangeCallback[] = [];
private sessionCheckInterval: NodeJS.Timeout | null = null;
@@ -105,21 +135,22 @@ class SpringAuthClient {
console.debug('[SpringAuth] getSession: Session retrieved successfully');
return { data: { session }, error: null };
} catch (error: any) {
} catch (error: unknown) {
console.error('[SpringAuth] getSession error:', error);
// If 401/403, token is invalid - clear it
if (error?.response?.status === 401 || error?.response?.status === 403) {
localStorage.removeItem('stirling_jwt');
console.debug('[SpringAuth] getSession: Not authenticated');
return { data: { session: null }, error: null };
if (axios.isAxiosError(error)) {
const status = error.response?.status;
if (status === 401 || status === 403) {
localStorage.removeItem('stirling_jwt');
console.debug('[SpringAuth] getSession: Not authenticated');
return { data: { session: null }, error: null };
}
}
// Clear potentially invalid token on other errors too
localStorage.removeItem('stirling_jwt');
return {
data: { session: null },
error: { message: error?.response?.data?.message || error?.message || 'Unknown error' },
error: { message: extractAxiosErrorMessage(error, 'Unknown error') },
};
}
}
@@ -157,9 +188,9 @@ class SpringAuthClient {
this.notifyListeners('SIGNED_IN', session);
return { user: data.user, session, error: null };
} catch (error: any) {
} catch (error: unknown) {
console.error('[SpringAuth] signInWithPassword error:', error);
const errorMessage = error?.response?.data?.error || error?.message || 'Login failed';
const errorMessage = extractAxiosErrorMessage(error, 'Login failed');
return {
user: null,
session: null,
@@ -189,9 +220,9 @@ class SpringAuthClient {
// Note: Spring backend auto-confirms users (no email verification)
// Return user but no session (user needs to login)
return { user: data.user, session: null, error: null };
} catch (error: any) {
} catch (error: unknown) {
console.error('[SpringAuth] signUp error:', error);
const errorMessage = error?.response?.data?.error || error?.message || 'Registration failed';
const errorMessage = extractAxiosErrorMessage(error, 'Registration failed');
return {
user: null,
session: null,
@@ -206,7 +237,7 @@ class SpringAuthClient {
*/
async signInWithOAuth(params: {
provider: 'github' | 'google' | 'apple' | 'azure';
options?: { redirectTo?: string; queryParams?: Record<string, any> };
options?: { redirectTo?: string; queryParams?: OAuthQueryParams };
}): Promise<{ error: AuthError | null }> {
try {
const redirectUrl = `/oauth2/authorization/${params.provider}`;
@@ -233,13 +264,11 @@ class SpringAuthClient {
});
return { data: {}, error: null };
} catch (error: any) {
} catch (error: unknown) {
console.error('[SpringAuth] resetPasswordForEmail error:', error);
return {
data: {},
error: {
message: error?.response?.data?.error || error?.message || 'Password reset failed',
},
error: { message: extractAxiosErrorMessage(error, 'Password reset failed') },
};
}
}
@@ -267,12 +296,10 @@ class SpringAuthClient {
this.notifyListeners('SIGNED_OUT', null);
return { error: null };
} catch (error: any) {
} catch (error: unknown) {
console.error('[SpringAuth] signOut error:', error);
return {
error: {
message: error?.response?.data?.error || error?.message || 'Logout failed',
},
error: { message: extractAxiosErrorMessage(error, 'Logout failed') },
};
}
}
@@ -306,18 +333,19 @@ class SpringAuthClient {
this.notifyListeners('TOKEN_REFRESHED', session);
return { data: { session }, error: null };
} catch (error: any) {
} catch (error: unknown) {
console.error('[SpringAuth] refreshSession error:', error);
localStorage.removeItem('stirling_jwt');
// Handle different error statuses
if (error?.response?.status === 401 || error?.response?.status === 403) {
const status = getAxiosStatus(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: error?.response?.data?.message || error?.message || 'Token refresh failed' },
error: { message: extractAxiosErrorMessage(error, 'Token refresh failed') },
};
}
}