EN · DE · RU · FR · ES

#2757 : rest.js

projectforge-webapp/src/utilities/rest.js Utilitaire JavaScript / React — client central de l'API REST pour le frontend React de ProjectForge. Source : projectforge-webapp/src/utilities/rest.js 88 lignes · 68 lignes de code · 7 commentaires · 13 lignes vides
Chaque composant React qui communique avec le backend ProjectForge passe par ce module. Il fournit une fine couche d'abstraction autour de l'API fetch avec construction d'URL tenant compte de l'environnement, authentification par cookie de session (credentials: 'include'), débouncing adaptatif pour les connexions lentes, et gestion des requêtes/réponses JSON. C'est le point de contact unique entre la SPA React et la couche REST Spring Boot — il n'existe aucun autre client HTTP dans le frontend.

Architecture

Pipeline de construction d'URL

Le module construit les URL REST via un pipeline en trois étapes, chacune gérant une préoccupation :

Étape 1 — baseURL : Résout l'origine du serveur. En développement (import.meta.env.DEV), pointe vers http://localhost:8080 — le serveur de développement Spring Boot. En production, retourne une chaîne vide, rendant toutes les URL relatives à l'origine de la page (pas de CORS nécessaire). La garde MODE !== 'test' empêche l'URL du serveur de test de fuiter dans les tests Jest.

Étape 2 — getServiceURL / baseRestURL : Ajoute le préfixe REST. Si l'URL du service commence par / (absolue depuis la racine), utilise baseURL directement. Sinon, utilise baseRestURL qui ajoute /rs — le préfixe standard des endpoints REST de ProjectForge. Cela permet à la fois getServiceURL('/some/path') (absolu) et getServiceURL('user/list') (relatif à /rs).

Étape 3 — evalServiceURL / createQueryParams : Ajoute les paramètres de requête. Filtre les valeurs undefined, encode chaque valeur, et utilise correctement ? vs & selon que l'URL contient déjà une chaîne de requête.

Stratégie de débouncing — Sensibilité à saveData

La constante debouncedWaitTime s'adapte en fonction de l'indication client Save-Data (MDN). Lorsque l'utilisateur a activé le mode d'économie de données (courant sur mobile), le temps d'attente passe de 250 ms à 1000 ms — réduisant la fréquence des requêtes sur les connexions facturées. Ceci est utilisé par les composants d'auto-complétion (recherche d'adresse, recherche d'utilisateur, recherche de tâche) pour limiter les appels API pendant la saisie.

La conception est défensive : navigator && navigator.connection && navigator.connection.saveData — trois gardes avant d'accéder à la propriété, car navigator.connection n'est pas disponible dans tous les navigateurs (Firefox, Safari).

API exportée

ExportTypeObjectif
debouncedWaitTimeconst (250 ou 1000)Débouncing adaptatif pour les champs d'auto-complétion basé sur l'en-tête Save-Data
baseURLconst (chaîne)Origine du serveur — localhost:8080 en dev, vide en prod
baseRestURLconst (chaîne)baseURL + "/rs" — préfixe standard des endpoints REST
createQueryParams(obj)fonction → chaîneConstruit key=val&key=val à partir d'un objet, filtre les valeurs undefined
evalServiceURL(url, params)fonction → chaîneAjoute les paramètres de requête à l'URL, gère le ? existant vs &
getServiceURL(url, params)fonction → chaîneConstructeur d'URL complet — ajoute base + préfixe REST, ajoute les paramètres
handleHTTPErrors(res)fonction → ResponseLève une erreur sur les réponses non-2xx, transmet la réponse en cas de succès
fetchJsonGet(url, params, cb)fonction → PromiseGET avec réponse JSON, analysée et transmise au callback
fetchJsonPost(url, body, cb)fonction → PromisePOST avec corps JSON-stringifié et réponse JSON
fetchGet(url, params, cb)fonction → PromiseGET simple avec callback optionnel (pas d'analyse JSON)
getObjectFromQuery(str)fonction → objetInverse de createQueryParams — analyse ?key=val&... en objet

Gestion des erreurs

Les trois fonctions de fetch utilisent le même modèle de gestion d'erreur : .catch((error) => alert('Erreur interne : ' + error)). La directive /* eslint-disable no-alert */ en haut du fichier reconnaît que alert() est normalement interdit par ESLint mais est intentionnellement utilisé ici comme notification utilisateur de dernier recours.

C'est un compromis délibéré : un système de notification d'erreur approprié (piloté par Redux, compatible i18n) serait une meilleure UX, mais rest.js est au bas du graphe de dépendances — il ne peut pas importer de composants Redux ou React. Utiliser alert() signifie que le gestionnaire d'erreurs a zéro dépendance et fonctionne dans n'importe quel contexte (même en dehors du cycle de vie de React). La directive /* eslint-disable no-alert */ a été ajoutée dans le commit ac30e55f7 pour supprimer l'avertissement de linting concernant ce choix intentionnel.

Authentification

Tous les appels fetch utilisent credentials: 'include' — cela ordonne au navigateur d'envoyer les cookies de session HTTP-only avec chaque requête. C'est le mécanisme d'authentification standard pour les SPA servies depuis la même origine que l'API. Le cookie de session est défini par le backend Spring Boot lors de la connexion et est automatiquement inclus par le navigateur. Il n'y a pas de gestion de token, pas d'en-tête Authorization, et pas de logique de session côté client — le navigateur gère tout de manière transparente via le flag credentials.

Utilisation dans le frontend

Ce module est importé par pratiquement tous les composants React qui récupèrent des données. Exemples d'utilisation typique :
// Dans un composant React :
import { fetchJsonGet, fetchJsonPost } from '../utilities/rest';

// Charger une liste
fetchJsonGet('user/list', { search: 'kai' }, (data) => setUsers(data));

// Sauvegarder une entité
fetchJsonPost('user/save', { id: 1, username: 'kai' }, (data) => console.log('sauvegardé', data));

// GET simple pour un téléchargement de fichier
fetchGet('export/excel', { id: 123 }, () => setDownloading(false));

Consommateurs

Le module est importé dans l'ensemble du frontend React — carnet d'adresses, calendrier, feuilles de temps, gestion des utilisateurs, sondages, scripts, et toutes les IUs des plugins. Au moment de la rédaction, environ 40+ composants React importent depuis ce fichier. Tout changement dans la logique de construction d'URL (par exemple, changer le préfixe REST de /rs vers autre chose) affecterait chaque appel API de l'application.

Historique Git

CommitCe qui a changé dans ce fichier
bf988bc6dMigration React-scripts→Vite. La détection d'environnement est passée de process.env.NODE_ENV === 'development' (convention CRA) à import.meta.env.DEV && import.meta.env.MODE !== 'test' (convention Vite). La garde ajoutée MODE !== 'test' empêche Jest de pointer vers localhost:8080 pendant les exécutions de test — un bug courant lors de la migration de CRA vers Vite où le mode de l'environnement de test est 'test', pas 'development'.
823ef0992Garde défensive du callback dans fetchGet. Passé de .then(() => callback()) à .then(() => { if (typeof callback === 'function' && callback()); }). La fonction fetchGet a un callback optionnel — lorsqu'elle est appelée sans (par exemple, requêtes de type fire-and-forget), l'ancien code levait TypeError: callback is not a function. La garde ignore silencieusement le callback quand ce n'est pas une fonction.
253b9f38bPassage de formatage du code. Les chaînes d'appels fetch multi-lignes ont été aplaties en appels sur une seule ligne. Avant : fetch(\n getServiceURL(...), {\n method: 'GET',\n .... Après : fetch(getServiceURL(...), { method: 'GET', .... Bruit visuel réduit, aucun changement de comportement.
ac30e55f7Ajout de la suppression ESLint. Insertion de /* eslint-disable no-alert */ en haut du fichier. Les gestionnaires d'erreur utilisent alert() pour les erreurs visibles par l'utilisateur, ce que la configuration recommandée d'ESLint interdit. La suppression reconnaît que c'est intentionnel — un mécanisme de notification d'erreur sans dépendance pour un module au bas du graphe de dépendances.
bbd81edc3Formatage piloté par ESLint. Les fonctions fléchées sont passées de params => ... à (params) => ... (paramètre unique entre parenthèses). Le corps de fonction redondant dans getServiceURL a été supprimé — la fonction était précédemment définie avec function getServiceURL(...) { ... } et la réécriture était un changement de formatage uniquement.

Décisions de conception

DécisionPourquoiCompromis
Pas d'Axios, fetch simpleZéro dépendance. Le module fait ~80 lignes et couvre tous les besoins du frontend. Ajouter Axios (~14KB gzippé) pour des intercepteurs et l'analyse JSON automatique n'est pas justifié pour trois méthodes HTTP.Pas d'intercepteurs requête/réponse — le renouvellement de token d'authentification nécessiterait du code personnalisé. Actuellement pas nécessaire car l'authentification est basée sur les cookies.
Basé sur des callbacks plutôt que retournant des promessesHistorique. Quand ce module a été écrit (2019), le frontend utilisait des composants de classe avec this.setState dans les callbacks. Retourner des promesses nécessiterait .then(data => this.setState(...)) dans chaque consommateur — le modèle de callback était plus simple à l'époque.Plus difficile à chaîner, plus difficile à utiliser avec async/await. Les consommateurs doivent passer des callbacks même lorsqu'ils veulent juste les données. Une réécriture moderne retournerait des promesses.
alert() pour la gestion des erreursZéro dépendance, fonctionne dans n'importe quel contexte. Un système de notification approprié nécessiterait d'importer React/Redux — créant un risque de dépendance circulaire.Moche, pas compatible i18n, bloque le thread UI. Un émetteur d'événements d'erreur différé serait mieux : window.dispatchEvent(new CustomEvent('rest:error', {detail: error})) — consommé par une boundary d'erreur React ou un composant de notification.
Débouncing sensible à saveDataAmélioration progressive. Les utilisateurs avec des connexions facturées (mobile, pays en développement) obtiennent un débouncing plus lent — moins d'appels API, moins d'utilisation de données. Les utilisateurs avec des connexions non facturées obtiennent un débouncing plus rapide — auto-complétion plus réactive.La prise en charge du navigateur est limitée — navigator.connection est uniquement Chromium. Les utilisateurs de Firefox/Safari obtiennent toujours la valeur par défaut de 250 ms. La dégradation progressive fonctionne correctement.