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.
This commit is contained in:
parent
bdac60f022
commit
031c309091
5 changed files with 56 additions and 14 deletions
|
|
@ -121,7 +121,12 @@ def version() -> dict[str, str | None]:
|
|||
# mobile sklei z baseUrl.
|
||||
public_url = os.environ.get("BACKEND_PUBLIC_URL", "").rstrip("/")
|
||||
apk_url = f"{public_url}/static/app-release.apk" if public_url else "/static/app-release.apk"
|
||||
return {"version": "0.3.0", "apk_url": apk_url}
|
||||
# `runtime_version` to jedyny numer, po którym aplikacja pozna, JAKIE APK ma
|
||||
# zainstalowane. `version` z app.json przyjeżdża do niej razem z paczką OTA, więc po
|
||||
# aktualizacji stary APK raportuje nową wersję i sam siebie uznaje za aktualny.
|
||||
# Runtime siedzi w AndroidManifest, OTA go nie rusza. Podbijaj RAZEM z `version`,
|
||||
# ale tylko przy wydaniu APK (zmiana natywna).
|
||||
return {"version": "0.3.0", "runtime_version": "1.3", "apk_url": apk_url}
|
||||
|
||||
|
||||
@app.get("/readyz")
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import { OnboardingModal } from './src/components/OnboardingModal';
|
|||
import { WhatsNewModal } from './src/components/WhatsNewModal';
|
||||
import { ErrorBoundary } from './src/ErrorBoundary';
|
||||
import { isAccepted as isAgeGateAccepted } from './src/lib/agegate';
|
||||
import { APP_VERSION, isNewerVersion } from './src/lib/appVersion';
|
||||
import { APP_VERSION, NATIVE_RUNTIME, isNewerVersion } from './src/lib/appVersion';
|
||||
import { DEFAULT_API_KEY, DEFAULT_BACKEND_URL } from './src/lib/backend';
|
||||
import { getSettings as getLockSettings } from './src/lib/applock';
|
||||
import { AppNavigator } from './src/navigation';
|
||||
|
|
@ -257,7 +257,14 @@ export default function App() {
|
|||
try {
|
||||
const out = await client.getServerVersion();
|
||||
const bundled = APP_VERSION;
|
||||
if (out.version && isNewerVersion(out.version, bundled) && out.apk_url) {
|
||||
// Porównujemy RUNTIME, nie numer wersji: `bundled` bierze się z app.json, które
|
||||
// przyjeżdża razem z paczką OTA, więc stary APK po aktualizacji raportuje nową
|
||||
// wersję i sam siebie uznaje za aktualny. Runtime jest z natywnego manifestu.
|
||||
// Fallback na porównanie wersji, gdy serwer jeszcze nie podaje runtime.
|
||||
const needsApk = out.runtime_version && NATIVE_RUNTIME
|
||||
? isNewerVersion(out.runtime_version, NATIVE_RUNTIME)
|
||||
: isNewerVersion(out.version, bundled);
|
||||
if (out.version && needsApk && out.apk_url) {
|
||||
// In-app installer dostępny → "Install" zamiast "Download"
|
||||
if (apkInstallerAvailable) {
|
||||
const pendingApkUrl = out.apk_url!;
|
||||
|
|
@ -301,7 +308,11 @@ export default function App() {
|
|||
};
|
||||
Alert.alert(
|
||||
'Update available',
|
||||
`Server: ${out.version}\nApp: ${bundled}\n\nInstall update now?`,
|
||||
// NIE pokazujemy tu numeru wersji aplikacji. Po aktualizacji OTA bierze się
|
||||
// on z manifestu paczki, a nie z zainstalowanego APK, więc na starym APK
|
||||
// wyświetlały się dwie identyczne liczby („Server: 0.3.0, App: 0.3.0") i
|
||||
// monit wyglądał na pomyłkę.
|
||||
`Goon ${out.version} is ready. It adds parts that silent updates cannot deliver, so it installs like a normal app.\n\nInstall now?`,
|
||||
[
|
||||
{ text: 'Later', style: 'cancel' },
|
||||
{ text: 'Install', onPress: tryInstall },
|
||||
|
|
@ -311,7 +322,7 @@ export default function App() {
|
|||
// Fallback: Chrome download
|
||||
Alert.alert(
|
||||
'Update available',
|
||||
`Server: ${out.version}\nApp: ${bundled}\n\nTap "Download" to install the latest APK.`,
|
||||
`Goon ${out.version} is ready. It adds parts that silent updates cannot deliver, so you install it yourself.\n\nTap "Download" to get the APK.`,
|
||||
[
|
||||
{ text: 'Later', style: 'cancel' },
|
||||
{
|
||||
|
|
|
|||
|
|
@ -554,7 +554,11 @@ export class GoonClient {
|
|||
return this.request('/bug-reports/mine/seen', { method: 'POST' });
|
||||
}
|
||||
|
||||
async getServerVersion(): Promise<{ version: string; apk_url: string | null }> {
|
||||
async getServerVersion(): Promise<{
|
||||
version: string;
|
||||
runtime_version?: string | null;
|
||||
apk_url: string | null;
|
||||
}> {
|
||||
// /version nie wymaga API key (publiczny endpoint do upgrade discovery).
|
||||
// Używamy this.request bo czysty fetch bez API key też jest OK, ale endpoint jest auth-free.
|
||||
const res = await fetch(`${this.baseUrl}/version`);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* Przy każdym publish OTA: DOPISZ nowy wpis NA GÓRZE (newest-first), z nowym `id`.
|
||||
* Treść user-facing (bez wewnętrznego żargonu), to widzi użytkownik.
|
||||
*/
|
||||
import { APP_VERSION, isNewerVersion } from './lib/appVersion';
|
||||
import { NATIVE_RUNTIME, isNewerVersion } from './lib/appVersion';
|
||||
|
||||
export type ChangelogEntry = {
|
||||
/** Stabilny, rosnący identyfikator (np. data + seq). Newest-first w CHANGELOG. */
|
||||
|
|
@ -16,13 +16,16 @@ export type ChangelogEntry = {
|
|||
/** Punkty zmian, krótkie, user-facing. */
|
||||
items: string[];
|
||||
/**
|
||||
* Pokaż ten wpis tylko użytkownikom PONIŻEJ tej wersji aplikacji.
|
||||
* Pokaż ten wpis tylko użytkownikom, których APK ma runtime STARSZY niż podany.
|
||||
*
|
||||
* Po co: przy zmianach wymagających nowego APK (natywnych) ten sam bundle leci na
|
||||
* kanał starej i nowej wersji. Bez tego użytkownik, który już zainstalował 0.3.0,
|
||||
* dostawał komunikat „pobierz 0.3.0". Wpisy bez tego pola widzą wszyscy.
|
||||
* Po co: przy zmianach natywnych ten sam bundle leci na kanał starej i nowej wersji.
|
||||
* Bez bramki użytkownik, który już zainstalował nowe APK, dostawał komunikat „pobierz
|
||||
* nowe APK". Bramkujemy po runtime, a nie po numerze wersji, bo numer wersji przyjeżdża
|
||||
* razem z paczką OTA i stary APK po aktualizacji podaje już nowy (przez to wpis ukrywał
|
||||
* się przed dokładnie tymi ludźmi, do których był kierowany). Wpisy bez tego pola widzą
|
||||
* wszyscy.
|
||||
*/
|
||||
onlyBelowVersion?: string;
|
||||
onlyBelowRuntime?: string;
|
||||
};
|
||||
|
||||
export const CHANGELOG: ChangelogEntry[] = [
|
||||
|
|
@ -32,7 +35,7 @@ export const CHANGELOG: ChangelogEntry[] = [
|
|||
items: [
|
||||
'Chromecast is here, but it needs version 0.3.0, which you have to download and install yourself. Casting needs a part of the app that cannot be delivered as a silent update. Get it from goon-foss.org; it installs over the version you already have and keeps your settings.',
|
||||
],
|
||||
onlyBelowVersion: '0.3.0',
|
||||
onlyBelowRuntime: '1.3',
|
||||
},
|
||||
{
|
||||
id: '2026-08-24a',
|
||||
|
|
@ -579,7 +582,7 @@ export const NEWEST_CHANGELOG_ID = CHANGELOG.length ? CHANGELOG[0].id : null;
|
|||
export function unseenEntries(lastSeenId: string | null): ChangelogEntry[] {
|
||||
if (!CHANGELOG.length) return [];
|
||||
const visible = CHANGELOG.filter(
|
||||
(e) => !e.onlyBelowVersion || isNewerVersion(e.onlyBelowVersion, APP_VERSION),
|
||||
(e) => !e.onlyBelowRuntime || isNewerVersion(e.onlyBelowRuntime, NATIVE_RUNTIME || '0'),
|
||||
);
|
||||
if (!visible.length) return [];
|
||||
if (!lastSeenId) return [visible[0]];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import Constants from 'expo-constants';
|
||||
import * as Updates from 'expo-updates';
|
||||
|
||||
/**
|
||||
* Aktualna wersja appki, używana do display'u (BugReportFAB, Update dialog)
|
||||
|
|
@ -19,6 +20,24 @@ import Constants from 'expo-constants';
|
|||
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[] {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue