// Helpers for normalizing dashboard state and date values. const { WebSocket } = require('ws'); function normalizeClientName(value) { return String(value || '').trim(); } function enrichScreensWithConnections(screens, connectionsBySlug, onboardingNameBySlug, playerUrlsBySlug) { return (screens || []).map(function (screen) { const connectionState = connectionsBySlug[screen.slug] || { count: 0, connections: [] }; return Object.assign({}, screen, { client_name: onboardingNameBySlug[screen.slug] || null, player_connection_count: connectionState.count || 0, player_connections: Array.isArray(connectionState.connections) ? connectionState.connections : [], player_url: String(playerUrlsBySlug && playerUrlsBySlug[screen.slug] || '').trim() || null }); }); } function buildClientRows(screens, connectionsBySlug, onboardingNameBySlug, onboardingNameByDeviceId, formatDashboardDate) { return (screens || []).flatMap(function (screen) { const connectionState = connectionsBySlug[screen.slug] || { count: 0, connections: [] }; return (connectionState.connections || []).map(function (connection) { const deviceId = String(connection.deviceId || '').trim(); return Object.assign({}, connection, { screen_slug: screen.slug, screen_name: screen.name, client_name: (deviceId && onboardingNameByDeviceId && onboardingNameByDeviceId[deviceId]) || connection.clientName || onboardingNameBySlug[screen.slug] || String(connection.clientId || '').trim() || null, playlist_name: screen.playlist_name || null, connectedAtLabel: formatDashboardDate(connection.connectedAt), lastSeenAtLabel: formatDashboardDate(connection.lastSeenAt), player_url: screen.player_url || String(connection.page || '').trim() || null }); }); }); } function compareScreenNames(left, right) { const leftName = String(left && left.name || '').trim(); const rightName = String(right && right.name || '').trim(); const nameCompare = leftName.localeCompare(rightName, undefined, { sensitivity: 'base', numeric: true }); if (nameCompare !== 0) { return nameCompare; } return String(left && left.slug || '').localeCompare(String(right && right.slug || ''), undefined, { sensitivity: 'base', numeric: true }); } function createDashboardStateService(options) { const pool = options && options.pool; const common = options && options.common; const playerSnapshotCache = options && options.playerSnapshotCache; const playerSnapshotSockets = options && options.playerSnapshotSockets; const ensurePlayerSnapshotSubscription = options && options.ensurePlayerSnapshotSubscription; const formatDashboardDate = options && options.formatDashboardDate; if (!pool || !common || !playerSnapshotCache || !playerSnapshotSockets || typeof ensurePlayerSnapshotSubscription !== 'function' || typeof formatDashboardDate !== 'function') { throw new Error('createDashboardStateService requires the dashboard dependencies.'); } async function buildDashboardState() { const data = await common.fetchAdminData(pool); const screensData = data.screens || []; const playerUrlsBySlug = typeof common.fetchScreenPlayerUrls === 'function' ? await common.fetchScreenPlayerUrls(pool) : {}; screensData.forEach(function (screen) { ensurePlayerSnapshotSubscription(screen.slug); }); const [onboardingRows] = await pool.query( `SELECT s.slug, pod.device_id, pod.client_name FROM d_onboarding_devices pod JOIN d_screens s ON s.id = pod.screen_id WHERE pod.client_name IS NOT NULL AND TRIM(pod.client_name) <> ''` ); const onboardingNameBySlug = {}; const onboardingNameByDeviceId = {}; onboardingRows.forEach(function (row) { const clientName = normalizeClientName(row.client_name); const slug = normalizeClientName(row.slug); const deviceId = normalizeClientName(row.device_id); if (slug) { onboardingNameBySlug[slug] = clientName; } if (deviceId) { onboardingNameByDeviceId[deviceId] = clientName; } }); const connectionsBySlug = {}; screensData.forEach(function (screen) { const cached = playerSnapshotCache.get(String(screen.slug || '').trim()); if (cached && Array.isArray(cached.connections)) { connectionsBySlug[screen.slug] = cached; } }); const screens = enrichScreensWithConnections(data.screens || [], connectionsBySlug, onboardingNameBySlug, playerUrlsBySlug) .map(function (screen) { return Object.assign({}, screen, { player_url: screen.player_url || null }); }) .sort(compareScreenNames); const clients = buildClientRows(screens, connectionsBySlug, onboardingNameBySlug, onboardingNameByDeviceId, formatDashboardDate); const playerServiceConnected = Array.from(playerSnapshotSockets.values()).some(function (socket) { return socket && socket.readyState === WebSocket.OPEN; }); return { playlists: data.playlists || [], screens: screens, clients: clients, slides: data.slides || [], playerServiceConnected: playerServiceConnected, connectedClientsCount: screens.reduce(function (total, screen) { return total + Number(screen.player_connection_count || 0); }, 0) }; } return { buildDashboardState: buildDashboardState }; } module.exports = { createDashboardStateService };