goon/mobile/src/lib/appVersion.ts
goon-foss 031c309091 fix(mobile): stary APK po OTA podawal sie za nowa wersje i nie dostawal monitu
Zgloszone przez uzytkownika: po wydaniu 0.3.0 nie dostal propozycji aktualizacji.

Przyczyna jest moja. `APP_VERSION` czyta `Constants.expoConfig.version`, czyli app.json,
a app.json przyjezdza do aplikacji RAZEM Z PACZKA OTA. Podbilem wersje na 0.3.0 i
opublikowalem te paczke rowniez na stary kanal 1.2, wiec kazdy uzytkownik APK 0.2.2
zaczal raportowac 0.3.0 i sam siebie uznal za aktualnego. Skutek podwojny: nie dostal ani
monitu o nowym APK, ani wpisu w changelogu, ktory byl kierowany dokladnie do niego.

Poprzednia poprawka (dad4bbf, porownanie "nowsza" zamiast "rozna") tego NIE naprawiala:
przy dwoch identycznych numerach oba warunki daja falsz.

Jedyny numer odporny na OTA to runtime z AndroidManifest, bo expo-updates uzywa go do
sprawdzania zgodnosci paczek i nie moze brac go z paczki. Stad `NATIVE_RUNTIME`
w `src/lib/appVersion.ts`, a serwer oddaje teraz `runtime_version` obok `version`.
Porownanie leci po runtime, z zapasowym porownaniem wersji, gdy serwer go nie poda.
Bramka wpisow w changelogu przeszla z `onlyBelowVersion` na `onlyBelowRuntime`.

`expo-application` bylby czystszym zrodlem natywnej wersji, ale to modul natywny, wiec
nie wszedlby przez OTA do ludzi na starym APK, czyli dokladnie do tych, ktorych to
dotyczy. Runtime dziala na tym, co juz maja zainstalowane.

Przy okazji tresc dialogu: pokazywala "Server: 0.3.0, App: 0.3.0", czyli dwie identyczne
liczby, bo drugi numer byl z paczki, a nie z APK. Teraz nie pokazujemy numeru
zainstalowanej wersji, bo po OTA nie mamy wiarygodnego.

Zweryfikowane na emulatorze w obie strony, na podpisanych APK:
* APK 0.2.2 (runtime 1.2) po pobraniu paczki: monit jest, tresc czytelna.
* APK 0.3.0 (runtime 1.3): monitu nie ma.
Instalacja 0.3.0 na wierzch 0.2.2 przeszla ponownie.
2026-08-24 14:47:04 +02:00

63 lines
2.7 KiB
TypeScript

import Constants from 'expo-constants';
import * as Updates from 'expo-updates';
/**
* Aktualna wersja appki, używana do display'u (BugReportFAB, Update dialog)
* i porównań z `/version` z serwera.
*
* Źródła w kolejności preferencji:
* 1. `Constants.expoConfig?.version`, działa gdy OTA manifest ma `extra.expoClient`
* (publish_update.py embeduje tam app.json). W release APK przed pierwszym OTA
* bierze się to z app.json bundled w build.
* 2. Hardcoded fallback, chroni przed OTA-z-pustego-extra (bug 2026-05-12, gdzie
* stara wersja publish_update.py wysyłała `extra: {}` → expoConfig=null po
* reload-zie → version='0.0.0' → fałszywy "Update available" dialog).
*
* **Bump przy każdym APK release** żeby APK fallback pasował do realnej wersji.
* OTA-only releases (silent JS update) NIE bumpują tego, wtedy version w app.json
* też nie idzie do góry, więc consistency jest zachowana.
*/
export const APP_VERSION: string =
(Constants.expoConfig?.version as string | undefined) || '0.3.0';
/**
* Runtime zapisany w APK (`EXPO_RUNTIME_VERSION` w AndroidManifest). JEDYNY numer, po
* którym aplikacja pozna, jaką paczkę natywną ma naprawdę zainstalowaną.
*
* Po co osobno, skoro jest `APP_VERSION`: ta bierze się z `app.json`, a `app.json`
* przyjeżdża do aplikacji RAZEM Z PACZKĄ OTA. Po podbiciu wersji na 0.3.0 i wysłaniu tej
* paczki na stary kanał każdy użytkownik APK 0.2.2 zaczął raportować 0.3.0 i sam siebie
* uznał za aktualnego: nie dostał ani monitu o nowym APK, ani wpisu w changelogu, który
* właśnie do niego był kierowany. Runtime siedzi w natywnym manifeście i OTA go nie rusza.
*/
export const NATIVE_RUNTIME: string | null = (() => {
try {
return Updates.runtimeVersion ?? null;
} catch {
return null;
}
})();
/** „0.3.0" → [0,3,0]. Człony nieliczbowe (np. „0.3.0-rc1") lecą na 0. */
function versionParts(v: string): number[] {
return String(v || '').split('.').map((x) => parseInt(x, 10) || 0);
}
/**
* Czy `candidate` jest NOWSZA niż `current`.
*
* Po co, skoro wcześniej wystarczyło `!==`: serwer trzyma numer najnowszego APK na
* sztywno, a przy wydaniu 0.3.0 nie został podbity od razu. Porównanie „różne" kazało
* wtedy KAŻDEMU użytkownikowi 0.3.0 zainstalować 0.2.2, czyli cofnąć się o wersję.
* Złapane na emulatorze przed publikacją.
*/
export function isNewerVersion(candidate: string, current: string): boolean {
const a = versionParts(candidate);
const b = versionParts(current);
for (let i = 0; i < Math.max(a.length, b.length); i++) {
const d = (a[i] ?? 0) - (b[i] ?? 0);
if (d !== 0) return d > 0;
}
return false;
}