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
4 changes: 4 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,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`

Expand Down
43 changes: 41 additions & 2 deletions 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,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
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
})

Expand All @@ -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)
Expand Down
47 changes: 46 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,
GetExtensionInstallStatus,
AfterUninstall,
SetExtensionEnabled,
} 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

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

/**
Expand All @@ -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,
Expand All @@ -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
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 SetExtensionEnabled = (
extensionId: ExtensionId,
details: ExtensionStatusDetails,
enabled: boolean,
) => Promise<void>

export type GetExtensionInstallStatus = (
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
setExtensionEnabled?: SetExtensionEnabled
getExtensionInstallStatus?: GetExtensionInstallStatus
}
Loading