6.2 KiB
Solution : [T1] Lire les événements via CalDAV (session navigateur)
Problème résolu
Donner à l'extension sa matière première : le flux iCalendar brut du
calendrier Nextcloud de l'Atelier du Huit, en réutilisant la session du
navigateur (cookies), sans jamais stocker de mot de passe. Frontière T1
tenue : on s'arrête au flux brut vérifié + une preuve de vie (comptage des
VEVENT). Le parsing ical.js, le dédoublonnage par UID, les RRULE/fuseaux et le
filtrage « à venir » sont hors T1 (tâche T2).
Le vrai enjeu n'était pas « fetcher une URL » mais savoir qu'on tient bien un
calendrier : Nextcloud peut renvoyer une page de login en HTTP 200 HTML,
donc response.ok ne suffit pas à garantir la validité du flux.
Approche choisie
GET …/?export + fetch direct depuis la page de l'extension
(credentials:"include"), validation du flux par sniffing du corps
(trimStart().startsWith("BEGIN:VCALENDAR")). Transport isolé dans son propre
module extension/nextcloud.js, pur de toute présentation : il renvoie un
type d'erreur, pas un message.
Pourquoi pas les alternatives :
- CalDAV REPORT +
time-range(filtre serveur, expansion RRULE) : corps XML à construire/maintenir, multi-statut 207, sémantique d'expansion à valider — surdimensionné pour 473 événements. Le?exportbrut suffit pour T1. - Couche réseau centralisée dans le background : utile plus tard (lecture CalDAV des statuts, cache), mais sur-ingénierie tant qu'il n'y a qu'un consommateur. On garde le déplacement peu coûteux grâce au module isolé.
Décisions clés
- Résultat discriminé
{ ok:true, flux } | { ok:false, erreur }avecerreur ∈ {"non-connecte","reseau","serveur"}. Le mapping type → message FR actionnable vit dansliste.js, pas dans le transport. Séparation nette données / présentation. fetchImplinjecté et obligatoire (convention « params obligatoires par défaut ») : le module est testable sans réseau réel (Bun fabrique desResponse), et le wiring navigateur reste un détail de l'appelant.- Ordre des branches : 401/403 traités avant
!reponse.ok(sinon le générique « serveur » les masquerait), puis sniffing du corps en dernier. - Détection « non connecté » par sniffing, pas par
redirect:"manual": le corps après redirection-login (200 HTML) est déjà détecté ; inutile de complexifier. 401/403 captés en amont pour le cas réel. manifest.jsonintact :host_permissionscouvrait déjàhttps://atelier-huit.frama.space/*. Changement purement additif.- Constatation terrain (testée contre le vrai serveur) : sans cookie,
Nextcloud répond 401 (
Sabre\DAV\Exception\NotAuthenticated, 0 redirect), pas un 200 HTML de login. Le sniffing du corps reste une ceinture-et- bretelles utile (cas redirection possible selon config), mais la branche réellement empruntée « pas connecté » est le 401.
Patterns à réutiliser
Transport injectable, pur, à résultat typé (testable sans réseau) :
export async function recupererFluxCalendrier(fetchImpl) {
let reponse;
try { reponse = await fetchImpl(URL_CALENDRIER, { credentials: "include" }); }
catch { return { ok: false, erreur: "reseau" }; } // fetch lève = réseau
if (reponse.status === 401 || reponse.status === 403)
return { ok: false, erreur: "non-connecte" }; // session refusée
if (!reponse.ok) return { ok: false, erreur: "serveur" };
const flux = await reponse.text();
if (!flux.trimStart().startsWith("BEGIN:VCALENDAR")) // login 200 HTML
return { ok: false, erreur: "non-connecte" };
return { ok: true, flux };
}
Comptage ancré ligne (ne compte pas une occurrence dans une DESCRIPTION,
tolère le CRLF de la RFC 5545) :
export function compterEvenements(flux) {
return (flux.match(/^BEGIN:VEVENT\s*$/gm) ?? []).length;
}
Wrapper fetch côté page (voir piège ci-dessous) :
const res = await recupererFluxCalendrier((u, o) => fetch(u, o));
Rendu d'état reconstruit à chaque tentative : etat.replaceChildren() avant
de re-rendre → pas d'empilement de messages/boutons/listeners quand on clique
Réessayer plusieurs fois. Région #etat en role="status" aria-live="polite" pour l'accessibilité.
Tests Bun important un module ES du navigateur : import { … } from "./extension/nextcloud.js", fetchImpl remplacé par
async () => new Response(corps, { status }) ou un throw. Aucun jsdom, aucun
réseau.
Pièges à éviter
response.okne prouve pas un calendrier : la page de login peut arriver en 200 HTML. Toujours valider le contenu (BEGIN:VCALENDAR), pas que le statut. C'est le piège central de la tâche.fetchnu détaché dewindowlève « Illegal invocation » dans le navigateur. Passer un wrapper(u, o) => fetch(u, o)(oufetch.bind(window)), jamaisfetchnu en argument.- Regex non ancrée :
/BEGIN:VEVENT/gcompterait une occurrence apparaissant dans uneDESCRIPTION. Ancrer^…$+ flagm. - Ordre des branches d'erreur : tester 401/403 avant le
!okgénérique, sinon « non-connecte » serait noyé dans « serveur ». - BOM / espaces en tête du flux :
trimStart()avant la comparaisonstartsWith, sinon un VCALENDAR valide précédé d'un BOM serait rejeté. - 0 VEVENT n'est pas une erreur : un calendrier valide mais vide est un succès (« 0 événement lu »), à ne pas confondre avec un échec.
- CSS mort : la classe
.liste-vide(squelette) n'est plus utilisée depuis queliste.jsorchestre#etat. Signalée P3 en review, laissée volontairement car T2 (rendu détaillé de la liste) la réemploiera. À nettoyer si T2 ne la reprend pas. - Warning
MISSING_DATA_COLLECTION_PERMISSIONS(web-ext) : préexistant surmanifest.json(commit squelette), non touché par T1, hors périmètre — pas une régression. À traiter dans une tâche dédiée au manifest.
Tags
tags: [firefox-extension, manifest-v3, webextension, vanilla-js, caldav, ical, nextcloud, fetch, session-cookies, bun-test, dependency-injection, error-handling, low-tech]