From 9b52002abb1b0566d23e79285b6ba8d16f4061de Mon Sep 17 00:00:00 2001 From: Aavash Shrestha Date: Wed, 2 Jul 2025 01:22:43 +0200 Subject: [PATCH 1/9] fix (electron-chrome-extensions): always disconnect native port when native messaging host is destroyed (#156) * fix (electron-chrome-extensions): fix typo on native msg disconnect ipc channel name * fix (electron-chrome-extensions): send native disconnect message regardless of native messaging host connection status --- .../src/browser/api/lib/native-messaging-host.ts | 1 - packages/electron-chrome-extensions/src/renderer/index.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/electron-chrome-extensions/src/browser/api/lib/native-messaging-host.ts b/packages/electron-chrome-extensions/src/browser/api/lib/native-messaging-host.ts index 79adf7dc..f5f17a7f 100644 --- a/packages/electron-chrome-extensions/src/browser/api/lib/native-messaging-host.ts +++ b/packages/electron-chrome-extensions/src/browser/api/lib/native-messaging-host.ts @@ -125,7 +125,6 @@ export class NativeMessagingHost { } destroy() { - if (!this.connected) return this.connected = false if (this.process) { this.process.kill() diff --git a/packages/electron-chrome-extensions/src/renderer/index.ts b/packages/electron-chrome-extensions/src/renderer/index.ts index 878b385d..78770e54 100644 --- a/packages/electron-chrome-extensions/src/renderer/index.ts +++ b/packages/electron-chrome-extensions/src/renderer/index.ts @@ -67,7 +67,7 @@ export const injectExtensionAPIs = () => { receive(message) } ipcRenderer.on(`crx-native-msg-${connectionId}`, onMessage) - ipcRenderer.once(`crx-native-msg-${connectNative}-disconnect`, () => { + ipcRenderer.once(`crx-native-msg-${connectionId}-disconnect`, () => { ipcRenderer.off(`crx-native-msg-${connectionId}`, onMessage) disconnect() }) From aec8c25d9367766469b0ececa53fce5808771e42 Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Tue, 1 Jul 2025 19:25:43 -0400 Subject: [PATCH 2/9] fix: clear sender on native host destroyed --- .../src/browser/api/lib/native-messaging-host.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/electron-chrome-extensions/src/browser/api/lib/native-messaging-host.ts b/packages/electron-chrome-extensions/src/browser/api/lib/native-messaging-host.ts index f5f17a7f..1fcbc612 100644 --- a/packages/electron-chrome-extensions/src/browser/api/lib/native-messaging-host.ts +++ b/packages/electron-chrome-extensions/src/browser/api/lib/native-messaging-host.ts @@ -99,7 +99,7 @@ async function readNativeMessagingHostConfig( } export class NativeMessagingHost { private process?: ReturnType - private sender: ExtensionSender + private sender?: ExtensionSender private connectionId: string private connected: boolean = false private pending?: any[] @@ -130,10 +130,11 @@ export class NativeMessagingHost { this.process.kill() this.process = undefined } - if (this.keepAlive) { + if (this.keepAlive && this.sender) { this.sender.ipc.off(`crx-native-msg-${this.connectionId}`, this.receiveExtensionMessage) this.sender.send(`crx-native-msg-${this.connectionId}-disconnect`) } + this.sender = undefined } private async launch(application: string, extensionId: string) { @@ -216,7 +217,7 @@ export class NativeMessagingHost { const length = data.readUInt32LE(0) const message = JSON.parse(data.subarray(4, 4 + length).toString()) d('receive: %s', message) - if (this.keepAlive) { + if (this.keepAlive && this.sender) { this.sender.send(`crx-native-msg-${this.connectionId}`, message) } else { this.resolveResponse?.(message) From 331dc5f8e588814ae581bd23822b2d7d7e2bd9c5 Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Tue, 1 Jul 2025 19:56:46 -0400 Subject: [PATCH 3/9] fix: crash when opening popup in fullscreen window --- packages/electron-chrome-extensions/src/browser/popup.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/electron-chrome-extensions/src/browser/popup.ts b/packages/electron-chrome-extensions/src/browser/popup.ts index 86b36305..107bf0e0 100644 --- a/packages/electron-chrome-extensions/src/browser/popup.ts +++ b/packages/electron-chrome-extensions/src/browser/popup.ts @@ -62,6 +62,8 @@ export class PopupView { movable: false, maximizable: false, minimizable: false, + // https://github.com/electron/electron/issues/47579 + fullscreenable: false, resizable: false, skipTaskbar: true, backgroundColor: '#ffffff', From b7796507d365b3fa90fc0f8a4ecef3a45df2d42a Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Tue, 1 Jul 2025 20:03:19 -0400 Subject: [PATCH 4/9] feat: emit some events on PopupView --- .../src/browser/popup.ts | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/electron-chrome-extensions/src/browser/popup.ts b/packages/electron-chrome-extensions/src/browser/popup.ts index 107bf0e0..e32de6cb 100644 --- a/packages/electron-chrome-extensions/src/browser/popup.ts +++ b/packages/electron-chrome-extensions/src/browser/popup.ts @@ -1,3 +1,4 @@ +import { EventEmitter } from 'node:events' import { BrowserWindow, Session } from 'electron' import { getAllWindows } from './api/common' import debug from 'debug' @@ -25,7 +26,7 @@ const supportsPreferredSize = () => { return major >= 12 } -export class PopupView { +export class PopupView extends EventEmitter { static POSITION_PADDING = 5 static BOUNDS = { @@ -50,6 +51,8 @@ export class PopupView { private readyPromise: Promise constructor(opts: PopupViewOptions) { + super() + this.parent = opts.parent this.extensionId = opts.extensionId this.anchorRect = opts.anchorRect @@ -173,13 +176,17 @@ export class PopupView { Math.min(PopupView.BOUNDS.maxHeight, Math.max(rect.height || 0, PopupView.BOUNDS.minHeight)), ) - d(`setSize`, { width, height }) + const size = { width, height } + d(`setSize`, size) + + this.emit('will-resize', size) this.browserWindow?.setBounds({ ...this.browserWindow.getBounds(), - width, - height, + ...size, }) + + this.emit('resized') } private maybeClose = () => { @@ -231,13 +238,17 @@ export class PopupView { x = Math.floor(x) y = Math.floor(y) - d(`updatePosition`, { x, y }) + const position = { x, y } + d(`updatePosition`, position) + + this.emit('will-move', position) this.browserWindow.setBounds({ ...this.browserWindow.getBounds(), - x, - y, + ...position, }) + + this.emit('moved') } /** Backwards compat for Electron <12 */ From e4c0a426411b307bc5d96eda96e595a9bea8410b Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Tue, 1 Jul 2025 20:11:44 -0400 Subject: [PATCH 5/9] refactor: limit webstore logs to dev --- .../src/renderer/chrome-web-store.preload.ts | 91 ++++++++++--------- 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/packages/electron-chrome-web-store/src/renderer/chrome-web-store.preload.ts b/packages/electron-chrome-web-store/src/renderer/chrome-web-store.preload.ts index 92c81268..8081a5b9 100644 --- a/packages/electron-chrome-web-store/src/renderer/chrome-web-store.preload.ts +++ b/packages/electron-chrome-web-store/src/renderer/chrome-web-store.preload.ts @@ -76,6 +76,13 @@ function overrideUserAgent() { ) } +const DEBUG = process.env.NODE_ENV === 'development' + +function log(...args: any[]) { + if (!DEBUG) return + console.debug(...args) +} + function setupChromeWebStoreApi() { let appName: string | undefined @@ -107,52 +114,52 @@ function setupChromeWebStoreApi() { WebGlStatus, beginInstallWithManifest3: async (details, callback) => { - console.log('webstorePrivate.beginInstallWithManifest3', details) + log('webstorePrivate.beginInstallWithManifest3', details) const { result, message } = await ipcRenderer.invoke('chromeWebstore.beginInstall', details) - console.log('webstorePrivate.beginInstallWithManifest3 result:', result) + log('webstorePrivate.beginInstallWithManifest3 result:', result) setExtensionError(result === Result.SUCCESS ? null : message) if (callback) callback(result) return result }, completeInstall: async (id, callback) => { - console.log('webstorePrivate.completeInstall', id) + log('webstorePrivate.completeInstall', id) const result = await ipcRenderer.invoke('chromeWebstore.completeInstall', id) - console.log('webstorePrivate.completeInstall result:', result) + log('webstorePrivate.completeInstall result:', result) if (callback) callback(result) maybeUpdateBranding() return result }, enableAppLauncher: async (enable, callback) => { - console.log('webstorePrivate.enableAppLauncher', enable) + log('webstorePrivate.enableAppLauncher', enable) const result = await ipcRenderer.invoke('chromeWebstore.enableAppLauncher', enable) - console.log('webstorePrivate.enableAppLauncher result:', result) + log('webstorePrivate.enableAppLauncher result:', result) if (callback) callback(result) return result }, getBrowserLogin: async (callback) => { - console.log('webstorePrivate.getBrowserLogin called') + log('webstorePrivate.getBrowserLogin called') const result = await ipcRenderer.invoke('chromeWebstore.getBrowserLogin') - console.log('webstorePrivate.getBrowserLogin result:', result) + log('webstorePrivate.getBrowserLogin result:', result) if (callback) callback(result) return result }, getExtensionStatus: async (id, manifestJson, callback) => { - console.log('webstorePrivate.getExtensionStatus', id, { id, manifestJson, callback }) + log('webstorePrivate.getExtensionStatus', id, { id, manifestJson, callback }) const result = await ipcRenderer.invoke('chromeWebstore.getExtensionStatus', id, manifestJson) - console.log('webstorePrivate.getExtensionStatus result:', id, result) + log('webstorePrivate.getExtensionStatus result:', id, result) if (callback) callback(result) maybeUpdateBranding() return result }, getFullChromeVersion: async (callback) => { - console.log('webstorePrivate.getFullChromeVersion called') + log('webstorePrivate.getFullChromeVersion called') const result = await ipcRenderer.invoke('chromeWebstore.getFullChromeVersion') - console.log('webstorePrivate.getFullChromeVersion result:', result) + log('webstorePrivate.getFullChromeVersion result:', result) if (result.app_name) { setAppName(result.app_name) @@ -164,73 +171,73 @@ function setupChromeWebStoreApi() { }, getIsLauncherEnabled: async (callback) => { - console.log('webstorePrivate.getIsLauncherEnabled called') + log('webstorePrivate.getIsLauncherEnabled called') const result = await ipcRenderer.invoke('chromeWebstore.getIsLauncherEnabled') - console.log('webstorePrivate.getIsLauncherEnabled result:', result) + log('webstorePrivate.getIsLauncherEnabled result:', result) if (callback) callback(result) return result }, getMV2DeprecationStatus: async (callback) => { - console.log('webstorePrivate.getMV2DeprecationStatus called') + log('webstorePrivate.getMV2DeprecationStatus called') const result = await ipcRenderer.invoke('chromeWebstore.getMV2DeprecationStatus') - console.log('webstorePrivate.getMV2DeprecationStatus result:', result) + log('webstorePrivate.getMV2DeprecationStatus result:', result) if (callback) callback(result) return result }, getReferrerChain: async (callback) => { - console.log('webstorePrivate.getReferrerChain called') + log('webstorePrivate.getReferrerChain called') const result = await ipcRenderer.invoke('chromeWebstore.getReferrerChain') - console.log('webstorePrivate.getReferrerChain result:', result) + log('webstorePrivate.getReferrerChain result:', result) if (callback) callback(result) return result }, getStoreLogin: async (callback) => { - console.log('webstorePrivate.getStoreLogin called') + log('webstorePrivate.getStoreLogin called') const result = await ipcRenderer.invoke('chromeWebstore.getStoreLogin') - console.log('webstorePrivate.getStoreLogin result:', result) + log('webstorePrivate.getStoreLogin result:', result) if (callback) callback(result) return result }, getWebGLStatus: async (callback) => { - console.log('webstorePrivate.getWebGLStatus called') + log('webstorePrivate.getWebGLStatus called') const result = await ipcRenderer.invoke('chromeWebstore.getWebGLStatus') - console.log('webstorePrivate.getWebGLStatus result:', result) + log('webstorePrivate.getWebGLStatus result:', result) if (callback) callback(result) return result }, install: async (id, silentInstall, callback) => { - console.log('webstorePrivate.install', { id, silentInstall }) + log('webstorePrivate.install', { id, silentInstall }) const result = await ipcRenderer.invoke('chromeWebstore.install', id, silentInstall) - console.log('webstorePrivate.install result:', result) + log('webstorePrivate.install result:', result) if (callback) callback(result) return result }, isInIncognitoMode: async (callback) => { - console.log('webstorePrivate.isInIncognitoMode called') + log('webstorePrivate.isInIncognitoMode called') const result = await ipcRenderer.invoke('chromeWebstore.isInIncognitoMode') - console.log('webstorePrivate.isInIncognitoMode result:', result) + log('webstorePrivate.isInIncognitoMode result:', result) if (callback) callback(result) return result }, isPendingCustodianApproval: async (id, callback) => { - console.log('webstorePrivate.isPendingCustodianApproval', id) + log('webstorePrivate.isPendingCustodianApproval', id) const result = await ipcRenderer.invoke('chromeWebstore.isPendingCustodianApproval', id) - console.log('webstorePrivate.isPendingCustodianApproval result:', result) + log('webstorePrivate.isPendingCustodianApproval result:', result) if (callback) callback(result) return result }, setStoreLogin: async (login, callback) => { - console.log('webstorePrivate.setStoreLogin', login) + log('webstorePrivate.setStoreLogin', login) const result = await ipcRenderer.invoke('chromeWebstore.setStoreLogin', login) - console.log('webstorePrivate.setStoreLogin result:', result) + log('webstorePrivate.setStoreLogin result:', result) if (callback) callback(result) return result }, @@ -243,7 +250,7 @@ function setupChromeWebStoreApi() { const runtime = { lastError: null, getManifest: async () => { - console.log('chrome.runtime.getManifest called') + log('chrome.runtime.getManifest called') return {} }, } @@ -252,41 +259,41 @@ function setupChromeWebStoreApi() { const management = { onInstalled: { addListener: (callback: () => void) => { - console.log('chrome.management.onInstalled.addListener called') + log('chrome.management.onInstalled.addListener called') ipcRenderer.on('chrome.management.onInstalled', callback) }, removeListener: (callback: () => void) => { - console.log('chrome.management.onInstalled.removeListener called') + log('chrome.management.onInstalled.removeListener called') ipcRenderer.removeListener('chrome.management.onInstalled', callback) }, }, onUninstalled: { addListener: (callback: () => void) => { - console.log('chrome.management.onUninstalled.addListener called') + log('chrome.management.onUninstalled.addListener called') ipcRenderer.on('chrome.management.onUninstalled', callback) }, removeListener: (callback: () => void) => { - console.log('chrome.management.onUninstalled.removeListener called') + log('chrome.management.onUninstalled.removeListener called') ipcRenderer.removeListener('chrome.management.onUninstalled', callback) }, }, getAll: (callback: (extensions: any[]) => void) => { - console.log('chrome.management.getAll called') + log('chrome.management.getAll called') ipcRenderer.invoke('chrome.management.getAll').then((result) => { - console.log('chrome.management.getAll result:', result) + log('chrome.management.getAll result:', result) callback(result) }) }, setEnabled: async (id: string, enabled: boolean) => { - console.log('chrome.management.setEnabled', { id, enabled }) + log('chrome.management.setEnabled', { id, enabled }) const result = await ipcRenderer.invoke('chrome.management.setEnabled', id, enabled) - console.log('chrome.management.setEnabled result:', result) + log('chrome.management.setEnabled result:', result) return result }, uninstall: (id: string, options: { showConfirmDialog: boolean }, callback?: () => void) => { - console.log('chrome.management.uninstall', { id, options }) + log('chrome.management.uninstall', { id, options }) ipcRenderer.invoke('chrome.management.uninstall', id, options).then((result) => { - console.log('chrome.management.uninstall result:', result) + log('chrome.management.uninstall result:', result) if (callback) callback() }) }, @@ -314,6 +321,6 @@ function setupChromeWebStoreApi() { } if (location.href.startsWith('https://chromewebstore.google.com')) { - console.log('Injecting Chrome Web Store API') + log('Injecting Chrome Web Store API') setupChromeWebStoreApi() } From 1f3cddf06d94599d736866a3f10ee759fbc6fa4a Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Tue, 1 Jul 2025 21:27:25 -0400 Subject: [PATCH 6/9] chore: bump electron --- .../electron-chrome-extensions/package.json | 2 +- packages/shell/package.json | 2 +- yarn.lock | 27 ++++++++++--------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/electron-chrome-extensions/package.json b/packages/electron-chrome-extensions/package.json index 1a1eb89b..54992ff8 100644 --- a/packages/electron-chrome-extensions/package.json +++ b/packages/electron-chrome-extensions/package.json @@ -49,7 +49,7 @@ "chai": "^4.2.0", "chai-as-promised": "^7.1.1", "colors": "^1.4.0", - "electron": "^35.0.0-beta.3", + "electron": "^37.1.0", "esbuild": "^0.24.2", "minimist": "^1.2.7", "mocha": "^8.2.1", diff --git a/packages/shell/package.json b/packages/shell/package.json index 064768b2..12b2e530 100644 --- a/packages/shell/package.json +++ b/packages/shell/package.json @@ -29,6 +29,6 @@ "@electron-forge/plugin-webpack": "^7.5.0", "copy-webpack-plugin": "^11.0.0", "cross-env": "^7.0.2", - "electron": "35.0.0-beta.12" + "electron": "^37.1.0" } } diff --git a/yarn.lock b/yarn.lock index 1849528e..eca36fb1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2294,6 +2294,16 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== +electron-chrome-web-store@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/electron-chrome-web-store/-/electron-chrome-web-store-0.10.0.tgz#39e8a2d6497c2024fdef223bcff81b6a260e9fd7" + integrity sha512-Zd1L+yv21SJ4cG24NMM0fMfro6xhqMx1kiB319xmOePUk+bnpHXKiLUMdU2jTb8uPO+1vqS6x3mCbn7o1E5UHQ== + dependencies: + "@types/chrome" "^0.0.287" + adm-zip "^0.5.16" + debug "^4.3.7" + pbf "^4.0.1" + electron-installer-dmg@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/electron-installer-dmg/-/electron-installer-dmg-5.0.1.tgz#309662eccce4a8585a79fe4bce79c23976a950f0" @@ -2330,19 +2340,10 @@ electron-winstaller@^5.3.0: optionalDependencies: "@electron/windows-sign" "^1.1.2" -electron@35.0.0-beta.12: - version "35.0.0-beta.12" - resolved "https://registry.yarnpkg.com/electron/-/electron-35.0.0-beta.12.tgz#3c13488ecb08b55f5d8e8f007db71ef563616073" - integrity sha512-p7TaVKGbowiDg0ltiPZ4ldlQy4/JBURHs2Fyr34Ul82wXg930pLbWA7RUV0UXX3mOaeYYDBKP04eLLVHZi6JdQ== - dependencies: - "@electron/get" "^2.0.0" - "@types/node" "^22.7.7" - extract-zip "^2.0.1" - -electron@^35.0.0-beta.3: - version "35.0.0-beta.3" - resolved "https://registry.yarnpkg.com/electron/-/electron-35.0.0-beta.3.tgz#395b246d1767be73ff39ee834f9025756c12a7de" - integrity sha512-huZMU1He5WdWcdIw41sAHQbbpf+WAs+NBrFP4e8nFovysqvjN49edsttkf23cD9wYDsYgarRdr26m1kDyz+/Ug== +electron@^37.1.0: + version "37.1.0" + resolved "https://registry.yarnpkg.com/electron/-/electron-37.1.0.tgz#6d6d1891f8add5d2d44007e2ee5d4542140fc4b4" + integrity sha512-Fcr3yfAw4oU392waVZSlrFUQx4P+h/k31+PRgkBY9tFx9E/zxzdPQQj0achZlG1HRDusw3ooQB+OXb9PvufdzA== dependencies: "@electron/get" "^2.0.0" "@types/node" "^22.7.7" From 3fbc156a680627478b286ff789d35efe62c233c6 Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Tue, 1 Jul 2025 21:45:58 -0400 Subject: [PATCH 7/9] refactor: avoid deprecated extension APIs --- .../src/browser/api/browser-action.ts | 13 ++++++++----- .../src/browser/api/commands.ts | 5 +++-- .../src/browser/api/context-menus.ts | 10 +++++++--- .../src/browser/api/notifications.ts | 3 ++- .../src/browser/api/permissions.ts | 7 ++++--- .../src/browser/index.ts | 3 ++- .../src/browser/router.ts | 9 ++++++--- packages/electron-chrome-web-store/README.md | 2 +- .../electron-chrome-web-store/src/browser/api.ts | 9 ++++++--- .../src/browser/installer.ts | 15 ++++++++++----- .../src/browser/loader.ts | 8 +++++--- .../src/browser/updater.ts | 10 ++++++---- packages/shell/browser/main.js | 4 ++-- 13 files changed, 62 insertions(+), 36 deletions(-) diff --git a/packages/electron-chrome-extensions/src/browser/api/browser-action.ts b/packages/electron-chrome-extensions/src/browser/api/browser-action.ts index 1fdcb47c..0592089b 100644 --- a/packages/electron-chrome-extensions/src/browser/api/browser-action.ts +++ b/packages/electron-chrome-extensions/src/browser/api/browser-action.ts @@ -202,11 +202,12 @@ export class BrowserActionAPI { } private setupSession(session: Electron.Session) { - session.on('extension-loaded', (event, extension) => { + const sessionExtensions = session.extensions || session + sessionExtensions.on('extension-loaded', (event, extension) => { this.processExtension(extension) }) - session.on('extension-unloaded', (event, extension) => { + sessionExtensions.on('extension-unloaded', (event, extension) => { this.removeActions(extension.id) }) } @@ -227,7 +228,8 @@ export class BrowserActionAPI { const imageSize = parseInt(fragments[2], 10) const resizeType = parseInt(fragments[3], 10) || ResizeType.Up - const extension = this.ctx.session.getExtension(extensionId) + const sessionExtensions = this.ctx.session.extensions || this.ctx.session + const extension = sessionExtensions.getExtension(extensionId) let iconDetails: chrome.browserAction.TabIconDetails | undefined @@ -433,7 +435,8 @@ export class BrowserActionAPI { private activateContextMenu(details: ActivateDetails) { const { extensionId, anchorRect } = details - const extension = this.ctx.session.getExtension(extensionId) + const sessionExtensions = this.ctx.session.extensions || this.ctx.session + const extension = sessionExtensions.getExtension(extensionId) if (!extension) { throw new Error(`Unregistered extension '${extensionId}'`) } @@ -480,7 +483,7 @@ export class BrowserActionAPI { label: 'Remove extension', click: () => { d(`removing extension "${extension.name}" (${extension.id})`) - this.ctx.session.removeExtension(extension.id) + sessionExtensions.removeExtension(extension.id) }, }) } diff --git a/packages/electron-chrome-extensions/src/browser/api/commands.ts b/packages/electron-chrome-extensions/src/browser/api/commands.ts index 887f1946..352f5e82 100644 --- a/packages/electron-chrome-extensions/src/browser/api/commands.ts +++ b/packages/electron-chrome-extensions/src/browser/api/commands.ts @@ -8,11 +8,12 @@ export class CommandsAPI { const handle = this.ctx.router.apiHandler() handle('commands.getAll', this.getAll) - ctx.session.on('extension-loaded', (_event, extension) => { + const sessionExtensions = ctx.session.extensions || ctx.session + sessionExtensions.on('extension-loaded', (_event, extension) => { this.processExtension(extension) }) - ctx.session.on('extension-unloaded', (_event, extension) => { + sessionExtensions.on('extension-unloaded', (_event, extension) => { this.removeCommands(extension) }) } diff --git a/packages/electron-chrome-extensions/src/browser/api/context-menus.ts b/packages/electron-chrome-extensions/src/browser/api/context-menus.ts index 8e9f0a89..d6dfc217 100644 --- a/packages/electron-chrome-extensions/src/browser/api/context-menus.ts +++ b/packages/electron-chrome-extensions/src/browser/api/context-menus.ts @@ -89,7 +89,8 @@ export class ContextMenusAPI { handle('contextMenus.remove', this.remove) handle('contextMenus.removeAll', this.removeAll) - this.ctx.session.on('extension-unloaded', (event, extension) => { + const sessionExtensions = ctx.session.extensions || ctx.session + sessionExtensions.on('extension-unloaded', (event, extension) => { if (this.menus.has(extension.id)) { this.menus.delete(extension.id) } @@ -193,8 +194,10 @@ export class ContextMenusAPI { documentUrl: params.frameURL || params.pageURL, } + const sessionExtensions = this.ctx.session.extensions || this.ctx.session + for (const [extensionId, propItems] of this.menus) { - const extension = this.ctx.session.getExtension(extensionId) + const extension = sessionExtensions.getExtension(extensionId) if (!extension) continue const extensionMenuItemOptions: ContextItemConstructorOptions[] = [] @@ -259,7 +262,8 @@ export class ContextMenusAPI { menuType: ContextMenuType, ): Electron.MenuItem[] { const extensionItems = this.menus.get(extensionId) - const extension = this.ctx.session.getExtension(extensionId) + const sessionExtensions = this.ctx.session.extensions || this.ctx.session + const extension = sessionExtensions.getExtension(extensionId) const activeTab = this.ctx.store.getActiveTabOfCurrentWindow() const menuItemOptions = [] diff --git a/packages/electron-chrome-extensions/src/browser/api/notifications.ts b/packages/electron-chrome-extensions/src/browser/api/notifications.ts index ac607d7f..462d953a 100644 --- a/packages/electron-chrome-extensions/src/browser/api/notifications.ts +++ b/packages/electron-chrome-extensions/src/browser/api/notifications.ts @@ -56,7 +56,8 @@ export class NotificationsAPI { handle('notifications.getPermissionLevel', this.getPermissionLevel) handle('notifications.update', this.update) - this.ctx.session.on('extension-unloaded', (event, extension) => { + const sessionExtensions = ctx.session.extensions || ctx.session + sessionExtensions.on('extension-unloaded', (event, extension) => { for (const [key, notification] of this.registry) { if (key.startsWith(extension.id)) { notification.close() diff --git a/packages/electron-chrome-extensions/src/browser/api/permissions.ts b/packages/electron-chrome-extensions/src/browser/api/permissions.ts index 48266805..2886b9f5 100644 --- a/packages/electron-chrome-extensions/src/browser/api/permissions.ts +++ b/packages/electron-chrome-extensions/src/browser/api/permissions.ts @@ -21,13 +21,14 @@ export class PermissionsAPI { handle('permissions.remove', this.remove) handle('permissions.request', this.request) - ctx.session.getAllExtensions().forEach((ext) => this.processExtension(ext)) + const sessionExtensions = ctx.session.extensions || ctx.session + sessionExtensions.getAllExtensions().forEach((ext) => this.processExtension(ext)) - ctx.session.on('extension-loaded', (_event, extension) => { + sessionExtensions.on('extension-loaded', (_event, extension) => { this.processExtension(extension) }) - ctx.session.on('extension-unloaded', (_event, extension) => { + sessionExtensions.on('extension-unloaded', (_event, extension) => { this.permissionMap.delete(extension.id) }) } diff --git a/packages/electron-chrome-extensions/src/browser/index.ts b/packages/electron-chrome-extensions/src/browser/index.ts index b233156c..d2e6a0a9 100644 --- a/packages/electron-chrome-extensions/src/browser/index.ts +++ b/packages/electron-chrome-extensions/src/browser/index.ts @@ -175,7 +175,8 @@ export class ElectronChromeExtensions extends EventEmitter { } private listenForExtensions() { - this.ctx.session.addListener('extension-loaded', (_event, extension) => { + const sessionExtensions = this.ctx.session.extensions || this.ctx.session + sessionExtensions.addListener('extension-loaded', (_event, extension) => { readLoadedExtensionManifest(this.ctx, extension) }) } diff --git a/packages/electron-chrome-extensions/src/browser/router.ts b/packages/electron-chrome-extensions/src/browser/router.ts index 5237044b..49e92929 100644 --- a/packages/electron-chrome-extensions/src/browser/router.ts +++ b/packages/electron-chrome-extensions/src/browser/router.ts @@ -236,7 +236,8 @@ export class ExtensionRouter { ) { this.delegate.addObserver(this) - session.on('extension-unloaded', (event, extension) => { + const sessionExtensions = session.extensions || session + sessionExtensions.on('extension-unloaded', (event, extension) => { this.filterListeners((listener) => listener.extensionId !== extension.id) }) @@ -297,7 +298,8 @@ export class ExtensionRouter { addListener(listener: EventListener, extensionId: string, eventName: string) { const { listeners, session } = this - const extension = session.getExtension(extensionId) + const sessionExtensions = session.extensions || session + const extension = sessionExtensions.getExtension(extensionId) if (!extension) { throw new Error(`extension not registered in session [extensionId:${extensionId}]`) } @@ -358,13 +360,14 @@ export class ExtensionRouter { ) { const { session } = this const eventSession = getSessionFromEvent(event) + const eventSessionExtensions = eventSession.extensions || eventSession const handler = this.getHandler(handlerName) if (eventSession !== session && !handler.allowRemote) { throw new Error(`${handlerName} does not support calling from a remote session`) } - const extension = extensionId ? eventSession.getExtension(extensionId) : undefined + const extension = extensionId ? eventSessionExtensions.getExtension(extensionId) : undefined if (!extension && handler.extensionContext) { throw new Error(`${handlerName} was sent from an unknown extension context`) } diff --git a/packages/electron-chrome-web-store/README.md b/packages/electron-chrome-web-store/README.md index 217fceb1..9adff48b 100644 --- a/packages/electron-chrome-web-store/README.md +++ b/packages/electron-chrome-web-store/README.md @@ -109,7 +109,7 @@ Installs Chrome extension from the Chrome Web Store. - `options` - `session`: The Electron session to load extensions in. Defaults to `session.defaultSession`. - `extensionsPath`: The path to the extensions directory. Defaults to 'Extensions/' in the app's userData path. - - `loadExtensionOptions`: Extension options passed into `session.loadExtension`. + - `loadExtensionOptions`: Extension options passed into `session.extensions.loadExtension`. ### `uninstallExtension` diff --git a/packages/electron-chrome-web-store/src/browser/api.ts b/packages/electron-chrome-web-store/src/browser/api.ts index 8af16160..aee1dfeb 100644 --- a/packages/electron-chrome-web-store/src/browser/api.ts +++ b/packages/electron-chrome-web-store/src/browser/api.ts @@ -62,7 +62,8 @@ function getExtensionInstallStatus( return ExtensionInstallStatus.BLOCKED_BY_POLICY } - const extensions = state.session.getAllExtensions() + const sessionExtensions = state.session.extensions || state.session + const extensions = sessionExtensions.getAllExtensions() const extension = extensions.find((ext) => ext.id === extensionId) if (!extension) { @@ -219,7 +220,8 @@ export function registerWebStoreApi(webStoreState: WebStoreState) { if (result.result === Result.SUCCESS) { queueMicrotask(() => { - const ext = webStoreState.session.getExtension(details.id) + const sessionExtensions = webStoreState.session.extensions || webStoreState.session + const ext = sessionExtensions.getExtension(details.id) if (ext && senderFrame && !senderFrame.isDestroyed()) { try { senderFrame.send('chrome.management.onInstalled', getExtensionInfo(ext)) @@ -314,7 +316,8 @@ export function registerWebStoreApi(webStoreState: WebStoreState) { }) handle('chrome.management.getAll', async (event) => { - const extensions = webStoreState.session.getAllExtensions() + const sessionExtensions = webStoreState.session.extensions || webStoreState.session + const extensions = sessionExtensions.getAllExtensions() return extensions.map(getExtensionInfo) }) diff --git a/packages/electron-chrome-web-store/src/browser/installer.ts b/packages/electron-chrome-web-store/src/browser/installer.ts index 4431e4d0..192cb112 100644 --- a/packages/electron-chrome-web-store/src/browser/installer.ts +++ b/packages/electron-chrome-web-store/src/browser/installer.ts @@ -215,10 +215,11 @@ export async function installExtension( d('installing %s', extensionId) const session = opts.session || electronSession.defaultSession + const sessionExtensions = session.extensions || session const extensionsPath = opts.extensionsPath || getDefaultExtensionsPath() // Check if already loaded - const existingExtension = session.getExtension(extensionId) + const existingExtension = sessionExtensions.getExtension(extensionId) if (existingExtension) { d('%s already loaded', extensionId) return existingExtension @@ -228,12 +229,15 @@ export async function installExtension( const existingExtensionInfo = await findExtensionInstall(extensionId, extensionsPath) if (existingExtensionInfo && existingExtensionInfo.type === 'store') { d('%s already installed', extensionId) - return await session.loadExtension(existingExtensionInfo.path, opts.loadExtensionOptions) + return await sessionExtensions.loadExtension( + existingExtensionInfo.path, + opts.loadExtensionOptions, + ) } // Download and load new extension const extensionPath = await downloadExtension(extensionId, extensionsPath) - const extension = await session.loadExtension(extensionPath, opts.loadExtensionOptions) + const extension = await sessionExtensions.loadExtension(extensionPath, opts.loadExtensionOptions) d('installed %s', extensionId) return extension @@ -249,12 +253,13 @@ export async function uninstallExtension( d('uninstalling %s', extensionId) const session = opts.session || electronSession.defaultSession + const sessionExtensions = session.extensions || session const extensionsPath = opts.extensionsPath || getDefaultExtensionsPath() - const extensions = session.getAllExtensions() + const extensions = sessionExtensions.getAllExtensions() const existingExt = extensions.find((ext) => ext.id === extensionId) if (existingExt) { - session.removeExtension(extensionId) + sessionExtensions.removeExtension(extensionId) } const extensionDir = path.join(extensionsPath, extensionId) diff --git a/packages/electron-chrome-web-store/src/browser/loader.ts b/packages/electron-chrome-web-store/src/browser/loader.ts index 5d80acb3..ac7fa3f1 100644 --- a/packages/electron-chrome-web-store/src/browser/loader.ts +++ b/packages/electron-chrome-web-store/src/browser/loader.ts @@ -125,6 +125,8 @@ export async function loadAllExtensions( allowUnpacked?: boolean } = {}, ) { + const sessionExtensions = session.extensions || session + let extensions = await discoverExtensions(extensionsPath) extensions = filterOutdatedExtensions(extensions) d('discovered %d extension(s) in %s', extensions.length, extensionsPath) @@ -133,16 +135,16 @@ export async function loadAllExtensions( try { let extension: Electron.Extension | undefined if (ext.type === 'store') { - const existingExt = session.getExtension(ext.id) + const existingExt = sessionExtensions.getExtension(ext.id) if (existingExt) { d('skipping loading existing extension %s', ext.id) continue } d('loading extension %s', `${ext.id}@${ext.manifest.version}`) - extension = await session.loadExtension(ext.path) + extension = await sessionExtensions.loadExtension(ext.path) } else if (options.allowUnpacked) { d('loading unpacked extension %s', ext.path) - extension = await session.loadExtension(ext.path) + extension = await sessionExtensions.loadExtension(ext.path) } if ( diff --git a/packages/electron-chrome-web-store/src/browser/updater.ts b/packages/electron-chrome-web-store/src/browser/updater.ts index a55ffc42..b53e4a4e 100644 --- a/packages/electron-chrome-web-store/src/browser/updater.ts +++ b/packages/electron-chrome-web-store/src/browser/updater.ts @@ -210,6 +210,7 @@ async function fetchAvailableUpdates(extensions: Electron.Extension[]): Promise< } async function updateExtension(session: Electron.Session, update: ExtensionUpdate) { + const sessionExtensions = session.extensions || session const extensionId = update.id const oldExtension = update.extension d('updating %s %s -> %s', extensionId, oldExtension.version, update.version) @@ -234,9 +235,9 @@ async function updateExtension(session: Electron.Session, update: ExtensionUpdat d('downloaded update %s@%s', extensionId, update.version) // Reload extension if already loaded - if (session.getExtension(extensionId)) { - session.removeExtension(extensionId) - await session.loadExtension(updatePath) + if (sessionExtensions.getExtension(extensionId)) { + sessionExtensions.removeExtension(extensionId) + await sessionExtensions.loadExtension(updatePath) d('loaded update %s@%s', extensionId, update.version) } @@ -246,7 +247,8 @@ async function updateExtension(session: Electron.Session, update: ExtensionUpdat async function checkForUpdates(session: Electron.Session) { // Only check for extensions from the store - const extensions = session.getAllExtensions().filter(filterWebStoreExtension) + const sessionExtensions = session.extensions || session + const extensions = sessionExtensions.getAllExtensions().filter(filterWebStoreExtension) d('checking for updates: %s', extensions.map((ext) => `${ext.id}@${ext.version}`).join(',')) const updates = await fetchAvailableUpdates(extensions) diff --git a/packages/shell/browser/main.js b/packages/shell/browser/main.js index 3d75d453..44e92313 100644 --- a/packages/shell/browser/main.js +++ b/packages/shell/browser/main.js @@ -211,7 +211,7 @@ class Browser { } }) - const webuiExtension = await this.session.loadExtension(PATHS.WEBUI) + const webuiExtension = await this.session.extensions.loadExtension(PATHS.WEBUI) webuiExtensionId = webuiExtension.id // Wait for web store extensions to finish loading as they may change the @@ -247,7 +247,7 @@ class Browser { } await Promise.all( - this.session.getAllExtensions().map(async (extension) => { + this.session.extensions.getAllExtensions().map(async (extension) => { const manifest = extension.manifest if (manifest.manifest_version === 3 && manifest?.background?.service_worker) { await this.session.serviceWorkers.startWorkerForScope(extension.url).catch((error) => { From 927ac340c3c6cc462f636a50ccd9991df0cd2e12 Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Tue, 1 Jul 2025 21:51:16 -0400 Subject: [PATCH 8/9] electron-chrome-extensions@4.9.0 --- packages/electron-chrome-extensions/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/electron-chrome-extensions/package.json b/packages/electron-chrome-extensions/package.json index 54992ff8..ce59f151 100644 --- a/packages/electron-chrome-extensions/package.json +++ b/packages/electron-chrome-extensions/package.json @@ -1,6 +1,6 @@ { "name": "electron-chrome-extensions", - "version": "4.8.0", + "version": "4.9.0", "description": "Chrome extension support for Electron", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.mjs", From 5b60d987f46d3568a94a037b2cdce6291c3706e2 Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Tue, 1 Jul 2025 21:52:49 -0400 Subject: [PATCH 9/9] electron-chrome-web-store@0.13.0 --- packages/electron-chrome-web-store/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/electron-chrome-web-store/package.json b/packages/electron-chrome-web-store/package.json index 221e949d..d15e5e52 100644 --- a/packages/electron-chrome-web-store/package.json +++ b/packages/electron-chrome-web-store/package.json @@ -1,6 +1,6 @@ { "name": "electron-chrome-web-store", - "version": "0.12.0", + "version": "0.13.0", "description": "Install and update Chrome extensions from the Chrome Web Store for Electron", "main": "./dist/cjs/browser/index.js", "module": "./dist/esm/browser/index.mjs",