refactor: extract additional main runtime dependency builders

This commit is contained in:
2026-02-20 00:10:36 -08:00
parent df380ed1ca
commit 5476d44005
21 changed files with 1299 additions and 110 deletions

View File

@@ -6,7 +6,7 @@ Read first. Keep concise.
| ------------ | -------------- | ---------------------------------------------------- | --------- | ------------------------------------- | ---------------------- | | ------------ | -------------- | ---------------------------------------------------- | --------- | ------------------------------------- | ---------------------- |
| `codex-main` | `planner-exec` | `Fix frequency/N+1 regression in plugin --start flow` | `in_progress` | `docs/subagents/agents/codex-main.md` | `2026-02-19T19:36:46Z` | | `codex-main` | `planner-exec` | `Fix frequency/N+1 regression in plugin --start flow` | `in_progress` | `docs/subagents/agents/codex-main.md` | `2026-02-19T19:36:46Z` |
| `codex-config-validation-20260219T172015Z-iiyf` | `codex-config-validation` | `Find root cause of config validation error for ~/.config/SubMiner/config.jsonc` | `completed` | `docs/subagents/agents/codex-config-validation-20260219T172015Z-iiyf.md` | `2026-02-19T17:26:17Z` | | `codex-config-validation-20260219T172015Z-iiyf` | `codex-config-validation` | `Find root cause of config validation error for ~/.config/SubMiner/config.jsonc` | `completed` | `docs/subagents/agents/codex-config-validation-20260219T172015Z-iiyf.md` | `2026-02-19T17:26:17Z` |
| `codex-task85-20260219T233711Z-46hc` | `codex-task85` | `Resume TASK-85 maintainability refactor from latest handoff point` | `in_progress` | `docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md` | `2026-02-20T07:35:01Z` | | `codex-task85-20260219T233711Z-46hc` | `codex-task85` | `Resume TASK-85 maintainability refactor from latest handoff point` | `in_progress` | `docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md` | `2026-02-20T08:00:02Z` |
| `codex-anilist-deeplink-20260219T233926Z` | `anilist-deeplink` | `Fix external subminer:// AniList callback handling from browser` | `done` | `docs/subagents/agents/codex-anilist-deeplink-20260219T233926Z.md` | `2026-02-19T23:59:21Z` | | `codex-anilist-deeplink-20260219T233926Z` | `anilist-deeplink` | `Fix external subminer:// AniList callback handling from browser` | `done` | `docs/subagents/agents/codex-anilist-deeplink-20260219T233926Z.md` | `2026-02-19T23:59:21Z` |
| `codex-texthooker-highlights-20260220T002354Z-927c` | `codex-texthooker-highlights` | `Add optional texthooker highlight toggles for known/n+1/frequency/JLPT` | `completed` | `docs/subagents/agents/codex-texthooker-highlights-20260220T002354Z-927c.md` | `2026-02-20T00:30:49Z` | | `codex-texthooker-highlights-20260220T002354Z-927c` | `codex-texthooker-highlights` | `Add optional texthooker highlight toggles for known/n+1/frequency/JLPT` | `completed` | `docs/subagents/agents/codex-texthooker-highlights-20260220T002354Z-927c.md` | `2026-02-20T00:30:49Z` |
| `codex-texthooker-ui-playwright-20260220T003827Z-k3p9` | `codex-texthooker-ui-playwright` | `Run Playwright MCP smoke/regression checks for texthooker-ui changes` | `completed` | `docs/subagents/agents/codex-texthooker-ui-playwright-20260220T003827Z-k3p9.md` | `2026-02-20T00:42:09Z` | | `codex-texthooker-ui-playwright-20260220T003827Z-k3p9` | `codex-texthooker-ui-playwright` | `Run Playwright MCP smoke/regression checks for texthooker-ui changes` | `completed` | `docs/subagents/agents/codex-texthooker-ui-playwright-20260220T003827Z-k3p9.md` | `2026-02-20T00:42:09Z` |

View File

@@ -9,6 +9,32 @@
## Current Work (newest first) ## Current Work (newest first)
- [2026-02-20T08:00:02Z] progress: extracted Jellyfin command-dispatch deps assembly into `src/main/runtime/jellyfin-command-dispatch-main-deps.ts` (`createBuildRunJellyfinCommandMainDepsHandler`) and rewired `runJellyfinCommand` construction in `src/main.ts`.
- [2026-02-20T08:00:02Z] progress: added parity tests in `src/main/runtime/jellyfin-command-dispatch-main-deps.test.ts`; `src/main.ts` now 2909 LOC.
- [2026-02-20T08:00:02Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/jellyfin-command-dispatch-main-deps.test.js dist/main/runtime/jellyfin-command-dispatch.test.js dist/main/runtime/jellyfin-cli-main-deps.test.js` pass (8/8).
- [2026-02-20T07:58:40Z] progress: extracted Jellyfin CLI handler deps assembly into `src/main/runtime/jellyfin-cli-main-deps.ts` (auth/list/play/remote-announce builders) and rewired those handler construction sites in `src/main.ts`.
- [2026-02-20T07:58:40Z] progress: added parity tests in `src/main/runtime/jellyfin-cli-main-deps.test.ts`; `src/main.ts` now 2905 LOC.
- [2026-02-20T07:58:40Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/jellyfin-cli-main-deps.test.js dist/main/runtime/jellyfin-cli-auth.test.js dist/main/runtime/jellyfin-cli-list.test.js dist/main/runtime/jellyfin-cli-play.test.js dist/main/runtime/jellyfin-cli-remote-announce.test.js` pass (16/16).
- [2026-02-20T07:57:16Z] progress: extracted AniList setup protocol deps assembly into `src/main/runtime/anilist-setup-protocol-main-deps.ts` (notify, consume-token, protocol-url, protocol-client builders) and rewired those handler construction sites in `src/main.ts`.
- [2026-02-20T07:57:16Z] progress: added parity tests in `src/main/runtime/anilist-setup-protocol-main-deps.test.ts`; `src/main.ts` now 2882 LOC.
- [2026-02-20T07:57:16Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/anilist-setup-protocol-main-deps.test.js dist/main/runtime/anilist-setup-protocol.test.js dist/main/runtime/jellyfin-client-info-main-deps.test.js` pass (6/6).
- [2026-02-20T07:52:56Z] progress: extracted Jellyfin client-info deps assembly into `src/main/runtime/jellyfin-client-info-main-deps.ts` (resolved-config + client-info builders) and rewired those handler construction sites in `src/main.ts`.
- [2026-02-20T07:52:56Z] progress: added parity tests in `src/main/runtime/jellyfin-client-info-main-deps.test.ts`; `src/main.ts` now 2859 LOC.
- [2026-02-20T07:52:56Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/jellyfin-client-info-main-deps.test.js dist/main/runtime/jellyfin-client-info.test.js dist/main/runtime/mpv-jellyfin-defaults-main-deps.test.js` pass (5/5).
- [2026-02-20T07:51:58Z] progress: extracted MPV/Jellyfin defaults deps assembly into `src/main/runtime/mpv-jellyfin-defaults-main-deps.ts` (apply-defaults + default-socket-path builders) and rewired those constructor sites in `src/main.ts`.
- [2026-02-20T07:51:58Z] progress: added parity tests in `src/main/runtime/mpv-jellyfin-defaults-main-deps.test.ts`; `src/main.ts` now 2848 LOC.
- [2026-02-20T07:51:58Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/mpv-jellyfin-defaults-main-deps.test.js dist/main/runtime/mpv-jellyfin-defaults.test.js dist/main/runtime/jellyfin-remote-connection-main-deps.test.js` pass (5/5).
- [2026-02-20T07:46:45Z] progress: extracted Jellyfin remote-connection deps assembly into `src/main/runtime/jellyfin-remote-connection-main-deps.ts` (wait, launch, ensure builders) and rewired those constructor sites in `src/main.ts`.
- [2026-02-20T07:46:45Z] progress: added parity tests in `src/main/runtime/jellyfin-remote-connection-main-deps.test.ts`; `src/main.ts` now 2837 LOC.
- [2026-02-20T07:46:45Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/jellyfin-remote-connection-main-deps.test.js dist/main/runtime/jellyfin-remote-connection.test.js dist/main/runtime/jellyfin-remote-main-deps.test.js` pass (8/8).
- [2026-02-20T07:44:09Z] progress: extracted runtime-options/overlay-action deps assembly into `src/main/runtime/overlay-runtime-main-actions-main-deps.ts` (get-state, restore-secondary-sub, broadcast, send-active-overlay, debug-visualization, open-palette builders) and rewired handler construction in `src/main.ts`.
- [2026-02-20T07:44:09Z] progress: added parity tests in `src/main/runtime/overlay-runtime-main-actions-main-deps.test.ts`; `src/main.ts` now 2821 LOC.
- [2026-02-20T07:44:09Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/overlay-runtime-main-actions-main-deps.test.js dist/main/runtime/overlay-runtime-main-actions.test.js dist/main/runtime/overlay-bootstrap-main-deps.test.js` pass (10/10).
- [2026-02-20T07:42:00Z] progress: extracted overlay bootstrap deps assembly into `src/main/runtime/overlay-bootstrap-main-deps.ts` (`createBuildOverlayContentMeasurementStoreMainDepsHandler`, `createBuildOverlayModalRuntimeMainDepsHandler`) and rewired overlay measurement/modal constructor wiring in `src/main.ts`.
- [2026-02-20T07:42:00Z] progress: added parity tests in `src/main/runtime/overlay-bootstrap-main-deps.test.ts`; `src/main.ts` now 2794 LOC.
- [2026-02-20T07:42:00Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/overlay-bootstrap-main-deps.test.js dist/core/services/overlay-content-measurement.test.js dist/main/runtime/runtime-bootstrap-main-deps.test.js` pass (8/8).
- [2026-02-20T07:40:24Z] progress: after push of commit `561f7b3`, extracted bootstrap runtime deps assembly into `src/main/runtime/runtime-bootstrap-main-deps.ts` (immersion media, AniList state, config-derived, subsync builders) and rewired those constructor sites in `src/main.ts`.
- [2026-02-20T07:40:24Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/runtime-bootstrap-main-deps.test.js dist/main/runtime/immersion-media.test.js dist/main/runtime/anilist-state.test.js` pass (10/10).
- [2026-02-20T07:35:01Z] progress: extracted subtitle processing controller deps assembly into `src/main/runtime/subtitle-processing-main-deps.ts` (`createBuildSubtitleProcessingControllerMainDepsHandler`) and rewired `subtitleProcessingController` construction in `src/main.ts`. - [2026-02-20T07:35:01Z] progress: extracted subtitle processing controller deps assembly into `src/main/runtime/subtitle-processing-main-deps.ts` (`createBuildSubtitleProcessingControllerMainDepsHandler`) and rewired `subtitleProcessingController` construction in `src/main.ts`.
- [2026-02-20T07:35:01Z] progress: added parity tests in `src/main/runtime/subtitle-processing-main-deps.test.ts`; `src/main.ts` now 2775 LOC. - [2026-02-20T07:35:01Z] progress: added parity tests in `src/main/runtime/subtitle-processing-main-deps.test.ts`; `src/main.ts` now 2775 LOC.
- [2026-02-20T07:35:01Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/subtitle-processing-main-deps.test.js dist/core/services/subtitle-processing-controller.test.js dist/main/runtime/jellyfin-remote-main-deps.test.js` pass (9/9). - [2026-02-20T07:35:01Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/subtitle-processing-main-deps.test.js dist/core/services/subtitle-processing-controller.test.js dist/main/runtime/jellyfin-remote-main-deps.test.js` pass (9/9).

View File

@@ -94,6 +94,12 @@ import {
createNotifyAnilistSetupHandler, createNotifyAnilistSetupHandler,
createRegisterSubminerProtocolClientHandler, createRegisterSubminerProtocolClientHandler,
} from './main/runtime/anilist-setup-protocol'; } from './main/runtime/anilist-setup-protocol';
import {
createBuildConsumeAnilistSetupTokenFromUrlMainDepsHandler,
createBuildHandleAnilistSetupProtocolUrlMainDepsHandler,
createBuildNotifyAnilistSetupMainDepsHandler,
createBuildRegisterSubminerProtocolClientMainDepsHandler,
} from './main/runtime/anilist-setup-protocol-main-deps';
import { createRefreshAnilistClientSecretStateHandler } from './main/runtime/anilist-token-refresh'; import { createRefreshAnilistClientSecretStateHandler } from './main/runtime/anilist-token-refresh';
import { import {
createHandleJellyfinRemoteGeneralCommand, createHandleJellyfinRemoteGeneralCommand,
@@ -114,11 +120,26 @@ import {
createBuildReportJellyfinRemoteStoppedMainDepsHandler, createBuildReportJellyfinRemoteStoppedMainDepsHandler,
} from './main/runtime/jellyfin-remote-main-deps'; } from './main/runtime/jellyfin-remote-main-deps';
import { createBuildSubtitleProcessingControllerMainDepsHandler } from './main/runtime/subtitle-processing-main-deps'; import { createBuildSubtitleProcessingControllerMainDepsHandler } from './main/runtime/subtitle-processing-main-deps';
import {
createBuildAnilistStateRuntimeMainDepsHandler,
createBuildConfigDerivedRuntimeMainDepsHandler,
createBuildImmersionMediaRuntimeMainDepsHandler,
createBuildMainSubsyncRuntimeMainDepsHandler,
} from './main/runtime/runtime-bootstrap-main-deps';
import {
createBuildOverlayContentMeasurementStoreMainDepsHandler,
createBuildOverlayModalRuntimeMainDepsHandler,
} from './main/runtime/overlay-bootstrap-main-deps';
import { import {
createEnsureMpvConnectedForJellyfinPlaybackHandler, createEnsureMpvConnectedForJellyfinPlaybackHandler,
createLaunchMpvIdleForJellyfinPlaybackHandler, createLaunchMpvIdleForJellyfinPlaybackHandler,
createWaitForMpvConnectedHandler, createWaitForMpvConnectedHandler,
} from './main/runtime/jellyfin-remote-connection'; } from './main/runtime/jellyfin-remote-connection';
import {
createBuildEnsureMpvConnectedForJellyfinPlaybackMainDepsHandler,
createBuildLaunchMpvIdleForJellyfinPlaybackMainDepsHandler,
createBuildWaitForMpvConnectedMainDepsHandler,
} from './main/runtime/jellyfin-remote-connection-main-deps';
import { import {
buildJellyfinSetupFormHtml, buildJellyfinSetupFormHtml,
createOpenJellyfinSetupWindowHandler, createOpenJellyfinSetupWindowHandler,
@@ -156,14 +177,29 @@ import { createRunJellyfinCommandHandler } from './main/runtime/jellyfin-command
import { createHandleJellyfinListCommands } from './main/runtime/jellyfin-cli-list'; import { createHandleJellyfinListCommands } from './main/runtime/jellyfin-cli-list';
import { createHandleJellyfinPlayCommand } from './main/runtime/jellyfin-cli-play'; import { createHandleJellyfinPlayCommand } from './main/runtime/jellyfin-cli-play';
import { createHandleJellyfinRemoteAnnounceCommand } from './main/runtime/jellyfin-cli-remote-announce'; import { createHandleJellyfinRemoteAnnounceCommand } from './main/runtime/jellyfin-cli-remote-announce';
import {
createBuildHandleJellyfinAuthCommandsMainDepsHandler,
createBuildHandleJellyfinListCommandsMainDepsHandler,
createBuildHandleJellyfinPlayCommandMainDepsHandler,
createBuildHandleJellyfinRemoteAnnounceCommandMainDepsHandler,
} from './main/runtime/jellyfin-cli-main-deps';
import { createBuildRunJellyfinCommandMainDepsHandler } from './main/runtime/jellyfin-command-dispatch-main-deps';
import { import {
createGetJellyfinClientInfoHandler, createGetJellyfinClientInfoHandler,
createGetResolvedJellyfinConfigHandler, createGetResolvedJellyfinConfigHandler,
} from './main/runtime/jellyfin-client-info'; } from './main/runtime/jellyfin-client-info';
import {
createBuildGetJellyfinClientInfoMainDepsHandler,
createBuildGetResolvedJellyfinConfigMainDepsHandler,
} from './main/runtime/jellyfin-client-info-main-deps';
import { import {
createApplyJellyfinMpvDefaultsHandler, createApplyJellyfinMpvDefaultsHandler,
createGetDefaultSocketPathHandler, createGetDefaultSocketPathHandler,
} from './main/runtime/mpv-jellyfin-defaults'; } from './main/runtime/mpv-jellyfin-defaults';
import {
createBuildApplyJellyfinMpvDefaultsMainDepsHandler,
createBuildGetDefaultSocketPathMainDepsHandler,
} from './main/runtime/mpv-jellyfin-defaults-main-deps';
import { createBuildMediaRuntimeMainDepsHandler } from './main/runtime/media-runtime-main-deps'; import { createBuildMediaRuntimeMainDepsHandler } from './main/runtime/media-runtime-main-deps';
import { import {
createBuildDictionaryRootsMainHandler, createBuildDictionaryRootsMainHandler,
@@ -271,6 +307,14 @@ import {
createSendToActiveOverlayWindowHandler, createSendToActiveOverlayWindowHandler,
createSetOverlayDebugVisualizationEnabledHandler, createSetOverlayDebugVisualizationEnabledHandler,
} from './main/runtime/overlay-runtime-main-actions'; } from './main/runtime/overlay-runtime-main-actions';
import {
createBuildBroadcastRuntimeOptionsChangedMainDepsHandler,
createBuildGetRuntimeOptionsStateMainDepsHandler,
createBuildOpenRuntimeOptionsPaletteMainDepsHandler,
createBuildRestorePreviousSecondarySubVisibilityMainDepsHandler,
createBuildSendToActiveOverlayWindowMainDepsHandler,
createBuildSetOverlayDebugVisualizationEnabledMainDepsHandler,
} from './main/runtime/overlay-runtime-main-actions-main-deps';
import { import {
createHandleMpvCommandFromIpcHandler, createHandleMpvCommandFromIpcHandler,
createRunSubsyncManualFromIpcHandler, createRunSubsyncManualFromIpcHandler,
@@ -486,10 +530,14 @@ let jellyfinMpvAutoLaunchInFlight: Promise<boolean> | null = null;
let backgroundWarmupsStarted = false; let backgroundWarmupsStarted = false;
let yomitanLoadInFlight: Promise<Extension | null> | null = null; let yomitanLoadInFlight: Promise<Extension | null> | null = null;
const applyJellyfinMpvDefaultsHandler = createApplyJellyfinMpvDefaultsHandler({ const buildApplyJellyfinMpvDefaultsMainDepsHandler =
createBuildApplyJellyfinMpvDefaultsMainDepsHandler({
sendMpvCommandRuntime: (client, command) => sendMpvCommandRuntime(client as never, command), sendMpvCommandRuntime: (client, command) => sendMpvCommandRuntime(client as never, command),
jellyfinLangPref: JELLYFIN_LANG_PREF, jellyfinLangPref: JELLYFIN_LANG_PREF,
}); });
const applyJellyfinMpvDefaultsHandler = createApplyJellyfinMpvDefaultsHandler(
buildApplyJellyfinMpvDefaultsMainDepsHandler(),
);
function applyJellyfinMpvDefaults(client: MpvIpcClient): void { function applyJellyfinMpvDefaults(client: MpvIpcClient): void {
applyJellyfinMpvDefaultsHandler(client); applyJellyfinMpvDefaultsHandler(client);
@@ -549,9 +597,12 @@ const appLogger = {
}, },
}; };
const getDefaultSocketPathHandler = createGetDefaultSocketPathHandler({ const buildGetDefaultSocketPathMainDepsHandler = createBuildGetDefaultSocketPathMainDepsHandler({
platform: process.platform, platform: process.platform,
}); });
const getDefaultSocketPathHandler = createGetDefaultSocketPathHandler(
buildGetDefaultSocketPathMainDepsHandler(),
);
function getDefaultSocketPath(): string { function getDefaultSocketPath(): string {
return getDefaultSocketPathHandler(); return getDefaultSocketPathHandler();
@@ -570,19 +621,24 @@ process.on('SIGTERM', () => {
}); });
const overlayManager = createOverlayManager(); const overlayManager = createOverlayManager();
const overlayContentMeasurementStore = createOverlayContentMeasurementStore({ const buildOverlayContentMeasurementStoreMainDepsHandler =
createBuildOverlayContentMeasurementStoreMainDepsHandler({
now: () => Date.now(), now: () => Date.now(),
warn: (message: string) => logger.warn(message), warn: (message: string) => logger.warn(message),
}); });
const overlayModalRuntime = createOverlayModalRuntimeService({ const buildOverlayModalRuntimeMainDepsHandler = createBuildOverlayModalRuntimeMainDepsHandler({
getMainWindow: () => overlayManager.getMainWindow(), getMainWindow: () => overlayManager.getMainWindow(),
getInvisibleWindow: () => overlayManager.getInvisibleWindow(), getInvisibleWindow: () => overlayManager.getInvisibleWindow(),
}); });
const overlayContentMeasurementStore = createOverlayContentMeasurementStore(
buildOverlayContentMeasurementStoreMainDepsHandler(),
);
const overlayModalRuntime = createOverlayModalRuntimeService(buildOverlayModalRuntimeMainDepsHandler());
const appState = createAppState({ const appState = createAppState({
mpvSocketPath: getDefaultSocketPath(), mpvSocketPath: getDefaultSocketPath(),
texthookerPort: DEFAULT_TEXTHOOKER_PORT, texthookerPort: DEFAULT_TEXTHOOKER_PORT,
}); });
const immersionMediaRuntime = createImmersionMediaRuntime({ const buildImmersionMediaRuntimeMainDepsHandler = createBuildImmersionMediaRuntimeMainDepsHandler({
getResolvedConfig: () => getResolvedConfig(), getResolvedConfig: () => getResolvedConfig(),
defaultImmersionDbPath: DEFAULT_IMMERSION_DB_PATH, defaultImmersionDbPath: DEFAULT_IMMERSION_DB_PATH,
getTracker: () => appState.immersionTracker, getTracker: () => appState.immersionTracker,
@@ -592,7 +648,7 @@ const immersionMediaRuntime = createImmersionMediaRuntime({
logDebug: (message) => logger.debug(message), logDebug: (message) => logger.debug(message),
logInfo: (message) => logger.info(message), logInfo: (message) => logger.info(message),
}); });
const anilistStateRuntime = createAnilistStateRuntime({ const buildAnilistStateRuntimeMainDepsHandler = createBuildAnilistStateRuntimeMainDepsHandler({
getClientSecretState: () => appState.anilistClientSecretState, getClientSecretState: () => appState.anilistClientSecretState,
setClientSecretState: (next) => { setClientSecretState: (next) => {
appState.anilistClientSecretState = next; appState.anilistClientSecretState = next;
@@ -607,7 +663,7 @@ const anilistStateRuntime = createAnilistStateRuntime({
anilistCachedAccessToken = null; anilistCachedAccessToken = null;
}, },
}); });
const configDerivedRuntime = createConfigDerivedRuntime({ const buildConfigDerivedRuntimeMainDepsHandler = createBuildConfigDerivedRuntimeMainDepsHandler({
getResolvedConfig: () => getResolvedConfig(), getResolvedConfig: () => getResolvedConfig(),
getRuntimeOptionsManager: () => appState.runtimeOptionsManager, getRuntimeOptionsManager: () => appState.runtimeOptionsManager,
platform: process.platform, platform: process.platform,
@@ -615,7 +671,7 @@ const configDerivedRuntime = createConfigDerivedRuntime({
defaultJimakuMaxEntryResults: DEFAULT_CONFIG.jimaku.maxEntryResults, defaultJimakuMaxEntryResults: DEFAULT_CONFIG.jimaku.maxEntryResults,
defaultJimakuApiBaseUrl: DEFAULT_CONFIG.jimaku.apiBaseUrl, defaultJimakuApiBaseUrl: DEFAULT_CONFIG.jimaku.apiBaseUrl,
}); });
const subsyncRuntime = createMainSubsyncRuntime({ const buildMainSubsyncRuntimeMainDepsHandler = createBuildMainSubsyncRuntimeMainDepsHandler({
getMpvClient: () => appState.mpvClient, getMpvClient: () => appState.mpvClient,
getResolvedConfig: () => getResolvedConfig(), getResolvedConfig: () => getResolvedConfig(),
getSubsyncInProgress: () => appState.subsyncInProgress, getSubsyncInProgress: () => appState.subsyncInProgress,
@@ -629,6 +685,10 @@ const subsyncRuntime = createMainSubsyncRuntime({
}); });
}, },
}); });
const immersionMediaRuntime = createImmersionMediaRuntime(buildImmersionMediaRuntimeMainDepsHandler());
const anilistStateRuntime = createAnilistStateRuntime(buildAnilistStateRuntimeMainDepsHandler());
const configDerivedRuntime = createConfigDerivedRuntime(buildConfigDerivedRuntimeMainDepsHandler());
const subsyncRuntime = createMainSubsyncRuntime(buildMainSubsyncRuntimeMainDepsHandler());
let appTray: Tray | null = null; let appTray: Tray | null = null;
const buildSubtitleProcessingControllerMainDepsHandler = const buildSubtitleProcessingControllerMainDepsHandler =
createBuildSubtitleProcessingControllerMainDepsHandler({ createBuildSubtitleProcessingControllerMainDepsHandler({
@@ -953,9 +1013,12 @@ const overlayVisibilityRuntime = createOverlayVisibilityRuntimeService(
})(), })(),
); );
const getRuntimeOptionsStateHandler = createGetRuntimeOptionsStateHandler({ const buildGetRuntimeOptionsStateMainDepsHandler = createBuildGetRuntimeOptionsStateMainDepsHandler({
getRuntimeOptionsManager: () => appState.runtimeOptionsManager, getRuntimeOptionsManager: () => appState.runtimeOptionsManager,
}); });
const getRuntimeOptionsStateHandler = createGetRuntimeOptionsStateHandler(
buildGetRuntimeOptionsStateMainDepsHandler(),
);
function getRuntimeOptionsState(): RuntimeOptionState[] { function getRuntimeOptionsState(): RuntimeOptionState[] {
return getRuntimeOptionsStateHandler(); return getRuntimeOptionsStateHandler();
@@ -965,10 +1028,12 @@ function getOverlayWindows(): BrowserWindow[] {
return overlayManager.getOverlayWindows(); return overlayManager.getOverlayWindows();
} }
const restorePreviousSecondarySubVisibilityHandler = createRestorePreviousSecondarySubVisibilityHandler( const buildRestorePreviousSecondarySubVisibilityMainDepsHandler =
{ createBuildRestorePreviousSecondarySubVisibilityMainDepsHandler({
getMpvClient: () => appState.mpvClient, getMpvClient: () => appState.mpvClient,
}, });
const restorePreviousSecondarySubVisibilityHandler = createRestorePreviousSecondarySubVisibilityHandler(
buildRestorePreviousSecondarySubVisibilityMainDepsHandler(),
); );
function restorePreviousSecondarySubVisibility(): void { function restorePreviousSecondarySubVisibility(): void {
@@ -979,20 +1044,28 @@ function broadcastToOverlayWindows(channel: string, ...args: unknown[]): void {
overlayManager.broadcastToOverlayWindows(channel, ...args); overlayManager.broadcastToOverlayWindows(channel, ...args);
} }
const broadcastRuntimeOptionsChangedHandler = createBroadcastRuntimeOptionsChangedHandler({ const buildBroadcastRuntimeOptionsChangedMainDepsHandler =
createBuildBroadcastRuntimeOptionsChangedMainDepsHandler({
broadcastRuntimeOptionsChangedRuntime, broadcastRuntimeOptionsChangedRuntime,
getRuntimeOptionsState: () => getRuntimeOptionsState(), getRuntimeOptionsState: () => getRuntimeOptionsState(),
broadcastToOverlayWindows: (channel, ...args) => broadcastToOverlayWindows(channel, ...args), broadcastToOverlayWindows: (channel, ...args) => broadcastToOverlayWindows(channel, ...args),
}); });
const broadcastRuntimeOptionsChangedHandler = createBroadcastRuntimeOptionsChangedHandler(
buildBroadcastRuntimeOptionsChangedMainDepsHandler(),
);
function broadcastRuntimeOptionsChanged(): void { function broadcastRuntimeOptionsChanged(): void {
broadcastRuntimeOptionsChangedHandler(); broadcastRuntimeOptionsChangedHandler();
} }
const sendToActiveOverlayWindowHandler = createSendToActiveOverlayWindowHandler({ const buildSendToActiveOverlayWindowMainDepsHandler =
createBuildSendToActiveOverlayWindowMainDepsHandler({
sendToActiveOverlayWindowRuntime: (channel, payload, runtimeOptions) => sendToActiveOverlayWindowRuntime: (channel, payload, runtimeOptions) =>
overlayModalRuntime.sendToActiveOverlayWindow(channel, payload, runtimeOptions), overlayModalRuntime.sendToActiveOverlayWindow(channel, payload, runtimeOptions),
}); });
const sendToActiveOverlayWindowHandler = createSendToActiveOverlayWindowHandler(
buildSendToActiveOverlayWindowMainDepsHandler(),
);
function sendToActiveOverlayWindow( function sendToActiveOverlayWindow(
channel: string, channel: string,
@@ -1002,24 +1075,30 @@ function sendToActiveOverlayWindow(
return sendToActiveOverlayWindowHandler(channel, payload, runtimeOptions); return sendToActiveOverlayWindowHandler(channel, payload, runtimeOptions);
} }
const setOverlayDebugVisualizationEnabledHandler = createSetOverlayDebugVisualizationEnabledHandler( const buildSetOverlayDebugVisualizationEnabledMainDepsHandler =
{ createBuildSetOverlayDebugVisualizationEnabledMainDepsHandler({
setOverlayDebugVisualizationEnabledRuntime, setOverlayDebugVisualizationEnabledRuntime,
getCurrentEnabled: () => appState.overlayDebugVisualizationEnabled, getCurrentEnabled: () => appState.overlayDebugVisualizationEnabled,
setCurrentEnabled: (next) => { setCurrentEnabled: (next) => {
appState.overlayDebugVisualizationEnabled = next; appState.overlayDebugVisualizationEnabled = next;
}, },
broadcastToOverlayWindows: (channel, ...args) => broadcastToOverlayWindows(channel, ...args), broadcastToOverlayWindows: (channel, ...args) => broadcastToOverlayWindows(channel, ...args),
}, });
const setOverlayDebugVisualizationEnabledHandler = createSetOverlayDebugVisualizationEnabledHandler(
buildSetOverlayDebugVisualizationEnabledMainDepsHandler(),
); );
function setOverlayDebugVisualizationEnabled(enabled: boolean): void { function setOverlayDebugVisualizationEnabled(enabled: boolean): void {
setOverlayDebugVisualizationEnabledHandler(enabled); setOverlayDebugVisualizationEnabledHandler(enabled);
} }
const openRuntimeOptionsPaletteHandler = createOpenRuntimeOptionsPaletteHandler({ const buildOpenRuntimeOptionsPaletteMainDepsHandler =
createBuildOpenRuntimeOptionsPaletteMainDepsHandler({
openRuntimeOptionsPaletteRuntime: () => overlayModalRuntime.openRuntimeOptionsPalette(), openRuntimeOptionsPaletteRuntime: () => overlayModalRuntime.openRuntimeOptionsPalette(),
}); });
const openRuntimeOptionsPaletteHandler = createOpenRuntimeOptionsPaletteHandler(
buildOpenRuntimeOptionsPaletteMainDepsHandler(),
);
function openRuntimeOptionsPalette(): void { function openRuntimeOptionsPalette(): void {
openRuntimeOptionsPaletteHandler(); openRuntimeOptionsPaletteHandler();
@@ -1029,30 +1108,41 @@ function getResolvedConfig() {
return configService.getConfig(); return configService.getConfig();
} }
const getResolvedJellyfinConfigHandler = createGetResolvedJellyfinConfigHandler({ const buildGetResolvedJellyfinConfigMainDepsHandler =
createBuildGetResolvedJellyfinConfigMainDepsHandler({
getResolvedConfig: () => getResolvedConfig(), getResolvedConfig: () => getResolvedConfig(),
}); });
const getResolvedJellyfinConfigHandler = createGetResolvedJellyfinConfigHandler(
buildGetResolvedJellyfinConfigMainDepsHandler(),
);
function getResolvedJellyfinConfig() { function getResolvedJellyfinConfig() {
return getResolvedJellyfinConfigHandler(); return getResolvedJellyfinConfigHandler();
} }
const getJellyfinClientInfoHandler = createGetJellyfinClientInfoHandler({ const buildGetJellyfinClientInfoMainDepsHandler = createBuildGetJellyfinClientInfoMainDepsHandler({
getResolvedJellyfinConfig: () => getResolvedJellyfinConfig(), getResolvedJellyfinConfig: () => getResolvedJellyfinConfig(),
getDefaultJellyfinConfig: () => DEFAULT_CONFIG.jellyfin, getDefaultJellyfinConfig: () => DEFAULT_CONFIG.jellyfin,
}); });
const getJellyfinClientInfoHandler = createGetJellyfinClientInfoHandler(
buildGetJellyfinClientInfoMainDepsHandler(),
);
function getJellyfinClientInfo(config = getResolvedJellyfinConfig()) { function getJellyfinClientInfo(config = getResolvedJellyfinConfig()) {
return getJellyfinClientInfoHandler(config); return getJellyfinClientInfoHandler(config);
} }
const waitForMpvConnected = createWaitForMpvConnectedHandler({ const buildWaitForMpvConnectedMainDepsHandler = createBuildWaitForMpvConnectedMainDepsHandler({
getMpvClient: () => appState.mpvClient, getMpvClient: () => appState.mpvClient,
now: () => Date.now(), now: () => Date.now(),
sleep: (delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)), sleep: (delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)),
}); });
const waitForMpvConnected = createWaitForMpvConnectedHandler(
buildWaitForMpvConnectedMainDepsHandler(),
);
const launchMpvIdleForJellyfinPlayback = createLaunchMpvIdleForJellyfinPlaybackHandler({ const buildLaunchMpvIdleForJellyfinPlaybackMainDepsHandler =
createBuildLaunchMpvIdleForJellyfinPlaybackMainDepsHandler({
getSocketPath: () => appState.mpvSocketPath, getSocketPath: () => appState.mpvSocketPath,
platform: process.platform, platform: process.platform,
execPath: process.execPath, execPath: process.execPath,
@@ -1068,9 +1158,13 @@ const launchMpvIdleForJellyfinPlayback = createLaunchMpvIdleForJellyfinPlaybackH
}), }),
logWarn: (message, error) => logger.warn(message, error), logWarn: (message, error) => logger.warn(message, error),
logInfo: (message) => logger.info(message), logInfo: (message) => logger.info(message),
}); });
const launchMpvIdleForJellyfinPlayback = createLaunchMpvIdleForJellyfinPlaybackHandler(
buildLaunchMpvIdleForJellyfinPlaybackMainDepsHandler(),
);
const ensureMpvConnectedForJellyfinPlayback = createEnsureMpvConnectedForJellyfinPlaybackHandler({ const buildEnsureMpvConnectedForJellyfinPlaybackMainDepsHandler =
createBuildEnsureMpvConnectedForJellyfinPlaybackMainDepsHandler({
getMpvClient: () => appState.mpvClient, getMpvClient: () => appState.mpvClient,
setMpvClient: (client) => { setMpvClient: (client) => {
appState.mpvClient = client as MpvIpcClient | null; appState.mpvClient = client as MpvIpcClient | null;
@@ -1084,7 +1178,10 @@ const ensureMpvConnectedForJellyfinPlayback = createEnsureMpvConnectedForJellyfi
}, },
connectTimeoutMs: JELLYFIN_MPV_CONNECT_TIMEOUT_MS, connectTimeoutMs: JELLYFIN_MPV_CONNECT_TIMEOUT_MS,
autoLaunchTimeoutMs: JELLYFIN_MPV_AUTO_LAUNCH_TIMEOUT_MS, autoLaunchTimeoutMs: JELLYFIN_MPV_AUTO_LAUNCH_TIMEOUT_MS,
}); });
const ensureMpvConnectedForJellyfinPlayback = createEnsureMpvConnectedForJellyfinPlaybackHandler(
buildEnsureMpvConnectedForJellyfinPlaybackMainDepsHandler(),
);
const preloadJellyfinExternalSubtitles = createPreloadJellyfinExternalSubtitlesHandler({ const preloadJellyfinExternalSubtitles = createPreloadJellyfinExternalSubtitlesHandler({
listJellyfinSubtitleTracks: (session, clientInfo, itemId) => listJellyfinSubtitleTracks: (session, clientInfo, itemId) =>
@@ -1143,7 +1240,8 @@ const playJellyfinItemInMpv = createPlayJellyfinItemInMpvHandler({
}, },
}); });
const handleJellyfinAuthCommands = createHandleJellyfinAuthCommands({ const buildHandleJellyfinAuthCommandsMainDepsHandler =
createBuildHandleJellyfinAuthCommandsMainDepsHandler({
patchRawConfig: (patch) => { patchRawConfig: (patch) => {
configService.patchRawConfig(patch); configService.patchRawConfig(patch);
}, },
@@ -1151,8 +1249,12 @@ const handleJellyfinAuthCommands = createHandleJellyfinAuthCommands({
authenticateWithPasswordRuntime(serverUrl, username, password, clientInfo), authenticateWithPasswordRuntime(serverUrl, username, password, clientInfo),
logInfo: (message) => logger.info(message), logInfo: (message) => logger.info(message),
}); });
const handleJellyfinAuthCommands = createHandleJellyfinAuthCommands(
buildHandleJellyfinAuthCommandsMainDepsHandler(),
);
const handleJellyfinListCommands = createHandleJellyfinListCommands({ const buildHandleJellyfinListCommandsMainDepsHandler =
createBuildHandleJellyfinListCommandsMainDepsHandler({
listJellyfinLibraries: (session, clientInfo) => listJellyfinLibrariesRuntime(session, clientInfo), listJellyfinLibraries: (session, clientInfo) => listJellyfinLibrariesRuntime(session, clientInfo),
listJellyfinItems: (session, clientInfo, params) => listJellyfinItems: (session, clientInfo, params) =>
listJellyfinItemsRuntime(session, clientInfo, params), listJellyfinItemsRuntime(session, clientInfo, params),
@@ -1160,19 +1262,31 @@ const handleJellyfinListCommands = createHandleJellyfinListCommands({
listJellyfinSubtitleTracksRuntime(session, clientInfo, itemId), listJellyfinSubtitleTracksRuntime(session, clientInfo, itemId),
logInfo: (message) => logger.info(message), logInfo: (message) => logger.info(message),
}); });
const handleJellyfinListCommands = createHandleJellyfinListCommands(
buildHandleJellyfinListCommandsMainDepsHandler(),
);
const handleJellyfinPlayCommand = createHandleJellyfinPlayCommand({ const buildHandleJellyfinPlayCommandMainDepsHandler = createBuildHandleJellyfinPlayCommandMainDepsHandler(
{
playJellyfinItemInMpv: (params) => playJellyfinItemInMpv: (params) =>
playJellyfinItemInMpv(params as Parameters<typeof playJellyfinItemInMpv>[0]), playJellyfinItemInMpv(params as Parameters<typeof playJellyfinItemInMpv>[0]),
logWarn: (message) => logger.warn(message), logWarn: (message) => logger.warn(message),
}); },
);
const handleJellyfinPlayCommand = createHandleJellyfinPlayCommand(
buildHandleJellyfinPlayCommandMainDepsHandler(),
);
const handleJellyfinRemoteAnnounceCommand = createHandleJellyfinRemoteAnnounceCommand({ const buildHandleJellyfinRemoteAnnounceCommandMainDepsHandler =
createBuildHandleJellyfinRemoteAnnounceCommandMainDepsHandler({
startJellyfinRemoteSession: () => startJellyfinRemoteSession(), startJellyfinRemoteSession: () => startJellyfinRemoteSession(),
getRemoteSession: () => appState.jellyfinRemoteSession, getRemoteSession: () => appState.jellyfinRemoteSession,
logInfo: (message) => logger.info(message), logInfo: (message) => logger.info(message),
logWarn: (message) => logger.warn(message), logWarn: (message) => logger.warn(message),
}); });
const handleJellyfinRemoteAnnounceCommand = createHandleJellyfinRemoteAnnounceCommand(
buildHandleJellyfinRemoteAnnounceCommandMainDepsHandler(),
);
const startJellyfinRemoteSession = createStartJellyfinRemoteSessionHandler({ const startJellyfinRemoteSession = createStartJellyfinRemoteSessionHandler({
getJellyfinConfig: () => getResolvedJellyfinConfig(), getJellyfinConfig: () => getResolvedJellyfinConfig(),
@@ -1201,7 +1315,7 @@ const stopJellyfinRemoteSession = createStopJellyfinRemoteSessionHandler({
}, },
}); });
const runJellyfinCommand = createRunJellyfinCommandHandler({ const buildRunJellyfinCommandMainDepsHandler = createBuildRunJellyfinCommandMainDepsHandler({
getJellyfinConfig: () => getResolvedJellyfinConfig(), getJellyfinConfig: () => getResolvedJellyfinConfig(),
defaultServerUrl: DEFAULT_CONFIG.jellyfin.serverUrl, defaultServerUrl: DEFAULT_CONFIG.jellyfin.serverUrl,
getJellyfinClientInfo: (jellyfinConfig) => getJellyfinClientInfo(jellyfinConfig), getJellyfinClientInfo: (jellyfinConfig) => getJellyfinClientInfo(jellyfinConfig),
@@ -1210,15 +1324,22 @@ const runJellyfinCommand = createRunJellyfinCommandHandler({
handleListCommands: (params) => handleJellyfinListCommands(params), handleListCommands: (params) => handleJellyfinListCommands(params),
handlePlayCommand: (params) => handleJellyfinPlayCommand(params), handlePlayCommand: (params) => handleJellyfinPlayCommand(params),
}); });
const runJellyfinCommand = createRunJellyfinCommandHandler(
buildRunJellyfinCommandMainDepsHandler(),
);
const notifyAnilistSetup = createNotifyAnilistSetupHandler({ const buildNotifyAnilistSetupMainDepsHandler = createBuildNotifyAnilistSetupMainDepsHandler({
hasMpvClient: () => Boolean(appState.mpvClient), hasMpvClient: () => Boolean(appState.mpvClient),
showMpvOsd: (message) => showMpvOsd(message), showMpvOsd: (message) => showMpvOsd(message),
showDesktopNotification: (title, options) => showDesktopNotification(title, options), showDesktopNotification: (title, options) => showDesktopNotification(title, options),
logInfo: (message) => logger.info(message), logInfo: (message) => logger.info(message),
}); });
const notifyAnilistSetup = createNotifyAnilistSetupHandler(
buildNotifyAnilistSetupMainDepsHandler(),
);
const consumeAnilistSetupTokenFromUrl = createConsumeAnilistSetupTokenFromUrlHandler({ const buildConsumeAnilistSetupTokenFromUrlMainDepsHandler =
createBuildConsumeAnilistSetupTokenFromUrlMainDepsHandler({
consumeAnilistSetupCallbackUrl, consumeAnilistSetupCallbackUrl,
saveToken: (token) => anilistTokenStore.saveToken(token), saveToken: (token) => anilistTokenStore.saveToken(token),
setCachedToken: (token) => { setCachedToken: (token) => {
@@ -1244,22 +1365,35 @@ const consumeAnilistSetupTokenFromUrl = createConsumeAnilistSetupTokenFromUrlHan
appState.anilistSetupWindow.close(); appState.anilistSetupWindow.close();
} }
}, },
}); });
const consumeAnilistSetupTokenFromUrl = createConsumeAnilistSetupTokenFromUrlHandler(
buildConsumeAnilistSetupTokenFromUrlMainDepsHandler(),
);
const handleAnilistSetupProtocolUrl = createHandleAnilistSetupProtocolUrlHandler({ const buildHandleAnilistSetupProtocolUrlMainDepsHandler =
createBuildHandleAnilistSetupProtocolUrlMainDepsHandler({
consumeAnilistSetupTokenFromUrl: (rawUrl) => consumeAnilistSetupTokenFromUrl(rawUrl), consumeAnilistSetupTokenFromUrl: (rawUrl) => consumeAnilistSetupTokenFromUrl(rawUrl),
logWarn: (message, details) => logger.warn(message, details), logWarn: (message, details) => logger.warn(message, details),
}); });
const handleAnilistSetupProtocolUrl = createHandleAnilistSetupProtocolUrlHandler(
buildHandleAnilistSetupProtocolUrlMainDepsHandler(),
);
const registerSubminerProtocolClient = createRegisterSubminerProtocolClientHandler({ const buildRegisterSubminerProtocolClientMainDepsHandler =
createBuildRegisterSubminerProtocolClientMainDepsHandler({
isDefaultApp: () => Boolean(process.defaultApp), isDefaultApp: () => Boolean(process.defaultApp),
getArgv: () => process.argv, getArgv: () => process.argv,
execPath: process.execPath, execPath: process.execPath,
resolvePath: (value) => path.resolve(value), resolvePath: (value) => path.resolve(value),
setAsDefaultProtocolClient: (scheme, appPath, args) => setAsDefaultProtocolClient: (scheme, appPath, args) =>
appPath ? app.setAsDefaultProtocolClient(scheme, appPath, args) : app.setAsDefaultProtocolClient(scheme), appPath
? app.setAsDefaultProtocolClient(scheme, appPath, args)
: app.setAsDefaultProtocolClient(scheme),
logWarn: (message, details) => logger.warn(message, details), logWarn: (message, details) => logger.warn(message, details),
}); });
const registerSubminerProtocolClient = createRegisterSubminerProtocolClientHandler(
buildRegisterSubminerProtocolClientMainDepsHandler(),
);
function openAnilistSetupWindow(): void { function openAnilistSetupWindow(): void {
createOpenAnilistSetupWindowHandler({ createOpenAnilistSetupWindowHandler({

View File

@@ -0,0 +1,87 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
createBuildConsumeAnilistSetupTokenFromUrlMainDepsHandler,
createBuildHandleAnilistSetupProtocolUrlMainDepsHandler,
createBuildNotifyAnilistSetupMainDepsHandler,
createBuildRegisterSubminerProtocolClientMainDepsHandler,
} from './anilist-setup-protocol-main-deps';
test('notify anilist setup main deps builder maps callbacks', () => {
const calls: string[] = [];
const deps = createBuildNotifyAnilistSetupMainDepsHandler({
hasMpvClient: () => true,
showMpvOsd: (message) => calls.push(`osd:${message}`),
showDesktopNotification: (title) => calls.push(`notify:${title}`),
logInfo: (message) => calls.push(`log:${message}`),
})();
assert.equal(deps.hasMpvClient(), true);
deps.showMpvOsd('ok');
deps.showDesktopNotification('SubMiner', { body: 'x' });
deps.logInfo('done');
assert.deepEqual(calls, ['osd:ok', 'notify:SubMiner', 'log:done']);
});
test('consume anilist setup token main deps builder maps callbacks', () => {
const calls: string[] = [];
const deps = createBuildConsumeAnilistSetupTokenFromUrlMainDepsHandler({
consumeAnilistSetupCallbackUrl: () => true,
saveToken: () => calls.push('save'),
setCachedToken: () => calls.push('cache'),
setResolvedState: () => calls.push('resolved'),
setSetupPageOpened: () => calls.push('opened'),
onSuccess: () => calls.push('success'),
closeWindow: () => calls.push('close'),
})();
assert.equal(
deps.consumeAnilistSetupCallbackUrl({
rawUrl: 'subminer://anilist-setup',
saveToken: () => {},
setCachedToken: () => {},
setResolvedState: () => {},
setSetupPageOpened: () => {},
onSuccess: () => {},
closeWindow: () => {},
}),
true,
);
deps.saveToken('token');
deps.setCachedToken('token');
deps.setResolvedState(Date.now());
deps.setSetupPageOpened(true);
deps.onSuccess();
deps.closeWindow();
assert.deepEqual(calls, ['save', 'cache', 'resolved', 'opened', 'success', 'close']);
});
test('handle anilist setup protocol url main deps builder maps callbacks', () => {
const calls: string[] = [];
const deps = createBuildHandleAnilistSetupProtocolUrlMainDepsHandler({
consumeAnilistSetupTokenFromUrl: () => true,
logWarn: (message) => calls.push(`warn:${message}`),
})();
assert.equal(deps.consumeAnilistSetupTokenFromUrl('subminer://anilist-setup'), true);
deps.logWarn('missing', null);
assert.deepEqual(calls, ['warn:missing']);
});
test('register subminer protocol client main deps builder maps callbacks', () => {
const calls: string[] = [];
const deps = createBuildRegisterSubminerProtocolClientMainDepsHandler({
isDefaultApp: () => true,
getArgv: () => ['electron', 'entry.js'],
execPath: '/tmp/electron',
resolvePath: (value) => `/abs/${value}`,
setAsDefaultProtocolClient: () => true,
logWarn: (message) => calls.push(`warn:${message}`),
})();
assert.equal(deps.isDefaultApp(), true);
assert.deepEqual(deps.getArgv(), ['electron', 'entry.js']);
assert.equal(deps.execPath, '/tmp/electron');
assert.equal(deps.resolvePath('entry.js'), '/abs/entry.js');
assert.equal(deps.setAsDefaultProtocolClient('subminer'), true);
});

View File

@@ -0,0 +1,64 @@
import type {
createConsumeAnilistSetupTokenFromUrlHandler,
createHandleAnilistSetupProtocolUrlHandler,
createNotifyAnilistSetupHandler,
createRegisterSubminerProtocolClientHandler,
} from './anilist-setup-protocol';
type NotifyAnilistSetupMainDeps = Parameters<typeof createNotifyAnilistSetupHandler>[0];
type ConsumeAnilistSetupTokenMainDeps = Parameters<
typeof createConsumeAnilistSetupTokenFromUrlHandler
>[0];
type HandleAnilistSetupProtocolUrlMainDeps = Parameters<
typeof createHandleAnilistSetupProtocolUrlHandler
>[0];
type RegisterSubminerProtocolClientMainDeps = Parameters<
typeof createRegisterSubminerProtocolClientHandler
>[0];
export function createBuildNotifyAnilistSetupMainDepsHandler(deps: NotifyAnilistSetupMainDeps) {
return (): NotifyAnilistSetupMainDeps => ({
hasMpvClient: () => deps.hasMpvClient(),
showMpvOsd: (message: string) => deps.showMpvOsd(message),
showDesktopNotification: (title: string, options: { body: string }) =>
deps.showDesktopNotification(title, options),
logInfo: (message: string) => deps.logInfo(message),
});
}
export function createBuildConsumeAnilistSetupTokenFromUrlMainDepsHandler(
deps: ConsumeAnilistSetupTokenMainDeps,
) {
return (): ConsumeAnilistSetupTokenMainDeps => ({
consumeAnilistSetupCallbackUrl: (input) => deps.consumeAnilistSetupCallbackUrl(input),
saveToken: (token: string) => deps.saveToken(token),
setCachedToken: (token: string) => deps.setCachedToken(token),
setResolvedState: (resolvedAt: number) => deps.setResolvedState(resolvedAt),
setSetupPageOpened: (opened: boolean) => deps.setSetupPageOpened(opened),
onSuccess: () => deps.onSuccess(),
closeWindow: () => deps.closeWindow(),
});
}
export function createBuildHandleAnilistSetupProtocolUrlMainDepsHandler(
deps: HandleAnilistSetupProtocolUrlMainDeps,
) {
return (): HandleAnilistSetupProtocolUrlMainDeps => ({
consumeAnilistSetupTokenFromUrl: (rawUrl: string) => deps.consumeAnilistSetupTokenFromUrl(rawUrl),
logWarn: (message: string, details: unknown) => deps.logWarn(message, details),
});
}
export function createBuildRegisterSubminerProtocolClientMainDepsHandler(
deps: RegisterSubminerProtocolClientMainDeps,
) {
return (): RegisterSubminerProtocolClientMainDeps => ({
isDefaultApp: () => deps.isDefaultApp(),
getArgv: () => deps.getArgv(),
execPath: deps.execPath,
resolvePath: (value: string) => deps.resolvePath(value),
setAsDefaultProtocolClient: (scheme: string, path?: string, args?: string[]) =>
deps.setAsDefaultProtocolClient(scheme, path, args),
logWarn: (message: string, details?: unknown) => deps.logWarn(message, details),
});
}

View File

@@ -0,0 +1,84 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
createBuildHandleJellyfinAuthCommandsMainDepsHandler,
createBuildHandleJellyfinListCommandsMainDepsHandler,
createBuildHandleJellyfinPlayCommandMainDepsHandler,
createBuildHandleJellyfinRemoteAnnounceCommandMainDepsHandler,
} from './jellyfin-cli-main-deps';
test('jellyfin auth commands main deps builder maps callbacks', async () => {
const calls: string[] = [];
const deps = createBuildHandleJellyfinAuthCommandsMainDepsHandler({
patchRawConfig: () => calls.push('patch'),
authenticateWithPassword: async () => ({}) as never,
logInfo: (message) => calls.push(`info:${message}`),
})();
deps.patchRawConfig({ jellyfin: {} });
await deps.authenticateWithPassword('', '', '', {
deviceId: '',
clientName: '',
clientVersion: '',
});
deps.logInfo('ok');
assert.deepEqual(calls, ['patch', 'info:ok']);
});
test('jellyfin list commands main deps builder maps callbacks', async () => {
const calls: string[] = [];
const deps = createBuildHandleJellyfinListCommandsMainDepsHandler({
listJellyfinLibraries: async () => {
calls.push('libraries');
return [];
},
listJellyfinItems: async () => {
calls.push('items');
return [];
},
listJellyfinSubtitleTracks: async () => {
calls.push('subtitles');
return [];
},
logInfo: (message) => calls.push(`info:${message}`),
})();
await deps.listJellyfinLibraries({} as never, {} as never);
await deps.listJellyfinItems({} as never, {} as never, { libraryId: '', limit: 1 });
await deps.listJellyfinSubtitleTracks({} as never, {} as never, 'id');
deps.logInfo('done');
assert.deepEqual(calls, ['libraries', 'items', 'subtitles', 'info:done']);
});
test('jellyfin play command main deps builder maps callbacks', async () => {
const calls: string[] = [];
const deps = createBuildHandleJellyfinPlayCommandMainDepsHandler({
playJellyfinItemInMpv: async () => {
calls.push('play');
},
logWarn: (message) => calls.push(`warn:${message}`),
})();
await deps.playJellyfinItemInMpv({} as never);
deps.logWarn('missing');
assert.deepEqual(calls, ['play', 'warn:missing']);
});
test('jellyfin remote announce main deps builder maps callbacks', async () => {
const calls: string[] = [];
const session = { advertiseNow: async () => true };
const deps = createBuildHandleJellyfinRemoteAnnounceCommandMainDepsHandler({
startJellyfinRemoteSession: async () => {
calls.push('start');
},
getRemoteSession: () => session,
logInfo: (message) => calls.push(`info:${message}`),
logWarn: (message) => calls.push(`warn:${message}`),
})();
await deps.startJellyfinRemoteSession();
assert.equal(deps.getRemoteSession(), session);
deps.logInfo('visible');
deps.logWarn('not-visible');
assert.deepEqual(calls, ['start', 'info:visible', 'warn:not-visible']);
});

View File

@@ -0,0 +1,63 @@
import type {
createHandleJellyfinAuthCommands,
} from './jellyfin-cli-auth';
import type {
createHandleJellyfinListCommands,
} from './jellyfin-cli-list';
import type {
createHandleJellyfinPlayCommand,
} from './jellyfin-cli-play';
import type {
createHandleJellyfinRemoteAnnounceCommand,
} from './jellyfin-cli-remote-announce';
type HandleJellyfinAuthCommandsMainDeps = Parameters<typeof createHandleJellyfinAuthCommands>[0];
type HandleJellyfinListCommandsMainDeps = Parameters<typeof createHandleJellyfinListCommands>[0];
type HandleJellyfinPlayCommandMainDeps = Parameters<typeof createHandleJellyfinPlayCommand>[0];
type HandleJellyfinRemoteAnnounceCommandMainDeps = Parameters<
typeof createHandleJellyfinRemoteAnnounceCommand
>[0];
export function createBuildHandleJellyfinAuthCommandsMainDepsHandler(
deps: HandleJellyfinAuthCommandsMainDeps,
) {
return (): HandleJellyfinAuthCommandsMainDeps => ({
patchRawConfig: (patch) => deps.patchRawConfig(patch),
authenticateWithPassword: (serverUrl, username, password, clientInfo) =>
deps.authenticateWithPassword(serverUrl, username, password, clientInfo),
logInfo: (message: string) => deps.logInfo(message),
});
}
export function createBuildHandleJellyfinListCommandsMainDepsHandler(
deps: HandleJellyfinListCommandsMainDeps,
) {
return (): HandleJellyfinListCommandsMainDeps => ({
listJellyfinLibraries: (session, clientInfo) => deps.listJellyfinLibraries(session, clientInfo),
listJellyfinItems: (session, clientInfo, params) =>
deps.listJellyfinItems(session, clientInfo, params),
listJellyfinSubtitleTracks: (session, clientInfo, itemId) =>
deps.listJellyfinSubtitleTracks(session, clientInfo, itemId),
logInfo: (message: string) => deps.logInfo(message),
});
}
export function createBuildHandleJellyfinPlayCommandMainDepsHandler(
deps: HandleJellyfinPlayCommandMainDeps,
) {
return (): HandleJellyfinPlayCommandMainDeps => ({
playJellyfinItemInMpv: (params) => deps.playJellyfinItemInMpv(params),
logWarn: (message: string) => deps.logWarn(message),
});
}
export function createBuildHandleJellyfinRemoteAnnounceCommandMainDepsHandler(
deps: HandleJellyfinRemoteAnnounceCommandMainDeps,
) {
return (): HandleJellyfinRemoteAnnounceCommandMainDeps => ({
startJellyfinRemoteSession: () => deps.startJellyfinRemoteSession(),
getRemoteSession: () => deps.getRemoteSession(),
logInfo: (message: string) => deps.logInfo(message),
logWarn: (message: string) => deps.logWarn(message),
});
}

View File

@@ -0,0 +1,26 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
createBuildGetJellyfinClientInfoMainDepsHandler,
createBuildGetResolvedJellyfinConfigMainDepsHandler,
} from './jellyfin-client-info-main-deps';
test('get resolved jellyfin config main deps builder maps callbacks', () => {
const resolved = { jellyfin: { url: 'https://example.com' } };
const deps = createBuildGetResolvedJellyfinConfigMainDepsHandler({
getResolvedConfig: () => resolved as never,
})();
assert.equal(deps.getResolvedConfig(), resolved);
});
test('get jellyfin client info main deps builder maps callbacks', () => {
const configured = { clientName: 'Configured' };
const defaults = { clientName: 'Default' };
const deps = createBuildGetJellyfinClientInfoMainDepsHandler({
getResolvedJellyfinConfig: () => configured as never,
getDefaultJellyfinConfig: () => defaults as never,
})();
assert.equal(deps.getResolvedJellyfinConfig(), configured);
assert.equal(deps.getDefaultJellyfinConfig(), defaults);
});

View File

@@ -0,0 +1,24 @@
import type {
createGetJellyfinClientInfoHandler,
createGetResolvedJellyfinConfigHandler,
} from './jellyfin-client-info';
type GetResolvedJellyfinConfigMainDeps = Parameters<typeof createGetResolvedJellyfinConfigHandler>[0];
type GetJellyfinClientInfoMainDeps = Parameters<typeof createGetJellyfinClientInfoHandler>[0];
export function createBuildGetResolvedJellyfinConfigMainDepsHandler(
deps: GetResolvedJellyfinConfigMainDeps,
) {
return (): GetResolvedJellyfinConfigMainDeps => ({
getResolvedConfig: () => deps.getResolvedConfig(),
});
}
export function createBuildGetJellyfinClientInfoMainDepsHandler(
deps: GetJellyfinClientInfoMainDeps,
) {
return (): GetJellyfinClientInfoMainDeps => ({
getResolvedJellyfinConfig: () => deps.getResolvedJellyfinConfig(),
getDefaultJellyfinConfig: () => deps.getDefaultJellyfinConfig(),
});
}

View File

@@ -0,0 +1,72 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import type { CliArgs } from '../../cli/args';
import { createBuildRunJellyfinCommandMainDepsHandler } from './jellyfin-command-dispatch-main-deps';
test('run jellyfin command main deps builder maps callbacks', async () => {
const calls: string[] = [];
const args = { raw: [] } as unknown as CliArgs;
const config = {
serverUrl: 'http://localhost:8096',
accessToken: 'token',
userId: 'uid',
username: 'alice',
};
const clientInfo = { clientName: 'SubMiner' };
const deps = createBuildRunJellyfinCommandMainDepsHandler({
getJellyfinConfig: () => config,
defaultServerUrl: 'http://127.0.0.1:8096',
getJellyfinClientInfo: () => clientInfo,
handleAuthCommands: async () => {
calls.push('auth');
return false;
},
handleRemoteAnnounceCommand: async () => {
calls.push('remote');
return false;
},
handleListCommands: async () => {
calls.push('list');
return false;
},
handlePlayCommand: async () => {
calls.push('play');
return true;
},
})();
assert.equal(deps.getJellyfinConfig(), config);
assert.equal(deps.defaultServerUrl, 'http://127.0.0.1:8096');
assert.equal(deps.getJellyfinClientInfo(config), clientInfo);
await deps.handleAuthCommands({
args,
jellyfinConfig: config,
serverUrl: config.serverUrl,
clientInfo,
});
await deps.handleRemoteAnnounceCommand(args);
await deps.handleListCommands({
args,
session: {
serverUrl: config.serverUrl,
accessToken: config.accessToken,
userId: config.userId,
username: config.username,
},
clientInfo,
jellyfinConfig: config,
});
await deps.handlePlayCommand({
args,
session: {
serverUrl: config.serverUrl,
accessToken: config.accessToken,
userId: config.userId,
username: config.username,
},
clientInfo,
jellyfinConfig: config,
});
assert.deepEqual(calls, ['auth', 'remote', 'list', 'play']);
});

View File

@@ -0,0 +1,55 @@
import type { CliArgs } from '../../cli/args';
type JellyfinConfigBase = {
serverUrl?: string;
accessToken?: string;
userId?: string;
username?: string;
};
type JellyfinSession = {
serverUrl: string;
accessToken: string;
userId: string;
username: string;
};
export type RunJellyfinCommandMainDeps<TClientInfo, TConfig extends JellyfinConfigBase> = {
getJellyfinConfig: () => TConfig;
defaultServerUrl: string;
getJellyfinClientInfo: (config: TConfig) => TClientInfo;
handleAuthCommands: (params: {
args: CliArgs;
jellyfinConfig: TConfig;
serverUrl: string;
clientInfo: TClientInfo;
}) => Promise<boolean>;
handleRemoteAnnounceCommand: (args: CliArgs) => Promise<boolean>;
handleListCommands: (params: {
args: CliArgs;
session: JellyfinSession;
clientInfo: TClientInfo;
jellyfinConfig: TConfig;
}) => Promise<boolean>;
handlePlayCommand: (params: {
args: CliArgs;
session: JellyfinSession;
clientInfo: TClientInfo;
jellyfinConfig: TConfig;
}) => Promise<boolean>;
};
export function createBuildRunJellyfinCommandMainDepsHandler<
TClientInfo,
TConfig extends JellyfinConfigBase,
>(deps: RunJellyfinCommandMainDeps<TClientInfo, TConfig>) {
return (): RunJellyfinCommandMainDeps<TClientInfo, TConfig> => ({
getJellyfinConfig: () => deps.getJellyfinConfig(),
defaultServerUrl: deps.defaultServerUrl,
getJellyfinClientInfo: (config: TConfig) => deps.getJellyfinClientInfo(config),
handleAuthCommands: (params) => deps.handleAuthCommands(params),
handleRemoteAnnounceCommand: (args: CliArgs) => deps.handleRemoteAnnounceCommand(args),
handleListCommands: (params) => deps.handleListCommands(params),
handlePlayCommand: (params) => deps.handlePlayCommand(params),
});
}

View File

@@ -0,0 +1,88 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
createBuildEnsureMpvConnectedForJellyfinPlaybackMainDepsHandler,
createBuildLaunchMpvIdleForJellyfinPlaybackMainDepsHandler,
createBuildWaitForMpvConnectedMainDepsHandler,
} from './jellyfin-remote-connection-main-deps';
test('wait for mpv connected main deps builder maps callbacks', async () => {
const calls: string[] = [];
const client = { connected: false, connect: () => calls.push('connect') };
const deps = createBuildWaitForMpvConnectedMainDepsHandler({
getMpvClient: () => client,
now: () => 123,
sleep: async () => {
calls.push('sleep');
},
})();
assert.equal(deps.getMpvClient(), client);
assert.equal(deps.now(), 123);
await deps.sleep(10);
assert.deepEqual(calls, ['sleep']);
});
test('launch mpv for jellyfin main deps builder maps callbacks', () => {
const calls: string[] = [];
const proc = {
on: () => {},
unref: () => {
calls.push('unref');
},
};
const deps = createBuildLaunchMpvIdleForJellyfinPlaybackMainDepsHandler({
getSocketPath: () => '/tmp/mpv.sock',
platform: 'darwin',
execPath: '/tmp/subminer',
defaultMpvLogPath: '/tmp/mpv.log',
defaultMpvArgs: ['--no-config'],
removeSocketPath: (socketPath) => calls.push(`rm:${socketPath}`),
spawnMpv: (args) => {
calls.push(`spawn:${args.join(' ')}`);
return proc;
},
logWarn: (message) => calls.push(`warn:${message}`),
logInfo: (message) => calls.push(`info:${message}`),
})();
assert.equal(deps.getSocketPath(), '/tmp/mpv.sock');
assert.equal(deps.platform, 'darwin');
assert.equal(deps.execPath, '/tmp/subminer');
assert.equal(deps.defaultMpvLogPath, '/tmp/mpv.log');
assert.deepEqual(deps.defaultMpvArgs, ['--no-config']);
deps.removeSocketPath('/tmp/mpv.sock');
deps.spawnMpv(['--idle=yes']);
deps.logInfo('launched');
deps.logWarn('bad', null);
assert.deepEqual(calls, ['rm:/tmp/mpv.sock', 'spawn:--idle=yes', 'info:launched', 'warn:bad']);
});
test('ensure mpv connected for jellyfin main deps builder maps callbacks', async () => {
const calls: string[] = [];
const client = { connected: true, connect: () => {} };
const waitPromise = Promise.resolve(true);
const inFlight = Promise.resolve(false);
const deps = createBuildEnsureMpvConnectedForJellyfinPlaybackMainDepsHandler({
getMpvClient: () => client,
setMpvClient: () => calls.push('set-client'),
createMpvClient: () => client,
waitForMpvConnected: () => waitPromise,
launchMpvIdleForJellyfinPlayback: () => calls.push('launch'),
getAutoLaunchInFlight: () => inFlight,
setAutoLaunchInFlight: () => calls.push('set-in-flight'),
connectTimeoutMs: 7000,
autoLaunchTimeoutMs: 15000,
})();
assert.equal(deps.getMpvClient(), client);
deps.setMpvClient(client);
assert.equal(deps.createMpvClient(), client);
assert.equal(await deps.waitForMpvConnected(1), true);
deps.launchMpvIdleForJellyfinPlayback();
assert.equal(deps.getAutoLaunchInFlight(), inFlight);
deps.setAutoLaunchInFlight(null);
assert.equal(deps.connectTimeoutMs, 7000);
assert.equal(deps.autoLaunchTimeoutMs, 15000);
assert.deepEqual(calls, ['set-client', 'launch', 'set-in-flight']);
});

View File

@@ -0,0 +1,45 @@
import type {
EnsureMpvConnectedDeps,
LaunchMpvForJellyfinDeps,
WaitForMpvConnectedDeps,
} from './jellyfin-remote-connection';
export function createBuildWaitForMpvConnectedMainDepsHandler(deps: WaitForMpvConnectedDeps) {
return (): WaitForMpvConnectedDeps => ({
getMpvClient: () => deps.getMpvClient(),
now: () => deps.now(),
sleep: (delayMs: number) => deps.sleep(delayMs),
});
}
export function createBuildLaunchMpvIdleForJellyfinPlaybackMainDepsHandler(
deps: LaunchMpvForJellyfinDeps,
) {
return (): LaunchMpvForJellyfinDeps => ({
getSocketPath: () => deps.getSocketPath(),
platform: deps.platform,
execPath: deps.execPath,
defaultMpvLogPath: deps.defaultMpvLogPath,
defaultMpvArgs: deps.defaultMpvArgs,
removeSocketPath: (socketPath: string) => deps.removeSocketPath(socketPath),
spawnMpv: (args: string[]) => deps.spawnMpv(args),
logWarn: (message: string, error: unknown) => deps.logWarn(message, error),
logInfo: (message: string) => deps.logInfo(message),
});
}
export function createBuildEnsureMpvConnectedForJellyfinPlaybackMainDepsHandler(
deps: EnsureMpvConnectedDeps,
) {
return (): EnsureMpvConnectedDeps => ({
getMpvClient: () => deps.getMpvClient(),
setMpvClient: (client) => deps.setMpvClient(client),
createMpvClient: () => deps.createMpvClient(),
waitForMpvConnected: (timeoutMs: number) => deps.waitForMpvConnected(timeoutMs),
launchMpvIdleForJellyfinPlayback: () => deps.launchMpvIdleForJellyfinPlayback(),
getAutoLaunchInFlight: () => deps.getAutoLaunchInFlight(),
setAutoLaunchInFlight: (promise) => deps.setAutoLaunchInFlight(promise),
connectTimeoutMs: deps.connectTimeoutMs,
autoLaunchTimeoutMs: deps.autoLaunchTimeoutMs,
});
}

View File

@@ -0,0 +1,25 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
createBuildApplyJellyfinMpvDefaultsMainDepsHandler,
createBuildGetDefaultSocketPathMainDepsHandler,
} from './mpv-jellyfin-defaults-main-deps';
test('apply jellyfin mpv defaults main deps builder maps callbacks', () => {
const calls: string[] = [];
const deps = createBuildApplyJellyfinMpvDefaultsMainDepsHandler({
sendMpvCommandRuntime: (_client, command) => calls.push(command.join(':')),
jellyfinLangPref: 'ja,jp',
})();
deps.sendMpvCommandRuntime({}, ['set_property', 'aid', 'auto']);
assert.equal(deps.jellyfinLangPref, 'ja,jp');
assert.deepEqual(calls, ['set_property:aid:auto']);
});
test('get default socket path main deps builder maps platform', () => {
const deps = createBuildGetDefaultSocketPathMainDepsHandler({
platform: 'darwin',
})();
assert.equal(deps.platform, 'darwin');
});

View File

@@ -0,0 +1,24 @@
import type {
createApplyJellyfinMpvDefaultsHandler,
createGetDefaultSocketPathHandler,
} from './mpv-jellyfin-defaults';
type ApplyJellyfinMpvDefaultsMainDeps = Parameters<typeof createApplyJellyfinMpvDefaultsHandler>[0];
type GetDefaultSocketPathMainDeps = Parameters<typeof createGetDefaultSocketPathHandler>[0];
export function createBuildApplyJellyfinMpvDefaultsMainDepsHandler(
deps: ApplyJellyfinMpvDefaultsMainDeps,
) {
return (): ApplyJellyfinMpvDefaultsMainDeps => ({
sendMpvCommandRuntime: (client, command) => deps.sendMpvCommandRuntime(client, command),
jellyfinLangPref: deps.jellyfinLangPref,
});
}
export function createBuildGetDefaultSocketPathMainDepsHandler(
deps: GetDefaultSocketPathMainDeps,
) {
return (): GetDefaultSocketPathMainDeps => ({
platform: deps.platform,
});
}

View File

@@ -0,0 +1,30 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
createBuildOverlayContentMeasurementStoreMainDepsHandler,
createBuildOverlayModalRuntimeMainDepsHandler,
} from './overlay-bootstrap-main-deps';
test('overlay content measurement store main deps builder maps callbacks', () => {
const calls: string[] = [];
const deps = createBuildOverlayContentMeasurementStoreMainDepsHandler({
now: () => 42,
warn: (message) => calls.push(`warn:${message}`),
})();
assert.equal(deps.now(), 42);
deps.warn('bad payload');
assert.deepEqual(calls, ['warn:bad payload']);
});
test('overlay modal runtime main deps builder maps window resolvers', () => {
const mainWindow = { id: 'main' };
const invisibleWindow = { id: 'invisible' };
const deps = createBuildOverlayModalRuntimeMainDepsHandler({
getMainWindow: () => mainWindow as never,
getInvisibleWindow: () => invisibleWindow as never,
})();
assert.equal(deps.getMainWindow(), mainWindow);
assert.equal(deps.getInvisibleWindow(), invisibleWindow);
});

View File

@@ -0,0 +1,22 @@
import type { OverlayWindowResolver } from '../overlay-runtime';
type OverlayContentMeasurementStoreMainDeps = {
now: () => number;
warn: (message: string) => void;
};
export function createBuildOverlayContentMeasurementStoreMainDepsHandler(
deps: OverlayContentMeasurementStoreMainDeps,
) {
return (): OverlayContentMeasurementStoreMainDeps => ({
now: () => deps.now(),
warn: (message: string) => deps.warn(message),
});
}
export function createBuildOverlayModalRuntimeMainDepsHandler(deps: OverlayWindowResolver) {
return (): OverlayWindowResolver => ({
getMainWindow: () => deps.getMainWindow(),
getInvisibleWindow: () => deps.getInvisibleWindow(),
});
}

View File

@@ -0,0 +1,78 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
createBuildBroadcastRuntimeOptionsChangedMainDepsHandler,
createBuildGetRuntimeOptionsStateMainDepsHandler,
createBuildOpenRuntimeOptionsPaletteMainDepsHandler,
createBuildRestorePreviousSecondarySubVisibilityMainDepsHandler,
createBuildSendToActiveOverlayWindowMainDepsHandler,
createBuildSetOverlayDebugVisualizationEnabledMainDepsHandler,
} from './overlay-runtime-main-actions-main-deps';
test('get runtime options state main deps builder maps callbacks', () => {
const manager = { listOptions: () => [] };
const deps = createBuildGetRuntimeOptionsStateMainDepsHandler({
getRuntimeOptionsManager: () => manager,
})();
assert.equal(deps.getRuntimeOptionsManager(), manager);
});
test('restore secondary sub visibility main deps builder maps callbacks', () => {
const deps = createBuildRestorePreviousSecondarySubVisibilityMainDepsHandler({
getMpvClient: () => ({ connected: true, restorePreviousSecondarySubVisibility: () => {} }),
})();
assert.equal(deps.getMpvClient()?.connected, true);
});
test('broadcast runtime options changed main deps builder maps callbacks', () => {
const calls: string[] = [];
const deps = createBuildBroadcastRuntimeOptionsChangedMainDepsHandler({
broadcastRuntimeOptionsChangedRuntime: () => calls.push('broadcast-runtime'),
getRuntimeOptionsState: () => [],
broadcastToOverlayWindows: (channel) => calls.push(channel),
})();
deps.broadcastRuntimeOptionsChangedRuntime(() => [], () => {});
deps.broadcastToOverlayWindows('runtime-options:changed');
assert.deepEqual(deps.getRuntimeOptionsState(), []);
assert.deepEqual(calls, ['broadcast-runtime', 'runtime-options:changed']);
});
test('send to active overlay window main deps builder maps callbacks', () => {
const calls: string[] = [];
const deps = createBuildSendToActiveOverlayWindowMainDepsHandler({
sendToActiveOverlayWindowRuntime: () => {
calls.push('send');
return true;
},
})();
assert.equal(deps.sendToActiveOverlayWindowRuntime('x'), true);
assert.deepEqual(calls, ['send']);
});
test('set overlay debug visualization main deps builder maps callbacks', () => {
const calls: string[] = [];
const deps = createBuildSetOverlayDebugVisualizationEnabledMainDepsHandler({
setOverlayDebugVisualizationEnabledRuntime: () => calls.push('set-runtime'),
getCurrentEnabled: () => false,
setCurrentEnabled: () => calls.push('set-current'),
broadcastToOverlayWindows: () => calls.push('broadcast'),
})();
deps.setOverlayDebugVisualizationEnabledRuntime(false, true, () => {}, () => {});
assert.equal(deps.getCurrentEnabled(), false);
deps.setCurrentEnabled(true);
deps.broadcastToOverlayWindows('overlay:debug');
assert.deepEqual(calls, ['set-runtime', 'set-current', 'broadcast']);
});
test('open runtime options palette main deps builder maps callbacks', () => {
const calls: string[] = [];
const deps = createBuildOpenRuntimeOptionsPaletteMainDepsHandler({
openRuntimeOptionsPaletteRuntime: () => calls.push('open'),
})();
deps.openRuntimeOptionsPaletteRuntime();
assert.deepEqual(calls, ['open']);
});

View File

@@ -0,0 +1,89 @@
import {
createBroadcastRuntimeOptionsChangedHandler,
createGetRuntimeOptionsStateHandler,
createOpenRuntimeOptionsPaletteHandler,
createRestorePreviousSecondarySubVisibilityHandler,
createSendToActiveOverlayWindowHandler,
createSetOverlayDebugVisualizationEnabledHandler,
} from './overlay-runtime-main-actions';
type GetRuntimeOptionsStateMainDeps = Parameters<typeof createGetRuntimeOptionsStateHandler>[0];
type RestorePreviousSecondarySubVisibilityMainDeps = Parameters<
typeof createRestorePreviousSecondarySubVisibilityHandler
>[0];
type BroadcastRuntimeOptionsChangedMainDeps = Parameters<
typeof createBroadcastRuntimeOptionsChangedHandler
>[0];
type SendToActiveOverlayWindowMainDeps = Parameters<typeof createSendToActiveOverlayWindowHandler>[0];
type SetOverlayDebugVisualizationEnabledMainDeps = Parameters<
typeof createSetOverlayDebugVisualizationEnabledHandler
>[0];
type OpenRuntimeOptionsPaletteMainDeps = Parameters<typeof createOpenRuntimeOptionsPaletteHandler>[0];
export function createBuildGetRuntimeOptionsStateMainDepsHandler(
deps: GetRuntimeOptionsStateMainDeps,
) {
return (): GetRuntimeOptionsStateMainDeps => ({
getRuntimeOptionsManager: () => deps.getRuntimeOptionsManager(),
});
}
export function createBuildRestorePreviousSecondarySubVisibilityMainDepsHandler(
deps: RestorePreviousSecondarySubVisibilityMainDeps,
) {
return (): RestorePreviousSecondarySubVisibilityMainDeps => ({
getMpvClient: () => deps.getMpvClient(),
});
}
export function createBuildBroadcastRuntimeOptionsChangedMainDepsHandler(
deps: BroadcastRuntimeOptionsChangedMainDeps,
) {
return (): BroadcastRuntimeOptionsChangedMainDeps => ({
broadcastRuntimeOptionsChangedRuntime: (getRuntimeOptionsState, broadcastToOverlayWindows) =>
deps.broadcastRuntimeOptionsChangedRuntime(getRuntimeOptionsState, broadcastToOverlayWindows),
getRuntimeOptionsState: () => deps.getRuntimeOptionsState(),
broadcastToOverlayWindows: (channel: string, ...args: unknown[]) =>
deps.broadcastToOverlayWindows(channel, ...args),
});
}
export function createBuildSendToActiveOverlayWindowMainDepsHandler(
deps: SendToActiveOverlayWindowMainDeps,
) {
return (): SendToActiveOverlayWindowMainDeps => ({
sendToActiveOverlayWindowRuntime: (channel, payload, runtimeOptions) =>
deps.sendToActiveOverlayWindowRuntime(channel, payload, runtimeOptions),
});
}
export function createBuildSetOverlayDebugVisualizationEnabledMainDepsHandler(
deps: SetOverlayDebugVisualizationEnabledMainDeps,
) {
return (): SetOverlayDebugVisualizationEnabledMainDeps => ({
setOverlayDebugVisualizationEnabledRuntime: (
currentEnabled,
nextEnabled,
setCurrentEnabled,
broadcastToOverlayWindows,
) =>
deps.setOverlayDebugVisualizationEnabledRuntime(
currentEnabled,
nextEnabled,
setCurrentEnabled,
broadcastToOverlayWindows,
),
getCurrentEnabled: () => deps.getCurrentEnabled(),
setCurrentEnabled: (enabled: boolean) => deps.setCurrentEnabled(enabled),
broadcastToOverlayWindows: (channel: string, ...args: unknown[]) =>
deps.broadcastToOverlayWindows(channel, ...args),
});
}
export function createBuildOpenRuntimeOptionsPaletteMainDepsHandler(
deps: OpenRuntimeOptionsPaletteMainDeps,
) {
return (): OpenRuntimeOptionsPaletteMainDeps => ({
openRuntimeOptionsPaletteRuntime: () => deps.openRuntimeOptionsPaletteRuntime(),
});
}

View File

@@ -0,0 +1,99 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
createBuildAnilistStateRuntimeMainDepsHandler,
createBuildConfigDerivedRuntimeMainDepsHandler,
createBuildImmersionMediaRuntimeMainDepsHandler,
createBuildMainSubsyncRuntimeMainDepsHandler,
} from './runtime-bootstrap-main-deps';
test('immersion media runtime main deps builder maps callbacks', async () => {
const calls: string[] = [];
const deps = createBuildImmersionMediaRuntimeMainDepsHandler({
getResolvedConfig: () => ({ immersionTracking: { dbPath: '/tmp/db.sqlite' } }),
defaultImmersionDbPath: '/tmp/default.sqlite',
getTracker: () => ({ handleMediaChange: () => calls.push('track') }),
getMpvClient: () => ({ connected: true }),
getCurrentMediaPath: () => '/tmp/media.mkv',
getCurrentMediaTitle: () => 'Title',
sleep: async () => {
calls.push('sleep');
},
seedWaitMs: 25,
seedAttempts: 3,
logDebug: (message) => calls.push(`debug:${message}`),
logInfo: (message) => calls.push(`info:${message}`),
})();
assert.equal(deps.defaultImmersionDbPath, '/tmp/default.sqlite');
assert.deepEqual(deps.getResolvedConfig(), { immersionTracking: { dbPath: '/tmp/db.sqlite' } });
assert.deepEqual(deps.getMpvClient(), { connected: true });
assert.equal(deps.getCurrentMediaPath(), '/tmp/media.mkv');
assert.equal(deps.getCurrentMediaTitle(), 'Title');
assert.equal(deps.seedWaitMs, 25);
assert.equal(deps.seedAttempts, 3);
await deps.sleep?.(1);
deps.logDebug('a');
deps.logInfo('b');
assert.deepEqual(calls, ['sleep', 'debug:a', 'info:b']);
});
test('anilist state runtime main deps builder maps callbacks', () => {
const calls: string[] = [];
const deps = createBuildAnilistStateRuntimeMainDepsHandler({
getClientSecretState: () => ({ status: 'resolved' } as never),
setClientSecretState: () => calls.push('set-client'),
getRetryQueueState: () => ({ pending: 1 } as never),
setRetryQueueState: () => calls.push('set-queue'),
getUpdateQueueSnapshot: () => ({ pending: 2 } as never),
clearStoredToken: () => calls.push('clear-stored'),
clearCachedAccessToken: () => calls.push('clear-cached'),
})();
assert.deepEqual(deps.getClientSecretState(), { status: 'resolved' });
assert.deepEqual(deps.getRetryQueueState(), { pending: 1 });
assert.deepEqual(deps.getUpdateQueueSnapshot(), { pending: 2 });
deps.setClientSecretState({} as never);
deps.setRetryQueueState({} as never);
deps.clearStoredToken();
deps.clearCachedAccessToken();
assert.deepEqual(calls, ['set-client', 'set-queue', 'clear-stored', 'clear-cached']);
});
test('config derived runtime main deps builder maps callbacks', () => {
const deps = createBuildConfigDerivedRuntimeMainDepsHandler({
getResolvedConfig: () => ({ jimaku: {} } as never),
getRuntimeOptionsManager: () => null,
platform: 'darwin',
defaultJimakuLanguagePreference: 'ja',
defaultJimakuMaxEntryResults: 20,
defaultJimakuApiBaseUrl: 'https://api.example.com',
})();
assert.deepEqual(deps.getResolvedConfig(), { jimaku: {} });
assert.equal(deps.getRuntimeOptionsManager(), null);
assert.equal(deps.platform, 'darwin');
assert.equal(deps.defaultJimakuLanguagePreference, 'ja');
assert.equal(deps.defaultJimakuMaxEntryResults, 20);
assert.equal(deps.defaultJimakuApiBaseUrl, 'https://api.example.com');
});
test('main subsync runtime main deps builder maps callbacks', () => {
const calls: string[] = [];
const deps = createBuildMainSubsyncRuntimeMainDepsHandler({
getMpvClient: () => ({ connected: true }) as never,
getResolvedConfig: () => ({ subsync: {} } as never),
getSubsyncInProgress: () => true,
setSubsyncInProgress: () => calls.push('set-progress'),
showMpvOsd: (text) => calls.push(`osd:${text}`),
openManualPicker: () => calls.push('open-picker'),
})();
assert.deepEqual(deps.getMpvClient(), { connected: true });
assert.deepEqual(deps.getResolvedConfig(), { subsync: {} });
assert.equal(deps.getSubsyncInProgress(), true);
deps.setSubsyncInProgress(false);
deps.showMpvOsd('ready');
deps.openManualPicker({} as never);
assert.deepEqual(calls, ['set-progress', 'osd:ready', 'open-picker']);
});

View File

@@ -0,0 +1,54 @@
import type { AnilistStateRuntimeDeps } from './anilist-state';
import type { ConfigDerivedRuntimeDeps } from './config-derived';
import type { ImmersionMediaRuntimeDeps } from './immersion-media';
import type { MainSubsyncRuntimeDeps } from './subsync-runtime';
export function createBuildImmersionMediaRuntimeMainDepsHandler(deps: ImmersionMediaRuntimeDeps) {
return (): ImmersionMediaRuntimeDeps => ({
getResolvedConfig: () => deps.getResolvedConfig(),
defaultImmersionDbPath: deps.defaultImmersionDbPath,
getTracker: () => deps.getTracker(),
getMpvClient: () => deps.getMpvClient(),
getCurrentMediaPath: () => deps.getCurrentMediaPath(),
getCurrentMediaTitle: () => deps.getCurrentMediaTitle(),
sleep: deps.sleep,
seedWaitMs: deps.seedWaitMs,
seedAttempts: deps.seedAttempts,
logDebug: (message: string) => deps.logDebug(message),
logInfo: (message: string) => deps.logInfo(message),
});
}
export function createBuildAnilistStateRuntimeMainDepsHandler(deps: AnilistStateRuntimeDeps) {
return (): AnilistStateRuntimeDeps => ({
getClientSecretState: () => deps.getClientSecretState(),
setClientSecretState: (next) => deps.setClientSecretState(next),
getRetryQueueState: () => deps.getRetryQueueState(),
setRetryQueueState: (next) => deps.setRetryQueueState(next),
getUpdateQueueSnapshot: () => deps.getUpdateQueueSnapshot(),
clearStoredToken: () => deps.clearStoredToken(),
clearCachedAccessToken: () => deps.clearCachedAccessToken(),
});
}
export function createBuildConfigDerivedRuntimeMainDepsHandler(deps: ConfigDerivedRuntimeDeps) {
return (): ConfigDerivedRuntimeDeps => ({
getResolvedConfig: () => deps.getResolvedConfig(),
getRuntimeOptionsManager: () => deps.getRuntimeOptionsManager(),
platform: deps.platform,
defaultJimakuLanguagePreference: deps.defaultJimakuLanguagePreference,
defaultJimakuMaxEntryResults: deps.defaultJimakuMaxEntryResults,
defaultJimakuApiBaseUrl: deps.defaultJimakuApiBaseUrl,
});
}
export function createBuildMainSubsyncRuntimeMainDepsHandler(deps: MainSubsyncRuntimeDeps) {
return (): MainSubsyncRuntimeDeps => ({
getMpvClient: () => deps.getMpvClient(),
getResolvedConfig: () => deps.getResolvedConfig(),
getSubsyncInProgress: () => deps.getSubsyncInProgress(),
setSubsyncInProgress: (inProgress: boolean) => deps.setSubsyncInProgress(inProgress),
showMpvOsd: (text: string) => deps.showMpvOsd(text),
openManualPicker: (payload) => deps.openManualPicker(payload),
});
}