120 lines
3.7 KiB
JavaScript
120 lines
3.7 KiB
JavaScript
// Client name reservation helpers for onboarding and live connection checks.
|
|
|
|
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 d_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
|
|
}; |