Release v2.10.3
This commit is contained in:
@@ -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');
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Audit event definitions and data access helpers for administrative activity.
|
||||
|
||||
const AUDIT_EVENT_CATEGORIES = Object.freeze({
|
||||
AUTHENTICATION: 'authentication',
|
||||
SECURITY: 'security',
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
// Persistent player registration and heartbeat helpers shared by the web app and bridge.
|
||||
|
||||
function normalizeDeviceId(value) {
|
||||
return String(value || '')
|
||||
.trim()
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user