3.3.2. Web Frontend

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.

3.3.2.1. Warum JavaScript?

  • Einfachere Syntax

  • Bei Problemen gibt es viele Lösungen im Internet zu finden

  • Viele einfache Frameworks, die das Arbeiten erleichtern

3.3.2.2. JavaScript-Anwendung:

Unsere JavaScript-Anwendung besteht aus:

  • index.html

  • index.mjs

  • styles.css

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.

3.3.2.3. Verschiedene HTML-Pages

const LOGIN_PAGE = `
    <div class="head">
        <h1 class="company">RFID Authenticator</h1>
    </div>
    <p class="msg">Welcome back</p>
    <div class="form">
        <form>
            <input type="text" placeholder="Username" class="text" id="username" required><br>
            <input type="password" placeholder="Password" class="password" id="password"><br>
            <div id="wrongRFID"></div>
            <a href="#" class="btn-login" id="do-login">Login</a>
            <a href="#" class="rfid-login" id="scan-rfid">Use RFID Scanner</a>
        </form>
    </div>
    <br><br>
    <p class="msg">
        Not a member?
        <a href="#" id="do-signup">Sign up!</a>
    </p>
    <div id="wrongRFID"></div>
`;

const SIGNUP_PAGE = `
    <div class="head">
        <h1 class="company">RFID Authenticator</h1>
    </div>
    <p class="msg">Signing you up!</p>
    <div class="form">
        <form>
            <input type="text" placeholder="Username" class="text" id="username" required><br>
            <input type="password" placeholder="Password" class="password" id="password"><br>
            <a href="#" class="btn-signup" id="do-signup">Sign Up</a>
            <a href="#" class="btn-logout" id="do-logout">Back to Login</a>
        </form>
    </div>
`;

const SCAN_VIEW = `
    <div class='rfid-overlay-inner' id='rfid-overlay-inner'>
        <h2>Scanning Card ...</h2>
        <p> Hold your RFID Card to the reader</p>
        <div id="wrongRFID"></div>
        <img src="/img/rfid-icon-0.jpg" alt="icon" height="80px" width="80px"></img>
    </div>
`;

LOGIN_PAGE
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
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
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.

3.3.2.4. presentLogin()-Funktion

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.

3.3.2.5. presentSignUp()-Funktion

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.

3.3.2.6. handleWebsocket(data)-Funktion

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.