Files
pulse-signage/src/data/player-registry.js
T

140 lines
4.5 KiB
JavaScript

function normalizeDeviceId(value) {
return String(value || '')
.trim()
.replace(/[^a-zA-Z0-9_-]/g, '')
.slice(0, 128);
}
function normalizeBaseUrl(value) {
return String(value || '').trim().replace(/\/$/, '');
}
function normalizeIdentifier(value) {
return String(value || '').trim().slice(0, 255);
}
async function columnExists(pool, tableName, columnName) {
const [rows] = await pool.query(
`SELECT COUNT(*) AS column_count
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND COLUMN_NAME = ?`,
[tableName, columnName]
);
return Number(rows && rows[0] && rows[0].column_count) > 0;
}
async function fetchPlayerRegistrations(pool) {
if (!pool) {
return [];
}
const [rows] = await pool.query(
`SELECT id, identifier, public_base_url, internal_base_url, last_seen_at, modified_at
FROM d_players
ORDER BY modified_at DESC, identifier ASC`
);
return rows;
}
function getConfiguredPlayerIdentifier() {
return normalizeDeviceId(process.env.PLAYER_IDENTIFIER || process.env.PLAYER_DEVICE_ID || '');
}
async function resolvePlayerRegistration(pool, identifier) {
const normalizedIdentifier = normalizeDeviceId(identifier);
if (!pool || !normalizedIdentifier) {
return null;
}
const hasIdentifierColumn = await columnExists(pool, 'd_players', 'identifier');
const hasDeviceIdColumn = await columnExists(pool, 'd_players', 'device_id');
const identifierColumn = hasIdentifierColumn ? 'identifier' : (hasDeviceIdColumn ? 'device_id' : '');
if (!identifierColumn) {
return null;
}
const selectIdExpression = hasIdentifierColumn ? 'id' : 'NULL AS id';
const selectIdentifierExpression = hasIdentifierColumn ? 'identifier' : 'device_id AS identifier';
const orderByExpression = hasIdentifierColumn ? 'modified_at DESC, id DESC' : 'modified_at DESC';
const [rows] = await pool.query(
`SELECT ${selectIdExpression}, ${selectIdentifierExpression}, public_base_url, internal_base_url, last_seen_at
FROM d_players
WHERE ${identifierColumn} = ?
LIMIT 1`,
[normalizedIdentifier]
);
if (rows[0]) {
return rows[0];
}
const [fallbackRows] = await pool.query(
`SELECT ${selectIdExpression}, ${selectIdentifierExpression}, public_base_url, internal_base_url, last_seen_at
FROM d_players
ORDER BY ${orderByExpression}
LIMIT 1`
);
return fallbackRows[0] || null;
}
async function upsertPlayerRegistration(pool, options) {
const identifier = normalizeDeviceId(options && (options.identifier || options.deviceId));
const publicBaseUrl = normalizeBaseUrl(options && options.publicBaseUrl);
const internalBaseUrl = normalizeBaseUrl(options && options.internalBaseUrl);
if (!pool || !identifier) {
return null;
}
await pool.query(
`INSERT INTO d_players (identifier, public_base_url, internal_base_url, last_seen_at)
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
ON DUPLICATE KEY UPDATE
public_base_url = VALUES(public_base_url),
internal_base_url = VALUES(internal_base_url),
last_seen_at = CURRENT_TIMESTAMP,
modified_at = CURRENT_TIMESTAMP`,
[identifier, publicBaseUrl || null, internalBaseUrl || null]
);
return resolvePlayerRegistration(pool, identifier);
}
async function recordPlayerHeartbeat(pool, options) {
const identifier = normalizeDeviceId(options && (options.identifier || options.deviceId));
const publicBaseUrl = normalizeBaseUrl(options && options.publicBaseUrl);
const internalBaseUrl = normalizeBaseUrl(options && options.internalBaseUrl);
if (!pool || !identifier) {
return null;
}
await pool.query(
`INSERT INTO d_players (identifier, public_base_url, internal_base_url, last_seen_at)
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
ON DUPLICATE KEY UPDATE
public_base_url = COALESCE(VALUES(public_base_url), public_base_url),
internal_base_url = COALESCE(VALUES(internal_base_url), internal_base_url),
last_seen_at = CURRENT_TIMESTAMP,
modified_at = CURRENT_TIMESTAMP`,
[identifier, publicBaseUrl || null, internalBaseUrl || null]
);
return resolvePlayerRegistration(pool, identifier);
}
module.exports = {
normalizeDeviceId: normalizeDeviceId,
normalizeIdentifier: normalizeIdentifier,
getConfiguredPlayerIdentifier: getConfiguredPlayerIdentifier,
fetchPlayerRegistrations: fetchPlayerRegistrations,
resolvePlayerRegistration: resolvePlayerRegistration,
upsertPlayerRegistration: upsertPlayerRegistration,
recordPlayerHeartbeat: recordPlayerHeartbeat
};