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); } }