Release v2.10.4
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m53s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 32s

This commit is contained in:
2026-08-29 12:12:39 +01:00
parent 3f4f57020a
commit ed58da4416
18 changed files with 260 additions and 31 deletions
+88
View File
@@ -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();