import "server-only"; import QRCode from "qrcode"; import { env } from "@/lib/env"; /** * QR-Codes für Geräte. * * Der Code enthält eine kurze URL auf die eigene Installation: * * https:///g/ * * Bewusst die Inventarnummer und nicht die UUID: Die Nummer steht ohnehin * lesbar auf dem Etikett, ist kurz genug für einen groben Code mit dicken * Modulen, und wer den Code mit einem beliebigen Scanner liest, sieht sofort, * um welches Gerät es geht. Eine UUID wäre 36 Zeichen lang und für niemanden * lesbar. * * Die URL ist keine Berechtigung: /g/ leitet nur weiter, und die Zielseite * verlangt eine Anmeldung wie jede andere auch. Wer den Aufkleber fotografiert, * bekommt dadurch keinen Zugang. */ /** Fehlerkorrektur. "M" verträgt rund 15 % Verlust — genug für ein Etikett, * das im Geräteraum Staub und Schrammen abbekommt. */ const FEHLERKORREKTUR = "M" as const; export function qrZiel(inventarnummer: string): string { const basis = env.APP_URL.replace(/\/+$/, ""); return `${basis}/g/${encodeURIComponent(inventarnummer)}`; } /** * QR-Code als SVG. SVG statt PNG, weil der Etikettenbogen als PDF gerendert * wird — dort skaliert eine Vektorgrafik ohne Treppenkanten. */ export async function qrAlsSvg(inventarnummer: string, groesseMm = 20): Promise { const svg = await QRCode.toString(qrZiel(inventarnummer), { type: "svg", errorCorrectionLevel: FEHLERKORREKTUR, margin: 0, width: Math.round(groesseMm * 8), }); // qrcode setzt feste Pixelmaße; für das PDF sollen Millimeter gelten. return svg .replace(/width="[^"]+"/, `width="${groesseMm}mm"`) .replace(/height="[^"]+"/, `height="${groesseMm}mm"`); } /** QR-Code als Data-URI. Für die Anzeige in der Gerätedetailseite. */ export async function qrAlsDataUri(inventarnummer: string, groessePx = 256): Promise { return QRCode.toDataURL(qrZiel(inventarnummer), { errorCorrectionLevel: FEHLERKORREKTUR, margin: 1, width: groessePx, color: { dark: "#000000ff", light: "#ffffffff" }, }); } /** * QR-Code aus beliebigem Text. * * Die Funktionen darüber setzen alle eine Inventarnummer voraus und bauen * daraus die Geräteadresse. Ab Phase 4 gibt es Codes, die keine Adresse * enthalten, sondern eine signierte Anmeldemarke — die soll unverändert im * Code stehen. */ export async function qrTextAlsDataUri(text: string, groessePx = 256): Promise { return QRCode.toDataURL(text, { errorCorrectionLevel: FEHLERKORREKTUR, margin: 1, width: groessePx, color: { dark: "#000000ff", light: "#ffffffff" }, }); } /** * Die Auswertung gescannter Codes steht in qr-parser.ts — sie läuft im * Browser und darf deshalb nicht in einem "server-only"-Modul liegen. */ export { inventarnummerAusScan } from "@/lib/stammdaten/qr-parser";