Release v2.2.0
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
const { isClientNameAvailable, withClientNameReservation } = require('../../data/client-name-check');
|
||||
const { getSharedSecret, verifyPageAuthToken } = require('../../request-auth');
|
||||
// Player onboarding routes and signup flow helpers.
|
||||
|
||||
const { isClientNameAvailable, withClientNameReservation } = require('#src/data/client-name-check');
|
||||
const { getSharedSecret, verifyPageAuthToken } = require('#src/request-auth');
|
||||
const { isTransientDbError } = require('./store');
|
||||
|
||||
const ONBOARDING_SIGNUP_LIMIT_WINDOW_MS = 5 * 60 * 1000;
|
||||
@@ -10,8 +12,8 @@ function normalizeDeviceId(value) {
|
||||
return String(value || '').trim().replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 128);
|
||||
}
|
||||
|
||||
function getPublicBaseUrl(req) {
|
||||
const configured = String(process.env.PLAYER_PUBLIC_BASE_URL || process.env.PLAYER_BASE_URL || '').trim().replace(/\/$/, '');
|
||||
function getPublicBaseUrl(req, configuredUrl) {
|
||||
const configured = String(configuredUrl || process.env.PLAYER_PUBLIC_BASE_URL || process.env.PLAYER_BASE_URL || '').trim().replace(/\/$/, '');
|
||||
if (configured) {
|
||||
return configured;
|
||||
}
|
||||
@@ -40,6 +42,15 @@ function getOnboardingLimitKey(req, deviceId) {
|
||||
return [getRequestIp(req), normalizeDeviceId(deviceId) || 'anonymous'].join('|');
|
||||
}
|
||||
|
||||
function getPlayerPublicBaseUrl(req, configuredUrl) {
|
||||
return getPublicBaseUrl(req, configuredUrl);
|
||||
}
|
||||
|
||||
function getPlayerInternalBaseUrl(configuredUrl) {
|
||||
const configured = String(configuredUrl || process.env.PLAYER_INTERNAL_BASE_URL || process.env.PLAYER_BASE_URL || '').trim().replace(/\/$/, '');
|
||||
return configured || null;
|
||||
}
|
||||
|
||||
function clearOnboardingSignupAttempts() {
|
||||
if (onboardingSignupAttempts.size > 1000) {
|
||||
onboardingSignupAttempts.clear();
|
||||
@@ -121,6 +132,62 @@ async function commitDeviceBinding(pool, deviceId, clientName, screenSlug, isNam
|
||||
});
|
||||
}
|
||||
|
||||
async function upsertPlayerRegistration(pool, deviceId, publicBaseUrl, internalBaseUrl, screenSlug) {
|
||||
const normalizedDeviceId = normalizeDeviceId(deviceId);
|
||||
const normalizedPublicBaseUrl = String(publicBaseUrl || '').trim().replace(/\/$/, '');
|
||||
const normalizedInternalBaseUrl = String(internalBaseUrl || '').trim().replace(/\/$/, '');
|
||||
if (!normalizedDeviceId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await pool.query(
|
||||
`INSERT INTO d_players (device_id, 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`,
|
||||
[normalizedDeviceId, normalizedPublicBaseUrl || null, normalizedInternalBaseUrl || null]
|
||||
);
|
||||
|
||||
return {
|
||||
device_id: normalizedDeviceId,
|
||||
public_base_url: normalizedPublicBaseUrl || null,
|
||||
internal_base_url: normalizedInternalBaseUrl || null
|
||||
};
|
||||
}
|
||||
|
||||
async function bindPlayerToScreen(pool, deviceId, screenSlug) {
|
||||
const normalizedDeviceId = normalizeDeviceId(deviceId);
|
||||
const normalizedScreenSlug = String(screenSlug || '').trim();
|
||||
if (!normalizedDeviceId || !normalizedScreenSlug) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [screenRows] = await pool.query('SELECT id FROM d_screens WHERE slug = ? LIMIT 1', [normalizedScreenSlug]);
|
||||
if (!screenRows.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const screenId = Number(screenRows[0].id);
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
// Current model: every screen binds to the shared player row '1'.
|
||||
// If we introduce multiple players, resolve the correct player row here instead of hardcoding it.
|
||||
await connection.query("UPDATE d_screens SET player_id = '1', modified_at = CURRENT_TIMESTAMP WHERE id = ?", [screenId]);
|
||||
await connection.commit();
|
||||
} catch (error) {
|
||||
await connection.rollback();
|
||||
throw error;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
|
||||
return {
|
||||
screen_id: screenId,
|
||||
screen_slug: normalizedScreenSlug,
|
||||
player_id: '1'
|
||||
};
|
||||
}
|
||||
|
||||
async function bindDeviceToScreen(pool, deviceId, clientName, screenSlug, isNameAvailableOnScreen, playerRuntime, onboardingStore) {
|
||||
const liveConnections = playerRuntime && typeof playerRuntime.snapshotAllConnections === 'function'
|
||||
? playerRuntime.snapshotAllConnections()
|
||||
@@ -184,9 +251,13 @@ function registerPlayerOnboardingRoutes(app, options) {
|
||||
res.send(common.renderPlayerOnboardingLandingPage());
|
||||
});
|
||||
|
||||
app.get('/onboard', function (req, res) {
|
||||
app.get('/onboard', async function (req, res, next) {
|
||||
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
||||
res.send(common.renderPlayerOnboardingFormPage(String(req.query.deviceId || '').trim()));
|
||||
try {
|
||||
res.send(common.renderPlayerOnboardingFormPage(String(req.query.deviceId || '').trim()));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/onboarding/status', function (req, res, next) {
|
||||
@@ -231,7 +302,7 @@ function registerPlayerOnboardingRoutes(app, options) {
|
||||
if (!deviceId) {
|
||||
return res.status(400).json({ error: 'Device ID is required' });
|
||||
}
|
||||
const onboardingUrl = `${getPublicBaseUrl(req)}/onboard?deviceId=${encodeURIComponent(deviceId)}`;
|
||||
const onboardingUrl = `${getPublicBaseUrl(req, options && options.playerPublicBaseUrl)}/onboard?deviceId=${encodeURIComponent(deviceId)}`;
|
||||
const svg = await QRCode.toString(onboardingUrl, { type: 'svg', margin: 1, errorCorrectionLevel: 'M' });
|
||||
res.set('Content-Type', 'image/svg+xml; charset=utf-8');
|
||||
res.set('Cache-Control', 'no-store');
|
||||
@@ -262,6 +333,7 @@ function registerPlayerOnboardingRoutes(app, options) {
|
||||
}
|
||||
|
||||
const status = await bindDeviceToScreen(pool, deviceId, clientName, screenSlug, playerRuntime.isClientNameAvailableOnScreen, playerRuntime, onboardingStore);
|
||||
await bindPlayerToScreen(pool, deviceId, screenSlug);
|
||||
res.json({
|
||||
deviceId: deviceId,
|
||||
clientName: status ? status.client_name : clientName,
|
||||
@@ -280,8 +352,12 @@ function registerPlayerOnboardingRoutes(app, options) {
|
||||
module.exports = {
|
||||
normalizeDeviceId: normalizeDeviceId,
|
||||
getPublicBaseUrl: getPublicBaseUrl,
|
||||
getPlayerPublicBaseUrl: getPlayerPublicBaseUrl,
|
||||
getPlayerInternalBaseUrl: getPlayerInternalBaseUrl,
|
||||
getOnboardingStatus: getOnboardingStatus,
|
||||
commitDeviceBinding: commitDeviceBinding,
|
||||
upsertPlayerRegistration: upsertPlayerRegistration,
|
||||
bindPlayerToScreen: bindPlayerToScreen,
|
||||
bindDeviceToScreen: bindDeviceToScreen,
|
||||
registerPlayerOnboardingRoutes: registerPlayerOnboardingRoutes
|
||||
};
|
||||
Reference in New Issue
Block a user