// Assemble player HTML and inline runtime scripts from the server-side templates. const fs = require('fs'); const path = require('path'); const announcementIcons = require('#src/data/announcement-icons'); const renderingShared = require('#src/player/rendering-shared'); const { escapeHtml, sanitizeRichTextAttributes, sanitizeRichText, renderEditorJsBlock, renderEditorJsListItem, renderEditorJsTable, renderEditorJsContent, normalizeRenderableString, buildHtmlDocument, sanitizeFontFamily, sanitizeFontSize, sanitizeTextColor, parseMaybeJson, normalizeContentValue, normalizeSlide, fitCanvasSize } = renderingShared; function mediaKind(mediaPath) { const ext = path.extname(mediaPath || '').toLowerCase(); if (['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg'].includes(ext)) { return 'image'; } if (['.mp4', '.webm', '.ogg'].includes(ext)) { return 'video'; } if (ext === '.pdf') { return 'pdf'; } return 'file'; } function safeJsonForScript(value) { return JSON.stringify(value === undefined ? null : value).replace(/HTML'; } return ''; } const playerPageTemplatePath = path.join(__dirname, 'player-page.template.html'); const playerClientNameScriptPath = path.join(__dirname, 'player-client-name.script.html'); const playerPageOfflineScriptPath = path.join(__dirname, 'public', 'js', 'player-page-offline.js'); const playerPagePlaylistScriptPath = path.join(__dirname, 'public', 'js', 'player-page-playlist.js'); const playerPageAnimationScriptPath = path.join(__dirname, 'public', 'js', 'player-page-animation.js'); const playerPageMediaScriptPath = path.join(__dirname, 'public', 'js', 'player-page-media.js'); const playerPageTransitionScriptPath = path.join(__dirname, 'public', 'js', 'player-page-transition.js'); const playerPageCommandsScriptPath = path.join(__dirname, 'public', 'js', 'player-page-commands.js'); const playerPageRenderingScriptPath = path.join(__dirname, 'public', 'js', 'player-page-rendering.js'); const playerPagePlaybackScriptPath = path.join(__dirname, 'public', 'js', 'player-page-playback.js'); const playerAnnouncementTemplatesScriptPath = path.join(__dirname, 'public', 'js', 'player-announcement-templates.js'); const playerAnnouncementLowerThirdTemplatePath = path.join(__dirname, 'announcement-templates', 'lower-third.template.html'); const playerAnnouncementTopBannerTemplatePath = path.join(__dirname, 'announcement-templates', 'top-banner.template.html'); const playerAnnouncementFullscreenTemplatePath = path.join(__dirname, 'announcement-templates', 'fullscreen.template.html'); const playerPageAnnouncementsScriptPath = path.join(__dirname, 'player-page-announcements.js'); const playerPageScriptPath = path.join(__dirname, 'player-page.script.html'); const playerRenderingSharedScriptPath = path.join(__dirname, 'rendering-shared.js'); const playerRegionScriptDir = path.join(__dirname, 'regions'); const playerOnboardingLandingScriptPath = path.join(__dirname, 'onboarding', 'player-onboarding-landing.script.html'); const playerOnboardingFormScriptPath = path.join(__dirname, 'onboarding', 'player-onboarding-form.script.html'); let playerPageTemplateCache = null; let playerClientNameScriptCache = null; let playerPageOfflineScriptCache = null; let playerPagePlaylistScriptCache = null; let playerPageCommandsScriptCache = null; let playerPageRenderingScriptCache = null; let playerPagePlaybackScriptCache = null; let playerAnnouncementTemplatesScriptCache = null; let playerAnnouncementTemplatesDataScriptCache = null; let playerPageAnnouncementsScriptCache = null; let playerPageScriptCache = null; let playerOnboardingLandingScriptCache = null; let playerOnboardingFormScriptCache = null; function loadTemplate(filePath, cache) { const stat = fs.statSync(filePath); if (cache.value && cache.mtimeMs === stat.mtimeMs) { return cache.value; } const compiled = require('handlebars').compile(fs.readFileSync(filePath, 'utf8')); cache.value = compiled; cache.mtimeMs = stat.mtimeMs; return compiled; } function getPlayerPageTemplate() { return loadTemplate(playerPageTemplatePath, playerPageTemplateCache || (playerPageTemplateCache = {})); } function getPlayerClientNameScript() { return loadTemplate(playerClientNameScriptPath, playerClientNameScriptCache || (playerClientNameScriptCache = {})); } function getPlayerPageOfflineScript() { return loadTemplate(playerPageOfflineScriptPath, playerPageOfflineScriptCache || (playerPageOfflineScriptCache = {})); } function getPlayerPagePlaylistScript() { return loadTemplate(playerPagePlaylistScriptPath, playerPagePlaylistScriptCache || (playerPagePlaylistScriptCache = {})); } function getPlayerPageCommandsScript() { return loadTemplate(playerPageCommandsScriptPath, playerPageCommandsScriptCache || (playerPageCommandsScriptCache = {})); } function getPlayerPageRenderingScript() { return loadTemplate(playerPageRenderingScriptPath, playerPageRenderingScriptCache || (playerPageRenderingScriptCache = {})); } function getPlayerPagePlaybackScript() { return loadTemplate(playerPagePlaybackScriptPath, playerPagePlaybackScriptCache || (playerPagePlaybackScriptCache = {})); } function getPlayerAnnouncementTemplatesScript() { if (playerAnnouncementTemplatesScriptCache && playerAnnouncementTemplatesScriptCache.path === playerAnnouncementTemplatesScriptPath) { return playerAnnouncementTemplatesScriptCache.value; } const value = fs.readFileSync(playerAnnouncementTemplatesScriptPath, 'utf8').trim(); playerAnnouncementTemplatesScriptCache = { path: playerAnnouncementTemplatesScriptPath, value: value }; return value; } function getAnnouncementIconsDataScript() { return [ '(function () {', ' window.pulseAnnouncementIconKeys = ' + safeJsonForScript(announcementIcons.ANNOUNCEMENT_ICON_CATALOG_KEYS) + ';', ' window.pulseAnnouncementDefaultIconKey = ' + safeJsonForScript(announcementIcons.DEFAULT_ANNOUNCEMENT_ICON) + ';', '}());' ].join('\n'); } function loadAnnouncementTemplate(filePath) { return fs.readFileSync(filePath, 'utf8').trim(); } function getPlayerAnnouncementTemplatesDataScript() { const templatePaths = [ playerAnnouncementLowerThirdTemplatePath, playerAnnouncementTopBannerTemplatePath, playerAnnouncementFullscreenTemplatePath ]; const signature = templatePaths.map(function (filePath) { return fs.statSync(filePath).mtimeMs; }).join('|'); if (playerAnnouncementTemplatesDataScriptCache && playerAnnouncementTemplatesDataScriptCache.signature === signature) { return playerAnnouncementTemplatesDataScriptCache.value; } const value = [ '(function () {', ' window.playerAnnouncementTemplates = {', ' lowerThird: ' + safeJsonForScript(loadAnnouncementTemplate(playerAnnouncementLowerThirdTemplatePath)) + ',', ' topBanner: ' + safeJsonForScript(loadAnnouncementTemplate(playerAnnouncementTopBannerTemplatePath)) + ',', ' fullscreen: ' + safeJsonForScript(loadAnnouncementTemplate(playerAnnouncementFullscreenTemplatePath)), ' };', '}());' ].join('\n'); playerAnnouncementTemplatesDataScriptCache = { signature: signature, value: value }; return value; } function getPlayerPageAnnouncementsScript() { return loadTemplate(playerPageAnnouncementsScriptPath, playerPageAnnouncementsScriptCache || (playerPageAnnouncementsScriptCache = {})); } function getPlayerPageScript() { return loadTemplate(playerPageScriptPath, playerPageScriptCache || (playerPageScriptCache = {})); } function getPlayerRegionScriptPaths() { if (!fs.existsSync(playerRegionScriptDir)) { return []; } return fs.readdirSync(playerRegionScriptDir, { withFileTypes: true }) .filter(function (entry) { return entry.isFile() && entry.name.toLowerCase().endsWith('.js'); }) .map(function (entry) { return path.join(playerRegionScriptDir, entry.name); }) .sort(function (left, right) { return left.localeCompare(right); }); } function getPlayerOnboardingLandingScript() { return loadTemplate(playerOnboardingLandingScriptPath, playerOnboardingLandingScriptCache || (playerOnboardingLandingScriptCache = {})); } function getPlayerOnboardingFormScript() { return loadTemplate(playerOnboardingFormScriptPath, playerOnboardingFormScriptCache || (playerOnboardingFormScriptCache = {})); } function getPlayerRuntimeScripts() { function getScriptBody(value) { return String(value || '') .replace(/^\s*]*)?>/i, '') .replace(/<\/script>\s*$/i, '') .trim(); } return [ ['region-registry', fs.readFileSync(path.join(__dirname, 'public', 'js', 'player-region-registry.js'), 'utf8').trim()], ['placeholder-utils', fs.readFileSync(path.join(__dirname, '..', 'web', 'public', 'js', 'shared', 'placeholder-utils.js'), 'utf8').trim()], ['chart-data', fs.readFileSync(path.join(__dirname, '..', 'web', 'public', 'js', 'regions', 'chart-data.js'), 'utf8').trim()], ['qr-code-svg', fs.readFileSync(path.join(__dirname, '..', 'web', 'public', 'js', 'shared', 'qr-code-svg.js'), 'utf8').trim()], ['rendering-shared', fs.readFileSync(playerRenderingSharedScriptPath, 'utf8').trim()], ['client-name', getScriptBody(getPlayerClientNameScript()())], ['offline', getScriptBody(getPlayerPageOfflineScript()())], ['playlist', getScriptBody(getPlayerPagePlaylistScript()())], ['animation', fs.readFileSync(playerPageAnimationScriptPath, 'utf8').trim()], ['media', fs.readFileSync(playerPageMediaScriptPath, 'utf8').trim()], ['transition', fs.readFileSync(playerPageTransitionScriptPath, 'utf8').trim()], ['commands', getScriptBody(getPlayerPageCommandsScript()())], ['rendering', getScriptBody(getPlayerPageRenderingScript()())], ['playback', getScriptBody(getPlayerPagePlaybackScript()())], ['announcement-icons', getAnnouncementIconsDataScript()], ['announcement-data', getPlayerAnnouncementTemplatesDataScript()], ['announcement-templates', getScriptBody(getPlayerAnnouncementTemplatesScript())] ].concat(getPlayerRegionScriptPaths().map(function (filePath, index) { return ['region-' + index, fs.readFileSync(filePath, 'utf8').trim()]; })).filter(function (entry) { return entry[1]; }); } module.exports = { mediaKind: mediaKind, escapeHtml: escapeHtml, sanitizeFontFamily: sanitizeFontFamily, sanitizeFontSize: sanitizeFontSize, sanitizeTextColor: sanitizeTextColor, sanitizeRichTextAttributes: sanitizeRichTextAttributes, safeJsonForScript: safeJsonForScript, parseMaybeJson: parseMaybeJson, normalizeContentValue: normalizeContentValue, normalizeSlide: normalizeSlide, sanitizeRichText: sanitizeRichText, renderEditorJsBlock: renderEditorJsBlock, renderEditorJsListItem: renderEditorJsListItem, renderEditorJsTable: renderEditorJsTable, renderEditorJsContent: renderEditorJsContent, renderHtmlRegionContent: renderHtmlRegionContent, fitCanvasSize: fitCanvasSize, loadTemplate: loadTemplate, getPlayerPageTemplate: getPlayerPageTemplate, getPlayerClientNameScript: getPlayerClientNameScript, getPlayerPageOfflineScript: getPlayerPageOfflineScript, getPlayerPagePlaylistScript: getPlayerPagePlaylistScript, getPlayerPageCommandsScript: getPlayerPageCommandsScript, getPlayerPageRenderingScript: getPlayerPageRenderingScript, getPlayerPagePlaybackScript: getPlayerPagePlaybackScript, getAnnouncementIconsDataScript: getAnnouncementIconsDataScript, getPlayerAnnouncementTemplatesDataScript: getPlayerAnnouncementTemplatesDataScript, getPlayerAnnouncementTemplatesScript: getPlayerAnnouncementTemplatesScript, getPlayerPageAnnouncementsScript: getPlayerPageAnnouncementsScript, getPlayerPageScript: getPlayerPageScript, getPlayerOnboardingLandingScript: getPlayerOnboardingLandingScript, getPlayerOnboardingFormScript: getPlayerOnboardingFormScript, getPlayerRuntimeScripts: getPlayerRuntimeScripts };