Release v2.10.4
This commit is contained in:
@@ -210,6 +210,94 @@ test('move client requires a registered player identity', async () => {
|
||||
assert.equal(response.body.error, 'Registered player identity is required');
|
||||
});
|
||||
|
||||
test('move client routes a remote connection through its bridge player identity', async () => {
|
||||
const calls = [];
|
||||
const { app, handlers } = createHandlers();
|
||||
|
||||
registerScreenCommandRoutes(app, {
|
||||
pool: {
|
||||
async query(sql, params) {
|
||||
if (sql.includes('SELECT id, name, slug FROM d_screens WHERE slug = ?') && params[0] === 'source-screen') {
|
||||
return [[{ id: 12, name: 'Source Screen', slug: 'source-screen' }]];
|
||||
}
|
||||
if (sql.includes('SELECT d.client_name, s.slug AS current_screen_slug')) {
|
||||
return [[{ client_name: 'Remote Client', current_screen_slug: 'source-screen' }]];
|
||||
}
|
||||
if (sql.includes('SELECT id, name, slug FROM d_screens WHERE slug = ?') && params[0] === 'target-screen') {
|
||||
return [[{ id: 27, name: 'Target Screen', slug: 'target-screen' }]];
|
||||
}
|
||||
return [[]];
|
||||
}
|
||||
},
|
||||
common: { fetchPlayerRegistrations: async () => [] },
|
||||
forwardPlayerCommand() {
|
||||
throw new Error('direct player fallback should not be used');
|
||||
},
|
||||
forwardPlayerCommandToBaseUrl() {
|
||||
throw new Error('public URL routing should not be used');
|
||||
},
|
||||
forwardPlayerCommandToDevice(deviceId, payload) {
|
||||
calls.push({ deviceId, payload });
|
||||
return { ok: true };
|
||||
},
|
||||
getScreenConnections: async () => ({
|
||||
connections: [{
|
||||
id: 'connection-1',
|
||||
clientId: 'connection-1',
|
||||
deviceId: 'browser-tab-1',
|
||||
playerDeviceId: 'remote-player-1',
|
||||
playerPublicBaseUrl: null
|
||||
}]
|
||||
}),
|
||||
isClientNameAvailable: async () => true,
|
||||
withClientNameReservation: async (_pool, _name, callback) => callback(),
|
||||
broadcastDashboardState: async () => {},
|
||||
requirePermission() {
|
||||
return function (_req, _res, next) {
|
||||
next();
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const response = {
|
||||
statusCode: 200,
|
||||
body: null,
|
||||
status(code) {
|
||||
this.statusCode = code;
|
||||
return this;
|
||||
},
|
||||
json(value) {
|
||||
this.body = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
await handlers['/clients/:slug/commands'][1]({
|
||||
params: { slug: 'source-screen' },
|
||||
body: {
|
||||
command: 'moveclient',
|
||||
clientId: 'browser-tab-1',
|
||||
clientName: 'Remote Client',
|
||||
targetScreenSlug: 'target-screen',
|
||||
connectionId: 'connection-1'
|
||||
},
|
||||
query: {}
|
||||
}, response, (error) => { throw error; });
|
||||
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.equal(response.body.ok, true);
|
||||
assert.deepEqual(calls, [{
|
||||
deviceId: 'remote-player-1',
|
||||
payload: {
|
||||
screenSlug: 'source-screen',
|
||||
command: 'redirect',
|
||||
url: '/screen/target-screen',
|
||||
moveToken: calls[0] && calls[0].payload.moveToken,
|
||||
connectionId: 'connection-1'
|
||||
}
|
||||
}]);
|
||||
});
|
||||
|
||||
test('screen control commands can target all screens', async () => {
|
||||
const calls = [];
|
||||
const { app, handlers } = createHandlers();
|
||||
|
||||
@@ -195,3 +195,56 @@ test('client command route forwards a single screen command', async () => {
|
||||
assert.equal(calls[0].connectionId, undefined);
|
||||
assert.equal(response.body.ok, true);
|
||||
});
|
||||
|
||||
test('client command route uses the bridge device route without a public player URL', async () => {
|
||||
const { app, handlers } = createAppHarness();
|
||||
const calls = [];
|
||||
registerScreenCommandRoutes(app, {
|
||||
pool: {
|
||||
async query(sql) {
|
||||
if (String(sql).includes('SELECT id, name, slug FROM d_screens WHERE slug = ?')) {
|
||||
return [[{ id: 1, name: 'Lobby', slug: 'lobby' }]];
|
||||
}
|
||||
return [[]];
|
||||
}
|
||||
},
|
||||
common: { async fetchPlayerRegistrations() { return []; } },
|
||||
forwardPlayerCommand() {
|
||||
throw new Error('direct player fallback should not be used');
|
||||
},
|
||||
forwardPlayerCommandToBaseUrl() {
|
||||
throw new Error('public URL fallback should not be used');
|
||||
},
|
||||
forwardPlayerCommandToDevice(deviceId, payload) {
|
||||
calls.push({ deviceId, payload });
|
||||
return { ok: true };
|
||||
},
|
||||
getScreenConnections: async () => ({
|
||||
connections: [{ id: 'connection-1', deviceId: 'browser-tab-1', playerDeviceId: 'remote-player-1', playerPublicBaseUrl: null }]
|
||||
}),
|
||||
isClientNameAvailable() { return true; },
|
||||
withClientNameReservation() {},
|
||||
broadcastDashboardState() {},
|
||||
requirePermission() {
|
||||
return function (_req, _res, next) { next(); };
|
||||
}
|
||||
});
|
||||
|
||||
const response = {
|
||||
statusCode: 200,
|
||||
body: null,
|
||||
status(code) { this.statusCode = code; return this; },
|
||||
json(payload) { this.body = payload; return this; }
|
||||
};
|
||||
await handlers['/clients/:slug/commands'][1]({
|
||||
params: { slug: 'lobby' },
|
||||
body: { command: 'pause', connectionId: 'connection-1' },
|
||||
query: {}
|
||||
}, response, () => {});
|
||||
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.deepEqual(calls, [{
|
||||
deviceId: 'remote-player-1',
|
||||
payload: { screenSlug: 'lobby', command: 'pause', connectionId: 'connection-1' }
|
||||
}]);
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ require('../src/common');
|
||||
const registerScreensRoutes = require('../src/web/routes/signage/screens/routes');
|
||||
const { renderScreenEditPage } = require('../src/web/pages');
|
||||
|
||||
test('screen edit page includes shared launcher downloads and base player url', async () => {
|
||||
test('screen edit page omits player URL data', async () => {
|
||||
const handlers = {};
|
||||
const app = {
|
||||
get(path, ...routeHandlers) {
|
||||
@@ -88,25 +88,11 @@ test('screen edit page includes shared launcher downloads and base player url',
|
||||
await handler[1]({ params: { id: '7' }, query: {}, currentUser: { id: 1 } }, res, () => {});
|
||||
|
||||
assert.equal(res.body, 'ok');
|
||||
assert.deepEqual(renderedArgs.screen.player_urls, [
|
||||
{
|
||||
identifier: 'player-alpha',
|
||||
public_base_url: 'http://alpha.example',
|
||||
player_url: 'http://alpha.example/screen/demo-conference'
|
||||
},
|
||||
{
|
||||
identifier: 'player-beta',
|
||||
public_base_url: 'http://beta.example',
|
||||
player_url: 'http://beta.example/screen/demo-conference'
|
||||
}
|
||||
]);
|
||||
assert.deepEqual(renderedArgs.screen.launcher_downloads, {
|
||||
windows: '/downloads/kiosk/pulse-signage-kiosk.bat',
|
||||
linux: '/downloads/kiosk/pulse-signage-kiosk.sh'
|
||||
});
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(renderedArgs.screen, 'player_urls'), false);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(renderedArgs.screen, 'launcher_downloads'), false);
|
||||
});
|
||||
|
||||
test('screen edit query route includes player urls for every registration', async () => {
|
||||
test('screen edit query route omits player URLs', async () => {
|
||||
const handlers = {};
|
||||
const app = {
|
||||
get(path, ...routeHandlers) {
|
||||
@@ -172,20 +158,11 @@ test('screen edit query route includes player urls for every registration', asyn
|
||||
await handler[1]({ query: { edit: '7' }, currentUser: { id: 1 } }, res, () => {});
|
||||
|
||||
assert.equal(res.body, 'ok');
|
||||
assert.deepEqual(renderedArgs.screen.player_urls, [
|
||||
{
|
||||
identifier: 'player-alpha',
|
||||
public_base_url: 'http://alpha.example',
|
||||
player_url: 'http://alpha.example/screen/demo-conference'
|
||||
}
|
||||
]);
|
||||
assert.deepEqual(renderedArgs.screen.launcher_downloads, {
|
||||
windows: '/downloads/kiosk/pulse-signage-kiosk.bat',
|
||||
linux: '/downloads/kiosk/pulse-signage-kiosk.sh'
|
||||
});
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(renderedArgs.screen, 'player_urls'), false);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(renderedArgs.screen, 'launcher_downloads'), false);
|
||||
});
|
||||
|
||||
test('screen edit page hides stale player registrations', async () => {
|
||||
test('screen edit page does not load player URL registrations', async () => {
|
||||
const handlers = {};
|
||||
const app = {
|
||||
get(path, ...routeHandlers) {
|
||||
@@ -258,22 +235,10 @@ test('screen edit page hides stale player registrations', async () => {
|
||||
await handler[1]({ params: { id: '7' }, query: {}, currentUser: { id: 1 } }, res, () => {});
|
||||
|
||||
assert.equal(res.body, 'ok');
|
||||
assert.deepEqual(renderedArgs.screen.player_urls.map(function (player) {
|
||||
return {
|
||||
identifier: player.identifier,
|
||||
public_base_url: player.public_base_url,
|
||||
player_url: player.player_url
|
||||
};
|
||||
}), [
|
||||
{
|
||||
identifier: 'player-recent',
|
||||
public_base_url: 'http://recent.example',
|
||||
player_url: 'http://recent.example/screen/demo-conference'
|
||||
}
|
||||
]);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(renderedArgs.screen, 'player_urls'), false);
|
||||
});
|
||||
|
||||
test('screen edit page renders player urls as an adminlte table', async () => {
|
||||
test('screen edit page does not render a player URL table', async () => {
|
||||
const html = renderScreenEditPage(
|
||||
{
|
||||
id: 7,
|
||||
@@ -282,24 +247,14 @@ test('screen edit page renders player urls as an adminlte table', async () => {
|
||||
playlist_id: null,
|
||||
slug_update_confirm_live_connection_count: 2,
|
||||
slug_update_confirm_message: 'Are you sure you want to update the slug? This will refresh all screens using this slug.',
|
||||
player_urls: [
|
||||
{
|
||||
identifier: 'player-alpha',
|
||||
public_base_url: 'http://alpha.example',
|
||||
player_url: 'http://alpha.example/screen/demo-conference'
|
||||
}
|
||||
]
|
||||
},
|
||||
{ playlists: [] },
|
||||
'',
|
||||
{ id: 1 }
|
||||
);
|
||||
|
||||
assert.match(html, /Player URLs/);
|
||||
assert.match(html, /<table/i);
|
||||
assert.match(html, /card-body table-responsive p-0/);
|
||||
assert.match(html, /table table-striped w-100 mb-0/);
|
||||
assert.match(html, /player-alpha/);
|
||||
assert.doesNotMatch(html, /Player URLs/);
|
||||
assert.doesNotMatch(html, /player-alpha/);
|
||||
assert.match(html, /<input[^>]+id="screen-slug"[^>]+disabled/);
|
||||
assert.match(html, /Slug cannot be changed after creation/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user