Skip to content
Open
Show file tree
Hide file tree
Changes from all 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. 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) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to getExtensionInstallStatus

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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to setExtensionEnabled and add documentation referencing chrome.management.setEnabled

}
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 })
})
Comment on lines +356 to +358
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the queueMicrotask needed?

Suggested change
queueMicrotask(() => {
webStoreState.afterUninstall?.({ id })
})
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
}