mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-28 00:55:16 -07:00
Fix Jellyfin Login (#76)
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import { appendLogLine, resolveDefaultLogFilePath } from '../shared/log-files';
|
||||
|
||||
export type FatalErrorReportOptions = {
|
||||
title: string;
|
||||
context: string;
|
||||
};
|
||||
|
||||
export type FatalErrorReporterDeps = {
|
||||
showErrorBox: (title: string, details: string) => void;
|
||||
consoleError?: (message: string, error?: unknown) => void;
|
||||
appendLogLine?: (line: string) => void;
|
||||
resolveLogFilePath?: () => string;
|
||||
now?: () => Date;
|
||||
};
|
||||
|
||||
export type FatalErrorReporter = (
|
||||
error: unknown,
|
||||
options?: Partial<FatalErrorReportOptions>,
|
||||
) => void;
|
||||
|
||||
const DEFAULT_TITLE = 'SubMiner crashed';
|
||||
const DEFAULT_CONTEXT = 'SubMiner encountered a fatal error';
|
||||
|
||||
let fatalErrorReported = false;
|
||||
|
||||
function pad(value: number): string {
|
||||
return String(value).padStart(2, '0');
|
||||
}
|
||||
|
||||
function formatTimestamp(date: Date): string {
|
||||
return [
|
||||
date.getFullYear(),
|
||||
'-',
|
||||
pad(date.getMonth() + 1),
|
||||
'-',
|
||||
pad(date.getDate()),
|
||||
' ',
|
||||
pad(date.getHours()),
|
||||
':',
|
||||
pad(date.getMinutes()),
|
||||
':',
|
||||
pad(date.getSeconds()),
|
||||
].join('');
|
||||
}
|
||||
|
||||
function stringifyUnknownError(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
return error.stack || error.message || error.name;
|
||||
}
|
||||
if (typeof error === 'string') {
|
||||
return error;
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(error) ?? String(error);
|
||||
} catch {
|
||||
return String(error);
|
||||
}
|
||||
}
|
||||
|
||||
export function buildFatalErrorDetails(options: {
|
||||
context: string;
|
||||
error: unknown;
|
||||
logFilePath: string;
|
||||
}): string {
|
||||
return [
|
||||
options.context,
|
||||
'',
|
||||
stringifyUnknownError(options.error),
|
||||
'',
|
||||
`Log file: ${options.logFilePath}`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function createFatalErrorReporter(deps: FatalErrorReporterDeps): FatalErrorReporter {
|
||||
return (error, options = {}) => {
|
||||
if (fatalErrorReported) {
|
||||
return;
|
||||
}
|
||||
fatalErrorReported = true;
|
||||
|
||||
const title = options.title ?? DEFAULT_TITLE;
|
||||
const context = options.context ?? DEFAULT_CONTEXT;
|
||||
const logFilePath = deps.resolveLogFilePath?.() ?? resolveDefaultLogFilePath('app');
|
||||
const details = buildFatalErrorDetails({ context, error, logFilePath });
|
||||
const timestamp = formatTimestamp(deps.now?.() ?? new Date());
|
||||
const line = `[subminer] - ${timestamp} - ERROR - [main:fatal] ${details.replace(/\r?\n/g, ' | ')}`;
|
||||
|
||||
try {
|
||||
(deps.appendLogLine ?? ((entry: string) => appendLogLine(logFilePath, entry)))(line);
|
||||
} catch {
|
||||
// Fatal reporting must never throw while handling the original failure.
|
||||
}
|
||||
|
||||
try {
|
||||
deps.consoleError?.(line, error);
|
||||
} catch {
|
||||
// ignore console sink failures
|
||||
}
|
||||
|
||||
try {
|
||||
deps.showErrorBox(title, details);
|
||||
} catch {
|
||||
// If native dialogs are unavailable, the file log above is still the source of truth.
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function registerFatalErrorHandlers(deps: {
|
||||
reportFatalError: FatalErrorReporter;
|
||||
exit: (code: number) => void;
|
||||
}): void {
|
||||
process.on('uncaughtException', (error) => {
|
||||
deps.reportFatalError(error, {
|
||||
title: 'SubMiner crashed',
|
||||
context: 'SubMiner main process threw an uncaught exception.',
|
||||
});
|
||||
deps.exit(1);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
deps.reportFatalError(reason, {
|
||||
title: 'SubMiner crashed',
|
||||
context: 'SubMiner main process had an unhandled promise rejection.',
|
||||
});
|
||||
deps.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export function resetFatalErrorReporterForTests(): void {
|
||||
fatalErrorReported = false;
|
||||
}
|
||||
Reference in New Issue
Block a user