import {Fetcher} from "openapi-typescript-fetch" import {type paths} from "../api/schema" const fetcher = Fetcher.for() const list: HTMLDivElement = document.querySelector("#list")! const template: HTMLTemplateElement = document.querySelector("#template-door")! type DoorType = { id: string; label: string; state: "unlocked" | "locked" | "unknown" | "unlocking" | "locking"; batteryLow: boolean; unreachable: boolean; jammed: boolean; } type AuthType = { username: string; authorized: boolean; authenticated: boolean; until: Date | null; recentLogout: boolean; } const auth: AuthType = { username: "user", authorized: true, authenticated: false, until: null, recentLogout: false, } const apiError: { current: "serverError" | null; } = { current: null, } const doors: Array = [] function loadAuthFromLocalStorage() { const localAuth = JSON.parse(localStorage.getItem("auth") ?? "") auth.recentLogout = localAuth.recentLogout // set recentLogout true, if user was logged in before auth.authenticated = localAuth.authenticated auth.authorized = localAuth.authorized auth.until = localAuth.until ? new Date(localAuth.until) : null auth.username = localAuth.username } async function checkUser() { const getUserInfo = fetcher.path("/api/user-info/").method("get").create() try { const {status, data: userInfo} = await getUserInfo({}) apiError.current = null auth.authenticated = userInfo.is_logged_in auth.authorized = true auth.until = userInfo.guaranteed_session_until ? new Date(userInfo.guaranteed_session_until) : null auth.username = userInfo.user_info?.username ?? "" auth.recentLogout = false } catch (e) { // check which operation threw the exception if (e instanceof getUserInfo.Error) { const error = e.getActualType() if (error.status === 401) { if (!auth.recentLogout) auth.recentLogout = auth.authenticated // set recentLogout true, if user was logged in before auth.authenticated = false auth.authorized = false auth.until = null auth.username = "" } else if (error.status >= 500 && error.status < 600) { apiError.current = "serverError" } else { console.error("unknown error:", error) } } } finally { localStorage.setItem("auth", JSON.stringify(auth)) refresh() } } async function fetchDoors() { const getDoors = fetcher.path("/api/locks/").method("get").create() try { const {status, data: doorInfo} = await getDoors({}) apiError.current = null while (doors.length) { doors.pop() } doorInfo.forEach(door => { let state: DoorType["state"] = "unknown" switch (door.status.activity_state) { case "locking": case "unlocking": case "unknown": state = door.status.activity_state break case "stable": state = door.status.lock_state } doors.push({ id: door.name, // TODO: replace by actual ID label: door.name, state: state, batteryLow: door.status.is_low_battery, unreachable: door.status.is_unreachable, jammed: door.status.is_error_jammed, }) }) } catch (e) { // check which operation threw the exception if (e instanceof getDoors.Error) { const error = e.getActualType() if (error.status === 401) { console.log("unauthorized") } else if (error.status >= 500 && error.status < 600) { apiError.current = "serverError" } else { console.error("unknown error:", error) } } } finally { refresh() } } function setDoorInfo(doorElement: HTMLDivElement, door: DoorType) { const labelElement: HTMLDivElement = doorElement.querySelector("[data-label]")! const buttonElements: Array = Array.from(doorElement.querySelectorAll("button")) doorElement.dataset.id = door.id doorElement.dataset.state = door.state if (auth.authenticated && auth.authorized) { doorElement.dataset.active = "" } else { delete doorElement.dataset.active } if (door.batteryLow) { doorElement.dataset.battery = "" } else { delete doorElement.dataset.battery } if (door.unreachable) { doorElement.dataset.unreachable = "" } else { delete doorElement.dataset.unreachable } if (door.jammed) { doorElement.dataset.jammed = "" } else { delete doorElement.dataset.jammed } labelElement.innerHTML = door.label buttonElements.forEach(button => { button.disabled = ["unlocking", "locking"].includes(door.state) || apiError.current !== null }) } function refresh() { for (const door of doors) { let doorElement: HTMLDivElement | null = list.querySelector(`[data-id='${door.id}']`) if (doorElement) { setDoorInfo(doorElement, door) continue } const targetElement = document.importNode(template.content, true) doorElement = targetElement.querySelector("[data-state]")! setDoorInfo(doorElement, door) list.appendChild(targetElement) } const alertUnauthenticated: HTMLDivElement = document.querySelector("#alert-unauthenticated")! const alertUnauthorized: HTMLDivElement = document.querySelector("#alert-unauthorized")! const alertLoggedout: HTMLDivElement = document.querySelector("#alert-loggedout")! const alertServerError: HTMLDivElement = document.querySelector("#alert-serverError")! const alertDivider: HTMLDivElement = document.querySelector("#alert-divider")! const badgeUsername: HTMLDivElement = document.querySelector("#badge-username")! const buttonLogin: HTMLDivElement = document.querySelector("#button-login")! const usernameElement: HTMLSpanElement = badgeUsername.querySelector("#username")! alertUnauthenticated.classList.toggle("hidden", auth.authenticated) alertUnauthorized.classList.toggle("hidden", !auth.authenticated || auth.authorized) alertLoggedout.classList.toggle("hidden", !auth.recentLogout) alertServerError.classList.toggle("hidden", apiError.current !== "serverError") alertDivider.classList.toggle("hidden", auth.authenticated && auth.authorized && !auth.recentLogout && apiError.current === null) badgeUsername.classList.toggle("hidden", !auth.authenticated) usernameElement.innerHTML = auth.username buttonLogin.classList.toggle("hidden", auth.authenticated) } loadAuthFromLocalStorage() fetchDoors() checkUser() document.addEventListener("loadeddata", () => { refresh() })