// SPDX-License-Identifier: AGPL-3.0-or-later import path from 'node:path'; import {APP_PROTOCOL, DESKTOP_APP_URL} from '@electron/common/Constants'; import {getDesktopTroubleshootingSettings, getDesktopWindowBehaviorSettings} from '@electron/common/DesktopConfig'; import type {DesktopInfo, DesktopTroubleshootingSettings, DesktopWindowBehaviorSettings} from '@electron/common/Types'; import {isPortableMode} from '@electron/common/UserDataPath'; import {hasEnabledBlinkFeature, MIDDLE_CLICK_AUTOSCROLL_BLINK_FEATURE} from '@electron/main/ChromiumRuntime'; import { type DesktopAccountStoreCounts, formatDesktopAccountStoreTelemetry, readDesktopAccountStoreTelemetry, } from '@electron/main/DesktopAccountStoreTelemetry'; import {type DesktopAppStoreState, readDesktopAppStoreState} from '@electron/main/DesktopAppStoreHealth'; import {getDesktopInfo} from '@electron/main/PlatformInfo'; import {app} from 'electron'; import log from 'electron-log'; const DEBUG_INFO_ARGS = new Set(['--fluxer-debug-info', '--fluxer-client-info', '--client-info']); const DISABLE_HARDWARE_ACCELERATION_ARGS = new Set(['--fluxer-disable-gpu', '--fluxer-disable-hardware-acceleration']); const OPEN_DEVTOOLS_ARGS = new Set(['--fluxer-devtools', '--fluxer-open-devtools']); const RESET_WINDOW_STATE_ARGS = new Set(['--fluxer-reset-window-state']); const SAFE_MODE_ARGS = new Set(['--fluxer-safe-mode']); const RENDERER_CONSOLE_LOG_ARGS = new Set(['--fluxer-log-renderer-console']); const NET_LOG_ARGS = new Set(['--fluxer-net-log']); interface DesktopDebugInfo { clientInfo: string; desktopInfo: DesktopInfo; appUrl: string; userDataPath: string; logsPath: string | null; logFilePath: string | null; configPath: string; accountStore: DesktopAppStoreState | null; accountStoreTelemetry: DesktopAccountStoreCounts; windowBehavior: DesktopWindowBehaviorSettings; troubleshooting: DesktopTroubleshootingSettings; packaged: boolean; portable: boolean; pid: number; execPath: string; appImagePath: string | null; appDirPath: string | null; cwd: string; launchArgs: Array; } interface DesktopDebugInfoOptions { nativeProbes?: boolean; } interface DesktopClientInfoOptions { locale?: string | null; } function getDesktopWindowBehaviorDebugSettings(): DesktopWindowBehaviorSettings { return { ...getDesktopWindowBehaviorSettings(), activeSmoothScrolling: !app.commandLine.hasSwitch('disable-smooth-scrolling'), activeMiddleClickAutoscroll: process.platform === 'linux' && hasEnabledBlinkFeature(MIDDLE_CLICK_AUTOSCROLL_BLINK_FEATURE), }; } export function hasDesktopDebugInfoArg(argv: ReadonlyArray): boolean { return hasFlag(argv, DEBUG_INFO_ARGS); } export function shouldDisableHardwareAccelerationForLaunch(argv: ReadonlyArray): boolean { return hasFlag(argv, DISABLE_HARDWARE_ACCELERATION_ARGS) || isSafeModeLaunch(argv); } export function resolveEffectiveDesktopTroubleshootingSettings( settings: DesktopTroubleshootingSettings, argv: ReadonlyArray, platform: NodeJS.Platform, ): DesktopTroubleshootingSettings { return { ...settings, disableHardwareAcceleration: platform !== 'darwin' && (settings.disableHardwareAcceleration || shouldDisableHardwareAccelerationForLaunch(argv)), }; } let launchTroubleshootingSettings: DesktopTroubleshootingSettings | null = null; export function getLaunchDesktopTroubleshootingSettings(): DesktopTroubleshootingSettings { launchTroubleshootingSettings ??= resolveEffectiveDesktopTroubleshootingSettings( getDesktopTroubleshootingSettings(), process.argv, process.platform, ); return launchTroubleshootingSettings; } export function shouldOpenDevToolsOnLaunch(argv: ReadonlyArray): boolean { return hasFlag(argv, OPEN_DEVTOOLS_ARGS); } export function shouldResetWindowStateOnLaunch(argv: ReadonlyArray): boolean { return hasFlag(argv, RESET_WINDOW_STATE_ARGS); } export function shouldIgnoreWindowStateForLaunch(argv: ReadonlyArray): boolean { return isSafeModeLaunch(argv); } export function shouldForwardRendererConsoleToMainLog(argv: ReadonlyArray): boolean { return hasFlag(argv, RENDERER_CONSOLE_LOG_ARGS); } function hasFlag(argv: ReadonlyArray, flags: ReadonlySet): boolean { return argv.some((arg) => flags.has(arg) || [...flags].some((flag) => arg.startsWith(`${flag}=`))); } function isSafeModeLaunch(argv: ReadonlyArray): boolean { return hasFlag(argv, SAFE_MODE_ARGS); } function getArgValue(argv: ReadonlyArray, names: ReadonlySet): string | null { for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; for (const name of names) { if (arg === name) { const next = argv[index + 1]; return next && !next.startsWith('--') ? next : ''; } if (arg.startsWith(`${name}=`)) { return arg.slice(name.length + 1); } } } return null; } export function getLaunchNetLogPath(userDataPath: string, argv: ReadonlyArray): string | null { const value = getArgValue(argv, NET_LOG_ARGS); if (value === null) { return null; } const trimmed = value.trim(); if (trimmed.length > 0) { return path.resolve(trimmed); } return path.join(userDataPath, `netlog-${new Date().toISOString().replace(/[:.]/g, '-')}.json`); } function safeGetLogsPath(): string | null { try { return app.getPath('logs'); } catch { return null; } } function getLogFilePath(): string | null { try { const file = log.transports.file.getFile(); return file?.path ?? null; } catch { return null; } } function sanitizeLaunchArg(arg: string): string { if (arg.toLowerCase().startsWith(`${APP_PROTOCOL}://`)) { return `${APP_PROTOCOL}://`; } if (/^(--[^=]*(?:token|secret|password|key|code)[^=]*)=/i.test(arg)) { return `${arg.slice(0, arg.indexOf('='))}=`; } return arg; } function sanitizeLaunchArgs(argv: ReadonlyArray): Array { return argv.map(sanitizeLaunchArg); } function normalizeArchitectureValue(value: string | null | undefined): string | undefined { if (!value) return undefined; const trimmed = value.trim(); if (/\barm64\b|\baarch64\b|\barmv8\b|\barm64e\b/i.test(trimmed)) return 'arm64'; if (/\barm\b|\barmv7\b|\barmv6\b/i.test(trimmed)) return 'arm'; if (/\bx86_64\b|\bx64\b|\bamd64\b|\bwin64\b|\bwow64\b/i.test(trimmed)) return 'x64'; if (/\bx86\b|\bi[3-6]86\b/i.test(trimmed)) return 'x86'; return trimmed || undefined; } function formatSystemArchitecture(desktopInfo: DesktopInfo): string { return ( normalizeArchitectureValue(desktopInfo.hardwareArch || desktopInfo.arch) ?? desktopInfo.hardwareArch ?? desktopInfo.arch ); } function getWindowsVersionName(osVersion: string): string { const parts = osVersion.split('.'); const majorVersion = parseInt(parts[0], 10); const buildNumber = parseInt(parts[2], 10); if (majorVersion === 10) { if (buildNumber >= 22000) { return 'Windows 11'; } return 'Windows 10'; } return 'Windows'; } function formatOsName(desktopInfo: DesktopInfo): string { switch (desktopInfo.os) { case 'darwin': return 'macOS'; case 'win32': return desktopInfo.osVersion ? getWindowsVersionName(desktopInfo.osVersion) : 'Windows'; case 'linux': return 'Linux'; default: return desktopInfo.os; } } function formatReleaseChannelLabel(value: string): string { const normalized = value.trim().toLowerCase(); return normalized.charAt(0).toUpperCase() + normalized.slice(1); } function getBuildString(desktopInfo: DesktopInfo): string { const buildVersion = process.env.PUBLIC_BUILD_VERSION || process.env.BUILD_VERSION || desktopInfo.version || 'dev'; const releaseChannel = formatReleaseChannelLabel( process.env.PUBLIC_RELEASE_CHANNEL || process.env.RELEASE_CHANNEL || desktopInfo.channel, ); return `${releaseChannel} Desktop ${buildVersion}`; } function safeGetLocale(): string { try { if (!app.isReady()) return 'en-US'; return app.getLocale() || 'en-US'; } catch { return 'en-US'; } } function formatDesktopClientInfo(desktopInfo: DesktopInfo, options: DesktopClientInfoOptions = {}): string { const osVersion = desktopInfo.systemVersion || desktopInfo.osVersion; const osDescription = `${[formatOsName(desktopInfo), osVersion].filter(Boolean).join(' ')} (${formatSystemArchitecture( desktopInfo, )})`; const parts = [ getBuildString(desktopInfo), osDescription, `Electron ${desktopInfo.electronVersion}`, `Chrome ${desktopInfo.chromeVersion}`, `Node ${desktopInfo.nodeVersion}`, options.locale ? `Locale ${options.locale}` : '', ]; return parts.filter(Boolean).join(', '); } export async function getDesktopDebugInfo( userDataPath: string, options: DesktopDebugInfoOptions = {}, ): Promise { const desktopInfo = await getDesktopInfo({nativeProbes: options.nativeProbes ?? true}); return { clientInfo: formatDesktopClientInfo(desktopInfo, {locale: safeGetLocale()}), desktopInfo, appUrl: DESKTOP_APP_URL, userDataPath, logsPath: safeGetLogsPath(), logFilePath: getLogFilePath(), configPath: path.join(userDataPath, 'settings.json'), accountStore: readDesktopAppStoreState(userDataPath), accountStoreTelemetry: readDesktopAccountStoreTelemetry(userDataPath), windowBehavior: getDesktopWindowBehaviorDebugSettings(), troubleshooting: getLaunchDesktopTroubleshootingSettings(), packaged: app.isPackaged, portable: isPortableMode(), pid: process.pid, execPath: process.execPath, appImagePath: process.env.APPIMAGE || null, appDirPath: process.env.APPDIR || null, cwd: process.cwd(), launchArgs: sanitizeLaunchArgs(process.argv.slice(1)), }; } function formatWindowBehavior(settings: DesktopWindowBehaviorSettings): string { return [ `showTrayIcon=${settings.showTrayIcon}`, `minimizeToTray=${settings.minimizeToTray}`, `closeToTray=${settings.closeToTray}`, `startMinimized=${settings.startMinimized}`, `useNativeTitleBar=${settings.useNativeTitleBar}`, `rememberWindowState=${settings.rememberWindowState}`, `allowTransparency=${settings.allowTransparency}`, `activeAllowTransparency=${settings.activeAllowTransparency}`, `smoothScrolling=${settings.smoothScrolling}`, `activeSmoothScrolling=${settings.activeSmoothScrolling}`, `middleClickAutoscroll=${settings.middleClickAutoscroll}`, `activeMiddleClickAutoscroll=${settings.activeMiddleClickAutoscroll}`, ].join(', '); } function formatAccountStoreState(state: DesktopAppStoreState | null): string { if (state === null) { return 'never opened successfully on this device'; } return `firstSuccessAt=${new Date(state.firstSuccessAt).toISOString()}, lastSuccessAt=${new Date( state.lastSuccessAt, ).toISOString()}, schemaVersion=${state.lastSchemaVersion}`; } export function formatDesktopDebugInfo(info: DesktopDebugInfo): string { return [ 'Fluxer desktop debug info', info.clientInfo, `App URL: ${info.appUrl}`, `User data: ${info.userDataPath}`, `Config: ${info.configPath}`, `Account store: ${formatAccountStoreState(info.accountStore)}`, `Account store telemetry: ${formatDesktopAccountStoreTelemetry(info.accountStoreTelemetry)}`, `Logs: ${info.logFilePath ?? info.logsPath ?? '(unavailable)'}`, `Troubleshooting: disableHardwareAcceleration=${info.troubleshooting.disableHardwareAcceleration}`, `Window behavior: ${formatWindowBehavior(info.windowBehavior)}`, `Runtime: packaged=${info.packaged}, portable=${info.portable}, pid=${info.pid}, cwd=${info.cwd}`, `Sandbox: flatpak=${info.desktopInfo.flatpak}, flatpakAppId=${info.desktopInfo.flatpakAppId ?? '(none)'}`, `Executable: ${info.execPath}`, ...(info.appImagePath ? [`AppImage: ${info.appImagePath}`] : []), `Launch args: ${info.launchArgs.length > 0 ? info.launchArgs.join(' ') : '(none)'}`, ].join('\n'); } export function logDesktopDebugInfo(info: DesktopDebugInfo): void { log.info('[DebugInfo] Client info:', info.clientInfo); log.info('[DebugInfo] Runtime:', { appUrl: info.appUrl, userDataPath: info.userDataPath, configPath: info.configPath, accountStore: info.accountStore, accountStoreTelemetry: info.accountStoreTelemetry, logsPath: info.logsPath, logFilePath: info.logFilePath, troubleshooting: info.troubleshooting, windowBehavior: info.windowBehavior, packaged: info.packaged, portable: info.portable, pid: info.pid, execPath: info.execPath, flatpak: info.desktopInfo.flatpak, flatpakAppId: info.desktopInfo.flatpakAppId, appImagePath: info.appImagePath, appDirPath: info.appDirPath, cwd: info.cwd, launchArgs: info.launchArgs, }); }