Bump version to 1.4.1 and tighten client handling

This commit is contained in:
2026-07-21 01:20:12 +01:00
parent 2ea8d389fa
commit 8393923c5a
15 changed files with 440 additions and 85 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ function enrichScreensWithConnections(screens, connectionsBySlug, onboardingName
return (screens || []).map(function (screen) {
const connectionState = connectionsBySlug[screen.slug] || { count: 0, connections: [] };
return Object.assign({}, screen, {
client_name: onboardingNameBySlug[screen.slug] || screen.client_name || null,
client_name: onboardingNameBySlug[screen.slug] || null,
player_connection_count: connectionState.count || 0,
player_connections: Array.isArray(connectionState.connections) ? connectionState.connections : []
});
+39 -2
View File
@@ -34,9 +34,45 @@ function createPlayerActionService(options) {
});
}
async function getScreenDeleteBlockMessage(pool, screen) {
async function getScreenConnections(slug) {
const response = await fetch(`${playerInternalBaseUrl}/api/screens/${encodeURIComponent(slug)}/connections`, {
method: 'GET',
headers: {
Accept: 'application/json'
}
});
if (!response.ok) {
const errorText = await response.text().catch(function () { return ''; });
const error = new Error(errorText || `Unable to load screen connections for ${slug}.`);
error.statusCode = response.status;
throw error;
}
return response.json().catch(function () {
return { connections: [] };
});
}
async function getScreenDeleteBlockMessage(pool, screen, getScreenConnections) {
const [rows] = await pool.query('SELECT COUNT(*) AS ref_count FROM player_onboarding_devices WHERE screen_id = ?', [screen.id]);
return Number(rows[0] && rows[0].ref_count) > 0 ? 'This screen is still linked to onboarding devices.' : '';
if (Number(rows[0] && rows[0].ref_count) > 0) {
return 'This screen is still linked to onboarding devices.';
}
if (typeof getScreenConnections === 'function' && String(screen && screen.slug ? screen.slug : '').trim()) {
try {
const response = await getScreenConnections(screen.slug);
const liveConnections = Array.isArray(response && response.connections) ? response.connections : [];
if (liveConnections.length > 0) {
return 'This screen is still in use by connected players.';
}
} catch (_error) {
// Keep the delete guard based on onboarding references if live connection lookup fails.
}
}
return '';
}
async function getSlideDeleteBlockMessage(pool, slide) {
@@ -61,6 +97,7 @@ function createPlayerActionService(options) {
return {
forwardPlayerCommand: forwardPlayerCommand,
getScreenConnections: getScreenConnections,
getScreenDeleteBlockMessage: getScreenDeleteBlockMessage,
getSlideDeleteBlockMessage: getSlideDeleteBlockMessage,
getTemplateDeleteBlockMessage: getTemplateDeleteBlockMessage,
@@ -399,7 +399,14 @@
}).then(function (response) {
if (!response.ok) {
return response.text().then(function (text) {
throw new Error(text || 'Unable to rename client.');
var message = text || 'Unable to rename client.';
try {
var payload = JSON.parse(text);
message = payload && (payload.error || payload.message) ? String(payload.error || payload.message) : message;
} catch (_error) {
// fall back to the raw text body
}
throw new Error(message);
});
}
return response.json().catch(function () {
+1 -1
View File
@@ -40,7 +40,7 @@
function getMessageVariant(message, fallbackVariant) {
var text = String(message || '').trim();
if (/^(unable to delete|cannot delete|can't delete)/i.test(text)) {
if (/^(unable to delete|cannot delete|can't delete)|\bstill (?:in use|linked|assigned|used)\b/i.test(text)) {
return 'danger';
}
return String(fallbackVariant || 'success').trim().toLowerCase() || 'success';
+2 -1
View File
@@ -15,6 +15,7 @@ module.exports = function registerAdminManageRoutes(app, deps) {
const notifyPlayerScreens = deps.notifyPlayerScreens;
const broadcastDashboardState = deps.broadcastDashboardState;
const getScreenDeleteBlockMessage = deps.getScreenDeleteBlockMessage;
const getScreenConnections = deps.getScreenConnections;
const getPlaylistDeleteBlockMessage = deps.getPlaylistDeleteBlockMessage;
const forwardPlayerCommand = deps.forwardPlayerCommand;
const PLAYER_PUBLIC_BASE_URL = deps.playerPublicBaseUrl;
@@ -529,7 +530,7 @@ module.exports = function registerAdminManageRoutes(app, deps) {
if (!screen) {
return res.status(404).send('Screen not found');
}
const blockMessage = await getScreenDeleteBlockMessage(pool, screen);
const blockMessage = await getScreenDeleteBlockMessage(pool, screen, getScreenConnections);
if (blockMessage) {
return res.redirect('/admin/screens?message=' + encodeURIComponent(blockMessage));
}
+102
View File
@@ -1,6 +1,8 @@
module.exports = function registerAdminScreenCommandRoutes(app, deps) {
const pool = deps.pool;
const forwardPlayerCommand = deps.forwardPlayerCommand;
const getScreenConnections = deps.getScreenConnections;
const isClientNameAvailable = deps.isClientNameAvailable;
app.post('/admin/screens/:slug/commands', async function (req, res, next) {
try {
@@ -23,6 +25,106 @@ module.exports = function registerAdminScreenCommandRoutes(app, deps) {
return res.status(404).json({ error: 'Screen not found' });
}
if (command === 'setclientname') {
const deviceId = String((req.body && (req.body.deviceId || req.body.clientId)) || req.query.deviceId || req.query.clientId || '').trim();
const clientName = String((req.body && req.body.clientName) || req.query.clientName || '').trim();
if (!deviceId) {
return res.status(400).json({ error: 'Device ID is required' });
}
if (!clientName) {
return res.status(400).json({ error: 'Client name is required' });
}
const [currentRows] = await pool.query(
`SELECT client_name
FROM player_onboarding_devices
WHERE device_id = ?
LIMIT 1`,
[deviceId]
);
const onboardingRow = currentRows[0] || null;
const currentName = String(onboardingRow && onboardingRow.client_name ? onboardingRow.client_name : '').trim();
if (currentName && currentName.toLowerCase() === clientName.toLowerCase()) {
return res.json({
screen: screenRows[0],
screenSlug: slug,
command: command,
connectionId: connectionId || null,
deviceId: deviceId,
clientName: currentName,
ok: true,
unchanged: true
});
}
let liveConnections = [];
try {
const [screenSlugs] = await pool.query('SELECT slug FROM screens ORDER BY slug ASC');
const liveResults = await Promise.all((screenSlugs || []).map(async function (row) {
const screenSlug = String(row && row.slug ? row.slug : '').trim();
if (!screenSlug || typeof getScreenConnections !== 'function') {
return [];
}
try {
const liveResponse = await getScreenConnections(screenSlug);
return Array.isArray(liveResponse && liveResponse.connections) ? liveResponse.connections : [];
} catch (_error) {
return [];
}
}));
liveConnections = liveResults.flat();
} catch (_error) {
liveConnections = [];
}
const available = typeof isClientNameAvailable === 'function'
? await isClientNameAvailable(pool, clientName, deviceId, liveConnections)
: true;
if (!available) {
return res.status(409).json({ error: 'Client name already exists.' });
}
if (!onboardingRow) {
await forwardPlayerCommand(slug, {
command: command,
clientName: clientName,
clientId: connectionId || deviceId || null,
deviceId: deviceId || null
}, connectionId || deviceId || undefined);
return res.json({
screen: screenRows[0],
screenSlug: slug,
command: command,
connectionId: connectionId || null,
deviceId: deviceId,
clientName: clientName,
ok: true,
liveOnly: true
});
}
const [updateResult] = await pool.query(
`UPDATE player_onboarding_devices pod
JOIN screens s ON s.id = pod.screen_id
SET pod.client_name = ?, pod.modified_at = CURRENT_TIMESTAMP
WHERE s.slug = ? AND pod.device_id = ?`,
[clientName, slug, deviceId]
);
if (!updateResult.affectedRows) {
return res.status(404).json({ error: 'Client not found' });
}
return res.json({
screen: screenRows[0],
screenSlug: slug,
command: command,
connectionId: connectionId || null,
deviceId: deviceId,
clientName: clientName,
ok: true
});
}
const commandPayload = req.body && typeof req.body === 'object' && !Array.isArray(req.body)
? Object.assign({}, req.body, { command: command })
: { command: command };