diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..edfca02 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,172 @@ +## 3.0.2 + +### Bug Fixes + +* **electron-updater:** addRandomQueryToAvoidCaching does not respect query parameters + +## 3.0.1 + +### Bug Fixes + +* **electron-updater:** Replace all occurrences of version in old blockmap file url ([#3120](https://github.com/electron-userland/electron-builder/issues/3120)) ([ca18b74](https://github.com/electron-userland/electron-builder/commit/ca18b74)) +* **electron-updater:** vertical upgrading for channels ([b1f2272](https://github.com/electron-userland/electron-builder/commit/b1f2272)), closes [#3111](https://github.com/electron-userland/electron-builder/issues/3111) + +## 2.23.3 + +* **electron-updater:** fix case of blockmap file extension, detect s3 urls on setFeedURL ([369e9c0](https://github.com/electron-userland/electron-builder/commit/369e9c0)) +* **electron-updater:** ignore unknown powershell errors ([a0026a7](https://github.com/electron-userland/electron-builder/commit/a0026a7)), closes [#2589](https://github.com/electron-userland/electron-builder/issues/2589) +* **electron-updater:** web installer differential download perMachine ([82708a5](https://github.com/electron-userland/electron-builder/commit/82708a5)), closes [#2949](https://github.com/electron-userland/electron-builder/issues/2949) + + +## 2.23.2 + +### Bug Fixes + +* **electron-updater:** addRandomQueryToAvoidCaching breaks s3 provider for updater with private acl ([577b61b](https://github.com/electron-userland/electron-builder/commit/577b61b)), closes [#3021](https://github.com/electron-userland/electron-builder/issues/3021) + + +## 2.23.1 + +### Features + +* **electron-updater:** [Delta updates for NSIS](https://github.com/electron-userland/electron-builder/releases/tag/v20.17.0) target ([7dd59fb](https://github.com/electron-userland/electron-builder/commit/7dd59fb)), closes [#2217](https://github.com/electron-userland/electron-builder/issues/2217) [#3042](https://github.com/electron-userland/electron-builder/issues/3042) [#3000](https://github.com/electron-userland/electron-builder/issues/3000) [#2977](https://github.com/electron-userland/electron-builder/issues/2977) +* **electron-updater:** support prereleases in a Github private repository ([59aac66](https://github.com/electron-userland/electron-builder/commit/59aac66)), closes [#3005](https://github.com/electron-userland/electron-builder/issues/3005) [#3037](https://github.com/electron-userland/electron-builder/issues/3037) +* **electron-updater:** cache downloaded update and reuse if valid later ([ba4809a](https://github.com/electron-userland/electron-builder/commit/ba4809a)) +* **electron-updater:** electron-updater will update even I don't call quitAndInstall after app quit ([29f1c10](https://github.com/electron-userland/electron-builder/commit/29f1c10)), closes [#2493](https://github.com/electron-userland/electron-builder/issues/2493) + +### Bug Fixes +* **electron-updater:** do not rename AppImage file if no version in the name ([48a0811](https://github.com/electron-userland/electron-builder/commit/48a0811)), closes [#2964](https://github.com/electron-userland/electron-builder/issues/2964) +* **electron-updater:** downloading builds(updates) more than once even if downloaded already ([6500b35](https://github.com/electron-userland/electron-builder/commit/6500b35)), closes [#3007](https://github.com/electron-userland/electron-builder/issues/3007) [#3003](https://github.com/electron-userland/electron-builder/issues/3003) +* **electron-updater:** set _packageFile to null on clear ([7fe72da](https://github.com/electron-userland/electron-builder/commit/7fe72da)) +* **electron-updater:** Prevent download notification queueing ([68804e4](https://github.com/electron-userland/electron-builder/commit/68804e4)), closes [#2850](https://github.com/electron-userland/electron-builder/issues/2850) +* **electron-updater:** add random query param to avoid caching ([254d7c5](https://github.com/electron-userland/electron-builder/commit/254d7c5)), closes [#2741](https://github.com/electron-userland/electron-builder/issues/2741) +* **electron-updater:** Close opened parenthese in update checking log ([8f19ea9](https://github.com/electron-userland/electron-builder/commit/8f19ea9)), closes [#2763](https://github.com/electron-userland/electron-builder/issues/2763) +* **electron-updater:** set actual http status code instead of 404 [#2741](https://github.com/electron-userland/electron-builder/issues/2741) ([8453a77](https://github.com/electron-userland/electron-builder/commit/8453a77)) +* **electron-updater:** return correct release notes & name ([#2743](https://github.com/electron-userland/electron-builder/issues/2743)) ([37014be](https://github.com/electron-userland/electron-builder/commit/37014be)), closes [#2742](https://github.com/electron-userland/electron-builder/issues/2742) +* **electron-updater:** Allow --package-file arg to escape spaces in filenames ([#2739](https://github.com/electron-userland/electron-builder/issues/2739)) ([24a585b](https://github.com/electron-userland/electron-builder/commit/24a585b)) +* **electron-updater:** Race condition during Application Quit ([#2746](https://github.com/electron-userland/electron-builder/issues/2746)) ([1df5d98](https://github.com/electron-userland/electron-builder/commit/1df5d98)), closes [#2745](https://github.com/electron-userland/electron-builder/issues/2745) +* **electron-updater:** use updateInfo.path as AppImage installer name ([#2722](https://github.com/electron-userland/electron-builder/issues/2722)) ([8233eae](https://github.com/electron-userland/electron-builder/commit/8233eae)), closes [#2672](https://github.com/electron-userland/electron-builder/issues/2672) +* **electron-updater:** add response code to error message about Accept-Ranges ([62cf1df](https://github.com/electron-userland/electron-builder/commit/62cf1df)) +* **electron-updater:** Nsis app from fall 2017 (electron-updater 2.10.0) won't update to new version ([ba2957e](https://github.com/electron-userland/electron-builder/commit/ba2957e)), closes [#2583](https://github.com/electron-userland/electron-builder/issues/2583) +* **electron-updater:** recurrent 404 Errors on GitHub Enterprise ([afc1a9e](https://github.com/electron-userland/electron-builder/commit/afc1a9e)) + + +## 2.19.0 + +## 2.19.0 + +### Features + +* useMultipleRangeRequest option to disable using of multiple ranges request + +## 2.18.2 + +### Bug Fixes + +* **electron-updater:** AutoUpdate takes 60 seconds to fail validating signature on Windows 7 due to PowerShell version [#2421](https://github.com/electron-userland/electron-builder/issues/2421) ([da96e73](https://github.com/electron-userland/electron-builder/commit/da96e73)) + +## 2.18.1 + +### Bug Fixes + +* **electron-updater:** add error codes ([2822049](https://github.com/electron-userland/electron-builder/commit/2822049)), closes [#2415](https://github.com/electron-userland/electron-builder/issues/2415) + +## 2.18.0 + +### Bug Fixes + +* **electron-updater:** redirect event in electron.net ([e2ac601](https://github.com/electron-userland/electron-builder/commit/e2ac601)), closes [#2374](https://github.com/electron-userland/electron-builder/issues/2374) +* use solid compression for web installer package ([6ea5668](https://github.com/electron-userland/electron-builder/commit/6ea5668)) + +## 2.17.2 + +### Bug Fixes + +* Fix AppImage auto-update [#2240](https://github.com/electron-userland/electron-builder/issues/2240). + +## 2.17.0 + +### Bug Fixes + +* PrivateGitHubProvider requires at least Electron 1.6.11. Better to use latest stable. + +### Features + +* PrivateGitHubProvider [fixes](https://github.com/electron-userland/electron-builder/issues/2342). + +## 2.16.2 + +### Features + +* [Use the only HTTP request to download all changed blocks](https://github.com/electron-userland/electron-builder/releases/tag/v19.45.1). + +## 2.16.0 + +### Features + +* [Update metadata format allows several files](https://github.com/electron-userland/electron-builder/releases/tag/v19.44.0). + +### Bug Fixes + +* Include application name in update notification ([#2262](https://github.com/electron-userland/electron-builder/issues/2262)) ([1809c94](https://github.com/electron-userland/electron-builder/commit/1809c94)) + +## 2.13.0 + +### Features + +* full changelog for all versions from current to latest ([67fe9ff](https://github.com/electron-userland/electron-builder/commit/67fe9ff)) + +## 2.12.1 + +### Performance Improvements + +* a little bit more compact blockmap data ([c92bc38](https://github.com/electron-userland/electron-builder/commit/c92bc38)) + +## 2.12.0 + +### Features + +* [Linux auto-update](https://github.com/electron-userland/electron-builder/releases/tag/v19.37.0) + +## 2.11.0 + +### Features + +* Differential updater: use [content defined chunking](https://github.com/electron-userland/electron-builder/releases/tag/v19.36.0) + +## 2.10.2 + +### Bug Fixes + +* Differential updater: fix "To download" in percentage value calculation (cosmetic fix) + +## 2.10.1 + +### Bug Fixes + +* PrivateGitHubProvider: clear error if no channel file in the latest github release + +# 2.10.0 (2017-09-22) + +### Features + +* [DigitalOcean Spaces support](https://github.com/electron-userland/electron-builder/releases/tag/v19.30.0). + +# 2.9.3 (2017-09-10) + +### Features + +* [Delta updates for Windows Web Installer](https://github.com/electron-userland/electron-builder/releases/tag/v19.28.4). + + +## 2.8.9 (2017-09-01) + +### Bug Fixes + +* Electron-updater does not support enterprise Github. [#1903](https://github.com/electron-userland/electron-builder/issues/1903). + +## 2.8.8 (2017-09-01) + +### Bug Fixes + +* handle aborted event. [#1975](https://github.com/electron-userland/electron-builder/issues/1975). \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..aacf031 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# electron-updater + +This module allows you to automatically update your application. You only need to install this module and write two lines of code! +To publish your updates you just need simple file hosting, it does not require a dedicated server. + +See [Auto Update](https://electron.build/auto-update) for more information. + +Supported OS: + - macOS ([Squirrel.Mac](https://github.com/Squirrel/Squirrel.Mac)). + - Windows (NSIS). + - Linux (AppImage). + +## Credits + +Thanks to [Evolve Labs](https://www.evolvehq.com) for donating the npm package name. diff --git a/out/AppImageUpdater.d.ts b/out/AppImageUpdater.d.ts new file mode 100644 index 0000000..279f359 --- /dev/null +++ b/out/AppImageUpdater.d.ts @@ -0,0 +1,12 @@ +import { AllPublishOptions } from "builder-util-runtime"; +import "source-map-support/register"; +import { DownloadUpdateOptions } from "./AppUpdater"; +import { BaseUpdater } from "./BaseUpdater"; +import { UpdateCheckResult } from "./main"; +export declare class AppImageUpdater extends BaseUpdater { + constructor(options?: AllPublishOptions | null, app?: any); + checkForUpdatesAndNotify(): Promise; + /*** @private */ + protected doDownloadUpdate(downloadUpdateOptions: DownloadUpdateOptions): Promise>; + protected doInstall(installerPath: string, isSilent: boolean, isRunAfter: boolean): boolean; +} diff --git a/out/AppImageUpdater.js b/out/AppImageUpdater.js new file mode 100644 index 0000000..287e63f --- /dev/null +++ b/out/AppImageUpdater.js @@ -0,0 +1,194 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.AppImageUpdater = void 0; + +function _builderUtilRuntime() { + const data = require("builder-util-runtime"); + + _builderUtilRuntime = function () { + return data; + }; + + return data; +} + +function _child_process() { + const data = require("child_process"); + + _child_process = function () { + return data; + }; + + return data; +} + +function _electronIsDev() { + const data = _interopRequireDefault(require("electron-is-dev")); + + _electronIsDev = function () { + return data; + }; + + return data; +} + +function _fsExtraP() { + const data = require("fs-extra-p"); + + _fsExtraP = function () { + return data; + }; + + return data; +} + +var path = _interopRequireWildcard(require("path")); + +require("source-map-support/register"); + +function _BaseUpdater() { + const data = require("./BaseUpdater"); + + _BaseUpdater = function () { + return data; + }; + + return data; +} + +function _FileWithEmbeddedBlockMapDifferentialDownloader() { + const data = require("./differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader"); + + _FileWithEmbeddedBlockMapDifferentialDownloader = function () { + return data; + }; + + return data; +} + +function _Provider() { + const data = require("./providers/Provider"); + + _Provider = function () { + return data; + }; + + return data; +} + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +class AppImageUpdater extends _BaseUpdater().BaseUpdater { + constructor(options, app) { + super(options, app); + } + + checkForUpdatesAndNotify() { + if (_electronIsDev().default) { + return Promise.resolve(null); + } + + if (process.env.APPIMAGE == null) { + if (process.env.SNAP == null) { + this._logger.warn("APPIMAGE env is not defined, current application is not an AppImage"); + } else { + this._logger.info("SNAP env is defined, updater is disabled"); + } + + return Promise.resolve(null); + } + + return super.checkForUpdatesAndNotify(); + } + /*** @private */ + + + async doDownloadUpdate(downloadUpdateOptions) { + const provider = await this.provider; + const fileInfo = (0, _Provider().findFile)(provider.resolveFiles(downloadUpdateOptions.updateInfo), "AppImage"); + return await this.executeDownload({ + fileExtension: "AppImage", + fileInfo, + downloadUpdateOptions, + task: async (updateFile, downloadOptions) => { + const oldFile = process.env.APPIMAGE; + + if (oldFile == null) { + throw (0, _builderUtilRuntime().newError)("APPIMAGE env is not defined", "ERR_UPDATER_OLD_FILE_NOT_FOUND"); + } + + let isDownloadFull = false; + + try { + await new (_FileWithEmbeddedBlockMapDifferentialDownloader().FileWithEmbeddedBlockMapDifferentialDownloader)(fileInfo.info, this.httpExecutor, { + newUrl: fileInfo.url.href, + oldFile, + logger: this._logger, + newFile: updateFile, + useMultipleRangeRequest: provider.useMultipleRangeRequest, + requestHeaders: downloadUpdateOptions.requestHeaders + }).download(); + } catch (e) { + this._logger.error(`Cannot download differentially, fallback to full download: ${e.stack || e}`); // during test (developer machine mac) we must throw error + + + isDownloadFull = process.platform === "linux"; + } + + if (isDownloadFull) { + await this.httpExecutor.download(fileInfo.url.href, updateFile, downloadOptions); + } + + await (0, _fsExtraP().chmod)(updateFile, 0o755); + } + }); + } + + doInstall(installerPath, isSilent, isRunAfter) { + const appImageFile = process.env.APPIMAGE; + + if (appImageFile == null) { + throw (0, _builderUtilRuntime().newError)("APPIMAGE env is not defined", "ERR_UPDATER_OLD_FILE_NOT_FOUND"); + } // https://stackoverflow.com/a/1712051/1910191 + + + (0, _fsExtraP().unlinkSync)(appImageFile); + let destination; + const existingBaseName = path.basename(appImageFile); // https://github.com/electron-userland/electron-builder/issues/2964 + // if no version in existing file name, it means that user wants to preserve current custom name + + if (path.basename(installerPath) === existingBaseName || !/\d+\.\d+\.\d+/.test(existingBaseName)) { + // no version in the file name, overwrite existing + destination = appImageFile; + } else { + destination = path.join(path.dirname(appImageFile), path.basename(installerPath)); + } + + (0, _child_process().execFileSync)("mv", ["-f", installerPath, destination]); + const env = Object.assign({}, process.env, { + APPIMAGE_SILENT_INSTALL: "true" + }); + + if (isRunAfter) { + (0, _child_process().spawn)(destination, [], { + detached: true, + stdio: "ignore", + env + }).unref(); + } else { + env.APPIMAGE_EXIT_AFTER_INSTALL = "true"; + (0, _child_process().execFileSync)(destination, [], { + env + }); + } + + return true; + } + +} exports.AppImageUpdater = AppImageUpdater; +//# sourceMappingURL=AppImageUpdater.js.map \ No newline at end of file diff --git a/out/AppImageUpdater.js.map b/out/AppImageUpdater.js.map new file mode 100644 index 0000000..e97d086 --- /dev/null +++ b/out/AppImageUpdater.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/AppImageUpdater.ts"],"names":[],"mappings":"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","sourcesContent":["import { AllPublishOptions, newError } from \"builder-util-runtime\"\r\nimport { execFileSync, spawn } from \"child_process\"\r\nimport isDev from \"electron-is-dev\"\r\nimport { chmod, unlinkSync } from \"fs-extra-p\"\r\nimport * as path from \"path\"\r\nimport \"source-map-support/register\"\r\nimport { DownloadUpdateOptions } from \"./AppUpdater\"\r\nimport { BaseUpdater } from \"./BaseUpdater\"\r\nimport { FileWithEmbeddedBlockMapDifferentialDownloader } from \"./differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader\"\r\nimport { UpdateCheckResult } from \"./main\"\r\nimport { findFile } from \"./providers/Provider\"\r\n\r\nexport class AppImageUpdater extends BaseUpdater {\r\n constructor(options?: AllPublishOptions | null, app?: any) {\r\n super(options, app)\r\n }\r\n\r\n checkForUpdatesAndNotify(): Promise {\r\n if (isDev) {\r\n return Promise.resolve(null)\r\n }\r\n\r\n if (process.env.APPIMAGE == null) {\r\n if (process.env.SNAP == null) {\r\n this._logger.warn(\"APPIMAGE env is not defined, current application is not an AppImage\")\r\n }\r\n else {\r\n this._logger.info(\"SNAP env is defined, updater is disabled\")\r\n }\r\n return Promise.resolve(null)\r\n }\r\n\r\n return super.checkForUpdatesAndNotify()\r\n }\r\n\r\n /*** @private */\r\n protected async doDownloadUpdate(downloadUpdateOptions: DownloadUpdateOptions): Promise> {\r\n const provider = await this.provider\r\n const fileInfo = findFile(provider.resolveFiles(downloadUpdateOptions.updateInfo), \"AppImage\")!!\r\n return await this.executeDownload({\r\n fileExtension: \"AppImage\",\r\n fileInfo,\r\n downloadUpdateOptions,\r\n task: async (updateFile, downloadOptions) => {\r\n const oldFile = process.env.APPIMAGE!!\r\n if (oldFile == null) {\r\n throw newError(\"APPIMAGE env is not defined\", \"ERR_UPDATER_OLD_FILE_NOT_FOUND\")\r\n }\r\n\r\n let isDownloadFull = false\r\n try {\r\n await new FileWithEmbeddedBlockMapDifferentialDownloader(fileInfo.info, this.httpExecutor, {\r\n newUrl: fileInfo.url.href,\r\n oldFile,\r\n logger: this._logger,\r\n newFile: updateFile,\r\n useMultipleRangeRequest: provider.useMultipleRangeRequest,\r\n requestHeaders: downloadUpdateOptions.requestHeaders,\r\n })\r\n .download()\r\n }\r\n catch (e) {\r\n this._logger.error(`Cannot download differentially, fallback to full download: ${e.stack || e}`)\r\n // during test (developer machine mac) we must throw error\r\n isDownloadFull = process.platform === \"linux\"\r\n }\r\n\r\n if (isDownloadFull) {\r\n await this.httpExecutor.download(fileInfo.url.href, updateFile, downloadOptions)\r\n }\r\n\r\n await chmod(updateFile, 0o755)\r\n },\r\n })\r\n }\r\n\r\n protected doInstall(installerPath: string, isSilent: boolean, isRunAfter: boolean): boolean {\r\n const appImageFile = process.env.APPIMAGE!!\r\n if (appImageFile == null) {\r\n throw newError(\"APPIMAGE env is not defined\", \"ERR_UPDATER_OLD_FILE_NOT_FOUND\")\r\n }\r\n\r\n // https://stackoverflow.com/a/1712051/1910191\r\n unlinkSync(appImageFile)\r\n\r\n let destination: string\r\n const existingBaseName = path.basename(appImageFile)\r\n // https://github.com/electron-userland/electron-builder/issues/2964\r\n // if no version in existing file name, it means that user wants to preserve current custom name\r\n if (path.basename(installerPath) === existingBaseName || !/\\d+\\.\\d+\\.\\d+/.test(existingBaseName)) {\r\n // no version in the file name, overwrite existing\r\n destination = appImageFile\r\n }\r\n else {\r\n destination = path.join(path.dirname(appImageFile), path.basename(installerPath))\r\n }\r\n\r\n execFileSync(\"mv\", [\"-f\", installerPath, destination])\r\n\r\n const env: any = {\r\n ...process.env,\r\n APPIMAGE_SILENT_INSTALL: \"true\",\r\n }\r\n\r\n if (isRunAfter) {\r\n spawn(destination, [], {\r\n detached: true,\r\n stdio: \"ignore\",\r\n env,\r\n })\r\n .unref()\r\n }\r\n else {\r\n env.APPIMAGE_EXIT_AFTER_INSTALL = \"true\"\r\n execFileSync(destination, [], {env})\r\n }\r\n return true\r\n }\r\n}"],"sourceRoot":""} diff --git a/out/AppUpdater.d.ts b/out/AppUpdater.d.ts new file mode 100644 index 0000000..9d9e4c5 --- /dev/null +++ b/out/AppUpdater.d.ts @@ -0,0 +1,141 @@ +/// +import { AllPublishOptions, CancellationToken, PublishConfiguration, UpdateInfo, DownloadOptions } from "builder-util-runtime"; +import { EventEmitter } from "events"; +import { OutgoingHttpHeaders } from "http"; +import { Lazy } from "lazy-val"; +import { SemVer } from "semver"; +import "source-map-support/register"; +import { DownloadedUpdateHelper } from "./DownloadedUpdateHelper"; +import { Logger, Provider, ResolvedUpdateFileInfo, UpdateCheckResult, UpdaterSignal } from "./main"; +export declare abstract class AppUpdater extends EventEmitter { + /** + * Whether to automatically download an update when it is found. + */ + autoDownload: boolean; + /** + * Whether to automatically install a downloaded update on app quit (if `quitAndInstall` was not called before). + * + * Applicable only on Windows and Linux. + */ + autoInstallOnAppQuit: boolean; + /** + * *GitHub provider only.* Whether to allow update to pre-release versions. Defaults to `true` if application version contains prerelease components (e.g. `0.12.1-alpha.1`, here `alpha` is a prerelease component), otherwise `false`. + * + * If `true`, downgrade will be allowed (`allowDowngrade` will be set to `true`). + */ + allowPrerelease: boolean; + /** + * *GitHub provider only.* Get all release notes (from current version to latest), not just the latest. + * @default false + */ + fullChangelog: boolean; + /** + * Whether to allow version downgrade (when a user from the beta channel wants to go back to the stable channel). + * + * Taken in account only if channel differs (pre-release version component in terms of semantic versioning). + * + * @default false + */ + allowDowngrade: boolean; + /** + * The current application version. + */ + readonly currentVersion: SemVer; + private _channel; + protected readonly downloadedUpdateHelper: DownloadedUpdateHelper; + /** + * Get the update channel. Not applicable for GitHub. Doesn't return `channel` from the update configuration, only if was previously set. + */ + /** + * Set the update channel. Not applicable for GitHub. Overrides `channel` in the update configuration. + * + * `allowDowngrade` will be automatically set to `true`. If this behavior is not suitable for you, simple set `allowDowngrade` explicitly after. + */ + channel: string | null; + /** + * The request headers. + */ + requestHeaders: OutgoingHttpHeaders | null; + protected _logger: Logger; + /** + * The logger. You can pass [electron-log](https://github.com/megahertz/electron-log), [winston](https://github.com/winstonjs/winston) or another logger with the following interface: `{ info(), warn(), error() }`. + * Set it to `null` if you would like to disable a logging feature. + */ + logger: Logger | null; + /** + * For type safety you can use signals, e.g. `autoUpdater.signals.updateDownloaded(() => {})` instead of `autoUpdater.on('update-available', () => {})` + */ + readonly signals: UpdaterSignal; + private _appUpdateConfigPath; + /** + * test only + * @private + */ + updateConfigPath: string | null; + private clientPromise; + protected readonly provider: Promise>; + protected readonly stagingUserIdPromise: Lazy; + private readonly untilAppReady; + private checkForUpdatesPromise; + protected readonly app: Electron.App; + protected updateInfo: UpdateInfo | null; + protected constructor(options: AllPublishOptions | null | undefined, app?: Electron.App); + getFeedURL(): string | null | undefined; + /** + * Configure update provider. If value is `string`, [GenericServerOptions](/configuration/publish.md#genericserveroptions) will be set with value as `url`. + * @param options If you want to override configuration in the `app-update.yml`. + */ + setFeedURL(options: PublishConfiguration | AllPublishOptions | string): void; + /** + * Asks the server whether there is an update. + */ + checkForUpdates(): Promise; + checkForUpdatesAndNotify(): Promise; + private isStagingMatch; + private _checkForUpdates; + private computeFinalHeaders; + private isUpdateAvailable; + protected getUpdateInfo(): Promise; + private doCheckForUpdates; + protected onUpdateAvailable(updateInfo: UpdateInfo): void; + /** + * Start downloading update manually. You can use this method if `autoDownload` option is set to `false`. + * @returns {Promise} Path to downloaded file. + */ + downloadUpdate(cancellationToken?: CancellationToken): Promise; + protected dispatchError(e: Error): void; + protected abstract doDownloadUpdate(downloadUpdateOptions: DownloadUpdateOptions): Promise>; + /** + * Restarts the app and installs the update after it has been downloaded. + * It should only be called after `update-downloaded` has been emitted. + * + * **Note:** `autoUpdater.quitAndInstall()` will close all application windows first and only emit `before-quit` event on `app` after that. + * This is different from the normal quit event sequence. + * + * @param isSilent *windows-only* Runs the installer in silent mode. Defaults to `false`. + * @param isForceRunAfter Run the app after finish even on silent install. Not applicable for macOS. Ignored if `isSilent` is set to `false`. + */ + abstract quitAndInstall(isSilent?: boolean, isForceRunAfter?: boolean): void; + private loadUpdateConfig; + private computeRequestHeaders; + private getOrCreateStagingUserId; + protected executeDownload(taskOptions: DownloadExecutorTask): Promise>; +} +export interface DownloadUpdateOptions { + readonly updateInfo: UpdateInfo; + readonly requestHeaders: OutgoingHttpHeaders; + readonly cancellationToken: CancellationToken; +} +/** @private */ +export declare class NoOpLogger implements Logger { + info(message?: any): void; + warn(message?: any): void; + error(message?: any): void; +} +export interface DownloadExecutorTask { + readonly fileExtension: string; + readonly fileInfo: ResolvedUpdateFileInfo; + readonly downloadUpdateOptions: DownloadUpdateOptions; + readonly task: (destinationFile: string, downloadOptions: DownloadOptions, packageFile: string | null, removeTempDirIfAny: () => Promise) => Promise; + readonly done?: (destinationFile: string) => Promise; +} diff --git a/out/AppUpdater.js b/out/AppUpdater.js new file mode 100644 index 0000000..2543eaf --- /dev/null +++ b/out/AppUpdater.js @@ -0,0 +1,741 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.NoOpLogger = exports.AppUpdater = void 0; + +function _builderUtilRuntime() { + const data = require("builder-util-runtime"); + + _builderUtilRuntime = function () { + return data; + }; + + return data; +} + +function _crypto() { + const data = require("crypto"); + + _crypto = function () { + return data; + }; + + return data; +} + +function _electron() { + const data = require("electron"); + + _electron = function () { + return data; + }; + + return data; +} + +function _electronIsDev() { + const data = _interopRequireDefault(require("electron-is-dev")); + + _electronIsDev = function () { + return data; + }; + + return data; +} + +function _events() { + const data = require("events"); + + _events = function () { + return data; + }; + + return data; +} + +function _fsExtraP() { + const data = require("fs-extra-p"); + + _fsExtraP = function () { + return data; + }; + + return data; +} + +function _jsYaml() { + const data = require("js-yaml"); + + _jsYaml = function () { + return data; + }; + + return data; +} + +function _lazyVal() { + const data = require("lazy-val"); + + _lazyVal = function () { + return data; + }; + + return data; +} + +var path = _interopRequireWildcard(require("path")); + +function _semver() { + const data = require("semver"); + + _semver = function () { + return data; + }; + + return data; +} + +require("source-map-support/register"); + +function _DownloadedUpdateHelper() { + const data = require("./DownloadedUpdateHelper"); + + _DownloadedUpdateHelper = function () { + return data; + }; + + return data; +} + +function _electronHttpExecutor() { + const data = require("./electronHttpExecutor"); + + _electronHttpExecutor = function () { + return data; + }; + + return data; +} + +function _GenericProvider() { + const data = require("./providers/GenericProvider"); + + _GenericProvider = function () { + return data; + }; + + return data; +} + +function _main() { + const data = require("./main"); + + _main = function () { + return data; + }; + + return data; +} + +function _providerFactory() { + const data = require("./providerFactory"); + + _providerFactory = function () { + return data; + }; + + return data; +} + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +class AppUpdater extends _events().EventEmitter { + constructor(options, app) { + super(); + /** + * Whether to automatically download an update when it is found. + */ + + this.autoDownload = true; + /** + * Whether to automatically install a downloaded update on app quit (if `quitAndInstall` was not called before). + * + * Applicable only on Windows and Linux. + */ + + this.autoInstallOnAppQuit = true; + /** + * *GitHub provider only.* Whether to allow update to pre-release versions. Defaults to `true` if application version contains prerelease components (e.g. `0.12.1-alpha.1`, here `alpha` is a prerelease component), otherwise `false`. + * + * If `true`, downgrade will be allowed (`allowDowngrade` will be set to `true`). + */ + + this.allowPrerelease = false; + /** + * *GitHub provider only.* Get all release notes (from current version to latest), not just the latest. + * @default false + */ + + this.fullChangelog = false; + /** + * Whether to allow version downgrade (when a user from the beta channel wants to go back to the stable channel). + * + * Taken in account only if channel differs (pre-release version component in terms of semantic versioning). + * + * @default false + */ + + this.allowDowngrade = false; + this._channel = null; + /** + * The request headers. + */ + + this.requestHeaders = null; + this._logger = console; // noinspection JSUnusedGlobalSymbols + + /** + * For type safety you can use signals, e.g. `autoUpdater.signals.updateDownloaded(() => {})` instead of `autoUpdater.on('update-available', () => {})` + */ + + this.signals = new (_main().UpdaterSignal)(this); + this._appUpdateConfigPath = null; + this.clientPromise = null; + this.stagingUserIdPromise = new (_lazyVal().Lazy)(() => this.getOrCreateStagingUserId()); // public, allow to read old config for anyone + + /** @internal */ + + this.configOnDisk = new (_lazyVal().Lazy)(() => this.loadUpdateConfig()); + this.checkForUpdatesPromise = null; + this.updateInfo = null; + this.on("error", error => { + this._logger.error(`Error: ${error.stack || error.message}`); + }); + + if (app != null || global.__test_app != null) { + this.app = app || global.__test_app; + this.untilAppReady = Promise.resolve(); + this.httpExecutor = null; + } else { + this.app = require("electron").app; + this.httpExecutor = new (_electronHttpExecutor().ElectronHttpExecutor)((authInfo, callback) => this.emit("login", authInfo, callback)); + this.untilAppReady = new Promise(resolve => { + if (this.app.isReady()) { + resolve(); + } else { + this.app.on("ready", resolve); + } + }); + } + + this.downloadedUpdateHelper = new (_DownloadedUpdateHelper().DownloadedUpdateHelper)(path.join(this.app.getPath("userData"), "__update__")); + const currentVersionString = this.app.getVersion(); + const currentVersion = (0, _semver().parse)(currentVersionString); + + if (currentVersion == null) { + throw (0, _builderUtilRuntime().newError)(`App version is not a valid semver version: "${currentVersionString}"`, "ERR_UPDATER_INVALID_VERSION"); + } + + this.currentVersion = currentVersion; + this.allowPrerelease = hasPrereleaseComponents(currentVersion); + + if (options != null) { + this.setFeedURL(options); + } + } + /** + * Get the update channel. Not applicable for GitHub. Doesn't return `channel` from the update configuration, only if was previously set. + */ + + + get channel() { + return this._channel; + } + /** + * Set the update channel. Not applicable for GitHub. Overrides `channel` in the update configuration. + * + * `allowDowngrade` will be automatically set to `true`. If this behavior is not suitable for you, simple set `allowDowngrade` explicitly after. + */ + + + set channel(value) { + if (this._channel != null) { + // noinspection SuspiciousTypeOfGuard + if (typeof value !== "string") { + throw (0, _builderUtilRuntime().newError)(`Channel must be a string, but got: ${value}`, "ERR_UPDATER_INVALID_CHANNEL"); + } else if (value.length === 0) { + throw (0, _builderUtilRuntime().newError)(`Channel must be not an empty string`, "ERR_UPDATER_INVALID_CHANNEL"); + } + } + + this._channel = value; + this.allowDowngrade = true; + } + /** + * The logger. You can pass [electron-log](https://github.com/megahertz/electron-log), [winston](https://github.com/winstonjs/winston) or another logger with the following interface: `{ info(), warn(), error() }`. + * Set it to `null` if you would like to disable a logging feature. + */ + + + get logger() { + return this._logger; + } + + set logger(value) { + this._logger = value == null ? new NoOpLogger() : value; + } // noinspection JSUnusedGlobalSymbols + + /** + * test only + * @private + */ + + + set updateConfigPath(value) { + this.clientPromise = null; + this._appUpdateConfigPath = value; + this.configOnDisk = new (_lazyVal().Lazy)(() => this.loadUpdateConfig()); + } + + get provider() { + return this.clientPromise; + } //noinspection JSMethodCanBeStatic,JSUnusedGlobalSymbols + + + getFeedURL() { + return "Deprecated. Do not use it."; + } + /** + * Configure update provider. If value is `string`, [GenericServerOptions](/configuration/publish.md#genericserveroptions) will be set with value as `url`. + * @param options If you want to override configuration in the `app-update.yml`. + */ + + + setFeedURL(options) { + // https://github.com/electron-userland/electron-builder/issues/1105 + let provider; + + if (typeof options === "string") { + provider = new (_GenericProvider().GenericProvider)({ + provider: "generic", + url: options + }, this, (0, _providerFactory().isUrlProbablySupportMultiRangeRequests)(options)); + } else { + provider = (0, _providerFactory().createClient)(options, this); + } + + this.clientPromise = Promise.resolve(provider); + } + /** + * Asks the server whether there is an update. + */ + + + checkForUpdates() { + let checkForUpdatesPromise = this.checkForUpdatesPromise; + + if (checkForUpdatesPromise != null) { + return checkForUpdatesPromise; + } + + checkForUpdatesPromise = this._checkForUpdates(); + this.checkForUpdatesPromise = checkForUpdatesPromise; + + const nullizePromise = () => this.checkForUpdatesPromise = null; + + checkForUpdatesPromise.then(nullizePromise).catch(nullizePromise); + return checkForUpdatesPromise; + } + + checkForUpdatesAndNotify() { + if (_electronIsDev().default) { + return Promise.resolve(null); + } + + const checkForUpdatesPromise = this.checkForUpdates(); + checkForUpdatesPromise.then(it => { + const downloadPromise = it.downloadPromise; + + if (downloadPromise == null) { + const debug = this._logger.debug; + + if (debug != null) { + debug("checkForUpdatesAndNotify called, downloadPromise is null"); + } + + return; + } + + downloadPromise.then(() => { + new (_electron().Notification)({ + title: "A new update is ready to install", + body: `${this.app.getName()} version ${it.updateInfo.version} is downloaded and will be automatically installed on exit` + }).show(); + }); + }); + return checkForUpdatesPromise; + } + + async isStagingMatch(updateInfo) { + const rawStagingPercentage = updateInfo.stagingPercentage; + let stagingPercentage = rawStagingPercentage; + + if (stagingPercentage == null) { + return true; + } + + stagingPercentage = parseInt(stagingPercentage, 10); + + if (isNaN(stagingPercentage)) { + this._logger.warn(`Staging percentage is NaN: ${rawStagingPercentage}`); + + return true; + } // convert from user 0-100 to internal 0-1 + + + stagingPercentage = stagingPercentage / 100; + const stagingUserId = await this.stagingUserIdPromise.value; + + const val = _builderUtilRuntime().UUID.parse(stagingUserId).readUInt32BE(12); + + const percentage = val / 0xFFFFFFFF; + + this._logger.info(`Staging percentage: ${stagingPercentage}, percentage: ${percentage}, user id: ${stagingUserId}`); + + return percentage < stagingPercentage; + } + + async _checkForUpdates() { + try { + await this.untilAppReady; + + this._logger.info("Checking for update"); + + this.emit("checking-for-update"); + return await this.doCheckForUpdates(); + } catch (e) { + this.emit("error", e, `Cannot check for updates: ${(e.stack || e).toString()}`); + throw e; + } + } + + computeFinalHeaders(headers) { + if (this.requestHeaders != null) { + Object.assign(headers, this.requestHeaders); + } + + return headers; + } + + async isUpdateAvailable(updateInfo) { + const latestVersion = (0, _semver().parse)(updateInfo.version); + + if (latestVersion == null) { + throw (0, _builderUtilRuntime().newError)(`This file could not be downloaded, or the latest version (from update server) does not have a valid semver version: "${latestVersion}"`, "ERR_UPDATER_INVALID_VERSION"); + } + + const currentVersion = this.currentVersion; + + if ((0, _semver().eq)(latestVersion, currentVersion)) { + return false; + } + + const isStagingMatch = await this.isStagingMatch(updateInfo); + + if (!isStagingMatch) { + return false; + } // https://github.com/electron-userland/electron-builder/pull/3111#issuecomment-405033227 + // https://github.com/electron-userland/electron-builder/pull/3111#issuecomment-405030797 + + + const isLatestVersionNewer = (0, _semver().gt)(latestVersion, currentVersion); + + if (!this.allowDowngrade) { + return isLatestVersionNewer; + } + + const currentVersionPrereleaseComponent = (0, _semver().prerelease)(currentVersion); + const latestVersionPrereleaseComponent = (0, _semver().prerelease)(latestVersion); + + if (currentVersionPrereleaseComponent === latestVersionPrereleaseComponent) { + // allowDowngrade taken in account only if channel differs + return isLatestVersionNewer; + } + + return true; + } + + async getUpdateInfo() { + await this.untilAppReady; + + if (this.clientPromise == null) { + this.clientPromise = this.configOnDisk.value.then(it => (0, _providerFactory().createClient)(it, this)); + } + + const client = await this.clientPromise; + const stagingUserId = await this.stagingUserIdPromise.value; + client.setRequestHeaders(this.computeFinalHeaders({ + "x-user-staging-id": stagingUserId + })); + return await client.getLatestVersion(); + } + + async doCheckForUpdates() { + const updateInfo = await this.getUpdateInfo(); + + if (!(await this.isUpdateAvailable(updateInfo))) { + this._logger.info(`Update for version ${this.currentVersion} is not available (latest version: ${updateInfo.version}, downgrade is ${this.allowDowngrade ? "allowed" : "disallowed"}).`); + + this.emit("update-not-available", updateInfo); + return { + versionInfo: updateInfo, + updateInfo + }; + } + + this.updateInfo = updateInfo; + this.onUpdateAvailable(updateInfo); + const cancellationToken = new (_builderUtilRuntime().CancellationToken)(); //noinspection ES6MissingAwait + + return { + versionInfo: updateInfo, + updateInfo, + cancellationToken, + downloadPromise: this.autoDownload ? this.downloadUpdate(cancellationToken) : null + }; + } + + onUpdateAvailable(updateInfo) { + this._logger.info(`Found version ${updateInfo.version} (url: ${(0, _builderUtilRuntime().asArray)(updateInfo.files).map(it => it.url).join(", ")})`); + + this.emit("update-available", updateInfo); + } + /** + * Start downloading update manually. You can use this method if `autoDownload` option is set to `false`. + * @returns {Promise} Path to downloaded file. + */ + + + async downloadUpdate(cancellationToken = new (_builderUtilRuntime().CancellationToken)()) { + const updateInfo = this.updateInfo; + + if (updateInfo == null) { + const error = new Error("Please check update first"); + this.dispatchError(error); + throw error; + } + + this._logger.info(`Downloading update from ${(0, _builderUtilRuntime().asArray)(updateInfo.files).map(it => it.url).join(", ")}`); + + try { + return await this.doDownloadUpdate({ + updateInfo, + requestHeaders: await this.computeRequestHeaders(), + cancellationToken + }); + } catch (e) { + this.dispatchError(e); + throw e; + } + } + + dispatchError(e) { + this.emit("error", e, (e.stack || e).toString()); + } + + async loadUpdateConfig() { + if (this._appUpdateConfigPath == null) { + this._appUpdateConfigPath = _electronIsDev().default ? path.join(this.app.getAppPath(), "dev-app-update.yml") : path.join(process.resourcesPath, "app-update.yml"); + } + + return (0, _jsYaml().safeLoad)((await (0, _fsExtraP().readFile)(this._appUpdateConfigPath, "utf-8"))); + } + + async computeRequestHeaders() { + const fileExtraDownloadHeaders = (await this.provider).fileExtraDownloadHeaders; + + if (fileExtraDownloadHeaders != null) { + const requestHeaders = this.requestHeaders; + return requestHeaders == null ? fileExtraDownloadHeaders : Object.assign({}, fileExtraDownloadHeaders, requestHeaders); + } + + return this.computeFinalHeaders({ + accept: "*/*" + }); + } + + async getOrCreateStagingUserId() { + const file = path.join(this.app.getPath("userData"), ".updaterId"); + + try { + const id = await (0, _fsExtraP().readFile)(file, "utf-8"); + + if (_builderUtilRuntime().UUID.check(id)) { + return id; + } else { + this._logger.warn(`Staging user id file exists, but content was invalid: ${id}`); + } + } catch (e) { + if (e.code !== "ENOENT") { + this._logger.warn(`Couldn't read staging user ID, creating a blank one: ${e}`); + } + } + + const id = _builderUtilRuntime().UUID.v5((0, _crypto().randomBytes)(4096), _builderUtilRuntime().UUID.OID); + + this._logger.info(`Generated new staging user ID: ${id}`); + + try { + await (0, _fsExtraP().outputFile)(file, id); + } catch (e) { + this._logger.warn(`Couldn't write out staging user ID: ${e}`); + } + + return id; + } + /** @internal */ + + + get isAddNoCacheQuery() { + const headers = this.requestHeaders; // https://github.com/electron-userland/electron-builder/issues/3021 + + if (headers == null) { + return true; + } + + for (const headerName of Object.keys(headers)) { + const s = headerName.toLowerCase(); + + if (s === "authorization" || s === "private-token") { + return false; + } + } + + return true; + } + + async executeDownload(taskOptions) { + const fileInfo = taskOptions.fileInfo; + const downloadOptions = { + skipDirCreation: true, + headers: taskOptions.downloadUpdateOptions.requestHeaders, + cancellationToken: taskOptions.downloadUpdateOptions.cancellationToken, + sha2: fileInfo.info.sha2, + sha512: fileInfo.info.sha512 + }; + + if (this.listenerCount(_main().DOWNLOAD_PROGRESS) > 0) { + downloadOptions.onProgress = it => this.emit(_main().DOWNLOAD_PROGRESS, it); + } + + const updateInfo = taskOptions.downloadUpdateOptions.updateInfo; + const version = updateInfo.version; + const packageInfo = fileInfo.packageInfo; + + function getCacheUpdateFileName() { + // bloody NodeJS URL doesn't decode automatically + const urlPath = decodeURIComponent(taskOptions.fileInfo.url.pathname); + + if (urlPath.endsWith(`.${taskOptions.fileExtension}`)) { + return path.posix.basename(urlPath); + } else { + // url like /latest, generate name + return `update.${taskOptions.fileExtension}`; + } + } + + const cacheDir = this.downloadedUpdateHelper.cacheDir; + await (0, _fsExtraP().ensureDir)(cacheDir); + const updateFileName = getCacheUpdateFileName(); + let updateFile = path.join(cacheDir, updateFileName); + const packageFile = packageInfo == null ? null : path.join(cacheDir, `package-${version}${path.extname(packageInfo.path) || ".7z"}`); + + const done = async isSaveCache => { + this.downloadedUpdateHelper.setDownloadedFile(updateFile, packageFile, updateInfo, fileInfo); + + if (isSaveCache) { + await this.downloadedUpdateHelper.cacheUpdateInfo(updateFileName); + } + + this.emit(_main().UPDATE_DOWNLOADED, updateInfo); + await taskOptions.done(updateFile); + return packageFile == null ? [updateFile] : [updateFile, packageFile]; + }; + + const log = this._logger; + const cachedUpdateFile = await this.downloadedUpdateHelper.validateDownloadedPath(updateFile, updateInfo, fileInfo, log); + + if (cachedUpdateFile != null) { + updateFile = cachedUpdateFile; + return await done(false); + } + + const removeFileIfAny = async () => { + await this.downloadedUpdateHelper.clear().catch(() => {// ignore + }); + return await (0, _fsExtraP().unlink)(updateFile).catch(() => {// ignore + }); + }; // https://github.com/electron-userland/electron-builder/pull/2474#issuecomment-366481912 + + + let nameCounter = 0; + let tempUpdateFile = path.join(cacheDir, `temp-${updateFileName}`); + + for (let i = 0; i < 3; i++) { + try { + await (0, _fsExtraP().unlink)(tempUpdateFile); + } catch (e) { + if (e.code === "ENOENT") { + break; + } + + log.warn(`Error on remove temp update file: ${e}`); + tempUpdateFile = path.join(cacheDir, `temp-${nameCounter++}-${updateFileName}`); + } + } + + try { + await taskOptions.task(tempUpdateFile, downloadOptions, packageFile, removeFileIfAny); + await (0, _fsExtraP().rename)(tempUpdateFile, updateFile); + } catch (e) { + await removeFileIfAny(); + + if (e instanceof _builderUtilRuntime().CancellationError) { + log.info("Cancelled"); + this.emit("update-cancelled", updateInfo); + } + + throw e; + } + + log.info(`New version ${version} has been downloaded to ${updateFile}`); + return await done(true); + } + +} + +exports.AppUpdater = AppUpdater; + +function hasPrereleaseComponents(version) { + const versionPrereleaseComponent = (0, _semver().prerelease)(version); + return versionPrereleaseComponent != null && versionPrereleaseComponent.length > 0; +} +/** @private */ + + +class NoOpLogger { + info(message) {// ignore + } + + warn(message) {// ignore + } + + error(message) {// ignore + } + +} exports.NoOpLogger = NoOpLogger; +//# sourceMappingURL=AppUpdater.js.map \ No newline at end of file diff --git a/out/AppUpdater.js.map b/out/AppUpdater.js.map new file mode 100644 index 0000000..0a8cb37 --- /dev/null +++ b/out/AppUpdater.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/AppUpdater.ts"],"names":[],"mappings":"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","sourcesContent":["import { AllPublishOptions, asArray, CancellationToken, newError, PublishConfiguration, UpdateInfo, UUID, DownloadOptions, CancellationError } from \"builder-util-runtime\"\r\nimport { randomBytes } from \"crypto\"\r\nimport { Notification } from \"electron\"\r\nimport isDev from \"electron-is-dev\"\r\nimport { EventEmitter } from \"events\"\r\nimport { ensureDir, outputFile, readFile, rename, unlink } from \"fs-extra-p\"\r\nimport { OutgoingHttpHeaders } from \"http\"\r\nimport { safeLoad } from \"js-yaml\"\r\nimport { Lazy } from \"lazy-val\"\r\nimport * as path from \"path\"\r\nimport { eq as isVersionsEqual, gt as isVersionGreaterThan, parse as parseVersion, prerelease as getVersionPreleaseComponents, SemVer } from \"semver\"\r\nimport \"source-map-support/register\"\r\nimport { DownloadedUpdateHelper } from \"./DownloadedUpdateHelper\"\r\nimport { ElectronHttpExecutor } from \"./electronHttpExecutor\"\r\nimport { GenericProvider } from \"./providers/GenericProvider\"\r\nimport { DOWNLOAD_PROGRESS, Logger, Provider, ResolvedUpdateFileInfo, UPDATE_DOWNLOADED, UpdateCheckResult, UpdaterSignal } from \"./main\"\r\nimport { createClient, isUrlProbablySupportMultiRangeRequests } from \"./providerFactory\"\r\n\r\nexport abstract class AppUpdater extends EventEmitter {\r\n /**\r\n * Whether to automatically download an update when it is found.\r\n */\r\n autoDownload: boolean = true\r\n\r\n /**\r\n * Whether to automatically install a downloaded update on app quit (if `quitAndInstall` was not called before).\r\n *\r\n * Applicable only on Windows and Linux.\r\n */\r\n autoInstallOnAppQuit: boolean = true\r\n\r\n /**\r\n * *GitHub provider only.* Whether to allow update to pre-release versions. Defaults to `true` if application version contains prerelease components (e.g. `0.12.1-alpha.1`, here `alpha` is a prerelease component), otherwise `false`.\r\n *\r\n * If `true`, downgrade will be allowed (`allowDowngrade` will be set to `true`).\r\n */\r\n allowPrerelease: boolean = false\r\n\r\n /**\r\n * *GitHub provider only.* Get all release notes (from current version to latest), not just the latest.\r\n * @default false\r\n */\r\n fullChangelog: boolean = false\r\n\r\n /**\r\n * Whether to allow version downgrade (when a user from the beta channel wants to go back to the stable channel).\r\n *\r\n * Taken in account only if channel differs (pre-release version component in terms of semantic versioning).\r\n *\r\n * @default false\r\n */\r\n allowDowngrade: boolean = false\r\n\r\n /**\r\n * The current application version.\r\n */\r\n readonly currentVersion: SemVer\r\n\r\n private _channel: string | null = null\r\n\r\n protected readonly downloadedUpdateHelper: DownloadedUpdateHelper\r\n\r\n /**\r\n * Get the update channel. Not applicable for GitHub. Doesn't return `channel` from the update configuration, only if was previously set.\r\n */\r\n get channel(): string | null {\r\n return this._channel\r\n }\r\n\r\n /**\r\n * Set the update channel. Not applicable for GitHub. Overrides `channel` in the update configuration.\r\n *\r\n * `allowDowngrade` will be automatically set to `true`. If this behavior is not suitable for you, simple set `allowDowngrade` explicitly after.\r\n */\r\n set channel(value: string | null) {\r\n if (this._channel != null) {\r\n // noinspection SuspiciousTypeOfGuard\r\n if (typeof value !== \"string\") {\r\n throw newError(`Channel must be a string, but got: ${value}`, \"ERR_UPDATER_INVALID_CHANNEL\")\r\n }\r\n else if (value.length === 0) {\r\n throw newError(`Channel must be not an empty string`, \"ERR_UPDATER_INVALID_CHANNEL\")\r\n }\r\n }\r\n\r\n this._channel = value\r\n this.allowDowngrade = true\r\n }\r\n\r\n /**\r\n * The request headers.\r\n */\r\n requestHeaders: OutgoingHttpHeaders | null = null\r\n\r\n protected _logger: Logger = console\r\n\r\n /**\r\n * The logger. You can pass [electron-log](https://github.com/megahertz/electron-log), [winston](https://github.com/winstonjs/winston) or another logger with the following interface: `{ info(), warn(), error() }`.\r\n * Set it to `null` if you would like to disable a logging feature.\r\n */\r\n get logger(): Logger | null {\r\n return this._logger\r\n }\r\n\r\n set logger(value: Logger | null) {\r\n this._logger = value == null ? new NoOpLogger() : value\r\n }\r\n\r\n // noinspection JSUnusedGlobalSymbols\r\n /**\r\n * For type safety you can use signals, e.g. `autoUpdater.signals.updateDownloaded(() => {})` instead of `autoUpdater.on('update-available', () => {})`\r\n */\r\n readonly signals = new UpdaterSignal(this)\r\n\r\n private _appUpdateConfigPath: string | null = null\r\n\r\n // noinspection JSUnusedGlobalSymbols\r\n /**\r\n * test only\r\n * @private\r\n */\r\n set updateConfigPath(value: string | null) {\r\n this.clientPromise = null\r\n this._appUpdateConfigPath = value\r\n this.configOnDisk = new Lazy(() => this.loadUpdateConfig())\r\n }\r\n\r\n private clientPromise: Promise> | null = null\r\n\r\n protected get provider(): Promise> {\r\n return this.clientPromise!!\r\n }\r\n\r\n protected readonly stagingUserIdPromise = new Lazy(() => this.getOrCreateStagingUserId())\r\n\r\n // public, allow to read old config for anyone\r\n /** @internal */\r\n configOnDisk = new Lazy(() => this.loadUpdateConfig())\r\n\r\n private readonly untilAppReady: Promise\r\n private checkForUpdatesPromise: Promise | null = null\r\n\r\n protected readonly app: Electron.App\r\n\r\n protected updateInfo: UpdateInfo | null = null\r\n\r\n /** @internal */\r\n readonly httpExecutor: ElectronHttpExecutor\r\n\r\n protected constructor(options: AllPublishOptions | null | undefined, app?: Electron.App) {\r\n super()\r\n\r\n this.on(\"error\", (error: Error) => {\r\n this._logger.error(`Error: ${error.stack || error.message}`)\r\n })\r\n\r\n if (app != null || (global as any).__test_app != null) {\r\n this.app = app || (global as any).__test_app\r\n this.untilAppReady = Promise.resolve()\r\n this.httpExecutor = null as any\r\n }\r\n else {\r\n this.app = require(\"electron\").app\r\n this.httpExecutor = new ElectronHttpExecutor((authInfo, callback) => this.emit(\"login\", authInfo, callback))\r\n this.untilAppReady = new Promise(resolve => {\r\n if (this.app.isReady()) {\r\n resolve()\r\n }\r\n else {\r\n this.app.on(\"ready\", resolve)\r\n }\r\n })\r\n }\r\n\r\n this.downloadedUpdateHelper = new DownloadedUpdateHelper(path.join(this.app.getPath(\"userData\"), \"__update__\"))\r\n\r\n const currentVersionString = this.app.getVersion()\r\n const currentVersion = parseVersion(currentVersionString)\r\n if (currentVersion == null) {\r\n throw newError(`App version is not a valid semver version: \"${currentVersionString}\"`, \"ERR_UPDATER_INVALID_VERSION\")\r\n }\r\n this.currentVersion = currentVersion\r\n\r\n this.allowPrerelease = hasPrereleaseComponents(currentVersion)\r\n\r\n if (options != null) {\r\n this.setFeedURL(options)\r\n }\r\n }\r\n\r\n //noinspection JSMethodCanBeStatic,JSUnusedGlobalSymbols\r\n getFeedURL(): string | null | undefined {\r\n return \"Deprecated. Do not use it.\"\r\n }\r\n\r\n /**\r\n * Configure update provider. If value is `string`, [GenericServerOptions](/configuration/publish.md#genericserveroptions) will be set with value as `url`.\r\n * @param options If you want to override configuration in the `app-update.yml`.\r\n */\r\n setFeedURL(options: PublishConfiguration | AllPublishOptions | string) {\r\n // https://github.com/electron-userland/electron-builder/issues/1105\r\n let provider: Provider\r\n if (typeof options === \"string\") {\r\n provider = new GenericProvider({provider: \"generic\", url: options}, this, isUrlProbablySupportMultiRangeRequests(options))\r\n }\r\n else {\r\n provider = createClient(options, this)\r\n }\r\n this.clientPromise = Promise.resolve(provider)\r\n }\r\n\r\n /**\r\n * Asks the server whether there is an update.\r\n */\r\n checkForUpdates(): Promise {\r\n let checkForUpdatesPromise = this.checkForUpdatesPromise\r\n if (checkForUpdatesPromise != null) {\r\n return checkForUpdatesPromise\r\n }\r\n\r\n checkForUpdatesPromise = this._checkForUpdates()\r\n this.checkForUpdatesPromise = checkForUpdatesPromise\r\n const nullizePromise = () => this.checkForUpdatesPromise = null\r\n checkForUpdatesPromise\r\n .then(nullizePromise)\r\n .catch(nullizePromise)\r\n return checkForUpdatesPromise\r\n }\r\n\r\n checkForUpdatesAndNotify(): Promise {\r\n if (isDev) {\r\n return Promise.resolve(null)\r\n }\r\n\r\n const checkForUpdatesPromise = this.checkForUpdates()\r\n checkForUpdatesPromise\r\n .then(it => {\r\n const downloadPromise = it.downloadPromise\r\n if (downloadPromise == null) {\r\n const debug = this._logger.debug\r\n if (debug != null) {\r\n debug(\"checkForUpdatesAndNotify called, downloadPromise is null\")\r\n }\r\n return\r\n }\r\n\r\n downloadPromise\r\n .then(() => {\r\n new Notification({\r\n title: \"A new update is ready to install\",\r\n body: `${this.app.getName()} version ${it.updateInfo.version} is downloaded and will be automatically installed on exit`\r\n }).show()\r\n })\r\n })\r\n\r\n return checkForUpdatesPromise\r\n }\r\n\r\n private async isStagingMatch(updateInfo: UpdateInfo): Promise {\r\n const rawStagingPercentage = updateInfo.stagingPercentage\r\n let stagingPercentage = rawStagingPercentage\r\n if (stagingPercentage == null) {\r\n return true\r\n }\r\n\r\n stagingPercentage = parseInt(stagingPercentage as any, 10)\r\n if (isNaN(stagingPercentage)) {\r\n this._logger.warn(`Staging percentage is NaN: ${rawStagingPercentage}`)\r\n return true\r\n }\r\n\r\n // convert from user 0-100 to internal 0-1\r\n stagingPercentage = stagingPercentage / 100\r\n\r\n const stagingUserId = await this.stagingUserIdPromise.value\r\n const val = UUID.parse(stagingUserId).readUInt32BE(12)\r\n const percentage = (val / 0xFFFFFFFF)\r\n this._logger.info(`Staging percentage: ${stagingPercentage}, percentage: ${percentage}, user id: ${stagingUserId}`)\r\n return percentage < stagingPercentage\r\n }\r\n\r\n private async _checkForUpdates(): Promise {\r\n try {\r\n await this.untilAppReady\r\n this._logger.info(\"Checking for update\")\r\n this.emit(\"checking-for-update\")\r\n return await this.doCheckForUpdates()\r\n }\r\n catch (e) {\r\n this.emit(\"error\", e, `Cannot check for updates: ${(e.stack || e).toString()}`)\r\n throw e\r\n }\r\n }\r\n\r\n private computeFinalHeaders(headers: OutgoingHttpHeaders) {\r\n if (this.requestHeaders != null) {\r\n Object.assign(headers, this.requestHeaders)\r\n }\r\n return headers\r\n }\r\n\r\n private async isUpdateAvailable(updateInfo: UpdateInfo): Promise {\r\n const latestVersion = parseVersion(updateInfo.version)\r\n if (latestVersion == null) {\r\n throw newError(`This file could not be downloaded, or the latest version (from update server) does not have a valid semver version: \"${latestVersion}\"`, \"ERR_UPDATER_INVALID_VERSION\")\r\n }\r\n\r\n const currentVersion = this.currentVersion\r\n if (isVersionsEqual(latestVersion, currentVersion)) {\r\n return false\r\n }\r\n\r\n const isStagingMatch = await this.isStagingMatch(updateInfo)\r\n if (!isStagingMatch) {\r\n return false\r\n }\r\n\r\n // https://github.com/electron-userland/electron-builder/pull/3111#issuecomment-405033227\r\n // https://github.com/electron-userland/electron-builder/pull/3111#issuecomment-405030797\r\n const isLatestVersionNewer = isVersionGreaterThan(latestVersion, currentVersion)\r\n if (!this.allowDowngrade) {\r\n return isLatestVersionNewer\r\n }\r\n\r\n const currentVersionPrereleaseComponent = getVersionPreleaseComponents(currentVersion)\r\n const latestVersionPrereleaseComponent = getVersionPreleaseComponents(latestVersion)\r\n if (currentVersionPrereleaseComponent === latestVersionPrereleaseComponent) {\r\n // allowDowngrade taken in account only if channel differs\r\n return isLatestVersionNewer\r\n }\r\n\r\n return true\r\n }\r\n\r\n protected async getUpdateInfo(): Promise {\r\n await this.untilAppReady\r\n\r\n if (this.clientPromise == null) {\r\n this.clientPromise = this.configOnDisk.value.then(it => createClient(it, this))\r\n }\r\n\r\n const client = await this.clientPromise\r\n const stagingUserId = await this.stagingUserIdPromise.value\r\n client.setRequestHeaders(this.computeFinalHeaders({\"x-user-staging-id\": stagingUserId}))\r\n return await client.getLatestVersion()\r\n }\r\n\r\n private async doCheckForUpdates(): Promise {\r\n const updateInfo = await this.getUpdateInfo()\r\n if (!await this.isUpdateAvailable(updateInfo)) {\r\n this._logger.info(`Update for version ${this.currentVersion} is not available (latest version: ${updateInfo.version}, downgrade is ${this.allowDowngrade ? \"allowed\" : \"disallowed\"}).`)\r\n this.emit(\"update-not-available\", updateInfo)\r\n return {\r\n versionInfo: updateInfo,\r\n updateInfo,\r\n }\r\n }\r\n\r\n this.updateInfo = updateInfo\r\n\r\n this.onUpdateAvailable(updateInfo)\r\n\r\n const cancellationToken = new CancellationToken()\r\n //noinspection ES6MissingAwait\r\n return {\r\n versionInfo: updateInfo,\r\n updateInfo,\r\n cancellationToken,\r\n downloadPromise: this.autoDownload ? this.downloadUpdate(cancellationToken) : null\r\n }\r\n }\r\n\r\n protected onUpdateAvailable(updateInfo: UpdateInfo) {\r\n this._logger.info(`Found version ${updateInfo.version} (url: ${asArray(updateInfo.files).map(it => it.url).join(\", \")})`)\r\n this.emit(\"update-available\", updateInfo)\r\n }\r\n\r\n /**\r\n * Start downloading update manually. You can use this method if `autoDownload` option is set to `false`.\r\n * @returns {Promise} Path to downloaded file.\r\n */\r\n async downloadUpdate(cancellationToken: CancellationToken = new CancellationToken()): Promise {\r\n const updateInfo = this.updateInfo\r\n if (updateInfo == null) {\r\n const error = new Error(\"Please check update first\")\r\n this.dispatchError(error)\r\n throw error\r\n }\r\n\r\n this._logger.info(`Downloading update from ${asArray(updateInfo.files).map(it => it.url).join(\", \")}`)\r\n\r\n try {\r\n return await this.doDownloadUpdate({\r\n updateInfo,\r\n requestHeaders: await this.computeRequestHeaders(),\r\n cancellationToken,\r\n })\r\n }\r\n catch (e) {\r\n this.dispatchError(e)\r\n throw e\r\n }\r\n }\r\n\r\n protected dispatchError(e: Error) {\r\n this.emit(\"error\", e, (e.stack || e).toString())\r\n }\r\n\r\n protected async abstract doDownloadUpdate(downloadUpdateOptions: DownloadUpdateOptions): Promise>\r\n\r\n /**\r\n * Restarts the app and installs the update after it has been downloaded.\r\n * It should only be called after `update-downloaded` has been emitted.\r\n *\r\n * **Note:** `autoUpdater.quitAndInstall()` will close all application windows first and only emit `before-quit` event on `app` after that.\r\n * This is different from the normal quit event sequence.\r\n *\r\n * @param isSilent *windows-only* Runs the installer in silent mode. Defaults to `false`.\r\n * @param isForceRunAfter Run the app after finish even on silent install. Not applicable for macOS. Ignored if `isSilent` is set to `false`.\r\n */\r\n abstract quitAndInstall(isSilent?: boolean, isForceRunAfter?: boolean): void\r\n\r\n private async loadUpdateConfig() {\r\n if (this._appUpdateConfigPath == null) {\r\n this._appUpdateConfigPath = isDev ? path.join(this.app.getAppPath(), \"dev-app-update.yml\") : path.join(process.resourcesPath!, \"app-update.yml\")\r\n }\r\n return safeLoad(await readFile(this._appUpdateConfigPath, \"utf-8\"))\r\n }\r\n\r\n private async computeRequestHeaders(): Promise {\r\n const fileExtraDownloadHeaders = (await this.provider).fileExtraDownloadHeaders\r\n if (fileExtraDownloadHeaders != null) {\r\n const requestHeaders = this.requestHeaders\r\n return requestHeaders == null ? fileExtraDownloadHeaders : {\r\n ...fileExtraDownloadHeaders,\r\n ...requestHeaders,\r\n }\r\n }\r\n return this.computeFinalHeaders({accept: \"*/*\"})\r\n }\r\n\r\n private async getOrCreateStagingUserId(): Promise {\r\n const file = path.join(this.app.getPath(\"userData\"), \".updaterId\")\r\n try {\r\n const id = await readFile(file, \"utf-8\")\r\n if (UUID.check(id)) {\r\n return id\r\n }\r\n else {\r\n this._logger.warn(`Staging user id file exists, but content was invalid: ${id}`)\r\n }\r\n }\r\n catch (e) {\r\n if (e.code !== \"ENOENT\") {\r\n this._logger.warn(`Couldn't read staging user ID, creating a blank one: ${e}`)\r\n }\r\n }\r\n\r\n const id = UUID.v5(randomBytes(4096), UUID.OID)\r\n this._logger.info(`Generated new staging user ID: ${id}`)\r\n try {\r\n await outputFile(file, id)\r\n }\r\n catch (e) {\r\n this._logger.warn(`Couldn't write out staging user ID: ${e}`)\r\n }\r\n return id\r\n }\r\n\r\n /** @internal */\r\n get isAddNoCacheQuery(): boolean {\r\n const headers = this.requestHeaders\r\n // https://github.com/electron-userland/electron-builder/issues/3021\r\n if (headers == null) {\r\n return true\r\n }\r\n\r\n for (const headerName of Object.keys(headers)) {\r\n const s = headerName.toLowerCase()\r\n if (s === \"authorization\" || s === \"private-token\") {\r\n return false\r\n }\r\n }\r\n return true\r\n }\r\n\r\n protected async executeDownload(taskOptions: DownloadExecutorTask): Promise> {\r\n const fileInfo = taskOptions.fileInfo\r\n const downloadOptions: DownloadOptions = {\r\n skipDirCreation: true,\r\n headers: taskOptions.downloadUpdateOptions.requestHeaders,\r\n cancellationToken: taskOptions.downloadUpdateOptions.cancellationToken,\r\n sha2: (fileInfo.info as any).sha2,\r\n sha512: fileInfo.info.sha512,\r\n }\r\n\r\n if (this.listenerCount(DOWNLOAD_PROGRESS) > 0) {\r\n downloadOptions.onProgress = it => this.emit(DOWNLOAD_PROGRESS, it)\r\n }\r\n\r\n const updateInfo = taskOptions.downloadUpdateOptions.updateInfo\r\n const version = updateInfo.version\r\n const packageInfo = fileInfo.packageInfo\r\n\r\n function getCacheUpdateFileName(): string {\r\n // bloody NodeJS URL doesn't decode automatically\r\n const urlPath = decodeURIComponent(taskOptions.fileInfo.url.pathname)\r\n if (urlPath.endsWith(`.${taskOptions.fileExtension}`)) {\r\n return path.posix.basename(urlPath)\r\n }\r\n else {\r\n // url like /latest, generate name\r\n return `update.${taskOptions.fileExtension}`\r\n }\r\n }\r\n\r\n const cacheDir = this.downloadedUpdateHelper.cacheDir\r\n await ensureDir(cacheDir)\r\n const updateFileName = getCacheUpdateFileName()\r\n let updateFile = path.join(cacheDir, updateFileName)\r\n const packageFile = packageInfo == null ? null : path.join(cacheDir, `package-${version}${path.extname(packageInfo.path) || \".7z\"}`)\r\n\r\n const done = async (isSaveCache: boolean) => {\r\n this.downloadedUpdateHelper.setDownloadedFile(updateFile, packageFile, updateInfo, fileInfo)\r\n if (isSaveCache) {\r\n await this.downloadedUpdateHelper.cacheUpdateInfo(updateFileName)\r\n }\r\n\r\n this.emit(UPDATE_DOWNLOADED, updateInfo)\r\n await taskOptions.done!!(updateFile)\r\n return packageFile == null ? [updateFile] : [updateFile, packageFile]\r\n }\r\n\r\n const log = this._logger\r\n const cachedUpdateFile = await this.downloadedUpdateHelper.validateDownloadedPath(updateFile, updateInfo, fileInfo, log)\r\n if (cachedUpdateFile != null) {\r\n updateFile = cachedUpdateFile\r\n return await done(false)\r\n }\r\n\r\n const removeFileIfAny = async () => {\r\n await this.downloadedUpdateHelper.clear()\r\n .catch(() => {\r\n // ignore\r\n })\r\n return await unlink(updateFile)\r\n .catch(() => {\r\n // ignore\r\n })\r\n }\r\n\r\n // https://github.com/electron-userland/electron-builder/pull/2474#issuecomment-366481912\r\n let nameCounter = 0\r\n let tempUpdateFile = path.join(cacheDir, `temp-${updateFileName}`)\r\n for (let i = 0; i < 3; i++) {\r\n try {\r\n await unlink(tempUpdateFile)\r\n }\r\n catch (e) {\r\n if (e.code === \"ENOENT\") {\r\n break\r\n }\r\n\r\n log.warn(`Error on remove temp update file: ${e}`)\r\n tempUpdateFile = path.join(cacheDir, `temp-${nameCounter++}-${updateFileName}`)\r\n }\r\n }\r\n\r\n try {\r\n await taskOptions.task(tempUpdateFile, downloadOptions, packageFile, removeFileIfAny)\r\n await rename(tempUpdateFile, updateFile)\r\n }\r\n catch (e) {\r\n await removeFileIfAny()\r\n\r\n if (e instanceof CancellationError) {\r\n log.info(\"Cancelled\")\r\n this.emit(\"update-cancelled\", updateInfo)\r\n }\r\n throw e\r\n }\r\n\r\n log.info(`New version ${version} has been downloaded to ${updateFile}`)\r\n return await done(true)\r\n }\r\n}\r\n\r\nexport interface DownloadUpdateOptions {\r\n readonly updateInfo: UpdateInfo\r\n readonly requestHeaders: OutgoingHttpHeaders\r\n readonly cancellationToken: CancellationToken\r\n}\r\n\r\nfunction hasPrereleaseComponents(version: SemVer) {\r\n const versionPrereleaseComponent = getVersionPreleaseComponents(version)\r\n return versionPrereleaseComponent != null && versionPrereleaseComponent.length > 0\r\n}\r\n\r\n/** @private */\r\nexport class NoOpLogger implements Logger {\r\n info(message?: any) {\r\n // ignore\r\n }\r\n\r\n warn(message?: any) {\r\n // ignore\r\n }\r\n\r\n error(message?: any) {\r\n // ignore\r\n }\r\n}\r\n\r\nexport interface DownloadExecutorTask {\r\n readonly fileExtension: string\r\n readonly fileInfo: ResolvedUpdateFileInfo\r\n readonly downloadUpdateOptions: DownloadUpdateOptions\r\n readonly task: (destinationFile: string, downloadOptions: DownloadOptions, packageFile: string | null, removeTempDirIfAny: () => Promise) => Promise\r\n\r\n readonly done?: (destinationFile: string) => Promise\r\n}"],"sourceRoot":""} diff --git a/out/BaseUpdater.d.ts b/out/BaseUpdater.d.ts new file mode 100644 index 0000000..5819c05 --- /dev/null +++ b/out/BaseUpdater.d.ts @@ -0,0 +1,12 @@ +import { AllPublishOptions } from "builder-util-runtime"; +import { AppUpdater, DownloadExecutorTask } from "./AppUpdater"; +export declare abstract class BaseUpdater extends AppUpdater { + protected quitAndInstallCalled: boolean; + private quitHandlerAdded; + protected constructor(options?: AllPublishOptions | null, app?: any); + quitAndInstall(isSilent?: boolean, isForceRunAfter?: boolean): Promise; + protected executeDownload(taskOptions: DownloadExecutorTask): Promise>; + protected abstract doInstall(installerPath: string, isSilent: boolean, isRunAfter: boolean): boolean; + protected install(isSilent: boolean, isRunAfter: boolean): Promise; + protected addQuitHandler(): void; +} diff --git a/out/BaseUpdater.js b/out/BaseUpdater.js new file mode 100644 index 0000000..c5db2b8 --- /dev/null +++ b/out/BaseUpdater.js @@ -0,0 +1,95 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.BaseUpdater = void 0; + +function _AppUpdater() { + const data = require("./AppUpdater"); + + _AppUpdater = function () { + return data; + }; + + return data; +} + +class BaseUpdater extends _AppUpdater().AppUpdater { + constructor(options, app) { + super(options, app); + this.quitAndInstallCalled = false; + this.quitHandlerAdded = false; + } + + async quitAndInstall(isSilent = false, isForceRunAfter = false) { + this._logger.info(`Install on explicit quitAndInstall`); + + const isInstalled = await this.install(isSilent, isSilent ? isForceRunAfter : true); + + if (isInstalled) { + setImmediate(() => { + if (this.app.quit !== undefined) { + this.app.quit(); + } + }); + } else { + this.quitAndInstallCalled = false; + } + } + + executeDownload(taskOptions) { + return super.executeDownload(Object.assign({}, taskOptions, { + done: async () => { + this.addQuitHandler(); + } + })); + } + + async install(isSilent, isRunAfter) { + if (this.quitAndInstallCalled) { + this._logger.warn("install call ignored: quitAndInstallCalled is set to true"); + + return false; + } + + const installerPath = this.downloadedUpdateHelper.file; // todo check (for now it is ok to no check as before, cached (from previous launch) update file checked in any case) + // const isValid = await this.isUpdateValid(installerPath) + + if (installerPath == null) { + this.dispatchError(new Error("No valid update available, can't quit and install")); + return false; + } // prevent calling several times + + + this.quitAndInstallCalled = true; + + try { + this._logger.info(`Install: isSilent: ${isSilent}, isRunAfter: ${isRunAfter}`); + + return this.doInstall(installerPath, isSilent, isRunAfter); + } catch (e) { + this.dispatchError(e); + return false; + } + } + + addQuitHandler() { + if (this.quitHandlerAdded || !this.autoInstallOnAppQuit) { + return; + } + + this.quitHandlerAdded = true; + this.app.once("quit", async () => { + if (!this.quitAndInstallCalled) { + this._logger.info("Auto install update on quit"); + + await this.install(true, false); + } else { + this._logger.info("Update installer has already been triggered. Quitting application."); + } + }); + } + +} exports.BaseUpdater = BaseUpdater; +//# sourceMappingURL=BaseUpdater.js.map \ No newline at end of file diff --git a/out/BaseUpdater.js.map b/out/BaseUpdater.js.map new file mode 100644 index 0000000..4280743 --- /dev/null +++ b/out/BaseUpdater.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/BaseUpdater.ts"],"names":[],"mappings":";;;;;;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAEM,MAAgB,WAAhB,SAAoC,wBAApC,CAA8C;AAIlD,EAAA,WAAA,CAAsB,OAAtB,EAA0D,GAA1D,EAAmE;AACjE,UAAM,OAAN,EAAe,GAAf;AAJQ,SAAA,oBAAA,GAAuB,KAAvB;AACF,SAAA,gBAAA,GAAmB,KAAnB;AAIP;;AAED,QAAM,cAAN,CAAqB,QAAA,GAAoB,KAAzC,EAAgD,eAAA,GAA2B,KAA3E,EAAgF;AAC9E,SAAK,OAAL,CAAa,IAAb,CAAkB,oCAAlB;;AACA,UAAM,WAAW,GAAG,MAAM,KAAK,OAAL,CAAa,QAAb,EAAuB,QAAQ,GAAG,eAAH,GAAqB,IAApD,CAA1B;;AACA,QAAI,WAAJ,EAAiB;AACf,MAAA,YAAY,CAAC,MAAK;AAChB,YAAI,KAAK,GAAL,CAAS,IAAT,KAAkB,SAAtB,EAAiC;AAC/B,eAAK,GAAL,CAAS,IAAT;AACD;AACF,OAJW,CAAZ;AAKD,KAND,MAOK;AACH,WAAK,oBAAL,GAA4B,KAA5B;AACD;AACF;;AAES,EAAA,eAAe,CAAC,WAAD,EAAkC;AACzD,WAAO,MAAM,eAAN,CAAqB,MAAA,CAAA,MAAA,CAAA,EAAA,EACvB,WADuB,EACZ;AACd,MAAA,IAAI,EAAE,YAAW;AACf,aAAK,cAAL;AACD;AAHa,KADY,CAArB,CAAP;AAMD;;AAIS,QAAM,OAAN,CAAc,QAAd,EAAiC,UAAjC,EAAoD;AAC5D,QAAI,KAAK,oBAAT,EAA+B;AAC7B,WAAK,OAAL,CAAa,IAAb,CAAkB,2DAAlB;;AACA,aAAO,KAAP;AACD;;AAED,UAAM,aAAa,GAAG,KAAK,sBAAL,CAA4B,IAAlD,CAN4D,CAO5D;AACA;;AACA,QAAI,aAAa,IAAI,IAArB,EAA2B;AACzB,WAAK,aAAL,CAAmB,IAAI,KAAJ,CAAU,mDAAV,CAAnB;AACA,aAAO,KAAP;AACD,KAZ2D,CAc5D;;;AACA,SAAK,oBAAL,GAA4B,IAA5B;;AAEA,QAAI;AACF,WAAK,OAAL,CAAa,IAAb,CAAkB,sBAAsB,QAAQ,iBAAiB,UAAU,EAA3E;;AACA,aAAO,KAAK,SAAL,CAAe,aAAf,EAA8B,QAA9B,EAAwC,UAAxC,CAAP;AACD,KAHD,CAIA,OAAO,CAAP,EAAU;AACR,WAAK,aAAL,CAAmB,CAAnB;AACA,aAAO,KAAP;AACD;AACF;;AAES,EAAA,cAAc,GAAA;AACtB,QAAI,KAAK,gBAAL,IAAyB,CAAC,KAAK,oBAAnC,EAAyD;AACvD;AACD;;AAED,SAAK,gBAAL,GAAwB,IAAxB;AAEA,SAAK,GAAL,CAAS,IAAT,CAAc,MAAd,EAAsB,YAAW;AAC/B,UAAI,CAAC,KAAK,oBAAV,EAAgC;AAC9B,aAAK,OAAL,CAAa,IAAb,CAAkB,6BAAlB;;AACA,cAAM,KAAK,OAAL,CAAa,IAAb,EAAmB,KAAnB,CAAN;AACD,OAHD,MAIK;AACH,aAAK,OAAL,CAAa,IAAb,CAAkB,oEAAlB;AACD;AACF,KARD;AASD;;AA7EiD,C","sourcesContent":["import { AllPublishOptions } from \"builder-util-runtime\"\r\nimport { AppUpdater, DownloadExecutorTask } from \"./AppUpdater\"\r\n\r\nexport abstract class BaseUpdater extends AppUpdater {\r\n protected quitAndInstallCalled = false\r\n private quitHandlerAdded = false\r\n\r\n protected constructor(options?: AllPublishOptions | null, app?: any) {\r\n super(options, app)\r\n }\r\n\r\n async quitAndInstall(isSilent: boolean = false, isForceRunAfter: boolean = false): Promise {\r\n this._logger.info(`Install on explicit quitAndInstall`)\r\n const isInstalled = await this.install(isSilent, isSilent ? isForceRunAfter : true)\r\n if (isInstalled) {\r\n setImmediate(() => {\r\n if (this.app.quit !== undefined) {\r\n this.app.quit()\r\n }\r\n })\r\n }\r\n else {\r\n this.quitAndInstallCalled = false\r\n }\r\n }\r\n\r\n protected executeDownload(taskOptions: DownloadExecutorTask): Promise> {\r\n return super.executeDownload({\r\n ...taskOptions,\r\n done: async () => {\r\n this.addQuitHandler()\r\n }\r\n })\r\n }\r\n\r\n protected abstract doInstall(installerPath: string, isSilent: boolean, isRunAfter: boolean): boolean\r\n\r\n protected async install(isSilent: boolean, isRunAfter: boolean): Promise {\r\n if (this.quitAndInstallCalled) {\r\n this._logger.warn(\"install call ignored: quitAndInstallCalled is set to true\")\r\n return false\r\n }\r\n\r\n const installerPath = this.downloadedUpdateHelper.file\r\n // todo check (for now it is ok to no check as before, cached (from previous launch) update file checked in any case)\r\n // const isValid = await this.isUpdateValid(installerPath)\r\n if (installerPath == null) {\r\n this.dispatchError(new Error(\"No valid update available, can't quit and install\"))\r\n return false\r\n }\r\n\r\n // prevent calling several times\r\n this.quitAndInstallCalled = true\r\n\r\n try {\r\n this._logger.info(`Install: isSilent: ${isSilent}, isRunAfter: ${isRunAfter}`)\r\n return this.doInstall(installerPath, isSilent, isRunAfter)\r\n }\r\n catch (e) {\r\n this.dispatchError(e)\r\n return false\r\n }\r\n }\r\n\r\n protected addQuitHandler() {\r\n if (this.quitHandlerAdded || !this.autoInstallOnAppQuit) {\r\n return\r\n }\r\n\r\n this.quitHandlerAdded = true\r\n\r\n this.app.once(\"quit\", async () => {\r\n if (!this.quitAndInstallCalled) {\r\n this._logger.info(\"Auto install update on quit\")\r\n await this.install(true, false)\r\n }\r\n else {\r\n this._logger.info(\"Update installer has already been triggered. Quitting application.\")\r\n }\r\n })\r\n }\r\n}"],"sourceRoot":""} diff --git a/out/DownloadedUpdateHelper.d.ts b/out/DownloadedUpdateHelper.d.ts new file mode 100644 index 0000000..b961f44 --- /dev/null +++ b/out/DownloadedUpdateHelper.d.ts @@ -0,0 +1,19 @@ +import { UpdateInfo } from "builder-util-runtime"; +import { Logger, ResolvedUpdateFileInfo } from "./main"; +/** @private **/ +export declare class DownloadedUpdateHelper { + readonly cacheDir: string; + private _file; + private _packageFile; + private versionInfo; + private fileInfo; + constructor(cacheDir: string); + readonly file: string | null; + readonly packageFile: string | null; + validateDownloadedPath(updateFile: string, versionInfo: UpdateInfo, fileInfo: ResolvedUpdateFileInfo, logger: Logger): Promise; + setDownloadedFile(downloadedFile: string, packageFile: string | null, versionInfo: UpdateInfo, fileInfo: ResolvedUpdateFileInfo): void; + cacheUpdateInfo(updateFileName: string): Promise; + clear(): Promise; + private cleanCacheDir; + private getValidCachedUpdateFile; +} diff --git a/out/DownloadedUpdateHelper.js b/out/DownloadedUpdateHelper.js new file mode 100644 index 0000000..8870909 --- /dev/null +++ b/out/DownloadedUpdateHelper.js @@ -0,0 +1,194 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.DownloadedUpdateHelper = void 0; + +function _crypto() { + const data = require("crypto"); + + _crypto = function () { + return data; + }; + + return data; +} + +function _fs() { + const data = require("fs"); + + _fs = function () { + return data; + }; + + return data; +} + +function _lodash() { + const data = _interopRequireDefault(require("lodash.isequal")); + + _lodash = function () { + return data; + }; + + return data; +} + +function _fsExtraP() { + const data = require("fs-extra-p"); + + _fsExtraP = function () { + return data; + }; + + return data; +} + +var path = _interopRequireWildcard(require("path")); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** @private **/ +class DownloadedUpdateHelper { + constructor(cacheDir) { + this.cacheDir = cacheDir; + this._file = null; + this._packageFile = null; + this.versionInfo = null; + this.fileInfo = null; + } + + get file() { + return this._file; + } + + get packageFile() { + return this._packageFile; + } + + async validateDownloadedPath(updateFile, versionInfo, fileInfo, logger) { + if (this.versionInfo != null && this.file === updateFile && this.fileInfo != null) { + // update has already been downloaded from this running instance + // check here only existence, not checksum + if ((0, _lodash().default)(this.versionInfo, versionInfo) && (0, _lodash().default)(this.fileInfo.info, fileInfo.info) && (await (0, _fsExtraP().pathExists)(updateFile))) { + return updateFile; + } else { + return null; + } + } // update has already been downloaded from some previous app launch + + + const cachedUpdateFile = await this.getValidCachedUpdateFile(fileInfo, logger); + + if (cachedUpdateFile == null) { + return null; + } + + logger.info(`Update has already been downloaded to ${updateFile}).`); + return cachedUpdateFile; + } + + setDownloadedFile(downloadedFile, packageFile, versionInfo, fileInfo) { + this._file = downloadedFile; + this._packageFile = packageFile; + this.versionInfo = versionInfo; + this.fileInfo = fileInfo; + } + + async cacheUpdateInfo(updateFileName) { + const data = { + fileName: updateFileName, + sha512: this.fileInfo.info.sha512 + }; + await (0, _fsExtraP().outputJson)(path.join(this.cacheDir, "update-info.json"), data); + } + + async clear() { + this._file = null; + this._packageFile = null; + this.versionInfo = null; + this.fileInfo = null; + await this.cleanCacheDir(); + } + + async cleanCacheDir() { + try { + // remove stale data + await (0, _fsExtraP().emptyDir)(this.cacheDir); + } catch (ignore) {// ignore + } + } + + async getValidCachedUpdateFile(fileInfo, logger) { + let cachedInfo; + const updateInfoFile = path.join(this.cacheDir, "update-info.json"); + + try { + cachedInfo = await (0, _fsExtraP().readJson)(updateInfoFile); + } catch (e) { + let message = `No cached update info available`; + + if (e.code !== "ENOENT") { + await this.cleanCacheDir(); + message += ` (error on read: ${e.message})`; + } + + logger.info(message); + return null; + } + + if (cachedInfo.fileName == null) { + logger.warn(`Cached update info is corrupted: no fileName, directory for cached update will be cleaned`); + await this.cleanCacheDir(); + return null; + } + + if (fileInfo.info.sha512 !== cachedInfo.sha512) { + logger.info(`Cached update sha512 checksum doesn't match the latest available update. New update must be downloaded. Cached: ${cachedInfo.sha512}, expected: ${fileInfo.info.sha512}. Directory for cached update will be cleaned`); + await this.cleanCacheDir(); + return null; + } + + const updateFile = path.join(this.cacheDir, cachedInfo.fileName); + + if (!(await (0, _fsExtraP().pathExists)(updateFile))) { + logger.info("Cached update file doesn't exist, directory for cached update will be cleaned"); + await this.cleanCacheDir(); + return null; + } + + const sha512 = await hashFile(updateFile); + + if (fileInfo.info.sha512 !== sha512) { + logger.warn(`Sha512 checksum doesn't match the latest available update. New update must be downloaded. Cached: ${sha512}, expected: ${fileInfo.info.sha512}`); + await this.cleanCacheDir(); + return null; + } + + return updateFile; + } + +} + +exports.DownloadedUpdateHelper = DownloadedUpdateHelper; + +function hashFile(file, algorithm = "sha512", encoding = "base64", options) { + return new Promise((resolve, reject) => { + const hash = (0, _crypto().createHash)(algorithm); + hash.on("error", reject).setEncoding(encoding); + (0, _fs().createReadStream)(file, Object.assign({}, options, { + highWaterMark: 1024 * 1024 + /* better to use more memory but hash faster */ + + })).on("error", reject).on("end", () => { + hash.end(); + resolve(hash.read()); + }).pipe(hash, { + end: false + }); + }); +} +//# sourceMappingURL=DownloadedUpdateHelper.js.map \ No newline at end of file diff --git a/out/DownloadedUpdateHelper.js.map b/out/DownloadedUpdateHelper.js.map new file mode 100644 index 0000000..d64bbeb --- /dev/null +++ b/out/DownloadedUpdateHelper.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/DownloadedUpdateHelper.ts"],"names":[],"mappings":";;;;;;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAEA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;;;;;;AAEA;AACM,MAAO,sBAAP,CAA6B;AAOjC,EAAA,WAAA,CAAqB,QAArB,EAAqC;AAAhB,SAAA,QAAA,GAAA,QAAA;AANb,SAAA,KAAA,GAAuB,IAAvB;AACA,SAAA,YAAA,GAA8B,IAA9B;AAEA,SAAA,WAAA,GAAiC,IAAjC;AACA,SAAA,QAAA,GAA0C,IAA1C;AAGP;;AAED,MAAI,IAAJ,GAAQ;AACN,WAAO,KAAK,KAAZ;AACD;;AAED,MAAI,WAAJ,GAAe;AACb,WAAO,KAAK,YAAZ;AACD;;AAED,QAAM,sBAAN,CAA6B,UAA7B,EAAiD,WAAjD,EAA0E,QAA1E,EAA4G,MAA5G,EAA0H;AACxH,QAAI,KAAK,WAAL,IAAoB,IAApB,IAA4B,KAAK,IAAL,KAAc,UAA1C,IAAwD,KAAK,QAAL,IAAiB,IAA7E,EAAmF;AACjF;AACA;AACA,UAAI,uBAAQ,KAAK,WAAb,EAA0B,WAA1B,KAA0C,uBAAQ,KAAK,QAAL,CAAc,IAAtB,EAA4B,QAAQ,CAAC,IAArC,CAA1C,KAAyF,MAAM,4BAAW,UAAX,CAA/F,CAAJ,EAA4H;AAC1H,eAAO,UAAP;AACD,OAFD,MAGK;AACH,eAAO,IAAP;AACD;AACF,KAVuH,CAYxH;;;AACA,UAAM,gBAAgB,GAAG,MAAM,KAAK,wBAAL,CAA8B,QAA9B,EAAwC,MAAxC,CAA/B;;AACA,QAAI,gBAAgB,IAAI,IAAxB,EAA8B;AAC5B,aAAO,IAAP;AACD;;AACD,IAAA,MAAM,CAAC,IAAP,CAAY,yCAAyC,UAAU,IAA/D;AACA,WAAO,gBAAP;AACD;;AAED,EAAA,iBAAiB,CAAC,cAAD,EAAyB,WAAzB,EAAqD,WAArD,EAA8E,QAA9E,EAA8G;AAC7H,SAAK,KAAL,GAAa,cAAb;AACA,SAAK,YAAL,GAAoB,WAApB;AACA,SAAK,WAAL,GAAmB,WAAnB;AACA,SAAK,QAAL,GAAgB,QAAhB;AACD;;AAED,QAAM,eAAN,CAAsB,cAAtB,EAA4C;AAC1C,UAAM,IAAI,GAAqB;AAC7B,MAAA,QAAQ,EAAE,cADmB;AAE7B,MAAA,MAAM,EAAE,KAAK,QAAL,CAAgB,IAAhB,CAAqB;AAFA,KAA/B;AAIA,UAAM,4BAAW,IAAI,CAAC,IAAL,CAAU,KAAK,QAAf,EAAyB,kBAAzB,CAAX,EAAyD,IAAzD,CAAN;AACD;;AAED,QAAM,KAAN,GAAW;AACT,SAAK,KAAL,GAAa,IAAb;AACA,SAAK,YAAL,GAAoB,IAApB;AACA,SAAK,WAAL,GAAmB,IAAnB;AACA,SAAK,QAAL,GAAgB,IAAhB;AACA,UAAM,KAAK,aAAL,EAAN;AACD;;AAEO,QAAM,aAAN,GAAmB;AACzB,QAAI;AACF;AACA,YAAM,0BAAS,KAAK,QAAd,CAAN;AACD,KAHD,CAIA,OAAO,MAAP,EAAe,CACb;AACD;AACF;;AAEO,QAAM,wBAAN,CAA+B,QAA/B,EAAiE,MAAjE,EAA+E;AACrF,QAAI,UAAJ;AACA,UAAM,cAAc,GAAG,IAAI,CAAC,IAAL,CAAU,KAAK,QAAf,EAAyB,kBAAzB,CAAvB;;AACA,QAAI;AACF,MAAA,UAAU,GAAG,MAAM,0BAAS,cAAT,CAAnB;AACD,KAFD,CAGA,OAAO,CAAP,EAAU;AACR,UAAI,OAAO,GAAG,iCAAd;;AACA,UAAI,CAAC,CAAC,IAAF,KAAW,QAAf,EAAyB;AACvB,cAAM,KAAK,aAAL,EAAN;AACA,QAAA,OAAO,IAAI,oBAAoB,CAAC,CAAC,OAAO,GAAxC;AACD;;AACD,MAAA,MAAM,CAAC,IAAP,CAAY,OAAZ;AACA,aAAO,IAAP;AACD;;AAED,QAAI,UAAU,CAAC,QAAX,IAAuB,IAA3B,EAAiC;AAC/B,MAAA,MAAM,CAAC,IAAP,CAAY,2FAAZ;AACA,YAAM,KAAK,aAAL,EAAN;AACA,aAAO,IAAP;AACD;;AAED,QAAI,QAAQ,CAAC,IAAT,CAAc,MAAd,KAAyB,UAAU,CAAC,MAAxC,EAAgD;AAC9C,MAAA,MAAM,CAAC,IAAP,CAAY,mHAAmH,UAAU,CAAC,MAAM,eAAe,QAAQ,CAAC,IAAT,CAAc,MAAM,+CAAnL;AACA,YAAM,KAAK,aAAL,EAAN;AACA,aAAO,IAAP;AACD;;AAED,UAAM,UAAU,GAAG,IAAI,CAAC,IAAL,CAAU,KAAK,QAAf,EAAyB,UAAU,CAAC,QAApC,CAAnB;;AACA,QAAI,EAAE,MAAM,4BAAW,UAAX,CAAR,CAAJ,EAAqC;AACnC,MAAA,MAAM,CAAC,IAAP,CAAY,+EAAZ;AACA,YAAM,KAAK,aAAL,EAAN;AACA,aAAO,IAAP;AACD;;AAED,UAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,UAAD,CAA7B;;AACA,QAAI,QAAQ,CAAC,IAAT,CAAc,MAAd,KAAyB,MAA7B,EAAqC;AACnC,MAAA,MAAM,CAAC,IAAP,CAAY,qGAAqG,MAAM,eAAe,QAAQ,CAAC,IAAT,CAAc,MAAM,EAA1J;AACA,YAAM,KAAK,aAAL,EAAN;AACA,aAAO,IAAP;AACD;;AACD,WAAO,UAAP;AACD;;AAlHgC;;;;AA0HnC,SAAS,QAAT,CAAkB,IAAlB,EAAgC,SAAA,GAAoB,QAApD,EAA8D,QAAA,GAA6B,QAA3F,EAAqG,OAArG,EAAkH;AAChH,SAAO,IAAI,OAAJ,CAAoB,CAAC,OAAD,EAAU,MAAV,KAAoB;AAC7C,UAAM,IAAI,GAAG,0BAAW,SAAX,CAAb;AACA,IAAA,IAAI,CACD,EADH,CACM,OADN,EACe,MADf,EAEG,WAFH,CAEe,QAFf;AAIA,gCAAiB,IAAjB,EAAqB,MAAA,CAAA,MAAA,CAAA,EAAA,EAAM,OAAN,EAAa;AAAE,MAAA,aAAa,EAAE,OAAO;AAAK;;AAA7B,KAAb,CAArB,EACG,EADH,CACM,OADN,EACe,MADf,EAEG,EAFH,CAEM,KAFN,EAEa,MAAK;AACd,MAAA,IAAI,CAAC,GAAL;AACA,MAAA,OAAO,CAAC,IAAI,CAAC,IAAL,EAAD,CAAP;AACD,KALH,EAMG,IANH,CAMQ,IANR,EAMc;AAAC,MAAA,GAAG,EAAE;AAAN,KANd;AAOD,GAbM,CAAP;AAcD,C","sourcesContent":["import { UpdateInfo } from \"builder-util-runtime\"\r\nimport { createHash } from \"crypto\"\r\nimport { createReadStream } from \"fs\"\r\nimport isEqual from \"lodash.isequal\"\r\nimport { Logger, ResolvedUpdateFileInfo } from \"./main\"\r\nimport { pathExists, readJson, emptyDir, outputJson } from \"fs-extra-p\"\r\nimport * as path from \"path\"\r\n\r\n/** @private **/\r\nexport class DownloadedUpdateHelper {\r\n private _file: string | null = null\r\n private _packageFile: string | null = null\r\n\r\n private versionInfo: UpdateInfo | null = null\r\n private fileInfo: ResolvedUpdateFileInfo | null = null\r\n\r\n constructor(readonly cacheDir: string) {\r\n }\r\n\r\n get file() {\r\n return this._file\r\n }\r\n\r\n get packageFile() {\r\n return this._packageFile\r\n }\r\n\r\n async validateDownloadedPath(updateFile: string, versionInfo: UpdateInfo, fileInfo: ResolvedUpdateFileInfo, logger: Logger): Promise {\r\n if (this.versionInfo != null && this.file === updateFile && this.fileInfo != null) {\r\n // update has already been downloaded from this running instance\r\n // check here only existence, not checksum\r\n if (isEqual(this.versionInfo, versionInfo) && isEqual(this.fileInfo.info, fileInfo.info) && (await pathExists(updateFile))) {\r\n return updateFile\r\n }\r\n else {\r\n return null\r\n }\r\n }\r\n\r\n // update has already been downloaded from some previous app launch\r\n const cachedUpdateFile = await this.getValidCachedUpdateFile(fileInfo, logger)\r\n if (cachedUpdateFile == null) {\r\n return null\r\n }\r\n logger.info(`Update has already been downloaded to ${updateFile}).`)\r\n return cachedUpdateFile\r\n }\r\n\r\n setDownloadedFile(downloadedFile: string, packageFile: string | null, versionInfo: UpdateInfo, fileInfo: ResolvedUpdateFileInfo) {\r\n this._file = downloadedFile\r\n this._packageFile = packageFile\r\n this.versionInfo = versionInfo\r\n this.fileInfo = fileInfo\r\n }\r\n\r\n async cacheUpdateInfo(updateFileName: string) {\r\n const data: CachedUpdateInfo = {\r\n fileName: updateFileName,\r\n sha512: this.fileInfo!!.info.sha512,\r\n }\r\n await outputJson(path.join(this.cacheDir, \"update-info.json\"), data)\r\n }\r\n\r\n async clear() {\r\n this._file = null\r\n this._packageFile = null\r\n this.versionInfo = null\r\n this.fileInfo = null\r\n await this.cleanCacheDir()\r\n }\r\n\r\n private async cleanCacheDir(): Promise {\r\n try {\r\n // remove stale data\r\n await emptyDir(this.cacheDir)\r\n }\r\n catch (ignore) {\r\n // ignore\r\n }\r\n }\r\n\r\n private async getValidCachedUpdateFile(fileInfo: ResolvedUpdateFileInfo, logger: Logger): Promise {\r\n let cachedInfo: CachedUpdateInfo\r\n const updateInfoFile = path.join(this.cacheDir, \"update-info.json\")\r\n try {\r\n cachedInfo = await readJson(updateInfoFile)\r\n }\r\n catch (e) {\r\n let message = `No cached update info available`\r\n if (e.code !== \"ENOENT\") {\r\n await this.cleanCacheDir()\r\n message += ` (error on read: ${e.message})`\r\n }\r\n logger.info(message)\r\n return null\r\n }\r\n\r\n if (cachedInfo.fileName == null) {\r\n logger.warn(`Cached update info is corrupted: no fileName, directory for cached update will be cleaned`)\r\n await this.cleanCacheDir()\r\n return null\r\n }\r\n\r\n if (fileInfo.info.sha512 !== cachedInfo.sha512) {\r\n logger.info(`Cached update sha512 checksum doesn't match the latest available update. New update must be downloaded. Cached: ${cachedInfo.sha512}, expected: ${fileInfo.info.sha512}. Directory for cached update will be cleaned`)\r\n await this.cleanCacheDir()\r\n return null\r\n }\r\n\r\n const updateFile = path.join(this.cacheDir, cachedInfo.fileName)\r\n if (!(await pathExists(updateFile))) {\r\n logger.info(\"Cached update file doesn't exist, directory for cached update will be cleaned\")\r\n await this.cleanCacheDir()\r\n return null\r\n }\r\n\r\n const sha512 = await hashFile(updateFile)\r\n if (fileInfo.info.sha512 !== sha512) {\r\n logger.warn(`Sha512 checksum doesn't match the latest available update. New update must be downloaded. Cached: ${sha512}, expected: ${fileInfo.info.sha512}`)\r\n await this.cleanCacheDir()\r\n return null\r\n }\r\n return updateFile\r\n }\r\n}\r\n\r\ninterface CachedUpdateInfo {\r\n fileName: string\r\n sha512: string\r\n}\r\n\r\nfunction hashFile(file: string, algorithm: string = \"sha512\", encoding: \"base64\" | \"hex\" = \"base64\", options?: any) {\r\n return new Promise((resolve, reject) => {\r\n const hash = createHash(algorithm)\r\n hash\r\n .on(\"error\", reject)\r\n .setEncoding(encoding)\r\n\r\n createReadStream(file, {...options, highWaterMark: 1024 * 1024 /* better to use more memory but hash faster */})\r\n .on(\"error\", reject)\r\n .on(\"end\", () => {\r\n hash.end()\r\n resolve(hash.read() as string)\r\n })\r\n .pipe(hash, {end: false})\r\n })\r\n}"],"sourceRoot":""} diff --git a/out/MacUpdater.d.ts b/out/MacUpdater.d.ts new file mode 100644 index 0000000..742897c --- /dev/null +++ b/out/MacUpdater.d.ts @@ -0,0 +1,9 @@ +import { AllPublishOptions } from "builder-util-runtime"; +import { AppUpdater, DownloadUpdateOptions } from "./AppUpdater"; +export declare class MacUpdater extends AppUpdater { + private readonly nativeUpdater; + constructor(options?: AllPublishOptions); + protected doDownloadUpdate(downloadUpdateOptions: DownloadUpdateOptions): Promise>; + private doProxyUpdateFile; + quitAndInstall(): void; +} diff --git a/out/MacUpdater.js b/out/MacUpdater.js new file mode 100644 index 0000000..d3a75d3 --- /dev/null +++ b/out/MacUpdater.js @@ -0,0 +1,220 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.MacUpdater = void 0; + +function _builderUtilRuntime() { + const data = require("builder-util-runtime"); + + _builderUtilRuntime = function () { + return data; + }; + + return data; +} + +function _http() { + const data = require("http"); + + _http = function () { + return data; + }; + + return data; +} + +function _AppUpdater() { + const data = require("./AppUpdater"); + + _AppUpdater = function () { + return data; + }; + + return data; +} + +function _main() { + const data = require("./main"); + + _main = function () { + return data; + }; + + return data; +} + +function _Provider() { + const data = require("./providers/Provider"); + + _Provider = function () { + return data; + }; + + return data; +} + +function _fsExtraP() { + const data = require("fs-extra-p"); + + _fsExtraP = function () { + return data; + }; + + return data; +} + +class MacUpdater extends _AppUpdater().AppUpdater { + constructor(options) { + super(options); + this.nativeUpdater = require("electron").autoUpdater; + this.nativeUpdater.on("error", it => { + this._logger.warn(it); + + this.emit("error", it); + }); + } + + async doDownloadUpdate(downloadUpdateOptions) { + const files = (await this.provider).resolveFiles(downloadUpdateOptions.updateInfo); + const zipFileInfo = (0, _Provider().findFile)(files, "zip", ["pkg", "dmg"]); + + if (zipFileInfo == null) { + throw (0, _builderUtilRuntime().newError)(`ZIP file not provided: ${(0, _builderUtilRuntime().safeStringifyJson)(files)}`, "ERR_UPDATER_ZIP_FILE_NOT_FOUND"); + } + + const server = (0, _http().createServer)(); + server.on("close", () => { + this._logger.info(`Proxy server for native Squirrel.Mac is closed (was started to download ${zipFileInfo.url.href})`); + }); + + function getServerUrl() { + const address = server.address(); + return `http://${address.address}:${address.port}`; + } + + return await this.executeDownload({ + fileExtension: "zip", + fileInfo: zipFileInfo, + downloadUpdateOptions, + task: (destinationFile, downloadOptions) => { + return this.httpExecutor.download(zipFileInfo.url.href, destinationFile, downloadOptions); + }, + done: async updateFile => { + let updateFileSize = zipFileInfo.info.size; + + if (updateFileSize == null) { + updateFileSize = (await (0, _fsExtraP().stat)(updateFile)).size; + } + + return await new Promise((resolve, reject) => { + server.on("request", (request, response) => { + const requestUrl = request.url; + + this._logger.info(`${requestUrl} requested`); + + if (requestUrl === "/") { + const data = Buffer.from(`{ "url": "${getServerUrl()}/app.zip" }`); + response.writeHead(200, { + "Content-Type": "application/json", + "Content-Length": data.length + }); + response.end(data); + } else if (requestUrl.startsWith("/app.zip")) { + let errorOccurred = false; + response.on("finish", () => { + try { + setImmediate(() => server.close()); + } finally { + if (!errorOccurred) { + this.nativeUpdater.removeListener("error", reject); + resolve([]); + } + } + }); + + this._logger.info(`app.zip requested by Squirrel.Mac, pipe ${updateFile}`); + + const readStream = (0, _fsExtraP().createReadStream)(updateFile); + readStream.on("error", error => { + try { + response.end(); + } catch (e) { + errorOccurred = true; + this.nativeUpdater.removeListener("error", reject); + reject(new Error(`Cannot pipe "${updateFile}": ${error}`)); + } + }); + response.writeHead(200, { + "Content-Type": "application/zip", + "Content-Length": updateFileSize + }); + readStream.pipe(response); + } else { + this._logger.warn(`${requestUrl} requested, but not supported`); + + response.writeHead(404); + response.end(); + } + }); + server.listen(0, "127.0.0.1", 8, () => { + this.nativeUpdater.setFeedURL(`${getServerUrl()}`, { + "Cache-Control": "no-cache" + }); + this.nativeUpdater.once("error", reject); + this.nativeUpdater.checkForUpdates(); + }); + }); + } + }); + } + + doProxyUpdateFile(nativeResponse, url, headers, sha512, cancellationToken, errorHandler) { + const downloadRequest = this.httpExecutor.doRequest((0, _builderUtilRuntime().configureRequestOptionsFromUrl)(url, { + headers + }), downloadResponse => { + const nativeHeaders = { + "Content-Type": "application/zip" + }; + const streams = []; + const downloadListenerCount = this.listenerCount(_main().DOWNLOAD_PROGRESS); + + this._logger.info(`${_main().DOWNLOAD_PROGRESS} listener count: ${downloadListenerCount}`); + + nativeResponse.writeHead(200, nativeHeaders); // for mac only sha512 is produced (sha256 is published for windows only to preserve backward compatibility) + + if (sha512 != null) { + // "hex" to easy migrate to new base64 encoded hash (we already produces latest-mac.yml with hex encoded hash) + streams.push(new (_builderUtilRuntime().DigestTransform)(sha512, "sha512", sha512.length === 128 && !sha512.includes("+") && !sha512.includes("Z") && !sha512.includes("=") ? "hex" : "base64")); + } + + streams.push(nativeResponse); + let lastStream = downloadResponse; + + for (const stream of streams) { + stream.on("error", errorHandler); + lastStream = lastStream.pipe(stream); + } + }); + downloadRequest.on("redirect", (statusCode, method, redirectUrl) => { + if (headers.authorization != null && headers.authorization.startsWith("token")) { + const parsedNewUrl = new URL(redirectUrl); + + if (parsedNewUrl.hostname.endsWith(".amazonaws.com")) { + delete headers.authorization; + } + } + + this.doProxyUpdateFile(nativeResponse, redirectUrl, headers, sha512, cancellationToken, errorHandler); + }); + downloadRequest.on("error", errorHandler); + downloadRequest.end(); + } + + quitAndInstall() { + this.nativeUpdater.quitAndInstall(); + } + +} exports.MacUpdater = MacUpdater; +//# sourceMappingURL=MacUpdater.js.map \ No newline at end of file diff --git a/out/MacUpdater.js.map b/out/MacUpdater.js.map new file mode 100644 index 0000000..d822ac1 --- /dev/null +++ b/out/MacUpdater.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/MacUpdater.ts"],"names":[],"mappings":"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","sourcesContent":["import { AllPublishOptions, CancellationToken, configureRequestOptionsFromUrl, DigestTransform, newError, RequestHeaders, safeStringifyJson } from \"builder-util-runtime\"\r\nimport { createServer, IncomingMessage, OutgoingHttpHeaders, ServerResponse } from \"http\"\r\nimport { AddressInfo } from \"net\"\r\nimport { AppUpdater, DownloadUpdateOptions } from \"./AppUpdater\"\r\nimport { DOWNLOAD_PROGRESS } from \"./main\"\r\nimport { findFile } from \"./providers/Provider\"\r\nimport { createReadStream, stat } from \"fs-extra-p\"\r\nimport AutoUpdater = Electron.AutoUpdater\r\n\r\nexport class MacUpdater extends AppUpdater {\r\n private readonly nativeUpdater: AutoUpdater = require(\"electron\").autoUpdater\r\n\r\n constructor(options?: AllPublishOptions) {\r\n super(options)\r\n\r\n this.nativeUpdater.on(\"error\", it => {\r\n this._logger.warn(it)\r\n this.emit(\"error\", it)\r\n })\r\n }\r\n\r\n protected async doDownloadUpdate(downloadUpdateOptions: DownloadUpdateOptions): Promise> {\r\n const files = (await this.provider).resolveFiles(downloadUpdateOptions.updateInfo)\r\n const zipFileInfo = findFile(files, \"zip\", [\"pkg\", \"dmg\"])\r\n if (zipFileInfo == null) {\r\n throw newError(`ZIP file not provided: ${safeStringifyJson(files)}`, \"ERR_UPDATER_ZIP_FILE_NOT_FOUND\")\r\n }\r\n\r\n const server = createServer()\r\n server.on(\"close\", () => {\r\n this._logger.info(`Proxy server for native Squirrel.Mac is closed (was started to download ${zipFileInfo.url.href})`)\r\n })\r\n\r\n function getServerUrl() {\r\n const address = server.address() as AddressInfo\r\n return `http://${address.address}:${address.port}`\r\n }\r\n\r\n return await this.executeDownload({\r\n fileExtension: \"zip\",\r\n fileInfo: zipFileInfo,\r\n downloadUpdateOptions,\r\n task: (destinationFile, downloadOptions) => {\r\n return this.httpExecutor.download(zipFileInfo.url.href, destinationFile, downloadOptions)\r\n },\r\n done: async updateFile => {\r\n let updateFileSize = zipFileInfo.info.size\r\n if (updateFileSize == null) {\r\n updateFileSize = (await stat(updateFile)).size\r\n }\r\n\r\n return await new Promise>((resolve, reject) => {\r\n server.on(\"request\", (request: IncomingMessage, response: ServerResponse) => {\r\n const requestUrl = request.url!!\r\n this._logger.info(`${requestUrl} requested`)\r\n if (requestUrl === \"/\") {\r\n const data = Buffer.from(`{ \"url\": \"${getServerUrl()}/app.zip\" }`)\r\n response.writeHead(200, {\"Content-Type\": \"application/json\", \"Content-Length\": data.length})\r\n response.end(data)\r\n }\r\n else if (requestUrl.startsWith(\"/app.zip\")) {\r\n let errorOccurred = false\r\n response.on(\"finish\", () => {\r\n try {\r\n setImmediate(() => server.close())\r\n }\r\n finally {\r\n if (!errorOccurred) {\r\n this.nativeUpdater.removeListener(\"error\", reject)\r\n resolve([])\r\n }\r\n }\r\n })\r\n\r\n this._logger.info(`app.zip requested by Squirrel.Mac, pipe ${updateFile}`)\r\n\r\n const readStream = createReadStream(updateFile)\r\n readStream.on(\"error\", error => {\r\n try {\r\n response.end()\r\n }\r\n catch (e) {\r\n errorOccurred = true\r\n this.nativeUpdater.removeListener(\"error\", reject)\r\n reject(new Error(`Cannot pipe \"${updateFile}\": ${error}`))\r\n }\r\n })\r\n\r\n response.writeHead(200, {\r\n \"Content-Type\": \"application/zip\",\r\n \"Content-Length\": updateFileSize,\r\n })\r\n readStream.pipe(response)\r\n }\r\n else {\r\n this._logger.warn(`${requestUrl} requested, but not supported`)\r\n response.writeHead(404)\r\n response.end()\r\n }\r\n })\r\n server.listen(0, \"127.0.0.1\", 8, () => {\r\n this.nativeUpdater.setFeedURL(`${getServerUrl()}`, {\"Cache-Control\": \"no-cache\"})\r\n\r\n this.nativeUpdater.once(\"error\", reject)\r\n this.nativeUpdater.checkForUpdates()\r\n })\r\n })\r\n }\r\n })\r\n }\r\n\r\n private doProxyUpdateFile(nativeResponse: ServerResponse, url: string, headers: OutgoingHttpHeaders, sha512: string | null, cancellationToken: CancellationToken, errorHandler: (error: Error) => void) {\r\n const downloadRequest = this.httpExecutor.doRequest(configureRequestOptionsFromUrl(url, {headers}), downloadResponse => {\r\n const nativeHeaders: RequestHeaders = {\"Content-Type\": \"application/zip\"}\r\n const streams: Array = []\r\n const downloadListenerCount = this.listenerCount(DOWNLOAD_PROGRESS)\r\n this._logger.info(`${DOWNLOAD_PROGRESS} listener count: ${downloadListenerCount}`)\r\n nativeResponse.writeHead(200, nativeHeaders)\r\n\r\n // for mac only sha512 is produced (sha256 is published for windows only to preserve backward compatibility)\r\n if (sha512 != null) {\r\n // \"hex\" to easy migrate to new base64 encoded hash (we already produces latest-mac.yml with hex encoded hash)\r\n streams.push(new DigestTransform(sha512, \"sha512\", sha512.length === 128 && !sha512.includes(\"+\") && !sha512.includes(\"Z\") && !sha512.includes(\"=\") ? \"hex\" : \"base64\"))\r\n }\r\n\r\n streams.push(nativeResponse)\r\n\r\n let lastStream = downloadResponse\r\n for (const stream of streams) {\r\n stream.on(\"error\", errorHandler)\r\n lastStream = lastStream.pipe(stream)\r\n }\r\n })\r\n\r\n downloadRequest.on(\"redirect\", (statusCode: number, method: string, redirectUrl: string) => {\r\n if (headers.authorization != null && (headers!!.authorization as string).startsWith(\"token\")) {\r\n const parsedNewUrl = new URL(redirectUrl)\r\n if (parsedNewUrl.hostname.endsWith(\".amazonaws.com\")) {\r\n delete headers.authorization\r\n }\r\n }\r\n this.doProxyUpdateFile(nativeResponse, redirectUrl, headers, sha512, cancellationToken, errorHandler)\r\n })\r\n downloadRequest.on(\"error\", errorHandler)\r\n downloadRequest.end()\r\n }\r\n\r\n quitAndInstall(): void {\r\n this.nativeUpdater.quitAndInstall()\r\n }\r\n}"],"sourceRoot":""} diff --git a/out/NsisUpdater.d.ts b/out/NsisUpdater.d.ts new file mode 100644 index 0000000..74054ae --- /dev/null +++ b/out/NsisUpdater.d.ts @@ -0,0 +1,13 @@ +import { AllPublishOptions } from "builder-util-runtime"; +import "source-map-support/register"; +import { DownloadUpdateOptions } from "./AppUpdater"; +import { BaseUpdater } from "./BaseUpdater"; +export declare class NsisUpdater extends BaseUpdater { + constructor(options?: AllPublishOptions | null, app?: any); + /*** @private */ + protected doDownloadUpdate(downloadUpdateOptions: DownloadUpdateOptions): Promise>; + private verifySignature; + protected doInstall(installerPath: string, isSilent: boolean, isForceRunAfter: boolean): boolean; + private differentialDownloadInstaller; + private differentialDownloadWebPackage; +} diff --git a/out/NsisUpdater.js b/out/NsisUpdater.js new file mode 100644 index 0000000..e6cd6c9 --- /dev/null +++ b/out/NsisUpdater.js @@ -0,0 +1,298 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.NsisUpdater = void 0; + +function _builderUtilRuntime() { + const data = require("builder-util-runtime"); + + _builderUtilRuntime = function () { + return data; + }; + + return data; +} + +function _child_process() { + const data = require("child_process"); + + _child_process = function () { + return data; + }; + + return data; +} + +var path = _interopRequireWildcard(require("path")); + +require("source-map-support/register"); + +function _BaseUpdater() { + const data = require("./BaseUpdater"); + + _BaseUpdater = function () { + return data; + }; + + return data; +} + +function _FileWithEmbeddedBlockMapDifferentialDownloader() { + const data = require("./differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader"); + + _FileWithEmbeddedBlockMapDifferentialDownloader = function () { + return data; + }; + + return data; +} + +function _GenericDifferentialDownloader() { + const data = require("./differentialDownloader/GenericDifferentialDownloader"); + + _GenericDifferentialDownloader = function () { + return data; + }; + + return data; +} + +function _main() { + const data = require("./main"); + + _main = function () { + return data; + }; + + return data; +} + +function _Provider() { + const data = require("./providers/Provider"); + + _Provider = function () { + return data; + }; + + return data; +} + +function _fsExtraP() { + const data = require("fs-extra-p"); + + _fsExtraP = function () { + return data; + }; + + return data; +} + +function _windowsExecutableCodeSignatureVerifier() { + const data = require("./windowsExecutableCodeSignatureVerifier"); + + _windowsExecutableCodeSignatureVerifier = function () { + return data; + }; + + return data; +} + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +class NsisUpdater extends _BaseUpdater().BaseUpdater { + constructor(options, app) { + super(options, app); + } + /*** @private */ + + + async doDownloadUpdate(downloadUpdateOptions) { + const provider = await this.provider; + const fileInfo = (0, _Provider().findFile)(provider.resolveFiles(downloadUpdateOptions.updateInfo), "exe"); + return await this.executeDownload({ + fileExtension: "exe", + downloadUpdateOptions, + fileInfo, + task: async (destinationFile, downloadOptions, packageFile, removeTempDirIfAny) => { + const packageInfo = fileInfo.packageInfo; + const isWebInstaller = packageInfo != null && packageFile != null; + + if (isWebInstaller || (await this.differentialDownloadInstaller(fileInfo, downloadUpdateOptions, destinationFile, downloadUpdateOptions.requestHeaders, provider))) { + await this.httpExecutor.download(fileInfo.url.href, destinationFile, downloadOptions); + } + + const signatureVerificationStatus = await this.verifySignature(destinationFile); + + if (signatureVerificationStatus != null) { + await removeTempDirIfAny(); // noinspection ThrowInsideFinallyBlockJS + + throw (0, _builderUtilRuntime().newError)(`New version ${downloadUpdateOptions.updateInfo.version} is not signed by the application owner: ${signatureVerificationStatus}`, "ERR_UPDATER_INVALID_SIGNATURE"); + } + + if (isWebInstaller) { + if (await this.differentialDownloadWebPackage(packageInfo, packageFile, provider)) { + try { + await this.httpExecutor.download(packageInfo.path, packageFile, { + skipDirCreation: true, + headers: downloadUpdateOptions.requestHeaders, + cancellationToken: downloadUpdateOptions.cancellationToken, + sha512: packageInfo.sha512 + }); + } catch (e) { + try { + await (0, _fsExtraP().unlink)(packageFile); + } catch (ignored) {// ignore + } + + throw e; + } + } + } + } + }); + } // $certificateInfo = (Get-AuthenticodeSignature 'xxx\yyy.exe' + // | where {$_.Status.Equals([System.Management.Automation.SignatureStatus]::Valid) -and $_.SignerCertificate.Subject.Contains("CN=siemens.com")}) + // | Out-String ; if ($certificateInfo) { exit 0 } else { exit 1 } + + + async verifySignature(tempUpdateFile) { + let publisherName; + + try { + publisherName = (await this.configOnDisk.value).publisherName; + + if (publisherName == null) { + return null; + } + } catch (e) { + if (e.code === "ENOENT") { + // no app-update.yml + return null; + } + + throw e; + } + + return await (0, _windowsExecutableCodeSignatureVerifier().verifySignature)(Array.isArray(publisherName) ? publisherName : [publisherName], tempUpdateFile, this._logger); + } + + doInstall(installerPath, isSilent, isForceRunAfter) { + const args = ["--updated"]; + + if (isSilent) { + args.push("/S"); + } + + if (isForceRunAfter) { + args.push("--force-run"); + } + + const packagePath = this.downloadedUpdateHelper.packageFile; + + if (packagePath != null) { + // only = form is supported + args.push(`--package-file="${packagePath}"`); + } + + const spawnOptions = { + detached: true, + stdio: "ignore" + }; + + try { + (0, _child_process().spawn)(installerPath, args, spawnOptions).unref(); + } catch (e) { + // yes, such errors dispatched not as error event + // https://github.com/electron-userland/electron-builder/issues/1129 + if (e.code === "UNKNOWN" || e.code === "EACCES") { + // Node 8 sends errors: https://nodejs.org/dist/latest-v8.x/docs/api/errors.html#errors_common_system_errors + this._logger.info("Access denied or UNKNOWN error code on spawn, will be executed again using elevate"); + + try { + (0, _child_process().spawn)(path.join(process.resourcesPath, "elevate.exe"), [installerPath].concat(args), spawnOptions).unref(); + } catch (e) { + this.dispatchError(e); + } + } else { + this.dispatchError(e); + } + } + + return true; + } // private downloadBlockMap(provider: Provider) { + // await provider.getBytes(newBlockMapUrl, cancellationToken) + // } + + + async differentialDownloadInstaller(fileInfo, downloadUpdateOptions, installerPath, requestHeaders, provider) { + try { + const newBlockMapUrl = (0, _main().newUrlFromBase)(`${fileInfo.url.pathname}.blockmap`, fileInfo.url); + const oldBlockMapUrl = (0, _main().newUrlFromBase)(`${fileInfo.url.pathname.replace(new RegExp(downloadUpdateOptions.updateInfo.version, "g"), this.currentVersion.version)}.blockmap`, fileInfo.url); + + this._logger.info(`Download block maps (old: "${oldBlockMapUrl.href}", new: ${newBlockMapUrl.href})`); + + const downloadBlockMap = async url => { + const requestOptions = (0, _Provider().configureRequestOptionsFromUrl)(url, { + headers: downloadUpdateOptions.requestHeaders + }); + requestOptions.gzip = true; + const data = await this.httpExecutor.request(requestOptions, downloadUpdateOptions.cancellationToken); + + if (data == null) { + throw new Error(`Blockmap "${url.href}" is empty`); + } + + try { + return JSON.parse(data); + } catch (e) { + throw new Error(`Cannot parse blockmap "${url.href}", error: ${e}, raw data: ${data}`); + } + }; + + const blockMapData = await downloadBlockMap(newBlockMapUrl); + const oldBlockMapData = await downloadBlockMap(oldBlockMapUrl); + await new (_GenericDifferentialDownloader().GenericDifferentialDownloader)(fileInfo.info, this.httpExecutor, { + newUrl: fileInfo.url.href, + oldFile: path.join(this.app.getPath("userData"), _builderUtilRuntime().CURRENT_APP_INSTALLER_FILE_NAME), + logger: this._logger, + newFile: installerPath, + useMultipleRangeRequest: provider.useMultipleRangeRequest, + requestHeaders + }).download(oldBlockMapData, blockMapData); + } catch (e) { + this._logger.error(`Cannot download differentially, fallback to full download: ${e.stack || e}`); + + return true; + } + + return false; + } + + async differentialDownloadWebPackage(packageInfo, packagePath, provider) { + if (packageInfo.blockMapSize == null) { + return true; + } + + try { + await new (_FileWithEmbeddedBlockMapDifferentialDownloader().FileWithEmbeddedBlockMapDifferentialDownloader)(packageInfo, this.httpExecutor, { + newUrl: packageInfo.path, + oldFile: path.join(this.app.getPath("userData"), _builderUtilRuntime().CURRENT_APP_PACKAGE_FILE_NAME), + logger: this._logger, + newFile: packagePath, + requestHeaders: this.requestHeaders, + useMultipleRangeRequest: provider.useMultipleRangeRequest + }).download(); + } catch (e) { + this._logger.error(`Cannot download differentially, fallback to full download: ${e.stack || e}`); // during test (developer machine mac or linux) we must throw error + + + return process.platform === "win32"; + } + + return false; + } + +} exports.NsisUpdater = NsisUpdater; +//# sourceMappingURL=NsisUpdater.js.map \ No newline at end of file diff --git a/out/NsisUpdater.js.map b/out/NsisUpdater.js.map new file mode 100644 index 0000000..6db4eb7 --- /dev/null +++ b/out/NsisUpdater.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/NsisUpdater.ts"],"names":[],"mappings":"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","sourcesContent":["import { AllPublishOptions, newError, PackageFileInfo, BlockMap, CURRENT_APP_PACKAGE_FILE_NAME, CURRENT_APP_INSTALLER_FILE_NAME } from \"builder-util-runtime\"\r\nimport { spawn } from \"child_process\"\r\nimport { OutgoingHttpHeaders } from \"http\"\r\nimport * as path from \"path\"\r\nimport \"source-map-support/register\"\r\nimport { DownloadUpdateOptions } from \"./AppUpdater\"\r\nimport { BaseUpdater } from \"./BaseUpdater\"\r\nimport { FileWithEmbeddedBlockMapDifferentialDownloader } from \"./differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader\"\r\nimport { GenericDifferentialDownloader } from \"./differentialDownloader/GenericDifferentialDownloader\"\r\nimport { newUrlFromBase, ResolvedUpdateFileInfo } from \"./main\"\r\nimport { configureRequestOptionsFromUrl, findFile, Provider } from \"./providers/Provider\"\r\nimport { unlink } from \"fs-extra-p\"\r\nimport { verifySignature } from \"./windowsExecutableCodeSignatureVerifier\"\r\nimport { URL } from \"url\"\r\n\r\nexport class NsisUpdater extends BaseUpdater {\r\n constructor(options?: AllPublishOptions | null, app?: any) {\r\n super(options, app)\r\n }\r\n\r\n /*** @private */\r\n protected async doDownloadUpdate(downloadUpdateOptions: DownloadUpdateOptions): Promise> {\r\n const provider = await this.provider\r\n const fileInfo = findFile(provider.resolveFiles(downloadUpdateOptions.updateInfo), \"exe\")!!\r\n return await this.executeDownload({\r\n fileExtension: \"exe\",\r\n downloadUpdateOptions,\r\n fileInfo,\r\n task: async (destinationFile, downloadOptions, packageFile, removeTempDirIfAny) => {\r\n const packageInfo = fileInfo.packageInfo\r\n const isWebInstaller = packageInfo != null && packageFile != null\r\n if (isWebInstaller || await this.differentialDownloadInstaller(fileInfo, downloadUpdateOptions, destinationFile, downloadUpdateOptions.requestHeaders, provider)) {\r\n await this.httpExecutor.download(fileInfo.url.href, destinationFile, downloadOptions)\r\n }\r\n\r\n const signatureVerificationStatus = await this.verifySignature(destinationFile)\r\n if (signatureVerificationStatus != null) {\r\n await removeTempDirIfAny()\r\n // noinspection ThrowInsideFinallyBlockJS\r\n throw newError(`New version ${downloadUpdateOptions.updateInfo!.version} is not signed by the application owner: ${signatureVerificationStatus}`, \"ERR_UPDATER_INVALID_SIGNATURE\")\r\n }\r\n\r\n if (isWebInstaller) {\r\n if (await this.differentialDownloadWebPackage(packageInfo!!, packageFile!!, provider)) {\r\n try {\r\n await this.httpExecutor.download(packageInfo!!.path, packageFile!!, {\r\n skipDirCreation: true,\r\n headers: downloadUpdateOptions.requestHeaders,\r\n cancellationToken: downloadUpdateOptions.cancellationToken,\r\n sha512: packageInfo!!.sha512,\r\n })\r\n }\r\n catch (e) {\r\n try {\r\n await unlink(packageFile!!)\r\n }\r\n catch (ignored) {\r\n // ignore\r\n }\r\n\r\n throw e\r\n }\r\n }\r\n }\r\n },\r\n })\r\n }\r\n\r\n // $certificateInfo = (Get-AuthenticodeSignature 'xxx\\yyy.exe'\r\n // | where {$_.Status.Equals([System.Management.Automation.SignatureStatus]::Valid) -and $_.SignerCertificate.Subject.Contains(\"CN=siemens.com\")})\r\n // | Out-String ; if ($certificateInfo) { exit 0 } else { exit 1 }\r\n private async verifySignature(tempUpdateFile: string): Promise {\r\n let publisherName: Array | string | null\r\n try {\r\n publisherName = (await this.configOnDisk.value).publisherName\r\n if (publisherName == null) {\r\n return null\r\n }\r\n }\r\n catch (e) {\r\n if (e.code === \"ENOENT\") {\r\n // no app-update.yml\r\n return null\r\n }\r\n throw e\r\n }\r\n return await verifySignature(Array.isArray(publisherName) ? publisherName : [publisherName], tempUpdateFile, this._logger)\r\n }\r\n\r\n protected doInstall(installerPath: string, isSilent: boolean, isForceRunAfter: boolean): boolean {\r\n const args = [\"--updated\"]\r\n if (isSilent) {\r\n args.push(\"/S\")\r\n }\r\n\r\n if (isForceRunAfter) {\r\n args.push(\"--force-run\")\r\n }\r\n\r\n const packagePath = this.downloadedUpdateHelper.packageFile\r\n if (packagePath != null) {\r\n // only = form is supported\r\n args.push(`--package-file=\"${packagePath}\"`)\r\n }\r\n\r\n const spawnOptions: any = {\r\n detached: true,\r\n stdio: \"ignore\",\r\n }\r\n\r\n try {\r\n spawn(installerPath, args, spawnOptions)\r\n .unref()\r\n }\r\n catch (e) {\r\n // yes, such errors dispatched not as error event\r\n // https://github.com/electron-userland/electron-builder/issues/1129\r\n if ((e as any).code === \"UNKNOWN\" || (e as any).code === \"EACCES\") { // Node 8 sends errors: https://nodejs.org/dist/latest-v8.x/docs/api/errors.html#errors_common_system_errors\r\n this._logger.info(\"Access denied or UNKNOWN error code on spawn, will be executed again using elevate\")\r\n try {\r\n spawn(path.join(process.resourcesPath!, \"elevate.exe\"), [installerPath].concat(args), spawnOptions)\r\n .unref()\r\n }\r\n catch (e) {\r\n this.dispatchError(e)\r\n }\r\n }\r\n else {\r\n this.dispatchError(e)\r\n }\r\n }\r\n\r\n return true\r\n }\r\n\r\n // private downloadBlockMap(provider: Provider) {\r\n // await provider.getBytes(newBlockMapUrl, cancellationToken)\r\n // }\r\n\r\n private async differentialDownloadInstaller(fileInfo: ResolvedUpdateFileInfo, downloadUpdateOptions: DownloadUpdateOptions, installerPath: string, requestHeaders: OutgoingHttpHeaders, provider: Provider) {\r\n try {\r\n const newBlockMapUrl = newUrlFromBase(`${fileInfo.url.pathname}.blockmap`, fileInfo.url)\r\n const oldBlockMapUrl = newUrlFromBase(`${fileInfo.url.pathname.replace(new RegExp(downloadUpdateOptions.updateInfo.version, \"g\"), this.currentVersion.version)}.blockmap`, fileInfo.url)\r\n this._logger.info(`Download block maps (old: \"${oldBlockMapUrl.href}\", new: ${newBlockMapUrl.href})`)\r\n\r\n const downloadBlockMap = async (url: URL): Promise => {\r\n const requestOptions = configureRequestOptionsFromUrl(url, {headers: downloadUpdateOptions.requestHeaders});\r\n (requestOptions as any).gzip = true\r\n const data = await this.httpExecutor.request(requestOptions, downloadUpdateOptions.cancellationToken)\r\n if (data == null) {\r\n throw new Error(`Blockmap \"${url.href}\" is empty`)\r\n }\r\n\r\n try {\r\n return JSON.parse(data)\r\n }\r\n catch (e) {\r\n throw new Error(`Cannot parse blockmap \"${url.href}\", error: ${e}, raw data: ${data}`)\r\n }\r\n }\r\n\r\n const blockMapData = await downloadBlockMap(newBlockMapUrl)\r\n const oldBlockMapData = await downloadBlockMap(oldBlockMapUrl)\r\n await new GenericDifferentialDownloader(fileInfo.info, this.httpExecutor, {\r\n newUrl: fileInfo.url.href,\r\n oldFile: path.join(this.app.getPath(\"userData\"), CURRENT_APP_INSTALLER_FILE_NAME),\r\n logger: this._logger,\r\n newFile: installerPath,\r\n useMultipleRangeRequest: provider.useMultipleRangeRequest,\r\n requestHeaders,\r\n })\r\n .download(oldBlockMapData, blockMapData)\r\n }\r\n catch (e) {\r\n this._logger.error(`Cannot download differentially, fallback to full download: ${e.stack || e}`)\r\n return true\r\n }\r\n\r\n return false\r\n }\r\n\r\n private async differentialDownloadWebPackage(packageInfo: PackageFileInfo, packagePath: string, provider: Provider): Promise {\r\n if (packageInfo.blockMapSize == null) {\r\n return true\r\n }\r\n\r\n try {\r\n await new FileWithEmbeddedBlockMapDifferentialDownloader(packageInfo, this.httpExecutor, {\r\n newUrl: packageInfo.path,\r\n oldFile: path.join(this.app.getPath(\"userData\"), CURRENT_APP_PACKAGE_FILE_NAME),\r\n logger: this._logger,\r\n newFile: packagePath,\r\n requestHeaders: this.requestHeaders,\r\n useMultipleRangeRequest: provider.useMultipleRangeRequest,\r\n })\r\n .download()\r\n }\r\n catch (e) {\r\n this._logger.error(`Cannot download differentially, fallback to full download: ${e.stack || e}`)\r\n // during test (developer machine mac or linux) we must throw error\r\n return process.platform === \"win32\"\r\n }\r\n return false\r\n }\r\n}\r\n"],"sourceRoot":""} diff --git a/out/differentialDownloader/DataSplitter.d.ts b/out/differentialDownloader/DataSplitter.d.ts new file mode 100644 index 0000000..d05452e --- /dev/null +++ b/out/differentialDownloader/DataSplitter.d.ts @@ -0,0 +1,33 @@ +/// +import { Writable } from "stream"; +import { Operation } from "./downloadPlanBuilder"; +export interface PartListDataTask { + readonly oldFileFd: number; + readonly tasks: Array; + readonly start: number; + readonly end: number; +} +export declare function copyData(task: Operation, out: Writable, oldFileFd: number, reject: (error: Error) => void, resolve: () => void): void; +export declare class DataSplitter extends Writable { + private readonly out; + private readonly options; + private readonly partIndexToTaskIndex; + private readonly partIndexToLength; + private readonly finishHandler; + partIndex: number; + private headerListBuffer; + private readState; + private ignoreByteCount; + private remainingPartDataCount; + private readonly boundaryLength; + constructor(out: Writable, options: PartListDataTask, partIndexToTaskIndex: Map, boundary: string, partIndexToLength: Array, finishHandler: () => any); + readonly isFinished: boolean; + _write(data: Buffer, encoding: string, callback: (error?: Error) => void): void; + private handleData; + private copyExistingData; + private searchHeaderListEnd; + private actualPartLength; + private onPartEnd; + private processPartStarted; + private processPartData; +} diff --git a/out/differentialDownloader/DataSplitter.js b/out/differentialDownloader/DataSplitter.js new file mode 100644 index 0000000..cc48ea1 --- /dev/null +++ b/out/differentialDownloader/DataSplitter.js @@ -0,0 +1,281 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.copyData = copyData; +exports.DataSplitter = void 0; + +function _builderUtilRuntime() { + const data = require("builder-util-runtime"); + + _builderUtilRuntime = function () { + return data; + }; + + return data; +} + +function _fsExtraP() { + const data = require("fs-extra-p"); + + _fsExtraP = function () { + return data; + }; + + return data; +} + +function _stream() { + const data = require("stream"); + + _stream = function () { + return data; + }; + + return data; +} + +function _downloadPlanBuilder() { + const data = require("./downloadPlanBuilder"); + + _downloadPlanBuilder = function () { + return data; + }; + + return data; +} + +const DOUBLE_CRLF = Buffer.from("\r\n\r\n"); +var ReadState; + +(function (ReadState) { + ReadState[ReadState["INIT"] = 0] = "INIT"; + ReadState[ReadState["HEADER"] = 1] = "HEADER"; + ReadState[ReadState["BODY"] = 2] = "BODY"; +})(ReadState || (ReadState = {})); + +function copyData(task, out, oldFileFd, reject, resolve) { + const readStream = (0, _fsExtraP().createReadStream)("", { + fd: oldFileFd, + autoClose: false, + start: task.start, + // end is inclusive + end: task.end - 1 + }); + readStream.on("error", reject); + readStream.once("end", resolve); + readStream.pipe(out, { + end: false + }); +} + +class DataSplitter extends _stream().Writable { + constructor(out, options, partIndexToTaskIndex, boundary, partIndexToLength, finishHandler) { + super(); + this.out = out; + this.options = options; + this.partIndexToTaskIndex = partIndexToTaskIndex; + this.partIndexToLength = partIndexToLength; + this.finishHandler = finishHandler; + this.partIndex = -1; + this.headerListBuffer = null; + this.readState = ReadState.INIT; + this.ignoreByteCount = 0; + this.remainingPartDataCount = 0; + this.actualPartLength = 0; + this.boundaryLength = boundary.length + 4; + /* size of \r\n-- */ + // first chunk doesn't start with \r\n + + this.ignoreByteCount = this.boundaryLength - 2; + } + + get isFinished() { + return this.partIndex === this.partIndexToLength.length; + } // noinspection JSUnusedGlobalSymbols + + + _write(data, encoding, callback) { + if (this.isFinished) { + console.error(`Trailing ignored data: ${data.length} bytes`); + return; + } + + this.handleData(data).then(callback).catch(callback); + } + + async handleData(chunk) { + let start = 0; + + if (this.ignoreByteCount !== 0 && this.remainingPartDataCount !== 0) { + throw (0, _builderUtilRuntime().newError)("Internal error", "ERR_DATA_SPLITTER_BYTE_COUNT_MISMATCH"); + } + + if (this.ignoreByteCount > 0) { + const toIgnore = Math.min(this.ignoreByteCount, chunk.length); + this.ignoreByteCount -= toIgnore; + start = toIgnore; + } else if (this.remainingPartDataCount > 0) { + const toRead = Math.min(this.remainingPartDataCount, chunk.length); + this.remainingPartDataCount -= toRead; + await this.processPartData(chunk, 0, toRead); + start = toRead; + } + + if (start === chunk.length) { + return; + } + + if (this.readState === ReadState.HEADER) { + const headerListEnd = this.searchHeaderListEnd(chunk, start); + + if (headerListEnd === -1) { + return; + } + + start = headerListEnd; + this.readState = ReadState.BODY; // header list is ignored, we don't need it + + this.headerListBuffer = null; + } + + while (true) { + if (this.readState === ReadState.BODY) { + this.readState = ReadState.INIT; + } else { + this.partIndex++; + let taskIndex = this.partIndexToTaskIndex.get(this.partIndex); + + if (taskIndex == null) { + if (this.isFinished) { + taskIndex = this.options.end; + } else { + throw (0, _builderUtilRuntime().newError)("taskIndex is null", "ERR_DATA_SPLITTER_TASK_INDEX_IS_NULL"); + } + } + + const prevTaskIndex = this.partIndex === 0 ? this.options.start : this.partIndexToTaskIndex.get(this.partIndex - 1) + 1 + /* prev part is download, next maybe copy */ + ; + + if (prevTaskIndex < taskIndex) { + await this.copyExistingData(prevTaskIndex, taskIndex); + } else if (prevTaskIndex > taskIndex) { + throw (0, _builderUtilRuntime().newError)("prevTaskIndex must be < taskIndex", "ERR_DATA_SPLITTER_TASK_INDEX_ASSERT_FAILED"); + } + + if (this.isFinished) { + this.onPartEnd(); + this.finishHandler(); + return; + } + + start = this.searchHeaderListEnd(chunk, start); + + if (start === -1) { + this.readState = ReadState.HEADER; + return; + } + } + + const partLength = this.partIndexToLength[this.partIndex]; + const end = start + partLength; + const effectiveEnd = Math.min(end, chunk.length); + await this.processPartStarted(chunk, start, effectiveEnd); + this.remainingPartDataCount = partLength - (effectiveEnd - start); + + if (this.remainingPartDataCount > 0) { + return; + } + + start = end + this.boundaryLength; + + if (start >= chunk.length) { + this.ignoreByteCount = this.boundaryLength - (chunk.length - end); + return; + } + } + } + + copyExistingData(index, end) { + return new Promise((resolve, reject) => { + const w = () => { + if (index === end) { + resolve(); + return; + } + + const task = this.options.tasks[index]; + + if (task.kind !== _downloadPlanBuilder().OperationKind.COPY) { + reject(new Error("Task kind must be COPY")); + return; + } + + copyData(task, this.out, this.options.oldFileFd, reject, () => { + index++; + w(); + }); + }; + + w(); + }); + } + + searchHeaderListEnd(chunk, readOffset) { + const headerListEnd = chunk.indexOf(DOUBLE_CRLF, readOffset); + + if (headerListEnd !== -1) { + return headerListEnd + DOUBLE_CRLF.length; + } // not all headers data were received, save to buffer + + + const partialChunk = readOffset === 0 ? chunk : chunk.slice(readOffset); + + if (this.headerListBuffer == null) { + this.headerListBuffer = partialChunk; + } else { + this.headerListBuffer = Buffer.concat([this.headerListBuffer, partialChunk]); + } + + return -1; + } + + onPartEnd() { + const expectedLength = this.partIndexToLength[this.partIndex - 1]; + + if (this.actualPartLength !== expectedLength) { + throw (0, _builderUtilRuntime().newError)(`Expected length: ${expectedLength} differs from actual: ${this.actualPartLength}`, "ERR_DATA_SPLITTER_LENGTH_MISMATCH"); + } + + this.actualPartLength = 0; + } + + processPartStarted(data, start, end) { + if (this.partIndex !== 0) { + this.onPartEnd(); + } + + return this.processPartData(data, start, end); + } + + processPartData(data, start, end) { + this.actualPartLength += end - start; + const out = this.out; + + if (out.write(start === 0 && data.length === end ? data : data.slice(start, end))) { + return Promise.resolve(); + } else { + return new Promise((resolve, reject) => { + out.on("error", reject); + out.once("drain", () => { + out.removeListener("error", reject); + resolve(); + }); + }); + } + } + +} exports.DataSplitter = DataSplitter; +//# sourceMappingURL=DataSplitter.js.map \ No newline at end of file diff --git a/out/differentialDownloader/DataSplitter.js.map b/out/differentialDownloader/DataSplitter.js.map new file mode 100644 index 0000000..a00fd9b --- /dev/null +++ b/out/differentialDownloader/DataSplitter.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../src/differentialDownloader/DataSplitter.ts"],"names":[],"mappings":"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","sourcesContent":["import { newError } from \"builder-util-runtime\"\r\nimport { createReadStream } from \"fs-extra-p\"\r\nimport { Writable } from \"stream\"\r\nimport { Operation, OperationKind } from \"./downloadPlanBuilder\"\r\n\r\nconst DOUBLE_CRLF = Buffer.from(\"\\r\\n\\r\\n\")\r\n\r\nenum ReadState {\r\n INIT, HEADER, BODY\r\n}\r\n\r\nexport interface PartListDataTask {\r\n readonly oldFileFd: number\r\n readonly tasks: Array\r\n readonly start: number\r\n readonly end: number\r\n}\r\n\r\nexport function copyData(task: Operation, out: Writable, oldFileFd: number, reject: (error: Error) => void, resolve: () => void) {\r\n const readStream = createReadStream(\"\", {\r\n fd: oldFileFd,\r\n autoClose: false,\r\n start: task.start,\r\n // end is inclusive\r\n end: task.end - 1,\r\n })\r\n readStream.on(\"error\", reject)\r\n readStream.once(\"end\", resolve)\r\n readStream.pipe(out, {\r\n end: false\r\n })\r\n}\r\n\r\nexport class DataSplitter extends Writable {\r\n partIndex = -1\r\n\r\n private headerListBuffer: Buffer | null = null\r\n private readState = ReadState.INIT\r\n private ignoreByteCount = 0\r\n private remainingPartDataCount = 0\r\n\r\n private readonly boundaryLength: number\r\n\r\n constructor(private readonly out: Writable, private readonly options: PartListDataTask, private readonly partIndexToTaskIndex: Map, boundary: string, private readonly partIndexToLength: Array, private readonly finishHandler: () => any) {\r\n super()\r\n\r\n this.boundaryLength = boundary.length + 4 /* size of \\r\\n-- */\r\n // first chunk doesn't start with \\r\\n\r\n this.ignoreByteCount = this.boundaryLength - 2\r\n }\r\n\r\n get isFinished() {\r\n return this.partIndex === this.partIndexToLength.length\r\n }\r\n\r\n // noinspection JSUnusedGlobalSymbols\r\n _write(data: Buffer, encoding: string, callback: (error?: Error) => void) {\r\n if (this.isFinished) {\r\n console.error(`Trailing ignored data: ${data.length} bytes`)\r\n return\r\n }\r\n\r\n this.handleData(data)\r\n .then(callback)\r\n .catch(callback)\r\n }\r\n\r\n private async handleData(chunk: Buffer): Promise {\r\n let start = 0\r\n\r\n if (this.ignoreByteCount !== 0 && this.remainingPartDataCount !== 0) {\r\n throw newError(\"Internal error\", \"ERR_DATA_SPLITTER_BYTE_COUNT_MISMATCH\")\r\n }\r\n\r\n if (this.ignoreByteCount > 0) {\r\n const toIgnore = Math.min(this.ignoreByteCount, chunk.length)\r\n this.ignoreByteCount -= toIgnore\r\n start = toIgnore\r\n }\r\n else if (this.remainingPartDataCount > 0) {\r\n const toRead = Math.min(this.remainingPartDataCount, chunk.length)\r\n this.remainingPartDataCount -= toRead\r\n await this.processPartData(chunk, 0, toRead)\r\n start = toRead\r\n }\r\n\r\n if (start === chunk.length) {\r\n return\r\n }\r\n\r\n if (this.readState === ReadState.HEADER) {\r\n const headerListEnd = this.searchHeaderListEnd(chunk, start)\r\n if (headerListEnd === -1) {\r\n return\r\n }\r\n\r\n start = headerListEnd\r\n this.readState = ReadState.BODY\r\n // header list is ignored, we don't need it\r\n this.headerListBuffer = null\r\n }\r\n\r\n while (true) {\r\n if (this.readState === ReadState.BODY) {\r\n this.readState = ReadState.INIT\r\n }\r\n else {\r\n this.partIndex++\r\n\r\n let taskIndex = this.partIndexToTaskIndex.get(this.partIndex)\r\n if (taskIndex == null) {\r\n if (this.isFinished) {\r\n taskIndex = this.options.end\r\n }\r\n else {\r\n throw newError(\"taskIndex is null\", \"ERR_DATA_SPLITTER_TASK_INDEX_IS_NULL\")\r\n }\r\n }\r\n\r\n const prevTaskIndex = this.partIndex === 0 ? this.options.start : (this.partIndexToTaskIndex.get(this.partIndex - 1)!! + 1 /* prev part is download, next maybe copy */)\r\n if (prevTaskIndex < taskIndex) {\r\n await this.copyExistingData(prevTaskIndex, taskIndex)\r\n }\r\n else if (prevTaskIndex > taskIndex) {\r\n throw newError(\"prevTaskIndex must be < taskIndex\", \"ERR_DATA_SPLITTER_TASK_INDEX_ASSERT_FAILED\")\r\n }\r\n\r\n if (this.isFinished) {\r\n this.onPartEnd()\r\n this.finishHandler()\r\n return\r\n }\r\n\r\n start = this.searchHeaderListEnd(chunk, start)\r\n\r\n if (start === -1) {\r\n this.readState = ReadState.HEADER\r\n return\r\n }\r\n }\r\n\r\n const partLength = this.partIndexToLength[this.partIndex]\r\n const end = start + partLength\r\n const effectiveEnd = Math.min(end, chunk.length)\r\n await this.processPartStarted(chunk, start, effectiveEnd)\r\n this.remainingPartDataCount = partLength - (effectiveEnd - start)\r\n if (this.remainingPartDataCount > 0) {\r\n return\r\n }\r\n\r\n start = end + this.boundaryLength\r\n if (start >= chunk.length) {\r\n this.ignoreByteCount = this.boundaryLength - (chunk.length - end)\r\n return\r\n }\r\n }\r\n }\r\n\r\n private copyExistingData(index: number, end: number) {\r\n return new Promise((resolve, reject) => {\r\n const w = () => {\r\n if (index === end) {\r\n resolve()\r\n return\r\n }\r\n\r\n const task = this.options.tasks[index]\r\n if (task.kind !== OperationKind.COPY) {\r\n reject(new Error(\"Task kind must be COPY\"))\r\n return\r\n }\r\n\r\n copyData(task, this.out, this.options.oldFileFd, reject, () => {\r\n index++\r\n w()\r\n })\r\n }\r\n w()\r\n })\r\n }\r\n\r\n private searchHeaderListEnd(chunk: Buffer, readOffset: number): number {\r\n const headerListEnd = chunk.indexOf(DOUBLE_CRLF, readOffset)\r\n if (headerListEnd !== -1) {\r\n return headerListEnd + DOUBLE_CRLF.length\r\n }\r\n\r\n // not all headers data were received, save to buffer\r\n const partialChunk = readOffset === 0 ? chunk : chunk.slice(readOffset)\r\n if (this.headerListBuffer == null) {\r\n this.headerListBuffer = partialChunk\r\n }\r\n else {\r\n this.headerListBuffer = Buffer.concat([this.headerListBuffer, partialChunk])\r\n }\r\n return -1\r\n }\r\n\r\n private actualPartLength = 0\r\n\r\n private onPartEnd() {\r\n const expectedLength = this.partIndexToLength[this.partIndex - 1]\r\n if (this.actualPartLength !== expectedLength) {\r\n throw newError(`Expected length: ${expectedLength} differs from actual: ${this.actualPartLength}`, \"ERR_DATA_SPLITTER_LENGTH_MISMATCH\")\r\n }\r\n this.actualPartLength = 0\r\n }\r\n\r\n private processPartStarted(data: Buffer, start: number, end: number): Promise {\r\n if (this.partIndex !== 0) {\r\n this.onPartEnd()\r\n }\r\n return this.processPartData(data, start, end)\r\n }\r\n\r\n private processPartData(data: Buffer, start: number, end: number): Promise {\r\n this.actualPartLength += end - start\r\n const out = this.out\r\n if (out.write(start === 0 && data.length === end ? data : data.slice(start, end))) {\r\n return Promise.resolve()\r\n }\r\n else {\r\n return new Promise((resolve, reject) => {\r\n out.on(\"error\", reject)\r\n out.once(\"drain\", () => {\r\n out.removeListener(\"error\", reject)\r\n resolve()\r\n })\r\n })\r\n }\r\n }\r\n}"],"sourceRoot":""} diff --git a/out/differentialDownloader/DifferentialDownloader.d.ts b/out/differentialDownloader/DifferentialDownloader.d.ts new file mode 100644 index 0000000..39058a1 --- /dev/null +++ b/out/differentialDownloader/DifferentialDownloader.d.ts @@ -0,0 +1,29 @@ +/// +import { BlockMapDataHolder, HttpExecutor } from "builder-util-runtime"; +import { BlockMap } from "builder-util-runtime/out/blockMapApi"; +import { OutgoingHttpHeaders, RequestOptions } from "http"; +import { Logger } from "../main"; +export interface DifferentialDownloaderOptions { + readonly oldFile: string; + readonly newUrl: string; + readonly logger: Logger; + readonly newFile: string; + readonly requestHeaders: OutgoingHttpHeaders | null; + readonly useMultipleRangeRequest?: boolean; +} +export declare abstract class DifferentialDownloader { + protected readonly blockAwareFileInfo: BlockMapDataHolder; + readonly httpExecutor: HttpExecutor; + readonly options: DifferentialDownloaderOptions; + private readonly baseRequestOptions; + fileMetadataBuffer: Buffer | null; + private readonly logger; + constructor(blockAwareFileInfo: BlockMapDataHolder, httpExecutor: HttpExecutor, options: DifferentialDownloaderOptions); + createRequestOptions(method?: "head" | "get", newUrl?: string | null): RequestOptions; + protected doDownload(oldBlockMap: BlockMap, newBlockMap: BlockMap): Promise; + private downloadFile; + private doDownloadFile; + protected readRemoteBytes(start: number, endInclusive: number): Promise; + private request; +} +export declare function readBlockMap(data: Buffer): Promise; diff --git a/out/differentialDownloader/DifferentialDownloader.js b/out/differentialDownloader/DifferentialDownloader.js new file mode 100644 index 0000000..0d2dda5 --- /dev/null +++ b/out/differentialDownloader/DifferentialDownloader.js @@ -0,0 +1,318 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.readBlockMap = readBlockMap; +exports.DifferentialDownloader = void 0; + +function _bluebirdLst() { + const data = _interopRequireDefault(require("bluebird-lst")); + + _bluebirdLst = function () { + return data; + }; + + return data; +} + +function _builderUtilRuntime() { + const data = require("builder-util-runtime"); + + _builderUtilRuntime = function () { + return data; + }; + + return data; +} + +function _fsExtraP() { + const data = require("fs-extra-p"); + + _fsExtraP = function () { + return data; + }; + + return data; +} + +function _DataSplitter() { + const data = require("./DataSplitter"); + + _DataSplitter = function () { + return data; + }; + + return data; +} + +function _downloadPlanBuilder() { + const data = require("./downloadPlanBuilder"); + + _downloadPlanBuilder = function () { + return data; + }; + + return data; +} + +function _multipleRangeDownloader() { + const data = require("./multipleRangeDownloader"); + + _multipleRangeDownloader = function () { + return data; + }; + + return data; +} + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const inflateRaw = _bluebirdLst().default.promisify(require("zlib").inflateRaw); + +class DifferentialDownloader { + // noinspection TypeScriptAbstractClassConstructorCanBeMadeProtected + constructor(blockAwareFileInfo, httpExecutor, options) { + this.blockAwareFileInfo = blockAwareFileInfo; + this.httpExecutor = httpExecutor; + this.options = options; + this.fileMetadataBuffer = null; + this.logger = options.logger; + this.baseRequestOptions = (0, _builderUtilRuntime().configureRequestOptionsFromUrl)(options.newUrl, {}); + } + + createRequestOptions(method = "get", newUrl) { + return Object.assign({}, newUrl == null ? this.baseRequestOptions : (0, _builderUtilRuntime().configureRequestOptionsFromUrl)(newUrl, {}), { + method, + headers: Object.assign({}, this.options.requestHeaders, { + accept: "*/*" + }) + }); + } + + doDownload(oldBlockMap, newBlockMap) { + // we don't check other metadata like compressionMethod - generic check that it is make sense to differentially update is suitable for it + if (oldBlockMap.version !== newBlockMap.version) { + throw new Error(`version is different (${oldBlockMap.version} - ${newBlockMap.version}), full download is required`); + } + + const logger = this.logger; + const operations = (0, _downloadPlanBuilder().computeOperations)(oldBlockMap, newBlockMap, logger); + + if (logger.debug != null) { + logger.debug(JSON.stringify(operations, null, 2)); + } + + let downloadSize = 0; + let copySize = 0; + + for (const operation of operations) { + const length = operation.end - operation.start; + + if (operation.kind === _downloadPlanBuilder().OperationKind.DOWNLOAD) { + downloadSize += length; + } else { + copySize += length; + } + } + + const newPackageSize = this.blockAwareFileInfo.size; + + if (downloadSize + copySize + (this.fileMetadataBuffer == null ? 0 : this.fileMetadataBuffer.length) !== newPackageSize) { + throw new Error(`Internal error, size mismatch: downloadSize: ${downloadSize}, copySize: ${copySize}, newPackageSize: ${newPackageSize}`); + } + + logger.info(`Full: ${formatBytes(newPackageSize)}, To download: ${formatBytes(downloadSize)} (${Math.round(downloadSize / (newPackageSize / 100))}%)`); + return this.downloadFile(operations); + } + + downloadFile(tasks) { + const fdList = []; + + const closeFiles = () => { + return _bluebirdLst().default.map(fdList, openedFile => { + return (0, _fsExtraP().close)(openedFile.descriptor).catch(e => { + this.logger.error(`cannot close file "${openedFile.path}": ${e}`); + }); + }); + }; + + return this.doDownloadFile(tasks, fdList).then(closeFiles).catch(e => { + // then must be after catch here (since then always throws error) + return closeFiles().catch(closeFilesError => { + // closeFiles never throw error, but just to be sure + try { + this.logger.error(`cannot close files: ${closeFilesError}`); + } catch (errorOnLog) { + try { + console.error(errorOnLog); + } catch (ignored) {// ok, give up and ignore error + } + } + + throw e; + }).then(() => { + throw e; + }); + }); + } + + async doDownloadFile(tasks, fdList) { + const oldFileFd = await (0, _fsExtraP().open)(this.options.oldFile, "r"); + fdList.push({ + descriptor: oldFileFd, + path: this.options.oldFile + }); + const newFileFd = await (0, _fsExtraP().open)(this.options.newFile, "w"); + fdList.push({ + descriptor: newFileFd, + path: this.options.newFile + }); + const fileOut = (0, _fsExtraP().createWriteStream)(this.options.newFile, { + fd: newFileFd + }); + await new Promise((resolve, reject) => { + const streams = []; + const digestTransform = new (_builderUtilRuntime().DigestTransform)(this.blockAwareFileInfo.sha512); // to simply debug, do manual validation to allow file to be fully written + + digestTransform.isValidateOnEnd = false; + streams.push(digestTransform); // noinspection JSArrowFunctionCanBeReplacedWithShorthand + + fileOut.on("finish", () => { + fileOut.close(() => { + try { + digestTransform.validate(); + } catch (e) { + reject(e); + return; + } + + resolve(); + }); + }); + streams.push(fileOut); + let lastStream = null; + + for (const stream of streams) { + stream.on("error", reject); + + if (lastStream == null) { + lastStream = stream; + } else { + lastStream = lastStream.pipe(stream); + } + } + + const firstStream = streams[0]; + let w; + + if (this.options.useMultipleRangeRequest) { + w = (0, _multipleRangeDownloader().executeTasks)(this, tasks, firstStream, oldFileFd, reject); + } else { + let attemptCount = 0; + let actualUrl = null; + this.logger.info(`Differential download: ${this.options.newUrl}`); + + w = index => { + if (index >= tasks.length) { + if (this.fileMetadataBuffer != null) { + firstStream.write(this.fileMetadataBuffer); + } + + firstStream.end(); + return; + } + + const operation = tasks[index++]; + + if (operation.kind === _downloadPlanBuilder().OperationKind.COPY) { + (0, _DataSplitter().copyData)(operation, firstStream, oldFileFd, reject, () => w(index)); + } else { + const requestOptions = this.createRequestOptions("get", actualUrl); + const range = `bytes=${operation.start}-${operation.end - 1}`; + requestOptions.headers.Range = range; + requestOptions.redirect = "manual"; + const debug = this.logger.debug; + + if (debug != null) { + debug(`effective url: ${actualUrl == null ? "original" : removeQuery(actualUrl)}, range: ${range}`); + } + + const request = this.httpExecutor.doRequest(requestOptions, response => { + // Electron net handles redirects automatically, our NodeJS test server doesn't use redirects - so, we don't check 3xx codes. + if (response.statusCode >= 400) { + reject((0, _builderUtilRuntime().createHttpError)(response)); + } + + response.pipe(firstStream, { + end: false + }); + response.once("end", () => { + if (++attemptCount === 100) { + attemptCount = 0; + setTimeout(() => w(index), 1000); + } else { + w(index); + } + }); + }); + request.on("redirect", (statusCode, method, redirectUrl) => { + this.logger.info(`Redirect to ${removeQuery(redirectUrl)}`); + actualUrl = redirectUrl; + request.followRedirect(); + }); + this.httpExecutor.addErrorAndTimeoutHandlers(request, reject); + request.end(); + } + }; + } + + w(0); + }); + } + + async readRemoteBytes(start, endInclusive) { + const buffer = Buffer.allocUnsafe(endInclusive + 1 - start); + const requestOptions = this.createRequestOptions(); + requestOptions.headers.Range = `bytes=${start}-${endInclusive}`; + let position = 0; + await this.request(requestOptions, chunk => { + chunk.copy(buffer, position); + position += chunk.length; + }); + return buffer; + } + + request(requestOptions, dataHandler) { + return new Promise((resolve, reject) => { + const request = this.httpExecutor.doRequest(requestOptions, response => { + if (!(0, _multipleRangeDownloader().checkIsRangesSupported)(response, reject)) { + return; + } + + response.on("data", dataHandler); + response.on("end", () => resolve()); + }); + this.httpExecutor.addErrorAndTimeoutHandlers(request, reject); + request.end(); + }); + } + +} + +exports.DifferentialDownloader = DifferentialDownloader; + +async function readBlockMap(data) { + return JSON.parse((await inflateRaw(data)).toString()); +} + +function formatBytes(value, symbol = " KB") { + return new Intl.NumberFormat("en").format((value / 1024).toFixed(2)) + symbol; +} // safety + + +function removeQuery(url) { + const index = url.indexOf("?"); + return index < 0 ? url : url.substring(0, index); +} +//# sourceMappingURL=DifferentialDownloader.js.map \ No newline at end of file diff --git a/out/differentialDownloader/DifferentialDownloader.js.map b/out/differentialDownloader/DifferentialDownloader.js.map new file mode 100644 index 0000000..3b4c1cc --- /dev/null +++ b/out/differentialDownloader/DifferentialDownloader.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../src/differentialDownloader/DifferentialDownloader.ts"],"names":[],"mappings":";;;;;;;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAEA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAGA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;;;AAEA,MAAM,UAAU,GAAQ,uBAAgB,SAAhB,CAA0B,OAAO,CAAC,MAAD,CAAP,CAAgB,UAA1C,CAAxB;;AAaM,MAAgB,sBAAhB,CAAsC;AAO1C;AACA,EAAA,WAAA,CAA+B,kBAA/B,EAAgF,YAAhF,EAA0H,OAA1H,EAAgK;AAAjI,SAAA,kBAAA,GAAA,kBAAA;AAAiD,SAAA,YAAA,GAAA,YAAA;AAA0C,SAAA,OAAA,GAAA,OAAA;AAL1H,SAAA,kBAAA,GAAoC,IAApC;AAME,SAAK,MAAL,GAAc,OAAO,CAAC,MAAtB;AACA,SAAK,kBAAL,GAA0B,0DAA+B,OAAO,CAAC,MAAvC,EAA+C,EAA/C,CAA1B;AACD;;AAED,EAAA,oBAAoB,CAAC,MAAA,GAAyB,KAA1B,EAAiC,MAAjC,EAAuD;AACzE,WAAA,MAAA,CAAA,MAAA,CAAA,EAAA,EACM,MAAM,IAAI,IAAV,GAAiB,KAAK,kBAAtB,GAA2C,0DAA+B,MAA/B,EAAuC,EAAvC,CADjD,EAC4F;AAC1F,MAAA,MAD0F;AAE1F,MAAA,OAAO,EAAE,MAAA,CAAA,MAAA,CAAA,EAAA,EACJ,KAAK,OAAL,CAAa,cADT,EACuB;AAC9B,QAAA,MAAM,EAAE;AADsB,OADvB;AAFiF,KAD5F,CAAA;AAQD;;AAES,EAAA,UAAU,CAAC,WAAD,EAAwB,WAAxB,EAA6C;AAC/D;AACA,QAAI,WAAW,CAAC,OAAZ,KAAwB,WAAW,CAAC,OAAxC,EAAiD;AAC/C,YAAM,IAAI,KAAJ,CAAU,yBAAyB,WAAW,CAAC,OAAO,MAAM,WAAW,CAAC,OAAO,8BAA/E,CAAN;AACD;;AAED,UAAM,MAAM,GAAG,KAAK,MAApB;AACA,UAAM,UAAU,GAAG,8CAAkB,WAAlB,EAA+B,WAA/B,EAA4C,MAA5C,CAAnB;;AACA,QAAI,MAAM,CAAC,KAAP,IAAgB,IAApB,EAA0B;AACxB,MAAA,MAAM,CAAC,KAAP,CAAa,IAAI,CAAC,SAAL,CAAe,UAAf,EAA2B,IAA3B,EAAiC,CAAjC,CAAb;AACD;;AAED,QAAI,YAAY,GAAG,CAAnB;AACA,QAAI,QAAQ,GAAG,CAAf;;AACA,SAAK,MAAM,SAAX,IAAwB,UAAxB,EAAoC;AAClC,YAAM,MAAM,GAAG,SAAS,CAAC,GAAV,GAAgB,SAAS,CAAC,KAAzC;;AACA,UAAI,SAAS,CAAC,IAAV,KAAmB,qCAAc,QAArC,EAA+C;AAC7C,QAAA,YAAY,IAAI,MAAhB;AACD,OAFD,MAGK;AACH,QAAA,QAAQ,IAAI,MAAZ;AACD;AACF;;AAED,UAAM,cAAc,GAAG,KAAK,kBAAL,CAAwB,IAA/C;;AACA,QAAK,YAAY,GAAG,QAAf,IAA2B,KAAK,kBAAL,IAA2B,IAA3B,GAAkC,CAAlC,GAAsC,KAAK,kBAAL,CAAwB,MAAzF,CAAD,KAAuG,cAA3G,EAA2H;AACzH,YAAM,IAAI,KAAJ,CAAU,gDAAgD,YAAY,eAAe,QAAQ,qBAAqB,cAAc,EAAhI,CAAN;AACD;;AAED,IAAA,MAAM,CAAC,IAAP,CAAY,SAAS,WAAW,CAAC,cAAD,CAAgB,kBAAkB,WAAW,CAAC,YAAD,CAAc,KAAK,IAAI,CAAC,KAAL,CAAW,YAAY,IAAI,cAAc,GAAG,GAArB,CAAvB,CAAiD,IAAjJ;AAEA,WAAO,KAAK,YAAL,CAAkB,UAAlB,CAAP;AACD;;AAEO,EAAA,YAAY,CAAC,KAAD,EAAwB;AAC1C,UAAM,MAAM,GAAsB,EAAlC;;AACA,UAAM,UAAU,GAAG,MAAK;AACtB,aAAO,uBAAgB,GAAhB,CAAoB,MAApB,EAA4B,UAAU,IAAG;AAC9C,eAAO,uBAAM,UAAU,CAAC,UAAjB,EACJ,KADI,CACE,CAAC,IAAG;AACT,eAAK,MAAL,CAAY,KAAZ,CAAkB,sBAAsB,UAAU,CAAC,IAAI,MAAM,CAAC,EAA9D;AACD,SAHI,CAAP;AAID,OALM,CAAP;AAMD,KAPD;;AAQA,WAAO,KAAK,cAAL,CAAoB,KAApB,EAA2B,MAA3B,EACJ,IADI,CACC,UADD,EAEJ,KAFI,CAEE,CAAC,IAAG;AACT;AACA,aAAO,UAAU,GACd,KADI,CACE,eAAe,IAAG;AACvB;AACA,YAAI;AACF,eAAK,MAAL,CAAY,KAAZ,CAAkB,uBAAuB,eAAe,EAAxD;AACD,SAFD,CAGA,OAAO,UAAP,EAAmB;AACjB,cAAI;AACF,YAAA,OAAO,CAAC,KAAR,CAAc,UAAd;AACD,WAFD,CAGA,OAAO,OAAP,EAAgB,CACd;AACD;AACF;;AACD,cAAM,CAAN;AACD,OAfI,EAgBJ,IAhBI,CAgBC,MAAK;AACT,cAAM,CAAN;AACD,OAlBI,CAAP;AAmBD,KAvBI,CAAP;AAwBD;;AAEO,QAAM,cAAN,CAAqB,KAArB,EAA8C,MAA9C,EAAuE;AAC7E,UAAM,SAAS,GAAG,MAAM,sBAAK,KAAK,OAAL,CAAa,OAAlB,EAA2B,GAA3B,CAAxB;AACA,IAAA,MAAM,CAAC,IAAP,CAAY;AAAC,MAAA,UAAU,EAAE,SAAb;AAAwB,MAAA,IAAI,EAAE,KAAK,OAAL,CAAa;AAA3C,KAAZ;AACA,UAAM,SAAS,GAAG,MAAM,sBAAK,KAAK,OAAL,CAAa,OAAlB,EAA2B,GAA3B,CAAxB;AACA,IAAA,MAAM,CAAC,IAAP,CAAY;AAAC,MAAA,UAAU,EAAE,SAAb;AAAwB,MAAA,IAAI,EAAE,KAAK,OAAL,CAAa;AAA3C,KAAZ;AACA,UAAM,OAAO,GAAG,mCAAkB,KAAK,OAAL,CAAa,OAA/B,EAAwC;AAAC,MAAA,EAAE,EAAE;AAAL,KAAxC,CAAhB;AACA,UAAM,IAAI,OAAJ,CAAY,CAAC,OAAD,EAAU,MAAV,KAAoB;AACpC,YAAM,OAAO,GAAe,EAA5B;AACA,YAAM,eAAe,GAAG,KAAI,qCAAJ,EAAoB,KAAK,kBAAL,CAAwB,MAA5C,CAAxB,CAFoC,CAGpC;;AACA,MAAA,eAAe,CAAC,eAAhB,GAAkC,KAAlC;AACA,MAAA,OAAO,CAAC,IAAR,CAAa,eAAb,EALoC,CAOpC;;AACA,MAAA,OAAO,CAAC,EAAR,CAAW,QAAX,EAAqB,MAAK;AACvB,QAAA,OAAO,CAAC,KAAR,CAAsB,MAAK;AAC1B,cAAI;AACF,YAAA,eAAe,CAAC,QAAhB;AACD,WAFD,CAGA,OAAO,CAAP,EAAU;AACR,YAAA,MAAM,CAAC,CAAD,CAAN;AACA;AACD;;AAED,UAAA,OAAO;AACR,SAVA;AAWF,OAZD;AAcA,MAAA,OAAO,CAAC,IAAR,CAAa,OAAb;AAEA,UAAI,UAAU,GAAG,IAAjB;;AACA,WAAK,MAAM,MAAX,IAAqB,OAArB,EAA8B;AAC5B,QAAA,MAAM,CAAC,EAAP,CAAU,OAAV,EAAmB,MAAnB;;AACA,YAAI,UAAU,IAAI,IAAlB,EAAwB;AACtB,UAAA,UAAU,GAAG,MAAb;AACD,SAFD,MAGK;AACH,UAAA,UAAU,GAAG,UAAU,CAAC,IAAX,CAAgB,MAAhB,CAAb;AACD;AACF;;AAED,YAAM,WAAW,GAAG,OAAO,CAAC,CAAD,CAA3B;AAEA,UAAI,CAAJ;;AACA,UAAI,KAAK,OAAL,CAAa,uBAAjB,EAA0C;AACxC,QAAA,CAAC,GAAG,6CAAa,IAAb,EAAmB,KAAnB,EAA0B,WAA1B,EAAuC,SAAvC,EAAkD,MAAlD,CAAJ;AACD,OAFD,MAGK;AACH,YAAI,YAAY,GAAG,CAAnB;AACA,YAAI,SAAS,GAAkB,IAA/B;AACA,aAAK,MAAL,CAAY,IAAZ,CAAiB,0BAA0B,KAAK,OAAL,CAAa,MAAM,EAA9D;;AACA,QAAA,CAAC,GAAI,KAAD,IAAkB;AACpB,cAAI,KAAK,IAAI,KAAK,CAAC,MAAnB,EAA2B;AACzB,gBAAI,KAAK,kBAAL,IAA2B,IAA/B,EAAqC;AACnC,cAAA,WAAW,CAAC,KAAZ,CAAkB,KAAK,kBAAvB;AACD;;AACD,YAAA,WAAW,CAAC,GAAZ;AACA;AACD;;AAED,gBAAM,SAAS,GAAG,KAAK,CAAC,KAAK,EAAN,CAAvB;;AACA,cAAI,SAAS,CAAC,IAAV,KAAmB,qCAAc,IAArC,EAA2C;AACzC,0CAAS,SAAT,EAAoB,WAApB,EAAiC,SAAjC,EAA4C,MAA5C,EAAoD,MAAM,CAAC,CAAC,KAAD,CAA3D;AACD,WAFD,MAGK;AACH,kBAAM,cAAc,GAAG,KAAK,oBAAL,CAA0B,KAA1B,EAAiC,SAAjC,CAAvB;AACA,kBAAM,KAAK,GAAG,SAAS,SAAS,CAAC,KAAK,IAAI,SAAS,CAAC,GAAV,GAAgB,CAAC,EAA3D;AACA,YAAA,cAAc,CAAC,OAAf,CAAyB,KAAzB,GAAiC,KAAjC;AACC,YAAA,cAAsB,CAAC,QAAvB,GAAkC,QAAlC;AAED,kBAAM,KAAK,GAAG,KAAK,MAAL,CAAY,KAA1B;;AACA,gBAAI,KAAK,IAAI,IAAb,EAAmB;AACjB,cAAA,KAAK,CAAC,kBAAkB,SAAS,IAAI,IAAb,GAAoB,UAApB,GAAiC,WAAW,CAAC,SAAD,CAAW,YAAY,KAAK,EAA3F,CAAL;AACD;;AAED,kBAAM,OAAO,GAAG,KAAK,YAAL,CAAkB,SAAlB,CAA4B,cAA5B,EAA4C,QAAQ,IAAG;AACrE;AACA,kBAAI,QAAQ,CAAC,UAAT,IAAuB,GAA3B,EAAgC;AAC9B,gBAAA,MAAM,CAAC,2CAAgB,QAAhB,CAAD,CAAN;AACD;;AAED,cAAA,QAAQ,CAAC,IAAT,CAAc,WAAd,EAA2B;AACzB,gBAAA,GAAG,EAAE;AADoB,eAA3B;AAGA,cAAA,QAAQ,CAAC,IAAT,CAAc,KAAd,EAAqB,MAAK;AACxB,oBAAI,EAAE,YAAF,KAAmB,GAAvB,EAA4B;AAC1B,kBAAA,YAAY,GAAG,CAAf;AACA,kBAAA,UAAU,CAAC,MAAM,CAAC,CAAC,KAAD,CAAR,EAAiB,IAAjB,CAAV;AACD,iBAHD,MAIK;AACH,kBAAA,CAAC,CAAC,KAAD,CAAD;AACD;AACF,eARD;AASD,aAlBe,CAAhB;AAmBA,YAAA,OAAO,CAAC,EAAR,CAAW,UAAX,EAAuB,CAAC,UAAD,EAAqB,MAArB,EAAqC,WAArC,KAA4D;AACjF,mBAAK,MAAL,CAAY,IAAZ,CAAiB,eAAe,WAAW,CAAC,WAAD,CAAa,EAAxD;AACA,cAAA,SAAS,GAAG,WAAZ;AACA,cAAA,OAAO,CAAC,cAAR;AACD,aAJD;AAKA,iBAAK,YAAL,CAAkB,0BAAlB,CAA6C,OAA7C,EAAsD,MAAtD;AACA,YAAA,OAAO,CAAC,GAAR;AACD;AACF,SAnDD;AAoDD;;AAED,MAAA,CAAC,CAAC,CAAD,CAAD;AACD,KApGK,CAAN;AAqGD;;AAES,QAAM,eAAN,CAAsB,KAAtB,EAAqC,YAArC,EAAyD;AACjE,UAAM,MAAM,GAAG,MAAM,CAAC,WAAP,CAAoB,YAAY,GAAG,CAAhB,GAAqB,KAAxC,CAAf;AACA,UAAM,cAAc,GAAG,KAAK,oBAAL,EAAvB;AACA,IAAA,cAAc,CAAC,OAAf,CAAyB,KAAzB,GAAiC,SAAS,KAAK,IAAI,YAAY,EAA/D;AACA,QAAI,QAAQ,GAAG,CAAf;AACA,UAAM,KAAK,OAAL,CAAa,cAAb,EAA6B,KAAK,IAAG;AACzC,MAAA,KAAK,CAAC,IAAN,CAAW,MAAX,EAAmB,QAAnB;AACA,MAAA,QAAQ,IAAI,KAAK,CAAC,MAAlB;AACD,KAHK,CAAN;AAIA,WAAO,MAAP;AACD;;AAEO,EAAA,OAAO,CAAC,cAAD,EAAiC,WAAjC,EAAqE;AAClF,WAAO,IAAI,OAAJ,CAAY,CAAC,OAAD,EAAU,MAAV,KAAoB;AACrC,YAAM,OAAO,GAAG,KAAK,YAAL,CAAkB,SAAlB,CAA4B,cAA5B,EAA4C,QAAQ,IAAG;AACrE,YAAI,CAAC,uDAAuB,QAAvB,EAAiC,MAAjC,CAAL,EAA+C;AAC7C;AACD;;AAED,QAAA,QAAQ,CAAC,EAAT,CAAY,MAAZ,EAAoB,WAApB;AACA,QAAA,QAAQ,CAAC,EAAT,CAAY,KAAZ,EAAmB,MAAM,OAAO,EAAhC;AACD,OAPe,CAAhB;AAQA,WAAK,YAAL,CAAkB,0BAAlB,CAA6C,OAA7C,EAAsD,MAAtD;AACA,MAAA,OAAO,CAAC,GAAR;AACD,KAXM,CAAP;AAYD;;AApOyC;;;;AAuOrC,eAAe,YAAf,CAA4B,IAA5B,EAAwC;AAC7C,SAAO,IAAI,CAAC,KAAL,CAAW,CAAC,MAAM,UAAU,CAAC,IAAD,CAAjB,EAAyB,QAAzB,EAAX,CAAP;AACD;;AAED,SAAS,WAAT,CAAqB,KAArB,EAAoC,MAAM,GAAG,KAA7C,EAAkD;AAChD,SAAO,IAAI,IAAI,CAAC,YAAT,CAAsB,IAAtB,EAA4B,MAA5B,CAAmC,CAAC,KAAK,GAAG,IAAT,EAAe,OAAf,CAAuB,CAAvB,CAAnC,IAAuE,MAA9E;AACD,C,CAED;;;AACA,SAAS,WAAT,CAAqB,GAArB,EAAgC;AAC9B,QAAM,KAAK,GAAG,GAAG,CAAC,OAAJ,CAAY,GAAZ,CAAd;AACA,SAAO,KAAK,GAAG,CAAR,GAAY,GAAZ,GAAkB,GAAG,CAAC,SAAJ,CAAc,CAAd,EAAiB,KAAjB,CAAzB;AACD,C","sourcesContent":["import BluebirdPromise from \"bluebird-lst\"\r\nimport { BlockMapDataHolder, configureRequestOptionsFromUrl, createHttpError, DigestTransform, HttpExecutor } from \"builder-util-runtime\"\r\nimport { BlockMap } from \"builder-util-runtime/out/blockMapApi\"\r\nimport { close, createWriteStream, open } from \"fs-extra-p\"\r\nimport { OutgoingHttpHeaders, RequestOptions } from \"http\"\r\nimport { Logger } from \"../main\"\r\nimport { copyData } from \"./DataSplitter\"\r\nimport { computeOperations, Operation, OperationKind } from \"./downloadPlanBuilder\"\r\nimport { checkIsRangesSupported, executeTasks } from \"./multipleRangeDownloader\"\r\n\r\nconst inflateRaw: any = BluebirdPromise.promisify(require(\"zlib\").inflateRaw)\r\n\r\nexport interface DifferentialDownloaderOptions {\r\n readonly oldFile: string\r\n readonly newUrl: string\r\n readonly logger: Logger\r\n readonly newFile: string\r\n\r\n readonly requestHeaders: OutgoingHttpHeaders | null\r\n\r\n readonly useMultipleRangeRequest?: boolean\r\n}\r\n\r\nexport abstract class DifferentialDownloader {\r\n private readonly baseRequestOptions: RequestOptions\r\n\r\n fileMetadataBuffer: Buffer | null = null\r\n\r\n private readonly logger: Logger\r\n\r\n // noinspection TypeScriptAbstractClassConstructorCanBeMadeProtected\r\n constructor(protected readonly blockAwareFileInfo: BlockMapDataHolder, readonly httpExecutor: HttpExecutor, readonly options: DifferentialDownloaderOptions) {\r\n this.logger = options.logger\r\n this.baseRequestOptions = configureRequestOptionsFromUrl(options.newUrl, {})\r\n }\r\n\r\n createRequestOptions(method: \"head\" | \"get\" = \"get\", newUrl?: string | null): RequestOptions {\r\n return {\r\n ...(newUrl == null ? this.baseRequestOptions : configureRequestOptionsFromUrl(newUrl, {})),\r\n method,\r\n headers: {\r\n ...this.options.requestHeaders,\r\n accept: \"*/*\",\r\n } as any,\r\n }\r\n }\r\n\r\n protected doDownload(oldBlockMap: BlockMap, newBlockMap: BlockMap): Promise {\r\n // we don't check other metadata like compressionMethod - generic check that it is make sense to differentially update is suitable for it\r\n if (oldBlockMap.version !== newBlockMap.version) {\r\n throw new Error(`version is different (${oldBlockMap.version} - ${newBlockMap.version}), full download is required`)\r\n }\r\n\r\n const logger = this.logger\r\n const operations = computeOperations(oldBlockMap, newBlockMap, logger)\r\n if (logger.debug != null) {\r\n logger.debug(JSON.stringify(operations, null, 2))\r\n }\r\n\r\n let downloadSize = 0\r\n let copySize = 0\r\n for (const operation of operations) {\r\n const length = operation.end - operation.start\r\n if (operation.kind === OperationKind.DOWNLOAD) {\r\n downloadSize += length\r\n }\r\n else {\r\n copySize += length\r\n }\r\n }\r\n\r\n const newPackageSize = this.blockAwareFileInfo.size\r\n if ((downloadSize + copySize + (this.fileMetadataBuffer == null ? 0 : this.fileMetadataBuffer.length)) !== newPackageSize) {\r\n throw new Error(`Internal error, size mismatch: downloadSize: ${downloadSize}, copySize: ${copySize}, newPackageSize: ${newPackageSize}`)\r\n }\r\n\r\n logger.info(`Full: ${formatBytes(newPackageSize)}, To download: ${formatBytes(downloadSize)} (${Math.round(downloadSize / (newPackageSize / 100))}%)`)\r\n\r\n return this.downloadFile(operations)\r\n }\r\n\r\n private downloadFile(tasks: Array): Promise {\r\n const fdList: Array = []\r\n const closeFiles = () => {\r\n return BluebirdPromise.map(fdList, openedFile => {\r\n return close(openedFile.descriptor)\r\n .catch(e => {\r\n this.logger.error(`cannot close file \"${openedFile.path}\": ${e}`)\r\n })\r\n })\r\n }\r\n return this.doDownloadFile(tasks, fdList)\r\n .then(closeFiles)\r\n .catch(e => {\r\n // then must be after catch here (since then always throws error)\r\n return closeFiles()\r\n .catch(closeFilesError => {\r\n // closeFiles never throw error, but just to be sure\r\n try {\r\n this.logger.error(`cannot close files: ${closeFilesError}`)\r\n }\r\n catch (errorOnLog) {\r\n try {\r\n console.error(errorOnLog)\r\n }\r\n catch (ignored) {\r\n // ok, give up and ignore error\r\n }\r\n }\r\n throw e\r\n })\r\n .then(() => {\r\n throw e\r\n })\r\n })\r\n }\r\n\r\n private async doDownloadFile(tasks: Array, fdList: Array): Promise {\r\n const oldFileFd = await open(this.options.oldFile, \"r\")\r\n fdList.push({descriptor: oldFileFd, path: this.options.oldFile})\r\n const newFileFd = await open(this.options.newFile, \"w\")\r\n fdList.push({descriptor: newFileFd, path: this.options.newFile})\r\n const fileOut = createWriteStream(this.options.newFile, {fd: newFileFd})\r\n await new Promise((resolve, reject) => {\r\n const streams: Array = []\r\n const digestTransform = new DigestTransform(this.blockAwareFileInfo.sha512)\r\n // to simply debug, do manual validation to allow file to be fully written\r\n digestTransform.isValidateOnEnd = false\r\n streams.push(digestTransform)\r\n\r\n // noinspection JSArrowFunctionCanBeReplacedWithShorthand\r\n fileOut.on(\"finish\", () => {\r\n (fileOut.close as any)(() => {\r\n try {\r\n digestTransform.validate()\r\n }\r\n catch (e) {\r\n reject(e)\r\n return\r\n }\r\n\r\n resolve()\r\n })\r\n })\r\n\r\n streams.push(fileOut)\r\n\r\n let lastStream = null\r\n for (const stream of streams) {\r\n stream.on(\"error\", reject)\r\n if (lastStream == null) {\r\n lastStream = stream\r\n }\r\n else {\r\n lastStream = lastStream.pipe(stream)\r\n }\r\n }\r\n\r\n const firstStream = streams[0]\r\n\r\n let w: any\r\n if (this.options.useMultipleRangeRequest) {\r\n w = executeTasks(this, tasks, firstStream, oldFileFd, reject)\r\n }\r\n else {\r\n let attemptCount = 0\r\n let actualUrl: string | null = null\r\n this.logger.info(`Differential download: ${this.options.newUrl}`)\r\n w = (index: number) => {\r\n if (index >= tasks.length) {\r\n if (this.fileMetadataBuffer != null) {\r\n firstStream.write(this.fileMetadataBuffer)\r\n }\r\n firstStream.end()\r\n return\r\n }\r\n\r\n const operation = tasks[index++]\r\n if (operation.kind === OperationKind.COPY) {\r\n copyData(operation, firstStream, oldFileFd, reject, () => w(index))\r\n }\r\n else {\r\n const requestOptions = this.createRequestOptions(\"get\", actualUrl)\r\n const range = `bytes=${operation.start}-${operation.end - 1}`\r\n requestOptions.headers!!.Range = range;\r\n (requestOptions as any).redirect = \"manual\"\r\n\r\n const debug = this.logger.debug\r\n if (debug != null) {\r\n debug(`effective url: ${actualUrl == null ? \"original\" : removeQuery(actualUrl)}, range: ${range}`)\r\n }\r\n\r\n const request = this.httpExecutor.doRequest(requestOptions, response => {\r\n // Electron net handles redirects automatically, our NodeJS test server doesn't use redirects - so, we don't check 3xx codes.\r\n if (response.statusCode >= 400) {\r\n reject(createHttpError(response))\r\n }\r\n\r\n response.pipe(firstStream, {\r\n end: false\r\n })\r\n response.once(\"end\", () => {\r\n if (++attemptCount === 100) {\r\n attemptCount = 0\r\n setTimeout(() => w(index), 1000)\r\n }\r\n else {\r\n w(index)\r\n }\r\n })\r\n })\r\n request.on(\"redirect\", (statusCode: number, method: string, redirectUrl: string) => {\r\n this.logger.info(`Redirect to ${removeQuery(redirectUrl)}`)\r\n actualUrl = redirectUrl\r\n request.followRedirect()\r\n })\r\n this.httpExecutor.addErrorAndTimeoutHandlers(request, reject)\r\n request.end()\r\n }\r\n }\r\n }\r\n\r\n w(0)\r\n })\r\n }\r\n\r\n protected async readRemoteBytes(start: number, endInclusive: number) {\r\n const buffer = Buffer.allocUnsafe((endInclusive + 1) - start)\r\n const requestOptions = this.createRequestOptions()\r\n requestOptions.headers!!.Range = `bytes=${start}-${endInclusive}`\r\n let position = 0\r\n await this.request(requestOptions, chunk => {\r\n chunk.copy(buffer, position)\r\n position += chunk.length\r\n })\r\n return buffer\r\n }\r\n\r\n private request(requestOptions: RequestOptions, dataHandler: (chunk: Buffer) => void) {\r\n return new Promise((resolve, reject) => {\r\n const request = this.httpExecutor.doRequest(requestOptions, response => {\r\n if (!checkIsRangesSupported(response, reject)) {\r\n return\r\n }\r\n\r\n response.on(\"data\", dataHandler)\r\n response.on(\"end\", () => resolve())\r\n })\r\n this.httpExecutor.addErrorAndTimeoutHandlers(request, reject)\r\n request.end()\r\n })\r\n }\r\n}\r\n\r\nexport async function readBlockMap(data: Buffer): Promise {\r\n return JSON.parse((await inflateRaw(data)).toString())\r\n}\r\n\r\nfunction formatBytes(value: number, symbol = \" KB\") {\r\n return new Intl.NumberFormat(\"en\").format((value / 1024).toFixed(2) as any) + symbol\r\n}\r\n\r\n// safety\r\nfunction removeQuery(url: string) {\r\n const index = url.indexOf(\"?\")\r\n return index < 0 ? url : url.substring(0, index)\r\n}\r\n\r\ninterface OpenedFile {\r\n readonly descriptor: number\r\n readonly path: string\r\n}"],"sourceRoot":""} diff --git a/out/differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader.d.ts b/out/differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader.d.ts new file mode 100644 index 0000000..d6b6372 --- /dev/null +++ b/out/differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader.d.ts @@ -0,0 +1,4 @@ +import { DifferentialDownloader } from "./DifferentialDownloader"; +export declare class FileWithEmbeddedBlockMapDifferentialDownloader extends DifferentialDownloader { + download(): Promise; +} diff --git a/out/differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader.js b/out/differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader.js new file mode 100644 index 0000000..4bf0e94 --- /dev/null +++ b/out/differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader.js @@ -0,0 +1,39 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.FileWithEmbeddedBlockMapDifferentialDownloader = void 0; + +function _blockMapApi() { + const data = require("builder-util-runtime/out/blockMapApi"); + + _blockMapApi = function () { + return data; + }; + + return data; +} + +function _DifferentialDownloader() { + const data = require("./DifferentialDownloader"); + + _DifferentialDownloader = function () { + return data; + }; + + return data; +} + +class FileWithEmbeddedBlockMapDifferentialDownloader extends _DifferentialDownloader().DifferentialDownloader { + async download() { + const packageInfo = this.blockAwareFileInfo; + const fileSize = packageInfo.size; + const offset = fileSize - (packageInfo.blockMapSize + 4); + this.fileMetadataBuffer = await this.readRemoteBytes(offset, fileSize - 1); + const newBlockMap = await (0, _DifferentialDownloader().readBlockMap)(this.fileMetadataBuffer.slice(0, this.fileMetadataBuffer.length - 4)); + await this.doDownload(JSON.parse((await (0, _blockMapApi().readEmbeddedBlockMapData)(this.options.oldFile))), newBlockMap); + } + +} exports.FileWithEmbeddedBlockMapDifferentialDownloader = FileWithEmbeddedBlockMapDifferentialDownloader; +//# sourceMappingURL=FileWithEmbeddedBlockMapDifferentialDownloader.js.map \ No newline at end of file diff --git a/out/differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader.js.map b/out/differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader.js.map new file mode 100644 index 0000000..49c4063 --- /dev/null +++ b/out/differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../src/differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader.ts"],"names":[],"mappings":";;;;;;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAEM,MAAO,8CAAP,SAA8D,gDAA9D,CAAoF;AACxF,QAAM,QAAN,GAAc;AACZ,UAAM,WAAW,GAAG,KAAK,kBAAzB;AACA,UAAM,QAAQ,GAAG,WAAW,CAAC,IAA7B;AACA,UAAM,MAAM,GAAG,QAAQ,IAAI,WAAW,CAAC,YAAZ,GAA6B,CAAjC,CAAvB;AACA,SAAK,kBAAL,GAA0B,MAAM,KAAK,eAAL,CAAqB,MAArB,EAA6B,QAAQ,GAAG,CAAxC,CAAhC;AACA,UAAM,WAAW,GAAG,MAAM,4CAAa,KAAK,kBAAL,CAAwB,KAAxB,CAA8B,CAA9B,EAAiC,KAAK,kBAAL,CAAwB,MAAxB,GAAiC,CAAlE,CAAb,CAA1B;AACA,UAAM,KAAK,UAAL,CAAgB,IAAI,CAAC,KAAL,EAAW,MAAM,6CAAyB,KAAK,OAAL,CAAa,OAAtC,CAAjB,EAAhB,EAAkF,WAAlF,CAAN;AACD;;AARuF,C","sourcesContent":["import { readEmbeddedBlockMapData } from \"builder-util-runtime/out/blockMapApi\"\r\nimport { DifferentialDownloader, readBlockMap } from \"./DifferentialDownloader\"\r\n\r\nexport class FileWithEmbeddedBlockMapDifferentialDownloader extends DifferentialDownloader {\r\n async download() {\r\n const packageInfo = this.blockAwareFileInfo\r\n const fileSize = packageInfo.size!!\r\n const offset = fileSize - (packageInfo.blockMapSize!! + 4)\r\n this.fileMetadataBuffer = await this.readRemoteBytes(offset, fileSize - 1)\r\n const newBlockMap = await readBlockMap(this.fileMetadataBuffer.slice(0, this.fileMetadataBuffer.length - 4))\r\n await this.doDownload(JSON.parse(await readEmbeddedBlockMapData(this.options.oldFile)), newBlockMap)\r\n }\r\n}"],"sourceRoot":""} diff --git a/out/differentialDownloader/GenericDifferentialDownloader.d.ts b/out/differentialDownloader/GenericDifferentialDownloader.d.ts new file mode 100644 index 0000000..28b55f0 --- /dev/null +++ b/out/differentialDownloader/GenericDifferentialDownloader.d.ts @@ -0,0 +1,5 @@ +import { BlockMap } from "builder-util-runtime/out/blockMapApi"; +import { DifferentialDownloader } from "./DifferentialDownloader"; +export declare class GenericDifferentialDownloader extends DifferentialDownloader { + download(oldBlockMap: BlockMap, newBlockMap: BlockMap): Promise; +} diff --git a/out/differentialDownloader/GenericDifferentialDownloader.js b/out/differentialDownloader/GenericDifferentialDownloader.js new file mode 100644 index 0000000..86be5bb --- /dev/null +++ b/out/differentialDownloader/GenericDifferentialDownloader.js @@ -0,0 +1,24 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.GenericDifferentialDownloader = void 0; + +function _DifferentialDownloader() { + const data = require("./DifferentialDownloader"); + + _DifferentialDownloader = function () { + return data; + }; + + return data; +} + +class GenericDifferentialDownloader extends _DifferentialDownloader().DifferentialDownloader { + async download(oldBlockMap, newBlockMap) { + await this.doDownload(oldBlockMap, newBlockMap); + } + +} exports.GenericDifferentialDownloader = GenericDifferentialDownloader; +//# sourceMappingURL=GenericDifferentialDownloader.js.map \ No newline at end of file diff --git a/out/differentialDownloader/GenericDifferentialDownloader.js.map b/out/differentialDownloader/GenericDifferentialDownloader.js.map new file mode 100644 index 0000000..14ebbca --- /dev/null +++ b/out/differentialDownloader/GenericDifferentialDownloader.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../src/differentialDownloader/GenericDifferentialDownloader.ts"],"names":[],"mappings":";;;;;;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAEM,MAAO,6BAAP,SAA6C,gDAA7C,CAAmE;AACvE,QAAM,QAAN,CAAe,WAAf,EAAsC,WAAtC,EAA2D;AACzD,UAAM,KAAK,UAAL,CAAgB,WAAhB,EAA6B,WAA7B,CAAN;AACD;;AAHsE,C","sourcesContent":["import { BlockMap } from \"builder-util-runtime/out/blockMapApi\"\r\nimport { DifferentialDownloader } from \"./DifferentialDownloader\"\r\n\r\nexport class GenericDifferentialDownloader extends DifferentialDownloader {\r\n async download(oldBlockMap: BlockMap, newBlockMap: BlockMap) {\r\n await this.doDownload(oldBlockMap, newBlockMap)\r\n }\r\n}"],"sourceRoot":""} diff --git a/out/differentialDownloader/downloadPlanBuilder.d.ts b/out/differentialDownloader/downloadPlanBuilder.d.ts new file mode 100644 index 0000000..efb2bc9 --- /dev/null +++ b/out/differentialDownloader/downloadPlanBuilder.d.ts @@ -0,0 +1,12 @@ +import { BlockMap } from "builder-util-runtime/out/blockMapApi"; +import { Logger } from "../main"; +export declare enum OperationKind { + COPY = 0, + DOWNLOAD = 1 +} +export interface Operation { + kind: OperationKind; + start: number; + end: number; +} +export declare function computeOperations(oldBlockMap: BlockMap, newBlockMap: BlockMap, logger: Logger): Operation[]; diff --git a/out/differentialDownloader/downloadPlanBuilder.js b/out/differentialDownloader/downloadPlanBuilder.js new file mode 100644 index 0000000..d60993a --- /dev/null +++ b/out/differentialDownloader/downloadPlanBuilder.js @@ -0,0 +1,126 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.computeOperations = computeOperations; +exports.OperationKind = void 0; +var OperationKind; +exports.OperationKind = OperationKind; + +(function (OperationKind) { + OperationKind[OperationKind["COPY"] = 0] = "COPY"; + OperationKind[OperationKind["DOWNLOAD"] = 1] = "DOWNLOAD"; +})(OperationKind || (exports.OperationKind = OperationKind = {})); + +function computeOperations(oldBlockMap, newBlockMap, logger) { + const nameToOldBlocks = buildBlockFileMap(oldBlockMap.files); + const nameToNewBlocks = buildBlockFileMap(newBlockMap.files); + const oldEntryMap = buildEntryMap(oldBlockMap.files); + let lastOperation = null; + const operations = []; + + for (const blockMapFile of newBlockMap.files) { + const name = blockMapFile.name; + const oldEntry = oldEntryMap.get(name); + + if (oldEntry == null) { + // new file + operations.push({ + kind: OperationKind.DOWNLOAD, + start: blockMapFile.offset, + end: blockMapFile.offset + blockMapFile.sizes.reduce((accumulator, currentValue) => accumulator + currentValue) + }); + continue; + } + + const newFile = nameToNewBlocks.get(name); + let changedBlockCount = 0; + const { + checksumToOffset: checksumToOldOffset, + checksumToOldSize + } = buildChecksumMap(nameToOldBlocks.get(name), oldEntry.offset); + let newOffset = blockMapFile.offset; + + for (let i = 0; i < newFile.checksums.length; newOffset += newFile.sizes[i], i++) { + const blockSize = newFile.sizes[i]; + const checksum = newFile.checksums[i]; + let oldOffset = checksumToOldOffset.get(checksum); + + if (oldOffset != null && checksumToOldSize.get(checksum) !== blockSize) { + logger.warn(`Checksum ("${checksum}") matches, but size differs (old: ${checksumToOldSize.get(checksum)}, new: ${blockSize})`); + oldOffset = null; + } + + if (oldOffset == null) { + changedBlockCount++; + + if (lastOperation == null || lastOperation.kind !== OperationKind.DOWNLOAD || lastOperation.end !== newOffset) { + lastOperation = { + kind: OperationKind.DOWNLOAD, + start: newOffset, + end: newOffset + blockSize + }; + operations.push(lastOperation); + } else { + lastOperation.end += blockSize; + } + } else if (lastOperation == null || lastOperation.kind !== OperationKind.COPY || lastOperation.end !== oldOffset) { + lastOperation = { + kind: OperationKind.COPY, + start: oldOffset, + end: oldOffset + blockSize + }; + operations.push(lastOperation); + } else { + lastOperation.end += blockSize; + } + } + + if (changedBlockCount > 0) { + logger.info(`File${blockMapFile.name === "file" ? "" : " " + blockMapFile.name} has ${changedBlockCount} changed blocks`); + } + } + + return operations; +} + +function buildChecksumMap(file, fileOffset) { + const checksumToOffset = new Map(); + const checksumToSize = new Map(); + let offset = fileOffset; + + for (let i = 0; i < file.checksums.length; i++) { + const checksum = file.checksums[i]; + const size = file.sizes[i]; + checksumToOffset.set(checksum, offset); + checksumToSize.set(checksum, size); + offset += size; + } + + return { + checksumToOffset, + checksumToOldSize: checksumToSize + }; +} + +function buildEntryMap(list) { + const result = new Map(); + + for (const item of list) { + result.set(item.name, item); + } + + return result; +} + +function buildBlockFileMap(list) { + const result = new Map(); + + for (const item of list) { + result.set(item.name, item); + } + + return result; +} +//# sourceMappingURL=downloadPlanBuilder.js.map \ No newline at end of file diff --git a/out/differentialDownloader/downloadPlanBuilder.js.map b/out/differentialDownloader/downloadPlanBuilder.js.map new file mode 100644 index 0000000..b5689d5 --- /dev/null +++ b/out/differentialDownloader/downloadPlanBuilder.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../src/differentialDownloader/downloadPlanBuilder.ts"],"names":[],"mappings":"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","sourcesContent":["import { BlockMap, BlockMapFile } from \"builder-util-runtime/out/blockMapApi\"\r\nimport { Logger } from \"../main\"\r\n\r\nexport enum OperationKind {\r\n COPY, DOWNLOAD\r\n}\r\n\r\nexport interface Operation {\r\n kind: OperationKind\r\n\r\n start: number\r\n end: number\r\n}\r\n\r\nexport function computeOperations(oldBlockMap: BlockMap, newBlockMap: BlockMap, logger: Logger) {\r\n const nameToOldBlocks = buildBlockFileMap(oldBlockMap.files)\r\n const nameToNewBlocks = buildBlockFileMap(newBlockMap.files)\r\n\r\n const oldEntryMap = buildEntryMap(oldBlockMap.files)\r\n\r\n let lastOperation: Operation | null = null\r\n\r\n const operations: Array = []\r\n for (const blockMapFile of newBlockMap.files) {\r\n const name = blockMapFile.name\r\n const oldEntry = oldEntryMap.get(name)\r\n if (oldEntry == null) {\r\n // new file\r\n operations.push({\r\n kind: OperationKind.DOWNLOAD,\r\n start: blockMapFile.offset,\r\n end: blockMapFile.offset + blockMapFile.sizes.reduce((accumulator, currentValue) => accumulator + currentValue),\r\n })\r\n continue\r\n }\r\n\r\n const newFile = nameToNewBlocks.get(name)!!\r\n let changedBlockCount = 0\r\n\r\n const {checksumToOffset: checksumToOldOffset, checksumToOldSize} = buildChecksumMap(nameToOldBlocks.get(name)!!, oldEntry.offset)\r\n\r\n let newOffset = blockMapFile.offset\r\n for (let i = 0; i < newFile.checksums.length; newOffset += newFile.sizes[i], i++) {\r\n const blockSize = newFile.sizes[i]\r\n const checksum = newFile.checksums[i]\r\n let oldOffset: number | null | undefined = checksumToOldOffset.get(checksum)\r\n if (oldOffset != null && checksumToOldSize.get(checksum) !== blockSize) {\r\n logger.warn(`Checksum (\"${checksum}\") matches, but size differs (old: ${checksumToOldSize.get(checksum)}, new: ${blockSize})`)\r\n oldOffset = null\r\n }\r\n\r\n if (oldOffset == null) {\r\n changedBlockCount++\r\n\r\n if (lastOperation == null || lastOperation.kind !== OperationKind.DOWNLOAD || lastOperation.end !== newOffset) {\r\n lastOperation = {\r\n kind: OperationKind.DOWNLOAD,\r\n start: newOffset,\r\n end: newOffset + blockSize,\r\n }\r\n operations.push(lastOperation)\r\n }\r\n else {\r\n lastOperation.end += blockSize\r\n }\r\n }\r\n else if (lastOperation == null || lastOperation.kind !== OperationKind.COPY || lastOperation.end !== oldOffset) {\r\n lastOperation = {\r\n kind: OperationKind.COPY,\r\n start: oldOffset,\r\n end: oldOffset + blockSize,\r\n }\r\n operations.push(lastOperation)\r\n }\r\n else {\r\n lastOperation.end += blockSize\r\n }\r\n }\r\n\r\n if (changedBlockCount > 0) {\r\n logger.info(`File${blockMapFile.name === \"file\" ? \"\" : (\" \" + blockMapFile.name)} has ${changedBlockCount} changed blocks`)\r\n }\r\n }\r\n return operations\r\n}\r\n\r\nfunction buildChecksumMap(file: BlockMapFile, fileOffset: number) {\r\n const checksumToOffset = new Map()\r\n const checksumToSize = new Map()\r\n let offset = fileOffset\r\n for (let i = 0; i < file.checksums.length; i++) {\r\n const checksum = file.checksums[i]\r\n const size = file.sizes[i]\r\n checksumToOffset.set(checksum, offset)\r\n checksumToSize.set(checksum, size)\r\n offset += size\r\n }\r\n return {checksumToOffset, checksumToOldSize: checksumToSize}\r\n}\r\n\r\nfunction buildEntryMap(list: Array) {\r\n const result = new Map()\r\n for (const item of list) {\r\n result.set(item.name, item)\r\n }\r\n return result\r\n}\r\n\r\nfunction buildBlockFileMap(list: Array) {\r\n const result = new Map()\r\n for (const item of list) {\r\n result.set(item.name, item)\r\n }\r\n return result\r\n}"],"sourceRoot":""} diff --git a/out/differentialDownloader/multipleRangeDownloader.d.ts b/out/differentialDownloader/multipleRangeDownloader.d.ts new file mode 100644 index 0000000..7af31e0 --- /dev/null +++ b/out/differentialDownloader/multipleRangeDownloader.d.ts @@ -0,0 +1,9 @@ +/// +import { IncomingMessage } from "http"; +import { Writable } from "stream"; +import { PartListDataTask } from "./DataSplitter"; +import { DifferentialDownloader } from "./DifferentialDownloader"; +import { Operation } from "./downloadPlanBuilder"; +export declare function executeTasks(differentialDownloader: DifferentialDownloader, tasks: Array, out: Writable, oldFileFd: number, reject: (error: Error) => void): (taskOffset: number) => void; +export declare function _executeTasks(differentialDownloader: DifferentialDownloader, options: PartListDataTask, out: Writable, resolve: () => void, reject: (error: Error) => void): void; +export declare function checkIsRangesSupported(response: IncomingMessage, reject: (error: Error) => void): boolean; diff --git a/out/differentialDownloader/multipleRangeDownloader.js b/out/differentialDownloader/multipleRangeDownloader.js new file mode 100644 index 0000000..b7537ad --- /dev/null +++ b/out/differentialDownloader/multipleRangeDownloader.js @@ -0,0 +1,156 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.executeTasks = executeTasks; +exports._executeTasks = _executeTasks; +exports.checkIsRangesSupported = checkIsRangesSupported; + +function _builderUtilRuntime() { + const data = require("builder-util-runtime"); + + _builderUtilRuntime = function () { + return data; + }; + + return data; +} + +function _DataSplitter() { + const data = require("./DataSplitter"); + + _DataSplitter = function () { + return data; + }; + + return data; +} + +function _downloadPlanBuilder() { + const data = require("./downloadPlanBuilder"); + + _downloadPlanBuilder = function () { + return data; + }; + + return data; +} + +function executeTasks(differentialDownloader, tasks, out, oldFileFd, reject) { + const w = taskOffset => { + if (taskOffset >= tasks.length) { + if (differentialDownloader.fileMetadataBuffer != null) { + out.write(differentialDownloader.fileMetadataBuffer); + } + + out.end(); + return; + } + + const nextOffset = taskOffset + (differentialDownloader.options.useMultipleRangeRequest === false ? 1 : 1000); + + _executeTasks(differentialDownloader, { + tasks, + start: taskOffset, + end: Math.min(tasks.length, nextOffset), + oldFileFd + }, out, () => w(nextOffset), reject); + }; + + return w; +} + +function _executeTasks(differentialDownloader, options, out, resolve, reject) { + let ranges = "bytes="; + let partCount = 0; + const partIndexToTaskIndex = new Map(); + const partIndexToLength = []; + + for (let i = options.start; i < options.end; i++) { + const task = options.tasks[i]; + + if (task.kind === _downloadPlanBuilder().OperationKind.DOWNLOAD) { + ranges += `${task.start}-${task.end - 1}, `; + partIndexToTaskIndex.set(partCount, i); + partCount++; + partIndexToLength.push(task.end - task.start); + } + } + + if (partCount <= 1) { + // the only remote range - copy + const w = index => { + if (index >= options.end) { + resolve(); + return; + } + + const task = options.tasks[index++]; + + if (task.kind === _downloadPlanBuilder().OperationKind.COPY) { + (0, _DataSplitter().copyData)(task, out, options.oldFileFd, reject, () => w(index)); + } else { + const requestOptions = differentialDownloader.createRequestOptions("get"); + requestOptions.headers.Range = `bytes=${task.start}-${task.end - 1}`; + const request = differentialDownloader.httpExecutor.doRequest(requestOptions, response => { + if (!checkIsRangesSupported(response, reject)) { + return; + } + + response.pipe(out, { + end: false + }); + response.once("end", () => w(index)); + }); + differentialDownloader.httpExecutor.addErrorAndTimeoutHandlers(request, reject); + request.end(); + } + }; + + w(options.start); + return; + } + + const requestOptions = differentialDownloader.createRequestOptions("get"); + requestOptions.headers.Range = ranges.substring(0, ranges.length - 2); + const request = differentialDownloader.httpExecutor.doRequest(requestOptions, response => { + if (!checkIsRangesSupported(response, reject)) { + return; + } + + const contentType = (0, _builderUtilRuntime().safeGetHeader)(response, "content-type"); + const m = /^multipart\/.+?(?:; boundary=(?:(?:"(.+)")|(?:([^\s]+))))$/i.exec(contentType); + + if (m == null) { + reject(new Error(`Content-Type "multipart/byteranges" is expected, but got "${contentType}"`)); + return; + } + + const dicer = new (_DataSplitter().DataSplitter)(out, options, partIndexToTaskIndex, m[1] || m[2], partIndexToLength, resolve); + dicer.on("error", reject); + response.pipe(dicer); + }); + differentialDownloader.httpExecutor.addErrorAndTimeoutHandlers(request, reject); + request.end(); +} + +function checkIsRangesSupported(response, reject) { + // Electron net handles redirects automatically, our NodeJS test server doesn't use redirects - so, we don't check 3xx codes. + if (response.statusCode >= 400) { + reject((0, _builderUtilRuntime().createHttpError)(response)); + return false; + } + + if (response.statusCode !== 206) { + const acceptRanges = (0, _builderUtilRuntime().safeGetHeader)(response, "accept-ranges"); + + if (acceptRanges == null || acceptRanges === "none") { + reject(new Error(`Server doesn't support Accept-Ranges (response code ${response.statusCode})`)); + return false; + } + } + + return true; +} +//# sourceMappingURL=multipleRangeDownloader.js.map \ No newline at end of file diff --git a/out/differentialDownloader/multipleRangeDownloader.js.map b/out/differentialDownloader/multipleRangeDownloader.js.map new file mode 100644 index 0000000..aa50e1f --- /dev/null +++ b/out/differentialDownloader/multipleRangeDownloader.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../src/differentialDownloader/multipleRangeDownloader.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHttpError, safeGetHeader } from \"builder-util-runtime\"\r\nimport { IncomingMessage } from \"http\"\r\nimport { Writable } from \"stream\"\r\nimport { copyData, DataSplitter, PartListDataTask } from \"./DataSplitter\"\r\nimport { DifferentialDownloader } from \"./DifferentialDownloader\"\r\nimport { Operation, OperationKind } from \"./downloadPlanBuilder\"\r\n\r\nexport function executeTasks(differentialDownloader: DifferentialDownloader, tasks: Array, out: Writable, oldFileFd: number, reject: (error: Error) => void) {\r\n const w = (taskOffset: number) => {\r\n if (taskOffset >= tasks.length) {\r\n if (differentialDownloader.fileMetadataBuffer != null) {\r\n out.write(differentialDownloader.fileMetadataBuffer)\r\n }\r\n out.end()\r\n return\r\n }\r\n\r\n const nextOffset = taskOffset + (differentialDownloader.options.useMultipleRangeRequest === false ? 1 : 1000)\r\n _executeTasks(differentialDownloader, {\r\n tasks,\r\n start: taskOffset,\r\n end: Math.min(tasks.length, nextOffset),\r\n oldFileFd,\r\n }, out, () => w(nextOffset), reject)\r\n }\r\n return w\r\n}\r\n\r\nexport function _executeTasks(differentialDownloader: DifferentialDownloader, options: PartListDataTask, out: Writable, resolve: () => void, reject: (error: Error) => void) {\r\n let ranges = \"bytes=\"\r\n let partCount = 0\r\n const partIndexToTaskIndex = new Map()\r\n const partIndexToLength: Array = []\r\n for (let i = options.start; i < options.end; i++) {\r\n const task = options.tasks[i]\r\n if (task.kind === OperationKind.DOWNLOAD) {\r\n ranges += `${task.start}-${task.end - 1}, `\r\n partIndexToTaskIndex.set(partCount, i)\r\n partCount++\r\n partIndexToLength.push(task.end - task.start)\r\n }\r\n }\r\n\r\n if (partCount <= 1) {\r\n // the only remote range - copy\r\n const w = (index: number) => {\r\n if (index >= options.end) {\r\n resolve()\r\n return\r\n }\r\n\r\n const task = options.tasks[index++]\r\n\r\n if (task.kind === OperationKind.COPY) {\r\n copyData(task, out, options.oldFileFd, reject, () => w(index))\r\n }\r\n else {\r\n const requestOptions = differentialDownloader.createRequestOptions(\"get\")\r\n requestOptions.headers!!.Range = `bytes=${task.start}-${task.end - 1}`\r\n const request = differentialDownloader.httpExecutor.doRequest(requestOptions, response => {\r\n if (!checkIsRangesSupported(response, reject)) {\r\n return\r\n }\r\n\r\n response.pipe(out, {\r\n end: false\r\n })\r\n response.once(\"end\", () => w(index))\r\n })\r\n differentialDownloader.httpExecutor.addErrorAndTimeoutHandlers(request, reject)\r\n request.end()\r\n }\r\n }\r\n\r\n w(options.start)\r\n return\r\n }\r\n\r\n const requestOptions = differentialDownloader.createRequestOptions(\"get\")\r\n requestOptions.headers!!.Range = ranges.substring(0, ranges.length - 2)\r\n const request = differentialDownloader.httpExecutor.doRequest(requestOptions, response => {\r\n if (!checkIsRangesSupported(response, reject)) {\r\n return\r\n }\r\n\r\n const contentType = safeGetHeader(response, \"content-type\")\r\n const m = /^multipart\\/.+?(?:; boundary=(?:(?:\"(.+)\")|(?:([^\\s]+))))$/i.exec(contentType)\r\n if (m == null) {\r\n reject(new Error(`Content-Type \"multipart/byteranges\" is expected, but got \"${contentType}\"`))\r\n return\r\n }\r\n\r\n const dicer = new DataSplitter(out, options, partIndexToTaskIndex, m[1] || m[2], partIndexToLength, resolve)\r\n dicer.on(\"error\", reject)\r\n response.pipe(dicer)\r\n })\r\n differentialDownloader.httpExecutor.addErrorAndTimeoutHandlers(request, reject)\r\n request.end()\r\n}\r\n\r\nexport function checkIsRangesSupported(response: IncomingMessage, reject: (error: Error) => void): boolean {\r\n // Electron net handles redirects automatically, our NodeJS test server doesn't use redirects - so, we don't check 3xx codes.\r\n if (response.statusCode!! >= 400) {\r\n reject(createHttpError(response))\r\n return false\r\n }\r\n\r\n if (response.statusCode !== 206) {\r\n const acceptRanges = safeGetHeader(response, \"accept-ranges\")\r\n if (acceptRanges == null || acceptRanges === \"none\") {\r\n reject(new Error(`Server doesn't support Accept-Ranges (response code ${response.statusCode})`))\r\n return false\r\n }\r\n }\r\n return true\r\n}"],"sourceRoot":""} diff --git a/out/electronHttpExecutor.d.ts b/out/electronHttpExecutor.d.ts new file mode 100644 index 0000000..2430596 --- /dev/null +++ b/out/electronHttpExecutor.d.ts @@ -0,0 +1,12 @@ +/// +import { DownloadOptions, HttpExecutor } from "builder-util-runtime"; +import { RequestOptions } from "http"; +export declare type LoginCallback = (username: string, password: string) => void; +export declare class ElectronHttpExecutor extends HttpExecutor { + private readonly proxyLoginCallback?; + constructor(proxyLoginCallback?: ((authInfo: any, callback: LoginCallback) => void) | undefined); + download(url: string, destination: string, options: DownloadOptions): Promise; + doRequest(options: any, callback: (response: any) => void): any; + private addProxyLoginHandler; + protected addRedirectHandlers(request: any, options: RequestOptions, reject: (error: Error) => void, redirectCount: number, handler: (options: RequestOptions) => void): void; +} diff --git a/out/electronHttpExecutor.js b/out/electronHttpExecutor.js new file mode 100644 index 0000000..e8189eb --- /dev/null +++ b/out/electronHttpExecutor.js @@ -0,0 +1,94 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.ElectronHttpExecutor = void 0; + +function _builderUtilRuntime() { + const data = require("builder-util-runtime"); + + _builderUtilRuntime = function () { + return data; + }; + + return data; +} + +function _electron() { + const data = require("electron"); + + _electron = function () { + return data; + }; + + return data; +} + +function _fsExtraP() { + const data = require("fs-extra-p"); + + _fsExtraP = function () { + return data; + }; + + return data; +} + +var path = _interopRequireWildcard(require("path")); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +class ElectronHttpExecutor extends _builderUtilRuntime().HttpExecutor { + constructor(proxyLoginCallback) { + super(); + this.proxyLoginCallback = proxyLoginCallback; + } + + async download(url, destination, options) { + if (options == null || !options.skipDirCreation) { + await (0, _fsExtraP().ensureDir)(path.dirname(destination)); + } + + return await options.cancellationToken.createPromise((resolve, reject, onCancel) => { + this.doDownload(Object.assign({}, (0, _builderUtilRuntime().configureRequestOptionsFromUrl)(url, { + headers: options.headers || undefined + }), { + redirect: "manual" + }), destination, 0, options, error => { + if (error == null) { + resolve(destination); + } else { + reject(error); + } + }, onCancel); + }); + } + + doRequest(options, callback) { + const request = _electron().net.request(options); + + request.on("response", callback); + this.addProxyLoginHandler(request); + return request; + } + + addProxyLoginHandler(request) { + if (this.proxyLoginCallback != null) { + request.on("login", this.proxyLoginCallback); + } + } + + addRedirectHandlers(request, options, reject, redirectCount, handler) { + request.on("redirect", (statusCode, method, redirectUrl) => { + if (redirectCount > 10) { + reject(new Error("Too many redirects (> 10)")); + return; + } + + handler(_builderUtilRuntime().HttpExecutor.prepareRedirectUrlOptions(redirectUrl, options)); + }); + } + +} exports.ElectronHttpExecutor = ElectronHttpExecutor; +//# sourceMappingURL=electronHttpExecutor.js.map \ No newline at end of file diff --git a/out/electronHttpExecutor.js.map b/out/electronHttpExecutor.js.map new file mode 100644 index 0000000..b860725 --- /dev/null +++ b/out/electronHttpExecutor.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/electronHttpExecutor.ts"],"names":[],"mappings":";;;;;;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAEA;;;;AAIM,MAAO,oBAAP,SAAoC,kCAApC,CAAwE;AAC5E,EAAA,WAAA,CAA6B,kBAA7B,EAAkG;AAChG;AAD2B,SAAA,kBAAA,GAAA,kBAAA;AAE5B;;AAED,QAAM,QAAN,CAAe,GAAf,EAA4B,WAA5B,EAAiD,OAAjD,EAAyE;AACvE,QAAI,OAAO,IAAI,IAAX,IAAmB,CAAC,OAAO,CAAC,eAAhC,EAAiD;AAC/C,YAAM,2BAAU,IAAI,CAAC,OAAL,CAAa,WAAb,CAAV,CAAN;AACD;;AAED,WAAO,MAAM,OAAO,CAAC,iBAAR,CAA0B,aAA1B,CAAgD,CAAC,OAAD,EAAU,MAAV,EAAkB,QAAlB,KAA8B;AACzF,WAAK,UAAL,CAAe,MAAA,CAAA,MAAA,CAAA,EAAA,EACV,0DAA+B,GAA/B,EAAoC;AACrC,QAAA,OAAO,EAAE,OAAO,CAAC,OAAR,IAAmB;AADS,OAApC,CADU,EAGX;AACF,QAAA,QAAQ,EAAE;AADR,OAHW,CAAf,EAKG,WALH,EAKgB,CALhB,EAKmB,OALnB,EAK4B,KAAK,IAAG;AAClC,YAAI,KAAK,IAAI,IAAb,EAAmB;AACjB,UAAA,OAAO,CAAC,WAAD,CAAP;AACD,SAFD,MAGK;AACH,UAAA,MAAM,CAAC,KAAD,CAAN;AACD;AACF,OAZD,EAYG,QAZH;AAaD,KAdY,CAAb;AAeD;;AAEM,EAAA,SAAS,CAAC,OAAD,EAAe,QAAf,EAAgD;AAC9D,UAAM,OAAO,GAAG,gBAAI,OAAJ,CAAY,OAAZ,CAAhB;;AACA,IAAA,OAAO,CAAC,EAAR,CAAW,UAAX,EAAuB,QAAvB;AACA,SAAK,oBAAL,CAA0B,OAA1B;AACA,WAAO,OAAP;AACD;;AAEO,EAAA,oBAAoB,CAAC,OAAD,EAAgC;AAC1D,QAAI,KAAK,kBAAL,IAA2B,IAA/B,EAAqC;AACnC,MAAA,OAAO,CAAC,EAAR,CAAW,OAAX,EAAoB,KAAK,kBAAzB;AACD;AACF;;AAES,EAAA,mBAAmB,CAAC,OAAD,EAAe,OAAf,EAAwC,MAAxC,EAAwE,aAAxE,EAA+F,OAA/F,EAAyI;AACpK,IAAA,OAAO,CAAC,EAAR,CAAW,UAAX,EAAuB,CAAC,UAAD,EAAqB,MAArB,EAAqC,WAArC,KAA4D;AACjF,UAAI,aAAa,GAAG,EAApB,EAAwB;AACtB,QAAA,MAAM,CAAC,IAAI,KAAJ,CAAU,2BAAV,CAAD,CAAN;AACA;AACD;;AAED,MAAA,OAAO,CAAC,mCAAa,yBAAb,CAAuC,WAAvC,EAAoD,OAApD,CAAD,CAAP;AACD,KAPD;AAQD;;AAjD2E,C","sourcesContent":["import { configureRequestOptionsFromUrl, DownloadOptions, HttpExecutor } from \"builder-util-runtime\"\r\nimport { net } from \"electron\"\r\nimport { ensureDir } from \"fs-extra-p\"\r\nimport { RequestOptions } from \"http\"\r\nimport * as path from \"path\"\r\n\r\nexport type LoginCallback = (username: string, password: string) => void\r\n\r\nexport class ElectronHttpExecutor extends HttpExecutor {\r\n constructor(private readonly proxyLoginCallback?: (authInfo: any, callback: LoginCallback) => void) {\r\n super()\r\n }\r\n\r\n async download(url: string, destination: string, options: DownloadOptions): Promise {\r\n if (options == null || !options.skipDirCreation) {\r\n await ensureDir(path.dirname(destination))\r\n }\r\n\r\n return await options.cancellationToken.createPromise((resolve, reject, onCancel) => {\r\n this.doDownload({\r\n ...configureRequestOptionsFromUrl(url, {\r\n headers: options.headers || undefined,\r\n }),\r\n redirect: \"manual\",\r\n }, destination, 0, options, error => {\r\n if (error == null) {\r\n resolve(destination)\r\n }\r\n else {\r\n reject(error)\r\n }\r\n }, onCancel)\r\n })\r\n }\r\n\r\n public doRequest(options: any, callback: (response: any) => void): any {\r\n const request = net.request(options)\r\n request.on(\"response\", callback)\r\n this.addProxyLoginHandler(request)\r\n return request\r\n }\r\n\r\n private addProxyLoginHandler(request: Electron.ClientRequest) {\r\n if (this.proxyLoginCallback != null) {\r\n request.on(\"login\", this.proxyLoginCallback)\r\n }\r\n }\r\n\r\n protected addRedirectHandlers(request: any, options: RequestOptions, reject: (error: Error) => void, redirectCount: number, handler: (options: RequestOptions) => void) {\r\n request.on(\"redirect\", (statusCode: number, method: string, redirectUrl: string) => {\r\n if (redirectCount > 10) {\r\n reject(new Error(\"Too many redirects (> 10)\"))\r\n return\r\n }\r\n\r\n handler(HttpExecutor.prepareRedirectUrlOptions(redirectUrl, options))\r\n })\r\n }\r\n}"],"sourceRoot":""} diff --git a/out/main.d.ts b/out/main.d.ts new file mode 100644 index 0000000..ee6e0d9 --- /dev/null +++ b/out/main.d.ts @@ -0,0 +1,50 @@ +/// +import { CancellationToken, PackageFileInfo, ProgressInfo, UpdateFileInfo, UpdateInfo } from "builder-util-runtime"; +import { EventEmitter } from "events"; +import { URL } from "url"; +import { AppUpdater } from "./AppUpdater"; +import { LoginCallback } from "./electronHttpExecutor"; +export { AppUpdater, NoOpLogger } from "./AppUpdater"; +export { UpdateInfo }; +export { CancellationToken } from "builder-util-runtime"; +export { Provider } from "./providers/Provider"; +export declare const autoUpdater: AppUpdater; +export interface ResolvedUpdateFileInfo { + readonly url: URL; + readonly info: UpdateFileInfo; + packageInfo?: PackageFileInfo; +} +export declare function getDefaultChannelName(): string; +export declare function getCustomChannelName(channel: string): string; +export declare function getCurrentPlatform(): string; +export declare function isUseOldMacProvider(): boolean; +export declare function getChannelFilename(channel: string): string; +export interface UpdateCheckResult { + readonly updateInfo: UpdateInfo; + readonly downloadPromise?: Promise> | null; + readonly cancellationToken?: CancellationToken; + /** @deprecated */ + readonly versionInfo: UpdateInfo; +} +export declare type UpdaterEvents = "login" | "checking-for-update" | "update-available" | "update-cancelled" | "download-progress" | "update-downloaded" | "error"; +export declare const DOWNLOAD_PROGRESS: UpdaterEvents; +export declare const UPDATE_DOWNLOADED: UpdaterEvents; +export declare type LoginHandler = (authInfo: any, callback: LoginCallback) => void; +export declare class UpdaterSignal { + private emitter; + constructor(emitter: EventEmitter); + /** + * Emitted when an authenticating proxy is [asking for user credentials](https://github.com/electron/electron/blob/master/docs/api/client-request.md#event-login). + */ + login(handler: LoginHandler): void; + progress(handler: (info: ProgressInfo) => void): void; + updateDownloaded(handler: (info: UpdateInfo) => void): void; + updateCancelled(handler: (info: UpdateInfo) => void): void; +} +export interface Logger { + info(message?: any): void; + warn(message?: any): void; + error(message?: any): void; + debug?(message: string): void; +} +export declare function newUrlFromBase(pathname: string, baseUrl: URL, addRandomQueryToAvoidCaching?: boolean): URL; diff --git a/out/main.js b/out/main.js new file mode 100644 index 0000000..ce277ca --- /dev/null +++ b/out/main.js @@ -0,0 +1,209 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.getDefaultChannelName = getDefaultChannelName; +exports.getCustomChannelName = getCustomChannelName; +exports.getCurrentPlatform = getCurrentPlatform; +exports.isUseOldMacProvider = isUseOldMacProvider; +exports.getChannelFilename = getChannelFilename; +exports.newBaseUrl = newBaseUrl; +exports.newUrlFromBase = newUrlFromBase; +Object.defineProperty(exports, "AppUpdater", { + enumerable: true, + get: function () { + return _AppUpdater().AppUpdater; + } +}); +Object.defineProperty(exports, "NoOpLogger", { + enumerable: true, + get: function () { + return _AppUpdater().NoOpLogger; + } +}); +Object.defineProperty(exports, "CancellationToken", { + enumerable: true, + get: function () { + return _builderUtilRuntime().CancellationToken; + } +}); +Object.defineProperty(exports, "Provider", { + enumerable: true, + get: function () { + return _Provider().Provider; + } +}); +exports.UpdaterSignal = exports.UPDATE_DOWNLOADED = exports.DOWNLOAD_PROGRESS = void 0; + +function _url() { + const data = require("url"); + + _url = function () { + return data; + }; + + return data; +} + +function _AppUpdater() { + const data = require("./AppUpdater"); + + _AppUpdater = function () { + return data; + }; + + return data; +} + +function _builderUtilRuntime() { + const data = require("builder-util-runtime"); + + _builderUtilRuntime = function () { + return data; + }; + + return data; +} + +function _Provider() { + const data = require("./providers/Provider"); + + _Provider = function () { + return data; + }; + + return data; +} + +// autoUpdater to mimic electron bundled autoUpdater +let _autoUpdater; + +function _load_autoUpdater() { + // tslint:disable:prefer-conditional-expression + if (process.platform === "win32") { + _autoUpdater = new (require("./NsisUpdater").NsisUpdater)(); + } else if (process.platform === "darwin") { + _autoUpdater = new (require("./MacUpdater").MacUpdater)(); + } else { + _autoUpdater = new (require("./AppImageUpdater").AppImageUpdater)(); + } + + return _autoUpdater; +} + +Object.defineProperty(exports, "autoUpdater", { + enumerable: true, + get: () => { + return _autoUpdater || _load_autoUpdater(); + } +}); // due to historical reasons for windows we use channel name without platform specifier + +function getDefaultChannelName() { + return `latest${getChannelFilePrefix()}`; +} + +function getChannelFilePrefix() { + const currentPlatform = getCurrentPlatform(); + + if (currentPlatform === "linux") { + const arch = process.env.TEST_UPDATER_ARCH || process.arch; + const archSuffix = arch === "x64" ? "" : `-${arch}`; + return "-linux" + archSuffix; + } else { + return currentPlatform === "darwin" ? "-mac" : ""; + } +} + +function getCustomChannelName(channel) { + return `${channel}${getChannelFilePrefix()}`; +} + +function getCurrentPlatform() { + return process.env.TEST_UPDATER_PLATFORM || process.platform; +} + +function isUseOldMacProvider() { + // getCurrentPlatform() === "darwin" + return false; +} + +function getChannelFilename(channel) { + return `${channel}.yml`; +} + +const DOWNLOAD_PROGRESS = "download-progress"; +exports.DOWNLOAD_PROGRESS = DOWNLOAD_PROGRESS; +const UPDATE_DOWNLOADED = "update-downloaded"; +exports.UPDATE_DOWNLOADED = UPDATE_DOWNLOADED; + +class UpdaterSignal { + constructor(emitter) { + this.emitter = emitter; + } + /** + * Emitted when an authenticating proxy is [asking for user credentials](https://github.com/electron/electron/blob/master/docs/api/client-request.md#event-login). + */ + + + login(handler) { + addHandler(this.emitter, "login", handler); + } + + progress(handler) { + addHandler(this.emitter, DOWNLOAD_PROGRESS, handler); + } + + updateDownloaded(handler) { + addHandler(this.emitter, UPDATE_DOWNLOADED, handler); + } + + updateCancelled(handler) { + addHandler(this.emitter, "update-cancelled", handler); + } + +} + +exports.UpdaterSignal = UpdaterSignal; +const isLogEvent = false; + +function addHandler(emitter, event, handler) { + if (isLogEvent) { + emitter.on(event, (...args) => { + console.log("%s %s", event, args); + handler.apply(null, args); + }); + } else { + emitter.on(event, handler); + } +} // if baseUrl path doesn't ends with /, this path will be not prepended to passed pathname for new URL(input, base) + +/** @internal */ + + +function newBaseUrl(url) { + const result = new (_url().URL)(url); + + if (!result.pathname.endsWith("/")) { + result.pathname += "/"; + } + + return result; +} // addRandomQueryToAvoidCaching is false by default because in most cases URL already contains version number, +// so, it makes sense only for Generic Provider for channel files + + +function newUrlFromBase(pathname, baseUrl, addRandomQueryToAvoidCaching = false) { + const result = new (_url().URL)(pathname, baseUrl); // search is not propagated (search is an empty string if not specified) + + const search = baseUrl.search; + + if (search != null && search.length !== 0) { + result.search = search; + } else if (addRandomQueryToAvoidCaching) { + result.search = `noCache=${Date.now().toString(32)}`; + } + + return result; +} +//# sourceMappingURL=main.js.map \ No newline at end of file diff --git a/out/main.js.map b/out/main.js.map new file mode 100644 index 0000000..8b8db44 --- /dev/null +++ b/out/main.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/main.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAIA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAEA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAEA;AACA,IAAI,YAAJ;;AAKA,SAAS,iBAAT,GAA0B;AACxB;AACA,MAAI,OAAO,CAAC,QAAR,KAAqB,OAAzB,EAAkC;AAChC,IAAA,YAAY,GAAG,KAAK,OAAO,CAAC,eAAD,CAAP,CAAyB,WAA9B,GAAf;AACD,GAFD,MAGK,IAAI,OAAO,CAAC,QAAR,KAAqB,QAAzB,EAAmC;AACtC,IAAA,YAAY,GAAG,KAAK,OAAO,CAAC,cAAD,CAAP,CAAwB,UAA7B,GAAf;AACD,GAFI,MAGA;AACH,IAAA,YAAY,GAAG,KAAK,OAAO,CAAC,mBAAD,CAAP,CAA6B,eAAlC,GAAf;AACD;;AACD,SAAO,YAAP;AACD;;AAED,MAAM,CAAC,cAAP,CAAsB,OAAtB,EAA+B,aAA/B,EAA8C;AAC5C,EAAA,UAAU,EAAE,IADgC;AAE5C,EAAA,GAAG,EAAE,MAAK;AACR,WAAO,YAAY,IAAI,iBAAiB,EAAxC;AACD;AAJ2C,CAA9C,E,CAcA;;AACM,SAAU,qBAAV,GAA+B;AACnC,SAAO,SAAS,oBAAoB,EAAE,EAAtC;AACD;;AAED,SAAS,oBAAT,GAA6B;AAC3B,QAAM,eAAe,GAAG,kBAAkB,EAA1C;;AACA,MAAI,eAAe,KAAK,OAAxB,EAAiC;AAC/B,UAAM,IAAI,GAAG,OAAO,CAAC,GAAR,CAAY,iBAAZ,IAAiC,OAAO,CAAC,IAAtD;AACA,UAAM,UAAU,GAAG,IAAI,KAAK,KAAT,GAAiB,EAAjB,GAAsB,IAAI,IAAI,EAAjD;AACA,WAAO,WAAW,UAAlB;AACD,GAJD,MAKK;AACH,WAAO,eAAe,KAAK,QAApB,GAA+B,MAA/B,GAAwC,EAA/C;AACD;AACF;;AAEK,SAAU,oBAAV,CAA+B,OAA/B,EAA8C;AAClD,SAAO,GAAG,OAAO,GAAG,oBAAoB,EAAE,EAA1C;AACD;;AAEK,SAAU,kBAAV,GAA4B;AAChC,SAAO,OAAO,CAAC,GAAR,CAAY,qBAAZ,IAAqC,OAAO,CAAC,QAApD;AACD;;AAEK,SAAU,mBAAV,GAA6B;AACjC;AACA,SAAO,KAAP;AACD;;AAEK,SAAU,kBAAV,CAA6B,OAA7B,EAA4C;AAChD,SAAO,GAAG,OAAO,MAAjB;AACD;;AAeM,MAAM,iBAAiB,GAAkB,mBAAzC;;AACA,MAAM,iBAAiB,GAAkB,mBAAzC;;;AAID,MAAO,aAAP,CAAoB;AACxB,EAAA,WAAA,CAAoB,OAApB,EAAyC;AAArB,SAAA,OAAA,GAAA,OAAA;AACnB;AAED;;;;;AAGA,EAAA,KAAK,CAAC,OAAD,EAAsB;AACzB,IAAA,UAAU,CAAC,KAAK,OAAN,EAAe,OAAf,EAAwB,OAAxB,CAAV;AACD;;AAED,EAAA,QAAQ,CAAC,OAAD,EAAsC;AAC5C,IAAA,UAAU,CAAC,KAAK,OAAN,EAAe,iBAAf,EAAkC,OAAlC,CAAV;AACD;;AAED,EAAA,gBAAgB,CAAC,OAAD,EAAoC;AAClD,IAAA,UAAU,CAAC,KAAK,OAAN,EAAe,iBAAf,EAAkC,OAAlC,CAAV;AACD;;AAED,EAAA,eAAe,CAAC,OAAD,EAAoC;AACjD,IAAA,UAAU,CAAC,KAAK,OAAN,EAAe,kBAAf,EAAmC,OAAnC,CAAV;AACD;;AArBuB;;;AAwB1B,MAAM,UAAU,GAAG,KAAnB;;AAEA,SAAS,UAAT,CAAoB,OAApB,EAA2C,KAA3C,EAAiE,OAAjE,EAAuG;AACrG,MAAI,UAAJ,EAAgB;AACd,IAAA,OAAO,CAAC,EAAR,CAAW,KAAX,EAAkB,CAAC,GAAG,IAAJ,KAAwB;AACxC,MAAA,OAAO,CAAC,GAAR,CAAY,OAAZ,EAAqB,KAArB,EAA4B,IAA5B;AACA,MAAA,OAAO,CAAC,KAAR,CAAc,IAAd,EAAoB,IAApB;AACD,KAHD;AAID,GALD,MAMK;AACH,IAAA,OAAO,CAAC,EAAR,CAAW,KAAX,EAAkB,OAAlB;AACD;AACF,C,CAYD;;AACA;;;AACM,SAAU,UAAV,CAAqB,GAArB,EAAgC;AACpC,QAAM,MAAM,GAAG,KAAI,UAAJ,EAAQ,GAAR,CAAf;;AACA,MAAI,CAAC,MAAM,CAAC,QAAP,CAAgB,QAAhB,CAAyB,GAAzB,CAAL,EAAoC;AAClC,IAAA,MAAM,CAAC,QAAP,IAAmB,GAAnB;AACD;;AACD,SAAO,MAAP;AACD,C,CAED;AACA;;;AACM,SAAU,cAAV,CAAyB,QAAzB,EAA2C,OAA3C,EAAyD,4BAAA,GAAwC,KAAjG,EAAsG;AAC1G,QAAM,MAAM,GAAG,KAAI,UAAJ,EAAQ,QAAR,EAAkB,OAAlB,CAAf,CAD0G,CAE1G;;AACA,QAAM,MAAM,GAAG,OAAO,CAAC,MAAvB;;AACA,MAAI,MAAM,IAAI,IAAV,IAAkB,MAAM,CAAC,MAAP,KAAkB,CAAxC,EAA2C;AACzC,IAAA,MAAM,CAAC,MAAP,GAAgB,MAAhB;AACD,GAFD,MAGK,IAAI,4BAAJ,EAAkC;AACrC,IAAA,MAAM,CAAC,MAAP,GAAgB,WAAW,IAAI,CAAC,GAAL,GAAW,QAAX,CAAoB,EAApB,CAAuB,EAAlD;AACD;;AACD,SAAO,MAAP;AACD,C","sourcesContent":["import { CancellationToken, PackageFileInfo, ProgressInfo, UpdateFileInfo, UpdateInfo } from \"builder-util-runtime\"\r\nimport { EventEmitter } from \"events\"\r\nimport { URL } from \"url\"\r\nimport { AppUpdater } from \"./AppUpdater\"\r\nimport { LoginCallback } from \"./electronHttpExecutor\"\r\n\r\nexport { AppUpdater, NoOpLogger } from \"./AppUpdater\"\r\nexport { UpdateInfo }\r\nexport { CancellationToken } from \"builder-util-runtime\"\r\nexport { Provider } from \"./providers/Provider\"\r\n\r\n// autoUpdater to mimic electron bundled autoUpdater\r\nlet _autoUpdater: any\r\n\r\n// required for jsdoc\r\nexport declare const autoUpdater: AppUpdater\r\n\r\nfunction _load_autoUpdater(): AppUpdater {\r\n // tslint:disable:prefer-conditional-expression\r\n if (process.platform === \"win32\") {\r\n _autoUpdater = new (require(\"./NsisUpdater\").NsisUpdater)()\r\n }\r\n else if (process.platform === \"darwin\") {\r\n _autoUpdater = new (require(\"./MacUpdater\").MacUpdater)()\r\n }\r\n else {\r\n _autoUpdater = new (require(\"./AppImageUpdater\").AppImageUpdater)()\r\n }\r\n return _autoUpdater\r\n}\r\n\r\nObject.defineProperty(exports, \"autoUpdater\", {\r\n enumerable: true,\r\n get: () => {\r\n return _autoUpdater || _load_autoUpdater()\r\n }\r\n})\r\n\r\nexport interface ResolvedUpdateFileInfo {\r\n readonly url: URL\r\n readonly info: UpdateFileInfo\r\n\r\n packageInfo?: PackageFileInfo\r\n}\r\n\r\n// due to historical reasons for windows we use channel name without platform specifier\r\nexport function getDefaultChannelName() {\r\n return `latest${getChannelFilePrefix()}`\r\n}\r\n\r\nfunction getChannelFilePrefix() {\r\n const currentPlatform = getCurrentPlatform()\r\n if (currentPlatform === \"linux\") {\r\n const arch = process.env.TEST_UPDATER_ARCH || process.arch\r\n const archSuffix = arch === \"x64\" ? \"\" : `-${arch}`\r\n return \"-linux\" + archSuffix\r\n }\r\n else {\r\n return currentPlatform === \"darwin\" ? \"-mac\" : \"\"\r\n }\r\n}\r\n\r\nexport function getCustomChannelName(channel: string) {\r\n return `${channel}${getChannelFilePrefix()}`\r\n}\r\n\r\nexport function getCurrentPlatform() {\r\n return process.env.TEST_UPDATER_PLATFORM || process.platform\r\n}\r\n\r\nexport function isUseOldMacProvider() {\r\n // getCurrentPlatform() === \"darwin\"\r\n return false\r\n}\r\n\r\nexport function getChannelFilename(channel: string) {\r\n return `${channel}.yml`\r\n}\r\n\r\nexport interface UpdateCheckResult {\r\n readonly updateInfo: UpdateInfo\r\n\r\n readonly downloadPromise?: Promise> | null\r\n\r\n readonly cancellationToken?: CancellationToken\r\n\r\n /** @deprecated */\r\n readonly versionInfo: UpdateInfo\r\n}\r\n\r\nexport type UpdaterEvents = \"login\" | \"checking-for-update\" | \"update-available\" | \"update-cancelled\" | \"download-progress\" | \"update-downloaded\" | \"error\"\r\n\r\nexport const DOWNLOAD_PROGRESS: UpdaterEvents = \"download-progress\"\r\nexport const UPDATE_DOWNLOADED: UpdaterEvents = \"update-downloaded\"\r\n\r\nexport type LoginHandler = (authInfo: any, callback: LoginCallback) => void\r\n\r\nexport class UpdaterSignal {\r\n constructor(private emitter: EventEmitter) {\r\n }\r\n\r\n /**\r\n * Emitted when an authenticating proxy is [asking for user credentials](https://github.com/electron/electron/blob/master/docs/api/client-request.md#event-login).\r\n */\r\n login(handler: LoginHandler) {\r\n addHandler(this.emitter, \"login\", handler)\r\n }\r\n\r\n progress(handler: (info: ProgressInfo) => void) {\r\n addHandler(this.emitter, DOWNLOAD_PROGRESS, handler)\r\n }\r\n\r\n updateDownloaded(handler: (info: UpdateInfo) => void) {\r\n addHandler(this.emitter, UPDATE_DOWNLOADED, handler)\r\n }\r\n\r\n updateCancelled(handler: (info: UpdateInfo) => void) {\r\n addHandler(this.emitter, \"update-cancelled\", handler)\r\n }\r\n}\r\n\r\nconst isLogEvent = false\r\n\r\nfunction addHandler(emitter: EventEmitter, event: UpdaterEvents, handler: (...args: Array) => void) {\r\n if (isLogEvent) {\r\n emitter.on(event, (...args: Array) => {\r\n console.log(\"%s %s\", event, args)\r\n handler.apply(null, args)\r\n })\r\n }\r\n else {\r\n emitter.on(event, handler)\r\n }\r\n}\r\n\r\nexport interface Logger {\r\n info(message?: any): void\r\n\r\n warn(message?: any): void\r\n\r\n error(message?: any): void\r\n\r\n debug?(message: string): void\r\n}\r\n\r\n// if baseUrl path doesn't ends with /, this path will be not prepended to passed pathname for new URL(input, base)\r\n/** @internal */\r\nexport function newBaseUrl(url: string) {\r\n const result = new URL(url)\r\n if (!result.pathname.endsWith(\"/\")) {\r\n result.pathname += \"/\"\r\n }\r\n return result\r\n}\r\n\r\n// addRandomQueryToAvoidCaching is false by default because in most cases URL already contains version number,\r\n// so, it makes sense only for Generic Provider for channel files\r\nexport function newUrlFromBase(pathname: string, baseUrl: URL, addRandomQueryToAvoidCaching: boolean = false): URL {\r\n const result = new URL(pathname, baseUrl)\r\n // search is not propagated (search is an empty string if not specified)\r\n const search = baseUrl.search\r\n if (search != null && search.length !== 0) {\r\n result.search = search\r\n }\r\n else if (addRandomQueryToAvoidCaching) {\r\n result.search = `noCache=${Date.now().toString(32)}`\r\n }\r\n return result\r\n}\r\n"],"sourceRoot":""} diff --git a/out/providerFactory.d.ts b/out/providerFactory.d.ts new file mode 100644 index 0000000..fb46ed5 --- /dev/null +++ b/out/providerFactory.d.ts @@ -0,0 +1,8 @@ +import { AllPublishOptions, PublishConfiguration } from "builder-util-runtime"; +import { AppUpdater } from "./AppUpdater"; +import { BintrayProvider } from "./providers/BintrayProvider"; +import { GenericProvider } from "./providers/GenericProvider"; +import { GitHubProvider } from "./providers/GitHubProvider"; +import { PrivateGitHubProvider } from "./providers/PrivateGitHubProvider"; +export declare function isUrlProbablySupportMultiRangeRequests(url: string): boolean; +export declare function createClient(data: PublishConfiguration | AllPublishOptions, updater: AppUpdater): GenericProvider | BintrayProvider | GitHubProvider | PrivateGitHubProvider; diff --git a/out/providerFactory.js b/out/providerFactory.js new file mode 100644 index 0000000..ad926c3 --- /dev/null +++ b/out/providerFactory.js @@ -0,0 +1,104 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.isUrlProbablySupportMultiRangeRequests = isUrlProbablySupportMultiRangeRequests; +exports.createClient = createClient; + +function _builderUtilRuntime() { + const data = require("builder-util-runtime"); + + _builderUtilRuntime = function () { + return data; + }; + + return data; +} + +function _BintrayProvider() { + const data = require("./providers/BintrayProvider"); + + _BintrayProvider = function () { + return data; + }; + + return data; +} + +function _GenericProvider() { + const data = require("./providers/GenericProvider"); + + _GenericProvider = function () { + return data; + }; + + return data; +} + +function _GitHubProvider() { + const data = require("./providers/GitHubProvider"); + + _GitHubProvider = function () { + return data; + }; + + return data; +} + +function _PrivateGitHubProvider() { + const data = require("./providers/PrivateGitHubProvider"); + + _PrivateGitHubProvider = function () { + return data; + }; + + return data; +} + +function isUrlProbablySupportMultiRangeRequests(url) { + return !url.includes("s3.amazonaws.com"); +} + +function createClient(data, updater) { + // noinspection SuspiciousTypeOfGuard + if (typeof data === "string") { + throw (0, _builderUtilRuntime().newError)("Please pass PublishConfiguration object", "ERR_UPDATER_INVALID_PROVIDER_CONFIGURATION"); + } + + const httpExecutor = updater.httpExecutor; + const provider = data.provider; + + switch (provider) { + case "github": + const githubOptions = data; + const token = (githubOptions.private ? process.env.GH_TOKEN || process.env.GITHUB_TOKEN : null) || githubOptions.token; + + if (token == null) { + return new (_GitHubProvider().GitHubProvider)(githubOptions, updater, httpExecutor); + } else { + return new (_PrivateGitHubProvider().PrivateGitHubProvider)(githubOptions, updater, token, httpExecutor); + } + + case "s3": + case "spaces": + return new (_GenericProvider().GenericProvider)({ + provider: "generic", + url: (0, _builderUtilRuntime().getS3LikeProviderBaseUrl)(data), + channel: data.channel || null + }, updater, provider === "spaces" + /* https://github.com/minio/minio/issues/5285#issuecomment-350428955 */ + ); + + case "generic": + const options = data; + return new (_GenericProvider().GenericProvider)(options, updater, options.useMultipleRangeRequest !== false && isUrlProbablySupportMultiRangeRequests(options.url)); + + case "bintray": + return new (_BintrayProvider().BintrayProvider)(data, httpExecutor); + + default: + throw (0, _builderUtilRuntime().newError)(`Unsupported provider: ${provider}`, "ERR_UPDATER_UNSUPPORTED_PROVIDER"); + } +} +//# sourceMappingURL=providerFactory.js.map \ No newline at end of file diff --git a/out/providerFactory.js.map b/out/providerFactory.js.map new file mode 100644 index 0000000..5cd54c0 --- /dev/null +++ b/out/providerFactory.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/providerFactory.ts"],"names":[],"mappings":";;;;;;;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAEA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAEM,SAAU,sCAAV,CAAiD,GAAjD,EAA4D;AAChE,SAAO,CAAC,GAAG,CAAC,QAAJ,CAAa,kBAAb,CAAR;AACD;;AAEK,SAAU,YAAV,CAAuB,IAAvB,EAAuE,OAAvE,EAA0F;AAC9F;AACA,MAAI,OAAO,IAAP,KAAgB,QAApB,EAA8B;AAC5B,UAAM,oCAAS,yCAAT,EAAoD,4CAApD,CAAN;AACD;;AAED,QAAM,YAAY,GAAG,OAAO,CAAC,YAA7B;AACA,QAAM,QAAQ,GAAG,IAAI,CAAC,QAAtB;;AACA,UAAQ,QAAR;AACE,SAAK,QAAL;AACE,YAAM,aAAa,GAAG,IAAtB;AACA,YAAM,KAAK,GAAG,CAAC,aAAa,CAAC,OAAd,GAAwB,OAAO,CAAC,GAAR,CAAY,QAAZ,IAAwB,OAAO,CAAC,GAAR,CAAY,YAA5D,GAA2E,IAA5E,KAAqF,aAAa,CAAC,KAAjH;;AACA,UAAI,KAAK,IAAI,IAAb,EAAmB;AACjB,eAAO,KAAI,gCAAJ,EAAmB,aAAnB,EAAkC,OAAlC,EAA2C,YAA3C,CAAP;AACD,OAFD,MAGK;AACH,eAAO,KAAI,8CAAJ,EAA0B,aAA1B,EAAyC,OAAzC,EAAkD,KAAlD,EAAyD,YAAzD,CAAP;AACD;;AAEH,SAAK,IAAL;AACA,SAAK,QAAL;AACE,aAAO,KAAI,kCAAJ,EAAoB;AACzB,QAAA,QAAQ,EAAE,SADe;AAEzB,QAAA,GAAG,EAAE,oDAAyB,IAAzB,CAFoB;AAGzB,QAAA,OAAO,EAAG,IAAsB,CAAC,OAAvB,IAAkC;AAHnB,OAApB,EAIJ,OAJI,EAIK,QAAQ,KAAK;AAAS;AAJ3B,OAAP;;AAMF,SAAK,SAAL;AACE,YAAM,OAAO,GAAG,IAAhB;AACA,aAAO,KAAI,kCAAJ,EAAoB,OAApB,EAA6B,OAA7B,EAAsC,OAAO,CAAC,uBAAR,KAAoC,KAApC,IAA6C,sCAAsC,CAAC,OAAO,CAAC,GAAT,CAAzH,CAAP;;AAEF,SAAK,SAAL;AACE,aAAO,KAAI,kCAAJ,EAAoB,IAApB,EAA4C,YAA5C,CAAP;;AAEF;AACE,YAAM,oCAAS,yBAAyB,QAAQ,EAA1C,EAA8C,kCAA9C,CAAN;AA3BJ;AA6BD,C","sourcesContent":["import { AllPublishOptions, BaseS3Options, BintrayOptions, GenericServerOptions, getS3LikeProviderBaseUrl, GithubOptions, newError, PublishConfiguration } from \"builder-util-runtime\"\r\nimport { AppUpdater } from \"./AppUpdater\"\r\nimport { BintrayProvider } from \"./providers/BintrayProvider\"\r\nimport { GenericProvider } from \"./providers/GenericProvider\"\r\nimport { GitHubProvider } from \"./providers/GitHubProvider\"\r\nimport { PrivateGitHubProvider } from \"./providers/PrivateGitHubProvider\"\r\n\r\nexport function isUrlProbablySupportMultiRangeRequests(url: string): boolean {\r\n return !url.includes(\"s3.amazonaws.com\")\r\n}\r\n\r\nexport function createClient(data: PublishConfiguration | AllPublishOptions, updater: AppUpdater) {\r\n // noinspection SuspiciousTypeOfGuard\r\n if (typeof data === \"string\") {\r\n throw newError(\"Please pass PublishConfiguration object\", \"ERR_UPDATER_INVALID_PROVIDER_CONFIGURATION\")\r\n }\r\n\r\n const httpExecutor = updater.httpExecutor\r\n const provider = data.provider\r\n switch (provider) {\r\n case \"github\":\r\n const githubOptions = data as GithubOptions\r\n const token = (githubOptions.private ? process.env.GH_TOKEN || process.env.GITHUB_TOKEN : null) || githubOptions.token\r\n if (token == null) {\r\n return new GitHubProvider(githubOptions, updater, httpExecutor)\r\n }\r\n else {\r\n return new PrivateGitHubProvider(githubOptions, updater, token, httpExecutor)\r\n }\r\n\r\n case \"s3\":\r\n case \"spaces\":\r\n return new GenericProvider({\r\n provider: \"generic\",\r\n url: getS3LikeProviderBaseUrl(data),\r\n channel: (data as BaseS3Options).channel || null\r\n }, updater, provider === \"spaces\" /* https://github.com/minio/minio/issues/5285#issuecomment-350428955 */)\r\n\r\n case \"generic\":\r\n const options = data as GenericServerOptions\r\n return new GenericProvider(options, updater, options.useMultipleRangeRequest !== false && isUrlProbablySupportMultiRangeRequests(options.url))\r\n\r\n case \"bintray\":\r\n return new BintrayProvider(data as BintrayOptions, httpExecutor)\r\n\r\n default:\r\n throw newError(`Unsupported provider: ${provider}`, \"ERR_UPDATER_UNSUPPORTED_PROVIDER\")\r\n }\r\n}"],"sourceRoot":""} diff --git a/out/providers/BintrayProvider.d.ts b/out/providers/BintrayProvider.d.ts new file mode 100644 index 0000000..723c84c --- /dev/null +++ b/out/providers/BintrayProvider.d.ts @@ -0,0 +1,10 @@ +import { BintrayOptions, HttpExecutor, UpdateInfo } from "builder-util-runtime"; +import { Provider, ResolvedUpdateFileInfo } from "../main"; +export declare class BintrayProvider extends Provider { + private client; + private readonly baseUrl; + constructor(configuration: BintrayOptions, httpExecutor: HttpExecutor); + setRequestHeaders(value: any): void; + getLatestVersion(): Promise; + resolveFiles(updateInfo: UpdateInfo): Array; +} diff --git a/out/providers/BintrayProvider.js b/out/providers/BintrayProvider.js new file mode 100644 index 0000000..d4f704a --- /dev/null +++ b/out/providers/BintrayProvider.js @@ -0,0 +1,98 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.BintrayProvider = void 0; + +function _builderUtilRuntime() { + const data = require("builder-util-runtime"); + + _builderUtilRuntime = function () { + return data; + }; + + return data; +} + +function _bintray() { + const data = require("builder-util-runtime/out/bintray"); + + _bintray = function () { + return data; + }; + + return data; +} + +function _url() { + const data = require("url"); + + _url = function () { + return data; + }; + + return data; +} + +function _main() { + const data = require("../main"); + + _main = function () { + return data; + }; + + return data; +} + +function _Provider() { + const data = require("./Provider"); + + _Provider = function () { + return data; + }; + + return data; +} + +class BintrayProvider extends _main().Provider { + constructor(configuration, httpExecutor) { + super(httpExecutor); + this.client = new (_bintray().BintrayClient)(configuration, httpExecutor, new (_builderUtilRuntime().CancellationToken)()); + this.baseUrl = (0, _main().newBaseUrl)(`https://dl.bintray.com/${this.client.owner}/${this.client.repo}`); + } + + setRequestHeaders(value) { + super.setRequestHeaders(value); + this.client.setRequestHeaders(value); + } + + async getLatestVersion() { + try { + const data = await this.client.getVersion("_latest"); + const channelFilename = (0, _main().getChannelFilename)((0, _main().getDefaultChannelName)()); + const files = await this.client.getVersionFiles(data.name); + const channelFile = files.find(it => it.name.endsWith(`_${channelFilename}`) || it.name.endsWith(`-${channelFilename}`)); + + if (channelFile == null) { + // noinspection ExceptionCaughtLocallyJS + throw (0, _builderUtilRuntime().newError)(`Cannot find channel file "${channelFilename}", existing files:\n${files.map(it => JSON.stringify(it, null, 2)).join(",\n")}`, "ERR_UPDATER_CHANNEL_FILE_NOT_FOUND"); + } + + const channelFileUrl = new (_url().URL)(`https://dl.bintray.com/${this.client.owner}/${this.client.repo}/${channelFile.name}`); + return (0, _Provider().parseUpdateInfo)((await this.httpRequest(channelFileUrl)), channelFilename, channelFileUrl); + } catch (e) { + if ("statusCode" in e && e.statusCode === 404) { + throw (0, _builderUtilRuntime().newError)(`No latest version, please ensure that user, package and repository correctly configured. Or at least one version is published. ${e.stack || e.message}`, "ERR_UPDATER_LATEST_VERSION_NOT_FOUND"); + } + + throw e; + } + } + + resolveFiles(updateInfo) { + return (0, _Provider().resolveFiles)(updateInfo, this.baseUrl); + } + +} exports.BintrayProvider = BintrayProvider; +//# sourceMappingURL=BintrayProvider.js.map \ No newline at end of file diff --git a/out/providers/BintrayProvider.js.map b/out/providers/BintrayProvider.js.map new file mode 100644 index 0000000..29d048c --- /dev/null +++ b/out/providers/BintrayProvider.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../src/providers/BintrayProvider.ts"],"names":[],"mappings":";;;;;;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAEM,MAAO,eAAP,SAA+B,gBAA/B,CAAmD;AAIvD,EAAA,WAAA,CAAY,aAAZ,EAA2C,YAA3C,EAA0E;AACxE,UAAM,YAAN;AAEA,SAAK,MAAL,GAAc,KAAI,wBAAJ,EAAkB,aAAlB,EAAiC,YAAjC,EAA+C,KAAI,uCAAJ,GAA/C,CAAd;AACA,SAAK,OAAL,GAAe,wBAAW,0BAA0B,KAAK,MAAL,CAAY,KAAK,IAAI,KAAK,MAAL,CAAY,IAAI,EAA1E,CAAf;AACD;;AAED,EAAA,iBAAiB,CAAC,KAAD,EAAW;AAC1B,UAAM,iBAAN,CAAwB,KAAxB;AACA,SAAK,MAAL,CAAY,iBAAZ,CAA8B,KAA9B;AACD;;AAED,QAAM,gBAAN,GAAsB;AACpB,QAAI;AACF,YAAM,IAAI,GAAG,MAAM,KAAK,MAAL,CAAY,UAAZ,CAAuB,SAAvB,CAAnB;AACA,YAAM,eAAe,GAAG,gCAAmB,oCAAnB,CAAxB;AACA,YAAM,KAAK,GAAG,MAAM,KAAK,MAAL,CAAY,eAAZ,CAA4B,IAAI,CAAC,IAAjC,CAApB;AACA,YAAM,WAAW,GAAG,KAAK,CAAC,IAAN,CAAW,EAAE,IAAI,EAAE,CAAC,IAAH,CAAQ,QAAR,CAAiB,IAAI,eAAe,EAApC,KAA2C,EAAE,CAAC,IAAH,CAAQ,QAAR,CAAiB,IAAI,eAAe,EAApC,CAA5D,CAApB;;AACA,UAAI,WAAW,IAAI,IAAnB,EAAyB;AACvB;AACA,cAAM,oCAAS,6BAA6B,eAAe,uBAAuB,KAAK,CAAC,GAAN,CAAU,EAAE,IAAI,IAAI,CAAC,SAAL,CAAe,EAAf,EAAmB,IAAnB,EAAyB,CAAzB,CAAhB,EAA6C,IAA7C,CAAkD,KAAlD,CAAwD,EAApI,EAAwI,oCAAxI,CAAN;AACD;;AAED,YAAM,cAAc,GAAG,KAAI,UAAJ,EAAQ,0BAA0B,KAAK,MAAL,CAAY,KAAK,IAAI,KAAK,MAAL,CAAY,IAAI,IAAI,WAAW,CAAC,IAAI,EAA3F,CAAvB;AACA,aAAO,kCAAgB,MAAM,KAAK,WAAL,CAAiB,cAAjB,CAAtB,GAAwD,eAAxD,EAAyE,cAAzE,CAAP;AACD,KAZD,CAaA,OAAO,CAAP,EAAU;AACR,UAAI,gBAAgB,CAAhB,IAAqB,CAAC,CAAC,UAAF,KAAiB,GAA1C,EAA+C;AAC7C,cAAM,oCAAS,kIAAkI,CAAC,CAAC,KAAF,IAAW,CAAC,CAAC,OAAO,EAA/J,EAAmK,sCAAnK,CAAN;AACD;;AACD,YAAM,CAAN;AACD;AACF;;AAED,EAAA,YAAY,CAAC,UAAD,EAAuB;AACjC,WAAO,8BAAa,UAAb,EAAyB,KAAK,OAA9B,CAAP;AACD;;AAxCsD,C","sourcesContent":["import { BintrayOptions, CancellationToken, HttpExecutor, newError, UpdateInfo } from \"builder-util-runtime\"\r\nimport { BintrayClient } from \"builder-util-runtime/out/bintray\"\r\nimport { URL } from \"url\"\r\nimport { getChannelFilename, getDefaultChannelName, newBaseUrl, Provider, ResolvedUpdateFileInfo } from \"../main\"\r\nimport { parseUpdateInfo, resolveFiles } from \"./Provider\"\r\n\r\nexport class BintrayProvider extends Provider {\r\n private client: BintrayClient\r\n private readonly baseUrl: URL\r\n\r\n constructor(configuration: BintrayOptions, httpExecutor: HttpExecutor) {\r\n super(httpExecutor)\r\n\r\n this.client = new BintrayClient(configuration, httpExecutor, new CancellationToken())\r\n this.baseUrl = newBaseUrl(`https://dl.bintray.com/${this.client.owner}/${this.client.repo}`)\r\n }\r\n\r\n setRequestHeaders(value: any): void {\r\n super.setRequestHeaders(value)\r\n this.client.setRequestHeaders(value)\r\n }\r\n\r\n async getLatestVersion(): Promise {\r\n try {\r\n const data = await this.client.getVersion(\"_latest\")\r\n const channelFilename = getChannelFilename(getDefaultChannelName())\r\n const files = await this.client.getVersionFiles(data.name)\r\n const channelFile = files.find(it => it.name.endsWith(`_${channelFilename}`) || it.name.endsWith(`-${channelFilename}`))\r\n if (channelFile == null) {\r\n // noinspection ExceptionCaughtLocallyJS\r\n throw newError(`Cannot find channel file \"${channelFilename}\", existing files:\\n${files.map(it => JSON.stringify(it, null, 2)).join(\",\\n\")}`, \"ERR_UPDATER_CHANNEL_FILE_NOT_FOUND\")\r\n }\r\n\r\n const channelFileUrl = new URL(`https://dl.bintray.com/${this.client.owner}/${this.client.repo}/${channelFile.name}`)\r\n return parseUpdateInfo(await this.httpRequest(channelFileUrl), channelFilename, channelFileUrl)\r\n }\r\n catch (e) {\r\n if (\"statusCode\" in e && e.statusCode === 404) {\r\n throw newError(`No latest version, please ensure that user, package and repository correctly configured. Or at least one version is published. ${e.stack || e.message}`, \"ERR_UPDATER_LATEST_VERSION_NOT_FOUND\")\r\n }\r\n throw e\r\n }\r\n }\r\n\r\n resolveFiles(updateInfo: UpdateInfo): Array {\r\n return resolveFiles(updateInfo, this.baseUrl)\r\n }\r\n}"],"sourceRoot":""} diff --git a/out/providers/GenericProvider.d.ts b/out/providers/GenericProvider.d.ts new file mode 100644 index 0000000..4fb25c4 --- /dev/null +++ b/out/providers/GenericProvider.d.ts @@ -0,0 +1,12 @@ +import { GenericServerOptions, UpdateInfo } from "builder-util-runtime"; +import { AppUpdater } from "../AppUpdater"; +import { Provider, ResolvedUpdateFileInfo } from "../main"; +export declare class GenericProvider extends Provider { + private readonly configuration; + private readonly updater; + private readonly baseUrl; + constructor(configuration: GenericServerOptions, updater: AppUpdater, useMultipleRangeRequest?: boolean); + private readonly channel; + getLatestVersion(): Promise; + resolveFiles(updateInfo: UpdateInfo): Array; +} diff --git a/out/providers/GenericProvider.js b/out/providers/GenericProvider.js new file mode 100644 index 0000000..126481c --- /dev/null +++ b/out/providers/GenericProvider.js @@ -0,0 +1,92 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.GenericProvider = void 0; + +function _builderUtilRuntime() { + const data = require("builder-util-runtime"); + + _builderUtilRuntime = function () { + return data; + }; + + return data; +} + +function _main() { + const data = require("../main"); + + _main = function () { + return data; + }; + + return data; +} + +function _Provider() { + const data = require("./Provider"); + + _Provider = function () { + return data; + }; + + return data; +} + +class GenericProvider extends _main().Provider { + constructor(configuration, updater, useMultipleRangeRequest = true) { + super(updater.httpExecutor, useMultipleRangeRequest); + this.configuration = configuration; + this.updater = updater; + this.baseUrl = (0, _main().newBaseUrl)(this.configuration.url); + } + + get channel() { + const result = this.updater.channel || this.configuration.channel; + return result == null ? (0, _main().getDefaultChannelName)() : (0, _main().getCustomChannelName)(result); + } + + async getLatestVersion() { + let result; + const channelFile = (0, _main().getChannelFilename)(this.channel); + const channelUrl = (0, _main().newUrlFromBase)(channelFile, this.baseUrl, this.updater.isAddNoCacheQuery); + + for (let attemptNumber = 0;; attemptNumber++) { + try { + result = (0, _Provider().parseUpdateInfo)((await this.httpRequest(channelUrl)), channelFile, channelUrl); + break; + } catch (e) { + if (e instanceof _builderUtilRuntime().HttpError && e.statusCode === 404) { + throw (0, _builderUtilRuntime().newError)(`Cannot find channel "${channelFile}" update info: ${e.stack || e.message}`, "ERR_UPDATER_CHANNEL_FILE_NOT_FOUND"); + } else if (e.code === "ECONNREFUSED") { + if (attemptNumber < 3) { + await new Promise((resolve, reject) => { + try { + setTimeout(resolve, 1000 * attemptNumber); + } catch (e) { + reject(e); + } + }); + continue; + } + } + + throw e; + } + } + + if ((0, _main().isUseOldMacProvider)()) { + result.releaseJsonUrl = channelUrl.href; + } + + return result; + } + + resolveFiles(updateInfo) { + return (0, _Provider().resolveFiles)(updateInfo, this.baseUrl); + } + +} exports.GenericProvider = GenericProvider; +//# sourceMappingURL=GenericProvider.js.map \ No newline at end of file diff --git a/out/providers/GenericProvider.js.map b/out/providers/GenericProvider.js.map new file mode 100644 index 0000000..723b9f3 --- /dev/null +++ b/out/providers/GenericProvider.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../src/providers/GenericProvider.ts"],"names":[],"mappings":";;;;;;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAEA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AACA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAEM,MAAO,eAAP,SAA+B,gBAA/B,CAAmD;AAGvD,EAAA,WAAA,CAA6B,aAA7B,EAAmF,OAAnF,EAAwG,uBAAuB,GAAG,IAAlI,EAAsI;AACpI,UAAM,OAAO,CAAC,YAAd,EAA4B,uBAA5B;AAD2B,SAAA,aAAA,GAAA,aAAA;AAAsD,SAAA,OAAA,GAAA,OAAA;AAFlE,SAAA,OAAA,GAAU,wBAAW,KAAK,aAAL,CAAmB,GAA9B,CAAV;AAIhB;;AAED,MAAY,OAAZ,GAAmB;AACjB,UAAM,MAAM,GAAG,KAAK,OAAL,CAAa,OAAb,IAAwB,KAAK,aAAL,CAAmB,OAA1D;AACA,WAAO,MAAM,IAAI,IAAV,GAAiB,oCAAjB,GAA2C,kCAAqB,MAArB,CAAlD;AACD;;AAED,QAAM,gBAAN,GAAsB;AACpB,QAAI,MAAJ;AACA,UAAM,WAAW,GAAG,gCAAmB,KAAK,OAAxB,CAApB;AACA,UAAM,UAAU,GAAG,4BAAe,WAAf,EAA4B,KAAK,OAAjC,EAA0C,KAAK,OAAL,CAAa,iBAAvD,CAAnB;;AACA,SAAK,IAAI,aAAa,GAAG,CAAzB,GAA8B,aAAa,EAA3C,EAA+C;AAC7C,UAAI;AACF,QAAA,MAAM,GAAG,kCAAgB,MAAM,KAAK,WAAL,CAAiB,UAAjB,CAAtB,GAAoD,WAApD,EAAiE,UAAjE,CAAT;AACA;AACD,OAHD,CAIA,OAAO,CAAP,EAAU;AACR,YAAI,CAAC,YAAY,+BAAb,IAA0B,CAAC,CAAC,UAAF,KAAiB,GAA/C,EAAoD;AAClD,gBAAM,oCAAS,wBAAwB,WAAW,kBAAkB,CAAC,CAAC,KAAF,IAAW,CAAC,CAAC,OAAO,EAAlF,EAAsF,oCAAtF,CAAN;AACD,SAFD,MAGK,IAAI,CAAC,CAAC,IAAF,KAAW,cAAf,EAA+B;AAClC,cAAI,aAAa,GAAG,CAApB,EAAuB;AACrB,kBAAM,IAAI,OAAJ,CAAY,CAAC,OAAD,EAAU,MAAV,KAAoB;AACpC,kBAAI;AACF,gBAAA,UAAU,CAAC,OAAD,EAAU,OAAO,aAAjB,CAAV;AACD,eAFD,CAGA,OAAO,CAAP,EAAU;AACR,gBAAA,MAAM,CAAC,CAAD,CAAN;AACD;AACF,aAPK,CAAN;AAQA;AACD;AACF;;AACD,cAAM,CAAN;AACD;AACF;;AAED,QAAI,kCAAJ,EAA2B;AACxB,MAAA,MAAc,CAAC,cAAf,GAAgC,UAAU,CAAC,IAA3C;AACF;;AACD,WAAO,MAAP;AACD;;AAED,EAAA,YAAY,CAAC,UAAD,EAAuB;AACjC,WAAO,8BAAa,UAAb,EAAyB,KAAK,OAA9B,CAAP;AACD;;AAlDsD,C","sourcesContent":["import { GenericServerOptions, HttpError, newError, UpdateInfo } from \"builder-util-runtime\"\r\nimport { AppUpdater } from \"../AppUpdater\"\r\nimport { getChannelFilename, getCustomChannelName, getDefaultChannelName, isUseOldMacProvider, newBaseUrl, newUrlFromBase, Provider, ResolvedUpdateFileInfo } from \"../main\"\r\nimport { parseUpdateInfo, resolveFiles } from \"./Provider\"\r\n\r\nexport class GenericProvider extends Provider {\r\n private readonly baseUrl = newBaseUrl(this.configuration.url)\r\n\r\n constructor(private readonly configuration: GenericServerOptions, private readonly updater: AppUpdater, useMultipleRangeRequest = true) {\r\n super(updater.httpExecutor, useMultipleRangeRequest)\r\n }\r\n\r\n private get channel(): string {\r\n const result = this.updater.channel || this.configuration.channel\r\n return result == null ? getDefaultChannelName() : getCustomChannelName(result)\r\n }\r\n\r\n async getLatestVersion(): Promise {\r\n let result: UpdateInfo\r\n const channelFile = getChannelFilename(this.channel)\r\n const channelUrl = newUrlFromBase(channelFile, this.baseUrl, this.updater.isAddNoCacheQuery)\r\n for (let attemptNumber = 0; ; attemptNumber++) {\r\n try {\r\n result = parseUpdateInfo(await this.httpRequest(channelUrl), channelFile, channelUrl)\r\n break\r\n }\r\n catch (e) {\r\n if (e instanceof HttpError && e.statusCode === 404) {\r\n throw newError(`Cannot find channel \"${channelFile}\" update info: ${e.stack || e.message}`, \"ERR_UPDATER_CHANNEL_FILE_NOT_FOUND\")\r\n }\r\n else if (e.code === \"ECONNREFUSED\") {\r\n if (attemptNumber < 3) {\r\n await new Promise((resolve, reject) => {\r\n try {\r\n setTimeout(resolve, 1000 * attemptNumber)\r\n }\r\n catch (e) {\r\n reject(e)\r\n }\r\n })\r\n continue\r\n }\r\n }\r\n throw e\r\n }\r\n }\r\n\r\n if (isUseOldMacProvider()) {\r\n (result as any).releaseJsonUrl = channelUrl.href\r\n }\r\n return result\r\n }\r\n\r\n resolveFiles(updateInfo: UpdateInfo): Array {\r\n return resolveFiles(updateInfo, this.baseUrl)\r\n }\r\n}"],"sourceRoot":""} diff --git a/out/providers/GitHubProvider.d.ts b/out/providers/GitHubProvider.d.ts new file mode 100644 index 0000000..fb60a65 --- /dev/null +++ b/out/providers/GitHubProvider.d.ts @@ -0,0 +1,24 @@ +/// +import { GithubOptions, HttpExecutor, ReleaseNoteInfo, UpdateInfo, XElement } from "builder-util-runtime"; +import * as semver from "semver"; +import { URL } from "url"; +import { AppUpdater } from "../AppUpdater"; +import { Provider, ResolvedUpdateFileInfo } from "../main"; +export declare abstract class BaseGitHubProvider extends Provider { + protected readonly options: GithubOptions; + protected readonly baseUrl: URL; + protected readonly baseApiUrl: URL; + protected constructor(options: GithubOptions, defaultHost: string, executor: HttpExecutor); + protected computeGithubBasePath(result: string): string; +} +export declare class GitHubProvider extends BaseGitHubProvider { + protected readonly options: GithubOptions; + private readonly updater; + constructor(options: GithubOptions, updater: AppUpdater, executor: HttpExecutor); + getLatestVersion(): Promise; + private getLatestVersionString; + private readonly basePath; + resolveFiles(updateInfo: UpdateInfo): Array; + private getBaseDownloadPath; +} +export declare function computeReleaseNotes(currentVersion: semver.SemVer, isFullChangelog: boolean, feed: XElement, latestRelease: any): string | ReleaseNoteInfo[]; diff --git a/out/providers/GitHubProvider.js b/out/providers/GitHubProvider.js new file mode 100644 index 0000000..149e580 --- /dev/null +++ b/out/providers/GitHubProvider.js @@ -0,0 +1,219 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.computeReleaseNotes = computeReleaseNotes; +exports.GitHubProvider = exports.BaseGitHubProvider = void 0; + +function _builderUtilRuntime() { + const data = require("builder-util-runtime"); + + _builderUtilRuntime = function () { + return data; + }; + + return data; +} + +function semver() { + const data = _interopRequireWildcard(require("semver")); + + semver = function () { + return data; + }; + + return data; +} + +function _url() { + const data = require("url"); + + _url = function () { + return data; + }; + + return data; +} + +function _main() { + const data = require("../main"); + + _main = function () { + return data; + }; + + return data; +} + +function _Provider() { + const data = require("./Provider"); + + _Provider = function () { + return data; + }; + + return data; +} + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +const hrefRegExp = /\/tag\/v?([^\/]+)$/; + +class BaseGitHubProvider extends _main().Provider { + constructor(options, defaultHost, executor) { + super(executor, false + /* because GitHib uses S3 */ + ); + this.options = options; + this.baseUrl = (0, _main().newBaseUrl)((0, _builderUtilRuntime().githubUrl)(options, defaultHost)); + const apiHost = defaultHost === "github.com" ? "api.github.com" : defaultHost; + this.baseApiUrl = (0, _main().newBaseUrl)((0, _builderUtilRuntime().githubUrl)(options, apiHost)); + } + + computeGithubBasePath(result) { + // https://github.com/electron-userland/electron-builder/issues/1903#issuecomment-320881211 + const host = this.options.host; + return host != null && host !== "github.com" && host !== "api.github.com" ? `/api/v3${result}` : result; + } + +} + +exports.BaseGitHubProvider = BaseGitHubProvider; + +class GitHubProvider extends BaseGitHubProvider { + constructor(options, updater, executor) { + super(options, "github.com", executor); + this.options = options; + this.updater = updater; + } + + async getLatestVersion() { + const cancellationToken = new (_builderUtilRuntime().CancellationToken)(); + const feedXml = await this.httpRequest((0, _main().newUrlFromBase)(`${this.basePath}.atom`, this.baseUrl), { + accept: "application/xml, application/atom+xml, text/xml, */*" + }, cancellationToken); + const feed = (0, _builderUtilRuntime().parseXml)(feedXml); + let latestRelease = feed.element("entry", false, `No published versions on GitHub`); + let version; + + try { + if (this.updater.allowPrerelease) { + // noinspection TypeScriptValidateJSTypes + version = latestRelease.element("link").attribute("href").match(hrefRegExp)[1]; + } else { + version = await this.getLatestVersionString(cancellationToken); + + for (const element of feed.getElements("entry")) { + if (element.element("link").attribute("href").match(hrefRegExp)[1] === version) { + latestRelease = element; + break; + } + } + } + } catch (e) { + throw (0, _builderUtilRuntime().newError)(`Cannot parse releases feed: ${e.stack || e.message},\nXML:\n${feedXml}`, "ERR_UPDATER_INVALID_RELEASE_FEED"); + } + + if (version == null) { + throw (0, _builderUtilRuntime().newError)(`No published versions on GitHub`, "ERR_UPDATER_NO_PUBLISHED_VERSIONS"); + } + + const channelFile = (0, _main().getChannelFilename)((0, _main().getDefaultChannelName)()); + const channelFileUrl = (0, _main().newUrlFromBase)(this.getBaseDownloadPath(version, channelFile), this.baseUrl); + const requestOptions = this.createRequestOptions(channelFileUrl); + let rawData; + + try { + rawData = await this.executor.request(requestOptions, cancellationToken); + } catch (e) { + if (!this.updater.allowPrerelease && e instanceof _builderUtilRuntime().HttpError && e.statusCode === 404) { + throw (0, _builderUtilRuntime().newError)(`Cannot find ${channelFile} in the latest release artifacts (${channelFileUrl}): ${e.stack || e.message}`, "ERR_UPDATER_CHANNEL_FILE_NOT_FOUND"); + } + + throw e; + } + + const result = (0, _Provider().parseUpdateInfo)(rawData, channelFile, channelFileUrl); + + if ((0, _main().isUseOldMacProvider)()) { + result.releaseJsonUrl = `${(0, _builderUtilRuntime().githubUrl)(this.options)}/${requestOptions.path}`; + } + + if (result.releaseName == null) { + result.releaseName = latestRelease.elementValueOrEmpty("title"); + } + + if (result.releaseNotes == null) { + result.releaseNotes = computeReleaseNotes(this.updater.currentVersion, this.updater.fullChangelog, feed, latestRelease); + } + + return result; + } + + async getLatestVersionString(cancellationToken) { + const options = this.options; // do not use API for GitHub to avoid limit, only for custom host or GitHub Enterprise + + const url = options.host == null || options.host === "github.com" ? (0, _main().newUrlFromBase)(`${this.basePath}/latest`, this.baseUrl) : new (_url().URL)(`${this.computeGithubBasePath(`/repos/${options.owner}/${options.repo}/releases`)}/latest`, this.baseApiUrl); + + try { + const rawData = await this.httpRequest(url, { + Accept: "application/json" + }, cancellationToken); + + if (rawData == null) { + return null; + } + + const releaseInfo = JSON.parse(rawData); + return releaseInfo.tag_name.startsWith("v") ? releaseInfo.tag_name.substring(1) : releaseInfo.tag_name; + } catch (e) { + throw (0, _builderUtilRuntime().newError)(`Unable to find latest version on GitHub (${url}), please ensure a production release exists: ${e.stack || e.message}`, "ERR_UPDATER_LATEST_VERSION_NOT_FOUND"); + } + } + + get basePath() { + return `/${this.options.owner}/${this.options.repo}/releases`; + } + + resolveFiles(updateInfo) { + // still replace space to - due to backward compatibility + return (0, _Provider().resolveFiles)(updateInfo, this.baseUrl, p => this.getBaseDownloadPath(updateInfo.version, p.replace(/ /g, "-"))); + } + + getBaseDownloadPath(version, fileName) { + return `${this.basePath}/download/${this.options.vPrefixedTagName === false ? "" : "v"}${version}/${fileName}`; + } + +} + +exports.GitHubProvider = GitHubProvider; + +function getNoteValue(parent) { + const result = parent.elementValueOrEmpty("content"); // GitHub reports empty notes as No content. + + return result === "No content." ? "" : result; +} + +function computeReleaseNotes(currentVersion, isFullChangelog, feed, latestRelease) { + if (!isFullChangelog) { + return getNoteValue(latestRelease); + } + + const releaseNotes = []; + + for (const release of feed.getElements("entry")) { + // noinspection TypeScriptValidateJSTypes + const versionRelease = release.element("link").attribute("href").match(/\/tag\/v?([^\/]+)$/)[1]; + + if (semver().lt(currentVersion, versionRelease)) { + releaseNotes.push({ + version: versionRelease, + note: getNoteValue(release) + }); + } + } + + return releaseNotes.sort((a, b) => semver().rcompare(a.version, b.version)); +} +//# sourceMappingURL=GitHubProvider.js.map \ No newline at end of file diff --git a/out/providers/GitHubProvider.js.map b/out/providers/GitHubProvider.js.map new file mode 100644 index 0000000..5e9f496 --- /dev/null +++ b/out/providers/GitHubProvider.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../src/providers/GitHubProvider.ts"],"names":[],"mappings":"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","sourcesContent":["import { CancellationToken, GithubOptions, githubUrl, HttpError, HttpExecutor, newError, parseXml, ReleaseNoteInfo, UpdateInfo, XElement } from \"builder-util-runtime\"\r\nimport * as semver from \"semver\"\r\nimport { URL } from \"url\"\r\nimport { AppUpdater } from \"../AppUpdater\"\r\nimport { getChannelFilename, getDefaultChannelName, isUseOldMacProvider, newBaseUrl, newUrlFromBase, Provider, ResolvedUpdateFileInfo } from \"../main\"\r\nimport { parseUpdateInfo, resolveFiles } from \"./Provider\"\r\n\r\nconst hrefRegExp = /\\/tag\\/v?([^\\/]+)$/\r\n\r\nexport abstract class BaseGitHubProvider extends Provider {\r\n // so, we don't need to parse port (because node http doesn't support host as url does)\r\n protected readonly baseUrl: URL\r\n protected readonly baseApiUrl: URL\r\n\r\n protected constructor(protected readonly options: GithubOptions, defaultHost: string, executor: HttpExecutor) {\r\n super(executor, false /* because GitHib uses S3 */)\r\n\r\n this.baseUrl = newBaseUrl(githubUrl(options, defaultHost))\r\n const apiHost = defaultHost === \"github.com\" ? \"api.github.com\" : defaultHost\r\n this.baseApiUrl = newBaseUrl(githubUrl(options, apiHost))\r\n }\r\n\r\n protected computeGithubBasePath(result: string) {\r\n // https://github.com/electron-userland/electron-builder/issues/1903#issuecomment-320881211\r\n const host = this.options.host\r\n return host != null && host !== \"github.com\" && host !== \"api.github.com\" ? `/api/v3${result}` : result\r\n }\r\n}\r\n\r\nexport class GitHubProvider extends BaseGitHubProvider {\r\n constructor(protected readonly options: GithubOptions, private readonly updater: AppUpdater, executor: HttpExecutor) {\r\n super(options, \"github.com\", executor)\r\n }\r\n\r\n async getLatestVersion(): Promise {\r\n const cancellationToken = new CancellationToken()\r\n\r\n const feedXml: string = (await this.httpRequest(newUrlFromBase(`${this.basePath}.atom`, this.baseUrl), {\r\n accept: \"application/xml, application/atom+xml, text/xml, */*\",\r\n }, cancellationToken))!\r\n\r\n const feed = parseXml(feedXml)\r\n let latestRelease = feed.element(\"entry\", false, `No published versions on GitHub`)\r\n let version: string | null\r\n try {\r\n if (this.updater.allowPrerelease) {\r\n // noinspection TypeScriptValidateJSTypes\r\n version = latestRelease.element(\"link\").attribute(\"href\").match(hrefRegExp)!![1]\r\n }\r\n else {\r\n version = await this.getLatestVersionString(cancellationToken)\r\n for (const element of feed.getElements(\"entry\")) {\r\n if (element.element(\"link\").attribute(\"href\").match(hrefRegExp)!![1] === version) {\r\n latestRelease = element\r\n break\r\n }\r\n }\r\n\r\n }\r\n }\r\n catch (e) {\r\n throw newError(`Cannot parse releases feed: ${e.stack || e.message},\\nXML:\\n${feedXml}`, \"ERR_UPDATER_INVALID_RELEASE_FEED\")\r\n }\r\n\r\n if (version == null) {\r\n throw newError(`No published versions on GitHub`, \"ERR_UPDATER_NO_PUBLISHED_VERSIONS\")\r\n }\r\n\r\n const channelFile = getChannelFilename(getDefaultChannelName())\r\n const channelFileUrl = newUrlFromBase(this.getBaseDownloadPath(version, channelFile), this.baseUrl)\r\n const requestOptions = this.createRequestOptions(channelFileUrl)\r\n let rawData: string\r\n try {\r\n rawData = (await this.executor.request(requestOptions, cancellationToken))!!\r\n }\r\n catch (e) {\r\n if (!this.updater.allowPrerelease && e instanceof HttpError && e.statusCode === 404) {\r\n throw newError(`Cannot find ${channelFile} in the latest release artifacts (${channelFileUrl}): ${e.stack || e.message}`, \"ERR_UPDATER_CHANNEL_FILE_NOT_FOUND\")\r\n }\r\n throw e\r\n }\r\n\r\n const result = parseUpdateInfo(rawData, channelFile, channelFileUrl)\r\n if (isUseOldMacProvider()) {\r\n (result as any).releaseJsonUrl = `${githubUrl(this.options)}/${requestOptions.path}`\r\n }\r\n\r\n if (result.releaseName == null) {\r\n result.releaseName = latestRelease.elementValueOrEmpty(\"title\")\r\n }\r\n\r\n if (result.releaseNotes == null) {\r\n result.releaseNotes = computeReleaseNotes(this.updater.currentVersion, this.updater.fullChangelog, feed, latestRelease)\r\n }\r\n return result\r\n }\r\n\r\n private async getLatestVersionString(cancellationToken: CancellationToken): Promise {\r\n const options = this.options\r\n // do not use API for GitHub to avoid limit, only for custom host or GitHub Enterprise\r\n const url = (options.host == null || options.host === \"github.com\") ?\r\n newUrlFromBase(`${this.basePath}/latest`, this.baseUrl) :\r\n new URL(`${this.computeGithubBasePath(`/repos/${options.owner}/${options.repo}/releases`)}/latest`, this.baseApiUrl)\r\n try {\r\n const rawData = await this.httpRequest(url, {Accept: \"application/json\"}, cancellationToken)\r\n if (rawData == null) {\r\n return null\r\n }\r\n\r\n const releaseInfo: GithubReleaseInfo = JSON.parse(rawData)\r\n return (releaseInfo.tag_name.startsWith(\"v\")) ? releaseInfo.tag_name.substring(1) : releaseInfo.tag_name\r\n }\r\n catch (e) {\r\n throw newError(`Unable to find latest version on GitHub (${url}), please ensure a production release exists: ${e.stack || e.message}`, \"ERR_UPDATER_LATEST_VERSION_NOT_FOUND\")\r\n }\r\n }\r\n\r\n private get basePath() {\r\n return `/${this.options.owner}/${this.options.repo}/releases`\r\n }\r\n\r\n resolveFiles(updateInfo: UpdateInfo): Array {\r\n // still replace space to - due to backward compatibility\r\n return resolveFiles(updateInfo, this.baseUrl, p => this.getBaseDownloadPath(updateInfo.version, p.replace(/ /g, \"-\")))\r\n }\r\n\r\n private getBaseDownloadPath(version: string, fileName: string) {\r\n return `${this.basePath}/download/${this.options.vPrefixedTagName === false ? \"\" : \"v\"}${version}/${fileName}`\r\n }\r\n}\r\n\r\ninterface GithubReleaseInfo {\r\n readonly tag_name: string\r\n}\r\n\r\nfunction getNoteValue(parent: XElement): string {\r\n const result = parent.elementValueOrEmpty(\"content\")\r\n // GitHub reports empty notes as No content.\r\n return result === \"No content.\" ? \"\" : result\r\n}\r\n\r\nexport function computeReleaseNotes(currentVersion: semver.SemVer, isFullChangelog: boolean, feed: XElement, latestRelease: any) {\r\n if (!isFullChangelog) {\r\n return getNoteValue(latestRelease)\r\n }\r\n\r\n const releaseNotes: Array = []\r\n for (const release of feed.getElements(\"entry\")) {\r\n // noinspection TypeScriptValidateJSTypes\r\n const versionRelease = release.element(\"link\").attribute(\"href\").match(/\\/tag\\/v?([^\\/]+)$/)![1]\r\n if (semver.lt(currentVersion, versionRelease)) {\r\n releaseNotes.push({\r\n version: versionRelease,\r\n note: getNoteValue(release)\r\n })\r\n }\r\n }\r\n return releaseNotes\r\n .sort((a, b) => semver.rcompare(a.version, b.version))\r\n}\r\n"],"sourceRoot":""} diff --git a/out/providers/PrivateGitHubProvider.d.ts b/out/providers/PrivateGitHubProvider.d.ts new file mode 100644 index 0000000..27ac876 --- /dev/null +++ b/out/providers/PrivateGitHubProvider.d.ts @@ -0,0 +1,26 @@ +/// +import { GithubOptions, HttpExecutor, UpdateInfo } from "builder-util-runtime"; +import { OutgoingHttpHeaders, RequestOptions } from "http"; +import { AppUpdater } from "../AppUpdater"; +import { URL } from "url"; +import { BaseGitHubProvider } from "./GitHubProvider"; +import { ResolvedUpdateFileInfo } from "../main"; +export interface PrivateGitHubUpdateInfo extends UpdateInfo { + assets: Array; +} +export declare class PrivateGitHubProvider extends BaseGitHubProvider { + private readonly updater; + private readonly token; + constructor(options: GithubOptions, updater: AppUpdater, token: string, executor: HttpExecutor); + protected createRequestOptions(url: URL, headers?: OutgoingHttpHeaders | null): RequestOptions; + getLatestVersion(): Promise; + readonly fileExtraDownloadHeaders: OutgoingHttpHeaders | null; + private configureHeaders; + private getLatestVersionInfo; + private readonly basePath; + resolveFiles(updateInfo: PrivateGitHubUpdateInfo): Array; +} +export interface Asset { + name: string; + url: string; +} diff --git a/out/providers/PrivateGitHubProvider.js b/out/providers/PrivateGitHubProvider.js new file mode 100644 index 0000000..614cd6c --- /dev/null +++ b/out/providers/PrivateGitHubProvider.js @@ -0,0 +1,168 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.PrivateGitHubProvider = void 0; + +function _builderUtilRuntime() { + const data = require("builder-util-runtime"); + + _builderUtilRuntime = function () { + return data; + }; + + return data; +} + +function _jsYaml() { + const data = require("js-yaml"); + + _jsYaml = function () { + return data; + }; + + return data; +} + +var path = _interopRequireWildcard(require("path")); + +function _url() { + const data = require("url"); + + _url = function () { + return data; + }; + + return data; +} + +function _GitHubProvider() { + const data = require("./GitHubProvider"); + + _GitHubProvider = function () { + return data; + }; + + return data; +} + +function _main() { + const data = require("../main"); + + _main = function () { + return data; + }; + + return data; +} + +function _Provider() { + const data = require("./Provider"); + + _Provider = function () { + return data; + }; + + return data; +} + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +class PrivateGitHubProvider extends _GitHubProvider().BaseGitHubProvider { + constructor(options, updater, token, executor) { + super(options, "api.github.com", executor); + this.updater = updater; + this.token = token; + } + + createRequestOptions(url, headers) { + const result = super.createRequestOptions(url, headers); + result.redirect = "manual"; + return result; + } + + async getLatestVersion() { + const cancellationToken = new (_builderUtilRuntime().CancellationToken)(); + const channelFile = (0, _main().getChannelFilename)((0, _main().getDefaultChannelName)()); + const releaseInfo = await this.getLatestVersionInfo(cancellationToken); + const asset = releaseInfo.assets.find(it => it.name === channelFile); + + if (asset == null) { + // html_url must be always, but just to be sure + throw (0, _builderUtilRuntime().newError)(`Cannot find ${channelFile} in the release ${releaseInfo.html_url || releaseInfo.name}`, "ERR_UPDATER_CHANNEL_FILE_NOT_FOUND"); + } + + const url = new (_url().URL)(asset.url); + let result; + + try { + result = (0, _jsYaml().safeLoad)((await this.httpRequest(url, this.configureHeaders("application/octet-stream"), cancellationToken))); + } catch (e) { + if (e instanceof _builderUtilRuntime().HttpError && e.statusCode === 404) { + throw (0, _builderUtilRuntime().newError)(`Cannot find ${channelFile} in the latest release artifacts (${url}): ${e.stack || e.message}`, "ERR_UPDATER_CHANNEL_FILE_NOT_FOUND"); + } + + throw e; + } + + result.assets = releaseInfo.assets; + return result; + } + + get fileExtraDownloadHeaders() { + return this.configureHeaders("application/octet-stream"); + } + + configureHeaders(accept) { + return { + accept, + authorization: `token ${this.token}` + }; + } + + async getLatestVersionInfo(cancellationToken) { + let basePath = this.basePath; + const allowPrerelease = this.updater.allowPrerelease; + + if (!allowPrerelease) { + basePath = `${basePath}/latest`; + } + + const url = (0, _main().newUrlFromBase)(basePath, this.baseUrl); + + try { + let version = JSON.parse((await this.httpRequest(url, this.configureHeaders("application/vnd.github.v3+json"), cancellationToken))); + + if (allowPrerelease) { + version = version.find(v => v.prerelease); + } + + return version; + } catch (e) { + throw (0, _builderUtilRuntime().newError)(`Unable to find latest version on GitHub (${url}), please ensure a production release exists: ${e.stack || e.message}`, "ERR_UPDATER_LATEST_VERSION_NOT_FOUND"); + } + } + + get basePath() { + return this.computeGithubBasePath(`/repos/${this.options.owner}/${this.options.repo}/releases`); + } + + resolveFiles(updateInfo) { + return (0, _Provider().getFileList)(updateInfo).map(it => { + const name = path.posix.basename(it.url).replace(/ /g, "-"); + const asset = updateInfo.assets.find(it => it != null && it.name === name); + + if (asset == null) { + throw (0, _builderUtilRuntime().newError)(`Cannot find asset "${name}" in: ${JSON.stringify(updateInfo.assets, null, 2)}`, "ERR_UPDATER_ASSET_NOT_FOUND"); + } + + return { + url: new (_url().URL)(asset.url), + info: it + }; + }); + } + +} exports.PrivateGitHubProvider = PrivateGitHubProvider; +//# sourceMappingURL=PrivateGitHubProvider.js.map \ No newline at end of file diff --git a/out/providers/PrivateGitHubProvider.js.map b/out/providers/PrivateGitHubProvider.js.map new file mode 100644 index 0000000..984aa61 --- /dev/null +++ b/out/providers/PrivateGitHubProvider.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../src/providers/PrivateGitHubProvider.ts"],"names":[],"mappings":"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","sourcesContent":["import { CancellationToken, GithubOptions, HttpError, HttpExecutor, newError, UpdateInfo } from \"builder-util-runtime\"\r\nimport { OutgoingHttpHeaders, RequestOptions } from \"http\"\r\nimport { safeLoad } from \"js-yaml\"\r\nimport * as path from \"path\"\r\nimport { AppUpdater } from \"../AppUpdater\"\r\nimport { URL } from \"url\"\r\nimport { BaseGitHubProvider } from \"./GitHubProvider\"\r\nimport { getChannelFilename, getDefaultChannelName, newUrlFromBase, ResolvedUpdateFileInfo } from \"../main\"\r\nimport { getFileList } from \"./Provider\"\r\n\r\nexport interface PrivateGitHubUpdateInfo extends UpdateInfo {\r\n assets: Array\r\n}\r\n\r\nexport class PrivateGitHubProvider extends BaseGitHubProvider {\r\n constructor(options: GithubOptions, private readonly updater: AppUpdater, private readonly token: string, executor: HttpExecutor) {\r\n super(options, \"api.github.com\", executor)\r\n }\r\n\r\n protected createRequestOptions(url: URL, headers?: OutgoingHttpHeaders | null): RequestOptions {\r\n const result = super.createRequestOptions(url, headers);\r\n (result as any).redirect = \"manual\"\r\n return result\r\n }\r\n\r\n async getLatestVersion(): Promise {\r\n const cancellationToken = new CancellationToken()\r\n const channelFile = getChannelFilename(getDefaultChannelName())\r\n\r\n const releaseInfo = await this.getLatestVersionInfo(cancellationToken)\r\n const asset = releaseInfo.assets.find(it => it.name === channelFile)\r\n if (asset == null) {\r\n // html_url must be always, but just to be sure\r\n throw newError(`Cannot find ${channelFile} in the release ${releaseInfo.html_url || releaseInfo.name}`, \"ERR_UPDATER_CHANNEL_FILE_NOT_FOUND\")\r\n }\r\n\r\n const url = new URL(asset.url)\r\n let result: any\r\n try {\r\n result = safeLoad((await this.httpRequest(url, this.configureHeaders(\"application/octet-stream\"), cancellationToken))!!)\r\n }\r\n catch (e) {\r\n if (e instanceof HttpError && e.statusCode === 404) {\r\n throw newError(`Cannot find ${channelFile} in the latest release artifacts (${url}): ${e.stack || e.message}`, \"ERR_UPDATER_CHANNEL_FILE_NOT_FOUND\")\r\n }\r\n throw e\r\n }\r\n\r\n (result as PrivateGitHubUpdateInfo).assets = releaseInfo.assets\r\n return result\r\n }\r\n\r\n get fileExtraDownloadHeaders(): OutgoingHttpHeaders | null {\r\n return this.configureHeaders(\"application/octet-stream\")\r\n }\r\n\r\n private configureHeaders(accept: string) {\r\n return {\r\n accept,\r\n authorization: `token ${this.token}`,\r\n }\r\n }\r\n\r\n private async getLatestVersionInfo(cancellationToken: CancellationToken): Promise {\r\n let basePath = this.basePath\r\n const allowPrerelease = this.updater.allowPrerelease\r\n\r\n if (!allowPrerelease) {\r\n basePath = `${basePath}/latest`\r\n }\r\n\r\n const url = newUrlFromBase(basePath, this.baseUrl)\r\n try {\r\n let version = (JSON.parse((await this.httpRequest(url, this.configureHeaders(\"application/vnd.github.v3+json\"), cancellationToken))!!))\r\n if (allowPrerelease) {\r\n version = version.find((v: any) => v.prerelease)\r\n }\r\n return version\r\n }\r\n catch (e) {\r\n throw newError(`Unable to find latest version on GitHub (${url}), please ensure a production release exists: ${e.stack || e.message}`, \"ERR_UPDATER_LATEST_VERSION_NOT_FOUND\")\r\n }\r\n }\r\n\r\n private get basePath() {\r\n return this.computeGithubBasePath(`/repos/${this.options.owner}/${this.options.repo}/releases`)\r\n }\r\n\r\n resolveFiles(updateInfo: PrivateGitHubUpdateInfo): Array {\r\n return getFileList(updateInfo).map(it => {\r\n const name = path.posix.basename(it.url).replace(/ /g, \"-\")\r\n const asset = updateInfo.assets.find(it => it != null && it.name === name)\r\n if (asset == null) {\r\n throw newError(`Cannot find asset \"${name}\" in: ${JSON.stringify(updateInfo.assets, null, 2)}`, \"ERR_UPDATER_ASSET_NOT_FOUND\")\r\n }\r\n\r\n return {\r\n url: new URL(asset.url),\r\n info: it,\r\n }\r\n })\r\n }\r\n}\r\n\r\ninterface ReleaseInfo {\r\n name: string\r\n html_url: string\r\n assets: Array\r\n}\r\n\r\nexport interface Asset {\r\n name: string\r\n url: string\r\n}"],"sourceRoot":""} diff --git a/out/providers/Provider.d.ts b/out/providers/Provider.d.ts new file mode 100644 index 0000000..45aa5a7 --- /dev/null +++ b/out/providers/Provider.d.ts @@ -0,0 +1,25 @@ +/// +import { CancellationToken, HttpExecutor, UpdateFileInfo, UpdateInfo } from "builder-util-runtime"; +import { OutgoingHttpHeaders, RequestOptions } from "http"; +import { URL } from "url"; +import { ResolvedUpdateFileInfo } from "../main"; +export declare abstract class Provider { + protected readonly executor: HttpExecutor; + readonly useMultipleRangeRequest: boolean; + private requestHeaders; + protected constructor(executor: HttpExecutor, useMultipleRangeRequest?: boolean); + readonly fileExtraDownloadHeaders: OutgoingHttpHeaders | null; + setRequestHeaders(value: OutgoingHttpHeaders | null): void; + abstract getLatestVersion(): Promise; + abstract resolveFiles(updateInfo: UpdateInfo): Array; + /** + * Method to perform API request only to resolve update info, but not to download update. + */ + protected httpRequest(url: URL, headers?: OutgoingHttpHeaders | null, cancellationToken?: CancellationToken): Promise; + protected createRequestOptions(url: URL, headers?: OutgoingHttpHeaders | null): RequestOptions; +} +export declare function configureRequestOptionsFromUrl(url: URL, result: RequestOptions): RequestOptions; +export declare function findFile(files: Array, extension: string, not?: Array): ResolvedUpdateFileInfo | null | undefined; +export declare function parseUpdateInfo(rawData: string | null, channelFile: string, channelFileUrl: URL): UpdateInfo; +export declare function getFileList(updateInfo: UpdateInfo): Array; +export declare function resolveFiles(updateInfo: UpdateInfo, baseUrl: URL, pathTransformer?: (p: string) => string): Array; diff --git a/out/providers/Provider.js b/out/providers/Provider.js new file mode 100644 index 0000000..0fc5b82 --- /dev/null +++ b/out/providers/Provider.js @@ -0,0 +1,170 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.configureRequestOptionsFromUrl = configureRequestOptionsFromUrl; +exports.findFile = findFile; +exports.parseUpdateInfo = parseUpdateInfo; +exports.getFileList = getFileList; +exports.resolveFiles = resolveFiles; +exports.Provider = void 0; + +function _builderUtilRuntime() { + const data = require("builder-util-runtime"); + + _builderUtilRuntime = function () { + return data; + }; + + return data; +} + +function _jsYaml() { + const data = require("js-yaml"); + + _jsYaml = function () { + return data; + }; + + return data; +} + +function _main() { + const data = require("../main"); + + _main = function () { + return data; + }; + + return data; +} + +class Provider { + constructor(executor, useMultipleRangeRequest = true) { + this.executor = executor; + this.useMultipleRangeRequest = useMultipleRangeRequest; + this.requestHeaders = null; + } + + get fileExtraDownloadHeaders() { + return null; + } + + setRequestHeaders(value) { + this.requestHeaders = value; + } + /** + * Method to perform API request only to resolve update info, but not to download update. + */ + + + httpRequest(url, headers, cancellationToken) { + return this.executor.request(this.createRequestOptions(url, headers), cancellationToken); + } + + createRequestOptions(url, headers) { + const result = {}; + + if (this.requestHeaders == null) { + if (headers != null) { + result.headers = headers; + } + } else { + result.headers = headers == null ? this.requestHeaders : Object.assign({}, this.requestHeaders, headers); + } + + configureRequestOptionsFromUrl(url, result); + return result; + } + +} + +exports.Provider = Provider; + +function configureRequestOptionsFromUrl(url, result) { + result.protocol = url.protocol; + result.hostname = url.hostname; + + if (url.port) { + result.port = url.port; + } + + result.path = url.pathname + url.search; + return result; +} + +function findFile(files, extension, not) { + if (files.length === 0) { + throw (0, _builderUtilRuntime().newError)("No files provided", "ERR_UPDATER_NO_FILES_PROVIDED"); + } + + const result = files.find(it => it.url.pathname.toLowerCase().endsWith(`.${extension}`)); + + if (result != null) { + return result; + } else if (not == null) { + return files[0]; + } else { + return files.find(fileInfo => !not.some(ext => fileInfo.url.pathname.toLowerCase().endsWith(`.${ext}`))); + } +} + +function parseUpdateInfo(rawData, channelFile, channelFileUrl) { + if (rawData == null) { + throw (0, _builderUtilRuntime().newError)(`Cannot parse update info from ${channelFile} in the latest release artifacts (${channelFileUrl}): rawData: null`, "ERR_UPDATER_INVALID_UPDATE_INFO"); + } + + let result; + + try { + result = (0, _jsYaml().safeLoad)(rawData); + } catch (e) { + throw (0, _builderUtilRuntime().newError)(`Cannot parse update info from ${channelFile} in the latest release artifacts (${channelFileUrl}): ${e.stack || e.message}, rawData: ${rawData}`, "ERR_UPDATER_INVALID_UPDATE_INFO"); + } + + return result; +} + +function getFileList(updateInfo) { + const files = updateInfo.files; + + if (files != null && files.length > 0) { + return files; + } + + if (updateInfo.path != null) { + return [{ + url: updateInfo.path, + sha2: updateInfo.sha2, + sha512: updateInfo.sha512 + }]; + } else { + throw (0, _builderUtilRuntime().newError)(`No files provided: ${(0, _builderUtilRuntime().safeStringifyJson)(updateInfo)}`, "ERR_UPDATER_NO_FILES_PROVIDED"); + } +} + +function resolveFiles(updateInfo, baseUrl, pathTransformer = p => p) { + const files = getFileList(updateInfo); + const result = files.map(fileInfo => { + if (fileInfo.sha2 == null && fileInfo.sha512 == null) { + throw (0, _builderUtilRuntime().newError)(`Update info doesn't contain nor sha256 neither sha512 checksum: ${(0, _builderUtilRuntime().safeStringifyJson)(fileInfo)}`, "ERR_UPDATER_NO_CHECKSUM"); + } + + return { + url: (0, _main().newUrlFromBase)(pathTransformer(fileInfo.url), baseUrl), + info: fileInfo + }; + }); + const packages = updateInfo.packages; + const packageInfo = packages == null ? null : packages[process.arch] || packages.ia32; + + if (packageInfo != null) { + result[0].packageInfo = Object.assign({}, packageInfo, { + path: (0, _main().newUrlFromBase)(pathTransformer(packageInfo.path), baseUrl).href + }); + } + + return result; +} +//# sourceMappingURL=Provider.js.map \ No newline at end of file diff --git a/out/providers/Provider.js.map b/out/providers/Provider.js.map new file mode 100644 index 0000000..c35fe52 --- /dev/null +++ b/out/providers/Provider.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../src/providers/Provider.ts"],"names":[],"mappings":"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","sourcesContent":["import { CancellationToken, HttpExecutor, newError, safeStringifyJson, UpdateFileInfo, UpdateInfo, WindowsUpdateInfo } from \"builder-util-runtime\"\r\nimport { OutgoingHttpHeaders, RequestOptions } from \"http\"\r\nimport { safeLoad } from \"js-yaml\"\r\nimport { URL } from \"url\"\r\nimport { newUrlFromBase, ResolvedUpdateFileInfo } from \"../main\"\r\n\r\nexport abstract class Provider {\r\n private requestHeaders: OutgoingHttpHeaders | null = null\r\n\r\n protected constructor(protected readonly executor: HttpExecutor, readonly useMultipleRangeRequest = true) {\r\n }\r\n\r\n get fileExtraDownloadHeaders(): OutgoingHttpHeaders | null {\r\n return null\r\n }\r\n\r\n setRequestHeaders(value: OutgoingHttpHeaders | null): void {\r\n this.requestHeaders = value\r\n }\r\n\r\n abstract getLatestVersion(): Promise\r\n\r\n abstract resolveFiles(updateInfo: UpdateInfo): Array\r\n\r\n /**\r\n * Method to perform API request only to resolve update info, but not to download update.\r\n */\r\n protected httpRequest(url: URL, headers?: OutgoingHttpHeaders | null, cancellationToken?: CancellationToken) {\r\n return this.executor.request(this.createRequestOptions(url, headers), cancellationToken)\r\n }\r\n\r\n protected createRequestOptions(url: URL, headers?: OutgoingHttpHeaders | null): RequestOptions {\r\n const result: RequestOptions = {}\r\n if (this.requestHeaders == null) {\r\n if (headers != null) {\r\n result.headers = headers\r\n }\r\n }\r\n else {\r\n result.headers = headers == null ? this.requestHeaders : {...this.requestHeaders, ...headers}\r\n }\r\n\r\n configureRequestOptionsFromUrl(url, result)\r\n return result\r\n }\r\n}\r\n\r\nexport function configureRequestOptionsFromUrl(url: URL, result: RequestOptions): RequestOptions {\r\n result.protocol = url.protocol\r\n result.hostname = url.hostname\r\n if (url.port) {\r\n result.port = url.port\r\n }\r\n result.path = url.pathname + url.search\r\n return result\r\n}\r\n\r\nexport function findFile(files: Array, extension: string, not?: Array): ResolvedUpdateFileInfo | null | undefined {\r\n if (files.length === 0) {\r\n throw newError(\"No files provided\", \"ERR_UPDATER_NO_FILES_PROVIDED\")\r\n }\r\n\r\n const result = files.find(it => it.url.pathname.toLowerCase().endsWith(`.${extension}`))\r\n if (result != null) {\r\n return result\r\n }\r\n else if (not == null) {\r\n return files[0]\r\n }\r\n else {\r\n return files.find(fileInfo => !not.some(ext => fileInfo.url.pathname.toLowerCase().endsWith(`.${ext}`)))\r\n }\r\n}\r\n\r\nexport function parseUpdateInfo(rawData: string | null, channelFile: string, channelFileUrl: URL): UpdateInfo {\r\n if (rawData == null) {\r\n throw newError(`Cannot parse update info from ${channelFile} in the latest release artifacts (${channelFileUrl}): rawData: null`, \"ERR_UPDATER_INVALID_UPDATE_INFO\")\r\n }\r\n\r\n let result: UpdateInfo\r\n try {\r\n result = safeLoad(rawData) as UpdateInfo\r\n }\r\n catch (e) {\r\n throw newError(`Cannot parse update info from ${channelFile} in the latest release artifacts (${channelFileUrl}): ${e.stack || e.message}, rawData: ${rawData}`, \"ERR_UPDATER_INVALID_UPDATE_INFO\")\r\n }\r\n return result\r\n}\r\n\r\nexport function getFileList(updateInfo: UpdateInfo): Array {\r\n const files = updateInfo.files\r\n if (files != null && files.length > 0) {\r\n return files\r\n }\r\n\r\n if (updateInfo.path != null) {\r\n return [\r\n {\r\n url: updateInfo.path,\r\n sha2: (updateInfo as any).sha2,\r\n sha512: updateInfo.sha512,\r\n } as any,\r\n ]\r\n }\r\n else {\r\n throw newError(`No files provided: ${safeStringifyJson(updateInfo)}`, \"ERR_UPDATER_NO_FILES_PROVIDED\")\r\n }\r\n}\r\n\r\nexport function resolveFiles(updateInfo: UpdateInfo, baseUrl: URL, pathTransformer: (p: string) => string = p => p): Array {\r\n const files = getFileList(updateInfo)\r\n const result: Array = files.map(fileInfo => {\r\n if ((fileInfo as any).sha2 == null && fileInfo.sha512 == null) {\r\n throw newError(`Update info doesn't contain nor sha256 neither sha512 checksum: ${safeStringifyJson(fileInfo)}`, \"ERR_UPDATER_NO_CHECKSUM\")\r\n }\r\n return {\r\n url: newUrlFromBase(pathTransformer(fileInfo.url), baseUrl),\r\n info: fileInfo,\r\n }\r\n })\r\n\r\n const packages = (updateInfo as WindowsUpdateInfo).packages\r\n const packageInfo = packages == null ? null : (packages[process.arch] || packages.ia32)\r\n if (packageInfo != null) {\r\n (result[0] as any).packageInfo = {\r\n ...packageInfo,\r\n path: newUrlFromBase(pathTransformer(packageInfo.path), baseUrl).href,\r\n }\r\n }\r\n return result\r\n}"],"sourceRoot":""} diff --git a/out/windowsExecutableCodeSignatureVerifier.d.ts b/out/windowsExecutableCodeSignatureVerifier.d.ts new file mode 100644 index 0000000..9e5c620 --- /dev/null +++ b/out/windowsExecutableCodeSignatureVerifier.d.ts @@ -0,0 +1,2 @@ +import { Logger } from "./main"; +export declare function verifySignature(publisherNames: Array, tempUpdateFile: string, logger: Logger): Promise; diff --git a/out/windowsExecutableCodeSignatureVerifier.js b/out/windowsExecutableCodeSignatureVerifier.js new file mode 100644 index 0000000..a25a097 --- /dev/null +++ b/out/windowsExecutableCodeSignatureVerifier.js @@ -0,0 +1,129 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.verifySignature = verifySignature; + +function _builderUtilRuntime() { + const data = require("builder-util-runtime"); + + _builderUtilRuntime = function () { + return data; + }; + + return data; +} + +function _child_process() { + const data = require("child_process"); + + _child_process = function () { + return data; + }; + + return data; +} + +function os() { + const data = _interopRequireWildcard(require("os")); + + os = function () { + return data; + }; + + return data; +} + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +// $certificateInfo = (Get-AuthenticodeSignature 'xxx\yyy.exe' +// | where {$_.Status.Equals([System.Management.Automation.SignatureStatus]::Valid) -and $_.SignerCertificate.Subject.Contains("CN=siemens.com")}) +// | Out-String ; if ($certificateInfo) { exit 0 } else { exit 1 } +function verifySignature(publisherNames, tempUpdateFile, logger) { + return new Promise(resolve => { + // https://github.com/electron-userland/electron-builder/issues/2421 + // https://github.com/electron-userland/electron-builder/issues/2535 + (0, _child_process().execFile)("powershell.exe", ["-NoProfile", "-NonInteractive", "-InputFormat", "None", "-Command", `Get-AuthenticodeSignature '${tempUpdateFile}' | ConvertTo-Json -Compress`], { + timeout: 20 * 1000 + }, (error, stdout, stderr) => { + try { + if (error != null || stderr) { + handleError(logger, error, stderr); + resolve(null); + return; + } + + const data = parseOut(stdout); + + if (data.Status === 0) { + const name = (0, _builderUtilRuntime().parseDn)(data.SignerCertificate.Subject).get("CN"); + + if (publisherNames.includes(name)) { + resolve(null); + return; + } + } + + const result = `publisherNames: ${publisherNames.join(" | ")}, raw info: ` + JSON.stringify(data, (name, value) => name === "RawData" ? undefined : value, 2); + logger.warn(`Sign verification failed, installer signed with incorrect certificate: ${result}`); + resolve(result); + } catch (e) { + logger.warn(`Cannot execute Get-AuthenticodeSignature: ${error}. Ignoring signature validation due to unknown error.`); + resolve(null); + return; + } + }); + }); +} + +function parseOut(out) { + const data = JSON.parse(out); + delete data.PrivateKey; + delete data.IsOSBinary; + delete data.SignatureType; + const signerCertificate = data.SignerCertificate; + + if (signerCertificate != null) { + delete signerCertificate.Archived; + delete signerCertificate.Extensions; + delete signerCertificate.Handle; + delete signerCertificate.HasPrivateKey; // duplicates data.SignerCertificate (contains RawData) + + delete signerCertificate.SubjectName; + } + + delete data.Path; + return data; +} + +function handleError(logger, error, stderr) { + if (isOldWin6()) { + logger.warn(`Cannot execute Get-AuthenticodeSignature: ${error || stderr}. Ignoring signature validation due to unsupported powershell version. Please upgrade to powershell 3 or higher.`); + return; + } + + try { + (0, _child_process().execFileSync)("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", "ConvertTo-Json test"], { + timeout: 10 * 1000 + }); + } catch (testError) { + logger.warn(`Cannot execute ConvertTo-Json: ${testError.message}. Ignoring signature validation due to unsupported powershell version. Please upgrade to powershell 3 or higher.`); + return; + } + + if (error != null) { + throw error; + } + + if (stderr) { + logger.warn(`Cannot execute Get-AuthenticodeSignature, stderr: ${stderr}. Ignoring signature validation due to unknown stderr.`); + return; + } +} + +function isOldWin6() { + const winVersion = os().release(); + return winVersion.startsWith("6.") && !winVersion.startsWith("6.3"); +} +//# sourceMappingURL=windowsExecutableCodeSignatureVerifier.js.map \ No newline at end of file diff --git a/out/windowsExecutableCodeSignatureVerifier.js.map b/out/windowsExecutableCodeSignatureVerifier.js.map new file mode 100644 index 0000000..88e9ec5 --- /dev/null +++ b/out/windowsExecutableCodeSignatureVerifier.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/windowsExecutableCodeSignatureVerifier.ts"],"names":[],"mappings":"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","sourcesContent":["import { parseDn } from \"builder-util-runtime\"\r\nimport { execFile, execFileSync } from \"child_process\"\r\nimport * as os from \"os\"\r\nimport { Logger } from \"./main\"\r\n\r\n// $certificateInfo = (Get-AuthenticodeSignature 'xxx\\yyy.exe'\r\n// | where {$_.Status.Equals([System.Management.Automation.SignatureStatus]::Valid) -and $_.SignerCertificate.Subject.Contains(\"CN=siemens.com\")})\r\n// | Out-String ; if ($certificateInfo) { exit 0 } else { exit 1 }\r\nexport function verifySignature(publisherNames: Array, tempUpdateFile: string, logger: Logger): Promise {\r\n return new Promise(resolve => {\r\n // https://github.com/electron-userland/electron-builder/issues/2421\r\n // https://github.com/electron-userland/electron-builder/issues/2535\r\n execFile(\"powershell.exe\", [\"-NoProfile\", \"-NonInteractive\", \"-InputFormat\", \"None\", \"-Command\", `Get-AuthenticodeSignature '${tempUpdateFile}' | ConvertTo-Json -Compress`], {\r\n timeout: 20 * 1000\r\n }, (error, stdout, stderr) => {\r\n try {\r\n if (error != null || stderr) {\r\n handleError(logger, error, stderr)\r\n resolve(null)\r\n return\r\n }\r\n\r\n const data = parseOut(stdout)\r\n if (data.Status === 0) {\r\n const name = parseDn(data.SignerCertificate.Subject).get(\"CN\")!\r\n if (publisherNames.includes(name)) {\r\n resolve(null)\r\n return\r\n }\r\n }\r\n\r\n const result = `publisherNames: ${publisherNames.join(\" | \")}, raw info: ` + JSON.stringify(data, (name, value) => name === \"RawData\" ? undefined : value, 2)\r\n logger.warn(`Sign verification failed, installer signed with incorrect certificate: ${result}`)\r\n resolve(result)\r\n }\r\n catch (e) {\r\n logger.warn(`Cannot execute Get-AuthenticodeSignature: ${error}. Ignoring signature validation due to unknown error.`)\r\n resolve(null)\r\n return\r\n }\r\n })\r\n })\r\n}\r\n\r\nfunction parseOut(out: string): any {\r\n const data = JSON.parse(out)\r\n delete data.PrivateKey\r\n delete data.IsOSBinary\r\n delete data.SignatureType\r\n const signerCertificate = data.SignerCertificate\r\n if (signerCertificate != null) {\r\n delete signerCertificate.Archived\r\n delete signerCertificate.Extensions\r\n delete signerCertificate.Handle\r\n delete signerCertificate.HasPrivateKey\r\n // duplicates data.SignerCertificate (contains RawData)\r\n delete signerCertificate.SubjectName\r\n }\r\n delete data.Path\r\n return data\r\n}\r\n\r\nfunction handleError(logger: Logger, error: Error | null, stderr: string | null) {\r\n if (isOldWin6()) {\r\n logger.warn(`Cannot execute Get-AuthenticodeSignature: ${error || stderr}. Ignoring signature validation due to unsupported powershell version. Please upgrade to powershell 3 or higher.`)\r\n return\r\n }\r\n\r\n try {\r\n execFileSync(\"powershell.exe\", [\"-NoProfile\", \"-NonInteractive\", \"-Command\", \"ConvertTo-Json test\"], {timeout: 10 * 1000})\r\n }\r\n catch (testError) {\r\n logger.warn(`Cannot execute ConvertTo-Json: ${testError.message}. Ignoring signature validation due to unsupported powershell version. Please upgrade to powershell 3 or higher.`)\r\n return\r\n }\r\n\r\n if (error != null) {\r\n throw error\r\n }\r\n\r\n if (stderr) {\r\n logger.warn(`Cannot execute Get-AuthenticodeSignature, stderr: ${stderr}. Ignoring signature validation due to unknown stderr.`)\r\n return\r\n }\r\n}\r\n\r\nfunction isOldWin6() {\r\n const winVersion = os.release()\r\n return winVersion.startsWith(\"6.\") && !winVersion.startsWith(\"6.3\")\r\n}\r\n"],"sourceRoot":""} diff --git a/package.json b/package.json new file mode 100644 index 0000000..d7e2ca1 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "electron-updater", + "version": "0.0.0-semantic-release", + "description": "Cross platform updater for electron applications", + "main": "out/main.js", + "author": "Vladimir Krivosheev", + "license": "MIT", + "repository": "electron-userland/electron-builder", + "bugs": "https://github.com/electron-userland/electron-builder/issues", + "homepage": "https://github.com/electron-userland/electron-builder", + "files": [ + "out" + ], + "dependencies": { + "lazy-val": "^1.0.3", + "bluebird-lst": "^1.0.5", + "fs-extra-p": "^4.6.1", + "js-yaml": "^3.12.0", + "semver": "^5.5.1", + "source-map-support": "^0.5.9", + "builder-util-runtime": "~0.0.0-semantic-release", + "electron-is-dev": "^0.3.0", + "lodash.isequal": "^4.5.0" + }, + "typings": "./out/main.d.ts", + "publishConfig": { + "tag": "next" + } +}