From 0ef67306a4a3e6dfed2e1110766530f01b3285ab Mon Sep 17 00:00:00 2001 From: iamEvan <47493765+iamEvanYT@users.noreply.github.com> Date: Mon, 21 Apr 2025 23:44:13 +0100 Subject: [PATCH 1/6] afterInstall, afterUninstall and overrideExtensionInstallStatus --- packages/electron-chrome-web-store/README.md | 3 ++ .../src/browser/api.ts | 18 ++++++++++ .../src/browser/index.ts | 35 ++++++++++++++++++- .../src/browser/installer.ts | 15 ++++++-- .../src/browser/types.ts | 17 +++++++++ 5 files changed, 85 insertions(+), 3 deletions(-) diff --git a/packages/electron-chrome-web-store/README.md b/packages/electron-chrome-web-store/README.md index 217fceb1..b0183eb4 100644 --- a/packages/electron-chrome-web-store/README.md +++ b/packages/electron-chrome-web-store/README.md @@ -100,6 +100,9 @@ Installs Chrome Web Store support in the specified session. - `allowlist`: An array of allowed extension IDs to install. - `denylist`: An array of denied extension IDs to install. - `beforeInstall`: A function which receives install details and returns a promise. Allows for prompting prior to install. + - `afterInstall`: A function which receives install details. Allows for additional actions after install. + - `afterUninstall`: A function which receives extension ID, extension, and manifest. Allows for additional actions after uninstall. + - `overrideExtensionInstallStatus`: A function which receives the current state, extension ID, and manifest. Returns a string indicating the install status of the extension. ### `installExtension` diff --git a/packages/electron-chrome-web-store/src/browser/api.ts b/packages/electron-chrome-web-store/src/browser/api.ts index 7515e4d6..4dbbbc1b 100644 --- a/packages/electron-chrome-web-store/src/browser/api.ts +++ b/packages/electron-chrome-web-store/src/browser/api.ts @@ -50,6 +50,11 @@ function getExtensionInstallStatus( extensionId: ExtensionId, manifest?: chrome.runtime.Manifest, ) { + const customStatus = state.overrideExtensionInstallStatus?.(state, extensionId, manifest) + if (customStatus) { + return customStatus + } + if (manifest && manifest.manifest_version < state.minimumManifestVersion) { return ExtensionInstallStatus.DEPRECATED_MANIFEST_VERSION } @@ -155,6 +160,19 @@ async function beginInstall( state.installing.add(extensionId) await installExtension(extensionId, state) + + if (state.afterInstall) { + // Doesn't need to await, just a callback + state.afterInstall({ + id: extensionId, + localizedName: details.localizedName, + manifest, + icon, + frame: senderFrame, + browserWindow: browserWindow || undefined, + }) + } + return { result: Result.SUCCESS } } catch (error) { console.error('Extension installation failed:', error) diff --git a/packages/electron-chrome-web-store/src/browser/index.ts b/packages/electron-chrome-web-store/src/browser/index.ts index dcc3075a..8d81d8cb 100644 --- a/packages/electron-chrome-web-store/src/browser/index.ts +++ b/packages/electron-chrome-web-store/src/browser/index.ts @@ -10,7 +10,16 @@ export { installExtension, uninstallExtension, downloadExtension } from './insta import { initUpdater } from './updater' export { updateExtensions } from './updater' import { getDefaultExtensionsPath } from './utils' -import { BeforeInstall, ExtensionId, WebStoreState } from './types' +import { + BeforeInstall, + AfterInstall, + ExtensionId, + WebStoreState, + OverrideExtensionInstallStatus, + AfterUninstall, +} from './types' +import { ExtensionInstallStatus } from '../common/constants' +export { ExtensionInstallStatus } function resolvePreloadPath(modulePath?: string) { // Attempt to resolve preload path from module exports @@ -97,6 +106,21 @@ interface ElectronChromeWebStoreOptions { * to be taken. */ beforeInstall?: BeforeInstall + + /** + * Called when determining the install status of an extension. + */ + overrideExtensionInstallStatus?: OverrideExtensionInstallStatus + + /** + * Called after an extension is installed. + */ + afterInstall?: AfterInstall + + /** + * Called after an extension is uninstalled. + */ + afterUninstall?: AfterUninstall } /** @@ -114,6 +138,12 @@ export async function installChromeWebStore(opts: ElectronChromeWebStoreOptions const minimumManifestVersion = typeof opts.minimumManifestVersion === 'number' ? opts.minimumManifestVersion : 3 const beforeInstall = typeof opts.beforeInstall === 'function' ? opts.beforeInstall : undefined + const afterInstall = typeof opts.afterInstall === 'function' ? opts.afterInstall : undefined + const afterUninstall = typeof opts.afterUninstall === 'function' ? opts.afterUninstall : undefined + const overrideExtensionInstallStatus = + typeof opts.overrideExtensionInstallStatus === 'function' + ? opts.overrideExtensionInstallStatus + : undefined const webStoreState: WebStoreState = { session, @@ -123,6 +153,9 @@ export async function installChromeWebStore(opts: ElectronChromeWebStoreOptions denylist: opts.denylist ? new Set(opts.denylist) : undefined, minimumManifestVersion, beforeInstall, + afterInstall, + afterUninstall, + overrideExtensionInstallStatus, } // Add preload script to session diff --git a/packages/electron-chrome-web-store/src/browser/installer.ts b/packages/electron-chrome-web-store/src/browser/installer.ts index 4431e4d0..6dbdf0e3 100644 --- a/packages/electron-chrome-web-store/src/browser/installer.ts +++ b/packages/electron-chrome-web-store/src/browser/installer.ts @@ -13,7 +13,7 @@ import { readCrxFileHeader, readSignedData } from './crx3' import { convertHexadecimalToIDAlphabet, generateId } from './id' import { fetch, getChromeVersion, getDefaultExtensionsPath } from './utils' import { findExtensionInstall } from './loader' -import { ExtensionId } from './types' +import { AfterUninstall, ExtensionId } from './types' const d = debug('electron-chrome-web-store:installer') @@ -203,7 +203,10 @@ interface InstallExtensionOptions extends CommonExtensionOptions { loadExtensionOptions?: Electron.LoadExtensionOptions } -interface UninstallExtensionOptions extends CommonExtensionOptions {} +interface UninstallExtensionOptions extends CommonExtensionOptions { + /** Called after an extension is uninstalled. */ + afterUninstall?: AfterUninstall +} /** * Install extension from the web store. @@ -257,6 +260,14 @@ export async function uninstallExtension( session.removeExtension(extensionId) } + if (opts.afterUninstall) { + await opts.afterUninstall({ + id: extensionId, + extension: existingExt, + manifest: existingExt?.manifest, + }) + } + const extensionDir = path.join(extensionsPath, extensionId) try { const stat = await fs.promises.stat(extensionDir) diff --git a/packages/electron-chrome-web-store/src/browser/types.ts b/packages/electron-chrome-web-store/src/browser/types.ts index 2667fbbd..cf7e9841 100644 --- a/packages/electron-chrome-web-store/src/browser/types.ts +++ b/packages/electron-chrome-web-store/src/browser/types.ts @@ -13,6 +13,20 @@ export type BeforeInstall = ( details: ExtensionInstallDetails, ) => Promise<{ action: 'allow' | 'deny' }> +export type AfterInstall = (details: ExtensionInstallDetails) => Promise + +export type AfterUninstall = (details: { + id: ExtensionId + extension?: Electron.Extension + manifest?: chrome.runtime.Manifest +}) => Promise + +export type OverrideExtensionInstallStatus = ( + state: WebStoreState, + extensionId: ExtensionId, + manifest?: chrome.runtime.Manifest, +) => string | undefined + export interface WebStoreState { session: Electron.Session extensionsPath: string @@ -21,4 +35,7 @@ export interface WebStoreState { denylist?: Set minimumManifestVersion: number beforeInstall?: BeforeInstall + afterInstall?: AfterInstall + afterUninstall?: AfterUninstall + overrideExtensionInstallStatus?: OverrideExtensionInstallStatus } From 6d1b33e6a8d8402d0e9854d416ccbbca04e77834 Mon Sep 17 00:00:00 2001 From: iamEvan <47493765+iamEvanYT@users.noreply.github.com> Date: Mon, 21 Apr 2025 23:49:32 +0100 Subject: [PATCH 2/6] add customSetExtensionEnabled option to installChromeWebStore() --- .../electron-chrome-web-store/src/browser/api.ts | 3 +++ .../electron-chrome-web-store/src/browser/index.ts | 14 ++++++++++++++ .../electron-chrome-web-store/src/browser/types.ts | 7 +++++++ 3 files changed, 24 insertions(+) diff --git a/packages/electron-chrome-web-store/src/browser/api.ts b/packages/electron-chrome-web-store/src/browser/api.ts index 4dbbbc1b..9db4d3b3 100644 --- a/packages/electron-chrome-web-store/src/browser/api.ts +++ b/packages/electron-chrome-web-store/src/browser/api.ts @@ -316,6 +316,9 @@ export function registerWebStoreApi(webStoreState: WebStoreState) { handle('chrome.management.setEnabled', async (event, id, enabled) => { // TODO: Implement enabling/disabling extension + if (webStoreState.customSetExtensionEnabled) { + await webStoreState.customSetExtensionEnabled(webStoreState, id, enabled) + } return true }) diff --git a/packages/electron-chrome-web-store/src/browser/index.ts b/packages/electron-chrome-web-store/src/browser/index.ts index 8d81d8cb..3bfbd67c 100644 --- a/packages/electron-chrome-web-store/src/browser/index.ts +++ b/packages/electron-chrome-web-store/src/browser/index.ts @@ -17,6 +17,7 @@ import { WebStoreState, OverrideExtensionInstallStatus, AfterUninstall, + CustomSetExtensionEnabled, } from './types' import { ExtensionInstallStatus } from '../common/constants' export { ExtensionInstallStatus } @@ -107,6 +108,11 @@ interface ElectronChromeWebStoreOptions { */ beforeInstall?: BeforeInstall + /** + * Called when setting the enabled status of an extension. + */ + customSetExtensionEnabled?: CustomSetExtensionEnabled + /** * Called when determining the install status of an extension. */ @@ -137,9 +143,16 @@ export async function installChromeWebStore(opts: ElectronChromeWebStoreOptions const autoUpdate = typeof opts.autoUpdate === 'boolean' ? opts.autoUpdate : true const minimumManifestVersion = typeof opts.minimumManifestVersion === 'number' ? opts.minimumManifestVersion : 3 + const beforeInstall = typeof opts.beforeInstall === 'function' ? opts.beforeInstall : undefined const afterInstall = typeof opts.afterInstall === 'function' ? opts.afterInstall : undefined const afterUninstall = typeof opts.afterUninstall === 'function' ? opts.afterUninstall : undefined + + const customSetExtensionEnabled = + typeof opts.customSetExtensionEnabled === 'function' + ? opts.customSetExtensionEnabled + : undefined + const overrideExtensionInstallStatus = typeof opts.overrideExtensionInstallStatus === 'function' ? opts.overrideExtensionInstallStatus @@ -155,6 +168,7 @@ export async function installChromeWebStore(opts: ElectronChromeWebStoreOptions beforeInstall, afterInstall, afterUninstall, + customSetExtensionEnabled, overrideExtensionInstallStatus, } diff --git a/packages/electron-chrome-web-store/src/browser/types.ts b/packages/electron-chrome-web-store/src/browser/types.ts index cf7e9841..d91eab42 100644 --- a/packages/electron-chrome-web-store/src/browser/types.ts +++ b/packages/electron-chrome-web-store/src/browser/types.ts @@ -21,6 +21,12 @@ export type AfterUninstall = (details: { manifest?: chrome.runtime.Manifest }) => Promise +export type CustomSetExtensionEnabled = ( + state: WebStoreState, + extensionId: ExtensionId, + enabled: boolean, +) => Promise + export type OverrideExtensionInstallStatus = ( state: WebStoreState, extensionId: ExtensionId, @@ -37,5 +43,6 @@ export interface WebStoreState { beforeInstall?: BeforeInstall afterInstall?: AfterInstall afterUninstall?: AfterUninstall + customSetExtensionEnabled?: CustomSetExtensionEnabled overrideExtensionInstallStatus?: OverrideExtensionInstallStatus } From 33bf92267c295b9e3015f46998c41dc302266bec Mon Sep 17 00:00:00 2001 From: iamEvan <47493765+iamEvanYT@users.noreply.github.com> Date: Mon, 21 Apr 2025 23:51:25 +0100 Subject: [PATCH 3/6] docs --- packages/electron-chrome-web-store/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/electron-chrome-web-store/README.md b/packages/electron-chrome-web-store/README.md index b0183eb4..abe3383c 100644 --- a/packages/electron-chrome-web-store/README.md +++ b/packages/electron-chrome-web-store/README.md @@ -102,7 +102,7 @@ Installs Chrome Web Store support in the specified session. - `beforeInstall`: A function which receives install details and returns a promise. Allows for prompting prior to install. - `afterInstall`: A function which receives install details. Allows for additional actions after install. - `afterUninstall`: A function which receives extension ID, extension, and manifest. Allows for additional actions after uninstall. - - `overrideExtensionInstallStatus`: A function which receives the current state, extension ID, and manifest. Returns a string indicating the install status of the extension. + - `overrideExtensionInstallStatus`: A function which receives the current state, extension ID, and manifest. Returns a string indicating the install status of the extension, or returns undefined to fallback to the default install status. ### `installExtension` From edf0663dd5814a7f5c3b136c4eda95c9a83af5c7 Mon Sep 17 00:00:00 2001 From: Evan <47493765+iamEvanYT@users.noreply.github.com> Date: Thu, 24 Apr 2025 04:27:48 +0800 Subject: [PATCH 4/6] Update api.ts Co-authored-by: Sam Maddock --- packages/electron-chrome-web-store/src/browser/api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/electron-chrome-web-store/src/browser/api.ts b/packages/electron-chrome-web-store/src/browser/api.ts index 9db4d3b3..49331469 100644 --- a/packages/electron-chrome-web-store/src/browser/api.ts +++ b/packages/electron-chrome-web-store/src/browser/api.ts @@ -50,8 +50,8 @@ function getExtensionInstallStatus( extensionId: ExtensionId, manifest?: chrome.runtime.Manifest, ) { - const customStatus = state.overrideExtensionInstallStatus?.(state, extensionId, manifest) - if (customStatus) { + const customStatus: unknown = state.overrideExtensionInstallStatus?.(state, extensionId, manifest) + if (typeof customStatus === 'string') { return customStatus } From f5c57c5f1875010a6c39520f3b1a05acd5c4e161 Mon Sep 17 00:00:00 2001 From: iamEvan <47493765+iamEvanYT@users.noreply.github.com> Date: Thu, 24 Apr 2025 18:08:58 +0100 Subject: [PATCH 5/6] Move `afterUninstall` into api.ts & improvements --- packages/electron-chrome-web-store/README.md | 2 +- .../src/browser/api.ts | 8 ++++++++ .../src/browser/installer.ts | 18 +----------------- .../src/browser/types.ts | 6 +----- 4 files changed, 11 insertions(+), 23 deletions(-) diff --git a/packages/electron-chrome-web-store/README.md b/packages/electron-chrome-web-store/README.md index abe3383c..8169ffd4 100644 --- a/packages/electron-chrome-web-store/README.md +++ b/packages/electron-chrome-web-store/README.md @@ -101,7 +101,7 @@ Installs Chrome Web Store support in the specified session. - `denylist`: An array of denied extension IDs to install. - `beforeInstall`: A function which receives install details and returns a promise. Allows for prompting prior to install. - `afterInstall`: A function which receives install details. Allows for additional actions after install. - - `afterUninstall`: A function which receives extension ID, extension, and manifest. Allows for additional actions after uninstall. + - `afterUninstall`: A function which receives extension ID. Allows for additional actions after uninstall. - `overrideExtensionInstallStatus`: A function which receives the current state, extension ID, and manifest. Returns a string indicating the install status of the extension, or returns undefined to fallback to the default install status. ### `installExtension` diff --git a/packages/electron-chrome-web-store/src/browser/api.ts b/packages/electron-chrome-web-store/src/browser/api.ts index 49331469..a4dfb5e4 100644 --- a/packages/electron-chrome-web-store/src/browser/api.ts +++ b/packages/electron-chrome-web-store/src/browser/api.ts @@ -331,9 +331,17 @@ export function registerWebStoreApi(webStoreState: WebStoreState) { try { await uninstallExtension(id, webStoreState) + queueMicrotask(() => { event.sender.send('chrome.management.onUninstalled', id) }) + + if (webStoreState.afterUninstall) { + queueMicrotask(() => { + webStoreState.afterUninstall?.({ id }) + }) + } + return Result.SUCCESS } catch (error) { console.error(error) diff --git a/packages/electron-chrome-web-store/src/browser/installer.ts b/packages/electron-chrome-web-store/src/browser/installer.ts index 6dbdf0e3..5dd64693 100644 --- a/packages/electron-chrome-web-store/src/browser/installer.ts +++ b/packages/electron-chrome-web-store/src/browser/installer.ts @@ -203,11 +203,6 @@ interface InstallExtensionOptions extends CommonExtensionOptions { loadExtensionOptions?: Electron.LoadExtensionOptions } -interface UninstallExtensionOptions extends CommonExtensionOptions { - /** Called after an extension is uninstalled. */ - afterUninstall?: AfterUninstall -} - /** * Install extension from the web store. */ @@ -245,10 +240,7 @@ export async function installExtension( /** * Uninstall extension from the web store. */ -export async function uninstallExtension( - extensionId: string, - opts: UninstallExtensionOptions = {}, -) { +export async function uninstallExtension(extensionId: string, opts: CommonExtensionOptions = {}) { d('uninstalling %s', extensionId) const session = opts.session || electronSession.defaultSession @@ -260,14 +252,6 @@ export async function uninstallExtension( session.removeExtension(extensionId) } - if (opts.afterUninstall) { - await opts.afterUninstall({ - id: extensionId, - extension: existingExt, - manifest: existingExt?.manifest, - }) - } - const extensionDir = path.join(extensionsPath, extensionId) try { const stat = await fs.promises.stat(extensionDir) diff --git a/packages/electron-chrome-web-store/src/browser/types.ts b/packages/electron-chrome-web-store/src/browser/types.ts index d91eab42..e10aabc1 100644 --- a/packages/electron-chrome-web-store/src/browser/types.ts +++ b/packages/electron-chrome-web-store/src/browser/types.ts @@ -15,11 +15,7 @@ export type BeforeInstall = ( export type AfterInstall = (details: ExtensionInstallDetails) => Promise -export type AfterUninstall = (details: { - id: ExtensionId - extension?: Electron.Extension - manifest?: chrome.runtime.Manifest -}) => Promise +export type AfterUninstall = (details: { id: ExtensionId }) => Promise export type CustomSetExtensionEnabled = ( state: WebStoreState, From 5844a14c14fc083f0efaa96b011f1eb915d3fce8 Mon Sep 17 00:00:00 2001 From: iamEvan <47493765+iamEvanYT@users.noreply.github.com> Date: Thu, 24 Apr 2025 18:15:33 +0100 Subject: [PATCH 6/6] Expose ExtensionStatusDetails instead of WebStoreState --- .../src/browser/api.ts | 26 +++++++++++++++---- .../src/browser/types.ts | 9 +++++-- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/packages/electron-chrome-web-store/src/browser/api.ts b/packages/electron-chrome-web-store/src/browser/api.ts index a4dfb5e4..3eccf00f 100644 --- a/packages/electron-chrome-web-store/src/browser/api.ts +++ b/packages/electron-chrome-web-store/src/browser/api.ts @@ -11,7 +11,7 @@ import { WebGlStatus, } from '../common/constants' import { installExtension, uninstallExtension } from './installer' -import { ExtensionId, WebStoreState } from './types' +import { ExtensionId, ExtensionStatusDetails, WebStoreState } from './types' const d = debug('electron-chrome-web-store:api') @@ -50,9 +50,21 @@ function getExtensionInstallStatus( extensionId: ExtensionId, manifest?: chrome.runtime.Manifest, ) { - const customStatus: unknown = state.overrideExtensionInstallStatus?.(state, extensionId, manifest) - if (typeof customStatus === 'string') { - return customStatus + if (state.overrideExtensionInstallStatus) { + const details: ExtensionStatusDetails = { + session: state.session, + extensionsPath: state.extensionsPath, + } + + const customStatus: unknown = state.overrideExtensionInstallStatus?.( + extensionId, + details, + manifest, + ) + + if (typeof customStatus === 'string') { + return customStatus + } } if (manifest && manifest.manifest_version < state.minimumManifestVersion) { @@ -317,7 +329,11 @@ export function registerWebStoreApi(webStoreState: WebStoreState) { handle('chrome.management.setEnabled', async (event, id, enabled) => { // TODO: Implement enabling/disabling extension if (webStoreState.customSetExtensionEnabled) { - await webStoreState.customSetExtensionEnabled(webStoreState, id, enabled) + const details: ExtensionStatusDetails = { + session: webStoreState.session, + extensionsPath: webStoreState.extensionsPath, + } + await webStoreState.customSetExtensionEnabled(id, details, enabled) } return true }) diff --git a/packages/electron-chrome-web-store/src/browser/types.ts b/packages/electron-chrome-web-store/src/browser/types.ts index e10aabc1..74794245 100644 --- a/packages/electron-chrome-web-store/src/browser/types.ts +++ b/packages/electron-chrome-web-store/src/browser/types.ts @@ -17,15 +17,20 @@ export type AfterInstall = (details: ExtensionInstallDetails) => Promise export type AfterUninstall = (details: { id: ExtensionId }) => Promise +export type ExtensionStatusDetails = { + session: Electron.Session + extensionsPath: string +} + export type CustomSetExtensionEnabled = ( - state: WebStoreState, extensionId: ExtensionId, + details: ExtensionStatusDetails, enabled: boolean, ) => Promise export type OverrideExtensionInstallStatus = ( - state: WebStoreState, extensionId: ExtensionId, + details: ExtensionStatusDetails, manifest?: chrome.runtime.Manifest, ) => string | undefined