fix(electron): skip the login shell PATH wait for Flatpak host launches

In Flatpak, players start through `flatpak-spawn --host`, which resolves
the name with the host's PATH; the sandbox's login shell lookup cannot
change it. The launch handlers now decide from the same launch context
the player uses: no wait in flatpak-host mode, otherwise only for a bare
player name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-03 09:20:51 +02:00
1 parent e60a65b3d0
commit dbdec94685
2 files changed
+52 -14

No files matched your search

@@ -4,6 +4,11 @@ jest.mock('electron', () => ({
},
}));
jest.mock('fs', () => {
const actual = jest.requireActual<typeof import('fs')>('fs');
return { ...actual, existsSync: jest.fn(actual.existsSync) };
});
jest.mock('child_process', () => ({
spawn: jest.fn(),
}));
@@ -37,6 +42,7 @@ jest.mock('../services/stalker-playback-context.service', () => ({
import { ipcMain } from 'electron';
import { spawn, type ChildProcess } from 'child_process';
import { EventEmitter } from 'events';
import * as fs from 'fs';
import {
MPV_PLAYER_PATH,
store,
@@ -528,6 +534,39 @@ describe('external player launch handlers', () => {
}
);
it.each(['OPEN_MPV_PLAYER', 'OPEN_VLC_PLAYER'])(
'%s does not wait for a Flatpak host launch of a bare name',
async (channel) => {
const originalPlatform = process.platform;
const existsSync = fs.existsSync as unknown as jest.Mock;
existsSync.mockImplementation(
(candidate: unknown) => String(candidate) === '/.flatpak-info'
);
Object.defineProperty(process, 'platform', { value: 'linux' });
configurePlayerPath(channel, 'player-on-host-path');
try {
await launchUntilSpawn(
channel,
new Promise<void>(() => undefined),
async () => {
await waitForSpawnCallCount(1);
expect(waitForLoginShellPath).not.toHaveBeenCalled();
expect(
(spawn as unknown as jest.Mock).mock.calls[0][0]
).toBe('flatpak-spawn');
}
);
} finally {
existsSync.mockImplementation(
jest.requireActual<typeof import('fs')>('fs').existsSync
);
Object.defineProperty(process, 'platform', {
value: originalPlatform,
});
}
}
);
it.each(['OPEN_MPV_PLAYER', 'OPEN_VLC_PLAYER'])(
'%s starts a configured executable path without waiting',
async (channel) => {
@@ -1,6 +1,7 @@
import { ipcMain } from 'electron';
import {
CLOSE_EXTERNAL_PLAYER_SESSION,
type ExternalPlayerName,
PLAYBACK_SET_KEEP_AWAKE,
PlayerContentInfo,
} from '@iptvnator/shared/interfaces';
@@ -12,10 +13,8 @@ import {
VLC_PLAYER_PATH,
} from '../services/store.service';
import {
getDefaultMpvPath,
getDefaultVlcPath,
normalizeCustomPlayerPath,
normalizePlayerPathForStore,
resolveExternalPlayerLaunchContext as resolveLaunchContext,
} from './external-player-launch-context';
import {
externalPlayerSessions,
@@ -43,10 +42,16 @@ export {
/**
* A player resolved to a bare name (no configured path, no well-known
* install found) is looked up through PATH, so it waits for the login shell
* PATH; a path to an executable starts right away.
* PATH; a path to an executable starts right away. So does a Flatpak host
* launch: `flatpak-spawn --host` resolves the name with the host's PATH,
* which the sandbox's login shell lookup cannot change.
*/
async function waitForPathIfBareName(playerPath: string): Promise<void> {
if (!/[\\/]/.test(playerPath)) {
async function waitForPathIfBareName(
player: ExternalPlayerName,
configuredPath: string | undefined
): Promise<void> {
const context = resolveLaunchContext(player, configuredPath);
if (context.mode !== 'flatpak-host' && !/[\\/]/.test(context.playerPath)) {
await waitForLoginShellPath();
}
}
@@ -71,10 +76,7 @@ ipcMain.handle(
startTime?: number,
headers?: Record<string, string>
) => {
await waitForPathIfBareName(
normalizeCustomPlayerPath(store.get(MPV_PLAYER_PATH)) ??
getDefaultMpvPath()
);
await waitForPathIfBareName('mpv', store.get(MPV_PLAYER_PATH));
return openMpvPlayer({
url,
title,
@@ -118,10 +120,7 @@ ipcMain.handle(
startTime?: number,
headers?: Record<string, string>
) => {
await waitForPathIfBareName(
normalizeCustomPlayerPath(store.get(VLC_PLAYER_PATH)) ??
getDefaultVlcPath()
);
await waitForPathIfBareName('vlc', store.get(VLC_PLAYER_PATH));
return openVlcPlayer({
url,
title,