From cc9650e7a33bd62dfd5e6e64608965e557222f4f Mon Sep 17 00:00:00 2001 From: James Brunton Date: Mon, 20 Apr 2026 13:42:38 +0100 Subject: [PATCH] Fix `any` type usage in `desktop/` (#6033) # Description of Changes Follow on from #5949, expanding any type usage ban to the `desktop/` folder Also gets rid of a bunch of really verbose desktop logging that I don't think we really need anymore (or ever needed tbh, most of it doesn't make sense) because it was using a bunch of `any` typing and wasn't worth fixing. --- frontend/eslint.config.mjs | 1 + .../components/DesktopOnboardingModal.tsx | 4 +- .../SetupWizard/ServerSelection.tsx | 13 ++- .../shared/SelfHostedOfflineBanner.tsx | 2 +- .../tools/toolPicker/ToolButton.tsx | 8 +- .../src/desktop/services/apiClientConfig.ts | 7 +- .../desktop/services/connectionModeService.ts | 93 ++----------------- .../services/endpointAvailabilityService.ts | 15 +-- .../src/desktop/services/httpErrorHandler.ts | 5 +- .../desktop/services/localFileSaveService.ts | 2 +- .../desktop/services/saasErrorInterceptor.ts | 7 +- .../src/desktop/services/tauriHttpClient.ts | 8 +- frontend/src/global.d.ts | 2 + 13 files changed, 55 insertions(+), 112 deletions(-) diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs index 11fbbef99..a963558ec 100644 --- a/frontend/eslint.config.mjs +++ b/frontend/eslint.config.mjs @@ -78,6 +78,7 @@ export default defineConfig( // Folders that have been cleaned up and are now conformant - stricter rules enforced here { files: [ + "src/desktop/**/*.{js,mjs,jsx,ts,tsx}", "src/proprietary/**/*.{js,mjs,jsx,ts,tsx}", "src/saas/**/*.{js,mjs,jsx,ts,tsx}", "src/prototypes/**/*.{js,mjs,jsx,ts,tsx}", diff --git a/frontend/src/desktop/components/DesktopOnboardingModal.tsx b/frontend/src/desktop/components/DesktopOnboardingModal.tsx index de8bf31a7..0a07c8127 100644 --- a/frontend/src/desktop/components/DesktopOnboardingModal.tsx +++ b/frontend/src/desktop/components/DesktopOnboardingModal.tsx @@ -95,9 +95,9 @@ export function DesktopOnboardingModal() {
= ({ const [securityDisabled, setSecurityDisabled] = useState(false); const serverUrl = localStorage.getItem("server_url") || ""; - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - + const submitServerUrl = async () => { // Normalize and validate URL let url = customUrl.trim().replace(/\/+$/, "") || serverUrl; @@ -243,7 +241,12 @@ export const ServerSelection: React.FC = ({ }; return ( -
+ { + e.preventDefault(); + void submitServerUrl(); + }} + > = ({ setCustomUrl(serverUrl); // Auto-submit the form after setting the URL setTimeout(() => { - handleSubmit(new Event("submit") as any); + void submitServerUrl(); }, 0); }} > diff --git a/frontend/src/desktop/components/shared/SelfHostedOfflineBanner.tsx b/frontend/src/desktop/components/shared/SelfHostedOfflineBanner.tsx index c6b2eff63..ca75b03ec 100644 --- a/frontend/src/desktop/components/shared/SelfHostedOfflineBanner.tsx +++ b/frontend/src/desktop/components/shared/SelfHostedOfflineBanner.tsx @@ -140,7 +140,7 @@ export function SelfHostedOfflineBanner() { toolAvailability[id]?.reason === "selfHostedOffline", ) .map((id) => toolRegistry[id]?.name ?? id) - .filter(Boolean) as string[]; + .filter(Boolean); // Use translated tool names from the registry as prefixes const convertPrefix = toolRegistry["convert" as ToolId]?.name ?? "Convert"; diff --git a/frontend/src/desktop/components/tools/toolPicker/ToolButton.tsx b/frontend/src/desktop/components/tools/toolPicker/ToolButton.tsx index 7762f020b..ba1ecf8e0 100644 --- a/frontend/src/desktop/components/tools/toolPicker/ToolButton.tsx +++ b/frontend/src/desktop/components/tools/toolPicker/ToolButton.tsx @@ -3,12 +3,10 @@ import CoreToolButton from "@core/components/tools/toolPicker/ToolButton"; import { getToolDisabledReason } from "@app/components/tools/fullscreen/shared"; import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext"; import { useAppConfig } from "@app/contexts/AppConfigContext"; -import { ToolRegistryEntry } from "@app/data/toolsTaxonomy"; import { connectionModeService, type ConnectionMode, } from "@app/services/connectionModeService"; -import type { ToolId } from "@app/types/toolId"; type CoreToolButtonProps = React.ComponentProps; @@ -35,8 +33,8 @@ const ToolButton: React.FC = (props) => { }, []); const disabledReason = getToolDisabledReason( - props.id as string, - props.tool as ToolRegistryEntry, + props.id, + props.tool, toolAvailability, premiumEnabled, ); @@ -49,7 +47,7 @@ const ToolButton: React.FC = (props) => { connectionMode === "local" && disabledReason !== "comingSoon" && disabledReason !== "selfHostedOffline" - ? () => handleToolSelectForced(props.id as ToolId) + ? () => handleToolSelectForced(props.id) : undefined; return ( diff --git a/frontend/src/desktop/services/apiClientConfig.ts b/frontend/src/desktop/services/apiClientConfig.ts index 54fff092e..4989ed427 100644 --- a/frontend/src/desktop/services/apiClientConfig.ts +++ b/frontend/src/desktop/services/apiClientConfig.ts @@ -15,11 +15,8 @@ import { isTauri } from "@tauri-apps/api/core"; export function getApiBaseUrl(): string { if (!isTauri()) { // Runtime override to fix hardcoded localhost in builds - if ( - typeof window !== "undefined" && - (window as any).STIRLING_PDF_API_BASE_URL - ) { - return (window as any).STIRLING_PDF_API_BASE_URL; + if (typeof window !== "undefined" && window.STIRLING_PDF_API_BASE_URL) { + return window.STIRLING_PDF_API_BASE_URL; } return import.meta.env.VITE_API_BASE_URL; diff --git a/frontend/src/desktop/services/connectionModeService.ts b/frontend/src/desktop/services/connectionModeService.ts index 575ce8919..67c14f924 100644 --- a/frontend/src/desktop/services/connectionModeService.ts +++ b/frontend/src/desktop/services/connectionModeService.ts @@ -1,4 +1,4 @@ -import { invoke } from "@tauri-apps/api/core"; +import { invoke, isTauri } from "@tauri-apps/api/core"; import { fetch } from "@tauri-apps/plugin-http"; import { endpointAvailabilityService } from "@app/services/endpointAvailabilityService"; import { selfHostedServerMonitor } from "@app/services/selfHostedServerMonitor"; @@ -240,90 +240,11 @@ export class ConnectionModeService { */ async testConnection(url: string): Promise { console.log( - `[ConnectionModeService] 🔍 Starting comprehensive connection diagnostics for: ${url}`, + `[ConnectionModeService] 🔍 Starting connection diagnostics for: ${url}`, ); console.log( - `[ConnectionModeService] ==================== DIAGNOSTIC SESSION START ====================`, + `[ConnectionModeService] System: online=${navigator.onLine}, tauri=${isTauri()}, concurrency=${navigator.hardwareConcurrency || "unknown"}`, ); - console.log(`[ConnectionModeService] System Information:`); - console.log( - `[ConnectionModeService] - User Agent: ${navigator.userAgent}`, - ); - console.log(`[ConnectionModeService] - Platform: ${navigator.platform}`); - console.log(`[ConnectionModeService] - Online: ${navigator.onLine}`); - console.log( - `[ConnectionModeService] - Connection Type: ${(navigator as any).connection?.effectiveType || "unknown"}`, - ); - console.log(`[ConnectionModeService] - Language: ${navigator.language}`); - console.log( - `[ConnectionModeService] - Cookies Enabled: ${navigator.cookieEnabled}`, - ); - console.log( - `[ConnectionModeService] - Hardware Concurrency: ${navigator.hardwareConcurrency || "unknown"} cores`, - ); - console.log( - `[ConnectionModeService] - Max Touch Points: ${navigator.maxTouchPoints}`, - ); - - // Check for proxy environment variables - console.log(`[ConnectionModeService] Environment Check:`); - const envProxy = - (window as any).process?.env?.HTTP_PROXY || - (window as any).process?.env?.HTTPS_PROXY; - if (envProxy) { - console.log(`[ConnectionModeService] - Proxy detected: ${envProxy}`); - } else { - console.log( - `[ConnectionModeService] - No proxy environment variables detected`, - ); - } - - // Check if running in Tauri (v2 uses different detection) - console.log(`[ConnectionModeService] - Checking Tauri context...`); - console.log( - `[ConnectionModeService] - window.__TAURI__ type: ${typeof (window as any).__TAURI__}`, - ); - console.log( - `[ConnectionModeService] - window.__TAURI_INTERNALS__ type: ${typeof (window as any).__TAURI_INTERNALS__}`, - ); - console.log( - `[ConnectionModeService] - window.location.href:`, - window.location.href, - ); - console.log( - `[ConnectionModeService] - window.location.protocol:`, - window.location.protocol, - ); - - // Tauri v2 detection: check for __TAURI_INTERNALS__ or tauri:// protocol - const isTauriV2 = - typeof (window as any).__TAURI_INTERNALS__ !== "undefined" || - window.location.protocol === "tauri:" || - window.location.hostname === "tauri.localhost"; - const isTauriV1 = typeof (window as any).__TAURI__ !== "undefined"; - const isTauri = isTauriV1 || isTauriV2; - - console.log( - `[ConnectionModeService] - Running in Tauri v1: ${isTauriV1}`, - ); - console.log( - `[ConnectionModeService] - Running in Tauri v2: ${isTauriV2}`, - ); - console.log(`[ConnectionModeService] - Running in Tauri: ${isTauri}`); - - if (isTauri) { - if (isTauriV1) { - const tauriApi = (window as any).__TAURI__; - console.log(`[ConnectionModeService] - Tauri v1 API:`, tauriApi); - } - if (isTauriV2) { - console.log( - `[ConnectionModeService] - Tauri v2 detected via internals/protocol`, - ); - const internals = (window as any).__TAURI_INTERNALS__; - console.log(`[ConnectionModeService] - Tauri internals:`, internals); - } - } const diagnostics: DiagnosticResult[] = []; const healthUrl = `${url.replace(/\/$/, "")}/api/v1/info/status`; @@ -723,7 +644,13 @@ export class ConnectionModeService { `[ConnectionModeService] - Certificate validation: ${disableCertValidation ? "DISABLED" : "ENABLED"}`, ); - const fetchOptions: any = { + const fetchOptions: RequestInit & { + connectTimeout?: number; + danger?: { + acceptInvalidCerts: boolean; + acceptInvalidHostnames: boolean; + }; + } = { method: "GET", connectTimeout: 10000, }; diff --git a/frontend/src/desktop/services/endpointAvailabilityService.ts b/frontend/src/desktop/services/endpointAvailabilityService.ts index da64ce1bc..c10a56064 100644 --- a/frontend/src/desktop/services/endpointAvailabilityService.ts +++ b/frontend/src/desktop/services/endpointAvailabilityService.ts @@ -200,11 +200,14 @@ export class EndpointAvailabilityService { const data = await response.json(); const now = Date.now(); - Object.entries(data).forEach(([endpoint, details]: [string, any]) => { - const available = details?.enabled ?? false; - this.localCache.set(endpoint, available); - this.localCacheExpiry.set(endpoint, now + this.CACHE_DURATION); - }); + Object.entries(data).forEach( + ([endpoint, details]: [string, unknown]) => { + const available = + (details as { enabled?: boolean })?.enabled ?? false; + this.localCache.set(endpoint, available); + this.localCacheExpiry.set(endpoint, now + this.CACHE_DURATION); + }, + ); } else { console.warn( `[endpointAvailabilityService] Failed to preload endpoints: ${response.status}`, @@ -313,5 +316,5 @@ export const endpointAvailabilityService = new EndpointAvailabilityService(); // Expose to window for debugging if (typeof window !== "undefined") { - (window as any).endpointAvailabilityService = endpointAvailabilityService; + window.endpointAvailabilityService = endpointAvailabilityService; } diff --git a/frontend/src/desktop/services/httpErrorHandler.ts b/frontend/src/desktop/services/httpErrorHandler.ts index 55d3060be..e2deed091 100644 --- a/frontend/src/desktop/services/httpErrorHandler.ts +++ b/frontend/src/desktop/services/httpErrorHandler.ts @@ -1,3 +1,4 @@ +import { isAxiosError } from "axios"; import { handleHttpError as coreHandleHttpError } from "@core/services/httpErrorHandler"; /** @@ -6,8 +7,8 @@ import { handleHttpError as coreHandleHttpError } from "@core/services/httpError * login page must not appear. Instead, open the SignInModal for re-authentication. * All other error handling delegates to the core implementation. */ -export async function handleHttpError(error: any): Promise { - const status: number | undefined = error?.response?.status; +export async function handleHttpError(error: unknown): Promise { + const status = isAxiosError(error) ? error.response?.status : undefined; if (status === 401) { // In desktop builds, 401s are handled by the auth service (token refresh + toast diff --git a/frontend/src/desktop/services/localFileSaveService.ts b/frontend/src/desktop/services/localFileSaveService.ts index 0b9b6a6ec..6232c8a7c 100644 --- a/frontend/src/desktop/services/localFileSaveService.ts +++ b/frontend/src/desktop/services/localFileSaveService.ts @@ -98,7 +98,7 @@ export async function saveMultipleFilesWithPrompt( try { const fileName = file instanceof File ? file.name : `output_${savedCount + 1}.pdf`; - const filePath = await join(selectedFolder as string, fileName); + const filePath = await join(selectedFolder, fileName); const arrayBuffer = await file.arrayBuffer(); await writeFile(filePath, new Uint8Array(arrayBuffer)); savedCount++; diff --git a/frontend/src/desktop/services/saasErrorInterceptor.ts b/frontend/src/desktop/services/saasErrorInterceptor.ts index 7cd013bd1..0b4a10009 100644 --- a/frontend/src/desktop/services/saasErrorInterceptor.ts +++ b/frontend/src/desktop/services/saasErrorInterceptor.ts @@ -1,3 +1,4 @@ +import { isAxiosError } from "axios"; import { extractAxiosErrorMessage } from "@app/services/httpErrorUtils"; import { alert } from "@app/components/toast"; @@ -12,7 +13,11 @@ import { alert } from "@app/components/toast"; * false if this is not a SaaS error. */ export function handleSaaSError(error: unknown): boolean { - if ((error as any)?.config?._isSaaSRequest !== true) return false; + if ( + !isAxiosError(error) || + (error.config as { _isSaaSRequest?: boolean })?._isSaaSRequest !== true + ) + return false; const { title: originalTitle, body: originalBody } = extractAxiosErrorMessage(error); diff --git a/frontend/src/desktop/services/tauriHttpClient.ts b/frontend/src/desktop/services/tauriHttpClient.ts index b6c9631e4..c214f1330 100644 --- a/frontend/src/desktop/services/tauriHttpClient.ts +++ b/frontend/src/desktop/services/tauriHttpClient.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any -- Axios-compatible API requires matching axios's `any` signatures */ import { fetch } from "@tauri-apps/plugin-http"; /** @@ -231,7 +232,12 @@ class TauriHttpClient { // Make the request using Tauri's native HTTP client (standard Fetch API) // Enable certificate bypass for HTTPS to handle missing intermediate certs and self-signed certs - const fetchOptions: any = { + const fetchOptions: RequestInit & { + danger?: { + acceptInvalidCerts: boolean; + acceptInvalidHostnames: boolean; + }; + } = { method, headers, body, diff --git a/frontend/src/global.d.ts b/frontend/src/global.d.ts index bb9234baf..979a85735 100644 --- a/frontend/src/global.d.ts +++ b/frontend/src/global.d.ts @@ -15,6 +15,8 @@ declare module "assets/material-symbols-icons.json" { declare global { interface Window { __STIRLING_PDF_BASE_URL__?: string; + STIRLING_PDF_API_BASE_URL?: string; + endpointAvailabilityService?: unknown; } }