diff --git a/server/server.js b/server/server.js index 0b63a66..4547830 100644 --- a/server/server.js +++ b/server/server.js @@ -249,6 +249,22 @@ function publicPlayer(player) { }; } +function getPublicOrigin(req) { + const forwardedProto = String(req.get("x-forwarded-proto") || "").split(",")[0].trim(); + const proto = forwardedProto || req.protocol || "http"; + const forwardedHost = String(req.get("x-forwarded-host") || "").split(",")[0].trim(); + const host = forwardedHost || req.get("host") || ""; + return host ? `${proto}://${host}` : ""; +} + +function absoluteUrl(req, value) { + const url = String(value || "").trim(); + if (!url || /^https?:\/\//i.test(url)) return url; + if (!url.startsWith("/")) return url; + const origin = getPublicOrigin(req); + return origin ? `${origin}${url}` : url; +} + function getPlayerSession(req) { const token = parseCookies(req)[playerSessionCookie]; if (!token) return null; @@ -410,6 +426,10 @@ function getProtectedDownloadUrl(item) { return resolveDownloadUrl(item) ? `/api/software/${encodeURIComponent(item.id)}/download` : ""; } +function getUpdateDownloadUrl(item) { + return resolveDownloadUrl(item) ? `/api/software/${encodeURIComponent(item.id)}/update-download` : ""; +} + function getReleaseNotes(item, manifest) { return manifest.release_notes || manifest.releaseNotes || manifest.changelog || item.changelog || ""; } @@ -453,11 +473,12 @@ function applyVersionManifest(item, sourceManifest, mode = "auto") { return item; } -function buildVersionManifest(item) { +function buildVersionManifest(item, req) { const sourceManifest = item.versionManifest && typeof item.versionManifest === "object" ? item.versionManifest : {}; const latestVersion = getLatestVersion(item); - const downloadUrl = getProtectedDownloadUrl(item); + const downloadUrl = absoluteUrl(req, getUpdateDownloadUrl(item)); const versionManifestUrl = `/api/software/${encodeURIComponent(item.id)}/version.json`; + const updateUrl = absoluteUrl(req, versionManifestUrl); const releaseNotes = getReleaseNotes(item, sourceManifest); const minimumVersion = sourceManifest.min_supported_version || sourceManifest.minSupportedVersion || sourceManifest.minimumVersion || sourceManifest.minVersion || ""; @@ -473,8 +494,8 @@ function buildVersionManifest(item) { size: sourceManifest.size || item.size || "", sizeBytes: sourceManifest.sizeBytes ?? item.sizeBytes ?? 0, sha256: sourceManifest.sha256 || item.sha256 || "", - updateUrl: versionManifestUrl, - update_url: versionManifestUrl, + updateUrl, + update_url: updateUrl, downloadUrl, download_url: downloadUrl, fullInstallerUrl: downloadUrl, @@ -485,13 +506,13 @@ function buildVersionManifest(item) { min_supported_version: minimumVersion, deltaUpdates: sourceManifest.deltaUpdates || sourceManifest.delta_updates || {}, delta_updates: sourceManifest.delta_updates || sourceManifest.deltaUpdates || {}, - checkUpdateUrl: `/api/software/${encodeURIComponent(item.id)}/check-update`, - versionManifestUrl, + checkUpdateUrl: absoluteUrl(req, `/api/software/${encodeURIComponent(item.id)}/check-update`), + versionManifestUrl: updateUrl, }; } -function buildUpdateCheck(item, currentVersion) { - const manifest = buildVersionManifest(item); +function buildUpdateCheck(item, currentVersion, req) { + const manifest = buildVersionManifest(item, req); const latestVersion = manifest.version; const comparison = compareVersions(currentVersion, latestVersion); const minimumVersion = manifest.min_supported_version || manifest.minSupportedVersion || manifest.minimumVersion || manifest.minVersion || ""; @@ -697,7 +718,7 @@ app.get("/api/software/:id/latest", async (req, res, next) => { res.status(404).json({ error: "Software not found" }); return; } - const manifest = buildVersionManifest(item); + const manifest = buildVersionManifest(item, req); res.json({ id: item.id, name: item.name, @@ -731,7 +752,7 @@ app.get("/api/software/:id/version.json", async (req, res, next) => { res.status(404).json({ error: "Software not found" }); return; } - res.json(buildVersionManifest(item)); + res.json(buildVersionManifest(item, req)); } catch (error) { next(error); } @@ -751,7 +772,7 @@ app.get("/api/software/:id/check-update", async (req, res, next) => { res.status(404).json({ error: "Software not found" }); return; } - res.json(buildUpdateCheck(item, currentVersion)); + res.json(buildUpdateCheck(item, currentVersion, req)); } catch (error) { next(error); } @@ -771,13 +792,13 @@ app.post("/api/software/:id/check-update", async (req, res, next) => { res.status(404).json({ error: "Software not found" }); return; } - res.json(buildUpdateCheck(item, currentVersion)); + res.json(buildUpdateCheck(item, currentVersion, req)); } catch (error) { next(error); } }); -app.get("/api/software/:id/download", requirePlayer, async (req, res, next) => { +async function sendSoftwareDownload(req, res, next) { try { const store = await readStore(); const item = store.items.find((entry) => entry.id === req.params.id && entry.active !== false); @@ -810,7 +831,10 @@ app.get("/api/software/:id/download", requirePlayer, async (req, res, next) => { } catch (error) { next(error); } -}); +} + +app.get("/api/software/:id/update-download", sendSoftwareDownload); +app.get("/api/software/:id/download", requirePlayer, sendSoftwareDownload); app.get("/api/admin/software", requirePlayer, requireAdmin, async (req, res, next) => { try {