'use strict' // ───────────────────────────────────────────────────────────────────────────── // page() — HTML shell builder. // Inlines ui/design.css + ui/client.js (+ optionally scanner.js) into every // magic-link page so the client loads with zero extra requests and a server // crash can't leave a page partially styled. // ───────────────────────────────────────────────────────────────────────────── const fs = require('fs') const path = require('path') // Read assets once at module load. Changes on disk require a service restart. const DESIGN_CSS = fs.readFileSync(path.join(__dirname, 'design.css'), 'utf8') const CLIENT_JS = fs.readFileSync(path.join(__dirname, 'client.js'), 'utf8') const SCANNER_JS = fs.readFileSync(path.join(__dirname, 'scanner.js'), 'utf8') // Nested field-scan overlay markup — injected when includeScanner is true. const SCANNER_OVERLAY_HTML = `

📷Scanner

` const HTML_HEADERS = { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache, no-store' } function htmlHeaders () { return HTML_HEADERS } // Safe JSON embed — same as components.jsonScript but avoid circular dep. function jsonEmbed (v) { return JSON.stringify(v).replace(//g, '--\\u003e') } // ───────────────────────────────────────────────────────────────────────── // page(opts) // // title: + og title // themeColor: optional meta theme-color (default brand-dark) // lang: default 'fr' // head: extra HTML injected in <head> (meta, link, etc.) // body: page body HTML (required) // bootVars: object; each key becomes a top-level const in script, // e.g. { T: token, TODAY: 'yyyy-mm-dd' } ⇒ const T=...; const TODAY=... // cfg: object merged into window.UI_CFG for client.js // { token, hub, today, base } // script: page-specific JS (appended after client.js / scanner.js) // includeScanner: inject scanner.js + overlay markup // bodyClass: extra class on <body> (e.g. 'pad-body' to reserve tab-bar space) // // Returns a complete HTML document string. // ───────────────────────────────────────────────────────────────────────── function page (opts) { const { title = 'Targo', themeColor = '#3f3d7a', lang = 'fr', head = '', body = '', bootVars = {}, cfg = {}, script = '', includeScanner = false, bodyClass = '', } = opts // bootVars → "const K=v;" lines; JSON-safe, no user escaping needed. const bootLines = Object.keys(bootVars).map(k => `var ${k}=${jsonEmbed(bootVars[k])};`).join('\n') const cfgLine = `window.UI_CFG=${jsonEmbed(cfg)};` const scannerAssets = includeScanner ? `<script>${SCANNER_JS}</script>` : '' const scannerMarkup = includeScanner ? SCANNER_OVERLAY_HTML : '' return `<!DOCTYPE html> <html lang="${lang}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"> <meta name="theme-color" content="${themeColor}"> <title>${title} ${head} ${body} ${scannerMarkup}
${scannerAssets} ` } // ── Pre-baked error pages ──────────────────────────────────────────────── function pageExpired (message) { const msg = message || `Ce lien n'est plus valide. Votre superviseur vous enverra un nouveau lien par SMS.` return page({ title: 'Lien expiré', body: `
🔗

Lien expiré

${msg}

`, }) } function pageError (message) { const msg = message || 'Réessayez dans quelques instants.' return page({ title: 'Erreur', body: `

Erreur temporaire

${msg}

`, }) } module.exports = { page, pageExpired, pageError, htmlHeaders, html: htmlHeaders }