From c4fca732f430f5746f7305b70650afdee8d201ce Mon Sep 17 00:00:00 2001 From: gwleuverink Date: Tue, 9 Sep 2025 14:33:04 +0200 Subject: [PATCH 1/9] disabled nodeIntegration & enabled contextIsolation --- .../js/electron-plugin/src/preload/index.mts | 40 +++++++++++++++---- .../electron-plugin/src/server/api/menuBar.ts | 4 +- .../electron-plugin/src/server/api/window.ts | 4 +- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/resources/js/electron-plugin/src/preload/index.mts b/resources/js/electron-plugin/src/preload/index.mts index 031999b6..1136136c 100644 --- a/resources/js/electron-plugin/src/preload/index.mts +++ b/resources/js/electron-plugin/src/preload/index.mts @@ -1,6 +1,9 @@ import remote from "@electron/remote"; -import {ipcRenderer} from "electron"; +import {ipcRenderer, contextBridge} from "electron"; +// ------------------------------------------------------------------- +// The Native helper +// ------------------------------------------------------------------- const Native = { on: (event, callback) => { ipcRenderer.on('native-event', (_, data) => { @@ -19,12 +22,11 @@ const Native = { } }; -// @ts-ignore -window.Native = Native; - -// @ts-ignore -window.remote = remote; +contextBridge.exposeInMainWorld('Native', Native); +// ------------------------------------------------------------------- +// Log events +// ------------------------------------------------------------------- ipcRenderer.on('log', (event, {level, message, context}) => { if (level === 'error') { console.error(`[${level}] ${message}`, context) @@ -35,7 +37,10 @@ ipcRenderer.on('log', (event, {level, message, context}) => { } }); -// Add Livewire event listeners + +// ------------------------------------------------------------------- +// Livewire event listeners +// ------------------------------------------------------------------- ipcRenderer.on('native-event', (event, data) => { // Strip leading slashes @@ -82,3 +87,24 @@ ipcRenderer.on('native-event', (event, data) => { }) } }) + +// ------------------------------------------------------------------- +// Let the client know preload is fully evaluated +// ------------------------------------------------------------------- +contextBridge.exposeInMainWorld('_native_init', (function() { + // This is admittedly a bit hacky. Due to context isolation + // we don't have direct access to the renderer window object, + // but by assigning a bridge function that executes itself inside + // the renderer context we can hack around it. + + // It's recommended to use window.postMessage & dispatch an + // event from the renderer itself, but we're loading webcontent + // from localhost. We don't have a renderer process we can access. + // Though this is hacky it works well and is the quickest way to do this + // without sprinkling additional logic all over the place. + + window.dispatchEvent(new CustomEvent('native:init')); + + // Cleanup the window property we abused + delete window['_native_init']; +})()) diff --git a/resources/js/electron-plugin/src/server/api/menuBar.ts b/resources/js/electron-plugin/src/server/api/menuBar.ts index 90b3b48b..be76ee98 100644 --- a/resources/js/electron-plugin/src/server/api/menuBar.ts +++ b/resources/js/electron-plugin/src/server/api/menuBar.ts @@ -131,9 +131,9 @@ router.post("/create", (req, res) => { transparent: transparency, webPreferences: { preload: fileURLToPath(new URL('../../electron-plugin/dist/preload/index.mjs', import.meta.url)), - nodeIntegration: true, sandbox: false, - contextIsolation: false, + nodeIntegration: false, + contextIsolation: true, } } }); diff --git a/resources/js/electron-plugin/src/server/api/window.ts b/resources/js/electron-plugin/src/server/api/window.ts index 3224919a..43d9a84d 100644 --- a/resources/js/electron-plugin/src/server/api/window.ts +++ b/resources/js/electron-plugin/src/server/api/window.ts @@ -254,8 +254,8 @@ router.post('/open', (req, res) => { spellcheck: false, preload: preloadPath, sandbox: false, - contextIsolation: false, - nodeIntegration: true, + contextIsolation: true, + nodeIntegration: false, }; let windowState: windowStateKeeper.State | undefined = undefined; From 89ad429b60845b43402fef1ff13f65a39da02410 Mon Sep 17 00:00:00 2001 From: gwleuverink <17123491+gwleuverink@users.noreply.github.com> Date: Tue, 9 Sep 2025 12:37:55 +0000 Subject: [PATCH 2/9] Build plugin --- resources/js/electron-plugin/dist/preload/index.mjs | 9 ++++++--- resources/js/electron-plugin/dist/server/api/menuBar.js | 4 ++-- resources/js/electron-plugin/dist/server/api/window.js | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/resources/js/electron-plugin/dist/preload/index.mjs b/resources/js/electron-plugin/dist/preload/index.mjs index 46d3f01a..2e7bf04c 100644 --- a/resources/js/electron-plugin/dist/preload/index.mjs +++ b/resources/js/electron-plugin/dist/preload/index.mjs @@ -1,5 +1,5 @@ import remote from "@electron/remote"; -import { ipcRenderer } from "electron"; +import { ipcRenderer, contextBridge } from "electron"; const Native = { on: (event, callback) => { ipcRenderer.on('native-event', (_, data) => { @@ -15,8 +15,7 @@ const Native = { menu.popup({ window: remote.getCurrentWindow() }); } }; -window.Native = Native; -window.remote = remote; +contextBridge.exposeInMainWorld('Native', Native); ipcRenderer.on('log', (event, { level, message, context }) => { if (level === 'error') { console.error(`[${level}] ${message}`, context); @@ -52,3 +51,7 @@ ipcRenderer.on('native-event', (event, data) => { }); } }); +contextBridge.exposeInMainWorld('_native_init', (function () { + window.dispatchEvent(new CustomEvent('native:init')); + delete window['_native_init']; +})()); diff --git a/resources/js/electron-plugin/dist/server/api/menuBar.js b/resources/js/electron-plugin/dist/server/api/menuBar.js index 21ee49d4..41ed0164 100644 --- a/resources/js/electron-plugin/dist/server/api/menuBar.js +++ b/resources/js/electron-plugin/dist/server/api/menuBar.js @@ -83,9 +83,9 @@ router.post("/create", (req, res) => { transparent: transparency, webPreferences: { preload: fileURLToPath(new URL('../../electron-plugin/dist/preload/index.mjs', import.meta.url)), - nodeIntegration: true, sandbox: false, - contextIsolation: false, + nodeIntegration: false, + contextIsolation: true, } } }); diff --git a/resources/js/electron-plugin/dist/server/api/window.js b/resources/js/electron-plugin/dist/server/api/window.js index 8a75ec74..dadde4a0 100644 --- a/resources/js/electron-plugin/dist/server/api/window.js +++ b/resources/js/electron-plugin/dist/server/api/window.js @@ -158,8 +158,8 @@ router.post('/open', (req, res) => { spellcheck: false, preload: preloadPath, sandbox: false, - contextIsolation: false, - nodeIntegration: true, + contextIsolation: true, + nodeIntegration: false, }; let windowState = undefined; if (req.body.rememberState === true) { From ff62b0916c96ff9004366c94de53ccd7abc3e35f Mon Sep 17 00:00:00 2001 From: gwleuverink Date: Tue, 9 Sep 2025 15:11:06 +0200 Subject: [PATCH 3/9] remove unused preload --- resources/js/electron.vite.config.mjs | 3 --- resources/js/src/preload/index.js | 5 ----- 2 files changed, 8 deletions(-) delete mode 100644 resources/js/src/preload/index.js diff --git a/resources/js/electron.vite.config.mjs b/resources/js/electron.vite.config.mjs index a4d80ed0..4af81f9c 100644 --- a/resources/js/electron.vite.config.mjs +++ b/resources/js/electron.vite.config.mjs @@ -16,8 +16,5 @@ export default defineConfig({ }, }, plugins: [externalizeDepsPlugin()] - }, - preload: { - plugins: [externalizeDepsPlugin()] } }); diff --git a/resources/js/src/preload/index.js b/resources/js/src/preload/index.js deleted file mode 100644 index a085b054..00000000 --- a/resources/js/src/preload/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import { electronAPI } from '@electron-toolkit/preload' -import * as remote from '@electron/remote/index.js' - -window.electron = electronAPI -window.remote = remote; From a1061ff92f53aa4ed67cb18c5398622c51696ff4 Mon Sep 17 00:00:00 2001 From: gwleuverink Date: Wed, 10 Sep 2025 10:00:00 +0200 Subject: [PATCH 4/9] Allow overwriting of default web preferences Co-authored-by: @JulianGlueck --- .../electron-plugin/src/server/api/menuBar.ts | 8 ++----- .../electron-plugin/src/server/api/window.ts | 17 ++----------- .../src/server/webPreferences.ts | 24 +++++++++++++++++++ 3 files changed, 28 insertions(+), 21 deletions(-) create mode 100644 resources/js/electron-plugin/src/server/webPreferences.ts diff --git a/resources/js/electron-plugin/src/server/api/menuBar.ts b/resources/js/electron-plugin/src/server/api/menuBar.ts index be76ee98..567b8e11 100644 --- a/resources/js/electron-plugin/src/server/api/menuBar.ts +++ b/resources/js/electron-plugin/src/server/api/menuBar.ts @@ -6,6 +6,7 @@ import { menubar } from "menubar"; import { notifyLaravel } from "../utils.js"; import { fileURLToPath } from 'url' import { enable } from "@electron/remote/main/index.js"; +import mergePreferences from "../webPreferences.js"; const router = express.Router(); @@ -129,12 +130,7 @@ router.post("/create", (req, res) => { vibrancy, backgroundColor, transparent: transparency, - webPreferences: { - preload: fileURLToPath(new URL('../../electron-plugin/dist/preload/index.mjs', import.meta.url)), - sandbox: false, - nodeIntegration: false, - contextIsolation: true, - } + webPreferences: mergePreferences() } }); diff --git a/resources/js/electron-plugin/src/server/api/window.ts b/resources/js/electron-plugin/src/server/api/window.ts index 43d9a84d..da891061 100644 --- a/resources/js/electron-plugin/src/server/api/window.ts +++ b/resources/js/electron-plugin/src/server/api/window.ts @@ -4,6 +4,7 @@ import state from '../state.js'; import { fileURLToPath } from 'url' import { notifyLaravel, goToUrl, appendWindowIdToUrl } from '../utils.js'; import windowStateKeeper from 'electron-window-state'; +import mergePreferences from '../webPreferences.js' import {enable} from "@electron/remote/main/index.js"; @@ -247,17 +248,6 @@ router.post('/open', (req, res) => { return; } - let preloadPath = fileURLToPath(new URL('../../electron-plugin/dist/preload/index.mjs', import.meta.url)); - - const defaultWebPreferences = { - backgroundThrottling: false, - spellcheck: false, - preload: preloadPath, - sandbox: false, - contextIsolation: true, - nodeIntegration: false, - }; - let windowState: windowStateKeeper.State | undefined = undefined; if (req.body.rememberState === true) { @@ -301,10 +291,7 @@ router.post('/open', (req, res) => { hiddenInMissionControl, autoHideMenuBar, ...(process.platform === 'linux' ? {icon: state.icon} : {}), - webPreferences: { - ...webPreferences, - ...defaultWebPreferences - }, + webPreferences: mergePreferences(webPreferences), fullscreen, fullscreenable, kiosk, diff --git a/resources/js/electron-plugin/src/server/webPreferences.ts b/resources/js/electron-plugin/src/server/webPreferences.ts new file mode 100644 index 00000000..b53b631f --- /dev/null +++ b/resources/js/electron-plugin/src/server/webPreferences.ts @@ -0,0 +1,24 @@ +import { fileURLToPath } from 'url' + +let preloadPath = fileURLToPath(new URL('../../electron-plugin/dist/preload/index.mjs', import.meta.url)); + +const defaultWebPreferences = { + spellcheck: false, + nodeIntegration: false, + backgroundThrottling: false, +}; + +const requiredWebPreferences = { + sandbox: false, + preload: preloadPath, + contextIsolation: true, +} + +export default function(userWebPreferences: object = {}): object +{ + return { + ...defaultWebPreferences, + ...userWebPreferences, + ...requiredWebPreferences + } +} From 56a8d70bc6c42122e102b5201e2ee60790c4650b Mon Sep 17 00:00:00 2001 From: gwleuverink <17123491+gwleuverink@users.noreply.github.com> Date: Wed, 10 Sep 2025 08:00:58 +0000 Subject: [PATCH 5/9] Build plugin --- .../js/electron-plugin/dist/server/api/menuBar.js | 9 ++------- .../js/electron-plugin/dist/server/api/window.js | 13 ++----------- .../electron-plugin/dist/server/webPreferences.js | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 18 deletions(-) create mode 100644 resources/js/electron-plugin/dist/server/webPreferences.js diff --git a/resources/js/electron-plugin/dist/server/api/menuBar.js b/resources/js/electron-plugin/dist/server/api/menuBar.js index 41ed0164..062df7ed 100644 --- a/resources/js/electron-plugin/dist/server/api/menuBar.js +++ b/resources/js/electron-plugin/dist/server/api/menuBar.js @@ -4,8 +4,8 @@ import { compileMenu } from "./helper/index.js"; import state from "../state.js"; import { menubar } from "menubar"; import { notifyLaravel } from "../utils.js"; -import { fileURLToPath } from 'url'; import { enable } from "@electron/remote/main/index.js"; +import mergePreferences from "../webPreferences.js"; const router = express.Router(); router.post("/label", (req, res) => { var _a; @@ -81,12 +81,7 @@ router.post("/create", (req, res) => { vibrancy, backgroundColor, transparent: transparency, - webPreferences: { - preload: fileURLToPath(new URL('../../electron-plugin/dist/preload/index.mjs', import.meta.url)), - sandbox: false, - nodeIntegration: false, - contextIsolation: true, - } + webPreferences: mergePreferences() } }); state.activeMenuBar.on("after-create-window", () => { diff --git a/resources/js/electron-plugin/dist/server/api/window.js b/resources/js/electron-plugin/dist/server/api/window.js index dadde4a0..0d3befe7 100644 --- a/resources/js/electron-plugin/dist/server/api/window.js +++ b/resources/js/electron-plugin/dist/server/api/window.js @@ -1,9 +1,9 @@ import express from 'express'; import { BrowserWindow } from 'electron'; import state from '../state.js'; -import { fileURLToPath } from 'url'; import { notifyLaravel, goToUrl, appendWindowIdToUrl } from '../utils.js'; import windowStateKeeper from 'electron-window-state'; +import mergePreferences from '../webPreferences.js'; import { enable } from "@electron/remote/main/index.js"; const router = express.Router(); router.post('/maximize', (req, res) => { @@ -152,15 +152,6 @@ router.post('/open', (req, res) => { res.sendStatus(200); return; } - let preloadPath = fileURLToPath(new URL('../../electron-plugin/dist/preload/index.mjs', import.meta.url)); - const defaultWebPreferences = { - backgroundThrottling: false, - spellcheck: false, - preload: preloadPath, - sandbox: false, - contextIsolation: true, - nodeIntegration: false, - }; let windowState = undefined; if (req.body.rememberState === true) { windowState = windowStateKeeper({ @@ -187,7 +178,7 @@ router.post('/open', (req, res) => { focusable, skipTaskbar, hiddenInMissionControl, - autoHideMenuBar }, (process.platform === 'linux' ? { icon: state.icon } : {})), { webPreferences: Object.assign(Object.assign({}, webPreferences), defaultWebPreferences), fullscreen, + autoHideMenuBar }, (process.platform === 'linux' ? { icon: state.icon } : {})), { webPreferences: mergePreferences(webPreferences), fullscreen, fullscreenable, kiosk })); if ((process.env.NODE_ENV === 'development' || showDevTools === true) && showDevTools !== false) { diff --git a/resources/js/electron-plugin/dist/server/webPreferences.js b/resources/js/electron-plugin/dist/server/webPreferences.js new file mode 100644 index 00000000..4ab81a1f --- /dev/null +++ b/resources/js/electron-plugin/dist/server/webPreferences.js @@ -0,0 +1,15 @@ +import { fileURLToPath } from 'url'; +let preloadPath = fileURLToPath(new URL('../../electron-plugin/dist/preload/index.mjs', import.meta.url)); +const defaultWebPreferences = { + spellcheck: false, + nodeIntegration: false, + backgroundThrottling: false, +}; +const requiredWebPreferences = { + sandbox: false, + preload: preloadPath, + contextIsolation: true, +}; +export default function (userWebPreferences = {}) { + return Object.assign(Object.assign(Object.assign({}, defaultWebPreferences), userWebPreferences), requiredWebPreferences); +} From eab0140f5e8ad8030b31761e4e1fb03c5e7e7b93 Mon Sep 17 00:00:00 2001 From: gwleuverink Date: Wed, 10 Sep 2025 10:18:36 +0200 Subject: [PATCH 6/9] allow users to overwirte MenuBar webPreferences --- resources/js/electron-plugin/src/server/api/menuBar.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/js/electron-plugin/src/server/api/menuBar.ts b/resources/js/electron-plugin/src/server/api/menuBar.ts index 567b8e11..97246ad6 100644 --- a/resources/js/electron-plugin/src/server/api/menuBar.ts +++ b/resources/js/electron-plugin/src/server/api/menuBar.ts @@ -89,6 +89,7 @@ router.post("/create", (req, res) => { contextMenu, tooltip, resizable, + webPreferences, event, } = req.body; @@ -130,7 +131,7 @@ router.post("/create", (req, res) => { vibrancy, backgroundColor, transparent: transparency, - webPreferences: mergePreferences() + webPreferences: mergePreferences(webPreferences) } }); From dfa4084dc4be8d09fb6f6a386b1b2bd906937c82 Mon Sep 17 00:00:00 2001 From: gwleuverink <17123491+gwleuverink@users.noreply.github.com> Date: Wed, 10 Sep 2025 08:20:25 +0000 Subject: [PATCH 7/9] Build plugin --- resources/js/electron-plugin/dist/server/api/menuBar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/js/electron-plugin/dist/server/api/menuBar.js b/resources/js/electron-plugin/dist/server/api/menuBar.js index 062df7ed..a664a2a6 100644 --- a/resources/js/electron-plugin/dist/server/api/menuBar.js +++ b/resources/js/electron-plugin/dist/server/api/menuBar.js @@ -51,7 +51,7 @@ router.post("/create", (req, res) => { state.activeMenuBar.tray.destroy(); shouldSendCreatedEvent = false; } - const { width, height, url, label, alwaysOnTop, vibrancy, backgroundColor, transparency, icon, showDockIcon, onlyShowContextMenu, windowPosition, showOnAllWorkspaces, contextMenu, tooltip, resizable, event, } = req.body; + const { width, height, url, label, alwaysOnTop, vibrancy, backgroundColor, transparency, icon, showDockIcon, onlyShowContextMenu, windowPosition, showOnAllWorkspaces, contextMenu, tooltip, resizable, webPreferences, event, } = req.body; if (onlyShowContextMenu) { const tray = new Tray(icon || state.icon.replace("icon.png", "IconTemplate.png")); tray.setContextMenu(buildMenu(contextMenu)); @@ -81,7 +81,7 @@ router.post("/create", (req, res) => { vibrancy, backgroundColor, transparent: transparency, - webPreferences: mergePreferences() + webPreferences: mergePreferences(webPreferences) } }); state.activeMenuBar.on("after-create-window", () => { From 430b5dc85286606f89131b17ba1c5d1c64bd999a Mon Sep 17 00:00:00 2001 From: gwleuverink Date: Wed, 10 Sep 2025 20:03:58 +0200 Subject: [PATCH 8/9] revert variable cleanup - not needed --- resources/js/electron-plugin/src/preload/index.mts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/resources/js/electron-plugin/src/preload/index.mts b/resources/js/electron-plugin/src/preload/index.mts index 1136136c..579961bf 100644 --- a/resources/js/electron-plugin/src/preload/index.mts +++ b/resources/js/electron-plugin/src/preload/index.mts @@ -91,7 +91,7 @@ ipcRenderer.on('native-event', (event, data) => { // ------------------------------------------------------------------- // Let the client know preload is fully evaluated // ------------------------------------------------------------------- -contextBridge.exposeInMainWorld('_native_init', (function() { +contextBridge.exposeInMainWorld('native:initialized', (function() { // This is admittedly a bit hacky. Due to context isolation // we don't have direct access to the renderer window object, // but by assigning a bridge function that executes itself inside @@ -105,6 +105,5 @@ contextBridge.exposeInMainWorld('_native_init', (function() { window.dispatchEvent(new CustomEvent('native:init')); - // Cleanup the window property we abused - delete window['_native_init']; + return true; })()) From 4595d1ffd7d15891999cdb59a7af80135ca40117 Mon Sep 17 00:00:00 2001 From: gwleuverink <17123491+gwleuverink@users.noreply.github.com> Date: Wed, 10 Sep 2025 18:04:45 +0000 Subject: [PATCH 9/9] Build plugin --- resources/js/electron-plugin/dist/preload/index.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/js/electron-plugin/dist/preload/index.mjs b/resources/js/electron-plugin/dist/preload/index.mjs index 2e7bf04c..34bec7c9 100644 --- a/resources/js/electron-plugin/dist/preload/index.mjs +++ b/resources/js/electron-plugin/dist/preload/index.mjs @@ -51,7 +51,7 @@ ipcRenderer.on('native-event', (event, data) => { }); } }); -contextBridge.exposeInMainWorld('_native_init', (function () { +contextBridge.exposeInMainWorld('native:initialized', (function () { window.dispatchEvent(new CustomEvent('native:init')); - delete window['_native_init']; + return true; })());