// Screen route registration and dashboard wiring. const fs = require('fs'); module.exports = function registerScreensRoutes(app, deps) { const pool = deps.pool; const common = deps.common; const pages = deps.pages; const buildDashboardState = deps.buildDashboardState; const getScreenDeleteBlockMessage = deps.getScreenDeleteBlockMessage; const { buildPagination } = require('../../../lib/pagination'); const requirePermission = deps.requirePermission; const LIST_PAGE_SIZE = 25; const batTemplatePath = require.resolve('#root/scripts/pulse-signage-kiosk.bat'); const shTemplatePath = require.resolve('#root/scripts/pulse-signage-kiosk.sh'); const launcherDownloadPaths = { windows: '/downloads/kiosk/pulse-signage-kiosk.bat', linux: '/downloads/kiosk/pulse-signage-kiosk.sh' }; function normalizeTargetPlayerUrl(value) { const normalized = String(value || '').trim().replace(/\/$/, ''); if (!normalized) { return null; } if (!/^https?:\/\//i.test(normalized)) { return null; } return normalized; } function requireQueryPermission(readPermissionKey, editPermissionKey) { return function (req, res, next) { const permissionKey = req.query && req.query.edit ? editPermissionKey : readPermissionKey; return requirePermission(permissionKey)(req, res, next); }; } function applyConnectionCounts(screens, dashboardScreens) { const countsById = new Map(); const countsBySlug = new Map(); const dashboardScreenBySlug = new Map(); (dashboardScreens || []).forEach(function (screen) { const count = Number(screen && screen.player_connection_count || 0); const id = screen && screen.id !== undefined && screen.id !== null ? String(screen.id) : ''; const slug = String(screen && screen.slug || '').trim(); if (id) { countsById.set(id, count); } if (slug) { countsBySlug.set(slug, count); dashboardScreenBySlug.set(slug, screen); } if (id) { dashboardScreenBySlug.set(id, screen); } }); return (screens || []).map(function (screen) { const slug = String(screen && screen.slug || '').trim(); const id = screen && screen.id !== undefined && screen.id !== null ? String(screen.id) : ''; const dashboardScreen = dashboardScreenBySlug.get(slug) || dashboardScreenBySlug.get(id) || null; const playerConnectionCount = countsBySlug.has(slug) ? countsBySlug.get(slug) : countsById.get(id) || 0; return Object.assign({}, screen, { player_connection_count: playerConnectionCount, }); }); } async function buildPlayerUrl() { if (typeof common.fetchPlayerPublicBaseUrl !== 'function') { return null; } return common.fetchPlayerPublicBaseUrl(pool); } function buildLauncherContent(templatePath, playerUrl) { const template = fs.readFileSync(templatePath, 'utf8'); const normalizedPlayerUrl = String(playerUrl || '').trim(); if (!normalizedPlayerUrl) { return null; } if (template.indexOf('http://localhost:8081') === -1) { throw new Error(`Launcher template is missing the default player URL placeholder: ${templatePath}`); } return template.replace('http://localhost:8081', normalizedPlayerUrl); } function sendLauncherDownload(res, fileName, content) { res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); res.set('Pragma', 'no-cache'); res.attachment(fileName); res.type('text/plain; charset=utf-8'); res.send(content); } app.get('/screens', requireQueryPermission('screens.read', 'screens.update'), async function (req, res, next) { try { if (req.query.edit) { const screen = await common.fetchScreenById(pool, Number(req.query.edit)); if (!screen) { return res.status(404).send('Screen not found'); } screen.inUse = Boolean(await getScreenDeleteBlockMessage(pool, screen, deps.getScreenConnections)); const editData = await common.fetchScreenEditData(pool); return res.send(pages.renderScreenEditPage(screen, editData, req.query.message ? String(req.query.message) : '', req.currentUser)); } const page = Math.max(1, Math.floor(Number(req.query.page) || 1)); const search = common.getSearchQuery(req); const sort = common.getSortQuery(req); const direction = common.getSortDirectionQuery(req); const dashboardState = await buildDashboardState(pool); const data = await common.fetchScreensPage(pool, page, LIST_PAGE_SIZE, search, sort, direction); res.send(pages.renderScreensPage({ screens: applyConnectionCounts(data.screens || [], dashboardState.screens || []), pagination: buildPagination(data.totalItems, data.currentPage, 'page', { search: search, sort: sort, direction: direction }, LIST_PAGE_SIZE, 'screens', 'Screen pages') }, req.query.message ? String(req.query.message) : '', req.currentUser)); } catch (error) { next(error); } }); app.get('/screens/:id/edit', requirePermission('screens.update'), async function (req, res, next) { try { const screen = await common.fetchScreenById(pool, Number(req.params.id)); if (!screen) { return res.status(404).send('Screen not found'); } screen.inUse = Boolean(await getScreenDeleteBlockMessage(pool, screen, deps.getScreenConnections)); const editData = await common.fetchScreenEditData(pool); return res.send(pages.renderScreenEditPage(screen, editData, req.query.message ? String(req.query.message) : '', req.currentUser)); } catch (error) { next(error); } }); app.get('/downloads/kiosk/pulse-signage-kiosk.bat', requirePermission('screens.update'), async function (_req, res, next) { try { const playerUrl = normalizeTargetPlayerUrl(_req.query && _req.query.playerUrl) || await buildPlayerUrl(); if (!playerUrl) { return res.status(404).send('Player URL is not available yet.'); } const content = buildLauncherContent(batTemplatePath, playerUrl); if (!content) { return res.status(404).send('Launcher template is not available.'); } sendLauncherDownload(res, 'pulse-signage-kiosk.bat', content); } catch (error) { next(error); } }); app.get('/downloads/kiosk/pulse-signage-kiosk.sh', requirePermission('screens.update'), async function (_req, res, next) { try { const playerUrl = normalizeTargetPlayerUrl(_req.query && _req.query.playerUrl) || await buildPlayerUrl(); if (!playerUrl) { return res.status(404).send('Player URL is not available yet.'); } const content = buildLauncherContent(shTemplatePath, playerUrl); if (!content) { return res.status(404).send('Launcher template is not available.'); } sendLauncherDownload(res, 'pulse-signage-kiosk.sh', content); } catch (error) { next(error); } }); };