Add kiosk launcher downloads and player URL resolution

This commit is contained in:
2026-08-06 18:51:33 +01:00
parent 59730837ad
commit a4f8a807ff
23 changed files with 621 additions and 71 deletions
+5 -2
View File
@@ -10,7 +10,6 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
const isClientNameAvailable = deps.isClientNameAvailable;
const withClientNameReservation = deps.withClientNameReservation;
const broadcastDashboardState = deps.broadcastDashboardState;
const playerPublicBaseUrl = String(deps.playerPublicBaseUrl || '').trim().replace(/\/$/, '');
const requirePermission = deps.requirePermission;
app.post('/clients/:slug/commands', requirePermission('clients.allow'), async function (req, res, next) {
@@ -212,7 +211,11 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
const targetPlayerRecord = typeof common.fetchScreenPlayerRecord === 'function'
? await common.fetchScreenPlayerRecord(pool, targetScreenSlug)
: null;
const targetBaseUrl = String(targetPlayerRecord && targetPlayerRecord.public_base_url || playerPublicBaseUrl || '').trim().replace(/\/$/, '');
const targetBaseUrl = String(
targetPlayerRecord && targetPlayerRecord.public_base_url ||
(typeof common.fetchPlayerPublicBaseUrl === 'function' ? await common.fetchPlayerPublicBaseUrl(pool) : '') ||
''
).trim().replace(/\/$/, '');
const targetPlayerUrl = targetBaseUrl ? `${targetBaseUrl}/screen/${encodeURIComponent(targetScreenSlug)}` : `/screen/${encodeURIComponent(targetScreenSlug)}`;
await forwardPlayerCommand(slug, {
+6 -2
View File
@@ -11,7 +11,6 @@ module.exports = function registerManageRoutes(app, deps) {
const getScreenDeleteBlockMessage = deps.getScreenDeleteBlockMessage;
const getScreenConnections = deps.getScreenConnections;
const forwardPlayerCommand = deps.forwardPlayerCommand;
const PLAYER_PUBLIC_BASE_URL = deps.playerPublicBaseUrl;
const requirePermission = deps.requirePermission;
const SCREEN_NAME_MAX_LENGTH = 255;
@@ -153,9 +152,14 @@ module.exports = function registerManageRoutes(app, deps) {
const playerRecord = typeof common.fetchScreenPlayerRecord === 'function'
? await common.fetchScreenPlayerRecord(pool, previousSlug)
: null;
const playerBaseUrl = playerRecord && playerRecord.public_base_url
? String(playerRecord.public_base_url).replace(/\/$/, '')
: typeof common.fetchPlayerPublicBaseUrl === 'function'
? await common.fetchPlayerPublicBaseUrl(pool)
: '';
await forwardPlayerCommand(previousSlug, {
command: 'redirect',
url: (playerRecord && playerRecord.public_base_url ? String(playerRecord.public_base_url).replace(/\/$/, '') : PLAYER_PUBLIC_BASE_URL) + `/screen/${encodeURIComponent(slug)}`
url: playerBaseUrl ? `${playerBaseUrl}/screen/${encodeURIComponent(slug)}` : `/screen/${encodeURIComponent(slug)}`
});
}
redirectAfterSave(req, res, '/screens?edit=' + screen.id, {
+7 -4
View File
@@ -1,7 +1,5 @@
// Shared route helpers for web view rendering and player URL formatting.
const PLAYER_PUBLIC_BASE_URL = (process.env.PLAYER_PUBLIC_BASE_URL || process.env.PLAYER_BASE_URL || 'http://localhost:3001').replace(/\/$/, '');
function escapeHtml(value) {
return String(value ?? '')
.replace(/&/g, '&')
@@ -11,8 +9,13 @@ function escapeHtml(value) {
.replace(/'/g, ''');
}
function screenPlayerUrl(slug) {
return `${PLAYER_PUBLIC_BASE_URL}/screen/${encodeURIComponent(slug)}`;
function screenPlayerUrl(slug, baseUrl) {
const normalizedBaseUrl = String(baseUrl || '').trim().replace(/\/$/, '');
if (!normalizedBaseUrl) {
return '';
}
return `${normalizedBaseUrl}/screen/${encodeURIComponent(slug)}`;
}
module.exports = {
+1 -3
View File
@@ -48,7 +48,7 @@ function registerAuthAndAccountRoutes(app, deps) {
buildDashboardState: deps.buildDashboardState,
fetchScreensByPlaylistId: deps.fetchScreensByPlaylistId,
getScreenDeleteBlockMessage: deps.playerActionService.getScreenDeleteBlockMessage,
getScreenConnections: deps.playerActionService.getScreenConnections
getScreenConnections: deps.playerActionService.getScreenConnections,
});
registerAccountRoutes(app, {
@@ -107,7 +107,6 @@ function registerSignageRoutes(app, deps) {
getScreenDeleteBlockMessage: deps.playerActionService.getScreenDeleteBlockMessage,
getScreenConnections: deps.playerActionService.getScreenConnections,
forwardPlayerCommand: deps.playerActionService.forwardPlayerCommand,
playerPublicBaseUrl: deps.playerPublicBaseUrl,
requirePermission: deps.requirePermission
});
@@ -129,7 +128,6 @@ function registerSignageRoutes(app, deps) {
isClientNameAvailable: deps.isClientNameAvailable,
withClientNameReservation: deps.withClientNameReservation,
broadcastDashboardState: deps.broadcastDashboardState,
playerPublicBaseUrl: deps.playerPublicBaseUrl,
requirePermission: deps.requirePermission
});
}
+82 -1
View File
@@ -1,5 +1,7 @@
// Screen route registration and dashboard wiring.
const fs = require('fs');
module.exports = function registerScreensRoutes(app, deps) {
const pool = deps.pool;
const common = deps.common;
@@ -10,6 +12,12 @@ module.exports = function registerScreensRoutes(app, deps) {
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 requireQueryPermission(readPermissionKey, editPermissionKey) {
return function (req, res, next) {
@@ -55,6 +63,36 @@ module.exports = function registerScreensRoutes(app, deps) {
});
}
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) {
@@ -62,6 +100,10 @@ module.exports = function registerScreensRoutes(app, deps) {
if (!screen) {
return res.status(404).send('Screen not found');
}
screen.player_url = await buildPlayerUrl();
if (screen.player_url) {
screen.launcher_downloads = launcherDownloadPaths;
}
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));
@@ -99,7 +141,10 @@ module.exports = function registerScreensRoutes(app, deps) {
const playerUrlsBySlug = typeof common.fetchScreenPlayerUrls === 'function'
? await common.fetchScreenPlayerUrls(pool)
: {};
screen.player_url = screen.slug && playerUrlsBySlug[screen.slug] ? String(playerUrlsBySlug[screen.slug]).trim() : null;
screen.player_url = String(playerUrlsBySlug[screen.slug] || '').trim() || null;
if (screen.player_url) {
screen.launcher_downloads = launcherDownloadPaths;
}
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));
@@ -107,5 +152,41 @@ module.exports = function registerScreensRoutes(app, deps) {
next(error);
}
});
app.get('/downloads/kiosk/pulse-signage-kiosk.bat', requirePermission('screens.update'), async function (_req, res, next) {
try {
const 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 = 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);
}
});
};