From d74fcfcecb3f99c98def1b8b6bbb23b6bd59ea68 Mon Sep 17 00:00:00 2001 From: Jan Hannemann Date: Thu, 29 Jan 2026 13:38:26 -0800 Subject: [PATCH] feat: msix auto-updater (#49230) * feat: native auto updater for MSIX on Windows * doc: added MSIX debug documentation * fix: allow downgrade with json release file and emit update-available * test: msix auot-update tests * doc: API documentation * test: add package version validation * fix: docs typo * fix: don't allow auto-updating when using appinstaller manifest * fix: getPackageInfo interface implementation * fix: review feedback, add comment * fix: missed filename commit * fix: install test cert on demand * fix: time stamp mismatch in tests * fix: feedback - rename to MSIXPackageInfo * fix: update and reference windowsStore property * fix: remove getPackagInfo from public API * fix: type error bcause of removed API --- docs/api/auto-updater.md | 31 +- docs/api/environment-variables.md | 16 + docs/api/process.md | 4 +- filenames.auto.gni | 2 + filenames.gni | 2 + lib/browser/api/auto-updater.ts | 7 +- .../api/auto-updater/auto-updater-msix.ts | 449 +++++++++++++++ .../api/auto-updater/auto-updater-win.ts | 5 + .../api/auto-updater/msix-update-win.ts | 4 + .../browser/api/electron_api_msix_updater.cc | 527 ++++++++++++++++++ shell/browser/api/electron_api_msix_updater.h | 14 + shell/common/node_bindings.cc | 1 + spec/api-autoupdater-msix-spec.ts | 336 +++++++++++ .../msix/ElectronDevAppxManifest.xml | 50 ++ .../api/autoupdater/msix/HelloMSIX_V1.msix | Bin 0 -> 68114 bytes .../api/autoupdater/msix/HelloMSIX_V2.msix | Bin 0 -> 68114 bytes .../api/autoupdater/msix/MSIXDevCert.cer | Bin 0 -> 774 bytes .../autoupdater/msix/install_test_cert.ps1 | 22 + spec/fixtures/api/autoupdater/msix/main.js | 96 ++++ spec/lib/msix-helpers.ts | 149 +++++ 20 files changed, 1707 insertions(+), 8 deletions(-) create mode 100644 lib/browser/api/auto-updater/auto-updater-msix.ts create mode 100644 lib/browser/api/auto-updater/msix-update-win.ts create mode 100644 shell/browser/api/electron_api_msix_updater.cc create mode 100644 shell/browser/api/electron_api_msix_updater.h create mode 100644 spec/api-autoupdater-msix-spec.ts create mode 100644 spec/fixtures/api/autoupdater/msix/ElectronDevAppxManifest.xml create mode 100644 spec/fixtures/api/autoupdater/msix/HelloMSIX_V1.msix create mode 100644 spec/fixtures/api/autoupdater/msix/HelloMSIX_V2.msix create mode 100644 spec/fixtures/api/autoupdater/msix/MSIXDevCert.cer create mode 100644 spec/fixtures/api/autoupdater/msix/install_test_cert.ps1 create mode 100644 spec/fixtures/api/autoupdater/msix/main.js create mode 100644 spec/lib/msix-helpers.ts diff --git a/docs/api/auto-updater.md b/docs/api/auto-updater.md index 350bcb099b..5472904f10 100644 --- a/docs/api/auto-updater.md +++ b/docs/api/auto-updater.md @@ -32,9 +32,19 @@ update process. Apps that need to disable ATS can add the ### Windows -On Windows, you have to install your app into a user's machine before you can -use the `autoUpdater`, so it is recommended that you use -[electron-winstaller][installer-lib] or [Electron Forge's Squirrel.Windows maker][electron-forge-lib] to generate a Windows installer. +On Windows, the `autoUpdater` module automatically selects the appropriate update mechanism +based on how your app is packaged: + +* **MSIX packages**: If your app is running as an MSIX package (created with [electron-windows-msix][msix-lib] and detected via [`process.windowsStore`](process.md#processwindowsstore-readonly)), + the module uses the MSIX updater, which supports direct MSIX file links and JSON update feeds. +* **Squirrel.Windows**: For apps installed via traditional installers (created with + [electron-winstaller][installer-lib] or [Electron Forge's Squirrel.Windows maker][electron-forge-lib]), + the module uses Squirrel.Windows for updates. + +You don't need to configure which updater to use; Electron automatically detects the packaging +format and uses the appropriate one. + +#### Squirrel.Windows Apps built with Squirrel.Windows will trigger [custom launch events](https://github.com/Squirrel/Squirrel.Windows/blob/51f5e2cb01add79280a53d51e8d0cfa20f8c9f9f/docs/using/custom-squirrel-events-non-cs.md#application-startup-commands) that must be handled by your Electron application to ensure proper setup and teardown. @@ -55,6 +65,14 @@ The installer generated with Squirrel.Windows will create a shortcut icon with a same ID for your app with `app.setAppUserModelId` API, otherwise Windows will not be able to pin your app properly in task bar. +#### MSIX Packages + +When your app is packaged as an MSIX, the `autoUpdater` module provides additional +functionality: + +* Use the `allowAnyVersion` option in `setFeedURL()` to allow updates to older versions (downgrades) +* Support for direct MSIX file links or JSON update feeds (similar to Squirrel.Mac format) + ## Events The `autoUpdater` object emits the following events: @@ -92,7 +110,7 @@ Returns: Emitted when an update has been downloaded. -On Windows only `releaseName` is available. +With Squirrel.Windows only `releaseName` is available. > [!NOTE] > It is not strictly necessary to handle this event. A successfully @@ -128,10 +146,12 @@ changes: --> * `options` Object - * `url` string + * `url` string - The update server URL. For _Windows_ MSIX, this can be either a direct link to an MSIX file (e.g., `https://example.com/update.msix`) or a JSON endpoint that returns update information (see the [Squirrel.Mac][squirrel-mac] README for more information). * `headers` Record\ (optional) _macOS_ - HTTP request headers. * `serverType` string (optional) _macOS_ - Can be `json` or `default`, see the [Squirrel.Mac][squirrel-mac] README for more information. + * `allowAnyVersion` boolean (optional) _Windows_ - If `true`, allows downgrades to older versions for MSIX packages. + Defaults to `false`. Sets the `url` and initialize the auto updater. @@ -168,3 +188,4 @@ closed. [electron-forge-lib]: https://www.electronforge.io/config/makers/squirrel.windows [app-user-model-id]: https://learn.microsoft.com/en-us/windows/win32/shell/appids [event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter +[msix-lib]: https://github.com/electron-userland/electron-windows-msix diff --git a/docs/api/environment-variables.md b/docs/api/environment-variables.md index 5b6305edac..b2acc3ee3f 100644 --- a/docs/api/environment-variables.md +++ b/docs/api/environment-variables.md @@ -159,6 +159,22 @@ Notification activated (com.github.Electron:notification:EAF7B87C-A113-43D7-8E76 Notification replied to (com.github.Electron:notification:EAF7B87C-A113-43D7-8E76-F88EC9D73D44) ``` +### `ELECTRON_DEBUG_MSIX_UPDATER` + +Adds extra logs to MSIX updater operations on Windows to aid in debugging. Extra logging will be displayed when MSIX update operations are initiated, including package updates, package registration, and restart registration. This helps diagnose issues with MSIX package updates and deployments. + +Sample output: + +```sh +UpdateMsix called with URI: https://example.com/app.msix +DoUpdateMsix: Starting +Calling AddPackageByUriAsync... URI: https://example.com/app.msix +Update options - deferRegistration: true, developerMode: false, forceShutdown: false, forceTargetShutdown: false, forceUpdateFromAnyVersion: false +Waiting for deployment... +Deployment finished. +MSIX Deployment completed. +``` + ### `ELECTRON_LOG_ASAR_READS` When Electron reads from an ASAR file, log the read offset and file path to diff --git a/docs/api/process.md b/docs/api/process.md index 78685084d2..4ecdf8a464 100644 --- a/docs/api/process.md +++ b/docs/api/process.md @@ -128,8 +128,8 @@ A `string` representing Electron's version string. ### `process.windowsStore` _Readonly_ -A `boolean`. If the app is running as a Windows Store app (appx), this property is `true`, -for otherwise it is `undefined`. +A `boolean`. If the app is running as an MSIX package (including AppX for Windows Store), +this property is `true`, otherwise it is `undefined`. ### `process.contextId` _Readonly_ diff --git a/filenames.auto.gni b/filenames.auto.gni index 95c779b8e3..87434e4539 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -230,8 +230,10 @@ auto_filenames = { browser_bundle_deps = [ "lib/browser/api/app.ts", "lib/browser/api/auto-updater.ts", + "lib/browser/api/auto-updater/auto-updater-msix.ts", "lib/browser/api/auto-updater/auto-updater-native.ts", "lib/browser/api/auto-updater/auto-updater-win.ts", + "lib/browser/api/auto-updater/msix-update-win.ts", "lib/browser/api/auto-updater/squirrel-update-win.ts", "lib/browser/api/base-window.ts", "lib/browser/api/browser-view.ts", diff --git a/filenames.gni b/filenames.gni index 21b57205fe..d8ca1fec8d 100644 --- a/filenames.gni +++ b/filenames.gni @@ -279,6 +279,8 @@ filenames = { "shell/browser/api/electron_api_in_app_purchase.h", "shell/browser/api/electron_api_menu.cc", "shell/browser/api/electron_api_menu.h", + "shell/browser/api/electron_api_msix_updater.cc", + "shell/browser/api/electron_api_msix_updater.h", "shell/browser/api/electron_api_native_theme.cc", "shell/browser/api/electron_api_native_theme.h", "shell/browser/api/electron_api_net_log.cc", diff --git a/lib/browser/api/auto-updater.ts b/lib/browser/api/auto-updater.ts index d433cda0e6..d0c61dbc7d 100644 --- a/lib/browser/api/auto-updater.ts +++ b/lib/browser/api/auto-updater.ts @@ -1,5 +1,10 @@ if (process.platform === 'win32') { - module.exports = require('./auto-updater/auto-updater-win'); + // windowsStore indicates whether the app is running as a packaged app (MSIX), even outside of the store + if (process.windowsStore) { + module.exports = require('./auto-updater/auto-updater-msix'); + } else { + module.exports = require('./auto-updater/auto-updater-win'); + } } else { module.exports = require('./auto-updater/auto-updater-native'); } diff --git a/lib/browser/api/auto-updater/auto-updater-msix.ts b/lib/browser/api/auto-updater/auto-updater-msix.ts new file mode 100644 index 0000000000..5b8ff6856c --- /dev/null +++ b/lib/browser/api/auto-updater/auto-updater-msix.ts @@ -0,0 +1,449 @@ +import * as msixUpdate from '@electron/internal/browser/api/auto-updater/msix-update-win'; + +import { app, net } from 'electron/main'; + +import { EventEmitter } from 'events'; + +interface UpdateInfo { + ok: boolean; // False if error encountered + available?: boolean; // True if the update is available, false if not + updateUrl?: string; // The URL of the update + releaseNotes?: string; // The release notes of the update + releaseName?: string; // The release name of the update + releaseDate?: Date; // The release date of the update +} + +interface MSIXPackageInfo { + id: string; + familyName: string; + developmentMode: boolean; + version: string; + signatureKind: 'developer' | 'enterprise' | 'none' | 'store' | 'system'; + appInstallerUri?: string; +} + +/** + * Options for updating an MSIX package. + * Used with `updateMsix()` to control how the package update behaves. + * + * These options correspond to the Windows.Management.Deployment.AddPackageOptions class properties. + * + * @see https://learn.microsoft.com/en-us/uwp/api/windows.management.deployment.addpackageoptions?view=winrt-26100 + */ +export interface UpdateMsixOptions { + /** + * Gets or sets a value that indicates whether to delay registration of the main package + * or dependency packages if the packages are currently in use. + * + * Corresponds to `AddPackageOptions.DeferRegistrationWhenPackagesAreInUse` + * + * @default false + */ + deferRegistration?: boolean; + + /** + * Gets or sets a value that indicates whether the app is installed in developer mode. + * When set, the app is installed in development mode which allows for a more rapid + * development cycle. The BlockMap.xml, [Content_Types].xml, and digital signature + * files are not required for app installation. + * + * Corresponds to `AddPackageOptions.DeveloperMode` + * + * @default false + */ + developerMode?: boolean; + + /** + * Gets or sets a value that indicates whether the processes associated with the package + * will be shut down forcibly so that registration can continue if the package, or any + * package that depends on the package, is currently in use. + * + * Corresponds to `AddPackageOptions.ForceAppShutdown` + * + * @default false + */ + forceShutdown?: boolean; + + /** + * Gets or sets a value that indicates whether the processes associated with the package + * will be shut down forcibly so that registration can continue if the package is + * currently in use. + * + * Corresponds to `AddPackageOptions.ForceTargetAppShutdown` + * + * @default false + */ + forceTargetShutdown?: boolean; + + /** + * Gets or sets a value that indicates whether to force a specific version of a package + * to be added, regardless of if a higher version is already added. + * + * Corresponds to `AddPackageOptions.ForceUpdateFromAnyVersion` + * + * @default false + */ + forceUpdateFromAnyVersion?: boolean; +} + +/** + * Options for registering an MSIX package. + * Used with `registerPackage()` to control how the package registration behaves. + * + * These options correspond to the Windows.Management.Deployment.DeploymentOptions enum. + * + * @see https://learn.microsoft.com/en-us/uwp/api/windows.management.deployment.deploymentoptions?view=winrt-26100 + */ +interface RegisterPackageOptions { + /** + * Force shutdown of the application if it's currently running. + * If this package, or any package that depends on this package, is currently in use, + * the processes associated with the package are shut down forcibly so that registration can continue. + * + * Corresponds to `DeploymentOptions.ForceApplicationShutdown` (value: 1) + * + * @default false + */ + forceShutdown?: boolean; + + /** + * Force shutdown of the target application if it's currently running. + * If this package is currently in use, the processes associated with the package + * are shut down forcibly so that registration can continue. + * + * Corresponds to `DeploymentOptions.ForceTargetApplicationShutdown` (value: 64) + * + * @default false + */ + forceTargetShutdown?: boolean; + + /** + * Force a specific version of a package to be staged/registered, regardless of if + * a higher version is already staged/registered. + * + * Corresponds to `DeploymentOptions.ForceUpdateFromAnyVersion` (value: 262144) + * + * @default false + */ + forceUpdateFromAnyVersion?: boolean; +} + +class AutoUpdater extends EventEmitter implements Electron.AutoUpdater { + updateAvailable: boolean = false; + updateURL: string | null = null; + updateHeaders: Record | null = null; + allowAnyVersion: boolean = false; + + // Private: Validate that the URL points to an MSIX file (following redirects) + private async validateMsixUrl (url: string): Promise { + try { + // Make a HEAD request to follow redirects and get the final URL + const response = await net.fetch(url, { + method: 'HEAD', + headers: this.updateHeaders ? new Headers(this.updateHeaders) : undefined, + redirect: 'follow' // Follow redirects to get the final URL + }); + + // Get the final URL after redirects (response.url contains the final URL) + const finalUrl = response.url || url; + const urlObj = new URL(finalUrl); + const pathname = urlObj.pathname.toLowerCase(); + + // Check if final URL ends with .msix or .msixbundle extension + const hasMsixExtension = pathname.endsWith('.msix') || pathname.endsWith('.msixbundle'); + + if (!hasMsixExtension) { + throw new Error(`Update URL does not point to an MSIX file. Expected .msix or .msixbundle extension, got final URL: ${finalUrl}`); + } + } catch (error) { + if (error instanceof TypeError) { + throw new Error(`Invalid MSIX URL: ${url}`); + } + throw error; + } + } + + // Private: Check if URL is a direct MSIX file (following redirects) + private async isDirectMsixUrl (url: string, emitError: boolean = false): Promise { + try { + await this.validateMsixUrl(url); + return true; + } catch (error) { + if (emitError) { + this.emitError(error as Error); + } + return false; + } + } + + // Supports both versioning (x.y.z) and Windows version format (x.y.z.a) + // Returns: 1 if v1 > v2, -1 if v1 < v2, 0 if v1 === v2 + private compareVersions (v1: string, v2: string): number { + const parts1 = v1.split('.').map(part => { + const parsed = parseInt(part, 10); + return isNaN(parsed) ? 0 : parsed; + }); + const parts2 = v2.split('.').map(part => { + const parsed = parseInt(part, 10); + return isNaN(parsed) ? 0 : parsed; + }); + + const maxLength = Math.max(parts1.length, parts2.length); + + for (let i = 0; i < maxLength; i++) { + const part1 = parts1[i] ?? 0; + const part2 = parts2[i] ?? 0; + + if (part1 > part2) return 1; + if (part1 < part2) return -1; + } + + return 0; + } + + // Private: Parse the static releases array format + // This is a static JSON file containing all releases + private parseStaticReleasFile (json: any, currentVersion: string): { ok: boolean; available: boolean; url?: string; name?: string; notes?: string; pub_date?: string } { + if (!Array.isArray(json.releases) || !json.currentRelease || typeof json.currentRelease !== 'string') { + this.emitError(new Error('Invalid releases format. Expected \'releases\' array and \'currentRelease\' string.')); + return { ok: false, available: false }; + } + + // Use currentRelease property to determine if update is available + const currentReleaseVersion = json.currentRelease; + + // Compare current version with currentRelease + const versionComparison = this.compareVersions(currentReleaseVersion, currentVersion); + + // If versions match, we're up to date + if (versionComparison === 0) { + return { ok: true, available: false }; + } + + // If currentRelease is older than current version, check allowAnyVersion + if (versionComparison < 0) { + // If allowAnyVersion is true, allow downgrades + if (this.allowAnyVersion) { + // Continue to find the release entry for downgrade + } else { + return { ok: true, available: false }; + } + } + + // currentRelease is newer, find the release entry + const releaseEntry = json.releases.find((r: any) => r.version === currentReleaseVersion); + + if (!releaseEntry || !releaseEntry.updateTo) { + this.emitError(new Error(`Release entry for version '${currentReleaseVersion}' not found or missing 'updateTo' property.`)); + return { ok: false, available: false }; + } + + const updateTo = releaseEntry.updateTo; + + if (!updateTo.url) { + this.emitError(new Error(`Invalid release entry. 'updateTo.url' is missing for version ${currentReleaseVersion}.`)); + return { ok: false, available: false }; + } + + return { + ok: true, + available: true, + url: updateTo.url, + name: updateTo.name, + notes: updateTo.notes, + pub_date: updateTo.pub_date + }; + } + + private parseDynamicReleasFile (json: any): { ok: boolean; available: boolean; url?: string; name?: string; notes?: string; pub_date?: string } { + if (!json.url) { + this.emitError(new Error('Invalid releases format. Expected \'url\' string property.')); + return { ok: false, available: false }; + } + return { ok: true, available: true, url: json.url, name: json.name, notes: json.notes, pub_date: json.pub_date }; + } + + private async fetchSquirrelJson (url: string) { + const headers: Record = { + ...this.updateHeaders, + Accept: 'application/json' // Always set Accept header, overriding any user-provided Accept + }; + const response = await net.fetch(url, { + headers + }); + + if (response.status === 204) { + return { ok: true, available: false }; + } else if (response.status === 200) { + const updateJson = await response.json(); + + // Check if this is the static releases array format + if (Array.isArray(updateJson.releases)) { + // Get current package version + const packageInfo = msixUpdate.getPackageInfo(); + const currentVersion = packageInfo.version; + + if (!currentVersion) { + this.emitError(new Error('Cannot determine current package version.')); + return { ok: false, available: false }; + } + + return this.parseStaticReleasFile(updateJson, currentVersion); + } else { + // Dynamic format: server returns JSON with update info for current version + return this.parseDynamicReleasFile(updateJson); + } + } else { + this.emitError(new Error(`Unexpected response status: ${response.status}`)); + return { ok: false, available: false }; + } + } + + private async getUpdateInfo (url: string): Promise { + if (url && await this.isDirectMsixUrl(url)) { + return { ok: true, available: true, updateUrl: url, releaseDate: new Date() }; + } else { + const updateJson = await this.fetchSquirrelJson(url); + if (!updateJson.ok) { + return { ok: false }; + } else if (updateJson.ok && !updateJson.available) { + return { ok: true, available: false }; + } else { + // updateJson.ok && updateJson.available must be true here + // Parse the publication date if present (ISO 8601 format) + let releaseDate: Date | null = null; + if (updateJson.pub_date) { + releaseDate = new Date(updateJson.pub_date); + } + + const updateUrl = updateJson.url ?? ''; + const releaseNotes = updateJson.notes ?? ''; + const releaseName = updateJson.name ?? ''; + releaseDate = releaseDate ?? new Date(); + + if (!await this.isDirectMsixUrl(updateUrl, true)) { + return { ok: false }; + } else { + return { + ok: true, + available: true, + updateUrl, + releaseNotes, + releaseName, + releaseDate + }; + } + } + } + } + + getFeedURL () { + return this.updateURL ?? ''; + } + + setFeedURL (options: { url: string; headers?: Record; allowAnyVersion?: boolean } | string) { + let updateURL: string; + let headers: Record | undefined; + let allowAnyVersion: boolean | undefined; + if (typeof options === 'object') { + if (typeof options.url === 'string') { + updateURL = options.url; + headers = options.headers; + allowAnyVersion = options.allowAnyVersion; + } else { + throw new TypeError('Expected options object to contain a \'url\' string property in setFeedUrl call'); + } + } else if (typeof options === 'string') { + updateURL = options; + } else { + throw new TypeError('Expected an options object with a \'url\' property to be provided'); + } + this.updateURL = updateURL; + this.updateHeaders = headers ?? null; + this.allowAnyVersion = allowAnyVersion ?? false; + } + + getPackageInfo (): MSIXPackageInfo { + return msixUpdate.getPackageInfo() as MSIXPackageInfo; + } + + async checkForUpdates () { + const url = this.updateURL; + if (!url) { + return this.emitError(new Error('Update URL is not set')); + } + + // Check if running in MSIX package + const packageInfo = msixUpdate.getPackageInfo(); + if (!packageInfo.familyName) { + return this.emitError(new Error('MSIX updates are not supported')); + } + + // If appInstallerUri is set, Windows App Installer manages updates automatically + // Prevent updates here to avoid conflicts + if (packageInfo.appInstallerUri) { + return this.emitError(new Error('Auto-updates are managed by Windows App Installer. Updates are not allowed when installed via Application Manifest.')); + } + + this.emit('checking-for-update'); + try { + const msixUrlInfo = await this.getUpdateInfo(url); + if (!msixUrlInfo.ok) { + return this.emitError(new Error('Invalid update or MSIX URL. See previous errors.')); + } + + if (!msixUrlInfo.available) { + this.emit('update-not-available'); + } else { + this.updateAvailable = true; + this.emit('update-available'); + await msixUpdate.updateMsix(msixUrlInfo.updateUrl, { + deferRegistration: true, + developerMode: false, + forceShutdown: false, + forceTargetShutdown: false, + forceUpdateFromAnyVersion: this.allowAnyVersion + } as UpdateMsixOptions); + + this.emit('update-downloaded', {}, msixUrlInfo.releaseNotes, msixUrlInfo.releaseName, msixUrlInfo.releaseDate, msixUrlInfo.updateUrl, () => { + this.quitAndInstall(); + }); + } + } catch (error) { + this.emitError(error as Error); + } + } + + async quitAndInstall () { + if (!this.updateAvailable) { + this.emitError(new Error('No update available, can\'t quit and install')); + app.quit(); + return; + } + + try { + // Get package info to get family name + const packageInfo = msixUpdate.getPackageInfo(); + if (!packageInfo.familyName) { + return this.emitError(new Error('MSIX updates are not supported')); + } + + msixUpdate.registerRestartOnUpdate(''); + this.emit('before-quit-for-update'); + // force shutdown of the application and register the package to be installed on restart + await msixUpdate.registerPackage(packageInfo.familyName, { + forceShutdown: true + } as RegisterPackageOptions); + } catch (error) { + this.emitError(error as Error); + } + } + + // Private: Emit both error object and message, this is to keep compatibility + // with Old APIs. + emitError (error: Error) { + this.emit('error', error, error.message); + } +} + +export default new AutoUpdater(); diff --git a/lib/browser/api/auto-updater/auto-updater-win.ts b/lib/browser/api/auto-updater/auto-updater-win.ts index 255049cec3..851ad631ba 100644 --- a/lib/browser/api/auto-updater/auto-updater-win.ts +++ b/lib/browser/api/auto-updater/auto-updater-win.ts @@ -20,6 +20,11 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater { return this.updateURL ?? ''; } + getPackageInfo () { + // Squirrel-based Windows apps don't have MSIX package information + return undefined; + } + setFeedURL (options: { url: string } | string) { let updateURL: string; if (typeof options === 'object') { diff --git a/lib/browser/api/auto-updater/msix-update-win.ts b/lib/browser/api/auto-updater/msix-update-win.ts new file mode 100644 index 0000000000..c6500b254e --- /dev/null +++ b/lib/browser/api/auto-updater/msix-update-win.ts @@ -0,0 +1,4 @@ +const { updateMsix, registerPackage, registerRestartOnUpdate, getPackageInfo } = + process._linkedBinding('electron_browser_msix_updater'); + +export { updateMsix, registerPackage, registerRestartOnUpdate, getPackageInfo }; diff --git a/shell/browser/api/electron_api_msix_updater.cc b/shell/browser/api/electron_api_msix_updater.cc new file mode 100644 index 0000000000..605e7a45d0 --- /dev/null +++ b/shell/browser/api/electron_api_msix_updater.cc @@ -0,0 +1,527 @@ +// Copyright (c) 2025 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "shell/browser/api/electron_api_msix_updater.h" + +#include +#include +#include "base/environment.h" +#include "base/functional/bind.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "base/task/single_thread_task_runner.h" +#include "base/task/task_traits.h" +#include "base/task/thread_pool.h" +#include "content/public/browser/browser_task_traits.h" +#include "content/public/browser/browser_thread.h" +#include "shell/browser/browser.h" +#include "shell/browser/javascript_environment.h" +#include "shell/browser/native_window.h" +#include "shell/browser/window_list.h" +#include "shell/common/gin_helper/dictionary.h" +#include "shell/common/gin_helper/error_thrower.h" +#include "shell/common/gin_helper/promise.h" +#include "shell/common/node_includes.h" + +#if BUILDFLAG(IS_WIN) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "base/win/scoped_com_initializer.h" +#endif + +namespace electron { + +const bool debug_msix_updater = + base::Environment::Create()->HasVar("ELECTRON_DEBUG_MSIX_UPDATER"); + +} // namespace electron + +namespace { + +#if BUILDFLAG(IS_WIN) +// Helper function for debug logging +void DebugLog(std::string_view log_msg) { + if (electron::debug_msix_updater) + LOG(INFO) << std::string(log_msg); +} + +// Check if the process has a package identity +bool HasPackageIdentity() { + UINT32 length = 0; + LONG rc = GetCurrentPackageFullName(&length, NULL); + return rc != APPMODEL_ERROR_NO_PACKAGE; +} + +// POD struct to hold MSIX update options +struct UpdateMsixOptions { + bool defer_registration = false; + bool developer_mode = false; + bool force_shutdown = false; + bool force_target_shutdown = false; + bool force_update_from_any_version = false; +}; + +// POD struct to hold package registration options +struct RegisterPackageOptions { + bool force_shutdown = false; + bool force_target_shutdown = false; + bool force_update_from_any_version = false; +}; + +// Performs MSIX update on IO thread +void DoUpdateMsix(const std::string& package_uri, + UpdateMsixOptions opts, + scoped_refptr reply_runner, + gin_helper::Promise promise) { + DebugLog("DoUpdateMsix: Starting"); + + using winrt::Windows::Foundation::AsyncStatus; + using winrt::Windows::Foundation::Uri; + using winrt::Windows::Management::Deployment::AddPackageOptions; + using winrt::Windows::Management::Deployment::DeploymentResult; + using winrt::Windows::Management::Deployment::PackageManager; + + std::string error; + std::wstring packageUriString = + std::wstring(package_uri.begin(), package_uri.end()); + Uri uri{packageUriString}; + PackageManager packageManager; + AddPackageOptions packageOptions; + + // Use the pre-parsed options + packageOptions.DeferRegistrationWhenPackagesAreInUse(opts.defer_registration); + packageOptions.DeveloperMode(opts.developer_mode); + packageOptions.ForceAppShutdown(opts.force_shutdown); + packageOptions.ForceTargetAppShutdown(opts.force_target_shutdown); + packageOptions.ForceUpdateFromAnyVersion(opts.force_update_from_any_version); + + { + std::ostringstream oss; + oss << "Calling AddPackageByUriAsync... URI: " << package_uri; + DebugLog(oss.str()); + } + { + std::ostringstream oss; + oss << "Update options - deferRegistration: " << opts.defer_registration + << ", developerMode: " << opts.developer_mode + << ", forceShutdown: " << opts.force_shutdown + << ", forceTargetShutdown: " << opts.force_target_shutdown + << ", forceUpdateFromAnyVersion: " + << opts.force_update_from_any_version; + DebugLog(oss.str()); + } + + auto deploymentOperation = + packageManager.AddPackageByUriAsync(uri, packageOptions); + + if (!deploymentOperation) { + DebugLog("Deployment operation is null"); + error = + "Deployment is NULL. See " + "http://go.microsoft.com/fwlink/?LinkId=235160 for diagnosing."; + } else { + if (!opts.force_shutdown && !opts.force_target_shutdown) { + DebugLog("Waiting for deployment..."); + deploymentOperation.get(); + DebugLog("Deployment finished."); + + if (deploymentOperation.Status() == AsyncStatus::Error) { + auto deploymentResult{deploymentOperation.GetResults()}; + std::string errorText = winrt::to_string(deploymentResult.ErrorText()); + std::string errorCode = + std::to_string(static_cast(deploymentOperation.ErrorCode())); + error = errorText + " (" + errorCode + ")"; + { + std::ostringstream oss; + oss << "Deployment failed: " << error; + DebugLog(oss.str()); + } + } else if (deploymentOperation.Status() == AsyncStatus::Canceled) { + DebugLog("Deployment canceled"); + error = "Deployment canceled"; + } else if (deploymentOperation.Status() == AsyncStatus::Completed) { + DebugLog("MSIX Deployment completed."); + } else { + error = "Deployment status unknown"; + DebugLog("Deployment status unknown"); + } + } else { + // At this point, we can not await the deployment because we require a + // shutdown of the app to continue, so we do a fire and forget. When the + // deployment process tries ot shutdown the app, the process waits for us + // to finish here. But to finish we need to shutdow. That leads to a 30s + // dealock, till we forcefully get shutdown by the OS. + DebugLog( + "Deployment initiated. Force shutdown or target shutdown requested. " + "Good bye!"); + } + } + + // Post result back + reply_runner->PostTask( + FROM_HERE, base::BindOnce( + [](gin_helper::Promise promise, std::string error) { + if (error.empty()) { + promise.Resolve(); + } else { + promise.RejectWithErrorMessage(error); + } + }, + std::move(promise), error)); +} + +// Performs package registration on IO thread +void DoRegisterPackage(const std::string& family_name, + RegisterPackageOptions opts, + scoped_refptr reply_runner, + gin_helper::Promise promise) { + DebugLog("DoRegisterPackage: Starting"); + + using winrt::Windows::Foundation::AsyncStatus; + using winrt::Windows::Foundation::Collections::IIterable; + using winrt::Windows::Management::Deployment::DeploymentOptions; + using winrt::Windows::Management::Deployment::PackageManager; + + std::string error; + auto familyNameH = winrt::to_hstring(family_name); + PackageManager packageManager; + DeploymentOptions deploymentOptions = DeploymentOptions::None; + + // Use the pre-parsed options (no V8 access needed) + if (opts.force_shutdown) { + deploymentOptions |= DeploymentOptions::ForceApplicationShutdown; + } + if (opts.force_target_shutdown) { + deploymentOptions |= DeploymentOptions::ForceTargetApplicationShutdown; + } + if (opts.force_update_from_any_version) { + deploymentOptions |= DeploymentOptions::ForceUpdateFromAnyVersion; + } + + // Create empty collections for dependency and optional packages + IIterable emptyDependencies{nullptr}; + IIterable emptyOptional{nullptr}; + + { + std::ostringstream oss; + oss << "Calling RegisterPackageByFamilyNameAsync... FamilyName: " + << family_name; + DebugLog(oss.str()); + } + { + std::ostringstream oss; + oss << "Registration options - forceShutdown: " << opts.force_shutdown + << ", forceTargetShutdown: " << opts.force_target_shutdown + << ", forceUpdateFromAnyVersion: " + << opts.force_update_from_any_version; + DebugLog(oss.str()); + } + + auto deploymentOperation = packageManager.RegisterPackageByFamilyNameAsync( + familyNameH, emptyDependencies, deploymentOptions, nullptr, + emptyOptional); + + if (!deploymentOperation) { + error = + "Deployment is NULL. See " + "http://go.microsoft.com/fwlink/?LinkId=235160 for diagnosing."; + } else { + if (!opts.force_shutdown && !opts.force_target_shutdown) { + DebugLog("Waiting for registration..."); + deploymentOperation.get(); + DebugLog("Registration finished."); + + if (deploymentOperation.Status() == AsyncStatus::Error) { + auto deploymentResult{deploymentOperation.GetResults()}; + std::string errorText = winrt::to_string(deploymentResult.ErrorText()); + std::string errorCode = + std::to_string(static_cast(deploymentOperation.ErrorCode())); + error = errorText + " (" + errorCode + ")"; + { + std::ostringstream oss; + oss << "Registration failed: " << error; + DebugLog(oss.str()); + } + } else if (deploymentOperation.Status() == AsyncStatus::Canceled) { + DebugLog("Registration canceled"); + error = "Registration canceled"; + } else if (deploymentOperation.Status() == AsyncStatus::Completed) { + DebugLog("MSIX Registration completed."); + } else { + error = "Registration status unknown"; + DebugLog("Registration status unknown"); + } + } else { + // At this point, we can not await the registration because we require a + // shutdown of the app to continue, so we do a fire and forget. When the + // registration process tries ot shutdown the app, the process waits for + // us to finish here. But to finish we need to shutdown. That leads to a + // 30s dealock, till we forcefully get shutdown by the OS. + DebugLog( + "Registration initiated. Force shutdown or target shutdown " + "requested. Good bye!"); + } + } + + // Post result back to UI thread + reply_runner->PostTask( + FROM_HERE, base::BindOnce( + [](gin_helper::Promise promise, std::string error) { + if (error.empty()) { + promise.Resolve(); + } else { + promise.RejectWithErrorMessage(error); + } + }, + std::move(promise), error)); +} +#endif + +// Update MSIX package +v8::Local UpdateMsix(const std::string& package_uri, + gin_helper::Dictionary options) { + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + gin_helper::Promise promise(isolate); + v8::Local handle = promise.GetHandle(); + +#if BUILDFLAG(IS_WIN) + if (!HasPackageIdentity()) { + DebugLog("UpdateMsix: The process has no package identity"); + promise.RejectWithErrorMessage("The process has no package identity."); + return handle; + } + + // Parse options on UI thread (where V8 is available) + UpdateMsixOptions opts; + options.Get("deferRegistration", &opts.defer_registration); + options.Get("developerMode", &opts.developer_mode); + options.Get("forceShutdown", &opts.force_shutdown); + options.Get("forceTargetShutdown", &opts.force_target_shutdown); + options.Get("forceUpdateFromAnyVersion", &opts.force_update_from_any_version); + + { + std::ostringstream oss; + oss << "UpdateMsix called with URI: " << package_uri; + DebugLog(oss.str()); + } + + // Post to IO thread + content::GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, + base::BindOnce(&DoUpdateMsix, package_uri, opts, + base::SingleThreadTaskRunner::GetCurrentDefault(), + std::move(promise))); +#else + promise.RejectWithErrorMessage( + "MSIX updates are only supported on Windows with identity."); +#endif + + return handle; +} + +// Register MSIX package +v8::Local RegisterPackage(const std::string& family_name, + gin_helper::Dictionary options) { + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + gin_helper::Promise promise(isolate); + v8::Local handle = promise.GetHandle(); + +#if BUILDFLAG(IS_WIN) + if (!HasPackageIdentity()) { + DebugLog("RegisterPackage: The process has no package identity"); + promise.RejectWithErrorMessage("The process has no package identity."); + return handle; + } + + // Parse options on UI thread (where V8 is available) + RegisterPackageOptions opts; + options.Get("forceShutdown", &opts.force_shutdown); + options.Get("forceTargetShutdown", &opts.force_target_shutdown); + options.Get("forceUpdateFromAnyVersion", &opts.force_update_from_any_version); + + { + std::ostringstream oss; + oss << "RegisterPackage called with family name: " << family_name; + DebugLog(oss.str()); + } + + // Post to IO thread with POD options (no V8 objects) + content::GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, + base::BindOnce(&DoRegisterPackage, family_name, opts, + base::SingleThreadTaskRunner::GetCurrentDefault(), + std::move(promise))); +#else + promise.RejectWithErrorMessage( + "MSIX package registration is only supported on Windows."); +#endif + + return handle; +} + +// Register application restart +// Only registers for update restarts (not crashes, hangs, or reboots) +bool RegisterRestartOnUpdate(const std::string& command_line) { +#if BUILDFLAG(IS_WIN) + if (!HasPackageIdentity()) { + DebugLog("Cannot restart: no package identity"); + return false; + } + + const wchar_t* commandLine = nullptr; + // Flags: RESTART_NO_CRASH | RESTART_NO_HANG | RESTART_NO_REBOOT + // This means: only restart on updates (RESTART_NO_PATCH is NOT set) + const DWORD dwFlags = 1 | 2 | 8; // 11 + + if (!command_line.empty()) { + std::wstring commandLineW = + std::wstring(command_line.begin(), command_line.end()); + commandLine = commandLineW.c_str(); + } + + HRESULT hr = RegisterApplicationRestart(commandLine, dwFlags); + if (FAILED(hr)) { + { + std::ostringstream oss; + oss << "RegisterApplicationRestart failed with error code: " << hr; + DebugLog(oss.str()); + } + return false; + } + { + std::ostringstream oss; + oss << "RegisterApplicationRestart succeeded" + << (command_line.empty() ? "" : " with command line"); + DebugLog(oss.str()); + } + return true; +#else + return false; +#endif +} + +// Get package information +v8::Local GetPackageInfo() { + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + +#if BUILDFLAG(IS_WIN) + // Check if running in a package + if (!HasPackageIdentity()) { + DebugLog("GetPackageInfo: The process has no package identity"); + gin_helper::ErrorThrower thrower(isolate); + thrower.ThrowTypeError("The process has no package identity."); + return v8::Null(isolate); + } + + DebugLog("GetPackageInfo: Retrieving package information"); + + gin_helper::Dictionary result(isolate, v8::Object::New(isolate)); + + // Check API contract version (Windows 10 version 1703 or later) + if (winrt::Windows::Foundation::Metadata::ApiInformation:: + IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 7)) { + using winrt::Windows::ApplicationModel::Package; + using winrt::Windows::ApplicationModel::PackageSignatureKind; + Package package = Package::Current(); + + // Get package ID and family name + std::string packageId = winrt::to_string(package.Id().FullName()); + std::string familyName = winrt::to_string(package.Id().FamilyName()); + + result.Set("id", packageId); + result.Set("familyName", familyName); + result.Set("developmentMode", package.IsDevelopmentMode()); + + // Get package version + auto packageVersion = package.Id().Version(); + std::string version = std::to_string(packageVersion.Major) + "." + + std::to_string(packageVersion.Minor) + "." + + std::to_string(packageVersion.Build) + "." + + std::to_string(packageVersion.Revision); + result.Set("version", version); + + // Convert signature kind to string + std::string signatureKind; + switch (package.SignatureKind()) { + case PackageSignatureKind::Developer: + signatureKind = "developer"; + break; + case PackageSignatureKind::Enterprise: + signatureKind = "enterprise"; + break; + case PackageSignatureKind::None: + signatureKind = "none"; + break; + case PackageSignatureKind::Store: + signatureKind = "store"; + break; + case PackageSignatureKind::System: + signatureKind = "system"; + break; + default: + signatureKind = "none"; + break; + } + result.Set("signatureKind", signatureKind); + + // Get app installer info if available + auto appInstallerInfo = package.GetAppInstallerInfo(); + if (appInstallerInfo != nullptr) { + std::string uriStr = winrt::to_string(appInstallerInfo.Uri().ToString()); + result.Set("appInstallerUri", uriStr); + } + } else { + // Windows version doesn't meet minimum API requirements + result.Set("familyName", ""); + result.Set("id", ""); + result.Set("developmentMode", false); + result.Set("signatureKind", "none"); + result.Set("version", ""); + } + + return result.GetHandle(); +#else + // Non-Windows platforms + gin_helper::Dictionary result(isolate, v8::Object::New(isolate)); + result.Set("familyName", ""); + result.Set("id", ""); + result.Set("developmentMode", false); + result.Set("signatureKind", "none"); + result.Set("version", ""); + return result.GetHandle(); +#endif +} + +void Initialize(v8::Local exports, + v8::Local unused, + v8::Local context, + void* priv) { + v8::Isolate* const isolate = electron::JavascriptEnvironment::GetIsolate(); + gin_helper::Dictionary dict(isolate, exports); + + dict.SetMethod("updateMsix", base::BindRepeating(&UpdateMsix)); + dict.SetMethod("registerPackage", base::BindRepeating(&RegisterPackage)); + dict.SetMethod("registerRestartOnUpdate", + base::BindRepeating(&RegisterRestartOnUpdate)); + dict.SetMethod("getPackageInfo", + base::BindRepeating([]() { return GetPackageInfo(); })); +} + +} // namespace + +NODE_LINKED_BINDING_CONTEXT_AWARE(electron_browser_msix_updater, Initialize) diff --git a/shell/browser/api/electron_api_msix_updater.h b/shell/browser/api/electron_api_msix_updater.h new file mode 100644 index 0000000000..526392576b --- /dev/null +++ b/shell/browser/api/electron_api_msix_updater.h @@ -0,0 +1,14 @@ +// Copyright (c) 2013 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ELECTRON_SHELL_BROWSER_API_ELECTRON_API_MSIX_UPDATER_H_ +#define ELECTRON_SHELL_BROWSER_API_ELECTRON_API_MSIX_UPDATER_H_ + +namespace electron { + +extern const bool debug_msix_updater; + +} // namespace electron + +#endif // ELECTRON_SHELL_BROWSER_API_ELECTRON_API_MSIX_UPDATER_H_ diff --git a/shell/common/node_bindings.cc b/shell/common/node_bindings.cc index 3b887fa6ab..f5d6a22e31 100644 --- a/shell/common/node_bindings.cc +++ b/shell/common/node_bindings.cc @@ -68,6 +68,7 @@ V(electron_browser_in_app_purchase) \ V(electron_browser_menu) \ V(electron_browser_message_port) \ + V(electron_browser_msix_updater) \ V(electron_browser_native_theme) \ V(electron_browser_notification) \ V(electron_browser_power_monitor) \ diff --git a/spec/api-autoupdater-msix-spec.ts b/spec/api-autoupdater-msix-spec.ts new file mode 100644 index 0000000000..f66232d305 --- /dev/null +++ b/spec/api-autoupdater-msix-spec.ts @@ -0,0 +1,336 @@ +import { expect } from 'chai'; +import * as express from 'express'; + +import * as http from 'node:http'; +import { AddressInfo } from 'node:net'; + +import { + getElectronExecutable, + getMainJsFixturePath, + getMsixFixturePath, + getMsixPackageVersion, + installMsixCertificate, + installMsixPackage, + registerExecutableWithIdentity, + shouldRunMsixTests, + spawn, + uninstallMsixPackage, + unregisterExecutableWithIdentity +} from './lib/msix-helpers'; +import { ifdescribe } from './lib/spec-helpers'; + +const ELECTRON_MSIX_ALIAS = 'ElectronMSIX.exe'; +const MAIN_JS_PATH = getMainJsFixturePath(); +const MSIX_V1 = getMsixFixturePath('v1'); +const MSIX_V2 = getMsixFixturePath('v2'); + +// We can only test the MSIX updater on Windows +ifdescribe(shouldRunMsixTests)('autoUpdater MSIX behavior', function () { + this.timeout(120000); + + before(async function () { + await installMsixCertificate(); + + const electronExec = getElectronExecutable(); + await registerExecutableWithIdentity(electronExec); + }); + + after(async function () { + await unregisterExecutableWithIdentity(); + }); + + const launchApp = (executablePath: string, args: string[] = []) => { + return spawn(executablePath, args); + }; + + const logOnError = (what: any, fn: () => void) => { + try { + fn(); + } catch (err) { + console.error(what); + throw err; + } + }; + + it('should launch Electron via MSIX alias', async () => { + const launchResult = await launchApp(ELECTRON_MSIX_ALIAS, ['--version']); + logOnError(launchResult, () => { + expect(launchResult.code).to.equal(0); + }); + }); + + it('should print package identity information', async () => { + const launchResult = await launchApp(ELECTRON_MSIX_ALIAS, [MAIN_JS_PATH, '--printPackageId']); + logOnError(launchResult, () => { + expect(launchResult.code).to.equal(0); + expect(launchResult.out).to.include('Family Name: Electron.Dev.MSIX_rdjwn13tdj8dy'); + expect(launchResult.out).to.include('Package ID: Electron.Dev.MSIX_1.0.0.0_x64__rdjwn13tdj8dy'); + expect(launchResult.out).to.include('Version: 1.0.0.0'); + }); + }); + + describe('with update server', () => { + let port = 0; + let server: express.Application = null as any; + let httpServer: http.Server = null as any; + let requests: express.Request[] = []; + + beforeEach((done) => { + requests = []; + server = express(); + server.use((req, res, next) => { + requests.push(req); + next(); + }); + httpServer = server.listen(0, '127.0.0.1', () => { + port = (httpServer.address() as AddressInfo).port; + done(); + }); + }); + + afterEach(async () => { + if (httpServer) { + await new Promise(resolve => { + httpServer.close(() => { + httpServer = null as any; + server = null as any; + resolve(); + }); + }); + } + await uninstallMsixPackage('com.electron.myapp'); + }); + + it('should not update when no update is available', async () => { + server.get('/update-check', (req, res) => { + res.status(204).send(); + }); + + const launchResult = await launchApp(ELECTRON_MSIX_ALIAS, [MAIN_JS_PATH, '--checkUpdate', `http://localhost:${port}/update-check`]); + logOnError(launchResult, () => { + expect(launchResult.code).to.equal(0); + expect(requests.length).to.be.greaterThan(0); + expect(launchResult.out).to.include('Checking for update...'); + expect(launchResult.out).to.include('Update not available'); + }); + }); + + it('should hit the update endpoint with custom headers when checkForUpdates is called', async () => { + server.get('/update-check', (req, res) => { + expect(req.headers['x-appversion']).to.equal('1.0.0'); + expect(req.headers.authorization).to.equal('Bearer test-token'); + res.status(204).send(); + }); + + const launchResult = await launchApp(ELECTRON_MSIX_ALIAS, [ + MAIN_JS_PATH, + '--checkUpdate', + `http://localhost:${port}/update-check`, + '--useCustomHeaders' + ]); + + logOnError(launchResult, () => { + expect(launchResult.code).to.equal(0); + expect(requests.length).to.be.greaterThan(0); + expect(launchResult.out).to.include('Checking for update...'); + expect(launchResult.out).to.include('Update not available'); + }); + }); + + it('should update successfully with direct link to MSIX file', async () => { + await installMsixPackage(MSIX_V1); + const initialVersion = await getMsixPackageVersion('com.electron.myapp'); + expect(initialVersion).to.equal('1.0.0.0'); + + server.get('/update.msix', (req, res) => { + res.download(MSIX_V2); + }); + + const launchResult = await launchApp(ELECTRON_MSIX_ALIAS, [ + MAIN_JS_PATH, + '--checkUpdate', + `http://localhost:${port}/update.msix` + ]); + + logOnError(launchResult, () => { + expect(launchResult.code).to.equal(0); + expect(requests.length).to.be.greaterThan(0); + expect(launchResult.out).to.include('Checking for update...'); + expect(launchResult.out).to.include('Update available'); + expect(launchResult.out).to.include('Update downloaded'); + expect(launchResult.out).to.include('Release Name: N/A'); + expect(launchResult.out).to.include('Release Notes: N/A'); + expect(launchResult.out).to.include(`Update URL: http://localhost:${port}/update.msix`); + }); + + const updatedVersion = await getMsixPackageVersion('com.electron.myapp'); + expect(updatedVersion).to.equal('2.0.0.0'); + }); + + it('should update successfully with JSON response', async () => { + await installMsixPackage(MSIX_V1); + const initialVersion = await getMsixPackageVersion('com.electron.myapp'); + expect(initialVersion).to.equal('1.0.0.0'); + + const fixedPubDate = '2011-11-11T11:11:11.000Z'; + const expectedDateStr = new Date(fixedPubDate).toDateString(); + + server.get('/update-check', (req, res) => { + res.json({ + url: `http://localhost:${port}/update.msix`, + name: '2.0.0', + notes: 'Test release notes', + pub_date: fixedPubDate + }); + }); + + server.get('/update.msix', (req, res) => { + res.download(MSIX_V2); + }); + + const launchResult = await launchApp(ELECTRON_MSIX_ALIAS, [MAIN_JS_PATH, '--checkUpdate', `http://localhost:${port}/update-check`]); + logOnError(launchResult, () => { + expect(launchResult.code).to.equal(0); + expect(requests.length).to.be.greaterThan(0); + expect(launchResult.out).to.include('Checking for update...'); + expect(launchResult.out).to.include('Update available'); + expect(launchResult.out).to.include('Update downloaded'); + expect(launchResult.out).to.include('Release Name: 2.0.0'); + expect(launchResult.out).to.include('Release Notes: Test release notes'); + expect(launchResult.out).to.include(`Release Date: ${expectedDateStr}`); + expect(launchResult.out).to.include(`Update URL: http://localhost:${port}/update.msix`); + }); + + const updatedVersion = await getMsixPackageVersion('com.electron.myapp'); + expect(updatedVersion).to.equal('2.0.0.0'); + }); + + it('should update successfully with static JSON releases file', async () => { + await installMsixPackage(MSIX_V1); + const initialVersion = await getMsixPackageVersion('com.electron.myapp'); + expect(initialVersion).to.equal('1.0.0.0'); + + const fixedPubDate = '2011-11-11T11:11:11.000Z'; + const expectedDateStr = new Date(fixedPubDate).toDateString(); + + server.get('/update-check', (req, res) => { + res.json({ + currentRelease: '2.0.0', + releases: [ + { + version: '1.0.0', + updateTo: { + version: '1.0.0', + url: `http://localhost:${port}/update-v1.msix`, + name: '1.0.0', + notes: 'Initial release', + pub_date: '2010-10-10T10:10:10.000Z' + } + }, + { + version: '2.0.0', + updateTo: { + version: '2.0.0', + url: `http://localhost:${port}/update-v2.msix`, + name: '2.0.0', + notes: 'Test release notes for static format', + pub_date: fixedPubDate + } + } + ] + }); + }); + + server.get('/update-v2.msix', (req, res) => { + res.download(MSIX_V2); + }); + + const launchResult = await launchApp(ELECTRON_MSIX_ALIAS, [MAIN_JS_PATH, '--checkUpdate', `http://localhost:${port}/update-check`]); + + logOnError(launchResult, () => { + expect(launchResult.code).to.equal(0); + expect(requests.length).to.be.greaterThan(0); + expect(launchResult.out).to.include('Checking for update...'); + expect(launchResult.out).to.include('Update available'); + expect(launchResult.out).to.include('Update downloaded'); + expect(launchResult.out).to.include('Release Name: 2.0.0'); + expect(launchResult.out).to.include('Release Notes: Test release notes for static format'); + expect(launchResult.out).to.include(`Release Date: ${expectedDateStr}`); + expect(launchResult.out).to.include(`Update URL: http://localhost:${port}/update-v2.msix`); + }); + + const updatedVersion = await getMsixPackageVersion('com.electron.myapp'); + expect(updatedVersion).to.equal('2.0.0.0'); + }); + + it('should not update with update File JSON Format if currentRelease is older than installed version', async () => { + await installMsixPackage(MSIX_V2); + + server.get('/update-check', (req, res) => { + res.json({ + currentRelease: '1.0.0', + releases: [ + { + version: '1.0.0', + updateTo: { + version: '1.0.0', + url: `http://localhost:${port}/update-v1.msix`, + name: '1.0.0', + notes: 'Initial release', + pub_date: '2010-10-10T10:10:10.000Z' + } + } + ] + }); + }); + + const launchResult = await launchApp(ELECTRON_MSIX_ALIAS, [MAIN_JS_PATH, '--checkUpdate', `http://localhost:${port}/update-check`]); + + logOnError(launchResult, () => { + expect(launchResult.code).to.equal(0); + expect(requests.length).to.be.greaterThan(0); + expect(launchResult.out).to.include('Checking for update...'); + expect(launchResult.out).to.include('Update not available'); + }); + }); + + it('should downgrade to older version with JSON server format and allowAnyVersion is true', async () => { + await installMsixPackage(MSIX_V2); + const initialVersion = await getMsixPackageVersion('com.electron.myapp'); + expect(initialVersion).to.equal('2.0.0.0'); + + const fixedPubDate = '2010-10-10T10:10:10.000Z'; + const expectedDateStr = new Date(fixedPubDate).toDateString(); + + server.get('/update-check', (req, res) => { + res.json({ + url: `http://localhost:${port}/update-v1.msix`, + name: '1.0.0', + notes: 'Initial release', + pub_date: fixedPubDate + }); + }); + + server.get('/update-v1.msix', (req, res) => { + res.download(MSIX_V1); + }); + + const launchResult = await launchApp(ELECTRON_MSIX_ALIAS, [MAIN_JS_PATH, '--checkUpdate', `http://localhost:${port}/update-check`, '--allowAnyVersion']); + + logOnError(launchResult, () => { + expect(launchResult.code).to.equal(0); + expect(requests.length).to.be.greaterThan(0); + expect(launchResult.out).to.include('Checking for update...'); + expect(launchResult.out).to.include('Update available'); + expect(launchResult.out).to.include('Update downloaded'); + expect(launchResult.out).to.include('Release Name: 1.0.0'); + expect(launchResult.out).to.include('Release Notes: Initial release'); + expect(launchResult.out).to.include(`Release Date: ${expectedDateStr}`); + expect(launchResult.out).to.include(`Update URL: http://localhost:${port}/update-v1.msix`); + }); + + const downgradedVersion = await getMsixPackageVersion('com.electron.myapp'); + expect(downgradedVersion).to.equal('1.0.0.0'); + }); + }); +}); diff --git a/spec/fixtures/api/autoupdater/msix/ElectronDevAppxManifest.xml b/spec/fixtures/api/autoupdater/msix/ElectronDevAppxManifest.xml new file mode 100644 index 0000000000..742fd39fa9 --- /dev/null +++ b/spec/fixtures/api/autoupdater/msix/ElectronDevAppxManifest.xml @@ -0,0 +1,50 @@ + + + + + Electron Dev MSIX + Electron + assets\icon.png + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/fixtures/api/autoupdater/msix/HelloMSIX_V1.msix b/spec/fixtures/api/autoupdater/msix/HelloMSIX_V1.msix new file mode 100644 index 0000000000000000000000000000000000000000..71d5619596551603abf31b646e90a2cd36f4d913 GIT binary patch literal 68114 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

awIS%FnK2x5@{x7JZNCWV6sjkfuIZoS4cRK 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 D1tV89 literal 0 HcmV?d00001 diff --git a/spec/fixtures/api/autoupdater/msix/MSIXDevCert.cer b/spec/fixtures/api/autoupdater/msix/MSIXDevCert.cer new file mode 100644 index 0000000000000000000000000000000000000000..6a781185eeb8904de9ae211be6f96c58400b5234 GIT binary patch literal 774 zcmXqLVrDXEVtloLnTe5!NkDHY>xo0`SrgrV72LK>J)LL3%f_kI=F#?@mywa1mBB#5 zP|QGtjX9KsnTOXkCpEdGC_hiZH`p`6KtY_>$kf2x*wDZLh)tp-_>ByWOdtX#h6ct_ zc=R?gDj{3M$jZRn#K_M8bQc#>6C)$TL7nBFEhKraWm~Sep8eFPxpPOw^8O?7m)%zQ z2ZeqNef953?!1Q`=|vx|By3!Ky6V28ZsTWFwz`0{vlAW%5>xfy$Hr zb(ov~nC-Ryv(c^HMvju=y7h6w^Ib2`)r>y=xOr~R)f3S# zuN${c6yjXBBTmo#UD4Vy#e>T>tgdFc>z>qXd9bEMc*ZIJez9`#33L9$y*OTJ%~QRT zefuW8-q{jqzv@6h_A}RW5C76q0Pp~%F52nY#<8~(a_#?QSWh49Z zL*IG$P6<4iF@04+#`}eF_Zv=~&fjzJ*QcZF#CcVH`i*1{FYB@4d^S71yKXw4ig4O8 zozL~m6EG%8KGFO&=DZk=DUXRF4v&f_|3c_d*Zz_uSw*}nC)WZY1BW!fA+)< a^#eP1zHOJzxOk!RQ�ft_M~7g9HIM`Zopu literal 0 HcmV?d00001 diff --git a/spec/fixtures/api/autoupdater/msix/install_test_cert.ps1 b/spec/fixtures/api/autoupdater/msix/install_test_cert.ps1 new file mode 100644 index 0000000000..548b0727ae --- /dev/null +++ b/spec/fixtures/api/autoupdater/msix/install_test_cert.ps1 @@ -0,0 +1,22 @@ +Add-Type -AssemblyName System.Security.Cryptography.X509Certificates + +# Path to cert file one folder up relative to script location +$scriptDir = Split-Path -Parent $PSCommandPath +$certPath = Join-Path $scriptDir "MSIXDevCert.cer" | Resolve-Path + +# Load the certificate from file +$cert = [System.Security.Cryptography.X509Certificates.X509CertificateLoader]::LoadCertificateFromFile($certPath) + +$trustedStore = Get-ChildItem -Path "cert:\LocalMachine\TrustedPeople" | Where-Object { $_.Thumbprint -eq $cert.Thumbprint } +if (-not $trustedStore) { + # We gonna need admin privileges to install the cert + if (-Not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + Start-Process powershell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs + exit + } + # Install the public cert to LocalMachine\TrustedPeople (for MSIX trust) + Import-Certificate -FilePath $certPath -CertStoreLocation "cert:\LocalMachine\TrustedPeople" | Out-Null + Write-Host " 🏛️ Installed to: cert:\LocalMachine\TrustedPeople" +} else { + Write-Host " ✅ Certificate already trusted in: cert:\LocalMachine\TrustedPeople" +} \ No newline at end of file diff --git a/spec/fixtures/api/autoupdater/msix/main.js b/spec/fixtures/api/autoupdater/msix/main.js new file mode 100644 index 0000000000..2c92ae6db3 --- /dev/null +++ b/spec/fixtures/api/autoupdater/msix/main.js @@ -0,0 +1,96 @@ +const { app, autoUpdater } = require('electron'); + +// Parse command-line arguments +const args = process.argv.slice(2); +const command = args[0]; +const commandArg = args[1]; + +app.whenReady().then(() => { + try { + // Debug: log received arguments + if (process.env.DEBUG) { + console.log('Command:', command); + console.log('Command arg:', commandArg); + console.log('All args:', JSON.stringify(args)); + } + + if (command === '--printPackageId') { + const packageInfo = autoUpdater.getPackageInfo(); + if (packageInfo.familyName) { + console.log(`Family Name: ${packageInfo.familyName}`); + console.log(`Package ID: ${packageInfo.id || 'N/A'}`); + console.log(`Version: ${packageInfo.version || 'N/A'}`); + console.log(`Development Mode: ${packageInfo.developmentMode ? 'Yes' : 'No'}`); + console.log(`Signature Kind: ${packageInfo.signatureKind || 'N/A'}`); + if (packageInfo.appInstallerUri) { + console.log(`App Installer URI: ${packageInfo.appInstallerUri}`); + } + app.quit(); + } else { + console.error('No package identity found. Process is not running in an MSIX package context.'); + app.exit(1); + } + } else if (command === '--checkUpdate') { + if (!commandArg) { + console.error('Update URL is required for --checkUpdate'); + app.exit(1); + return; + } + + // Use hardcoded headers if --useCustomHeaders flag is provided + let headers; + let allowAnyVersion = false; + if (args[2] === '--useCustomHeaders') { + headers = { + 'X-AppVersion': '1.0.0', + Authorization: 'Bearer test-token' + }; + } else if (args[2] === '--allowAnyVersion') { + allowAnyVersion = true; + } + + // Set up event listeners + autoUpdater.on('checking-for-update', () => { + console.log('Checking for update...'); + }); + + autoUpdater.on('update-available', () => { + console.log('Update available'); + }); + + autoUpdater.on('update-not-available', () => { + console.log('Update not available'); + app.quit(); + }); + + autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName, releaseDate, updateUrl) => { + console.log('Update downloaded'); + console.log(`Release Name: ${releaseName || 'N/A'}`); + console.log(`Release Notes: ${releaseNotes || 'N/A'}`); + console.log(`Release Date: ${releaseDate || 'N/A'}`); + console.log(`Update URL: ${updateUrl || 'N/A'}`); + app.quit(); + }); + + autoUpdater.on('error', (error, message) => { + console.error(`Update error: ${message || error.message || 'Unknown error'}`); + app.exit(1); + }); + + // Set the feed URL with optional headers and allowAnyVersion, then check for updates + if (headers || allowAnyVersion) { + autoUpdater.setFeedURL({ url: commandArg, headers, allowAnyVersion }); + } else { + autoUpdater.setFeedURL(commandArg); + } + autoUpdater.checkForUpdates(); + } else { + console.error(`Unknown command: ${command || '(none)'}`); + app.exit(1); + } + } catch (error) { + console.error('Unhandled error:', error.message); + console.error(error.stack); + app.exit(1); + } +}); diff --git a/spec/lib/msix-helpers.ts b/spec/lib/msix-helpers.ts new file mode 100644 index 0000000000..027da53fc7 --- /dev/null +++ b/spec/lib/msix-helpers.ts @@ -0,0 +1,149 @@ +import { expect } from 'chai'; + +import * as cp from 'node:child_process'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +const fixturesPath = path.resolve(__dirname, '..', 'fixtures', 'api', 'autoupdater', 'msix'); +const manifestFixturePath = path.resolve(fixturesPath, 'ElectronDevAppxManifest.xml'); +const installCertScriptPath = path.resolve(fixturesPath, 'install_test_cert.ps1'); + +// Install the signing certificate for MSIX test packages to the Trusted People store +// This is required to install self-signed MSIX packages +export async function installMsixCertificate (): Promise { + const result = cp.spawnSync('powershell', [ + '-NoProfile', + '-ExecutionPolicy', 'Bypass', + '-File', installCertScriptPath + ]); + + if (result.status !== 0) { + throw new Error(`Failed to install MSIX certificate: ${result.stderr.toString() || result.stdout.toString()}`); + } +} + +// Check if we should run MSIX tests +export const shouldRunMsixTests = + process.platform === 'win32'; + +// Get the Electron executable path +export function getElectronExecutable (): string { + return process.execPath; +} + +// Get path to main.js fixture +export function getMainJsFixturePath (): string { + return path.resolve(fixturesPath, 'main.js'); +} + +// Register executable with identity +export async function registerExecutableWithIdentity (executablePath: string): Promise { + if (!fs.existsSync(manifestFixturePath)) { + throw new Error(`Manifest fixture not found: ${manifestFixturePath}`); + } + + const executableDir = path.dirname(executablePath); + const manifestPath = path.join(executableDir, 'AppxManifest.xml'); + const escapedManifestPath = manifestPath.replace(/'/g, "''").replace(/\\/g, '\\\\'); + const psCommand = ` + $ErrorActionPreference = 'Stop'; + try { + Add-AppxPackage -Register '${escapedManifestPath}' -ForceUpdateFromAnyVersion + } catch { + Write-Error $_.Exception.Message + exit 1 + } + `; + + fs.copyFileSync(manifestFixturePath, manifestPath); + + const result = cp.spawnSync('powershell', ['-NoProfile', '-Command', psCommand]); + if (result.status !== 0) { + const errorMsg = result.stderr.toString() || result.stdout.toString(); + try { + fs.unlinkSync(manifestPath); + } catch { + // Ignore cleanup errors + } + throw new Error(`Failed to register executable with identity: ${errorMsg}`); + } +} + +// Unregister the Electron Dev MSIX package +// This removes the sparse package registration created by registerExecutableWithIdentity +export async function unregisterExecutableWithIdentity (): Promise { + const result = cp.spawnSync('powershell', ['-NoProfile', '-Command', ' Get-AppxPackage Electron.Dev.MSIX | Remove-AppxPackage']); + // Don't throw if package doesn't exist + if (result.status !== 0) { + throw new Error(`Failed to unregister executable with identity: ${result.stderr.toString() || result.stdout.toString()}`); + } + + const electronExec = getElectronExecutable(); + const executableDir = path.dirname(electronExec); + const manifestPath = path.join(executableDir, 'AppxManifest.xml'); + try { + if (fs.existsSync(manifestPath)) { + fs.unlinkSync(manifestPath); + } + } catch { + // Ignore cleanup errors + } +} + +// Get path to MSIX fixture package +export function getMsixFixturePath (version: 'v1' | 'v2'): string { + const filename = `HelloMSIX_${version}.msix`; + return path.resolve(fixturesPath, filename); +} + +// Install MSIX package +export async function installMsixPackage (msixPath: string): Promise { + // Use Add-AppxPackage PowerShell cmdlet + const result = cp.spawnSync('powershell', [ + '-Command', + `Add-AppxPackage -Path "${msixPath}" -ForceApplicationShutdown` + ]); + if (result.status !== 0) { + throw new Error(`Failed to install MSIX package: ${result.stderr.toString()}`); + } +} + +// Uninstall MSIX package by name +export async function uninstallMsixPackage (name: string): Promise { + const result = cp.spawnSync('powershell', ['-NoProfile', '-Command', `Get-AppxPackage ${name} | Remove-AppxPackage`]); + // Don't throw if package doesn't exist + if (result.status !== 0) { + throw new Error(`Failed to uninstall MSIX package: ${result.stderr.toString() || result.stdout.toString()}`); + } +} + +// Get version of installed MSIX package by name +export async function getMsixPackageVersion (name: string): Promise { + const psCommand = `(Get-AppxPackage -Name '${name}').Version`; + const result = cp.spawnSync('powershell', ['-NoProfile', '-Command', psCommand]); + if (result.status !== 0) { + return null; + } + const version = result.stdout.toString().trim(); + return version || null; +} + +export function spawn (cmd: string, args: string[], opts: any = {}): Promise<{ code: number, out: string }> { + let out = ''; + const child = cp.spawn(cmd, args, opts); + child.stdout.on('data', (chunk: Buffer) => { + out += chunk.toString(); + }); + child.stderr.on('data', (chunk: Buffer) => { + out += chunk.toString(); + }); + return new Promise<{ code: number, out: string }>((resolve) => { + child.on('exit', (code, signal) => { + expect(signal).to.equal(null); + resolve({ + code: code!, + out + }); + }); + }); +}