Skip to content
Open
3 changes: 3 additions & 0 deletions packages/electron-chrome-web-store/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. 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`

Expand Down
47 changes: 46 additions & 1 deletion packages/electron-chrome-web-store/src/browser/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -50,6 +50,23 @@ function getExtensionInstallStatus(
extensionId: ExtensionId,
manifest?: chrome.runtime.Manifest,
) {
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) {
return ExtensionInstallStatus.DEPRECATED_MANIFEST_VERSION
}
Expand Down Expand Up @@ -155,6 +172,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)
Expand Down Expand Up @@ -298,6 +328,13 @@ export function registerWebStoreApi(webStoreState: WebStoreState) {

handle('chrome.management.setEnabled', async (event, id, enabled) => {
// TODO: Implement enabling/disabling extension
if (webStoreState.customSetExtensionEnabled) {
const details: ExtensionStatusDetails = {
session: webStoreState.session,
extensionsPath: webStoreState.extensionsPath,
}
await webStoreState.customSetExtensionEnabled(id, details, enabled)
}
return true
})

Expand All @@ -310,9 +347,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)
Expand Down
49 changes: 48 additions & 1 deletion packages/electron-chrome-web-store/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
OverrideExtensionInstallStatus,
AfterUninstall,
CustomSetExtensionEnabled,
} from './types'
import { ExtensionInstallStatus } from '../common/constants'
export { ExtensionInstallStatus }

function resolvePreloadPath(modulePath?: string) {
// Attempt to resolve preload path from module exports
Expand Down Expand Up @@ -97,6 +107,26 @@ interface ElectronChromeWebStoreOptions {
* to be taken.
*/
beforeInstall?: BeforeInstall

/**
* Called when setting the enabled status of an extension.
*/
customSetExtensionEnabled?: CustomSetExtensionEnabled

/**
* 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
}

/**
Expand All @@ -113,7 +143,20 @@ 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
: undefined

const webStoreState: WebStoreState = {
session,
Expand All @@ -123,6 +166,10 @@ export async function installChromeWebStore(opts: ElectronChromeWebStoreOptions
denylist: opts.denylist ? new Set(opts.denylist) : undefined,
minimumManifestVersion,
beforeInstall,
afterInstall,
afterUninstall,
customSetExtensionEnabled,
overrideExtensionInstallStatus,
}

// Add preload script to session
Expand Down
9 changes: 2 additions & 7 deletions packages/electron-chrome-web-store/src/browser/installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -203,8 +203,6 @@ interface InstallExtensionOptions extends CommonExtensionOptions {
loadExtensionOptions?: Electron.LoadExtensionOptions
}

interface UninstallExtensionOptions extends CommonExtensionOptions {}

/**
* Install extension from the web store.
*/
Expand Down Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions packages/electron-chrome-web-store/src/browser/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,27 @@ export type BeforeInstall = (
details: ExtensionInstallDetails,
) => Promise<{ action: 'allow' | 'deny' }>

export type AfterInstall = (details: ExtensionInstallDetails) => Promise<void>

export type AfterUninstall = (details: { id: ExtensionId }) => Promise<void>

export type ExtensionStatusDetails = {
session: Electron.Session
extensionsPath: string
}

export type CustomSetExtensionEnabled = (
extensionId: ExtensionId,
details: ExtensionStatusDetails,
enabled: boolean,
) => Promise<void>

export type OverrideExtensionInstallStatus = (
extensionId: ExtensionId,
details: ExtensionStatusDetails,
manifest?: chrome.runtime.Manifest,
) => string | undefined

export interface WebStoreState {
session: Electron.Session
extensionsPath: string
Expand All @@ -21,4 +42,8 @@ export interface WebStoreState {
denylist?: Set<ExtensionId>
minimumManifestVersion: number
beforeInstall?: BeforeInstall
afterInstall?: AfterInstall
afterUninstall?: AfterUninstall
customSetExtensionEnabled?: CustomSetExtensionEnabled
overrideExtensionInstallStatus?: OverrideExtensionInstallStatus
}