import type { IpcRenderer } from 'electron'; import { IPC_CHANNELS } from './shared/ipc/contracts'; import type { AnimeBrowserAPI, AnimeBrowserBridgeState, AnimeBrowserDetails, AnimeBrowserEpisode, AnimeBrowserEpisodeWatchState, AnimeBrowserPlayRequest, AnimeBrowserPlayResult, AnimeBrowserPlaybackState, AnimeBrowserQueueState, AnimeBrowserSearchResult, AnimeBrowserSearchUpdate, AnimeBrowserSetWatchedRequest, AnimeBrowserSnapshot, AnimeBrowserWatchStateRequest, AvailableExtensionsResult, SourcePreferenceView, } from './types/anime-browser'; type AnimeBrowserIpcRenderer = Pick; /** * Build the browser bridge for one renderer. The opaque session id keeps * source selection and in-flight searches independent between the standalone * window and the player-hosted modal. */ export function createAnimeBrowserAPI(ipcRenderer: AnimeBrowserIpcRenderer): AnimeBrowserAPI { const request = IPC_CHANNELS.request; // Sandboxed Electron preloads cannot require arbitrary Node built-ins. // Web Crypto is available in the isolated renderer world; the fallback only // covers test or legacy contexts where randomUUID is absent. const sessionId = globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`; return { getSnapshot: (): Promise => ipcRenderer.invoke(request.animeBrowserGetSnapshot, sessionId), ensureBridge: (): Promise => ipcRenderer.invoke(request.animeBrowserEnsureBridge, sessionId), updateBridge: (): Promise => ipcRenderer.invoke(request.animeBrowserUpdateBridge), selectSource: (sourceId: string): Promise => ipcRenderer.invoke(request.animeBrowserSelectSource, sessionId, sourceId), search: (query: string, page?: number): Promise => ipcRenderer.invoke(request.animeBrowserSearch, sessionId, query, page), getPopular: (page?: number): Promise => ipcRenderer.invoke(request.animeBrowserGetPopular, sessionId, page), getDetails: (animeUrl: string, sourceId?: string): Promise => ipcRenderer.invoke(request.animeBrowserGetDetails, sessionId, animeUrl, sourceId), getEpisodes: (animeUrl: string, sourceId?: string): Promise => ipcRenderer.invoke(request.animeBrowserGetEpisodes, sessionId, animeUrl, sourceId), getWatchState: ( watchRequest: AnimeBrowserWatchStateRequest, ): Promise => ipcRenderer.invoke(request.animeBrowserGetWatchState, watchRequest), setWatched: ( watchedRequest: AnimeBrowserSetWatchedRequest, ): Promise => ipcRenderer.invoke(request.animeBrowserSetWatched, watchedRequest), playEpisode: (playRequest: AnimeBrowserPlayRequest): Promise => ipcRenderer.invoke(request.animeBrowserPlayEpisode, playRequest), queueEpisode: (playRequest: AnimeBrowserPlayRequest): Promise => ipcRenderer.invoke(request.animeBrowserQueueEpisode, playRequest), dequeueEpisode: (sourceId: string, episodeUrl: string): Promise => ipcRenderer.invoke(request.animeBrowserDequeueEpisode, sourceId, episodeUrl), clearQueue: (): Promise => ipcRenderer.invoke(request.animeBrowserClearQueue), getQueue: (): Promise => ipcRenderer.invoke(request.animeBrowserGetQueue), getPlaybackState: (): Promise => ipcRenderer.invoke(request.animeBrowserGetPlaybackState), isPlaying: (): Promise => ipcRenderer.invoke(request.animeBrowserIsPlaying), getPreferences: (sourceId: string): Promise => ipcRenderer.invoke(request.animeBrowserGetPreferences, sourceId), setPreference: ( sourceId: string, key: string, value: string | string[] | boolean, ): Promise => ipcRenderer.invoke(request.animeBrowserSetPreference, sourceId, key, value), listAvailableExtensions: (): Promise => ipcRenderer.invoke(request.animeBrowserListAvailableExtensions), installExtension: (pkg: string): Promise => ipcRenderer.invoke(request.animeBrowserInstallExtension, pkg), removeExtension: (pkg: string): Promise => ipcRenderer.invoke(request.animeBrowserRemoveExtension, pkg), rescanExtensions: (): Promise => ipcRenderer.invoke(request.animeBrowserRescanExtensions), addRepo: (url: string): Promise => ipcRenderer.invoke(request.animeBrowserAddRepo, url), removeRepo: (url: string): Promise => ipcRenderer.invoke(request.animeBrowserRemoveRepo, url), onBridgeState: (listener: (state: AnimeBrowserBridgeState) => void): (() => void) => { const handler = (_event: unknown, state: AnimeBrowserBridgeState): void => listener(state); ipcRenderer.on(IPC_CHANNELS.event.animeBrowserBridgeState, handler); return () => ipcRenderer.removeListener(IPC_CHANNELS.event.animeBrowserBridgeState, handler); }, onSearchUpdate: (listener: (update: AnimeBrowserSearchUpdate) => void): (() => void) => { const handler = (_event: unknown, update: AnimeBrowserSearchUpdate): void => listener(update); ipcRenderer.on(IPC_CHANNELS.event.animeBrowserSearchUpdate, handler); return () => ipcRenderer.removeListener(IPC_CHANNELS.event.animeBrowserSearchUpdate, handler); }, onQueueState: (listener: (state: AnimeBrowserQueueState) => void): (() => void) => { const handler = (_event: unknown, state: AnimeBrowserQueueState): void => listener(state); ipcRenderer.on(IPC_CHANNELS.event.animeBrowserQueueState, handler); return () => ipcRenderer.removeListener(IPC_CHANNELS.event.animeBrowserQueueState, handler); }, onPlaybackState: ( listener: (state: AnimeBrowserPlaybackState | null) => void, ): (() => void) => { const handler = (_event: unknown, state: AnimeBrowserPlaybackState | null): void => listener(state); ipcRenderer.on(IPC_CHANNELS.event.animeBrowserPlaybackState, handler); return () => ipcRenderer.removeListener(IPC_CHANNELS.event.animeBrowserPlaybackState, handler); }, }; }