/** * Store del kiosk layout lato plugin. * * - cache su disco: data/kiosk-layout.json * - bootstrap HTTP: GET {API_URL}/kiosklayouts/sensor//active * - apply push WS: invocato da socket.js quando arriva _t:'kiosk_layout_update' * - SSE stream: espone un emitter consumato dalla route /meb/kiosk/stream * * Il plugin NON sceglie il layout: lo riceve dal server e lo mostra. */ const fs = require('fs').promises; const path = require('path'); const EventEmitter = require('events'); const configManager = require('../../config/configManager'); const cacheFile = path.join(__dirname, '../../../data/kiosk-layout.json'); const emitter = new EventEmitter(); let current = null; async function ensureDir() { try { await fs.mkdir(path.dirname(cacheFile), { recursive: true }); } catch {} } async function loadCache() { try { const raw = await fs.readFile(cacheFile, 'utf-8'); return JSON.parse(raw); } catch { return null; } } async function saveCache() { if (!current) return; try { await ensureDir(); await fs.writeFile(cacheFile, JSON.stringify(current, null, 2)); } catch (err) { console.warn('[KIOSK|STORE] save cache failed:', err.message); } } async function init() { current = await loadCache(); if (current) { console.log(`[KIOSK|STORE] cache caricata v=${current.version || '?'} (${current.content?.tiles?.length || 0} tiles)`); } else { console.log('[KIOSK|STORE] nessun layout in cache'); } } async function bootstrapFromServer() { const API_URL = process.env.API_URL; if (!API_URL) { console.warn('[KIOSK|STORE] API_URL non configurato'); return; } const sensorId = configManager.getSensorName() || process.env.SENSOR_ID; if (!sensorId) { console.warn('[KIOSK|STORE] SENSOR_ID non configurato'); return; } try { const r = await fetch(`${API_URL}/kiosklayouts/sensor/${encodeURIComponent(sensorId)}/active`, { signal: AbortSignal.timeout(8000) }); if (!r.ok) { if (r.status !== 404) console.warn('[KIOSK|STORE] bootstrap', r.status); return; } const layout = await r.json(); await applyRemote(layout); } catch (err) { console.warn('[KIOSK|STORE] bootstrap err:', err.message); } } /** * Applica un layout (dedup per id+version), salva cache, emette 'update'. */ async function applyRemote(layout) { if (!layout || !layout.content) return false; if (current && current.id === layout.id && current.version === layout.version) return false; current = layout; await saveCache(); console.log(`[KIOSK|STORE] applicato v=${layout.version || '?'} (${layout.content.tiles?.length || 0} tiles)`); emitter.emit('update', current); return true; } function get() { return current; } function onUpdate(fn) { emitter.on('update', fn); return () => emitter.off('update', fn); } module.exports = { init, bootstrapFromServer, applyRemote, get, onUpdate };