Release 2.6.7

This commit is contained in:
2026-08-08 13:02:31 +01:00
parent b20beb250b
commit 7e46fffc34
41 changed files with 2489 additions and 1290 deletions
+182 -139
View File
@@ -12,40 +12,122 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
const withClientNameReservation = deps.withClientNameReservation;
const broadcastDashboardState = deps.broadcastDashboardState;
const requirePermission = deps.requirePermission;
const ALL_SCREENS_SLUG = '__all__';
async function resolveScreenPlayerBaseUrls(screenSlug, connectionId) {
if (typeof getScreenConnections !== 'function' || !screenSlug) {
function normalizeExplicitPlayerBaseUrl(value) {
return String(value || '').trim().replace(/\/$/, '');
}
function normalizeConnectionBaseUrl(connection) {
return normalizeExplicitPlayerBaseUrl(connection && connection.playerPublicBaseUrl);
}
function normalizeBaseUrl(value) {
return String(value || '').trim().replace(/\/$/, '');
}
async function fetchPlayerRegistrations() {
if (typeof common.fetchPlayerRegistrations !== 'function') {
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)));
const registrations = await common.fetchPlayerRegistrations(pool);
return Array.isArray(registrations) ? registrations : [];
} catch (_error) {
return [];
}
}
function normalizeExplicitPlayerBaseUrl(value) {
return String(value || '').trim().replace(/\/$/, '');
async function fetchScreenConnections(screenSlug) {
if (typeof getScreenConnections !== 'function') {
return [];
}
try {
const response = await getScreenConnections(screenSlug);
return Array.isArray(response && response.connections) ? response.connections : [];
} catch (_error) {
return [];
}
}
async function fetchAllScreenTargets() {
const [rows] = await pool.query(
`SELECT slug
FROM d_screens
WHERE slug IS NOT NULL
ORDER BY slug ASC`
);
return (rows || [])
.map(function (row) {
return {
slug: String(row && row.slug ? row.slug : '').trim()
};
})
.filter(function (row) {
return Boolean(row.slug);
});
}
async function resolvePlayerBaseUrlsForConnections(connections) {
const connectionList = Array.isArray(connections) ? connections : [];
const seen = new Set();
const registrations = await fetchPlayerRegistrations();
return connectionList.map(function (connection) {
const selectedPublicBaseUrl = normalizeConnectionBaseUrl(connection);
if (!selectedPublicBaseUrl || seen.has(selectedPublicBaseUrl)) {
return '';
}
seen.add(selectedPublicBaseUrl);
const matchedPlayer = registrations.find(function (player) {
return normalizeExplicitPlayerBaseUrl(player && player.public_base_url) === selectedPublicBaseUrl;
}) || null;
return normalizeExplicitPlayerBaseUrl(matchedPlayer && matchedPlayer.internal_base_url) || selectedPublicBaseUrl;
}).filter(Boolean);
}
async function resolvePlayerBaseUrlForExplicitPublicBaseUrl(playerBaseUrl) {
const normalizedPublicBaseUrl = normalizeExplicitPlayerBaseUrl(playerBaseUrl);
if (!normalizedPublicBaseUrl) {
return '';
}
const registrations = await fetchPlayerRegistrations();
const matchedPlayer = registrations.find(function (player) {
return normalizeExplicitPlayerBaseUrl(player && player.public_base_url) === normalizedPublicBaseUrl;
}) || null;
return normalizeExplicitPlayerBaseUrl(matchedPlayer && matchedPlayer.internal_base_url) || normalizedPublicBaseUrl;
}
async function forwardPlayerCommandForConnections(screenSlug, connections, commandPayload, connectionId, deviceId) {
const targetBaseUrls = await resolvePlayerBaseUrlsForConnections(connections);
if (targetBaseUrls.length && typeof forwardPlayerCommandToBaseUrl === 'function') {
const results = await Promise.allSettled(targetBaseUrls.map(function (targetBaseUrl) {
return forwardPlayerCommandToBaseUrl(targetBaseUrl, screenSlug, commandPayload, connectionId || deviceId || undefined);
}));
return {
ok: true,
sent: results.filter(function (result) {
return result.status === 'fulfilled';
}).length,
results: results
};
}
return forwardPlayerCommand(screenSlug, commandPayload, connectionId || deviceId || undefined);
}
async function forwardPlayerCommandForPlayerBaseUrl(screenSlug, playerBaseUrl, commandPayload, connectionId, deviceId) {
const targetBaseUrl = await resolvePlayerBaseUrlForExplicitPublicBaseUrl(playerBaseUrl);
if (targetBaseUrl && typeof forwardPlayerCommandToBaseUrl === 'function') {
return forwardPlayerCommandToBaseUrl(targetBaseUrl, screenSlug, commandPayload, connectionId || deviceId || undefined);
}
return forwardPlayerCommand(screenSlug, commandPayload, connectionId || deviceId || undefined);
}
function isRecentPlayerRegistration(player, staleSeconds) {
@@ -56,32 +138,13 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
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 playerBaseUrl = String((req.body && req.body.playerBaseUrl) || req.query.playerBaseUrl || '').trim();
const blackoutValue = req.body && Object.prototype.hasOwnProperty.call(req.body, 'blackout')
? req.body.blackout
: req.query.blackout;
@@ -93,14 +156,25 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
return res.status(400).json({ error: 'Command is required' });
}
if (slug === ALL_SCREENS_SLUG) {
if (command === 'setclientname' || command === 'moveclient') {
if (slug === '__all__') {
if (command !== 'reload' && command !== 'pause' && command !== 'blackout') {
return res.status(400).json({ error: 'This command requires a specific screen.' });
}
const [screenRows] = await pool.query('SELECT id, name, slug FROM d_screens WHERE slug IS NOT NULL ORDER BY slug ASC');
if (!screenRows.length) {
return res.status(404).json({ error: 'No screens found' });
const targets = await fetchAllScreenTargets();
if (!targets.length) {
if (typeof broadcastDashboardState === 'function') {
await broadcastDashboardState();
}
return res.json({
ok: true,
allScreens: true,
command: command,
targetScreenCount: 0,
targetPlayerCount: 0,
sent: 0
});
}
const commandPayload = req.body && typeof req.body === 'object' && !Array.isArray(req.body)
@@ -109,36 +183,41 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
if (command === 'blackout' && blackoutValue !== undefined && commandPayload && typeof commandPayload === 'object') {
commandPayload.blackout = blackoutValue;
}
if (command === 'pause' && req.body && Object.prototype.hasOwnProperty.call(req.body, 'paused') && commandPayload && typeof commandPayload === 'object') {
commandPayload.paused = req.body.paused;
}
await Promise.all(screenRows.map(function (screenRow) {
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);
}));
}
const results = await Promise.allSettled(targets.map(async function (target) {
const liveConnections = await fetchScreenConnections(target.slug);
if (liveConnections.length) {
return forwardPlayerCommandForConnections(target.slug, liveConnections, commandPayload);
}
return forwardPlayerCommand(screenSlug, commandPayload);
});
return forwardPlayerCommand(target.slug, commandPayload);
}));
const sentCount = results.reduce(function (total, result) {
if (result.status !== 'fulfilled') {
return total;
}
const sentValue = Number(result.value && typeof result.value.sent === 'number' ? result.value.sent : 1);
return total + (Number.isFinite(sentValue) && sentValue > 0 ? sentValue : 1);
}, 0);
if (typeof broadcastDashboardState === 'function') {
await broadcastDashboardState();
}
return res.json({
screen: {
id: null,
name: 'All screens',
slug: ALL_SCREENS_SLUG
},
screenSlug: slug,
command: command,
connectionId: connectionId || null,
targetScreenCount: screenRows.length,
ok: true,
allScreens: true
allScreens: true,
command: command,
targetScreenCount: targets.length,
targetPlayerCount: sentCount,
sent: sentCount,
blackout: command === 'blackout' ? Boolean(commandPayload.blackout) : undefined,
paused: command === 'pause' ? Boolean(commandPayload.paused) : undefined
});
}
@@ -147,28 +226,6 @@ 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();
@@ -231,12 +288,21 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
}
if (!onboardingRow) {
await forwardPlayerCommand(slug, {
command: command,
clientName: clientName,
clientId: connectionId || deviceId || null,
deviceId: deviceId || null
}, connectionId || deviceId || undefined);
if (playerBaseUrl) {
await forwardPlayerCommandForPlayerBaseUrl(slug, playerBaseUrl, {
command: command,
clientName: clientName,
clientId: connectionId || deviceId || null,
deviceId: deviceId || null
}, connectionId || deviceId || undefined, deviceId || null);
} else {
await forwardPlayerCommandForConnections(slug, liveConnections, {
command: command,
clientName: clientName,
clientId: connectionId || deviceId || null,
deviceId: deviceId || null
}, connectionId || deviceId || undefined, deviceId || null);
}
if (typeof broadcastDashboardState === 'function') {
await broadcastDashboardState();
@@ -323,52 +389,35 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
return res.status(500).json({ error: 'Client binding is unavailable.' });
}
let liveConnections = [];
try {
const [screenSlugs] = await pool.query('SELECT slug FROM d_screens ORDER BY slug ASC');
const liveResults = await Promise.all((screenSlugs || []).map(async function (row) {
const screenSlug = String(row && row.slug ? row.slug : '').trim();
if (!screenSlug || typeof getScreenConnections !== 'function') {
return [];
}
try {
const liveResponse = await getScreenConnections(screenSlug);
return Array.isArray(liveResponse && liveResponse.connections) ? liveResponse.connections : [];
} catch (_error) {
return [];
}
}));
liveConnections = liveResults.flat();
} catch (_error) {
liveConnections = [];
}
const liveConnections = await fetchScreenConnections(slug);
const status = await commitDeviceBinding(pool, deviceId, resolvedClientName, targetScreenSlug, isClientNameAvailable, liveConnections);
let targetPlayerUrl = '';
const liveConnection = Array.isArray(liveConnections)
? liveConnections.find(function (connection) {
const candidateConnectionId = String(connection && (connection.id || connection.clientId) || '').trim();
const candidateDeviceId = String(connection && connection.deviceId || '').trim();
return candidateConnectionId === connectionId || candidateConnectionId === deviceId || candidateDeviceId === deviceId;
})
}) || liveConnections[0] || null
: null;
const sourcePlayerBaseUrl = String(
explicitPlayerBaseUrl ||
(liveConnection && liveConnection.playerPublicBaseUrl) ||
(typeof common.fetchPlayerPublicBaseUrl === 'function' ? await common.fetchPlayerPublicBaseUrl(pool) : '') ||
''
).trim().replace(/\/$/, '');
const targetPlayerUrl = sourcePlayerBaseUrl ? `${sourcePlayerBaseUrl}/screen/${encodeURIComponent(targetScreenSlug)}` : `/screen/${encodeURIComponent(targetScreenSlug)}`;
const sourcePlayerBaseUrl = normalizeExplicitPlayerBaseUrl(playerBaseUrl || (liveConnection && liveConnection.playerPublicBaseUrl) || '');
if (sourcePlayerBaseUrl) {
targetPlayerUrl = `${sourcePlayerBaseUrl}/screen/${encodeURIComponent(targetScreenSlug)}`;
}
if (!targetPlayerUrl) {
targetPlayerUrl = `/screen/${encodeURIComponent(targetScreenSlug)}`;
}
if (sourcePlayerBaseUrl && typeof forwardPlayerCommandToBaseUrl === 'function') {
await forwardPlayerCommandToBaseUrl(sourcePlayerBaseUrl, slug, {
if (playerBaseUrl) {
await forwardPlayerCommandForPlayerBaseUrl(slug, playerBaseUrl, {
command: 'redirect',
url: targetPlayerUrl
}, connectionId || deviceId || undefined);
}, connectionId || deviceId || undefined, deviceId || null);
} else {
await forwardPlayerCommand(slug, {
await forwardPlayerCommandForConnections(slug, liveConnections, {
command: 'redirect',
url: targetPlayerUrl
}, connectionId || deviceId || undefined);
}, connectionId || deviceId || undefined, deviceId || null);
}
if (typeof broadcastDashboardState === 'function') {
@@ -395,16 +444,10 @@ module.exports = function registerScreenCommandRoutes(app, deps) {
commandPayload.blackout = blackoutValue;
}
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));
const liveConnections = await fetchScreenConnections(slug);
const result = playerBaseUrl
? await forwardPlayerCommandForPlayerBaseUrl(slug, playerBaseUrl, commandPayload, connectionId, null)
: await forwardPlayerCommandForConnections(slug, liveConnections, commandPayload, connectionId, null);
if (typeof broadcastDashboardState === 'function') {
await broadcastDashboardState();
+138 -14
View File
@@ -11,6 +11,7 @@ module.exports = function registerManageRoutes(app, deps) {
const getScreenDeleteBlockMessage = deps.getScreenDeleteBlockMessage;
const getScreenConnections = deps.getScreenConnections;
const forwardPlayerCommand = deps.forwardPlayerCommand;
const forwardPlayerCommandToBaseUrl = deps.forwardPlayerCommandToBaseUrl;
const requirePermission = deps.requirePermission;
const SCREEN_NAME_MAX_LENGTH = 255;
@@ -25,13 +26,106 @@ module.exports = function registerManageRoutes(app, deps) {
return text.slice(0, limit);
}
async function fetchAllScreenSlugs() {
const [rows] = await pool.query('SELECT slug FROM d_screens ORDER BY slug ASC');
function normalizeBaseUrl(value) {
return String(value || '').trim().replace(/\/$/, '');
}
function normalizeExplicitPlayerBaseUrl(value) {
return String(value || '').trim().replace(/\/$/, '');
}
async function fetchPlayerRegistrations() {
if (typeof common.fetchPlayerRegistrations !== 'function') {
return [];
}
try {
const registrations = await common.fetchPlayerRegistrations(pool);
return Array.isArray(registrations) ? registrations : [];
} catch (_error) {
return [];
}
}
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 fetchLiveConnectionsForScreen(slug) {
if (typeof getScreenConnections !== 'function') {
return [];
}
try {
const response = await getScreenConnections(slug);
return Array.isArray(response && response.connections) ? response.connections : [];
} catch (_error) {
return [];
}
}
async function resolvePlayerBaseUrlsForConnections(connections) {
const connectionList = Array.isArray(connections) ? connections : [];
const seen = new Set();
const registrations = await fetchPlayerRegistrations();
return connectionList.map(function (connection) {
const selectedPublicBaseUrl = normalizeExplicitPlayerBaseUrl(connection && connection.playerPublicBaseUrl);
if (!selectedPublicBaseUrl || seen.has(selectedPublicBaseUrl)) {
return '';
}
seen.add(selectedPublicBaseUrl);
const matchedPlayer = registrations.find(function (player) {
return normalizeExplicitPlayerBaseUrl(player && player.public_base_url) === selectedPublicBaseUrl;
}) || null;
return normalizeExplicitPlayerBaseUrl(matchedPlayer && matchedPlayer.internal_base_url) || selectedPublicBaseUrl;
}).filter(Boolean);
}
async function forwardPlayerCommandForConnections(slug, connections, commandPayload) {
const targetBaseUrls = await resolvePlayerBaseUrlsForConnections(connections);
if (targetBaseUrls.length && typeof forwardPlayerCommandToBaseUrl === 'function') {
const results = await Promise.allSettled(targetBaseUrls.map(function (targetBaseUrl) {
return forwardPlayerCommandToBaseUrl(targetBaseUrl, slug, commandPayload);
}));
return {
ok: true,
sent: results.filter(function (result) {
return result.status === 'fulfilled';
}).length,
results: results
};
}
return forwardPlayerCommand(slug, commandPayload);
}
async function fetchAllScreenTargets() {
const [rows] = await pool.query(
`SELECT s.slug
FROM d_screens s
WHERE s.slug IS NOT NULL
ORDER BY s.slug ASC`
);
return (rows || [])
.map(function (row) {
return String(row && row.slug ? row.slug : '').trim();
return {
slug: String(row && row.slug ? row.slug : '').trim(),
playerId: '',
publicBaseUrl: '',
internalBaseUrl: ''
};
})
.filter(Boolean);
.filter(function (row) {
return Boolean(row.slug);
});
}
app.post('/commands', requirePermission('dashboard.allow'), async function (req, res, next) {
@@ -52,8 +146,8 @@ module.exports = function registerManageRoutes(app, deps) {
return res.status(400).json({ error: 'Unsupported command' });
}
const slugs = await fetchAllScreenSlugs();
if (!slugs.length) {
const targets = await fetchAllScreenTargets();
if (!targets.length) {
await broadcastDashboardState();
return res.json({ ok: true, command: command, sent: 0 });
}
@@ -70,7 +164,22 @@ module.exports = function registerManageRoutes(app, deps) {
}
: 'reload';
const sentCount = await notifyPlayerScreens(slugs, payload);
const results = await Promise.allSettled(targets.map(async function (target) {
const liveConnections = await fetchLiveConnectionsForScreen(target.slug);
if (liveConnections.length) {
return forwardPlayerCommandForConnections(target.slug, liveConnections, payload);
}
return forwardPlayerCommand(target.slug, payload);
}));
const sentCount = results.reduce(function (total, result) {
if (result.status !== 'fulfilled') {
return total;
}
const sentValue = Number(result.value && typeof result.value.sent === 'number' ? result.value.sent : 1);
return total + (Number.isFinite(sentValue) && sentValue > 0 ? sentValue : 1);
}, 0);
await broadcastDashboardState();
return res.json({
@@ -149,13 +258,28 @@ module.exports = function registerManageRoutes(app, deps) {
await notifyPlayerScreens([previousSlug], 'refresh');
}
if (previousSlug && previousSlug !== slug) {
const playerBaseUrl = typeof common.fetchPlayerPublicBaseUrl === 'function'
? await common.fetchPlayerPublicBaseUrl(pool)
: '';
await forwardPlayerCommand(previousSlug, {
command: 'redirect',
url: playerBaseUrl ? `${playerBaseUrl}/screen/${encodeURIComponent(slug)}` : `/screen/${encodeURIComponent(slug)}`
});
const previousScreenTargets = await pool.query(
`SELECT s.slug, s.player_id, p.public_base_url, p.internal_base_url
FROM d_screens s
LEFT JOIN d_players p ON p.device_id = s.player_id
WHERE s.slug = ?
LIMIT 1`,
[previousSlug]
);
const previousTargetRow = previousScreenTargets[0] && previousScreenTargets[0][0] || null;
const previousInternalBaseUrl = normalizeBaseUrl(previousTargetRow && previousTargetRow.internal_base_url) || '';
const previousPublicBaseUrl = normalizeBaseUrl(previousTargetRow && previousTargetRow.public_base_url) || '';
if (previousInternalBaseUrl && typeof forwardPlayerCommandToBaseUrl === 'function') {
await forwardPlayerCommandToBaseUrl(previousInternalBaseUrl, previousSlug, {
command: 'redirect',
url: previousPublicBaseUrl ? `${previousPublicBaseUrl}/screen/${encodeURIComponent(slug)}` : `/screen/${encodeURIComponent(slug)}`
});
} else {
await forwardPlayerCommand(previousSlug, {
command: 'redirect',
url: previousPublicBaseUrl ? `${previousPublicBaseUrl}/screen/${encodeURIComponent(slug)}` : `/screen/${encodeURIComponent(slug)}`
});
}
}
redirectAfterSave(req, res, '/screens?edit=' + screen.id, {
closeUrl: '/screens',
+2
View File
@@ -125,6 +125,8 @@ function registerSignageRoutes(app, deps) {
getScreenDeleteBlockMessage: deps.playerActionService.getScreenDeleteBlockMessage,
getScreenConnections: deps.playerActionService.getScreenConnections,
forwardPlayerCommand: deps.playerActionService.forwardPlayerCommand,
forwardPlayerCommandToBaseUrl: deps.playerActionService.forwardPlayerCommandToBaseUrl,
forwardPlayerCommandToDevice: deps.playerActionService.forwardPlayerCommandToDevice,
requirePermission: deps.requirePermission
});