v0.3.0 — mise à jour in-app pour Android
- Server : GET /api/version (version + APK URL + taille), GET /download/latest.apk - Server : releases/latest.apk servi avec Content-Type APK - Web : checkForUpdate() compare version locale vs serveur au démarrage - Web : modale de mise à jour (télécharger + installer, ou plus tard) - Web : version affichée dans l'onglet Plus - Vite : __APP_VERSION__ injecté au build depuis shared/APP_VERSION - Workflow : bump APP_VERSION → build APK → copier vers server/releases/latest.apk
This commit is contained in:
parent
246f989bdc
commit
90adee1eae
9 changed files with 220 additions and 3 deletions
78
web/src/update.ts
Normal file
78
web/src/update.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { Capacitor } from '@capacitor/core'
|
||||
import { getBaseUrl } from './config'
|
||||
|
||||
export interface UpdateInfo {
|
||||
version: string
|
||||
apkUrl: string | null
|
||||
apkSize: number | null
|
||||
notes: string | null
|
||||
}
|
||||
|
||||
/** Compare deux versions sémantiques. Renvoie 1 si a > b, -1 si a < b, 0 si égales. */
|
||||
function compareVersions(a: string, b: string): number {
|
||||
const pa = a.split('.').map(Number)
|
||||
const pb = b.split('.').map(Number)
|
||||
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
||||
const na = pa[i] ?? 0
|
||||
const nb = pb[i] ?? 0
|
||||
if (na > nb) return 1
|
||||
if (na < nb) return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si une mise à jour est disponible sur le serveur.
|
||||
* Ne retourne que sur Android natif (Capacitor) : la PWA iOS se met à jour
|
||||
* en rechargeant simplement la page.
|
||||
*/
|
||||
export async function checkForUpdate(): Promise<UpdateInfo | null> {
|
||||
if (!Capacitor.isNativePlatform() || Capacitor.getPlatform() !== 'android') {
|
||||
return null
|
||||
}
|
||||
|
||||
const localVersion = __APP_VERSION__
|
||||
|
||||
try {
|
||||
const response = await fetch(`${getBaseUrl()}/api/version`, {
|
||||
headers: { Accept: 'application/json' },
|
||||
})
|
||||
if (!response.ok) return null
|
||||
|
||||
const remote = (await response.json()) as UpdateInfo
|
||||
|
||||
// Le serveur est plus vieux ou à jour : rien à faire.
|
||||
if (compareVersions(remote.version, localVersion) <= 0) return null
|
||||
|
||||
// L'utilisateur a déjà ignoré cette version-là.
|
||||
const dismissed = localStorage.getItem('meteo-exam.dismissed-update')
|
||||
if (dismissed === remote.version) return null
|
||||
|
||||
return remote
|
||||
} catch {
|
||||
// Serveur injoignable : on ne bloque pas le démarrage de l'app.
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const currentAppVersion = (): string => __APP_VERSION__
|
||||
|
||||
export function dismissUpdate(version: string): void {
|
||||
localStorage.setItem('meteo-exam.dismissed-update', version)
|
||||
}
|
||||
|
||||
/**
|
||||
* Déclenche le téléchargement de l'APK. Sur Android, le WebView enregistre
|
||||
* le fichier et l'utilisateur ouvre la notification pour installer.
|
||||
*/
|
||||
export function downloadApk(apkUrl: string): void {
|
||||
const url = apkUrl.startsWith('http') ? apkUrl : `${getBaseUrl()}${apkUrl}`
|
||||
const anchor = document.createElement('a')
|
||||
anchor.href = url
|
||||
anchor.download = 'meteo-exam.apk'
|
||||
anchor.rel = 'noopener'
|
||||
anchor.style.display = 'none'
|
||||
document.body.append(anchor)
|
||||
anchor.click()
|
||||
anchor.remove()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue