production-taskbar-client / src / main / ipc / updateChannel.js
updateChannel.js
Raw
import os from "os";
import electronLog from "electron-log";
import { ipcMain } from "electron";
import { NsisUpdater, CancellationToken } from "electron-updater";
import { valid, lt } from "semver";

const is64bit = os.arch() === "x64";
const isWin = process.platform === "win32";

const kUpdatePrimaryUrl = is64bit
  ? process.env.AUTOUPDATE_PRIMARY_URL
  : process.env.AUTOUPDATE_PRIMARY_URL_X86;
const kUpdateSecondaryUrl = is64bit
  ? process.env.AUTOUPDATE_SECONDARY_URL
  : process.env.AUTOUPDATE_SECONDARY_URL_X86;

const generateRandomDelay = (min, max) => {
  return Math.floor(Math.random() * (max - min)) + min;
};

const kRandom =
  process.env.AUTO_UPDATE_TIMEOUT_ADD_RANDOM_IN_MIN * 60 * 1000 || 0;
const random = generateRandomDelay(0, kRandom);
const kTimeout =
  process.env.AUTO_UPDATE_TIMEOUT_IN_MIN * 60 * 1000 + random ||
  15 * 60 * 1000 + random;

const kUpdateChannel = "app-update";
const kUpdateDownloaded = "update-downloaded";
const kError = "error";
const kChecking = "checking-for-update";
const kUpdateAvailable = "update-available";
const kDownloadProgress = "download-progress";

export default function initAppUpdater(mainWindow) {
  if (kUpdatePrimaryUrl) {
    const optionsPrimary = {
      // requestHeaders: {
      //   Authorization: "Basic AUTH_CREDS_VALUE",
      // },
      provider: "generic",
      url: kUpdatePrimaryUrl,
    };
    const optionsSecondary = {
      provider: "generic",
      url: kUpdateSecondaryUrl,
    };
    const appUpdater = new NsisUpdater(optionsPrimary);
    let cancellationToken = new CancellationToken();
    appUpdater.autoDownload = false;
    appUpdater.disableWebInstaller = true;
    appUpdater.logger = electronLog.create("electron-updater-log");
    appUpdater.logger.transports.file.level = "warn";
    appUpdater.logger.transports.file.format =
      "[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [updater] [{level}] {text}";

    let isUpdating = false;
    let isSecondary = false;

    const downloadApp = () => {
      appUpdater.downloadUpdate(cancellationToken);
    };

    const abortDownload = () => {
      cancellationToken.cancel();
      cancellationToken = new CancellationToken();
    };

    const setPrimary = () => {
      isSecondary = false;
      appUpdater.setFeedURL(optionsPrimary);
    };

    const setSecondary = () => {
      isSecondary = true;
      appUpdater.setFeedURL(optionsSecondary);
    };

    const checkForUpdates = async (server = "primary") => {
      if (isUpdating) return;

      const check = async () => {
        try {
          await appUpdater.checkForUpdates();
        } catch (e) {
          if (server === "secondary") {
            const data = e;
            data.secondaryServer = isSecondary;
            mainWindow.webContents.send(kUpdateChannel, {
              event: kError,
              data: JSON.stringify(data),
            });
          }
        }
      };

      isUpdating = true;
      if (server === "primary") {
        if (isSecondary) setPrimary();
        check();
      } else if (server === "secondary") {
        setSecondary();
        check();
      }
    };

    appUpdater.on("error", (error) => {
      isUpdating = false;

      if (isSecondary) {
        const data = error;
        data.onError = true;
        data.secondaryServer = isSecondary;
        mainWindow.webContents.send(kUpdateChannel, {
          event: kError,
          data: JSON.stringify(data),
        });
      }

      if (kUpdateSecondaryUrl && !isSecondary) {
        checkForUpdates("secondary");
      }
    });

    appUpdater.on("checking-for-update", () => {
      isUpdating = true;
      mainWindow.webContents.send(kUpdateChannel, { event: kChecking });
    });

    appUpdater.on("update-available", (info) => {
      isUpdating = false;

      try {
        const notes = JSON.parse(info?.releaseNotes);
        if (isWin) {
          const minWinVersion = valid(notes?.minWinVersion);
          const winVersion = valid(os.release());
          const updateArch = notes?.arch;

          if (winVersion && minWinVersion && lt(winVersion, minWinVersion)) {
            abortDownload();
            appUpdater.logger.warn(
              `Download cancelled, minimum Windows version ${minWinVersion} required. Current ${winVersion} is not supported.`
            );
            return;
          }

          if (updateArch && updateArch !== os.arch()) {
            abortDownload();
            appUpdater.logger.warn(
              `Download cancelled, release is for ${updateArch} windows. Current ${os.arch()} is not supported.`
            );
            return;
          }
        }
        // const updateElectronVersion = valid(notes?.electronVersion);
        // const updateArch = notes?.arch;
      } catch (e) {
        appUpdater.logger.warn(
          `Can't parse releaseNotes, continue download. Error: ${e}`
        );
      }
      downloadApp();
      mainWindow.webContents.send(kUpdateChannel, {
        event: kUpdateAvailable,
        data: JSON.stringify(info),
      });
    });

    appUpdater.on("update-not-available", () => {
      isUpdating = false;
    });

    appUpdater.on(
      "download-progress",
      (progress, ProgressInfo, bytesPerSecond, percent, total, transferred) => {
        isUpdating = true;
        mainWindow.webContents.send(kUpdateChannel, {
          event: kDownloadProgress,
          data: {
            progress,
            ProgressInfo,
            bytesPerSecond,
            percent,
            total,
            transferred,
          },
        });
      }
    );

    appUpdater.on("update-downloaded", () => {
      mainWindow.webContents.send(kUpdateChannel, { event: kUpdateDownloaded });

      setTimeout(() => {
        appUpdater.quitAndInstall(false, true);
        isUpdating = false;
      }, 5000);
    });

    ipcMain.handle(kUpdateChannel, (_event, arg) => {
      if (arg === kChecking) checkForUpdates();
    });

    setTimeout(() => checkForUpdates(), 10 * 1000 + random);
    setInterval(() => checkForUpdates(), kTimeout);
  }
}