From 365a545965647d88295303e677a90b491e418904 Mon Sep 17 00:00:00 2001 From: Esteban Galvis Date: Thu, 7 May 2026 17:03:17 -0500 Subject: [PATCH 1/5] Feat: upgrade electron to version 25 and enforce source compilation for native modules --- beforeBuild.js | 7 ++++++- package-lock.json | 26 ++++++++++++++++++-------- package.json | 4 ++-- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/beforeBuild.js b/beforeBuild.js index 958220fed4..37bd004696 100644 --- a/beforeBuild.js +++ b/beforeBuild.js @@ -2,7 +2,12 @@ const electronRebuild = require('electron-rebuild'); module.exports = async (context) => { const { appDir, electronVersion, arch } = context; - await electronRebuild.rebuild({ buildPath: appDir, electronVersion, arch }); + // Force compilation from source so native modules are built against the + // exact Electron V8 headers rather than using a generic prebuilt. Without + // this, prebuild-install downloads an Electron-v116 prebuilt compiled for + // Electron 24 (V8 11.0) which segfaults under Electron 25+ (V8 11.4). + process.env.npm_config_build_from_source = 'true'; + await electronRebuild.rebuild({ buildPath: appDir, electronVersion, arch, force: true }); return false; }; diff --git a/package-lock.json b/package-lock.json index f8b27199a2..a7b345ebec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,7 +73,7 @@ "detect-port": "^1.3.0", "dotenv": "^10.0.0", "dotenv-webpack": "^7.0.3", - "electron": "^23.3.13", + "electron": "^25.0.0", "electron-builder": "^23.6.0", "electron-debug": "^3.2.0", "electron-fetch": "^1.9.1", @@ -10966,15 +10966,15 @@ } }, "node_modules/electron": { - "version": "23.3.13", - "resolved": "https://registry.npmjs.org/electron/-/electron-23.3.13.tgz", - "integrity": "sha512-BaXtHEb+KYKLouUXlUVDa/lj9pj4F5kiE0kwFdJV84Y2EU7euIDgPthfKtchhr5MVHmjtavRMIV/zAwEiSQ9rQ==", + "version": "25.9.8", + "resolved": "https://registry.npmjs.org/electron/-/electron-25.9.8.tgz", + "integrity": "sha512-PGgp6PH46QVENHuAHc2NT1Su8Q1qov7qIl2jI5tsDpTibwV2zD8539AeWBQySeBU4dhbj9onIl7+1bXQ0wefBg==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { "@electron/get": "^2.0.0", - "@types/node": "^16.11.26", + "@types/node": "^18.11.18", "extract-zip": "^2.0.1" }, "bin": { @@ -11501,9 +11501,19 @@ } }, "node_modules/electron/node_modules/@types/node": { - "version": "16.18.126", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.126.tgz", - "integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==", + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/electron/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index ea0b9a87b4..f2aa763142 100644 --- a/package.json +++ b/package.json @@ -142,7 +142,7 @@ "detect-port": "^1.3.0", "dotenv": "^10.0.0", "dotenv-webpack": "^7.0.3", - "electron": "^23.3.13", + "electron": "^25.0.0", "electron-builder": "^23.6.0", "electron-debug": "^3.2.0", "electron-fetch": "^1.9.1", @@ -225,4 +225,4 @@ "engines": { "node": ">=18.0.0 <19.0.0" } -} +} \ No newline at end of file From c5781758f0466e4252d7654ab9f63dc1b15c7903 Mon Sep 17 00:00:00 2001 From: Esteban Galvis Date: Thu, 7 May 2026 18:41:54 -0500 Subject: [PATCH 2/5] Feat: import app from electron and ensure app quits on auth window close if not logged in --- src/apps/main/windows/auth.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/apps/main/windows/auth.ts b/src/apps/main/windows/auth.ts index ee1fc8df63..72cedc918f 100644 --- a/src/apps/main/windows/auth.ts +++ b/src/apps/main/windows/auth.ts @@ -1,4 +1,4 @@ -import { BrowserWindow } from 'electron'; +import { app, BrowserWindow } from 'electron'; import { preloadPath, resolveHtmlPath } from '../util'; import { setUpCommonWindowHandlers } from '.'; @@ -41,6 +41,7 @@ export const createAuthWindow = async () => { authWindow.on('closed', () => { authWindow = null; + if (!getIsLoggedIn()) app.quit(); }); authWindow.on('blur', () => { const isLoggedIn = getIsLoggedIn(); From c3af9d7313827d0a9d278b3966ec473a930f8d26 Mon Sep 17 00:00:00 2001 From: Esteban Galvis Date: Thu, 7 May 2026 20:37:53 -0500 Subject: [PATCH 3/5] Feat: enhance tray menu initialization and context menu handling --- src/apps/main/tray/tray-menu.test.ts | 47 +++++----------------------- src/apps/main/tray/tray-menu.ts | 35 ++++++--------------- 2 files changed, 16 insertions(+), 66 deletions(-) diff --git a/src/apps/main/tray/tray-menu.test.ts b/src/apps/main/tray/tray-menu.test.ts index 51d03b589f..3c4be613af 100644 --- a/src/apps/main/tray/tray-menu.test.ts +++ b/src/apps/main/tray/tray-menu.test.ts @@ -44,7 +44,7 @@ describe('tray-menu', () => { trayHandlers.clear(); }); - it('should initialize the tray in loading state', () => { + it('should initialize the tray with context menu in loading state', () => { // Given const onClick = vi.fn(); const onQuit = vi.fn(); @@ -54,58 +54,25 @@ describe('tray-menu', () => { // Then expect(TrayMock).toBeCalledWith('/icons/loading.png'); - expect(trayInstance.setIgnoreDoubleClickEvents).toBeCalledWith(true); expect(createFromPathMock).toBeCalledWith('/icons/loading.png'); expect(trayInstance.setImage).toBeCalledWith({ imagePath: '/icons/loading.png' }); expect(trayInstance.setToolTip).toBeCalledWith('Loading Internxt...'); + expect(buildFromTemplateMock).toBeCalledWith([{ label: 'Open app', click: expect.any(Function) }]); + expect(trayInstance.setContextMenu).toBeCalledWith({ template: [{ label: 'Open app', click: expect.any(Function) }] }); }); - it('should invoke onClick and clear the context menu on tray click', async () => { + it('should invoke onClick when the context menu Open app item is clicked', async () => { // Given const onClick = vi.fn().mockResolvedValue(undefined); const onQuit = vi.fn(); new TrayMenu('/icons', onClick, onQuit); - // When - await trayHandlers.get('click')?.(); + // When – simulate clicking the first (only) menu item + const [[menuTemplate]] = buildFromTemplateMock.mock.calls as [[Electron.MenuItemConstructorOptions[]]]; + await (menuTemplate[0].click as () => Promise)(); // Then expect(onClick).toBeCalled(); - expect(trayInstance.setContextMenu).toBeCalledWith(null); - }); - - it('should build and set the tray context menu', () => { - // Given - const onClick = vi.fn().mockResolvedValue(undefined); - const onQuit = vi.fn(); - const trayMenu = new TrayMenu('/icons', onClick, onQuit); - - // When - trayMenu.updateContextMenu(); - - // Then - expect(buildFromTemplateMock).toBeCalledWith([ - { - label: 'Show/Hide', - click: expect.any(Function), - }, - { - label: 'Quit', - click: onQuit, - }, - ]); - expect(trayInstance.setContextMenu).toBeCalledWith({ - template: [ - { - label: 'Show/Hide', - click: expect.any(Function), - }, - { - label: 'Quit', - click: onQuit, - }, - ], - }); }); it('should update the tooltip for idle state', () => { diff --git a/src/apps/main/tray/tray-menu.ts b/src/apps/main/tray/tray-menu.ts index a30b356b01..0764d70967 100644 --- a/src/apps/main/tray/tray-menu.ts +++ b/src/apps/main/tray/tray-menu.ts @@ -21,39 +21,22 @@ export class TrayMenu { this.setState('LOADING'); - this.tray.setIgnoreDoubleClickEvents(true); - - this.tray.on('click', async () => { - await this.onClick(); - this.tray.setContextMenu(null); - }); - } - - getIconPath(state: TrayMenuState) { - return path.join(this.iconsPath, `${state.toLowerCase()}.png`); - } - - generateContextMenu() { - const contextMenuTemplate: Electron.MenuItemConstructorOptions[] = []; - contextMenuTemplate.push( + // On Linux with AppIndicator the 'click' event is unreliable — the first + // click is consumed by the system tray protocol. A context menu is the + // only interaction that works consistently on every Linux DE/WM. + const contextMenu = Menu.buildFromTemplate([ { - label: 'Show/Hide', + label: `Internxt ${PackageJson.version}`, click: () => { this.onClick(); }, }, - { - label: 'Quit', - click: this.onQuit, - }, - ); - - return Menu.buildFromTemplate(contextMenuTemplate); + ]); + this.tray.setContextMenu(contextMenu); } - updateContextMenu() { - const ctxMenu = this.generateContextMenu(); - this.tray.setContextMenu(ctxMenu); + getIconPath(state: TrayMenuState) { + return path.join(this.iconsPath, `${state.toLowerCase()}.png`); } setState(state: TrayMenuState) { From 06e2558f87fdccf961a9a30b002be56798422a62 Mon Sep 17 00:00:00 2001 From: Esteban Galvis Date: Thu, 7 May 2026 20:41:12 -0500 Subject: [PATCH 4/5] Feat: refine context menu handling for Linux tray interactions --- src/apps/main/tray/tray-menu.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/apps/main/tray/tray-menu.ts b/src/apps/main/tray/tray-menu.ts index 0764d70967..d71378abea 100644 --- a/src/apps/main/tray/tray-menu.ts +++ b/src/apps/main/tray/tray-menu.ts @@ -21,9 +21,6 @@ export class TrayMenu { this.setState('LOADING'); - // On Linux with AppIndicator the 'click' event is unreliable — the first - // click is consumed by the system tray protocol. A context menu is the - // only interaction that works consistently on every Linux DE/WM. const contextMenu = Menu.buildFromTemplate([ { label: `Internxt ${PackageJson.version}`, @@ -31,6 +28,12 @@ export class TrayMenu { this.onClick(); }, }, + { + label: 'Quit', + click: () => { + this.onQuit(); + }, + }, ]); this.tray.setContextMenu(contextMenu); } From 31a7fab206132ace8bda8d9f311535c232e2c789 Mon Sep 17 00:00:00 2001 From: AlexisMora Date: Fri, 8 May 2026 11:28:31 +0200 Subject: [PATCH 5/5] fix: tray menu test --- src/apps/main/tray/tray-menu.test.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/apps/main/tray/tray-menu.test.ts b/src/apps/main/tray/tray-menu.test.ts index 3c4be613af..07d7a1417c 100644 --- a/src/apps/main/tray/tray-menu.test.ts +++ b/src/apps/main/tray/tray-menu.test.ts @@ -51,14 +51,25 @@ describe('tray-menu', () => { // When new TrayMenu('/icons', onClick, onQuit); - + const expectedContextMenu = [ + { + label: `Internxt ${PackageJson.version}`, + click: expect.any(Function), + }, + { + label: 'Quit', + click: expect.any(Function), + }, + ]; // Then expect(TrayMock).toBeCalledWith('/icons/loading.png'); expect(createFromPathMock).toBeCalledWith('/icons/loading.png'); expect(trayInstance.setImage).toBeCalledWith({ imagePath: '/icons/loading.png' }); expect(trayInstance.setToolTip).toBeCalledWith('Loading Internxt...'); - expect(buildFromTemplateMock).toBeCalledWith([{ label: 'Open app', click: expect.any(Function) }]); - expect(trayInstance.setContextMenu).toBeCalledWith({ template: [{ label: 'Open app', click: expect.any(Function) }] }); + expect(buildFromTemplateMock).toBeCalledWith(expectedContextMenu); + expect(trayInstance.setContextMenu).toBeCalledWith({ + template: expectedContextMenu, + }); }); it('should invoke onClick when the context menu Open app item is clicked', async () => {