Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, 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, or returns undefined to fallback to the default install status.

### `installExtension`

Expand Down
21 changes: 21 additions & 0 deletions packages/electron-chrome-web-store/src/browser/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -298,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
})

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
15 changes: 13 additions & 2 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,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.
Expand Down Expand Up @@ -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)
Expand Down
24 changes: 24 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,26 @@ export type BeforeInstall = (
details: ExtensionInstallDetails,
) => Promise<{ action: 'allow' | 'deny' }>

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

export type AfterUninstall = (details: {
id: ExtensionId
extension?: Electron.Extension
manifest?: chrome.runtime.Manifest
}) => Promise<void>

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

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

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