Add multi-player and remote bridge support
This commit is contained in:
@@ -21,8 +21,10 @@ module.exports = function renderDashboardPage(data, message, currentUser) {
|
||||
playlists: data.playlists || [],
|
||||
screens: data.screens || [],
|
||||
clients: data.clients || [],
|
||||
kioskPlayers: data.kioskPlayers || [],
|
||||
slides: data.slides || [],
|
||||
connectedClientsCount: Number(data.connectedClientsCount || 0),
|
||||
connectedPlayersCount: Number(data.connectedPlayersCount || data.connectedClientsCount || 0),
|
||||
primaryPlayerUrl: normalizeBaseUrl(primaryPlayerUrl) || null,
|
||||
scripts: ['js/dashboard/dashboard-page.js']
|
||||
});
|
||||
|
||||
@@ -4,7 +4,8 @@ function buildScreenFormViewModel(screen, data, message, currentUser, isEdit) {
|
||||
const viewScreen = Object.assign({
|
||||
name: '',
|
||||
slug: '',
|
||||
playlist_id: null
|
||||
playlist_id: null,
|
||||
player_urls: []
|
||||
}, screen || {});
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Screen route registration and dashboard wiring.
|
||||
|
||||
const fs = require('fs');
|
||||
const { screenPlayerUrl } = require('../../../routes/common');
|
||||
|
||||
module.exports = function registerScreensRoutes(app, deps) {
|
||||
const pool = deps.pool;
|
||||
@@ -19,6 +20,17 @@ module.exports = function registerScreensRoutes(app, deps) {
|
||||
linux: '/downloads/kiosk/pulse-signage-kiosk.sh'
|
||||
};
|
||||
|
||||
function normalizeTargetPlayerUrl(value) {
|
||||
const normalized = String(value || '').trim().replace(/\/$/, '');
|
||||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
if (!/^https?:\/\//i.test(normalized)) {
|
||||
return null;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function requireQueryPermission(readPermissionKey, editPermissionKey) {
|
||||
return function (req, res, next) {
|
||||
const permissionKey = req.query && req.query.edit ? editPermissionKey : readPermissionKey;
|
||||
@@ -58,7 +70,6 @@ module.exports = function registerScreensRoutes(app, deps) {
|
||||
|
||||
return Object.assign({}, screen, {
|
||||
player_connection_count: playerConnectionCount,
|
||||
player_url: dashboardScreen && dashboardScreen.player_url ? dashboardScreen.player_url : screen.player_url || null
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -71,6 +82,21 @@ module.exports = function registerScreensRoutes(app, deps) {
|
||||
return common.fetchPlayerPublicBaseUrl(pool);
|
||||
}
|
||||
|
||||
async function buildScreenPlayerUrls(screen) {
|
||||
const playerRegistrations = typeof common.fetchPlayerRegistrations === 'function'
|
||||
? await common.fetchPlayerRegistrations(pool)
|
||||
: [];
|
||||
|
||||
return (Array.isArray(playerRegistrations) ? playerRegistrations : []).map(function (player) {
|
||||
const baseUrl = String(player && player.public_base_url || '').trim().replace(/\/$/, '');
|
||||
const playerUrl = screenPlayerUrl(screen && screen.slug ? screen.slug : '', baseUrl);
|
||||
return Object.assign({}, player, {
|
||||
public_base_url: baseUrl || null,
|
||||
player_url: playerUrl || null
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function buildLauncherContent(templatePath, playerUrl) {
|
||||
const template = fs.readFileSync(templatePath, 'utf8');
|
||||
const normalizedPlayerUrl = String(playerUrl || '').trim();
|
||||
@@ -100,8 +126,8 @@ 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.player_urls = await buildScreenPlayerUrls(screen);
|
||||
if (screen.player_urls.length) {
|
||||
screen.launcher_downloads = launcherDownloadPaths;
|
||||
}
|
||||
screen.inUse = Boolean(await getScreenDeleteBlockMessage(pool, screen, deps.getScreenConnections));
|
||||
@@ -113,18 +139,9 @@ module.exports = function registerScreensRoutes(app, deps) {
|
||||
const sort = common.getSortQuery(req);
|
||||
const direction = common.getSortDirectionQuery(req);
|
||||
const dashboardState = await buildDashboardState(pool);
|
||||
const playerUrlsBySlug = typeof common.fetchScreenPlayerUrls === 'function'
|
||||
? await common.fetchScreenPlayerUrls(pool)
|
||||
: {};
|
||||
const data = await common.fetchScreensPage(pool, page, LIST_PAGE_SIZE, search, sort, direction);
|
||||
res.send(pages.renderScreensPage({
|
||||
screens: applyConnectionCounts(data.screens || [], dashboardState.screens || []).map(function (screen) {
|
||||
const slug = String(screen && screen.slug || '').trim();
|
||||
const registryUrl = slug && playerUrlsBySlug[slug] ? String(playerUrlsBySlug[slug]).trim() : '';
|
||||
return Object.assign({}, screen, {
|
||||
player_url: screen.player_url || registryUrl || null
|
||||
});
|
||||
}),
|
||||
screens: applyConnectionCounts(data.screens || [], dashboardState.screens || []),
|
||||
pagination: buildPagination(data.totalItems, data.currentPage, 'page', { search: search, sort: sort, direction: direction }, LIST_PAGE_SIZE, 'screens', 'Screen pages')
|
||||
}, req.query.message ? String(req.query.message) : '', req.currentUser));
|
||||
} catch (error) {
|
||||
@@ -138,11 +155,8 @@ module.exports = function registerScreensRoutes(app, deps) {
|
||||
if (!screen) {
|
||||
return res.status(404).send('Screen not found');
|
||||
}
|
||||
const playerUrlsBySlug = typeof common.fetchScreenPlayerUrls === 'function'
|
||||
? await common.fetchScreenPlayerUrls(pool)
|
||||
: {};
|
||||
screen.player_url = String(playerUrlsBySlug[screen.slug] || '').trim() || null;
|
||||
if (screen.player_url) {
|
||||
screen.player_urls = await buildScreenPlayerUrls(screen);
|
||||
if (screen.player_urls.length) {
|
||||
screen.launcher_downloads = launcherDownloadPaths;
|
||||
}
|
||||
screen.inUse = Boolean(await getScreenDeleteBlockMessage(pool, screen, deps.getScreenConnections));
|
||||
@@ -155,7 +169,8 @@ module.exports = function registerScreensRoutes(app, deps) {
|
||||
|
||||
app.get('/downloads/kiosk/pulse-signage-kiosk.bat', requirePermission('screens.update'), async function (_req, res, next) {
|
||||
try {
|
||||
const playerUrl = await buildPlayerUrl();
|
||||
const playerUrl = normalizeTargetPlayerUrl(_req.query && _req.query.playerUrl)
|
||||
|| await buildPlayerUrl();
|
||||
if (!playerUrl) {
|
||||
return res.status(404).send('Player URL is not available yet.');
|
||||
}
|
||||
@@ -173,7 +188,8 @@ module.exports = function registerScreensRoutes(app, deps) {
|
||||
|
||||
app.get('/downloads/kiosk/pulse-signage-kiosk.sh', requirePermission('screens.update'), async function (_req, res, next) {
|
||||
try {
|
||||
const playerUrl = await buildPlayerUrl();
|
||||
const playerUrl = normalizeTargetPlayerUrl(_req.query && _req.query.playerUrl)
|
||||
|| await buildPlayerUrl();
|
||||
if (!playerUrl) {
|
||||
return res.status(404).send('Player URL is not available yet.');
|
||||
}
|
||||
|
||||
@@ -1,14 +1,32 @@
|
||||
// Slide route registration and pagination wiring.
|
||||
|
||||
const { renderFragment } = require('../../../view');
|
||||
const path = require('path');
|
||||
const { verifyRequestAuth } = require('#src/request-auth');
|
||||
const { getFontStylesheetHref } = require('#src/web/lib/media/font-library');
|
||||
const { buildThumbnailPreviewPayload } = require('#src/web/lib/media/slide-thumbnail-preview');
|
||||
|
||||
function safeJsonForScript(value) {
|
||||
return JSON.stringify(value === undefined ? null : value).replace(/</g, '\\u003c');
|
||||
}
|
||||
|
||||
module.exports = function registerSlidesRoutes(app, deps) {
|
||||
const pages = deps.pages;
|
||||
const common = deps.common;
|
||||
const pool = deps.pool;
|
||||
const mediaDir = String(deps.mediaDir || path.join(__dirname, '..', '..', '..', '..', 'media')).trim();
|
||||
const webBaseUrl = String(deps.webBaseUrl || '').trim().replace(/\/$/, '');
|
||||
const { buildPagination } = require('../../../lib/pagination');
|
||||
const requirePermission = deps.requirePermission;
|
||||
|
||||
function requireRequestAuth(req, res, next) {
|
||||
if (!verifyRequestAuth(req)) {
|
||||
return res.status(401).json({ error: 'Request authentication required.' });
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
const LIST_PAGE_SIZE = 25;
|
||||
|
||||
app.get('/slides', requirePermission('slides.read'), async function (req, res, next) {
|
||||
@@ -35,5 +53,30 @@ module.exports = function registerSlidesRoutes(app, deps) {
|
||||
frameBodyClass: 'slide-preview-popup-shell slide-preview-popup-body'
|
||||
}));
|
||||
});
|
||||
|
||||
app.get('/api/internal/slide-thumbnails/:id/popup-preview', async function (req, res, next) {
|
||||
try {
|
||||
const slide = await common.fetchSlideById(pool, Number(req.params.id));
|
||||
if (!slide) {
|
||||
return res.status(404).send('Slide not found');
|
||||
}
|
||||
|
||||
const payload = buildThumbnailPreviewPayload(slide, {
|
||||
baseUrl: webBaseUrl || (String(req.headers.host || '').trim() ? `${req.protocol}://${String(req.headers.host).trim()}` : ''),
|
||||
fontStylesheetHref: getFontStylesheetHref(mediaDir)
|
||||
});
|
||||
|
||||
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
||||
res.send(renderFragment('slides/popup-preview', {
|
||||
title: 'Slide preview',
|
||||
framePopupCard: true,
|
||||
hideFrameHeader: true,
|
||||
frameBodyClass: 'slide-preview-popup-shell slide-preview-popup-body',
|
||||
popupPreviewPayloadJson: safeJsonForScript(payload)
|
||||
}));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user