fix(playlist): open playlists handed over by the OS (#1299)

Opening an .m3u/.m3u8 file from the command line or a file association did
nothing. The renderer parsed `process.argv` and sent an `OPEN_FILE` IPC event
that had no `ipcMain` handler and no preload channel, so `sendIpcEvent` logged
it as an unknown type and dropped it.

The path now belongs to the main process, which is where the OS actually
delivers it:

- argv is parsed on first launch (skipping the executable and Chromium
  switches) and normalized to an absolute path;
- macOS gets an `open-file` listener registered before `whenReady`, since
  Launch Services never puts the path in argv;
- the single-instance guard forwards a second launch's argv and working
  directory instead of discarding them, so opening a playlist against a
  running app works too.

Requests are queued in the main process until the renderer subscribes to the
`OPEN_FILE` push and drains the queue, which closes the startup race. The
import itself reuses the existing file path, so persistence, playlist-scoped
EPG and the navigation to the new playlist behave exactly like a dialog
import; a failed open now surfaces a snackbar instead of silence.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5 authored and GitHub committed 2026-07-28 20:28:29 +02:00
1 parent 80af9257a0
commit f80eb4d1b9
43 files changed
+1702 -62

No files matched your search

@@ -177,6 +177,18 @@ export interface ElectronBridgeWindowState {
isFullScreen: boolean;
}
/**
* A playlist file the operating system asked the app to open — a command line
* argument, a file association double-click, or macOS' `open-file` event. The
* main process resolves the path to an absolute one before handing it over.
*/
export interface ElectronBridgePlaylistOpenRequest {
fileName: string;
filePath: string;
/** Pass back to `acknowledgePlaylistOpenRequest` once received. */
requestId: string;
}
export interface ElectronBridgeAppUpdateRelease {
version: string;
releaseDate?: string;
@@ -561,6 +573,21 @@ export interface ElectronBridgeApi {
title: string
) => Promise<Playlist>;
openPlaylistFromFile: () => Promise<Playlist | null>;
onPlaylistOpenRequest: (
callback: (request: ElectronBridgePlaylistOpenRequest) => void
) => () => void;
/**
* Tells the main process that an `onPlaylistOpenRequest` listener is
* attached, so it can push the playlist files the OS handed over — both
* the ones queued before this renderer existed and any that arrive later.
* Call it *after* subscribing.
*/
announcePlaylistOpenListener: () => Promise<void>;
/**
* Confirms a pushed request reached this renderer. Until it does, the main
* process keeps the request and replays it to the next renderer.
*/
acknowledgePlaylistOpenRequest: (requestId: string) => Promise<void>;
getPathForFile: (file: File) => string;
saveFileDialog: (
defaultPath: string,