Files
LoreMind/web/src/app/services/version-checker.service.ts
IETM_FIXE\ietm6 efaf5a3794
Some checks failed
E2E Tests / e2e (push) Has been cancelled
Build & Push Images / build (brain) (push) Successful in 1m0s
Build & Push Images / build (core) (push) Successful in 1m47s
Build & Push Images / build (web) (push) Successful in 1m38s
Mise en place d'un composant permettant d'améliorer l'experience de mise à jour (via un rafraichissement de l'appli).
Modification de la partie web pour prendre la modification en compte
2026-04-29 14:39:30 +02:00

95 lines
3.0 KiB
TypeScript

import { Injectable, signal, computed } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
/**
* Detecte qu'une nouvelle version de LoreMind a ete deployee pendant qu'un
* onglet utilisateur est deja ouvert.
*
* Strategie : polling toutes les {@link POLL_INTERVAL_MS} sur /api/version.
* La version observee au boot est figee comme reference. Si la version polled
* differe, {@link hasUpdate} passe a true et l'UI peut afficher un bandeau
* proposant un reload.
*
* Pourquoi pas un Service Worker ? Trop lourd pour le besoin (offline pas
* pertinent, lifecycle complexe). Polling simple = ~15 lignes, fait le job.
*/
@Injectable({ providedIn: 'root' })
export class VersionCheckerService {
private static readonly POLL_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
private static readonly INITIAL_DELAY_MS = 30 * 1000; // 30s avant premier poll
private readonly _bootVersion = signal<string | null>(null);
private readonly _remoteVersion = signal<string | null>(null);
private readonly _hasUpdate = computed(
() => {
const boot = this._bootVersion();
const remote = this._remoteVersion();
return boot !== null && remote !== null && boot !== remote;
}
);
/** True quand la version remote differe de celle observee au boot. */
readonly hasUpdate = this._hasUpdate;
/** Version observee a l'ouverture du tab (figee). */
readonly bootVersion = this._bootVersion.asReadonly();
/** Derniere version observee sur le backend. */
readonly remoteVersion = this._remoteVersion.asReadonly();
private timer: ReturnType<typeof setTimeout> | null = null;
constructor(private http: HttpClient) {}
/**
* Demarre le polling. A appeler une fois au boot de l'app.
* Ignore si deja demarre (idempotent).
*/
start(): void {
if (this.timer !== null) return;
void this.fetchInitial();
this.timer = setInterval(
() => void this.poll(),
VersionCheckerService.POLL_INTERVAL_MS,
);
}
stop(): void {
if (this.timer !== null) {
clearInterval(this.timer);
this.timer = null;
}
}
/** Recharge l'app (force re-fetch index.html + assets). */
reload(): void {
window.location.reload();
}
private async fetchInitial(): Promise<void> {
// Petit delai pour laisser le bootstrap se finir avant le premier appel.
await new Promise(r => setTimeout(r, VersionCheckerService.INITIAL_DELAY_MS));
const v = await this.fetchVersion();
if (v && this._bootVersion() === null) {
this._bootVersion.set(v);
this._remoteVersion.set(v);
}
}
private async poll(): Promise<void> {
const v = await this.fetchVersion();
if (v) this._remoteVersion.set(v);
}
private async fetchVersion(): Promise<string | null> {
try {
const resp = await firstValueFrom(
this.http.get<{ version: string }>('/api/version'),
);
return resp?.version ?? null;
} catch {
// Backend down / restart en cours — on ignore, on retentera au prochain tick
return null;
}
}
}