Add player control-plane and dashboard updates
This commit is contained in:
@@ -6,6 +6,7 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
const pool = deps.pool;
|
||||
const common = deps.common;
|
||||
const forwardPlayerCommand = deps.forwardPlayerCommand;
|
||||
const forwardPlayerCommandToBaseUrl = deps.forwardPlayerCommandToBaseUrl;
|
||||
const getScreenConnections = deps.getScreenConnections;
|
||||
const isClientNameAvailable = deps.isClientNameAvailable;
|
||||
const withClientNameReservation = deps.withClientNameReservation;
|
||||
@@ -13,11 +14,74 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
const requirePermission = deps.requirePermission;
|
||||
const ALL_SCREENS_SLUG = '__all__';
|
||||
|
||||
async function resolveScreenPlayerBaseUrls(screenSlug, connectionId) {
|
||||
if (typeof getScreenConnections !== 'function' || !screenSlug) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await getScreenConnections(screenSlug);
|
||||
const liveConnections = Array.isArray(response && response.connections) ? response.connections : [];
|
||||
if (!liveConnections.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const normalizedConnectionId = String(connectionId || '').trim();
|
||||
const liveConnection = normalizedConnectionId
|
||||
? liveConnections.find(function (connection) {
|
||||
const candidateConnectionId = String(connection && (connection.id || connection.clientId) || '').trim();
|
||||
const candidateDeviceId = String(connection && connection.deviceId || '').trim();
|
||||
return candidateConnectionId === normalizedConnectionId || candidateDeviceId === normalizedConnectionId;
|
||||
})
|
||||
: null;
|
||||
const targetConnections = liveConnection ? [liveConnection] : liveConnections;
|
||||
|
||||
return Array.from(new Set(targetConnections.map(function (connection) {
|
||||
return String(connection && connection.playerPublicBaseUrl || '').trim().replace(/\/$/, '');
|
||||
}).filter(Boolean)));
|
||||
} catch (_error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeExplicitPlayerBaseUrl(value) {
|
||||
return String(value || '').trim().replace(/\/$/, '');
|
||||
}
|
||||
|
||||
function isRecentPlayerRegistration(player, staleSeconds) {
|
||||
const lastSeenAt = player && player.last_seen_at;
|
||||
const lastSeenAtValue = lastSeenAt instanceof Date ? lastSeenAt.getTime() : new Date(lastSeenAt).getTime();
|
||||
const cutoffTime = Date.now() - Math.max(30, Number(staleSeconds || 60)) * 1000;
|
||||
|
||||
return Number.isFinite(lastSeenAtValue) && lastSeenAtValue >= cutoffTime;
|
||||
}
|
||||
|
||||
async function resolveAllPlayerBaseUrls() {
|
||||
if (!common || typeof common.fetchPlayerRegistrations !== 'function') {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const players = await common.fetchPlayerRegistrations(pool);
|
||||
return Array.from(new Set((Array.isArray(players) ? players : [])
|
||||
.filter(function (player) {
|
||||
return isRecentPlayerRegistration(player, 60);
|
||||
})
|
||||
.map(function (player) {
|
||||
return String(player && player.public_base_url || '').trim().replace(/\/$/, '');
|
||||
})
|
||||
.filter(Boolean)));
|
||||
} catch (_error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
app.post('/clients/:slug/commands', requirePermission('clients.allow'), async function (req, res, next) {
|
||||
try {
|
||||
const slug = String(req.params.slug || '').trim();
|
||||
const command = String((req.body && req.body.command) || req.query.command || '').trim().toLowerCase();
|
||||
const connectionId = String((req.body && (req.body.connectionId || req.body.clientId)) || req.query.connectionId || req.query.clientId || '').trim();
|
||||
const explicitPlayerBaseUrl = normalizeExplicitPlayerBaseUrl((req.body && (req.body.playerBaseUrl || req.body.playerPublicBaseUrl)) || req.query.playerBaseUrl || req.query.playerPublicBaseUrl || '');
|
||||
const blackoutValue = req.body && Object.prototype.hasOwnProperty.call(req.body, 'blackout')
|
||||
? req.body.blackout
|
||||
: req.query.blackout;
|
||||
@@ -47,7 +111,16 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
}
|
||||
|
||||
await Promise.all(screenRows.map(function (screenRow) {
|
||||
return forwardPlayerCommand(String(screenRow && screenRow.slug || '').trim(), commandPayload);
|
||||
const screenSlug = String(screenRow && screenRow.slug || '').trim();
|
||||
return resolveScreenPlayerBaseUrls(screenSlug, connectionId).then(function (playerBaseUrls) {
|
||||
if (playerBaseUrls.length && typeof forwardPlayerCommandToBaseUrl === 'function') {
|
||||
return Promise.all(playerBaseUrls.map(function (playerBaseUrl) {
|
||||
return forwardPlayerCommandToBaseUrl(playerBaseUrl, screenSlug, commandPayload, connectionId || undefined);
|
||||
}));
|
||||
}
|
||||
|
||||
return forwardPlayerCommand(screenSlug, commandPayload);
|
||||
});
|
||||
}));
|
||||
|
||||
if (typeof broadcastDashboardState === 'function') {
|
||||
@@ -74,6 +147,28 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
return res.status(404).json({ error: 'Screen not found' });
|
||||
}
|
||||
|
||||
if (explicitPlayerBaseUrl && typeof forwardPlayerCommandToBaseUrl === 'function' && command !== 'moveclient') {
|
||||
const commandPayload = req.body && typeof req.body === 'object' && !Array.isArray(req.body)
|
||||
? Object.assign({}, req.body, { command: command })
|
||||
: { command: command };
|
||||
if (command === 'blackout' && blackoutValue !== undefined && commandPayload && typeof commandPayload === 'object') {
|
||||
commandPayload.blackout = blackoutValue;
|
||||
}
|
||||
|
||||
const result = await forwardPlayerCommandToBaseUrl(explicitPlayerBaseUrl, slug, commandPayload, connectionId || undefined);
|
||||
|
||||
if (typeof broadcastDashboardState === 'function') {
|
||||
await broadcastDashboardState();
|
||||
}
|
||||
|
||||
return res.json(Object.assign({
|
||||
screen: screenRows[0],
|
||||
screenSlug: slug,
|
||||
command: command,
|
||||
connectionId: connectionId || null
|
||||
}, result && typeof result === 'object' ? result : {}));
|
||||
}
|
||||
|
||||
if (command === 'setclientname') {
|
||||
const deviceId = String((req.body && (req.body.deviceId || req.body.clientId)) || req.query.deviceId || req.query.clientId || '').trim();
|
||||
const clientName = String((req.body && req.body.clientName) || req.query.clientName || '').trim();
|
||||
@@ -256,17 +351,25 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
return candidateConnectionId === connectionId || candidateConnectionId === deviceId || candidateDeviceId === deviceId;
|
||||
})
|
||||
: null;
|
||||
const targetBaseUrl = String(
|
||||
const sourcePlayerBaseUrl = String(
|
||||
explicitPlayerBaseUrl ||
|
||||
(liveConnection && liveConnection.playerPublicBaseUrl) ||
|
||||
(typeof common.fetchPlayerPublicBaseUrl === 'function' ? await common.fetchPlayerPublicBaseUrl(pool) : '') ||
|
||||
''
|
||||
).trim().replace(/\/$/, '');
|
||||
const targetPlayerUrl = targetBaseUrl ? `${targetBaseUrl}/screen/${encodeURIComponent(targetScreenSlug)}` : `/screen/${encodeURIComponent(targetScreenSlug)}`;
|
||||
const targetPlayerUrl = sourcePlayerBaseUrl ? `${sourcePlayerBaseUrl}/screen/${encodeURIComponent(targetScreenSlug)}` : `/screen/${encodeURIComponent(targetScreenSlug)}`;
|
||||
|
||||
await forwardPlayerCommand(slug, {
|
||||
command: 'redirect',
|
||||
url: targetPlayerUrl
|
||||
}, connectionId || deviceId || undefined);
|
||||
if (sourcePlayerBaseUrl && typeof forwardPlayerCommandToBaseUrl === 'function') {
|
||||
await forwardPlayerCommandToBaseUrl(sourcePlayerBaseUrl, slug, {
|
||||
command: 'redirect',
|
||||
url: targetPlayerUrl
|
||||
}, connectionId || deviceId || undefined);
|
||||
} else {
|
||||
await forwardPlayerCommand(slug, {
|
||||
command: 'redirect',
|
||||
url: targetPlayerUrl
|
||||
}, connectionId || deviceId || undefined);
|
||||
}
|
||||
|
||||
if (typeof broadcastDashboardState === 'function') {
|
||||
await broadcastDashboardState();
|
||||
@@ -292,9 +395,16 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
|
||||
commandPayload.blackout = blackoutValue;
|
||||
}
|
||||
|
||||
const result = connectionId
|
||||
? await forwardPlayerCommand(slug, commandPayload, connectionId)
|
||||
: await forwardPlayerCommand(slug, commandPayload);
|
||||
const playerBaseUrls = await resolveScreenPlayerBaseUrls(slug, connectionId);
|
||||
const result = playerBaseUrls.length && typeof forwardPlayerCommandToBaseUrl === 'function'
|
||||
? await Promise.all(playerBaseUrls.map(function (playerBaseUrl) {
|
||||
return forwardPlayerCommandToBaseUrl(playerBaseUrl, slug, commandPayload, connectionId);
|
||||
})).then(function (results) {
|
||||
return Array.isArray(results) && results.length ? results[0] : { ok: true };
|
||||
})
|
||||
: (connectionId
|
||||
? await forwardPlayerCommand(slug, commandPayload, connectionId)
|
||||
: await forwardPlayerCommand(slug, commandPayload));
|
||||
|
||||
if (typeof broadcastDashboardState === 'function') {
|
||||
await broadcastDashboardState();
|
||||
|
||||
Reference in New Issue
Block a user