From 27c5fbb8562d315bd3fee58d3040a97c3eccf7bd Mon Sep 17 00:00:00 2001 From: M_Viper Date: Sun, 11 Oct 2026 07:05:48 +0000 Subject: [PATCH] Upload via GUI (202 Dateien) --- src/store.js | 2 ++ src/styles.css | 36 ++++++++++++------------ src/updater.js | 74 ++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 81 insertions(+), 31 deletions(-) diff --git a/src/store.js b/src/store.js index 79bcf64..2ddffdd 100644 --- a/src/store.js +++ b/src/store.js @@ -75,6 +75,8 @@ const DEFAULT_SETTINGS = { sortBy: 'lastPlayed', // lastPlayed | name iso3dIcons: true, // Block-Symbole räumlich darstellen bedrockEnabled: true, // Bedrock-Bereich anzeigen (nur wenn auch installiert) + betaTeilnahme: false, // Vorabfassungen (Beta, RC) als Update bekommen + letzteVersionVorab: false, // lief zuletzt eine Vorabfassung? (siehe main.js: Beta-Teilnahme) desktopNotifications: true, // Windows-Meldung bei fertigem Hintergrund-Download/-Backup friendsEnabled: true, friendsShareServer: true, // Freunde sehen, auf welchem Server man spielt (für "Beitreten") diff --git a/src/styles.css b/src/styles.css index 52fe4be..40d3db8 100644 --- a/src/styles.css +++ b/src/styles.css @@ -82,6 +82,8 @@ body{display:grid;grid-template-rows:auto auto 1fr auto;grid-template-columns:mi .titelleiste{display:flex;align-items:center;gap:8px;height:32px;padding:0 150px 0 10px;background:var(--titel-bg);color:var(--titel-fg);-webkit-app-region:drag;user-select:none;font-size:12px} .titel-logo{width:16px;height:16px;flex:none} .titel-text{white-space:nowrap} +/* Kennzeichnung einer Vorabfassung ("Beta 1", "RC 2") neben dem Namen */ +.titel-beta{padding:1px 8px;border-radius:99px;background:#e9a800;color:#1b1400;font-size:10.5px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;line-height:16px;white-space:nowrap} body.fenster-inaktiv .titelleiste>*{opacity:.55} body.is-popout>.titelleiste{display:none} @@ -941,20 +943,20 @@ select.s-wert,input.s-wert{padding:6px 8px;border:1px solid var(--border);border .se-u2d[data-werkzeug="pipette"] canvas{cursor:copy} .se-fuss{align-items:center} .se-status{flex:1;min-width:0;margin-left:6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} - -/* Manipulationsschutz: gesperrte Instanz (Verbotszeichen, siehe integritaet.js) */ -.card.gesperrt .card-icon,.card.gesperrt .card-icon-img{filter:grayscale(1) brightness(.55)} -.card-verbot{position:absolute;left:50%;top:6px;width:60px;height:60px;margin-left:-30px;opacity:.95;pointer-events:none;z-index:2;filter:drop-shadow(0 1px 2px rgba(0,0,0,.45))} -.card.gesperrt .card-name{color:#e05555} -.card.gesperrt.sel .card-name{color:#fff;background:#b02020} -.side-ico{position:relative} -.side-ico.gesperrt > :not(.side-verbot){filter:grayscale(1) brightness(.55)} -.side-ico img.side-verbot{position:absolute;left:-4px;top:-4px;width:60px;height:60px;object-fit:contain;border-radius:0;pointer-events:none} -.side-gesperrt{margin:8px 6px 2px;padding:9px 10px;border:1px solid rgba(200,0,0,.55);background:rgba(200,0,0,.10);border-radius:6px;font-size:12px} -.side-gesperrt-titel{display:flex;align-items:center;gap:6px;font-weight:700;color:#e04848;font-size:13px;margin-bottom:4px} -.side-gesperrt-titel img{width:18px;height:18px} -.side-gesperrt-text{line-height:1.35;margin:4px 0} -.side-gesperrt-text.dim{opacity:.7;font-size:11px} -.side-gesperrt-gruende{margin:4px 0 8px;padding-left:16px;max-height:110px;overflow:auto;word-break:break-word;line-height:1.35} -.side-gesperrt-gruende .dim{opacity:.7;list-style:none;margin-left:-16px} -#p-wiederherstellen{width:100%;margin:4px 0} + +/* Manipulationsschutz: gesperrte Instanz (Verbotszeichen, siehe integritaet.js) */ +.card.gesperrt .card-icon,.card.gesperrt .card-icon-img{filter:grayscale(1) brightness(.55)} +.card-verbot{position:absolute;left:50%;top:6px;width:60px;height:60px;margin-left:-30px;opacity:.95;pointer-events:none;z-index:2;filter:drop-shadow(0 1px 2px rgba(0,0,0,.45))} +.card.gesperrt .card-name{color:#e05555} +.card.gesperrt.sel .card-name{color:#fff;background:#b02020} +.side-ico{position:relative} +.side-ico.gesperrt > :not(.side-verbot){filter:grayscale(1) brightness(.55)} +.side-ico img.side-verbot{position:absolute;left:-4px;top:-4px;width:60px;height:60px;object-fit:contain;border-radius:0;pointer-events:none} +.side-gesperrt{margin:8px 6px 2px;padding:9px 10px;border:1px solid rgba(200,0,0,.55);background:rgba(200,0,0,.10);border-radius:6px;font-size:12px} +.side-gesperrt-titel{display:flex;align-items:center;gap:6px;font-weight:700;color:#e04848;font-size:13px;margin-bottom:4px} +.side-gesperrt-titel img{width:18px;height:18px} +.side-gesperrt-text{line-height:1.35;margin:4px 0} +.side-gesperrt-text.dim{opacity:.7;font-size:11px} +.side-gesperrt-gruende{margin:4px 0 8px;padding-left:16px;max-height:110px;overflow:auto;word-break:break-word;line-height:1.35} +.side-gesperrt-gruende .dim{opacity:.7;list-style:none;margin-left:-16px} +#p-wiederherstellen{width:100%;margin:4px 0} diff --git a/src/updater.js b/src/updater.js index 10be524..73ac693 100644 --- a/src/updater.js +++ b/src/updater.js @@ -15,6 +15,32 @@ const crypto = require('crypto'); const { spawn } = require('child_process'); const API = 'https://git.viper.ipv64.net/api/v1/repos/M_Viper/AeroMc-Launcher/releases'; +/* + * Beta-Kanal: Vorabfassungen (Beta, RC) liegen im selben Repository wie die + * fertigen Versionen, in Gitea als "Pre-Release" markiert – und mit einem Tag, + * dem "0.0.0-" vorangestellt ist: + * + * App-Version 1.2.4-beta.1 -> Tag 0.0.0-1.2.4-beta.1 + * + * Der Vorsatz ist nötig, weil Launcher bis 1.2.3 das Pre-Release-Feld nicht + * kennen und nur die Zahlen am Anfang des Tags lesen: "1.2.4-beta.1" wäre für + * sie ein normales Update auf 1.2.4, das ALLE Nutzer bekämen. "0.0.0-…" lesen + * sie als uralte Version 0.0.0 und ignorieren sie; fertige Versionen finden sie + * weiterhin. (Ein Tag ohne Zahl vorn, etwa "beta-…", taugt nicht: Er bringt bei + * alten Launchern die Sortierung durcheinander, sie fänden dann gar kein Update + * mehr – mit dem echten 1.2.3 geprüft.) + */ +const BETA_VORSATZ = /^0\.0\.0-(?=\d)/; + +// Version, die ein Release wirklich enthält: Tag ohne "v" und ohne Beta-Vorsatz +function tagVersion(tag) { + return normVersion(tag).replace(BETA_VORSATZ, ''); +} + +// Vorabfassung? In Gitea so markiert, mit Beta-Vorsatz oder mit Kennung im Tag +function istVorab(release) { + return !!release.prerelease || BETA_VORSATZ.test(normVersion(release.tag_name)) || !!parsePreRelease(tagVersion(release.tag_name)); +} const UA = 'AeroMC-Launcher'; // Downloads nur von hier – eine Release-Datei mit fremder Adresse wird abgelehnt const ERLAUBTER_HOST = 'git.viper.ipv64.net'; @@ -188,16 +214,23 @@ function parsePreRelease(v) { } /* - * Vergleicht die Vorabkennungen zweier gleicher Versionsnummern. + * Vergleicht die Vorabkennungen zweier gleicher Versionsnummern – wie SemVer: + * Die fertige Fassung ist neuer als jede Vorabfassung derselben Nummer + * ("1.3.0" > "1.3.0-beta.2"), sonst könnten Beta-Nutzer nie auf die fertige + * Version wechseln. * - * Bewusst nicht streng nach SemVer: trägt ein Release-Tag die Vorabkennung - * nicht, die die App selbst meldet (Tag "1.0.1" gegen App "1.0.1-beta"), - * würde "1.0.1" als neuer gelten – der Launcher böte dasselbe Update nach - * jeder Installation erneut an, eine Endlosschleife. Deshalb zählt eine - * Vorabkennung nur, wenn sie auf beiden Seiten vorhanden ist (beta.1 -> beta.2). + * Früher zählte eine Vorabkennung nur, wenn beide Seiten eine hatten – als + * Schutz vor einer Endlosschleife, falls ein Release unter dem Tag "1.0.1" + * steht, die App darin sich aber weiter als "1.0.1-beta" meldet. Das kann nicht + * mehr passieren: Die Signatur bindet die Version (siehe pruefeSignatur), und + * sign-release.js nimmt sie aus derselben package.json wie die App. Ein Release, + * bei dem Tag und App-Version nicht übereinstimmen, lässt sich gar nicht + * installieren. */ function comparePreRelease(a, b) { - if (!a || !b) return 0; + if (!a && !b) return 0; + if (!a) return 1; // a ist die fertige Fassung, b eine Vorabfassung + if (!b) return -1; for (let i = 0; i < Math.max(a.length, b.length); i++) { const x = a[i]; const y = b[i]; if (x === undefined) return -1; @@ -250,20 +283,31 @@ function pickAsset(release, installKind) { * Prüft auf Updates. * -> { available, currentVersion, latestVersion, notes, asset, url, noReleases } */ -async function checkForUpdate(currentVersion) { +async function checkForUpdate(currentVersion, opts) { const installKind = detectInstallKind(); - const res = await fetch(API + '?limit=10', { headers: { 'User-Agent': UA, Accept: 'application/json' } }); + const kopf = { headers: { 'User-Agent': UA, Accept: 'application/json' } }; + const res = await fetch(API + '?limit=30', kopf); if (!res.ok) throw new Error('Update-Server antwortet mit HTTP ' + res.status); const list = await res.json(); - const releases = (Array.isArray(list) ? list : []).filter((r) => !r.draft); - if (!releases.length) { + const alle = (Array.isArray(list) ? list : []).filter((r) => !r.draft); + + if (!alle.length) { return { available: false, noReleases: true, currentVersion }; } + // Vorabfassungen (Beta, RC …) bekommt nur, wer selbst eine Vorabfassung nutzt. + // Wer die fertige Fassung installiert hat, bleibt auf fertigen Versionen. + // Ob jemand teilnimmt, entscheidet der Schalter "An der Beta teilnehmen" + // (opts.beta). Ohne Angabe zählt die installierte Version. + const nutztVorab = opts && typeof opts.beta === 'boolean' ? opts.beta : !!parsePreRelease(currentVersion); + const releases = nutztVorab ? alle : alle.filter((r) => !istVorab(r)); + if (!releases.length) { + return { available: false, currentVersion, installKind }; + } // neueste anhand der Versionsnummer (nicht nach Datum, damit Nachträge nicht stören) - releases.sort((a, b) => compareVersions(b.tag_name, a.tag_name)); + releases.sort((a, b) => compareVersions(tagVersion(b.tag_name), tagVersion(a.tag_name))); const latest = releases[0]; - const available = compareVersions(latest.tag_name, currentVersion) > 0; - const latestVersion = normVersion(latest.tag_name); + const latestVersion = tagVersion(latest.tag_name); + const available = compareVersions(latestVersion, currentVersion) > 0; let asset = pickAsset(latest, installKind); // ohne Signatur oder von fremder Adresse wird nichts automatisch installiert let unsigniert = false; @@ -485,6 +529,8 @@ module.exports = { pruefeSignatur, signaturText, holeSignatur, + tagVersion, + istVorabVersion: (v) => !!parsePreRelease(v), SIGNATUR_API, cleanupUpdateCache, compareVersions,