Release v2.10.4
This commit is contained in:
@@ -5,7 +5,7 @@ function createWebConfig() {
|
||||
const uploadsDir = path.join(mediaDir, 'uploads');
|
||||
const thumbnailsDir = path.join(mediaDir, 'thumbnails');
|
||||
const assetDir = path.join(__dirname, '..', 'public');
|
||||
const playerInternalUrl = (process.env.PLAYER_INTERNAL_URL || process.env.PLAYER_BASE_URL || 'http://player:8081').replace(/\/$/, '');
|
||||
const playerInternalUrl = (process.env.PLAYER_INTERNAL_URL || 'http://player:8081').replace(/\/$/, '');
|
||||
const bridgeInternalUrl = (process.env.BRIDGE_INTERNAL_URL || 'http://player-bridge:8090').replace(/\/$/, '');
|
||||
const webInternalUrl = (process.env.WEB_INTERNAL_URL || `http://127.0.0.1:${Number(process.env.WEB_PORT || 8080)}`).replace(/\/$/, '');
|
||||
const sessionCookieName = 'digital_signage_session';
|
||||
|
||||
@@ -7,6 +7,7 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
const common = deps.common;
|
||||
const forwardPlayerCommand = deps.forwardPlayerCommand;
|
||||
const forwardPlayerCommandToBaseUrl = deps.forwardPlayerCommandToBaseUrl;
|
||||
const forwardPlayerCommandToDevice = deps.forwardPlayerCommandToDevice;
|
||||
const getScreenConnections = deps.getScreenConnections;
|
||||
const isClientNameAvailable = deps.isClientNameAvailable;
|
||||
const findAvailableClientName = deps.findAvailableClientName;
|
||||
@@ -104,12 +105,51 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
}
|
||||
|
||||
async function forwardPlayerCommandForConnections(screenSlug, connections, commandPayload, connectionId, deviceId) {
|
||||
const targetBaseUrls = await resolvePlayerBaseUrlsForConnections(connections);
|
||||
const connectionList = Array.isArray(connections) ? connections : [];
|
||||
const targetedConnections = connectionId
|
||||
? connectionList.filter(function (connection) {
|
||||
const candidateConnectionId = String(connection && connection.id || '').trim();
|
||||
const candidateClientId = String(connection && connection.clientId || '').trim();
|
||||
const candidateDeviceId = String(connection && connection.deviceId || '').trim();
|
||||
return candidateConnectionId === connectionId
|
||||
|| candidateClientId === connectionId
|
||||
|| candidateDeviceId === connectionId;
|
||||
})
|
||||
: (deviceId
|
||||
? connectionList.filter(function (connection) {
|
||||
return String(connection && connection.deviceId || '').trim() === String(deviceId).trim();
|
||||
})
|
||||
: connectionList);
|
||||
const remoteConnections = targetedConnections.filter(function (connection) {
|
||||
return String(connection && connection.playerDeviceId || '').trim();
|
||||
});
|
||||
const localConnections = targetedConnections.filter(function (connection) {
|
||||
return !String(connection && connection.playerDeviceId || '').trim();
|
||||
});
|
||||
const deviceTargets = Array.from(new Set(remoteConnections.map(function (connection) {
|
||||
return String(connection && connection.playerDeviceId || '').trim();
|
||||
}).filter(Boolean)));
|
||||
const results = [];
|
||||
if (deviceTargets.length && typeof forwardPlayerCommandToDevice === 'function') {
|
||||
const remoteResults = await Promise.allSettled(deviceTargets.map(function (targetDeviceId) {
|
||||
const payload = Object.assign({ screenSlug: screenSlug }, commandPayload || {});
|
||||
if (connectionId) {
|
||||
payload.connectionId = connectionId;
|
||||
}
|
||||
return forwardPlayerCommandToDevice(targetDeviceId, payload);
|
||||
}));
|
||||
results.push.apply(results, remoteResults);
|
||||
}
|
||||
|
||||
const targetBaseUrls = await resolvePlayerBaseUrlsForConnections(localConnections);
|
||||
if (targetBaseUrls.length && typeof forwardPlayerCommandToBaseUrl === 'function') {
|
||||
const results = await Promise.allSettled(targetBaseUrls.map(function (targetBaseUrl) {
|
||||
const localResults = await Promise.allSettled(targetBaseUrls.map(function (targetBaseUrl) {
|
||||
return forwardPlayerCommandToBaseUrl(targetBaseUrl, screenSlug, commandPayload, connectionId || deviceId || undefined);
|
||||
}));
|
||||
results.push.apply(results, localResults);
|
||||
}
|
||||
|
||||
if (results.length) {
|
||||
return {
|
||||
ok: true,
|
||||
sent: results.filter(function (result) {
|
||||
@@ -373,6 +413,7 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
return candidateConnectionId === connectionId || candidateClientId === connectionId;
|
||||
}) || null;
|
||||
const liveDeviceId = String(liveConnection && liveConnection.deviceId || '').trim();
|
||||
const livePlayerDeviceId = String(liveConnection && liveConnection.playerDeviceId || '').trim();
|
||||
const livePlayerBaseUrl = normalizeExplicitPlayerBaseUrl(liveConnection && liveConnection.playerPublicBaseUrl);
|
||||
|
||||
if (!connectionId && !submittedPlayerBaseUrl && !legacyDeviceId && !liveDeviceId) {
|
||||
@@ -382,7 +423,9 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
return res.status(400).json({ error: 'Target screen is required' });
|
||||
}
|
||||
|
||||
if (livePlayerBaseUrl || submittedPlayerBaseUrl) {
|
||||
if (livePlayerDeviceId) {
|
||||
physicalPlayerId = livePlayerDeviceId;
|
||||
} else if (livePlayerBaseUrl || submittedPlayerBaseUrl) {
|
||||
const [playerRows] = await pool.query(
|
||||
'SELECT identifier, public_base_url FROM d_players WHERE public_base_url = ? LIMIT 1',
|
||||
[livePlayerBaseUrl || submittedPlayerBaseUrl]
|
||||
@@ -523,7 +566,7 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
|| candidateDeviceId === physicalPlayerId;
|
||||
}) || liveConnections[0] || null
|
||||
: null;
|
||||
const sourcePlayerBaseUrl = registeredPlayerBaseUrl
|
||||
const sourcePlayerBaseUrl = livePlayerDeviceId ? '' : registeredPlayerBaseUrl
|
||||
|| normalizeExplicitPlayerBaseUrl(matchedLiveConnection && matchedLiveConnection.playerPublicBaseUrl);
|
||||
if (sourcePlayerBaseUrl) {
|
||||
targetPlayerUrl = `${sourcePlayerBaseUrl}/screen/${encodeURIComponent(targetScreenSlug)}`;
|
||||
@@ -537,7 +580,13 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
connectionId: connectionId || null,
|
||||
screenSlug: targetScreenSlug
|
||||
});
|
||||
if (sourcePlayerBaseUrl) {
|
||||
if (livePlayerDeviceId) {
|
||||
await forwardPlayerCommandForConnections(slug, liveConnections, {
|
||||
command: 'redirect',
|
||||
url: targetPlayerUrl,
|
||||
moveToken: moveToken || null
|
||||
}, connectionId || physicalPlayerId || undefined, physicalPlayerId || null);
|
||||
} else if (sourcePlayerBaseUrl) {
|
||||
await forwardPlayerCommandForPlayerBaseUrl(slug, sourcePlayerBaseUrl, {
|
||||
command: 'redirect',
|
||||
url: targetPlayerUrl,
|
||||
@@ -585,9 +634,12 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
: null;
|
||||
const livePlayerBaseUrl = normalizeExplicitPlayerBaseUrl(liveConnection && liveConnection.playerPublicBaseUrl);
|
||||
const targetPlayerBaseUrl = livePlayerBaseUrl || playerBaseUrl;
|
||||
const result = targetPlayerBaseUrl
|
||||
? await forwardPlayerCommandForPlayerBaseUrl(slug, targetPlayerBaseUrl, commandPayload, connectionId, null)
|
||||
: await forwardPlayerCommandForConnections(slug, liveConnections, commandPayload, connectionId, null);
|
||||
const liveDeviceId = String(liveConnection && liveConnection.playerDeviceId || '').trim();
|
||||
const result = liveDeviceId && typeof forwardPlayerCommandToDevice === 'function'
|
||||
? await forwardPlayerCommandToDevice(liveDeviceId, Object.assign({ screenSlug: slug }, commandPayload, connectionId ? { connectionId: connectionId } : {}))
|
||||
: targetPlayerBaseUrl
|
||||
? await forwardPlayerCommandForPlayerBaseUrl(slug, targetPlayerBaseUrl, commandPayload, connectionId, null)
|
||||
: await forwardPlayerCommandForConnections(slug, liveConnections, commandPayload, connectionId, null);
|
||||
|
||||
if (typeof broadcastDashboardState === 'function') {
|
||||
await broadcastDashboardState();
|
||||
|
||||
@@ -15,6 +15,7 @@ module.exports = function registerManageRoutes(app, deps) {
|
||||
const getScreenConnections = deps.getScreenConnections;
|
||||
const forwardPlayerCommand = deps.forwardPlayerCommand;
|
||||
const forwardPlayerCommandToBaseUrl = deps.forwardPlayerCommandToBaseUrl;
|
||||
const forwardPlayerCommandToDevice = deps.forwardPlayerCommandToDevice;
|
||||
const requirePermission = deps.requirePermission;
|
||||
|
||||
const SCREEN_NAME_MAX_LENGTH = 255;
|
||||
@@ -88,12 +89,30 @@ module.exports = function registerManageRoutes(app, deps) {
|
||||
}
|
||||
|
||||
async function forwardPlayerCommandForConnections(slug, connections, commandPayload) {
|
||||
const targetBaseUrls = await resolvePlayerBaseUrlsForConnections(connections);
|
||||
const connectionList = Array.isArray(connections) ? connections : [];
|
||||
const remoteDeviceIds = Array.from(new Set(connectionList.map(function (connection) {
|
||||
return String(connection && connection.playerDeviceId || '').trim();
|
||||
}).filter(Boolean)));
|
||||
const localConnections = connectionList.filter(function (connection) {
|
||||
return !String(connection && connection.playerDeviceId || '').trim();
|
||||
});
|
||||
const results = [];
|
||||
if (remoteDeviceIds.length && typeof forwardPlayerCommandToDevice === 'function') {
|
||||
const remoteResults = await Promise.allSettled(remoteDeviceIds.map(function (deviceId) {
|
||||
return forwardPlayerCommandToDevice(deviceId, Object.assign({ screenSlug: slug }, commandPayload || {}));
|
||||
}));
|
||||
results.push.apply(results, remoteResults);
|
||||
}
|
||||
|
||||
const targetBaseUrls = await resolvePlayerBaseUrlsForConnections(localConnections);
|
||||
if (targetBaseUrls.length && typeof forwardPlayerCommandToBaseUrl === 'function') {
|
||||
const results = await Promise.allSettled(targetBaseUrls.map(function (targetBaseUrl) {
|
||||
const localResults = await Promise.allSettled(targetBaseUrls.map(function (targetBaseUrl) {
|
||||
return forwardPlayerCommandToBaseUrl(targetBaseUrl, slug, commandPayload);
|
||||
}));
|
||||
results.push.apply(results, localResults);
|
||||
}
|
||||
|
||||
if (results.length) {
|
||||
return {
|
||||
ok: true,
|
||||
sent: results.filter(function (result) {
|
||||
|
||||
@@ -180,6 +180,7 @@ function registerSignageRoutes(app, deps) {
|
||||
common: deps.common,
|
||||
forwardPlayerCommand: deps.playerActionService.forwardPlayerCommand,
|
||||
forwardPlayerCommandToBaseUrl: deps.playerActionService.forwardPlayerCommandToBaseUrl,
|
||||
forwardPlayerCommandToDevice: deps.playerActionService.forwardPlayerCommandToDevice,
|
||||
getScreenConnections: deps.playerActionService.getScreenConnections,
|
||||
isClientNameAvailable: deps.isClientNameAvailable,
|
||||
findAvailableClientName: deps.findAvailableClientName,
|
||||
|
||||
@@ -4,8 +4,7 @@ function buildScreenFormViewModel(screen, data, message, currentUser, isEdit) {
|
||||
const viewScreen = Object.assign({
|
||||
name: '',
|
||||
slug: '',
|
||||
playlist_id: null,
|
||||
player_urls: []
|
||||
playlist_id: null
|
||||
}, screen || {});
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// 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;
|
||||
@@ -12,14 +11,6 @@ module.exports = function registerScreensRoutes(app, deps) {
|
||||
const { buildPagination } = require('../../../lib/pagination');
|
||||
const requirePermission = deps.requirePermission;
|
||||
|
||||
function isRecentPlayerRegistration(player, staleSeconds) {
|
||||
const lastSeenAt = player && player.last_seen_at;
|
||||
const lastSeenAtValue = lastSeenAt instanceof Date ? lastSeenAt.getTime() : new Date(lastSeenAt).getTime();
|
||||
const cutoffTime = Date.now() - Math.max(30, Number(staleSeconds || 60)) * 1000;
|
||||
|
||||
return Number.isFinite(lastSeenAtValue) && lastSeenAtValue >= cutoffTime;
|
||||
}
|
||||
|
||||
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');
|
||||
@@ -90,26 +81,6 @@ 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 : []).filter(function (player) {
|
||||
return isRecentPlayerRegistration(player, 60);
|
||||
}).map(function (player) {
|
||||
const baseUrl = String(player && player.public_base_url || '').trim().replace(/\/$/, '');
|
||||
const playerUrl = screenPlayerUrl(screen && screen.slug ? screen.slug : '', baseUrl);
|
||||
return {
|
||||
identifier: String(player && player.identifier || '').trim(),
|
||||
public_base_url: baseUrl || null,
|
||||
player_url: playerUrl || null
|
||||
};
|
||||
}).sort(function (left, right) {
|
||||
return String(left && left.identifier || '').localeCompare(String(right && right.identifier || ''), undefined, { sensitivity: 'base', numeric: true });
|
||||
});
|
||||
}
|
||||
|
||||
function buildLauncherContent(templatePath, playerUrl) {
|
||||
const template = fs.readFileSync(templatePath, 'utf8');
|
||||
const normalizedPlayerUrl = String(playerUrl || '').trim();
|
||||
@@ -139,10 +110,6 @@ module.exports = function registerScreensRoutes(app, deps) {
|
||||
if (!screen) {
|
||||
return res.status(404).send('Screen not found');
|
||||
}
|
||||
screen.player_urls = await buildScreenPlayerUrls(screen);
|
||||
if (screen.player_urls.length) {
|
||||
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));
|
||||
@@ -168,10 +135,6 @@ module.exports = function registerScreensRoutes(app, deps) {
|
||||
if (!screen) {
|
||||
return res.status(404).send('Screen not found');
|
||||
}
|
||||
screen.player_urls = await buildScreenPlayerUrls(screen);
|
||||
if (screen.player_urls.length) {
|
||||
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));
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2>{{#if isEdit}}Edit screen group - {{screen.name}}{{else}}Add screen group{{/if}}</h2>
|
||||
<p>{{#if isEdit}}Update the display name and assigned playlist.{{else}}Register a player endpoint and connect it to a playlist.{{/if}}</p>
|
||||
<p>{{#if isEdit}}Update the display name and assigned playlist.{{else}}Create a screen group and connect it to a playlist.{{/if}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="col-12">
|
||||
<div class="card card-outline card-primary admin-form-card h-100">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Screen group details</h3>
|
||||
@@ -40,39 +40,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="card card-outline card-secondary admin-form-card h-100">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Player URLs</h3>
|
||||
</div>
|
||||
<div class="card-body table-responsive p-0">
|
||||
{{#if screen.player_urls.length}}
|
||||
<table class="table table-striped w-100 mb-0" data-table-searchable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Player</th>
|
||||
<th>URL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each screen.player_urls}}
|
||||
<tr>
|
||||
<td data-label="Player" class="text-break">{{identifier}}</td>
|
||||
<td class="text-break">
|
||||
{{#if player_url}}
|
||||
<a href="{{player_url}}" target="_blank" rel="noreferrer">{{player_url}}</a>
|
||||
{{else}}
|
||||
<span class="text-muted">Not available</span>
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
<p class="mb-0 p-3 text-muted">No player URLs are available yet.</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user