From 8db5b4b2aca78f09c5f148a214bec42834735acc Mon Sep 17 00:00:00 2001 From: Michael Moore Date: Thu, 12 Dec 2024 20:19:35 -0600 Subject: [PATCH] feat(unreleased): add shortcut to open dev tools in electron also some general electron cleanup --- forge.config.ts | 1 - src-tauri/src/main.rs | 8 ++-- src/electron/main.ts | 89 ++++++++++++++++++++++++++-------------- src/electron/preload.cjs | 26 ++++++------ 4 files changed, 76 insertions(+), 48 deletions(-) diff --git a/forge.config.ts b/forge.config.ts index 69d08cb..a0c5534 100644 --- a/forge.config.ts +++ b/forge.config.ts @@ -34,7 +34,6 @@ const config: ForgeConfig = { [FuseV1Options.EnableCookieEncryption]: true, [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, [FuseV1Options.EnableNodeCliInspectArguments]: false, - // [FuseV1Options.GrantFileProtocolExtraPrivileges]: false, }), ], }; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index dc27214..0c0a81a 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -51,7 +51,7 @@ fn main() { // running the electron-forge start command directly here doesn't open the window for some reason // instead, we write a bash script to a file that the dev start script is watching for let command = format!( - "npx --package=@electron-forge/cli electron-forge start -- --__PORT__={} --__INVOKE_KEY__='{}' --__ORIGIN__='{}'", + "npx --package=@electron-forge/cli electron-forge start -- --PORT={} --INVOKE_KEY='{}' --ORIGIN='{}'", invoke_http_port, app.invoke_key(), invoke_http_origin, @@ -65,9 +65,9 @@ fn main() { .resolve("electron/games.michaelmakes.stellarmaps-electron", BaseDirectory::Resource)?; std::process::Command::new(resource_path) .args([ - format!("--__PORT__={}", invoke_http_port), - format!("--__INVOKE_KEY__={}", app.invoke_key()), - format!("--__ORIGIN__={}", invoke_http_origin) + format!("--PORT={}", invoke_http_port), + format!("--INVOKE_KEY={}", app.invoke_key()), + format!("--ORIGIN={}", invoke_http_origin) ]) .spawn() .expect("electron frontend failed to start"); diff --git a/src/electron/main.ts b/src/electron/main.ts index 6ae0e9b..889cbfb 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -2,18 +2,30 @@ import path from 'node:path'; import { parseArgs } from 'node:util'; import { app, BrowserWindow, ipcMain, nativeImage, session, shell } from 'electron'; +import { z } from 'zod'; import icon from '../../resources/icon.png?inline'; -const { values: args } = parseArgs({ - args: process.argv, - options: { - __PORT__: { type: 'string' }, - __INVOKE_KEY__: { type: 'string' }, - __ORIGIN__: { type: 'string' }, - }, - allowPositionals: true, -}); +declare const MAIN_WINDOW_VITE_DEV_SERVER_URL: string; +declare const MAIN_WINDOW_VITE_NAME: string; + +const args = z + .object({ + PORT: z.coerce.number().int().min(1), + INVOKE_KEY: z.string(), + ORIGIN: z.string().url(), + }) + .parse( + parseArgs({ + args: process.argv, + options: { + PORT: { type: 'string' }, + INVOKE_KEY: { type: 'string' }, + ORIGIN: { type: 'string' }, + }, + allowPositionals: true, + }).values, + ); const createWindow = () => { // Create the browser window. @@ -26,30 +38,38 @@ const createWindow = () => { title: 'StellarMaps', icon: nativeImage.createFromDataURL(icon), }); + mainWindow.removeMenu(); + mainWindow.maximize(); - // and load the index.html of the app. - // @ts-expect-error -- provided by @electron-forge/plugin-vite - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + // load the index.html of the app. if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { - // @ts-expect-error -- provided by @electron-forge/plugin-vite mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL); } else { mainWindow.loadFile( - // @ts-expect-error -- provided by @electron-forge/plugin-vite path.join(import.meta.dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`), ); } - ipcMain.handle('get-args', () => args); - + // open _blank links in system default browser mainWindow.webContents.setWindowOpenHandler(({ url }) => { shell.openExternal(url); return { action: 'deny' }; }); - mainWindow.removeMenu(); - mainWindow.maximize(); - if (process.env.NODE_ENV === 'development') { + // open dev tools on F12 / Ctrl+Shfit+I / Cmd+Opt+I + mainWindow.webContents.on('before-input-event', (_, input) => { + if ( + input.type === 'keyDown' && + (input.key === 'F12' || + (input.control && input.shift && input.key === 'I') || + (input.meta && input.alt && input.key === 'I')) + ) { + mainWindow.webContents.toggleDevTools(); + } + }); + + // open dev tools in dev mode + if (!app.isPackaged) { mainWindow.webContents.openDevTools(); } }; @@ -58,34 +78,43 @@ const createWindow = () => { // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on('ready', () => { - createWindow(); - // tauri invoke http validates the origin, which we fake here + // set up get-args ipc, which is used by preload script + ipcMain.handle('get-args', () => args); + + // fake the origin for tauri invoke requests session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => { - details.requestHeaders['Origin'] = args.__ORIGIN__ ?? 'tauri://localhost'; + details.requestHeaders['Origin'] = args.ORIGIN; callback({ requestHeaders: details.requestHeaders }); }); + + // wildcard cors for tauri invoke requests session.defaultSession.webRequest.onHeadersReceived( - { urls: [`http://localhost:${args.__PORT__}/*`] }, + { urls: [`http://localhost:${args.PORT}/*`] }, (details, callback) => { - if (details.responseHeaders) { - details.responseHeaders['Access-Control-Allow-Origin'] = ['*']; - } - callback({ responseHeaders: details.responseHeaders }); + callback({ + responseHeaders: { + ...details.responseHeaders, + 'Access-Control-Allow-Origin': ['*'], + }, + }); }, ); + + // finally, create the window + createWindow(); }); // when closed, send electron_closed command to tauri app.on('window-all-closed', async () => { - await fetch(`http://localhost:${args.__PORT__}/main/electron_closed`, { + await fetch(`http://localhost:${args.PORT}/main/electron_closed`, { method: 'POST', body: '{}', headers: { 'Content-Type': 'application/json', 'Tauri-Callback': '12345', 'Tauri-Error': '12345', - 'Tauri-Invoke-Key': args.__INVOKE_KEY__ ?? '', - Origin: args.__ORIGIN__ ?? 'tauri://localhost', + 'Tauri-Invoke-Key': args.INVOKE_KEY, + Origin: args.ORIGIN, }, }); app.quit(); diff --git a/src/electron/preload.cjs b/src/electron/preload.cjs index 6d9a35f..4ea9130 100644 --- a/src/electron/preload.cjs +++ b/src/electron/preload.cjs @@ -6,25 +6,25 @@ const { contextBridge, ipcRenderer } = require('electron/renderer'); let __PORT__ = null; let __INVOKE_KEY__ = null; const ready = ipcRenderer.invoke('get-args').then((args) => { - __PORT__ = args.__PORT__; - __INVOKE_KEY__ = args.__INVOKE_KEY__; + __PORT__ = args.PORT; + __INVOKE_KEY__ = args.INVOKE_KEY; }); -const __CALLBACKS__ = {}; +const CALLBACKS = {}; const __TAURI_INTERNALS__ = { metadata: { currentWindow: { label: 'main' } }, invoke(cmd, payload, options) { return new Promise(async (resolve, reject) => { const callback = window.crypto.getRandomValues(new Uint32Array(1))[0]?.toString() ?? ''; const error = window.crypto.getRandomValues(new Uint32Array(1))[0]?.toString() ?? ''; - __CALLBACKS__[callback] = (data) => { + CALLBACKS[callback] = (data) => { resolve(data); - delete __CALLBACKS__[callback]; - delete __CALLBACKS__[error]; + delete CALLBACKS[callback]; + delete CALLBACKS[error]; }; - __CALLBACKS__[error] = (data) => { + CALLBACKS[error] = (data) => { reject(data); - delete __CALLBACKS__[callback]; - delete __CALLBACKS__[error]; + delete CALLBACKS[callback]; + delete CALLBACKS[error]; }; await ready; sendIpcMessage({ cmd, callback, error, payload, options }); @@ -103,12 +103,12 @@ function sendIpcMessage(message) { } }) .then(([cb, data]) => { - // tauri-electron: check __CALLBACKS__[cb] instead of window[`_${cb}`] - if (__CALLBACKS__[cb]) { - __CALLBACKS__[cb](data); + // tauri-electron: check CALLBACKS[cb] instead of window[`_${cb}`] + if (CALLBACKS[cb]) { + CALLBACKS[cb](data); } else { console.warn( - `[TAURI] Couldn't find callback id {cb} in __CALLBACKS__. This might happen when the app is reloaded while Rust is running an asynchronous operation.`, + `[TAURI] Couldn't find callback id {cb} in CALLBACKS. This might happen when the app is reloaded while Rust is running an asynchronous operation.`, ); } });