EN · DE · RU · FR · ES

#2757: rest.js

projectforge-webapp/src/utilities/rest.js JavaScript / React-Dienstprogramm — zentraler REST-API-Client für das ProjectForge React-Frontend. Quelle: projectforge-webapp/src/utilities/rest.js 88 Zeilen · 68 Code · 7 Kommentare · 13 leer
Jede React-Komponente, die mit dem ProjectForge-Backend kommuniziert, durchläuft dieses Modul. Es bietet einen dünnen Wrapper um die fetch-API mit umgebungsbewusster URL-Konstruktion, Sitzungs-Cookie-Authentifizierung (credentials: 'include'), adaptivem Debouncing für langsame Verbindungen und JSON-Anfrage-/Antwortverarbeitung. Dies ist der einzige Kontaktpunkt zwischen der React-SPA und der Spring-Boot-REST-Schicht – es gibt keinen anderen HTTP-Client im Frontend.

Architektur

Pipeline zur URL-Konstruktion

Das Modul baut REST-URLs durch eine dreistufige Pipeline auf, wobei jede Stufe einen Aspekt behandelt:

Stufe 1 – baseURL: Ermittelt den Server-Ursprung. In der Entwicklung (import.meta.env.DEV) zeigt sie auf http://localhost:8080 – den Spring-Boot-Entwicklungsserver. In der Produktion wird eine leere Zeichenkette zurückgegeben, sodass alle URLs relativ zum Seitenursprung sind (kein CORS erforderlich). Die Abfrage MODE !== 'test' verhindert, dass die Testserver-URL in Jest-Tests durchsickert.

Stufe 2 – getServiceURL / baseRestURL: Stellt das REST-Präfix voran. Wenn die Service-URL mit / beginnt (absolut vom Stammverzeichnis), wird baseURL direkt verwendet. Andernfalls wird baseRestURL verwendet, das /rs anhängt – das Standard-Präfix für ProjectForge-REST-Endpunkte. Dies ermöglicht sowohl getServiceURL('/some/path') (absolut) als auch getServiceURL('user/list') (relativ zu /rs).

Stufe 3 – evalServiceURL / createQueryParams: Hängt Abfrageparameter an. Filtert undefined-Werte heraus, kodiert jeden Wert und verwendet korrekt ? vs. &, je nachdem, ob die URL bereits eine Abfragezeichenfolge enthält.

Debounce-Strategie – Save-Data-Bewusstsein

Die Konstante debouncedWaitTime passt sich basierend auf dem Save-Data-Client-Hinweis an (MDN). Wenn der Benutzer den Datensparmodus aktiviert hat (häufig auf Mobilgeräten), erhöht sich die Wartezeit von 250 ms auf 1000 ms – dies reduziert die Anforderungshäufigkeit bei getakteten Verbindungen. Dies wird von den Autovervollständigungskomponenten (Adresssuche, Benutzersuche, Aufgabensuche) verwendet, um API-Aufrufe während der Eingabe zu drosseln.

Das Design ist defensiv: navigator && navigator.connection && navigator.connection.saveData – drei Sicherheitsabfragen, bevor auf die Eigenschaft zugegriffen wird, da navigator.connection nicht in allen Browsern verfügbar ist (Firefox, Safari).

Exportierte API

ExportTypZweck
debouncedWaitTimeconst (250 oder 1000)Adaptives Debounce für Autovervollständigungs-Eingaben basierend auf dem Save-Data-Header
baseURLconst (string)Server-Ursprung – localhost:8080 in der Entwicklung, leer in der Produktion
baseRestURLconst (string)baseURL + "/rs" – Standard-REST-Endpunkt-Präfix
createQueryParams(obj)Funktion → stringErstellt key=val&key=val aus einem Objekt, filtert undefined-Werte heraus
evalServiceURL(url, params)Funktion → stringHängt Abfrageparameter an die URL an, behandelt vorhandenes ? vs. &
getServiceURL(url, params)Funktion → stringVollständiger URL-Builder – stellt Basis + REST-Präfix voran, hängt Parameter an
handleHTTPErrors(res)Funktion → ResponseWirft einen Fehler bei Nicht-2xx-Antworten, gibt die Antwort bei Erfolg durch
fetchJsonGet(url, params, cb)Funktion → PromiseGET mit JSON-Antwort, geparst und an Callback übergeben
fetchJsonPost(url, body, cb)Funktion → PromisePOST mit JSON-stringifiziertem Body und JSON-Antwort
fetchGet(url, params, cb)Funktion → PromiseEinfaches GET mit optionalem Callback (kein JSON-Parsing)
getObjectFromQuery(str)Funktion → objectUmkehrung von createQueryParams – parst ?key=val&... in ein Objekt

Fehlerbehandlung

Alle drei Fetch-Funktionen verwenden dasselbe Fehlerbehandlungsmuster: .catch((error) => alert('Interner Fehler: ' + error)). Die Direktive /* eslint-disable no-alert */ am Anfang erkennt an, dass alert() normalerweise von ESLint verboten wird, hier aber absichtlich als letzte Benachrichtigungsmöglichkeit für den Benutzer verwendet wird.

Dies ist ein bewusster Kompromiss: Ein ordentliches Fehler-Toast-System (Redux-gesteuert, i18n-fähig) wäre eine bessere Benutzererfahrung, aber rest.js ist die unterste Ebene des Abhängigkeitsgraphen – es kann keine Redux- oder React-Komponenten importieren. Die Verwendung von alert() bedeutet, dass der Fehlerhandler null Abhängigkeiten hat und in jedem Kontext funktioniert (auch außerhalb des React-Lebenszyklus). Die Datei /* eslint-disable no-alert */ wurde im Commit ac30e55f7 hinzugefügt, um die Linting-Warnung zu dieser beabsichtigten Entscheidung zu unterdrücken.

Authentifizierung

Alle Fetch-Aufrufe verwenden credentials: 'include' – dies weist den Browser an, HTTP-Only-Sitzungs-Cookies mit jeder Anfrage zu senden. Dies ist der Standard-Authentifizierungsmechanismus für SPAs, die von derselben Quelle wie die API bereitgestellt werden. Das Sitzungs-Cookie wird vom Spring-Boot-Backend bei der Anmeldung gesetzt und automatisch vom Browser eingefügt. Es gibt keine Token-Verwaltung, keinen Authorization-Header und keine clientseitige Sitzungslogik – der Browser handhabt dies transparent über das credentials-Flag.

Verwendung im Frontend

Dieses Modul wird von praktisch jeder React-Komponente importiert, die Daten abruft. Typische Verwendungsmuster:
// In einer React-Komponente:
import { fetchJsonGet, fetchJsonPost } from '../utilities/rest';

// Eine Liste laden
fetchJsonGet('user/list', { search: 'kai' }, (data) => setUsers(data));

// Eine Entität speichern
fetchJsonPost('user/save', { id: 1, username: 'kai' }, (data) => console.log('gespeichert', data));

// Einfaches GET für einen Dateidownload
fetchGet('export/excel', { id: 123 }, () => setDownloading(false));

Verbraucher

Das Modul wird im gesamten React-Frontend importiert – Adressbuch, Kalender, Zeiterfassung, Benutzerverwaltung, Umfragen, Skripte und alle Plugin-Oberflächen. Zum Zeitpunkt der Erstellung dieses Textes importieren etwa 40+ React-Komponenten aus dieser Datei. Jede Änderung an der URL-Konstruktionslogik (z. B. Änderung des REST-Präfix von /rs zu etwas anderem) würde jeden API-Aufruf in der Anwendung betreffen.

Git-Verlauf

CommitWas sich in dieser Datei geändert hat
bf988bc6dReact-Scripts→Vite-Migration. Die Umgebungserkennung wurde von process.env.NODE_ENV === 'development' (CRA-Konvention) auf import.meta.env.DEV && import.meta.env.MODE !== 'test' (Vite-Konvention) geändert. Die hinzugefügte Abfrage MODE !== 'test' verhindert, dass Jest während Testläufen auf localhost:8080 zeigt – ein häufiger Fehler bei der Migration von CRA zu Vite, bei dem der Testumgebungsmodus 'test' und nicht 'development' ist.
823ef0992Defensive Callback-Abfrage in fetchGet. Geändert von .then(() => callback()) zu .then(() => { if (typeof callback === 'function' && callback()); }). Die Funktion fetchGet hat einen optionalen Callback – wenn sie ohne einen solchen aufgerufen wird (z. B. Fire-and-Forget-Anfragen), würde der alte Code einen TypeError: callback is not a function auslösen. Die Abfrage überspringt den Callback stillschweigend, wenn er keine Funktion ist.
253b9f38bCode-Formatierungsdurchlauf. Mehrzeilige Fetch-Aufrufketten wurden in einzeilige Aufrufe umgewandelt. Vorher: fetch(\n getServiceURL(...), {\n method: 'GET',\n .... Nachher: fetch(getServiceURL(...), { method: 'GET', .... Reduziertes visuelles Rauschen, keine Verhaltensänderung.
ac30e55f7ESLint-Unterdrückung hinzugefügt. /* eslint-disable no-alert */ am Anfang der Datei eingefügt. Die Fehlerhandler verwenden alert() für benutzerseitige Fehler, was die empfohlene ESLint-Konfiguration verbietet. Die Unterdrückung erkennt an, dass dies beabsichtigt ist – ein benachrichtigungsmechanismus ohne Abhängigkeiten für ein Modul auf der untersten Ebene des Abhängigkeitsgraphen.
bbd81edc3ESLint-gesteuerte Formatierung. Pfeilfunktionen wurden von params => ... zu (params) => ... geändert (Einzelparameter in Klammern). Der redundante Funktionskörper in getServiceURL wurde entfernt – die Funktion war zuvor mit function getServiceURL(...) { ... } definiert, und die Neuschreibung war eine reine Formatierungsänderung.

Designentscheidungen

EntscheidungWarumKompromiss
Kein Axios, einfaches fetchKeine Abhängigkeiten. Das Modul hat ~80 Zeilen und deckt alle Anforderungen des Frontends ab. Das Hinzufügen von Axios (~14 KB gzippt) für Interceptoren und automatisches JSON-Parsing ist für drei HTTP-Methoden nicht gerechtfertigt.Keine Anfrage-/Antwort-Interceptoren – eine Aktualisierung des Authentifizierungstokens würde benutzerdefinierten Code erfordern. Derzeit nicht erforderlich, da die Authentifizierung cookie-basiert ist.
Callback-basiert statt Promise-RückgabeHistorisch bedingt. Als dieses Modul geschrieben wurde (2019), verwendete das Frontend Klassenkomponenten mit this.setState in Callbacks. Die Rückgabe von Promises würde in jedem Verbraucher .then(data => this.setState(...)) erfordern – das Callback-Muster war damals einfacher.Schwerer zu verketten, schwerer mit async/await zu verwenden. Verbraucher müssen Callbacks übergeben, auch wenn sie nur die Daten möchten. Eine moderne Neufassung würde Promises zurückgeben.
alert() für die FehlerbehandlungKeine Abhängigkeiten, funktioniert in jedem Kontext. Ein ordentliches Toast-System würde das Importieren von React/Redux erfordern – was ein zirkuläres Abhängigkeitsrisiko darstellt.Unschön, nicht i18n-fähig, blockiert den UI-Thread. Ein verzögerter Fehlerereignis-Emitter wäre besser: window.dispatchEvent(new CustomEvent('rest:error', {detail: error})) – verarbeitet von einer React-Fehlergrenze oder einer Toast-Komponente.
saveData-bewusstes DebounceFortschrittliche Verbesserung. Benutzer mit getakteten Verbindungen (mobil, Entwicklungsländer) erhalten ein langsameres Debounce – weniger API-Aufrufe, weniger Datennutzung. Benutzer mit unbegrenzten Verbindungen erhalten ein schnelleres Debounce – reaktionsschnellere Autovervollständigung.Die Browserunterstützung ist begrenzt – navigator.connection ist nur für Chromium verfügbar. Firefox-/Safari-Benutzer erhalten immer den Standardwert von 250 ms. Die abwärtskompatible Fehlerbehandlung funktioniert korrekt.