diff --git a/packages/electron-chrome-web-store/README.md b/packages/electron-chrome-web-store/README.md index 217fceb1..791754e4 100644 --- a/packages/electron-chrome-web-store/README.md +++ b/packages/electron-chrome-web-store/README.md @@ -100,6 +100,10 @@ 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. Allows for additional actions after uninstall. + - `setExtensionEnabled`: A function which receives the extension ID, current state details, and enabled flag. Allows the host app to implement [`chrome.management.setEnabled`](https://developer.chrome.com/docs/extensions/reference/api/management#method-setEnabled). + - `getExtensionInstallStatus`: 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 7515e4d6..784a5d9e 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,6 +50,19 @@ function getExtensionInstallStatus( extensionId: ExtensionId, manifest?: chrome.runtime.Manifest, ) { + if (state.getExtensionInstallStatus) { + const details: ExtensionStatusDetails = { + session: state.session, + extensionsPath: state.extensionsPath, + } + + const customStatus: unknown = state.getExtensionInstallStatus?.(extensionId, details, manifest) + + if (typeof customStatus === 'string') { + return customStatus + } + } + if (manifest && manifest.manifest_version < state.minimumManifestVersion) { return ExtensionInstallStatus.DEPRECATED_MANIFEST_VERSION } @@ -155,6 +168,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) @@ -297,7 +323,14 @@ export function registerWebStoreApi(webStoreState: WebStoreState) { }) handle('chrome.management.setEnabled', async (event, id, enabled) => { - // TODO: Implement enabling/disabling extension + // Allows the host app to implement its own logic for enabling/disabling extensions. + if (webStoreState.setExtensionEnabled) { + const details: ExtensionStatusDetails = { + session: webStoreState.session, + extensionsPath: webStoreState.extensionsPath, + } + await webStoreState.setExtensionEnabled(id, details, enabled) + } return true }) @@ -310,9 +343,15 @@ export function registerWebStoreApi(webStoreState: WebStoreState) { try { await uninstallExtension(id, webStoreState) + queueMicrotask(() => { event.sender.send('chrome.management.onUninstalled', id) }) + + if (webStoreState.afterUninstall) { + webStoreState.afterUninstall?.({ id }) + } + return Result.SUCCESS } catch (error) { console.error(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..3d093483 100644 --- a/packages/electron-chrome-web-store/src/browser/index.ts +++ b/packages/electron-chrome-web-store/src/browser/index.ts @@ -10,7 +10,17 @@ 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, + GetExtensionInstallStatus, + AfterUninstall, + SetExtensionEnabled, +} from './types' +import { ExtensionInstallStatus } from '../common/constants' +export { ExtensionInstallStatus } function resolvePreloadPath(modulePath?: string) { // Attempt to resolve preload path from module exports @@ -97,6 +107,26 @@ interface ElectronChromeWebStoreOptions { * to be taken. */ beforeInstall?: BeforeInstall + + /** + * Used to implement `chrome.management.setEnabled` + */ + setExtensionEnabled?: SetExtensionEnabled + + /** + * Called when determining the install status of an extension. + */ + getExtensionInstallStatus?: GetExtensionInstallStatus + + /** + * Called after an extension is installed. + */ + afterInstall?: AfterInstall + + /** + * Called after an extension is uninstalled. + */ + afterUninstall?: AfterUninstall } /** @@ -113,7 +143,18 @@ 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 setExtensionEnabled = + typeof opts.setExtensionEnabled === 'function' ? opts.setExtensionEnabled : undefined + + const getExtensionInstallStatus = + typeof opts.getExtensionInstallStatus === 'function' + ? opts.getExtensionInstallStatus + : undefined const webStoreState: WebStoreState = { session, @@ -123,6 +164,10 @@ export async function installChromeWebStore(opts: ElectronChromeWebStoreOptions denylist: opts.denylist ? new Set(opts.denylist) : undefined, minimumManifestVersion, beforeInstall, + afterInstall, + afterUninstall, + setExtensionEnabled, + getExtensionInstallStatus, } // 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..5dd64693 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,8 +203,6 @@ interface InstallExtensionOptions extends CommonExtensionOptions { loadExtensionOptions?: Electron.LoadExtensionOptions } -interface UninstallExtensionOptions extends CommonExtensionOptions {} - /** * Install extension from the web store. */ @@ -242,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 diff --git a/packages/electron-chrome-web-store/src/browser/types.ts b/packages/electron-chrome-web-store/src/browser/types.ts index 2667fbbd..683b0028 100644 --- a/packages/electron-chrome-web-store/src/browser/types.ts +++ b/packages/electron-chrome-web-store/src/browser/types.ts @@ -13,6 +13,27 @@ export type BeforeInstall = ( details: ExtensionInstallDetails, ) => Promise<{ action: 'allow' | 'deny' }> +export type AfterInstall = (details: ExtensionInstallDetails) => Promise + +export type AfterUninstall = (details: { id: ExtensionId }) => Promise + +export type ExtensionStatusDetails = { + session: Electron.Session + extensionsPath: string +} + +export type SetExtensionEnabled = ( + extensionId: ExtensionId, + details: ExtensionStatusDetails, + enabled: boolean, +) => Promise + +export type GetExtensionInstallStatus = ( + extensionId: ExtensionId, + details: ExtensionStatusDetails, + manifest?: chrome.runtime.Manifest, +) => string | undefined + export interface WebStoreState { session: Electron.Session extensionsPath: string @@ -21,4 +42,8 @@ export interface WebStoreState { denylist?: Set minimumManifestVersion: number beforeInstall?: BeforeInstall + afterInstall?: AfterInstall + afterUninstall?: AfterUninstall + setExtensionEnabled?: SetExtensionEnabled + getExtensionInstallStatus?: GetExtensionInstallStatus }