refactor: extract app shutdown orchestration service

This commit is contained in:
2026-02-10 00:06:10 -08:00
parent 2878a1f3d1
commit 8b286f15e8
4 changed files with 107 additions and 25 deletions

View File

@@ -0,0 +1,32 @@
import test from "node:test";
import assert from "node:assert/strict";
import { runAppShutdownRuntimeService } from "./app-shutdown-runtime-service";
test("runAppShutdownRuntimeService runs teardown steps in order", () => {
const calls: string[] = [];
runAppShutdownRuntimeService({
unregisterAllGlobalShortcuts: () => calls.push("unregisterAllGlobalShortcuts"),
stopSubtitleWebsocket: () => calls.push("stopSubtitleWebsocket"),
stopTexthookerService: () => calls.push("stopTexthookerService"),
destroyYomitanParserWindow: () => calls.push("destroyYomitanParserWindow"),
clearYomitanParserPromises: () => calls.push("clearYomitanParserPromises"),
stopWindowTracker: () => calls.push("stopWindowTracker"),
destroyMpvSocket: () => calls.push("destroyMpvSocket"),
clearReconnectTimer: () => calls.push("clearReconnectTimer"),
destroySubtitleTimingTracker: () => calls.push("destroySubtitleTimingTracker"),
destroyAnkiIntegration: () => calls.push("destroyAnkiIntegration"),
});
assert.deepEqual(calls, [
"unregisterAllGlobalShortcuts",
"stopSubtitleWebsocket",
"stopTexthookerService",
"destroyYomitanParserWindow",
"clearYomitanParserPromises",
"stopWindowTracker",
"destroyMpvSocket",
"clearReconnectTimer",
"destroySubtitleTimingTracker",
"destroyAnkiIntegration",
]);
});

View File

@@ -0,0 +1,27 @@
export interface AppShutdownRuntimeDeps {
unregisterAllGlobalShortcuts: () => void;
stopSubtitleWebsocket: () => void;
stopTexthookerService: () => void;
destroyYomitanParserWindow: () => void;
clearYomitanParserPromises: () => void;
stopWindowTracker: () => void;
destroyMpvSocket: () => void;
clearReconnectTimer: () => void;
destroySubtitleTimingTracker: () => void;
destroyAnkiIntegration: () => void;
}
export function runAppShutdownRuntimeService(
deps: AppShutdownRuntimeDeps,
): void {
deps.unregisterAllGlobalShortcuts();
deps.stopSubtitleWebsocket();
deps.stopTexthookerService();
deps.destroyYomitanParserWindow();
deps.clearYomitanParserPromises();
deps.stopWindowTracker();
deps.destroyMpvSocket();
deps.clearReconnectTimer();
deps.destroySubtitleTimingTracker();
deps.destroyAnkiIntegration();
}

View File

@@ -202,6 +202,7 @@ import {
setOverlayDebugVisualizationEnabledRuntimeService,
} from "./core/services/overlay-broadcast-runtime-service";
import { runAppReadyRuntimeService } from "./core/services/app-ready-runtime-service";
import { runAppShutdownRuntimeService } from "./core/services/app-shutdown-runtime-service";
import {
runSubsyncManualFromIpcRuntimeService,
triggerSubsyncFromConfigRuntimeService,
@@ -564,30 +565,52 @@ if (initialArgs.generateConfig && !shouldStartApp(initialArgs)) {
});
},
onWillQuitCleanup: () => {
globalShortcut.unregisterAll();
subtitleWsService.stop();
texthookerService.stop();
if (yomitanParserWindow && !yomitanParserWindow.isDestroyed()) {
yomitanParserWindow.destroy();
}
yomitanParserWindow = null;
yomitanParserReadyPromise = null;
yomitanParserInitPromise = null;
if (windowTracker) {
windowTracker.stop();
}
if (mpvClient && mpvClient.socket) {
mpvClient.socket.destroy();
}
if (reconnectTimer) {
clearTimeout(reconnectTimer);
}
if (subtitleTimingTracker) {
subtitleTimingTracker.destroy();
}
if (ankiIntegration) {
ankiIntegration.destroy();
}
runAppShutdownRuntimeService({
unregisterAllGlobalShortcuts: () => {
globalShortcut.unregisterAll();
},
stopSubtitleWebsocket: () => {
subtitleWsService.stop();
},
stopTexthookerService: () => {
texthookerService.stop();
},
destroyYomitanParserWindow: () => {
if (yomitanParserWindow && !yomitanParserWindow.isDestroyed()) {
yomitanParserWindow.destroy();
}
yomitanParserWindow = null;
},
clearYomitanParserPromises: () => {
yomitanParserReadyPromise = null;
yomitanParserInitPromise = null;
},
stopWindowTracker: () => {
if (windowTracker) {
windowTracker.stop();
}
},
destroyMpvSocket: () => {
if (mpvClient && mpvClient.socket) {
mpvClient.socket.destroy();
}
},
clearReconnectTimer: () => {
if (reconnectTimer) {
clearTimeout(reconnectTimer);
}
},
destroySubtitleTimingTracker: () => {
if (subtitleTimingTracker) {
subtitleTimingTracker.destroy();
}
},
destroyAnkiIntegration: () => {
if (ankiIntegration) {
ankiIntegration.destroy();
}
},
});
},
shouldRestoreWindowsOnActivate: () =>
overlayRuntimeInitialized && BrowserWindow.getAllWindows().length === 0,