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
+2
View File
@@ -1,3 +1,5 @@
// Load and expose the announcement icon catalog used by the editor and player.
const fs = require('fs');
const path = require('path');
+9 -7
View File
@@ -1,3 +1,5 @@
// Application-wide settings defaults and persistence helpers.
const { DEFAULT_ANNOUNCEMENT_ICON_KEYS } = require('./announcement-icons');
const SETTING_DEFINITIONS = [
@@ -35,13 +37,13 @@ const SETTING_DEFINITIONS = [
{ key: 'announcements.suggested_icons', type: 'string_array', defaultValue: DEFAULT_ANNOUNCEMENT_ICON_KEYS.slice() },
{ key: 'player.default_slide_duration_seconds', type: 'integer', min: 1, defaultValue: 10 },
{ key: 'player.default_fade_between_slides', type: 'boolean', defaultValue: true },
{ key: 'player.skip_unavailable_rtmp', type: 'boolean', defaultValue: true }
,{ key: 'data-sources.rss_default_interval_value', type: 'integer', min: 1, defaultValue: 60 }
,{ key: 'data-sources.rss_default_interval_unit', type: 'enum', values: ['seconds', 'minutes', 'hours'], defaultValue: 'minutes' }
,{ key: 'data-sources.api_default_interval_value', type: 'integer', min: 1, defaultValue: 60 }
,{ key: 'data-sources.api_default_interval_unit', type: 'enum', values: ['seconds', 'minutes', 'hours'], defaultValue: 'minutes' }
,{ key: 'weather.open_meteo_api_key', type: 'string', defaultValue: '' }
,{ key: 'weather.pirate_weather_api_key', type: 'string', defaultValue: '' }
{ key: 'player.skip_unavailable_rtmp', type: 'boolean', defaultValue: true },
{ key: 'data-sources.rss_default_interval_value', type: 'integer', min: 1, defaultValue: 60 },
{ key: 'data-sources.rss_default_interval_unit', type: 'enum', values: ['seconds', 'minutes', 'hours'], defaultValue: 'minutes' },
{ key: 'data-sources.api_default_interval_value', type: 'integer', min: 1, defaultValue: 60 },
{ key: 'data-sources.api_default_interval_unit', type: 'enum', values: ['seconds', 'minutes', 'hours'], defaultValue: 'minutes' },
{ key: 'weather.open_meteo_api_key', type: 'string', defaultValue: '' },
{ key: 'weather.pirate_weather_api_key', type: 'string', defaultValue: '' }
];
const DEFINITIONS_BY_KEY = new Map(SETTING_DEFINITIONS.map(function (definition) {
+2
View File
@@ -1,3 +1,5 @@
// Audit event definitions and data access helpers for administrative activity.
const AUDIT_EVENT_CATEGORIES = Object.freeze({
AUTHENTICATION: 'authentication',
SECURITY: 'security',
+24 -3
View File
@@ -23,6 +23,9 @@ async function isClientNameAvailable(pool, clientName, excludeDeviceId, liveConn
const normalizedDeviceId = normalizeDeviceId(excludeDeviceId);
const live = collectLiveConnections(liveConnections);
const lowerName = normalizedName.toLowerCase();
const liveDeviceIds = new Set(live.map(function (connection) {
return normalizeDeviceId(connection && (connection.deviceId || connection.clientId));
}).filter(Boolean));
try {
if (pool) {
@@ -32,12 +35,13 @@ async function isClientNameAvailable(pool, clientName, excludeDeviceId, liveConn
WHERE client_name IS NOT NULL
AND TRIM(client_name) <> ''
AND LOWER(TRIM(client_name)) = LOWER(TRIM(?))
AND device_id <> ?
LIMIT 1`,
AND device_id <> ?`,
[normalizedName, normalizedDeviceId]
);
if (deviceRows.length) {
if ((deviceRows || []).some(function (row) {
return liveDeviceIds.has(normalizeDeviceId(row && row.device_id));
})) {
return false;
}
}
@@ -72,6 +76,22 @@ async function isClientNameAvailable(pool, clientName, excludeDeviceId, liveConn
}
}
async function findAvailableClientName(pool, clientName, excludeDeviceId, liveConnections) {
const normalizedName = normalizeClientName(clientName);
if (!normalizedName) {
return '';
}
for (let suffix = 0; suffix < 1000; suffix += 1) {
const candidate = suffix === 0 ? normalizedName : `${normalizedName} (${suffix})`;
if (await isClientNameAvailable(pool, candidate, excludeDeviceId, liveConnections)) {
return candidate;
}
}
return '';
}
function buildClientNameLockName(clientName) {
return `ps_client_name_${crypto.createHash('sha1').update(String(clientName || '').trim().toLowerCase()).digest('hex')}`;
}
@@ -116,5 +136,6 @@ module.exports = {
normalizeDeviceId: normalizeDeviceId,
collectLiveConnections: collectLiveConnections,
isClientNameAvailable: isClientNameAvailable,
findAvailableClientName: findAvailableClientName,
withClientNameReservation: withClientNameReservation
};
+2
View File
@@ -1,3 +1,5 @@
// Persistent player registration and heartbeat helpers shared by the web app and bridge.
function normalizeDeviceId(value) {
return String(value || '')
.trim()
+2
View File
@@ -1,3 +1,5 @@
// QR code generation helpers for player onboarding and administrative links.
const path = require('path');
const QRCodeStyling = require(path.join(__dirname, '..', 'web', 'public', 'vendor', 'qr-code-styling', 'qr-code-styling.js'));
const QR_PNG_WIDTH = 2048;