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:
Mathis 2026-10-04 17:18:48 +02:00
parent 246f989bdc
commit 90adee1eae
9 changed files with 220 additions and 3 deletions

View file

@ -1,7 +1,8 @@
import express from 'express'
import { existsSync } from 'node:fs'
import { existsSync, statSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { APP_VERSION } from '@meteo-exam/shared'
import { classesRouter } from './routes/classes.js'
import { examsRouter } from './routes/exams.js'
import { streamRouter } from './routes/stream.js'
@ -10,6 +11,10 @@ import { configRouter } from './routes/config.js'
const here = dirname(fileURLToPath(import.meta.url))
const PORT = Number(process.env['PORT'] ?? 8787)
/** Dossier contenant l'APK à distribuer (latest.apk). */
const RELEASES_DIR = resolve(here, '../releases')
const LATEST_APK = resolve(RELEASES_DIR, 'latest.apk')
/** Origines autorisées. L'app Android (WebView) est en cross-origin. */
const ALLOW_ORIGINS = (process.env['METEO_ORIGINS'] ?? '*')
.split(',')
@ -40,6 +45,33 @@ app.use((req, res, next) => {
app.get('/api/health', (_req, res) => {
res.json({ ok: true })
})
// --- Mise à jour de l'APK ----------------------------------------------------
// GET /api/version : la version courante du serveur + URL de téléchargement.
// L'app Android compare avec sa propre version et propose la mise à jour.
app.get('/api/version', (_req, res) => {
const apkAvailable = existsSync(LATEST_APK)
const apkUrl = apkAvailable ? '/download/latest.apk' : null
const apkSize = apkAvailable ? statSync(LATEST_APK).size : null
res.json({
version: APP_VERSION,
apkUrl,
apkSize,
notes: process.env['METEO_UPDATE_NOTES'] ?? null,
})
})
// GET /download/latest.apk : sert l'APK pour l'installation directe.
app.get('/download/latest.apk', (_req, res) => {
if (!existsSync(LATEST_APK)) {
res.status(404).json({ error: 'Aucune APK disponible pour le moment.' })
return
}
res.setHeader('Content-Type', 'application/vnd.android.package-archive')
res.setHeader('Content-Disposition', 'attachment; filename="meteo-exam.apk"')
res.sendFile(LATEST_APK)
})
app.use('/api/classes', classesRouter)
app.use('/api/exams', examsRouter)
app.use('/api/stream', streamRouter)