import "./env"; import { app, BrowserWindow, components, dialog, ipcMain, session, shell } from "electron"; import { join } from "node:path"; import { startApiServer } from "./apiServer"; import { PORT } from "./constants"; import { applySettings, loadSettings, saveSettings } from "./settings"; const APP_URL = `http://127.0.0.1:${PORT}`; const LOGIN_HOSTS = ["spotify.com", "google.com", "facebook.com", "apple.com"]; let mainWindow: BrowserWindow | undefined; if (!app.requestSingleInstanceLock()) { app.quit(); } else { app.on("second-instance", () => { if (!mainWindow) return; if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.focus(); }); app.whenReady().then(start); } async function start() { app.userAgentFallback = app.userAgentFallback.replace(/ (Electron|aurora-spotirip|Aurora SpotiRip)\/\S+/g, ""); applySettings(); registerIpc(); try { await startApiServer(PORT, join(app.getAppPath(), "dist"), { onSpotifyLogin: adoptSpotifyLogin }); } catch (error) { const inUse = (error as NodeJS.ErrnoException).code === "EADDRINUSE"; dialog.showErrorBox( "Aurora SpotiRip could not start", inUse ? `Port ${PORT} is already in use by another program. Close it and start Aurora SpotiRip again.` : String(error), ); app.quit(); return; } try { await components.whenReady(); } catch (error) { console.error("Widevine is unavailable; in-app playback will use Spotify Connect instead.", error); } createWindow(); app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); } function createWindow() { mainWindow = new BrowserWindow({ width: 1360, height: 880, minWidth: 960, minHeight: 600, backgroundColor: "#0f1318", title: "Aurora SpotiRip", icon: join(app.getAppPath(), "build", "icon.png"), autoHideMenuBar: true, webPreferences: { preload: join(__dirname, "preload.cjs"), contextIsolation: true, nodeIntegration: false, }, }); mainWindow.webContents.setWindowOpenHandler(({ url }) => { shell.openExternal(url); return { action: "deny" }; }); mainWindow.webContents.on("will-navigate", (event, url) => { const { origin, hostname, pathname } = new URL(url); if (origin === APP_URL && pathname === "/_api/spotify-auth") { event.preventDefault(); shell.openExternal(url); return; } if (origin === APP_URL || LOGIN_HOSTS.some((host) => hostname === host || hostname.endsWith(`.${host}`))) return; event.preventDefault(); shell.openExternal(url); }); mainWindow.webContents.on("console-message", (event) => { if (event.level === "warning" || event.level === "error") { console.log(`[page ${event.level}] ${event.message}`); } }); mainWindow.loadURL(APP_URL); mainWindow.on("closed", () => { mainWindow = undefined; }); } async function adoptSpotifyLogin(setCookies: string[]) { for (const header of setCookies) { const [pair, ...attributes] = header.split(";").map((part) => part.trim()); const index = pair.indexOf("="); const name = pair.slice(0, index); const value = decodeURIComponent(pair.slice(index + 1)); const maxAge = Number(attributes.find((attr) => /^max-age=/i.test(attr))?.split("=")[1]); if (!name || name === "spotirip_oauth_state") continue; await session.defaultSession.cookies.set({ url: APP_URL, name, value, path: "/", secure: true, httpOnly: attributes.some((attr) => /^httponly$/i.test(attr)), sameSite: "lax", ...(Number.isFinite(maxAge) ? { expirationDate: Date.now() / 1000 + maxAge } : {}), }); } if (!mainWindow) createWindow(); else { mainWindow.loadURL(APP_URL); if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.focus(); } } function registerIpc() { ipcMain.handle("settings:get-download-folder", () => loadSettings().downloadFolder); ipcMain.handle("settings:choose-download-folder", async () => { const result = await dialog.showOpenDialog(mainWindow!, { title: "Choose download folder", defaultPath: loadSettings().downloadFolder, properties: ["openDirectory", "createDirectory"], }); if (result.canceled || !result.filePaths[0]) return undefined; return saveSettings({ downloadFolder: result.filePaths[0] }).downloadFolder; }); ipcMain.handle("shell:show-in-folder", (_event, path: string) => shell.showItemInFolder(path)); } app.on("window-all-closed", () => { if (process.platform !== "darwin") app.quit(); });