Files
pulse-signage/src/data/client-name-check.js
T
2026-07-25 02:29:19 +01:00

118 lines
3.7 KiB
JavaScript

const crypto = require('crypto');
function normalizeClientName(value) {
return String(value || '').trim();
}
function normalizeDeviceId(value) {
return String(value || '').trim().replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 128);
}
function collectLiveConnections(liveConnections) {
return Array.isArray(liveConnections) ? liveConnections : [];
}
async function isClientNameAvailable(pool, clientName, excludeDeviceId, liveConnections) {
const normalizedName = normalizeClientName(clientName);
if (!normalizedName) {
return false;
}
const normalizedDeviceId = normalizeDeviceId(excludeDeviceId);
const live = collectLiveConnections(liveConnections);
const lowerName = normalizedName.toLowerCase();
try {
if (pool) {
const [deviceRows] = await pool.query(
`SELECT device_id
FROM player_onboarding_devices
WHERE client_name IS NOT NULL
AND TRIM(client_name) <> ''
AND LOWER(TRIM(client_name)) = LOWER(TRIM(?))
AND device_id <> ?
LIMIT 1`,
[normalizedName, normalizedDeviceId]
);
if (deviceRows.length) {
return false;
}
}
for (const connection of live) {
const existingName = normalizeClientName(connection && connection.clientName ? connection.clientName : '');
if (!existingName || existingName.toLowerCase() !== lowerName) {
continue;
}
const existingDeviceId = normalizeDeviceId(connection && (connection.deviceId || connection.clientId) ? (connection.deviceId || connection.clientId) : '');
if (normalizedDeviceId && existingDeviceId && existingDeviceId === normalizedDeviceId) {
continue;
}
return false;
}
return true;
} catch (_error) {
for (const connection of live) {
const existingName = normalizeClientName(connection && connection.clientName ? connection.clientName : '');
if (!existingName || existingName.toLowerCase() !== lowerName) {
continue;
}
const existingDeviceId = normalizeDeviceId(connection && (connection.deviceId || connection.clientId) ? (connection.deviceId || connection.clientId) : '');
if (normalizedDeviceId && existingDeviceId && existingDeviceId === normalizedDeviceId) {
continue;
}
return false;
}
return true;
}
}
function buildClientNameLockName(clientName) {
return `ps_client_name_${crypto.createHash('sha1').update(String(clientName || '').trim().toLowerCase()).digest('hex')}`;
}
async function withClientNameReservation(pool, clientName, handler) {
if (!pool || typeof pool.getConnection !== 'function') {
return handler();
}
const normalizedName = normalizeClientName(clientName);
if (!normalizedName) {
return handler();
}
const connection = await pool.getConnection();
const lockName = buildClientNameLockName(normalizedName);
let lockAcquired = false;
try {
const [lockRows] = await connection.query('SELECT GET_LOCK(?, 5) AS lock_result', [lockName]);
const lockResult = lockRows && lockRows[0] ? Number(lockRows[0].lock_result) : 0;
if (lockResult !== 1) {
const error = new Error('Client name is busy. Please try again.');
error.statusCode = 409;
throw error;
}
lockAcquired = true;
return await handler();
} finally {
if (lockAcquired) {
try {
await connection.query('SELECT RELEASE_LOCK(?)', [lockName]);
} catch (_error) {}
}
connection.release();
}
}
module.exports = {
normalizeClientName: normalizeClientName,
normalizeDeviceId: normalizeDeviceId,
collectLiveConnections: collectLiveConnections,
isClientNameAvailable: isClientNameAvailable,
withClientNameReservation: withClientNameReservation
};