Release v2.10.3
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m13s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 31s

This commit is contained in:
2026-08-29 01:56:45 +01:00
parent 30814f3f46
commit 3f4f57020a
60 changed files with 1075 additions and 523 deletions
+75 -34
View File
@@ -9,6 +9,7 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
const forwardPlayerCommandToBaseUrl = deps.forwardPlayerCommandToBaseUrl;
const getScreenConnections = deps.getScreenConnections;
const isClientNameAvailable = deps.isClientNameAvailable;
const findAvailableClientName = deps.findAvailableClientName;
const withClientNameReservation = deps.withClientNameReservation;
const broadcastDashboardState = deps.broadcastDashboardState;
const requirePermission = deps.requirePermission;
@@ -297,8 +298,8 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
liveConnections = [];
}
const available = await isClientNameAvailable(pool, clientName, bindingDeviceId, liveConnections);
if (!available) {
const selectedClientName = await findAvailableClientName(pool, clientName, bindingDeviceId, liveConnections);
if (!selectedClientName) {
return res.status(409).json({ error: 'Client name already exists.' });
}
@@ -306,14 +307,14 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
if (playerBaseUrl) {
await forwardPlayerCommandForPlayerBaseUrl(slug, playerBaseUrl, {
command: command,
clientName: clientName,
clientName: selectedClientName,
clientId: connectionId || deviceId || null,
deviceId: deviceId || null
}, connectionId || deviceId || undefined, deviceId || null);
} else {
await forwardPlayerCommandForConnections(slug, liveConnections, {
command: command,
clientName: clientName,
clientName: selectedClientName,
clientId: connectionId || deviceId || null,
deviceId: deviceId || null
}, connectionId || deviceId || undefined, deviceId || null);
@@ -329,7 +330,7 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
command: command,
connectionId: connectionId || null,
deviceId: deviceId,
clientName: clientName,
clientName: selectedClientName,
ok: true,
liveOnly: true
});
@@ -339,7 +340,7 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
`UPDATE d_onboarding_devices
SET client_name = ?, modified_at = CURRENT_TIMESTAMP
WHERE device_id = ?`,
[clientName, bindingDeviceId]
[selectedClientName, bindingDeviceId]
);
if (!updateResult.affectedRows) {
return res.status(404).json({ error: 'Client not found' });
@@ -351,7 +352,7 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
command: command,
connectionId: connectionId || null,
deviceId: deviceId,
clientName: clientName,
clientName: selectedClientName,
ok: true
});
});
@@ -360,42 +361,68 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
if (command === 'moveclient') {
const legacyDeviceId = String((req.body && req.body.deviceId) || req.query.deviceId || '').trim();
let physicalPlayerId = '';
let registeredPlayerBaseUrl = '';
const tabClientId = String((req.body && req.body.clientId) || req.query.clientId || '').trim();
const clientName = String((req.body && req.body.clientName) || req.query.clientName || '').trim();
const targetScreenSlug = String((req.body && (req.body.targetScreenSlug || req.body.screenSlug)) || req.query.targetScreenSlug || req.query.screenSlug || '').trim();
const submittedPlayerBaseUrl = normalizeExplicitPlayerBaseUrl(playerBaseUrl);
const liveConnections = await fetchScreenConnections(slug);
const liveConnection = liveConnections.find(function (connection) {
const candidateConnectionId = String(connection && connection.id || '').trim();
const candidateClientId = String(connection && connection.clientId || '').trim();
return candidateConnectionId === connectionId || candidateClientId === connectionId;
}) || null;
const liveDeviceId = String(liveConnection && liveConnection.deviceId || '').trim();
const livePlayerBaseUrl = normalizeExplicitPlayerBaseUrl(liveConnection && liveConnection.playerPublicBaseUrl);
if (!connectionId && !submittedPlayerBaseUrl && !legacyDeviceId) {
if (!connectionId && !submittedPlayerBaseUrl && !legacyDeviceId && !liveDeviceId) {
return res.status(400).json({ error: 'Client connection is required' });
}
if (!targetScreenSlug) {
return res.status(400).json({ error: 'Target screen is required' });
}
if (submittedPlayerBaseUrl) {
if (livePlayerBaseUrl || submittedPlayerBaseUrl) {
const [playerRows] = await pool.query(
'SELECT identifier FROM d_players WHERE public_base_url = ? LIMIT 1',
[submittedPlayerBaseUrl]
'SELECT identifier, public_base_url FROM d_players WHERE public_base_url = ? LIMIT 1',
[livePlayerBaseUrl || submittedPlayerBaseUrl]
);
const registeredPlayerId = String(playerRows[0] && playerRows[0].identifier || '').trim();
if (registeredPlayerId) {
physicalPlayerId = registeredPlayerId;
}
const registeredPlayer = playerRows[0] || null;
physicalPlayerId = String(registeredPlayer && registeredPlayer.identifier || '').trim();
registeredPlayerBaseUrl = normalizeExplicitPlayerBaseUrl(registeredPlayer && registeredPlayer.public_base_url);
} else if (legacyDeviceId || liveDeviceId) {
const [playerRows] = await pool.query(
'SELECT identifier, public_base_url FROM d_players WHERE identifier = ? LIMIT 1',
[legacyDeviceId || liveDeviceId]
);
const registeredPlayer = playerRows[0] || null;
physicalPlayerId = String(registeredPlayer && registeredPlayer.identifier || '').trim();
registeredPlayerBaseUrl = normalizeExplicitPlayerBaseUrl(registeredPlayer && registeredPlayer.public_base_url);
}
physicalPlayerId = physicalPlayerId || legacyDeviceId;
if (!physicalPlayerId) {
return res.status(400).json({ error: 'Registered player identity is required' });
}
const [currentRows] = await pool.query(
`SELECT d.client_name, s.slug AS current_screen_slug
FROM d_onboarding_devices d
LEFT JOIN d_screens s ON s.id = d.screen_id
WHERE d.device_id = ?
LIMIT 1`,
[physicalPlayerId]
);
let [currentRows] = tabClientId
? await pool.query(
`SELECT d.client_name, s.slug AS current_screen_slug
FROM d_onboarding_devices d
LEFT JOIN d_screens s ON s.id = d.screen_id
WHERE d.device_id = ?
LIMIT 1`,
[tabClientId]
)
: [[]];
if (!currentRows.length && physicalPlayerId && physicalPlayerId !== tabClientId) {
[currentRows] = await pool.query(
`SELECT d.client_name, s.slug AS current_screen_slug
FROM d_onboarding_devices d
LEFT JOIN d_screens s ON s.id = d.screen_id
WHERE d.device_id = ?
LIMIT 1`,
[physicalPlayerId]
);
}
const onboardingRow = currentRows[0] || null;
const resolvedClientName = String(clientName || onboardingRow && onboardingRow.client_name || '').trim();
const currentScreenSlug = String(onboardingRow && onboardingRow.current_screen_slug || '').trim();
@@ -418,7 +445,6 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
});
}
const liveConnections = await fetchScreenConnections(slug);
let status = null;
if (onboardingRow) {
const [targetRows] = await pool.query('SELECT id, name, slug FROM d_screens WHERE slug = ? LIMIT 1', [targetScreenSlug]);
@@ -485,14 +511,20 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
};
}
let targetPlayerUrl = '';
const liveConnection = Array.isArray(liveConnections)
const matchedLiveConnection = Array.isArray(liveConnections)
? liveConnections.find(function (connection) {
const candidateConnectionId = String(connection && (connection.id || connection.clientId) || '').trim();
const candidateConnectionId = String(connection && connection.id || '').trim();
const candidateClientId = String(connection && connection.clientId || '').trim();
const candidateDeviceId = String(connection && connection.deviceId || '').trim();
return candidateConnectionId === connectionId || candidateConnectionId === physicalPlayerId || candidateDeviceId === physicalPlayerId;
return candidateConnectionId === connectionId
|| candidateClientId === connectionId
|| candidateConnectionId === physicalPlayerId
|| candidateClientId === physicalPlayerId
|| candidateDeviceId === physicalPlayerId;
}) || liveConnections[0] || null
: null;
const sourcePlayerBaseUrl = normalizeExplicitPlayerBaseUrl(playerBaseUrl || (liveConnection && liveConnection.playerPublicBaseUrl) || '');
const sourcePlayerBaseUrl = registeredPlayerBaseUrl
|| normalizeExplicitPlayerBaseUrl(matchedLiveConnection && matchedLiveConnection.playerPublicBaseUrl);
if (sourcePlayerBaseUrl) {
targetPlayerUrl = `${sourcePlayerBaseUrl}/screen/${encodeURIComponent(targetScreenSlug)}`;
}
@@ -505,8 +537,8 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
connectionId: connectionId || null,
screenSlug: targetScreenSlug
});
if (playerBaseUrl) {
await forwardPlayerCommandForPlayerBaseUrl(slug, playerBaseUrl, {
if (sourcePlayerBaseUrl) {
await forwardPlayerCommandForPlayerBaseUrl(slug, sourcePlayerBaseUrl, {
command: 'redirect',
url: targetPlayerUrl,
moveToken: moveToken || null
@@ -544,8 +576,17 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
}
const liveConnections = await fetchScreenConnections(slug);
const result = playerBaseUrl
? await forwardPlayerCommandForPlayerBaseUrl(slug, playerBaseUrl, commandPayload, connectionId, null)
const liveConnection = connectionId
? liveConnections.find(function (connection) {
const candidateConnectionId = String(connection && connection.id || '').trim();
const candidateClientId = String(connection && connection.clientId || '').trim();
return candidateConnectionId === connectionId || candidateClientId === connectionId;
})
: 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);
if (typeof broadcastDashboardState === 'function') {
+2
View File
@@ -1,3 +1,5 @@
// Web routes for pairing players and completing onboarding device bindings.
const { createRequestAuthHeaders } = require('#src/request-auth');
const { fetchPlayerRegistrations } = require('#src/data/player-registry');
const { renderView } = require('../view');
+1
View File
@@ -182,6 +182,7 @@ function registerSignageRoutes(app, deps) {
forwardPlayerCommandToBaseUrl: deps.playerActionService.forwardPlayerCommandToBaseUrl,
getScreenConnections: deps.playerActionService.getScreenConnections,
isClientNameAvailable: deps.isClientNameAvailable,
findAvailableClientName: deps.findAvailableClientName,
withClientNameReservation: deps.withClientNameReservation,
broadcastDashboardState: deps.broadcastDashboardState,
requirePermission: deps.requirePermission