"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WinPackager = void 0;
const bluebird_lst_1 = require("bluebird-lst");
const builder_util_1 = require("builder-util");
const builder_util_runtime_1 = require("builder-util-runtime");
const fs_1 = require("builder-util/out/fs");
const crypto_1 = require("crypto");
const promises_1 = require("fs/promises");
const isCI = require("is-ci");
const lazy_val_1 = require("lazy-val");
const path = require("path");
const codesign_1 = require("./codeSign/codesign");
const windowsCodeSign_1 = require("./codeSign/windowsCodeSign");
const core_1 = require("./core");
const platformPackager_1 = require("./platformPackager");
const NsisTarget_1 = require("./targets/nsis/NsisTarget");
const nsisUtil_1 = require("./targets/nsis/nsisUtil");
const WebInstallerTarget_1 = require("./targets/nsis/WebInstallerTarget");
const targetFactory_1 = require("./targets/targetFactory");
const cacheManager_1 = require("./util/cacheManager");
const flags_1 = require("./util/flags");
const timer_1 = require("./util/timer");
const vm_1 = require("./vm/vm");
const wine_1 = require("./wine");
class WinPackager extends platformPackager_1.PlatformPackager {
    constructor(info) {
        super(info, core_1.Platform.WINDOWS);
        this.cscInfo = new lazy_val_1.Lazy(() => {
            const platformSpecificBuildOptions = this.platformSpecificBuildOptions;
            if (platformSpecificBuildOptions.certificateSubjectName != null || platformSpecificBuildOptions.certificateSha1 != null) {
                return this.vm.value
                    .then(vm => windowsCodeSign_1.getCertificateFromStoreInfo(platformSpecificBuildOptions, vm))
                    .catch(e => {
                    // https://github.com/electron-userland/electron-builder/pull/2397
                    if (platformSpecificBuildOptions.sign == null) {
                        throw e;
                    }
                    else {
                        builder_util_1.log.debug({ error: e }, "getCertificateFromStoreInfo error");
                        return null;
                    }
                });
            }
            const certificateFile = platformSpecificBuildOptions.certificateFile;
            if (certificateFile != null) {
                const certificatePassword = this.getCscPassword();
                return Promise.resolve({
                    file: certificateFile,
                    password: certificatePassword == null ? null : certificatePassword.trim(),
                });
            }
            const cscLink = this.getCscLink("WIN_CSC_LINK");
            if (cscLink == null) {
                return Promise.resolve(null);
            }
            return (codesign_1.downloadCertificate(cscLink, this.info.tempDirManager, this.projectDir)
                // before then
                .catch(e => {
                if (e instanceof builder_util_1.InvalidConfigurationError) {
                    throw new builder_util_1.InvalidConfigurationError(`Env WIN_CSC_LINK is not correct, cannot resolve: ${e.message}`);
                }
                else {
                    throw e;
                }
            })
                .then(path => {
                return {
                    file: path,
                    password: this.getCscPassword(),
                };
            }));
        });
        this._iconPath = new lazy_val_1.Lazy(() => this.getOrConvertIcon("ico"));
        this.vm = new lazy_val_1.Lazy(() => (process.platform === "win32" ? Promise.resolve(new vm_1.VmManager()) : vm_1.getWindowsVm(this.debugLogger)));
        this.computedPublisherName = new lazy_val_1.Lazy(async () => {
            const publisherName = this.platformSpecificBuildOptions.publisherName;
            if (publisherName === null) {
                return null;
            }
            else if (publisherName != null) {
                return builder_util_1.asArray(publisherName);
            }
            const certInfo = await this.lazyCertInfo.value;
            return certInfo == null ? null : [certInfo.commonName];
        });
        this.lazyCertInfo = new lazy_val_1.Lazy(async () => {
            const cscInfo = await this.cscInfo.value;
            if (cscInfo == null) {
                return null;
            }
            if ("subject" in cscInfo) {
                const bloodyMicrosoftSubjectDn = cscInfo.subject;
                return {
                    commonName: builder_util_runtime_1.parseDn(bloodyMicrosoftSubjectDn).get("CN"),
                    bloodyMicrosoftSubjectDn,
                };
            }
            const cscFile = cscInfo.file;
            if (cscFile == null) {
                return null;
            }
            return await windowsCodeSign_1.getCertInfo(cscFile, cscInfo.password || "");
        });
    }
    get isForceCodeSigningVerification() {
        return this.platformSpecificBuildOptions.verifyUpdateCodeSignature !== false;
    }
    get defaultTarget() {
        return ["nsis"];
    }
    doGetCscPassword() {
        return platformPackager_1.chooseNotNull(platformPackager_1.chooseNotNull(this.platformSpecificBuildOptions.certificatePassword, process.env.WIN_CSC_KEY_PASSWORD), super.doGetCscPassword());
    }
    createTargets(targets, mapper) {
        let copyElevateHelper;
        const getCopyElevateHelper = () => {
            if (copyElevateHelper == null) {
                copyElevateHelper = new nsisUtil_1.CopyElevateHelper();
            }
            return copyElevateHelper;
        };
        let helper;
        const getHelper = () => {
            if (helper == null) {
                helper = new nsisUtil_1.AppPackageHelper(getCopyElevateHelper());
            }
            return helper;
        };
        for (const name of targets) {
            if (name === core_1.DIR_TARGET) {
                continue;
            }
            if (name === "nsis" || name === "portable") {
                mapper(name, outDir => new NsisTarget_1.NsisTarget(this, outDir, name, getHelper()));
            }
            else if (name === "nsis-web") {
                // package file format differs from nsis target
                mapper(name, outDir => new WebInstallerTarget_1.WebInstallerTarget(this, path.join(outDir, name), name, new nsisUtil_1.AppPackageHelper(getCopyElevateHelper())));
            }
            else {
                const targetClass = (() => {
                    switch (name) {
                        case "squirrel":
                            try {
                                return require("electron-builder-squirrel-windows").default;
                            }
                            catch (e) {
                                throw new builder_util_1.InvalidConfigurationError(`Module electron-builder-squirrel-windows must be installed in addition to build Squirrel.Windows: ${e.stack || e}`);
                            }
                        case "appx":
                            return require("./targets/AppxTarget").default;
                        case "msi":
                            return require("./targets/MsiTarget").default;
                        default:
                            return null;
                    }
                })();
                mapper(name, outDir => (targetClass === null ? targetFactory_1.createCommonTarget(name, outDir, this) : new targetClass(this, outDir, name)));
            }
        }
    }
    getIconPath() {
        return this._iconPath.value;
    }
    async sign(file, logMessagePrefix) {
        const signOptions = {
            path: file,
            name: this.appInfo.productName,
            site: await this.appInfo.computePackageUrl(),
            options: this.platformSpecificBuildOptions,
        };
        const cscInfo = await this.cscInfo.value;
        if (cscInfo == null) {
            if (this.platformSpecificBuildOptions.sign != null) {
                await windowsCodeSign_1.sign(signOptions, this);
            }
            else if (this.forceCodeSigning) {
                throw new builder_util_1.InvalidConfigurationError(`App is not signed and "forceCodeSigning" is set to true, please ensure that code signing configuration is correct, please see https://electron.build/code-signing`);
            }
            return;
        }
        if (logMessagePrefix == null) {
            logMessagePrefix = "signing";
        }
        if ("file" in cscInfo) {
            builder_util_1.log.info({
                file: builder_util_1.log.filePath(file),
                certificateFile: cscInfo.file,
            }, logMessagePrefix);
        }
        else {
            const info = cscInfo;
            builder_util_1.log.info({
                file: builder_util_1.log.filePath(file),
                subject: info.subject,
                thumbprint: info.thumbprint,
                store: info.store,
                user: info.isLocalMachineStore ? "local machine" : "current user",
            }, logMessagePrefix);
        }
        await this.doSign({
            ...signOptions,
            cscInfo,
            options: {
                ...this.platformSpecificBuildOptions,
            },
        });
    }
    async doSign(options) {
        for (let i = 0; i < 3; i++) {
            try {
                await windowsCodeSign_1.sign(options, this);
                break;
            }
            catch (e) {
                // https://github.com/electron-userland/electron-builder/issues/1414
                const message = e.message;
                if (message != null && message.includes("Couldn't resolve host name")) {
                    builder_util_1.log.warn({ error: message, attempt: i + 1 }, `cannot sign`);
                    continue;
                }
                throw e;
            }
        }
    }
    async signAndEditResources(file, arch, outDir, internalName, requestedExecutionLevel) {
        const appInfo = this.appInfo;
        const files = [];
        const args = [
            file,
            "--set-version-string",
            "FileDescription",
            appInfo.productName,
            "--set-version-string",
            "ProductName",
            appInfo.productName,
            "--set-version-string",
            "LegalCopyright",
            appInfo.copyright,
            "--set-file-version",
            appInfo.shortVersion || appInfo.buildVersion,
            "--set-product-version",
            appInfo.shortVersionWindows || appInfo.getVersionInWeirdWindowsForm(),
        ];
        if (internalName != null) {
            args.push("--set-version-string", "InternalName", internalName, "--set-version-string", "OriginalFilename", "");
        }
        if (requestedExecutionLevel != null && requestedExecutionLevel !== "asInvoker") {
            args.push("--set-requested-execution-level", requestedExecutionLevel);
        }
        builder_util_1.use(appInfo.companyName, it => args.push("--set-version-string", "CompanyName", it));
        builder_util_1.use(this.platformSpecificBuildOptions.legalTrademarks, it => args.push("--set-version-string", "LegalTrademarks", it));
        const iconPath = await this.getIconPath();
        builder_util_1.use(iconPath, it => {
            files.push(it);
            args.push("--set-icon", it);
        });
        const config = this.config;
        const cscInfoForCacheDigest = !flags_1.isBuildCacheEnabled() || isCI || config.electronDist != null ? null : await this.cscInfo.value;
        let buildCacheManager = null;
        // resources editing doesn't change executable for the same input and executed quickly - no need to complicate
        if (cscInfoForCacheDigest != null) {
            const cscFile = cscInfoForCacheDigest.file;
            if (cscFile != null) {
                files.push(cscFile);
            }
            const timer = timer_1.time("executable cache");
            const hash = crypto_1.createHash("sha512");
            hash.update(config.electronVersion || "no electronVersion");
            hash.update(JSON.stringify(this.platformSpecificBuildOptions));
            hash.update(JSON.stringify(args));
            hash.update(this.platformSpecificBuildOptions.certificateSha1 || "no certificateSha1");
            hash.update(this.platformSpecificBuildOptions.certificateSubjectName || "no subjectName");
            buildCacheManager = new cacheManager_1.BuildCacheManager(outDir, file, arch);
            if (await buildCacheManager.copyIfValid(await cacheManager_1.digest(hash, files))) {
                timer.end();
                return;
            }
            timer.end();
        }
        const timer = timer_1.time("wine&sign");
        // rcedit crashed of executed using wine, resourcehacker works
        if (process.platform === "win32" || process.platform === "darwin") {
            await builder_util_1.executeAppBuilder(["rcedit", "--args", JSON.stringify(args)], undefined /* child-process */, {}, 3 /* retry three times */);
        }
        else if (this.info.framework.name === "electron") {
            const vendorPath = await windowsCodeSign_1.getSignVendorPath();
            await wine_1.execWine(path.join(vendorPath, "rcedit-ia32.exe"), path.join(vendorPath, "rcedit-x64.exe"), args);
        }
        await this.sign(file);
        timer.end();
        if (buildCacheManager != null) {
            await buildCacheManager.save();
        }
    }
    isSignDlls() {
        return this.platformSpecificBuildOptions.signDlls === true;
    }
    createTransformerForExtraFiles(packContext) {
        if (this.platformSpecificBuildOptions.signAndEditExecutable === false) {
            return null;
        }
        return file => {
            if (file.endsWith(".exe") || (this.isSignDlls() && file.endsWith(".dll"))) {
                const parentDir = path.dirname(file);
                if (parentDir !== packContext.appOutDir) {
                    return new fs_1.CopyFileTransformer(file => this.sign(file));
                }
            }
            return null;
        };
    }
    async signApp(packContext, isAsar) {
        const exeFileName = `${this.appInfo.productFilename}.exe`;
        if (this.platformSpecificBuildOptions.signAndEditExecutable === false) {
            return;
        }
        await bluebird_lst_1.default.map(promises_1.readdir(packContext.appOutDir), (file) => {
            if (file === exeFileName) {
                return this.signAndEditResources(path.join(packContext.appOutDir, exeFileName), packContext.arch, packContext.outDir, path.basename(exeFileName, ".exe"), this.platformSpecificBuildOptions.requestedExecutionLevel);
            }
            else if (file.endsWith(".exe") || (this.isSignDlls() && file.endsWith(".dll"))) {
                return this.sign(path.join(packContext.appOutDir, file));
            }
            return null;
        });
        if (!isAsar) {
            return;
        }
        const outResourcesDir = path.join(packContext.appOutDir, "resources", "app.asar.unpacked");
        // noinspection JSUnusedLocalSymbols
        const fileToSign = await fs_1.walk(outResourcesDir, (file, stat) => stat.isDirectory() || file.endsWith(".exe") || (this.isSignDlls() && file.endsWith(".dll")));
        await bluebird_lst_1.default.map(fileToSign, file => this.sign(file), { concurrency: 4 });
    }
}
exports.WinPackager = WinPackager;
//# sourceMappingURL=winPackager.js.map