import { Html5QrcodeScanner, Html5QrcodeSupportedFormats } from "html5-qrcode"; import { ed448 } from "@noble/curves/ed448"; import { Buffer } from "buffer"; var currentlyAddingItemByTag = new Map(); var received_items = {}; var config; var scanner = new Html5QrcodeScanner("reader",{ fps: 10, qrbox: { width: 250, height: 250 }, showTorchButtonIfSupported: true, formatsToSupport: [ Html5QrcodeSupportedFormats.QR_CODE ], //there are many other types of codes on the labels we do not want rememberLastUsedCamera: true, }); var scanner_audio = new Audio("/beep.wav"); scanner_audio.load(); /* * Helper functions */ function htoa(h) { return Uint8Array.from(Buffer.from(h,"hex")); } async function fetchWithAuth(input, init) { if(init == undefined || init == null) { init = {}; } if(init["headers"] == null) { init["headers"] = {}; } if(!window.sessionStorage.getItem("access_token")) { await login(); } init["headers"]["Authorization"] = "Bearer " + window.sessionStorage.getItem("access_token"); let response = await fetch(input, init); while(response.status == 401) { console.log("Fetch with auth failed for", input); await login(); init["headers"]["Authorization"] = "Bearer " + window.sessionStorage.getItem("access_token"); response = await fetch(input, init); } return response; } async function checkResponseForError(response, message) { if (response.status !== 200) { let content = await response.text(); console.log("Got Response Error:", message, response); alert(message + "; Status: " + response.status + "; Content: " + content); return false; } return true; } async function getConfig() { if (config == undefined) { var config_request = await fetch("config.json"); console.log("Loading config"); if(await checkResponseForError(config_request, "Could not load config")) { config = await config_request.json(); console.log("Loaded config"); } } } async function login() { console.log("Performing login"); let loginSuccess = false; do { console.log("Prompting user for shared secret."); var password = prompt("provide the shared secret"); var form = new FormData(); form.append("grant_type", "password"); form.append("username", "worker"); form.append("password", password); console.log("Requesting token."); var response = await fetch(config.backend_url + "/token", { method: "POST", body: form, }); if (await checkResponseForError(response, "Login failed")) { var result = await response.json(); console.log("Got token"); window.sessionStorage.setItem("access_token", result.access_token); loginSuccess = true; } } while(!loginSuccess); } function updateItemsView() { console.log("Updating items view"); var items = document.querySelector("#items"); items.innerHTML = ""; for (var item in received_items) { var li = document.createElement("li"); li.innerHTML = "<div id=\"item_" + received_items[item].item.uuid + "\" class=\"alert alert-success\" role=\"alert\">" + received_items[item].item.tag + " (" + received_items[item].item.addressee + ", " + received_items[item].item.team + ")" + " <span class=\"badge badge-light\">"+received_items[item].num+"</span>"+ " <button class=\"btn btn-primary\" onclick=\"lib.increaseItemCount('" + received_items[item].item.uuid + "')\">+</button>" + " <button class=\"btn btn-primary\" onclick=\"lib.decreaseItemCount('" + received_items[item].item.uuid + "')\">-</button></div>"; items.appendChild(li); } } /* * various add Items functions */ export async function onTagTextUpdate(element) { // A character was typed if(element.value.length == 0) { element.setCustomValidity(""); element.classList.remove("is-invalid"); } else if(element.value.length != 6) { element.setCustomValidity("Code must be 6 chars long."); element.classList.add("is-invalid"); return; } else { tagEntered(element); } } export async function onTagInputChanged(element) { // Element lost focus or user pressed enter if(element.value.length == 6) { tagEntered(element); } else { element.reportValidity(); } } async function tagEntered(element) { let tag = element.value; console.log("Tag Entered", tag); if(currentlyAddingItemByTag.has(tag)) { return; } currentlyAddingItemByTag.set(tag, ""); let succ = await addItemByTag(element.value); if(succ) { element.classList.remove("is-invalid"); element.value = ""; element.setCustomValidity(""); } else { element.setCustomValidity("Unknown item"); element.classList.add("is-invalid"); } element.reportValidity(); currentlyAddingItemByTag.delete(tag); } export async function addItemByTag(tag) { if (tag.length != 6) { throw "Internal Error: Tag not length 6."; } tag = tag.toLowerCase(); console.log("Trying to add item by tag:", tag); var response = await fetchWithAuth(config.backend_url + "/tag/" + tag); if (response.status == 200) { var tracking_item = await response.json(); if (tracking_item.uuid in received_items) { received_items[tracking_item.uuid].num += 1; } else { received_items[tracking_item.uuid] = { "num":1, "item": tracking_item }; } updateItemsView(); scanner_audio.play(); console.log("Added item by tag"); return true; } else if(response.status == 404) { console.log("Item not found"); return false; } else { checkResponseForError(response, "Failed to get item from backend"); return false; } } async function handleQrError(msg, err){ if(err.type == 2 /*Not found*/ || err.errorMessage.includes("error = NotFoundException")) { return; } alert("Qrcode Error: " + msg + " (" + err + ")"); console.log("Qrcode Error: ", msg, err); } async function handleItemScanResult(item_uuid) { if (item_uuid.length == 36) { console.log("Trying to add item by uuid (scanned)"); if (item_uuid in received_items) { received_items[item_uuid].num += 1; console.log("Increased count of existing item uuid"); updateItemsView(); scanner_audio.play(); } else { received_items[item_uuid] = { "num":1, "item": null }; var response = await fetch(config.backend_url + "/item/"+item_uuid); if (response.status == 200) { var tracking_item = await response.json(); received_items[item_uuid] = { "num":1, "item": tracking_item }; updateItemsView(); scanner_audio.play(); console.log("Added item by uuid."); } else if(response.status == 404) { alert("Item not found"); console.log("Item not found"); } else { checkResponseForError(response, "Failed to get item from backend"); } } } else { alert("QR Code is not a bgp checkin code."); } } export async function addItemScan() { console.log("Starting scanner"); scanner.render(handleItemScanResult, handleQrError); document.getElementById("startScan").style.display = "none"; document.getElementById("stopScan").style.display = "inline-block"; } export async function stopScanningItem() { console.log("Stopping scanner"); scanner.clear(); document.getElementById("startScan").style.display = "inline-block"; document.getElementById("stopScan").style.display = "none"; } export async function addItemImage(e) { console.log("Adding item by image"); var form = new FormData(); form.append("image", e.files[0]); var response = await fetchWithAuth(config.backend_url + "/item/register", { method: "POST", body: form }); if(await checkResponseForError(response, "Failed to save item")) { var tracking_item = await response.json(); received_items[tracking_item.uuid] = {"num": 1, "item": tracking_item}; updateItemsView(); scanner_audio.play(); console.log("Added item by image."); } } export async function decreaseItemCount(uuid) { if(received_items[uuid] == null) { console.log("Trying to decrease item count of unknown item", uuid); return; } console.log("Decreasing item count of", uuid); received_items[uuid]["num"] --; if(received_items[uuid]["num"] < 0) { received_items[uuid]["num"] = 0; } updateItemsView(); } export async function increaseItemCount(uuid) { if(received_items[uuid] == null) { console.log("Trying to increase item count of unknown item", uuid); return; } console.log("Increasing item count of", uuid); received_items[uuid]["num"] ++; updateItemsView(); } /* * Checkin the selected items */ export async function checkinItems() { console.log("Checking in items"); for (var item in received_items) { var response = await fetchWithAuth(config.backend_url + "/checkin", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ "item_uuid": received_items[item].item.uuid, "storage_name": document.querySelector("#storage_select").value, "num": received_items[item].num }) }); if(await checkResponseForError(response, "Failed to checkin item")) { var tracking_item = await response.json(); // eslint-disable-line no-unused-vars } } received_items = {}; updateItemsView(); } async function onRecevierScanSuccess(scannedCode) { var [uuid,signature] = scannedCode.split("/"); console.log("Receiver scan success"); scanner_audio.play(); await getConfig(); var response = await fetch(config.backend_url + "/item/"+uuid); if (response.status == 200) { var tracking_item = await response.json(); if (ed448.verify(htoa(signature),new TextEncoder().encode(tracking_item.uuid),htoa(tracking_item.verification))) { document.querySelector("#uuid").innerHTML = tracking_item.uuid; document.querySelector("#tag").innerHTML = tracking_item.tag; document.querySelector("#storage").innerHTML = tracking_item.storage; document.querySelector("#addressee").innerHTML = tracking_item.addressee; document.querySelector("#team").innerHTML = tracking_item.team; console.log("Receiver verify success"); } else { alert("Could not verify signature."); console.log("Could not verify signature."); } } else if(response.status == 404) { alert("Item not found."); } else { checkResponseForError(response, "Failed to get item from backend."); } } export async function scanReceiver() { console.log("Scanning receiver."); scanner.render(onRecevierScanSuccess, handleQrError); } export async function loadStorages(selectedStorage) { await getConfig(); console.log("Retrieving storages"); var response = await fetchWithAuth(config.backend_url + "/storages"); if(await checkResponseForError(response, "Failed to load storages.")) { var storages = await response.json(); var select = document.querySelector("#storage_select"); console.log("Got storages"); for (var i in storages) { var option = document.createElement("option"); option.textContent = storages[i].name; option.value = storages[i].name; select.appendChild(option); if (selectedStorage == storages[i].name) { select.value = selectedStorage; } } } }