fix(mobile): monit o nowe APK takze na tym samym kanale OTA
0.4.0 i 0.4.1 maja ten sam runtime 1.4, a monit porownywal tylko runtime, wiec nikt z 0.4.0 nie dostal propozycji aktualizacji i crash expo-video przy zamykaniu z ostatnich wracal do Sentry dzien po poprawce (147164135). - /version oddaje apk_build (versionCode 15) - aplikacja czyta versionCode ZAINSTALOWANEGO APK przez natywny modul Sentry (expo-constants w SDK 52 nie oddaje go wcale, expo-application wymagalby nowego APK) - numer builda trafia tez do kontekstu zgloszen z apki: app_version jedzie z paczka OTA, wiec nie mowil, ktore wydanie natywne ma zglaszajacy Sprawdzone na emulatorze: 0.4.1 nie dostaje monitu i raportuje build=15, po instalacji 0.4.0 monit "Update available" pokazuje sie przy starcie. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
2cdc3d69da
commit
7d5dd450f7
6 changed files with 74 additions and 15 deletions
23
app/main.py
23
app/main.py
|
|
@ -100,7 +100,7 @@ def healthz() -> dict[str, str]:
|
|||
|
||||
|
||||
@app.get("/version")
|
||||
def version() -> dict[str, str | None]:
|
||||
def version() -> dict[str, str | int | None]:
|
||||
"""Mobile sprawdza po starcie żeby wykryć dostępność nowszej wersji APK.
|
||||
|
||||
Zwraca:
|
||||
|
|
@ -123,12 +123,21 @@ 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"
|
||||
# `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.4.1", "runtime_version": "1.4", "apk_url": apk_url}
|
||||
# `runtime_version` i `apk_build` to jedyne numery, po których 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.
|
||||
#
|
||||
# `apk_build` = `versionCode` z android/app/build.gradle. Runtime sam nie wystarcza:
|
||||
# 0.4.0 i 0.4.1 mają ten sam runtime 1.4 (0.4.1 to poprawki natywne bez zmiany kanału
|
||||
# OTA), więc przez dobę nikt z 0.4.0 nie dostał monitu, mimo że 0.4.1 naprawia crash
|
||||
# expo-video przy zamykaniu z ostatnich (Sentry 147164135, zgłoszenie 2026-09-15).
|
||||
# Podbijaj RAZEM z `version` przy każdym wydaniu APK.
|
||||
return {
|
||||
"version": "0.4.1",
|
||||
"runtime_version": "1.4",
|
||||
"apk_build": 15,
|
||||
"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, NATIVE_RUNTIME, isNewerVersion } from './src/lib/appVersion';
|
||||
import { APP_VERSION, NATIVE_RUNTIME, isNewerVersion, nativeBuildNumber } 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,13 +257,20 @@ export default function App() {
|
|||
try {
|
||||
const out = await client.getServerVersion();
|
||||
const bundled = APP_VERSION;
|
||||
// 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);
|
||||
// Kolejność źródeł: numer builda APK → runtime → 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, ale zmienia się tylko przy zmianie kanału OTA:
|
||||
// 0.4.0 i 0.4.1 mają ten sam runtime 1.4, więc wydanie naprawcze nie docierało
|
||||
// do nikogo (crash expo-video z 0.4.0 wracał do Sentry dzień po poprawce).
|
||||
// Numer builda rozróżnia nawet dwa APK na tym samym kanale.
|
||||
const nativeBuild = await nativeBuildNumber();
|
||||
const serverBuild = typeof out.apk_build === 'number' ? out.apk_build : null;
|
||||
const needsApk = serverBuild && nativeBuild
|
||||
? serverBuild > nativeBuild
|
||||
: 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) {
|
||||
|
|
|
|||
|
|
@ -589,6 +589,8 @@ export class GoonClient {
|
|||
async getServerVersion(): Promise<{
|
||||
version: string;
|
||||
runtime_version?: string | null;
|
||||
/** versionCode najnowszego APK; rozróżnia wydania na tym samym runtime. */
|
||||
apk_build?: number | null;
|
||||
apk_url: string | null;
|
||||
}> {
|
||||
// /version nie wymaga API key (publiczny endpoint do upgrade discovery).
|
||||
|
|
|
|||
|
|
@ -35,6 +35,13 @@ export type ChangelogEntry = {
|
|||
};
|
||||
|
||||
export const CHANGELOG: ChangelogEntry[] = [
|
||||
{
|
||||
id: '2026-09-16a',
|
||||
date: 'September 2026',
|
||||
items: [
|
||||
'The app now notices when a newer build is out even if it shares the silent-update channel with yours, so fixes that need a real install stop waiting for you to find them. If you are on 0.4.0, expect a prompt for 0.4.1: it fixes a crash when closing the app from recents.',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '2026-09-15d',
|
||||
date: 'September 2026',
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import { captureScreen } from 'react-native-view-shot';
|
|||
|
||||
import { GoonClient } from '../api';
|
||||
import { theme } from '../theme';
|
||||
import { nativeBuildNumber } from '../lib/appVersion';
|
||||
import { BOTTOM_BAR_GAP, BOTTOM_BAR_HEIGHT, barHidden } from '../lib/bottomBar';
|
||||
import { formatLastPlayback } from '../lib/lastPlayback';
|
||||
import { getSxyprnDiag } from '../lib/sxyprnResolver';
|
||||
|
|
@ -220,6 +221,11 @@ export function BugReportFAB({ client, appVersion, navRef }: Props) {
|
|||
? `${pc?.Model || 'android'} / Android ${pc?.Release || Platform.Version}`
|
||||
: `${Platform.OS} ${Platform.Version}`;
|
||||
extraContext.push(`device=${device.slice(0, 48)}`);
|
||||
// Numer builda ZAINSTALOWANEGO APK. `app_version` idzie z paczki OTA, wiec zgloszenie
|
||||
// z APK 0.4.0 po aktualizacji podawalo 0.4.1 i nie dalo sie odroznic, kto ma ktore
|
||||
// wydanie natywne (crash expo-video siedzial tylko w 0.4.0).
|
||||
const build = await nativeBuildNumber();
|
||||
if (build) extraContext.push(`build=${build}`);
|
||||
// Ostatnie odtwarzanie (serwer/host/pozycja/błąd), kluczowe dla "nie gra"/"audio
|
||||
// się rozjeżdża", bo mówi KTÓRE źródło i gdzie, niezależnie od ekranu zgłoszenia.
|
||||
const play = formatLastPlayback();
|
||||
|
|
|
|||
|
|
@ -39,6 +39,34 @@ export const NATIVE_RUNTIME: string | null = (() => {
|
|||
})();
|
||||
|
||||
|
||||
/**
|
||||
* `versionCode` ZAINSTALOWANEGO APK (0.4.0 = 14, 0.4.1 = 15), albo null.
|
||||
*
|
||||
* Po co, skoro jest runtime: runtime zmienia się tylko przy zmianie kanału OTA, a wydanie
|
||||
* naprawcze zostaje na tym samym kanale. 0.4.0 i 0.4.1 mają oba runtime 1.4, więc nikt
|
||||
* z 0.4.0 nie dostał monitu o 0.4.1, mimo że tamto APK naprawia crash przy zamykaniu
|
||||
* aplikacji z ostatnich (Sentry 147164135). Numer builda rozróżnia je bez podbijania
|
||||
* runtime i bez utrzymywania kolejnego kanału OTA.
|
||||
*
|
||||
* Skąd: natywny moduł Sentry, który i tak jest w aplikacji i czyta PackageInfo (to stąd
|
||||
* bierze się `app_build` w zdarzeniach). expo-constants w SDK 52 oddaje puste
|
||||
* `platform.android`, a expo-application wymagałby nowego APK, czyli dokładnie tego,
|
||||
* czego tu brakuje. Wejście prywatne, więc całość w try/catch: brak numeru = stare
|
||||
* zachowanie (porównanie runtime).
|
||||
*/
|
||||
export async function nativeBuildNumber(): Promise<number | null> {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { NATIVE } = require('@sentry/react-native/dist/js/wrapper');
|
||||
const ctx: any = await NATIVE.fetchNativeDeviceContexts();
|
||||
const raw = ctx?.contexts?.app?.app_build ?? ctx?.app?.app_build;
|
||||
const n = parseInt(String(raw ?? ''), 10);
|
||||
return Number.isFinite(n) && n > 0 ? n : 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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue