============ Web Frontend ============ .. role:: raw-html(raw) :format: html Wir haben uns im Frontend erstmal auf React geeinigt. Jedoch wurde uns schnell klar, dass dies ein ziemlicher Overkill ist. Daraufhin einigten wir uns für unsere Web-Anwendung auf JavaScript. Warum JavaScript? ----------------- - Einfachere Syntax - Bei Problemen gibt es viele Lösungen im Internet zu finden - Viele einfache Frameworks, die das Arbeiten erleichtern JavaScript-Anwendung: --------------------- Unsere JavaScript-Anwendung besteht aus: - index.html - index.mjs - styles.css .. csv-table:: :header: "File", "Anwendungszweck" "index.html", "Die `index.html` ist die Einstiegsdatei einer Webanwendung. Sie enthält den HTML-Code, der von einem Webbrowser geladen wird, wenn ein Benutzer die Anwendung besucht. Die `index.html` definiert die Struktur der Anwendung, verweist auf Skripte und Ressourcen und enthält den sichtbaren Inhalt." "index.mjs", "Eine `index.mjs` Datei ist eine Einstiegsdatei, die in der Regel als Hauptprogramm in einem JavaScript-Projekt verwendet wird. In dieser Datei werden in der Regel alle notwendigen Module importiert und konfiguriert, um eine Anwendung oder ein Skript auszuführen. Die `index.mjs` Datei ist also das Herzstück eines JavaScript-Projekts und stellt sicher, dass alle notwendigen Komponenten miteinander verbunden sind und reibungslos zusammenarbeiten." "styles.css", "Eine `styles.css` Datei wird verwendet, um das Aussehen einer Webseite zu gestalten und das Layout von HTML-Elementen zu definieren. In der `styles.css` Datei werden CSS-Regeln definiert, die bestimmen, wie die verschiedenen Elemente auf der Webseite dargestellt werden sollen." In der Doku werde ich nur auf das `index.mjs`-File eingehen, da diese die wichtigste und interessanteste aller Dateien ist. Verschiedene HTML-Pages ----------------------- .. code-block:: javascript const LOGIN_PAGE = `

RFID Authenticator

Welcome back





Not a member? Sign up!

`; const SIGNUP_PAGE = `

RFID Authenticator

Signing you up!



Back to Login
`; const SCAN_VIEW = `

Scanning Card ...

Hold your RFID Card to the reader

icon
`; *LOGIN_PAGE* :raw-html:`
` Die LOGIN_PAGE-Konstante ist ein String, der den HTML-Code für die Login-Seite des RFID-Authentifizierers enthält. Die Login-Seite enthält ein Formular, in das Benutzer ihren Benutzernamen und ihr Passwort eingeben können, mit der Option, sich über einen RFID-Scanner anzumelden. Die Seite enthält auch einen Link für Benutzer, die sich für den Service anmelden möchten. *SIGNUP_PAGE* :raw-html:`
` Die SIGNUP_PAGE-Konstante ist ein String, der den HTML-Code für die Registrierungsseite des RFID-Authentifizierers enthält. Die Registrierungsseite enthält ein Formular, in das Benutzer ein neues Konto mit Benutzernamen und Passwort erstellen können. Die Seite enthält auch einen Link für Benutzer, die zur Login-Seite zurückkehren möchten. *SCAN_VIEW* :raw-html:`
` Die SCAN_VIEW-Konstante ist ein String, der den HTML-Code für das Overlay enthält, das angezeigt wird, wenn ein Benutzer versucht, sich über einen RFID-Scanner anzumelden. Das Overlay enthält Anweisungen für den Benutzer, um ihre RFID-Karte an den Leser zu halten, zusammen mit einem Bild eines RFID-Symbols. Wenn der Benutzer seine RFID-Karte innerhalb von 60 Sekunden nicht scannt, wird eine Fehlermeldung angezeigt. presentLogin()-Funktion ------------------------ .. code-block:: javascript function presentLogin() { user = ""; console.log("Presenting Login view"); document.getElementById("login").innerHTML = LOGIN_PAGE; var btnLogin = document.getElementById("do-login"); btnLogin.onclick = function(){ user = document.getElementById("username").value; let creds = { "name": document.getElementById("username").value, "checksum": document.getElementById("password").value, }; console.log("Fetchin /auth/pw with " + JSON.stringify(creds)) // TODO: check Database fetch("/auth/pw", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(creds), }).then(resp => { let status = resp.status; if (status == 200) { presentMainPage(); } else { console.error("Wrong password!"); document.getElementById("password").value = ""; } }).catch(err => { console.error("Something went wrong: " + err); document.getElementById("password").value = ""; }); } var btnRFIDScan = document.getElementById("scan-rfid") btnRFIDScan.onclick = function(){ console.log("Listening to RFID scanner ..."); state = REQUESTING_ID; document.getElementById("rfid-overlay").innerHTML = SCAN_VIEW; // 60 Sekunden Zeit die RFID abzuscannen setTimeout(function() { if (state === REQUESTING_ID) { state = REQUESTING_NOTHING; document.getElementById("rfid-overlay").innerHTML = ""; document.getElementById("wrongRFID").innerHTML = TIMEOUT_ERR; } }, 60000); } var btnSignUp = document.getElementById("do-signup"); btnSignUp.onclick = presentSignUp; } Die Funktion presentLogin() wird aufgerufen, um die Login-Seite anzuzeigen und dem Benutzer zu ermöglichen, sich anzumelden. Sie enthält eine Event-Listener-Funktion, die aufgerufen wird, wenn der Benutzer auf die Schaltfläche "Anmelden" klickt, um die eingegebenen Anmeldeinformationen zu überprüfen. Wenn der Benutzer auf die Schaltfläche "RFID-Scan" klickt, wird ein Overlay angezeigt, das den Benutzer auffordert, seine RFID-Karte zu scannen. Wenn der Benutzer innerhalb von 60 Sekunden keine Karte scannt, wird eine Fehlermeldung angezeigt. Schließlich gibt es einen Listener für die Schaltfläche "Registrieren", der den Benutzer zur Registrierungsseite weiterleitet. presentSignUp()-Funktion ------------------------ .. code-block:: javascript function presentSignUp() { console.log("Presenting Sign Up view"); document.getElementById("login").innerHTML = SIGNUP_PAGE; var btnSignUp = document.getElementById("do-signup"); btnSignUp.onclick = function(){ let creds = { "name": document.getElementById("username").value, "checksum": document.getElementById("password").value, }; console.log("Fetching /auth/users with " + JSON.stringify(creds)) // TODO: check Database fetch("/auth/users", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(creds), }).then(resp => { let status = resp.status; if (status == 200) { presentMainPage(); } else { console.error("Something went wrong when creating a user: " + resp.text); } }).catch(err => { console.error("Something went wrong: " + err); }); } var btnLogout = document.getElementById("do-logout"); btnLogout.onclick = presentLogin; } Die Funktion presentLogin() wird aufgerufen, um die Login-Seite anzuzeigen und dem Benutzer zu ermöglichen, sich anzumelden. Sie enthält eine Event-Listener-Funktion, die aufgerufen wird, wenn der Benutzer auf die Schaltfläche "Anmelden" klickt, um die eingegebenen Anmeldeinformationen zu überprüfen. Wenn der Benutzer auf die Schaltfläche "RFID-Scan" klickt, wird ein Overlay angezeigt, das den Benutzer auffordert, seine RFID-Karte zu scannen. Wenn der Benutzer innerhalb von 60 Sekunden keine Karte scannt, wird eine Fehlermeldung angezeigt. Schließlich gibt es einen Listener für die Schaltfläche "Registrieren", der den Benutzer zur Registrierungsseite weiterleitet. handleWebsocket(data)-Funktion ------------------------------ .. code-block:: javascript function handleWebsocket(data) { console.log(data); const prev_state = state; state = REQUESTING_NOTHING; if (prev_state == REQUESTING_ID) { if (data.RfidUser) { console.log(data.RfidUser); console.log(data.RfidUser.checksum) console.log(data.RfidUser.name) document.getElementById("username").value = data.RfidUser.name; document.getElementById("password").value = data.RfidUser.checksum; document.getElementById("rfid-overlay").innerHTML = ""; document.getElementById("wrongRFID").innerHTML = ""; } else if (data.RfidUID) { console.log("Got RfidUID, ignoring ...") } else { document.getElementById("wrongRFID").innerHTML = WRONG_RFID; console.log("ERROR from RFID Reader."); } } else if (prev_state == REQUESTING_NEW_ID) { if (data.RfidUID || data.RfidUser) { if (data.RfidUser && data.RfidUser.name != user) { let err = "Not adding RfidUID to user " + user + ", since another user already uses this PICC." console.log(err); document.getElementById("rfid-overlay").innerHTML = ""; document.getElementById("wrongRFID").innerHTML = err; return; } console.log("Adding RfidUID to user "+ user); let rfid_uid = data.RfidUID ? data.rfid_uid : data.RfidUser.rfid_uid; let creds = { "name": user, "checksum": "", "rfid_uid": rfid_uid }; fetch("/auth/add_rfid", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(creds), }).then(resp => { console.log(resp); if (resp.status == 200) { console.log("Successfully added RFID Card to user " + user); document.getElementById("rfid-overlay").innerHTML = ""; document.getElementById("wrongRFID").innerHTML = ""; } else { console.err("Something went wrong when setting RFID Card: " + resp.text); document.getElementById("wrongRFID").innerHTML = "Error: " + resp.text; } }); } else { console.log("ERROR from RFID Reader."); document.getElementById("rfid-overlay").innerHTML = ""; document.getElementById("wrongRFID").innerHTML = "Error: " + data.RfidERR; } } else { // TODO: HTML Render console.log("Got payload, but ignoring since state is not active"); } } presentLogin() let url = new URL("/auth/rfid", window.location.href); // http => ws // https => wss url.protocol = url.protocol.replace("http", "ws"); let ws = new WebSocket(url.href); ws.onmessage = (ev) => { const data = JSON.parse(ev.data); handleWebsocket(data); } Am Endpunkt *ws://{IP_ADDRESS}:8080/auth/rfid* wird das Callback handleWebsocket bei Websocket-Upgrades integriert. Das Callback, das dadruch aufgerufen wird, muss dann anhand des States festlegen, was unternommen wird. Die Funktion handleWebsocket() wird bei jeder Nachricht auf dem Websocket ws ausgeführt und verarbeitet die empfangenen Daten. Zunächst wird der vorherige Zustand prev_state gespeichert und auf REQUESTING_NOTHING gesetzt. Wenn der vorherige Zustand REQUESTING_ID ist, werden die empfangenen Daten verarbeitet und in das Login-Formular eingegeben, falls es sich dabei um die Benutzerinformationen für das RFID handelt. Ansonsten wird eine Fehlermeldung ausgegeben. Wenn der vorherige Zustand REQUESTING_NEW_ID ist, werden die RFID-Informationen für den angemeldeten Benutzer hinzugefügt. Wenn weder RfidUser noch RfidUID in den Daten enthalten sind, wird eine Fehlermeldung ausgegeben. Die presentLogin()-Funktion wird zu Beginn aufgerufen und initialisiert die Benutzeroberfläche für die Anmeldung.