import { app, BrowserWindow, dialog } from "electron"; import * as path from "node:path"; import { registerIpcHandlers } from "./ipc/ipc_host"; import dotenv from "dotenv"; // @ts-ignore import started from "electron-squirrel-startup"; import { updateElectronApp } from "update-electron-app"; import log from "electron-log"; import { readSettings, writeSettings } from "./main/settings"; import { handleSupabaseOAuthReturn } from "./supabase_admin/supabase_return_handler"; import { handleDyadProReturn } from "./main/pro"; log.errorHandler.startCatching(); log.eventLogger.startLogging(); log.scope.labelPadding = false; const logger = log.scope("main"); updateElectronApp(); // additional configuration options available // Load environment variables from .env file dotenv.config(); // Register IPC handlers before app is ready registerIpcHandlers(); // Handle creating/removing shortcuts on Windows when installing/uninstalling. if (started) { app.quit(); } // https://www.electronjs.org/docs/latest/tutorial/launch-app-from-url-in-another-app#main-process-mainjs if (process.defaultApp) { if (process.argv.length >= 2) { app.setAsDefaultProtocolClient("dyad", process.execPath, [ path.resolve(process.argv[1]), ]); } } else { app.setAsDefaultProtocolClient("dyad"); } export async function onReady() { await onFirstRunMaybe(); } app.whenReady().then(onReady); /** * Is this the first run of Fiddle? If so, perform * tasks that we only want to do in this case. */ export async function onFirstRunMaybe() { const settings = readSettings(); if (!settings.hasRunBefore) { await promptMoveToApplicationsFolder(); writeSettings({ hasRunBefore: true, }); } } /** * Ask the user if the app should be moved to the * applications folder. */ async function promptMoveToApplicationsFolder(): Promise { if (process.platform !== "darwin") return; if (app.isInApplicationsFolder()) return; logger.log("Prompting user to move to applications folder"); const { response } = await dialog.showMessageBox({ type: "question", buttons: ["Move to Applications Folder", "Do Not Move"], defaultId: 0, message: "Move to Applications Folder? (required for auto-update)", }); if (response === 0) { logger.log("User chose to move to applications folder"); app.moveToApplicationsFolder(); } else { logger.log("User chose not to move to applications folder"); } } declare global { const MAIN_WINDOW_VITE_DEV_SERVER_URL: string; } let mainWindow: BrowserWindow | null = null; const createWindow = () => { // Create the browser window. mainWindow = new BrowserWindow({ width: process.env.NODE_ENV === "development" ? 1280 : 960, height: 700, titleBarStyle: "hidden", titleBarOverlay: false, trafficLightPosition: { x: 10, y: 8, }, webPreferences: { nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, "preload.js"), // transparent: true, }, // backgroundColor: "#00000001", // frame: false, }); // and load the index.html of the app. if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL); } else { mainWindow.loadFile( path.join(__dirname, "../renderer/main_window/index.html") ); } if (process.env.NODE_ENV === "development") { // Open the DevTools. mainWindow.webContents.openDevTools(); } }; const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) { app.quit(); } else { app.on("second-instance", (event, commandLine, workingDirectory) => { // Someone tried to run a second instance, we should focus our window. if (mainWindow) { if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.focus(); } // the commandLine is array of strings in which last element is deep link url handleDeepLinkReturn(commandLine.pop()!); }); // Create mainWindow, load the rest of the app, etc... app.whenReady().then(() => { createWindow(); }); } // Handle the protocol. In this case, we choose to show an Error Box. app.on("open-url", (event, url) => { handleDeepLinkReturn(url); }); function handleDeepLinkReturn(url: string) { // example url: "dyad://supabase-oauth-return?token=a&refreshToken=b" const parsed = new URL(url); // Intentionally do NOT log the full URL which may contain sensitive tokens. log.log( "Handling deep link: protocol", parsed.protocol, "hostname", parsed.hostname ); if (parsed.protocol !== "dyad:") { dialog.showErrorBox( "Invalid Protocol", `Expected dyad://, got ${parsed.protocol}. Full URL: ${url}` ); return; } if (parsed.hostname === "supabase-oauth-return") { const token = parsed.searchParams.get("token"); const refreshToken = parsed.searchParams.get("refreshToken"); const expiresIn = Number(parsed.searchParams.get("expiresIn")); if (!token || !refreshToken || !expiresIn) { dialog.showErrorBox( "Invalid URL", "Expected token, refreshToken, and expiresIn" ); return; } handleSupabaseOAuthReturn({ token, refreshToken, expiresIn }); // Send message to renderer to trigger re-render mainWindow?.webContents.send("deep-link-received", { type: parsed.hostname, url, }); return; } // dyad://dyad-pro-return?key=123&budget_reset_at=2025-05-26T16:31:13.492000Z&max_budget=100 if (parsed.hostname === "dyad-pro-return") { const apiKey = parsed.searchParams.get("key"); // UTC time // budget_reset_at: '2025-05-26T16:31:13.492000Z' const budgetResetAt = parsed.searchParams.get("budget_reset_at"); const maxBudget = Number(parsed.searchParams.get("max_budget")); if (!apiKey) { dialog.showErrorBox( "Invalid URL", "Expected key, budget_reset_at, and max_budget" ); return; } handleDyadProReturn({ apiKey, budgetResetAt, maxBudget, }); // Send message to renderer to trigger re-render mainWindow?.webContents.send("deep-link-received", { type: parsed.hostname, url, }); return; } dialog.showErrorBox("Invalid deep link URL", url); } // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits // explicitly with Cmd + Q. app.on("window-all-closed", () => { if (process.platform !== "darwin") { app.quit(); } }); app.on("activate", () => { // On OS X it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); // In this file you can include the rest of your app's specific main process // code. You can also put them in separate files and import them here.