Files
Fractured/tools/fractured-launcher-electron/lib/auto-update.js
T
Docker Build 9cb3c79dbe fix(launcher): opt-in GitHub auto-update; clarify Gitea for from_release
- Gate electron-updater GitHub provider on launcher_updates_from_github (default false)
  so GITHUB_TOKEN no longer targets the source repo without latest.yml.
- Improve GitHub releases 404 hint when assets are on Gitea.
- Document in README and default-launcher.json.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 15:38:07 -05:00

149 lines
4.5 KiB
JavaScript

'use strict';
const { dialog } = require('electron');
const { autoUpdater } = require('electron-updater');
const { useGiteaReleases, getGiteaUpdaterFeedBase } = require('./gitea-release');
/**
* @param {import('electron').App} app
* @param {() => import('electron').BrowserWindow | null} getMainWindow
* @param {{
* updateFeedUrl?: string,
* githubOwner?: string,
* githubRepo?: string,
* githubToken?: string,
* giteaToken?: string,
* allowGithubLauncherUpdates?: boolean,
* config?: object,
* }} opts
*/
async function setupAutoUpdater(app, getMainWindow, opts = {}) {
if (!app.isPackaged) {
return {
checkNow: async () => ({ skipped: true, reason: 'development build' }),
};
}
const ghToken = String(opts.githubToken || '').trim();
const giteaTok = String(opts.giteaToken || '').trim();
const envGeneric = String(process.env.LAUNCHER_UPDATE_URL || '').trim();
const configGeneric = String(opts.updateFeedUrl || '').trim();
let genericUrl = envGeneric || configGeneric;
let genericAuthHeader = '';
if (!genericUrl && opts.config && useGiteaReleases(opts.config)) {
const gfb = await getGiteaUpdaterFeedBase(opts.config);
if (gfb && gfb.url) {
genericUrl = gfb.url;
const t = String(gfb.token || giteaTok || '').trim();
if (t) genericAuthHeader = `token ${t}`;
}
} else if (genericUrl) {
if (giteaTok) genericAuthHeader = `token ${giteaTok}`;
else if (ghToken) genericAuthHeader = `Bearer ${ghToken}`;
}
const owner = String(opts.githubOwner || '').trim();
const repo = String(opts.githubRepo || '').trim();
let feedConfigured = false;
if (genericUrl) {
const base = genericUrl.replace(/\/?$/, '/');
autoUpdater.setFeedURL({
provider: 'generic',
url: base,
});
if (genericAuthHeader) {
autoUpdater.requestHeaders = {
...autoUpdater.requestHeaders,
Authorization: genericAuthHeader,
};
}
feedConfigured = true;
} else if (opts.allowGithubLauncherUpdates && ghToken && owner && repo) {
autoUpdater.setFeedURL({
provider: 'github',
owner,
repo,
private: true,
token: ghToken,
});
feedConfigured = true;
}
if (!feedConfigured) {
const reason =
'No update channel configured. Set launcher.json → update_feed_url (HTTPS folder with latest.yml), ' +
'or fill gitea.base_url/owner/repo (+ GITEA_TOKEN for private), ' +
'or set launcher_updates_from_github to true with GITHUB_TOKEN for private GitHub release feeds.';
return {
checkNow: async () => ({ skipped: true, reason }),
};
}
autoUpdater.autoDownload = true;
autoUpdater.autoInstallOnAppQuit = true;
const send = (msg) => {
const w = getMainWindow();
if (w && !w.isDestroyed()) {
w.webContents.send('launcher:progress', msg);
}
};
autoUpdater.on('checking-for-update', () => send('Checking for launcher updates…'));
autoUpdater.on('update-available', (info) => {
send(`Launcher update available: ${info.version}`);
});
autoUpdater.on('update-not-available', () => {});
autoUpdater.on('error', (err) => {
const m = (err && (err.message || String(err))) || '';
if (/404|releases\.atom|HttpError:\s*404/i.test(m)) {
send(
'Launcher update: 404 (no latest.yml or wrong URL). For Gitea use gitea.* + token, or set update_feed_url. ' +
'For private GitHub set GITHUB_TOKEN.'
);
return;
}
if (m && !/net::ERR|ENOTFOUND|ETIMEDOUT/i.test(m)) {
send(`Launcher update: ${m.slice(0, 400)}`);
}
});
autoUpdater.on('download-progress', (p) => {
const pct = Math.round(p.percent || 0);
send(`Launcher update download: ${pct}%`);
});
autoUpdater.on('update-downloaded', async (info) => {
const win = getMainWindow();
const r = await dialog.showMessageBox(win || undefined, {
type: 'info',
title: 'Launcher update',
message: `Version ${info.version} is ready to install.`,
detail: 'Restart the launcher now to finish. You can finish patching WoW after restart.',
buttons: ['Restart now', 'Later'],
defaultId: 0,
cancelId: 1,
noLink: true,
});
if (r.response === 0) {
autoUpdater.quitAndInstall(false, true);
}
});
const checkNow = async () => {
const r = await autoUpdater.checkForUpdates();
return { ok: true, updateInfo: r && r.updateInfo };
};
const tick = () => {
checkNow().catch(() => {});
};
setTimeout(tick, 5000);
setInterval(tick, 6 * 60 * 60 * 1000);
return { checkNow };
}
module.exports = { setupAutoUpdater };