fix: Favicon 502, QR Feld cr/mdcDeviceId Support und 401 Session-Handling
All checks were successful
Build and Deploy / build (push) Successful in 55s

This commit is contained in:
DanielS
2026-09-22 22:35:15 +02:00
parent 0dfbf5c013
commit f96c51809d
3 changed files with 23 additions and 13 deletions

View File

@@ -54,7 +54,9 @@ async function processQrSetup(qrPayload) {
throw new Error('Ungültiger QR-Code-Typ: $type muss "ww-setup" sein');
}
const { url, servicepasslink, mdcDeviceId } = data;
const { url, servicepasslink } = data;
const mdcDeviceId = data.mdcDeviceId || data.cr || data.cashRegisterNumber || '1';
if (!url || !servicepasslink) {
throw new Error('Fehlende Pflichtfelder (url oder servicepasslink) im QR-Code');
}
@@ -86,10 +88,10 @@ async function processQrSetup(qrPayload) {
upsert.run('pos_url', cleanUrl);
upsert.run('pos_pass_id', passId);
upsert.run('pos_app_id', appId);
if (mdcDeviceId) upsert.run('pos_cash_register', String(mdcDeviceId));
upsert.run('pos_cash_register', String(mdcDeviceId));
db.prepare('INSERT INTO sync_logs (status, message) VALUES (?, ?)')
.run('SUCCESS', `QR-Code Handshake erfolgreich. Pass-ID: ${passId}, Kasse: ${mdcDeviceId || '1'}`);
.run('SUCCESS', `QR-Code Handshake erfolgreich. Pass-ID: ${passId}, Kasse: ${mdcDeviceId}`);
// 3. Direkt automatischen Initial-Sync ausführen
let syncResult = null;
@@ -104,7 +106,7 @@ async function processQrSetup(qrPayload) {
url: cleanUrl,
passId,
appId,
mdcDeviceId: mdcDeviceId || '1',
mdcDeviceId: String(mdcDeviceId),
syncResult
};
}
@@ -231,7 +233,6 @@ async function parseAndStoreZip(buffer) {
if (Array.isArray(item.prices) && item.prices.length > 0) {
const vk1Obj = item.prices.find(p => String(p.level) === '1') || item.prices[0];
if (vk1Obj && vk1Obj.value !== undefined) {
// Deutsches Zahlenformat "2,86" -> 2.86
preis1 = parseFloat(String(vk1Obj.value).replace(',', '.'));
}
} else if (typeof item.prices === 'object' && item.prices !== null) {
@@ -267,7 +268,6 @@ async function parseAndStoreZip(buffer) {
try { parsed = await parser.parseStringPromise(rawContent); } catch(e) {}
const root = parsed.root || parsed;
// Warengruppen
let groups = root.ProductGroups?.ProductGroup || root.productGroups?.productGroup || [];
if (!Array.isArray(groups)) groups = [groups];
for (const g of groups) {
@@ -276,7 +276,6 @@ async function parseAndStoreZip(buffer) {
if (id) { insertGroup.run(id, name, 0); groupsCount++; }
}
// Artikel
let items = root.Products?.Product || root.products?.product || [];
if (!Array.isArray(items)) items = [items];
for (const item of items) {

View File

@@ -7,7 +7,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Space+Grotesk:wght@600;700&display=swap" rel="stylesheet">
<!-- jsQR & Html5Qrcode für maximale Kompatibilität -->
<!-- jsQR für maximale Kompatibilität -->
<script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script>
<style>
:root {
@@ -301,7 +301,7 @@
<input type="password" id="cfg-pass-id" placeholder="Passwort aus ServicePass" />
</div>
<div class="form-group">
<label>Kassennummer (cashRegisterNumber / MDCDEVICEID)</label>
<label>Kassennummer (cashRegisterNumber / MDCDEVICEID / cr)</label>
<input type="text" id="cfg-register" placeholder="z.B. 1" />
</div>
</div>
@@ -436,6 +436,7 @@
// Config laden & speichern
async function loadConfig() {
const res = await fetch('/api/admin/config');
if (res.status === 401) return showLogin();
if (res.ok) {
const cfg = await res.json();
document.getElementById('cfg-url').value = cfg.pos_url || '';
@@ -445,8 +446,7 @@
}
}
// ==================== Robuster jsQR Scanner ====================
// Kamera Scanner
document.getElementById('btn-toggle-camera').addEventListener('click', () => {
if (videoStream) {
stopCamera();
@@ -557,6 +557,10 @@
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ qrContent })
});
if (res.status === 401) {
showAlert('login-alert', 'Sitzung abgelaufen. Bitte neu anmelden.', true);
return showLogin();
}
const data = await res.json();
if (res.ok) {
showAlert('dash-alert', 'QR-Code Handshake erfolgreich! Parameter übernommen & aktiviert.');
@@ -591,6 +595,7 @@
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (res.status === 401) return showLogin();
const data = await res.json();
if (res.ok) {
showAlert('dash-alert', 'Einstellungen erfolgreich gespeichert!');
@@ -609,6 +614,7 @@
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type })
});
if (res.status === 401) return showLogin();
const data = await res.json();
if (res.ok) {
showAlert('dash-alert', `Sync erfolgreich: ${data.result.productsCount} Artikel importiert.`);
@@ -654,6 +660,7 @@
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(mockData)
});
if (res.status === 401) return showLogin();
if (res.ok) {
showAlert('dash-alert', 'Musterartikel erfolgreich eingespielt!');
loadData();
@@ -663,6 +670,7 @@
// Daten & Logs laden
async function loadData() {
const res = await fetch('/api/admin/data');
if (res.status === 401) return showLogin();
if (res.ok) {
const data = await res.json();

View File

@@ -17,6 +17,9 @@ app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/Getraenkekarte.pdf', express.static(path.join(__dirname, 'Getraenkekarte.pdf')));
// Favicon dummy damit kein 502/404 geloggt wird
app.get('/favicon.ico', (req, res) => res.status(204).end());
// JWT Auth Middleware
function getSecret() {
const row = db.prepare('SELECT value FROM config WHERE key = ?').get('jwt_secret');
@@ -86,8 +89,8 @@ app.post('/api/admin/login', (req, res) => {
const hash = hashRow ? hashRow.value : '';
if (username === adminUser && bcrypt.compareSync(password, hash)) {
const token = jwt.sign({ user: username }, getSecret(), { expiresIn: '7d' });
res.cookie('admin_token', token, { httpOnly: true, maxAge: 7 * 24 * 60 * 60 * 1000, sameSite: 'lax' });
const token = jwt.sign({ user: username }, getSecret(), { expiresIn: '30d' });
res.cookie('admin_token', token, { httpOnly: true, maxAge: 30 * 24 * 60 * 60 * 1000, sameSite: 'lax' });
return res.json({ success: true, message: 'Erfolgreich eingeloggt' });
}