Web-Frontend entwickeln: Unterschied zwischen den Versionen
Umlaute auch in kopierbaren Codebeispielen normalisiert |
Nicht installierte SyntaxHighlight-Tags durch kompatible pre-Codebloecke ersetzt |
||
| Zeile 69: | Zeile 69: | ||
`POST /enter-system` | `POST /enter-system` | ||
< | <pre> | ||
{ | { | ||
"systemCode": "123456" | "systemCode": "123456" | ||
} | } | ||
</ | </pre> | ||
`systemCode` darf nicht leer sein. Ein unbekannter oder bereits verwendeter Code ergibt `401 Unauthorized`. | `systemCode` darf nicht leer sein. Ein unbekannter oder bereits verwendeter Code ergibt `401 Unauthorized`. | ||
| Zeile 81: | Zeile 81: | ||
`POST /video-command` | `POST /video-command` | ||
< | <pre> | ||
{ | { | ||
"prSc": "<Projektkennung>", | "prSc": "<Projektkennung>", | ||
| Zeile 88: | Zeile 88: | ||
] | ] | ||
} | } | ||
</ | </pre> | ||
Regeln des aktuellen Request-Modells: | Regeln des aktuellen Request-Modells: | ||
| Zeile 105: | Zeile 105: | ||
Die Netzwerkanbindung soll an einer Stelle gekapselt werden. Dadurch bleiben Fehlerbehandlung, Header und JSON-Verarbeitung konsistent. | Die Netzwerkanbindung soll an einer Stelle gekapselt werden. Dadurch bleiben Fehlerbehandlung, Header und JSON-Verarbeitung konsistent. | ||
< | <pre> | ||
const megBesConfig = Object.freeze({ | const megBesConfig = Object.freeze({ | ||
apiBase: "https://<projekt>.megbes.de", | apiBase: "https://<projekt>.megbes.de", | ||
| Zeile 163: | Zeile 163: | ||
return responseBody; | return responseBody; | ||
} | } | ||
</ | </pre> | ||
== Variante A: Frontend ohne Authentifizierung == | == Variante A: Frontend ohne Authentifizierung == | ||
| Zeile 169: | Zeile 169: | ||
Diese Variante existiert nur, wenn `megbes.security` für die External-Instanz nicht aktiviert ist. Das Frontend darf diesen Modus nicht anhand eines fehlgeschlagenen Loginversuchs erraten; er wird vor der Entwicklung projektspezifisch festgelegt. | Diese Variante existiert nur, wenn `megbes.security` für die External-Instanz nicht aktiviert ist. Das Frontend darf diesen Modus nicht anhand eines fehlgeschlagenen Loginversuchs erraten; er wird vor der Entwicklung projektspezifisch festgelegt. | ||
< | <pre> | ||
async function sendCommandWithoutAuthentication(station, command) { | async function sendCommandWithoutAuthentication(station, command) { | ||
return megBesRequest("/video-command", { | return megBesRequest("/video-command", { | ||
| Zeile 179: | Zeile 179: | ||
}); | }); | ||
} | } | ||
</ | </pre> | ||
Auch ohne MegBes-Authentifizierung muss die External-Instanz ausschließlich über HTTPS angesprochen werden. Welche Personen die Seite erreichen dürfen, ist außerhalb von MegBes abzusichern. | Auch ohne MegBes-Authentifizierung muss die External-Instanz ausschließlich über HTTPS angesprochen werden. Welche Personen die Seite erreichen dürfen, ist außerhalb von MegBes abzusichern. | ||
| Zeile 189: | Zeile 189: | ||
Der Ticket-Monitor ruft `POST /authenticate` mit den Headern `customer` und `authId` auf. Zusätzlich prüft MegBes den User-Agent gegen die Projektkonfiguration. | Der Ticket-Monitor ruft `POST /authenticate` mit den Headern `customer` und `authId` auf. Zusätzlich prüft MegBes den User-Agent gegen die Projektkonfiguration. | ||
< | <pre> | ||
async function authenticateTicketMonitor(customer, authId) { | async function authenticateTicketMonitor(customer, authId) { | ||
const response = await megBesRequest("/authenticate", { | const response = await megBesRequest("/authenticate", { | ||
| Zeile 200: | Zeile 200: | ||
return response.id; | return response.id; | ||
} | } | ||
</ | </pre> | ||
`authId` ist ein Geheimwert und darf nicht fest in ausgelieferter Browser-JavaScript-Datei stehen. Wenn der Ticket-Monitor nur statische öffentliche Dateien ausliefert, ist dieses Verfahren sicherheitlich neu zu bewerten. Die Kennung gehört in eine geschützte Laufzeitumgebung oder einen vertrauenswürdigen lokalen Dienst. | `authId` ist ein Geheimwert und darf nicht fest in ausgelieferter Browser-JavaScript-Datei stehen. Wenn der Ticket-Monitor nur statische öffentliche Dateien ausliefert, ist dieses Verfahren sicherheitlich neu zu bewerten. Die Kennung gehört in eine geschützte Laufzeitumgebung oder einen vertrauenswürdigen lokalen Dienst. | ||
| Zeile 206: | Zeile 206: | ||
=== 2. Ticketcode abrufen === | === 2. Ticketcode abrufen === | ||
< | <pre> | ||
async function getTicket(adminToken, customer, ticketMonitorNumber) { | async function getTicket(adminToken, customer, ticketMonitorNumber) { | ||
const query = new URLSearchParams({ | const query = new URLSearchParams({ | ||
| Zeile 218: | Zeile 218: | ||
return response.id; | return response.id; | ||
} | } | ||
</ | </pre> | ||
Der gelieferte Code ist im aktuellen Stand sechsstellig. Das Frontend soll ihn als String behandeln, damit eine führende Null nicht verloren geht. | Der gelieferte Code ist im aktuellen Stand sechsstellig. Das Frontend soll ihn als String behandeln, damit eine führende Null nicht verloren geht. | ||
| Zeile 226: | Zeile 226: | ||
=== 1. Code aktivieren === | === 1. Code aktivieren === | ||
< | <pre> | ||
async function enterSystem(systemCode) { | async function enterSystem(systemCode) { | ||
await megBesRequest("/enter-system", { | await megBesRequest("/enter-system", { | ||
| Zeile 234: | Zeile 234: | ||
return systemCode; | return systemCode; | ||
} | } | ||
</ | </pre> | ||
Nach erfolgreichem Eintritt dient derselbe Code als Pseudo-ID für die weiteren Aufrufe. Ein Ticketcode kann nur einmal aktiviert werden. | Nach erfolgreichem Eintritt dient derselbe Code als Pseudo-ID für die weiteren Aufrufe. Ein Ticketcode kann nur einmal aktiviert werden. | ||
| Zeile 240: | Zeile 240: | ||
=== 2. Kennung prüfen === | === 2. Kennung prüfen === | ||
< | <pre> | ||
async function verifyUser(userToken) { | async function verifyUser(userToken) { | ||
await megBesRequest("/verify", { token: userToken }); | await megBesRequest("/verify", { token: userToken }); | ||
return true; | return true; | ||
} | } | ||
</ | </pre> | ||
=== 3. An Station anmelden === | === 3. An Station anmelden === | ||
< | <pre> | ||
async function loginStation(userToken, stationNumber) { | async function loginStation(userToken, stationNumber) { | ||
const query = new URLSearchParams({ station: String(stationNumber) }); | const query = new URLSearchParams({ station: String(stationNumber) }); | ||
await megBesRequest(`/media-station?${query}`, { token: userToken }); | await megBesRequest(`/media-station?${query}`, { token: userToken }); | ||
} | } | ||
</ | </pre> | ||
Im intern kontrollierten Modus ist die Stationsnummer zwingend. Ein `401` kann neben einer ungültigen Kennung auch bedeuten, dass die Station belegt ist oder die Stationsanmeldung abgelehnt wurde. Die Bedienoberfläche darf deshalb nicht automatisch behaupten, der Ticketcode sei falsch. | Im intern kontrollierten Modus ist die Stationsnummer zwingend. Ein `401` kann neben einer ungültigen Kennung auch bedeuten, dass die Station belegt ist oder die Stationsanmeldung abgelehnt wurde. Die Bedienoberfläche darf deshalb nicht automatisch behaupten, der Ticketcode sei falsch. | ||
| Zeile 262: | Zeile 262: | ||
Dieser Endpunkt ist nur verfügbar, wenn `megbes.internal.station.controlled=true` gilt. | Dieser Endpunkt ist nur verfügbar, wenn `megbes.internal.station.controlled=true` gilt. | ||
< | <pre> | ||
async function getStationStatus(userToken, stationNumber) { | async function getStationStatus(userToken, stationNumber) { | ||
const response = await megBesRequest( | const response = await megBesRequest( | ||
| Zeile 270: | Zeile 270: | ||
return response.stationStatus; | return response.stationStatus; | ||
} | } | ||
</ | </pre> | ||
Der aktuelle Code verwendet unter anderem die Zustände `OPEN`, `IDLE`, `REMOTE` und `TOUCHSCREEN`. Die Oberfläche soll unbekannte künftige Werte neutral anzeigen und nicht abstürzen. | Der aktuelle Code verwendet unter anderem die Zustände `OPEN`, `IDLE`, `REMOTE` und `TOUCHSCREEN`. Die Oberfläche soll unbekannte künftige Werte neutral anzeigen und nicht abstürzen. | ||
| Zeile 276: | Zeile 276: | ||
=== 5. Befehl senden === | === 5. Befehl senden === | ||
< | <pre> | ||
async function sendCommand(userToken, stationNumber, command) { | async function sendCommand(userToken, stationNumber, command) { | ||
return megBesRequest("/video-command", { | return megBesRequest("/video-command", { | ||
| Zeile 287: | Zeile 287: | ||
}); | }); | ||
} | } | ||
</ | </pre> | ||
=== 6. Station abmelden === | === 6. Station abmelden === | ||
< | <pre> | ||
async function logoutStation(userToken) { | async function logoutStation(userToken) { | ||
await megBesRequest("/media-station", { | await megBesRequest("/media-station", { | ||
| Zeile 298: | Zeile 298: | ||
}); | }); | ||
} | } | ||
</ | </pre> | ||
Die Abmeldung soll über eine sichtbare Bedienhandlung und bei regulärem Abschluss erfolgen. Ein Aufruf in `beforeunload` ist allein nicht zuverlässig, da Browser ausstehende Requests beim Schließen abbrechen können. Der projektspezifische Stations-Timer bleibt daher als Rückfallebene erforderlich. | Die Abmeldung soll über eine sichtbare Bedienhandlung und bei regulärem Abschluss erfolgen. Ein Aufruf in `beforeunload` ist allein nicht zuverlässig, da Browser ausstehende Requests beim Schließen abbrechen können. Der projektspezifische Stations-Timer bleibt daher als Rückfallebene erforderlich. | ||
| Zeile 304: | Zeile 304: | ||
== Vollständiger Ablauf eines Besucher-Frontends == | == Vollständiger Ablauf eines Besucher-Frontends == | ||
< | <pre> | ||
let userToken = null; | let userToken = null; | ||
let activeStation = null; | let activeStation = null; | ||
| Zeile 333: | Zeile 333: | ||
activeStation = null; | activeStation = null; | ||
} | } | ||
</ | </pre> | ||
== Minimales HTML-Beispiel == | == Minimales HTML-Beispiel == | ||
| Zeile 339: | Zeile 339: | ||
Das Beispiel zeigt die Struktur, ist aber ohne projektspezifische Werte und Gestaltung nicht produktionsfertig. | Das Beispiel zeigt die Struktur, ist aber ohne projektspezifische Werte und Gestaltung nicht produktionsfertig. | ||
< | <pre> | ||
<!doctype html> | <!doctype html> | ||
<html lang="de"> | <html lang="de"> | ||
| Zeile 413: | Zeile 413: | ||
</body> | </body> | ||
</html> | </html> | ||
</ | </pre> | ||
Die sichtbaren Befehle `play` und `stop` sind ebenfalls nur Platzhalter. Sie werden durch die freigegebenen Projektbefehle ersetzt. | Die sichtbaren Befehle `play` und `stop` sind ebenfalls nur Platzhalter. Sie werden durch die freigegebenen Projektbefehle ersetzt. | ||
| Zeile 419: | Zeile 419: | ||
== Fehlerbehandlung == | == Fehlerbehandlung == | ||
< | <pre> | ||
function messageForMegBesError(error) { | function messageForMegBesError(error) { | ||
if (!(error instanceof MegBesError)) { | if (!(error instanceof MegBesError)) { | ||
| Zeile 440: | Zeile 440: | ||
} | } | ||
} | } | ||
</ | </pre> | ||
Keine rohen Backend-Antworten, Stacktraces, Tokens oder internen Adressen an Besucher ausgeben. Technische Details gehören in eine geschützte Diagnoseansicht oder in bereinigte Logs. | Keine rohen Backend-Antworten, Stacktraces, Tokens oder internen Adressen an Besucher ausgeben. Technische Details gehören in eine geschützte Diagnoseansicht oder in bereinigte Logs. | ||
Version vom 6. August 2026, 19:20 Uhr
Web-Frontend entwickeln
Zweck und Geltungsbereich
Diese Seite beschreibt die HTML-/JavaScript-Anbindung eines Web-Frontends an `megbes-external`. Sie basiert auf den Controllern und Request-Modellen des aktuellen MegBes-Repositorys. Vor der Freigabe eines Frontends muss trotzdem geprüft werden, welcher MegBes-Release im betreffenden Projekt installiert ist.
Das Frontend spricht ausschließlich `megbes-external` an. Direkte Browseraufrufe an `megbes-internal` sind nicht vorgesehen.
Browser → HTTPS → megbes-external → megbes-internal → UDP → Station
Vor Beginn festlegen
| Wert | Bedeutung | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| API-Basisadresse | HTTPS-Adresse der projektspezifischen External-Instanz | Projektkennung | Wert für `prSc`; gegen den eingesetzten Release und die Projektkonfiguration prüfen | Sicherheitsmodus | ohne Authentifizierung oder zentrale Ticket-/Benutzerauthentifizierung | Stationssteuerung | extern oder durch `megbes-internal` kontrolliert | Token-Header | Wert der Laufzeitkonfiguration `jwt.header`; nicht pauschal `Authorization` annehmen | Stationsnummern | freigegebene Stationen des Projekts | Stationsbefehle | projektspezifische Befehle, die die jeweilige Station tatsächlich versteht |
Domains, Headername, Projektkennung, Stationsnummern und Befehle dürfen nicht ungeprüft von einem anderen Projekt übernommen werden.
API-Übersicht
| Methode und Pfad | Zweck | Berechtigung | Erfolgsantwort |
|---|---|---|---|
| `GET /info` | vom Server empfangenen User-Agent prüfen | frei | `200` mit User-Agent |
| `POST /authenticate` | Ticket-Monitor administrativ authentifizieren | frei; benötigt Header `customer` und `authId` | `200` und `{ "id": "..." }` |
| `GET /verify-admin` | Administrationskennung prüfen | ADMIN | `200` |
| `GET /ticket` | Ticketcode erzeugen oder vorhandenen unbenutzten Code abrufen | ADMIN | `200` und `{ "id": "......" }` |
| `POST /enter-system` | Ticketcode einmalig aktivieren | frei | `200` |
| `GET /verify` | aktivierte Benutzerkennung prüfen | USER oder ADMIN | `200` |
| `GET /media-station?station=...` | Benutzer an Station anmelden | USER oder ADMIN | `200` |
| `GET /media-station/status/{stationNumber}` | Stationszustand abrufen | USER oder ADMIN; nur bei intern kontrollierten Stationen | `200` und `{ "stationStatus": "..." }` |
| `POST /video-command` | einen oder mehrere UDP-Befehle senden | ohne Security frei, sonst USER oder ADMIN | Antwort von `megbes-internal` |
| `DELETE /media-station` | Benutzer abmelden und Station zurücksetzen | USER oder ADMIN | `200` |
Wichtige Authentifizierungsregel
MegBes liefert dem Browser beziehungsweise Ticket-Monitor eine Pseudo-ID. Der eigentliche JWT verbleibt serverseitig. Bei geschützten Folgeaufrufen sendet das Frontend die Pseudo-ID als Wert des projektspezifisch konfigurierten Token-Headers.
<konfigurierter-token-header>: <Pseudo-ID>
Der Headername stammt aus `jwt.header`. Er darf im Frontend nicht geraten werden. Die Pseudo-ID ist wie eine Sitzungskennung zu behandeln: nicht protokollieren, nicht in URLs anhängen und nicht ins Wiki kopieren.
Request-Modelle
System betreten
`POST /enter-system`
{
"systemCode": "123456"
}
`systemCode` darf nicht leer sein. Ein unbekannter oder bereits verwendeter Code ergibt `401 Unauthorized`.
Stationsbefehl
`POST /video-command`
{
"prSc": "<Projektkennung>",
"statNoCommands": [
"12-play"
]
}
Regeln des aktuellen Request-Modells:
- `prSc` ist Pflicht und darf nicht leer sein.
- `statNoCommands` ist eine nicht leere Liste.
- Jeder Listeneintrag folgt `\d{0,3}-[0-9a-zA-Z]{2,20}`.
- Vor dem Bindestrich sind damit null bis drei Ziffern zulässig; für produktive Stationsbefehle soll eine eindeutige, im Projekt dokumentierte Stationsnummer verwendet werden.
- Nach dem Bindestrich sind zwei bis zwanzig ASCII-Buchstaben oder Ziffern zulässig.
- Ob ein formal gültiger Befehl fachlich unterstützt wird, entscheidet die jeweilige Stationssoftware.
Beispiele wie `12-play` sind nur Formbeispiele und keine globale MegBes-Befehlsliste.
Empfohlener JavaScript-API-Client
Die Netzwerkanbindung soll an einer Stelle gekapselt werden. Dadurch bleiben Fehlerbehandlung, Header und JSON-Verarbeitung konsistent.
const megBesConfig = Object.freeze({
apiBase: "https://<projekt>.megbes.de",
projectCode: "<PROJEKTKENNUNG>",
tokenHeader: "<WERT-AUS-jwt.header>"
});
class MegBesError extends Error {
constructor(message, status, responseBody) {
super(message);
this.name = "MegBesError";
this.status = status;
this.responseBody = responseBody;
}
}
async function megBesRequest(path, {
method = "GET",
token = null,
body = undefined,
headers = {}
} = {}) {
const requestHeaders = new Headers(headers);
if (token) {
requestHeaders.set(megBesConfig.tokenHeader, token);
}
if (body !== undefined) {
requestHeaders.set("Content-Type", "application/json");
}
let response;
try {
response = await fetch(`${megBesConfig.apiBase}${path}`, {
method,
headers: requestHeaders,
body: body === undefined ? undefined : JSON.stringify(body),
cache: "no-store"
});
} catch (cause) {
throw new MegBesError("MegBes ist nicht erreichbar.", 0, cause);
}
const contentType = response.headers.get("content-type") || "";
const responseBody = contentType.includes("application/json")
? await response.json()
: await response.text();
if (!response.ok) {
throw new MegBesError(
`MegBes-Aufruf fehlgeschlagen (${response.status}).`,
response.status,
responseBody
);
}
return responseBody;
}
Variante A: Frontend ohne Authentifizierung
Diese Variante existiert nur, wenn `megbes.security` für die External-Instanz nicht aktiviert ist. Das Frontend darf diesen Modus nicht anhand eines fehlgeschlagenen Loginversuchs erraten; er wird vor der Entwicklung projektspezifisch festgelegt.
async function sendCommandWithoutAuthentication(station, command) {
return megBesRequest("/video-command", {
method: "POST",
body: {
prSc: megBesConfig.projectCode,
statNoCommands: [`${station}-${command}`]
}
});
}
Auch ohne MegBes-Authentifizierung muss die External-Instanz ausschließlich über HTTPS angesprochen werden. Welche Personen die Seite erreichen dürfen, ist außerhalb von MegBes abzusichern.
Variante B: Ticket-Monitor
1. Ticket-Monitor authentifizieren
Der Ticket-Monitor ruft `POST /authenticate` mit den Headern `customer` und `authId` auf. Zusätzlich prüft MegBes den User-Agent gegen die Projektkonfiguration.
async function authenticateTicketMonitor(customer, authId) {
const response = await megBesRequest("/authenticate", {
method: "POST",
headers: {
customer,
authId
}
});
return response.id;
}
`authId` ist ein Geheimwert und darf nicht fest in ausgelieferter Browser-JavaScript-Datei stehen. Wenn der Ticket-Monitor nur statische öffentliche Dateien ausliefert, ist dieses Verfahren sicherheitlich neu zu bewerten. Die Kennung gehört in eine geschützte Laufzeitumgebung oder einen vertrauenswürdigen lokalen Dienst.
2. Ticketcode abrufen
async function getTicket(adminToken, customer, ticketMonitorNumber) {
const query = new URLSearchParams({
customer,
ticketMonitorNumber: String(ticketMonitorNumber)
});
const response = await megBesRequest(`/ticket?${query}`, {
token: adminToken
});
return response.id;
}
Der gelieferte Code ist im aktuellen Stand sechsstellig. Das Frontend soll ihn als String behandeln, damit eine führende Null nicht verloren geht.
Variante C: Besucher- oder Guide-Frontend mit Ticketcode
1. Code aktivieren
async function enterSystem(systemCode) {
await megBesRequest("/enter-system", {
method: "POST",
body: { systemCode }
});
return systemCode;
}
Nach erfolgreichem Eintritt dient derselbe Code als Pseudo-ID für die weiteren Aufrufe. Ein Ticketcode kann nur einmal aktiviert werden.
2. Kennung prüfen
async function verifyUser(userToken) {
await megBesRequest("/verify", { token: userToken });
return true;
}
3. An Station anmelden
async function loginStation(userToken, stationNumber) {
const query = new URLSearchParams({ station: String(stationNumber) });
await megBesRequest(`/media-station?${query}`, { token: userToken });
}
Im intern kontrollierten Modus ist die Stationsnummer zwingend. Ein `401` kann neben einer ungültigen Kennung auch bedeuten, dass die Station belegt ist oder die Stationsanmeldung abgelehnt wurde. Die Bedienoberfläche darf deshalb nicht automatisch behaupten, der Ticketcode sei falsch.
4. Optional Stationsstatus prüfen
Dieser Endpunkt ist nur verfügbar, wenn `megbes.internal.station.controlled=true` gilt.
async function getStationStatus(userToken, stationNumber) {
const response = await megBesRequest(
`/media-station/status/${encodeURIComponent(stationNumber)}`,
{ token: userToken }
);
return response.stationStatus;
}
Der aktuelle Code verwendet unter anderem die Zustände `OPEN`, `IDLE`, `REMOTE` und `TOUCHSCREEN`. Die Oberfläche soll unbekannte künftige Werte neutral anzeigen und nicht abstürzen.
5. Befehl senden
async function sendCommand(userToken, stationNumber, command) {
return megBesRequest("/video-command", {
method: "POST",
token: userToken,
body: {
prSc: megBesConfig.projectCode,
statNoCommands: [`${stationNumber}-${command}`]
}
});
}
6. Station abmelden
async function logoutStation(userToken) {
await megBesRequest("/media-station", {
method: "DELETE",
token: userToken
});
}
Die Abmeldung soll über eine sichtbare Bedienhandlung und bei regulärem Abschluss erfolgen. Ein Aufruf in `beforeunload` ist allein nicht zuverlässig, da Browser ausstehende Requests beim Schließen abbrechen können. Der projektspezifische Stations-Timer bleibt daher als Rückfallebene erforderlich.
Vollständiger Ablauf eines Besucher-Frontends
let userToken = null;
let activeStation = null;
async function startSession(systemCode, stationNumber) {
userToken = await enterSystem(systemCode);
await verifyUser(userToken);
// Nur im intern kontrollierten Modus vorab verwenden.
// const status = await getStationStatus(userToken, stationNumber);
await loginStation(userToken, stationNumber);
activeStation = String(stationNumber);
}
async function play(command) {
if (!userToken || !activeStation) {
throw new Error("Keine aktive MegBes-Station.");
}
await sendCommand(userToken, activeStation, command);
}
async function endSession() {
if (userToken && activeStation) {
await logoutStation(userToken);
}
userToken = null;
activeStation = null;
}
Minimales HTML-Beispiel
Das Beispiel zeigt die Struktur, ist aber ohne projektspezifische Werte und Gestaltung nicht produktionsfertig.
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>MegBes-Steuerung</title>
</head>
<body>
<main>
<form id="login-form">
<label for="code">Ticketcode</label>
<input id="code" name="code" inputmode="numeric" autocomplete="one-time-code" required>
<label for="station">Station</label>
<input id="station" name="station" inputmode="numeric" required>
<button type="submit">Station verbinden</button>
</form>
<section id="controls" hidden>
<button type="button" data-command="play">Start</button>
<button type="button" data-command="stop">Stopp</button>
<button type="button" id="logout">Beenden</button>
</section>
<p id="message" role="status" aria-live="polite"></p>
</main>
<script src="megbes-api.js"></script>
<script>
const form = document.querySelector("#login-form");
const controls = document.querySelector("#controls");
const message = document.querySelector("#message");
form.addEventListener("submit", async event => {
event.preventDefault();
message.textContent = "Verbindung wird hergestellt ...";
try {
await startSession(form.code.value.trim(), form.station.value.trim());
controls.hidden = false;
message.textContent = "Station ist verbunden.";
} catch (error) {
message.textContent = messageForMegBesError(error);
}
});
controls.addEventListener("click", async event => {
const command = event.target.dataset.command;
if (!command) return;
try {
event.target.disabled = true;
await play(command);
message.textContent = "Befehl wurde gesendet.";
} catch (error) {
message.textContent = messageForMegBesError(error);
} finally {
event.target.disabled = false;
}
});
document.querySelector("#logout").addEventListener("click", async () => {
try {
await endSession();
controls.hidden = true;
form.reset();
message.textContent = "Sitzung wurde beendet.";
} catch (error) {
message.textContent = messageForMegBesError(error);
}
});
</script>
</body>
</html>
Die sichtbaren Befehle `play` und `stop` sind ebenfalls nur Platzhalter. Sie werden durch die freigegebenen Projektbefehle ersetzt.
Fehlerbehandlung
function messageForMegBesError(error) {
if (!(error instanceof MegBesError)) {
return "Unerwarteter Fehler. Bitte Aufsicht informieren.";
}
switch (error.status) {
case 0:
return "MegBes ist derzeit nicht erreichbar.";
case 400:
return "Die Anfrage ist unvollständig oder ungültig.";
case 401:
return "Anmeldung, Berechtigung oder Stationsbelegung konnte nicht bestätigt werden.";
case 403:
return "Die Station ist für diese Sitzung nicht freigegeben.";
case 404:
return "Diese Funktion ist in der aktuellen Betriebsart nicht verfügbar.";
default:
return "MegBes konnte den Auftrag nicht ausführen.";
}
}
Keine rohen Backend-Antworten, Stacktraces, Tokens oder internen Adressen an Besucher ausgeben. Technische Details gehören in eine geschützte Diagnoseansicht oder in bereinigte Logs.
Zustands- und Bedienregeln
- Mehrfachklicks verhindern, solange ein Request läuft.
- Erst nach erfolgreicher Antwort den nächsten fachlichen Zustand anzeigen.
- Ticketcodes und Pseudo-IDs nicht in der URL oder in dauerhaftem `localStorage` speichern.
- Bei Seitenneuladung `/verify` verwenden, bevor eine bestehende Sitzung angenommen wird.
- Eine Station erst als verbunden anzeigen, wenn `/media-station` erfolgreich war.
- Bei `401` nicht automatisch einen falschen Code behaupten; auch Belegung und Projektzuordnung sind mögliche Ursachen.
- Bei unbekanntem Stationsstatus eine neutrale Fehlermeldung anzeigen.
- Bedienoberfläche tastaturbedienbar gestalten und Statusmeldungen mit `aria-live` ausgeben.
- Stationsbefehle nur aus einer festen projektspezifischen Positivliste erzeugen, nicht aus frei eingegebenem Besuchertest.
CORS und Hosting
Der aktuelle MegBes-Stand erlaubt CORS-Anfragen grundsätzlich. Das ist keine Zusage, dass jede zukünftige Instanz beliebige Origins akzeptiert. Bevorzugt werden Frontend und API unter kontrollierten HTTPS-Adressen betrieben. Ein CORS-Fehler wird nicht durch Abschalten von Browsersicherheit oder Zertifikatsprüfung umgangen.
Entwicklungs- und Abnahmetests
- `GET /info` von der vorgesehenen Hardware aufrufen.
- korrekten Sicherheits- und Stationsmodus bestätigen.
- gültigen, ungültigen, bereits benutzten und abgelaufenen Ticketcode testen.
- freie, belegte und unbekannte Station testen.
- jeden freigegebenen Stationsbefehl einzeln prüfen.
- Mehrfachklick und parallele Requests testen.
- Abmeldung und erneute Anmeldung testen.
- Browser-Neuladen sowie Neustart von Frontend-Hardware und Station testen.
- Ausfall von External, Internal und Stationsnetz jeweils getrennt simulieren.
- sicherstellen, dass keine Tokens oder Geheimwerte in URL, Konsole, Screenshot oder sichtbarer Fehlermeldung erscheinen.
- installierten External-/Internal-Release, Frontend-Version und Testergebnis auf der Projektseite dokumentieren.
Grenzen dieser Referenz
Diese Seite dokumentiert den API-Vertrag des aktuellen Repository-Stands. Projektspezifische Stationsbefehle, Gestaltung, Inhalte, Ticketdruck, Hardware-Autostart und konkrete Frontend-Dateipfade werden auf der jeweiligen Projektseite dokumentiert. Bei Abweichungen hat der tatsächlich eingesetzte Release Vorrang.
Siehe auch Web-Frontend, Bedienung und Authentifizierung, UDP und Stationszustände, Client-Feedback und Stationsmeldungen und Routing und Signalweg.