feat(frontend): polish web (logout, 401→pairing, reconnexion globale) (#13)

Flow logout : POST /api/logout puis retour à l'écran de pairing et
déconnexion du live. 401 renvoie au pairing sans boucle. Bannière de
reconnexion couvrant le cas live-only et le serveur indisponible.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 08:23:11 +02:00
parent d538808a0d
commit 6117177957
10 changed files with 288 additions and 14 deletions

View File

@ -79,14 +79,33 @@ function resolveEndpoints(config: HttpWsGatewaysConfig): {
*/
export function createHttpWsGateways(config: HttpWsGatewaysConfig = {}): Gateways {
const { baseUrl, wsUrl } = resolveEndpoints(config);
// Route 401s back to the pairing screen via the shared web session (F2).
// Route 401s back to the pairing screen via the shared web session (F2/F6).
const session = getWebSession();
// `ws` is referenced by the invoker's 401 handler (declared below); the closure
// only runs after both are constructed, so the forward reference is safe.
let ws: WsLiveClient;
const http = new HttpInvoker({
baseUrl,
token: config.token,
onUnauthorized: () => session.notifyUnauthorized(),
onUnauthorized: () => {
// F6: a 401 means the session cookie is gone/revoked. Route back to pairing
// AND tear down the live socket so it stops its (now hopeless) reconnect
// loop. Re-pairing brings the same client back up cleanly.
session.notifyUnauthorized();
ws?.disconnect();
},
});
ws = new WsLiveClient({
wsUrl,
token: config.token,
// F6: when a WS reconnect fails we cannot read its HTTP status, so probe with
// a cheap `health` call. A revoked session answers `401` → the invoker's
// `onUnauthorized` above bounces to pairing; a network error is swallowed and
// the WS backoff keeps retrying.
onReconnectFailed: () => {
void http.invoke("health", { request: null }).catch(() => {});
},
});
const ws = new WsLiveClient({ wsUrl, token: config.token });
// Share the live client with the web feature so live surfaces can re-sync on
// reconnect (F5). Inert on desktop (never called there).
setWebLiveClient(ws);
@ -120,5 +139,5 @@ export function createHttpWsGateways(config: HttpWsGatewaysConfig = {}): Gateway
export { HttpInvoker } from "./httpInvoker";
export { WsLiveClient } from "./wsLiveClient";
export { WebSession, getWebSession, setWebSessionForTests } from "./webSession";
export { getWebLiveClient, setWebLiveClient } from "./webLive";
export { getWebLiveClient, setWebLiveClient, disconnectWebLive } from "./webLive";
export type { ConnectionState } from "./wsLiveClient";