const { createRequestAuthHeaders } = require('#src/request-auth'); const { fetchPlayerRegistrations } = require('#src/data/player-registry'); const { renderView } = require('../view'); module.exports = function registerOnboardingRoutes(app, deps) { const pool = deps.pool; const playerInternalBaseUrl = String(deps.playerInternalBaseUrl || '').replace(/\/$/, ''); const requirePermission = deps.requirePermission; app.get('/pairing', requirePermission('pairing.allow'), async function (req, res, next) { try { const [screens] = await pool.query('SELECT id, name, slug FROM d_screens ORDER BY name ASC, id ASC'); res.send(renderView('onboarding/pair', { title: 'Pair player', active: 'pairing', currentUser: req.currentUser, clientId: String(req.query.clientId || '').trim(), pairingCode: String(req.query.code || '').trim(), screens: screens, scripts: ['vendor/jsqr/jsQR.js', 'js/onboarding/pair.js?v=' + encodeURIComponent(require('#root/package.json').version)] })); } catch (error) { next(error); } }); app.post('/pairing', requirePermission('pairing.allow'), async function (req, res) { const pairingCode = String(req.body && req.body.pairingCode || '').trim(); const clientId = String(req.body && req.body.clientId || '').trim(); const clientName = String(req.body && req.body.clientName || '').trim(); const screenSlug = String(req.body && req.body.screenSlug || '').trim(); if (!pairingCode || !clientName || !screenSlug) { return res.status(400).json({ error: 'All pairing fields are required.' }); } try { let targetBaseUrl = playerInternalBaseUrl; let deviceId = ''; let resolvedClientId = clientId; { const resolvePath = '/api/onboarding/resolve?pairingCode=' + encodeURIComponent(pairingCode); const resolveAuthHeaders = createRequestAuthHeaders({ method: 'GET', pathname: '/api/onboarding/resolve' }); const resolveResponse = await fetch(`${targetBaseUrl}${resolvePath}`, { method: 'GET', headers: Object.assign({ Accept: 'application/json' }, resolveAuthHeaders) }); const resolveBody = await resolveResponse.text(); let resolved = null; try { resolved = JSON.parse(resolveBody); } catch (_error) {} if (!resolveResponse.ok || !resolved || !resolved.deviceId) { const registrations = await fetchPlayerRegistrations(pool); const remoteRegistrations = registrations.filter(function (player) { const internalUrl = String(player && player.internal_base_url || '').replace(/\/$/, ''); return internalUrl && internalUrl !== playerInternalBaseUrl; }); if (!remoteRegistrations.length) { return res.status(resolveResponse.status || 401).type(resolveResponse.headers.get('content-type') || 'application/json').send(resolveBody || JSON.stringify({ error: 'Unable to resolve kiosk pairing code.' })); } for (const remoteRegistration of remoteRegistrations) { targetBaseUrl = String(remoteRegistration.internal_base_url).replace(/\/$/, ''); const bridgeResolveHeaders = createRequestAuthHeaders({ method: 'GET', pathname: '/api/onboarding/resolve' }); const bridgeResolveResponse = await fetch(`${targetBaseUrl}/api/onboarding/resolve?pairingCode=${encodeURIComponent(pairingCode)}`, { method: 'GET', headers: Object.assign({ Accept: 'application/json' }, bridgeResolveHeaders) }); const bridgeResolveBody = await bridgeResolveResponse.text(); try { resolved = JSON.parse(bridgeResolveBody); } catch (_error) { resolved = null; } if (bridgeResolveResponse.ok && resolved && resolved.deviceId) { break; } } if (!resolved || !resolved.deviceId) { return res.status(401).json({ error: 'A valid kiosk pairing code is required.' }); } } if (resolved && resolved.deviceId) { deviceId = String(resolved.deviceId).trim(); resolvedClientId = String(resolved.clientId || resolvedClientId || '').trim(); } } if (!resolvedClientId) { return res.status(400).json({ error: 'Client ID is required.' }); } const payload = { clientId: resolvedClientId, pairingCode: pairingCode, clientName: clientName, screenSlug: screenSlug }; const authHeaders = createRequestAuthHeaders({ method: 'POST', pathname: '/api/onboarding', body: payload }); const response = await fetch(`${targetBaseUrl}/api/onboarding`, { method: 'POST', headers: Object.assign({ 'Content-Type': 'application/json', Accept: 'application/json' }, authHeaders), body: JSON.stringify(payload) }); const responseBody = await response.text(); res.status(response.status).type(response.headers.get('content-type') || 'application/json').send(responseBody); } catch (error) { res.status(502).json({ error: error && error.message ? error.message : 'Player unavailable.' }); } }); };