156 lines
5.9 KiB
JavaScript
156 lines
5.9 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const http = require('http');
|
|
const WebSocket = require('ws');
|
|
|
|
require('../src/common');
|
|
|
|
const { createPageAuthToken } = require('../src/request-auth');
|
|
const { createPlayerRuntime } = require('../src/player/runtime');
|
|
|
|
const originalSecret = process.env.PULSE_SIGNAGE_SHARED_SECRET;
|
|
|
|
test.after(() => {
|
|
if (originalSecret === undefined) {
|
|
delete process.env.PULSE_SIGNAGE_SHARED_SECRET;
|
|
} else {
|
|
process.env.PULSE_SIGNAGE_SHARED_SECRET = originalSecret;
|
|
}
|
|
});
|
|
|
|
function waitFor(predicate, timeoutMs = 1000) {
|
|
const start = Date.now();
|
|
return new Promise((resolve, reject) => {
|
|
const tick = () => {
|
|
const value = predicate();
|
|
if (value) {
|
|
return resolve(value);
|
|
}
|
|
if (Date.now() - start > timeoutMs) {
|
|
return reject(new Error('Timed out waiting for runtime state.'));
|
|
}
|
|
setTimeout(tick, 25);
|
|
};
|
|
tick();
|
|
});
|
|
}
|
|
|
|
test('player runtime snapshots websocket state and checks live names', async () => {
|
|
process.env.PULSE_SIGNAGE_SHARED_SECRET = 'runtime-secret';
|
|
|
|
const runtime = createPlayerRuntime({ pool: null });
|
|
const server = http.createServer();
|
|
runtime.installWebsocket(server);
|
|
|
|
await new Promise((resolve) => server.listen(0, resolve));
|
|
const { port } = server.address();
|
|
const token = createPageAuthToken({ scope: 'player', slug: 'test2' });
|
|
const client = new WebSocket(`ws://127.0.0.1:${port}/ws/screens/test2?auth=${encodeURIComponent(token)}`);
|
|
|
|
try {
|
|
await new Promise((resolve, reject) => {
|
|
client.once('open', resolve);
|
|
client.once('error', reject);
|
|
});
|
|
|
|
client.send(JSON.stringify({
|
|
type: 'state',
|
|
clientId: 'client-1',
|
|
clientName: ' Lobby Player ',
|
|
deviceId: 'device 123!?',
|
|
userAgent: 'Mozilla/5.0 (unit test)',
|
|
viewport: { width: 1920, height: 1080 },
|
|
page: 'http://localhost:8081/screen/test2',
|
|
paused: true,
|
|
blackout: false,
|
|
currentSlide: { id: 9, title: 'Intro', kind: 'slide', playlistSignature: 'sig' }
|
|
}));
|
|
|
|
await waitFor(() => {
|
|
const snapshot = runtime.snapshotConnections('test2')[0];
|
|
return snapshot && snapshot.clientName === 'Lobby Player' ? snapshot : null;
|
|
});
|
|
const snapshot = runtime.snapshotConnections('test2')[0];
|
|
|
|
assert.equal(snapshot.clientName, 'Lobby Player');
|
|
assert.equal(snapshot.deviceId, 'device123');
|
|
assert.match(snapshot.label, /Lobby Player/);
|
|
assert.equal(snapshot.paused, true);
|
|
assert.equal(snapshot.currentSlideId, 9);
|
|
assert.equal(snapshot.currentSlideTitle, 'Intro');
|
|
assert.equal(await runtime.isClientNameAvailableOnScreen(null, 'Lobby Player', 'other-device'), false);
|
|
assert.equal(await runtime.isClientNameAvailableOnScreen(null, 'Lobby Player', 'device123'), true);
|
|
assert.equal(await runtime.isClientNameAvailableOnScreen(null, 'Other Name', 'device123'), true);
|
|
} finally {
|
|
client.close();
|
|
await new Promise((resolve) => server.close(resolve));
|
|
}
|
|
});
|
|
|
|
test('player runtime sends targeted and broadcast commands to live sockets', async () => {
|
|
process.env.PULSE_SIGNAGE_SHARED_SECRET = 'runtime-secret';
|
|
|
|
const runtime = createPlayerRuntime({ pool: null });
|
|
const server = http.createServer();
|
|
runtime.installWebsocket(server);
|
|
|
|
await new Promise((resolve) => server.listen(0, resolve));
|
|
const { port } = server.address();
|
|
const token = createPageAuthToken({ scope: 'player', slug: 'test2' });
|
|
const clientA = new WebSocket(`ws://127.0.0.1:${port}/ws/screens/test2?auth=${encodeURIComponent(token)}`);
|
|
const clientB = new WebSocket(`ws://127.0.0.1:${port}/ws/screens/test2?auth=${encodeURIComponent(token)}`);
|
|
|
|
function openClient(client) {
|
|
return new Promise((resolve, reject) => {
|
|
client.once('open', resolve);
|
|
client.once('error', reject);
|
|
});
|
|
}
|
|
|
|
function waitForMessage(client) {
|
|
return new Promise((resolve) => {
|
|
client.once('message', (raw) => {
|
|
resolve(JSON.parse(String(raw)));
|
|
});
|
|
});
|
|
}
|
|
|
|
try {
|
|
await Promise.all([openClient(clientA), openClient(clientB)]);
|
|
|
|
clientA.send(JSON.stringify({ type: 'state', clientId: 'client-a', clientName: 'Alpha', deviceId: 'device-a', page: 'http://localhost:8081/screen/test2' }));
|
|
clientB.send(JSON.stringify({ type: 'state', clientId: 'client-b', clientName: 'Beta', deviceId: 'device-b', page: 'http://localhost:8081/screen/test2' }));
|
|
|
|
await waitFor(() => {
|
|
const connections = runtime.snapshotConnections('test2');
|
|
return connections.length === 2 && connections.every((connection) => connection.clientName);
|
|
});
|
|
const [firstConnection, secondConnection] = runtime.snapshotConnections('test2');
|
|
|
|
const targetedMessagePromise = waitForMessage(clientA);
|
|
const targetedCount = await runtime.sendCommandToConnection('test2', firstConnection.id, { action: 'pause' });
|
|
assert.equal(targetedCount, 1);
|
|
const targetedMessage = await targetedMessagePromise;
|
|
assert.equal(targetedMessage.type, 'command');
|
|
assert.equal(targetedMessage.action, 'pause');
|
|
assert.equal(targetedMessage.targetConnectionId, firstConnection.id);
|
|
|
|
const broadcastPromises = [waitForMessage(clientA), waitForMessage(clientB)];
|
|
const broadcastCount = await runtime.broadcastCommand('test2', 'resume');
|
|
assert.equal(broadcastCount, 2);
|
|
const [broadcastA, broadcastB] = await Promise.all(broadcastPromises);
|
|
assert.equal(broadcastA.type, 'command');
|
|
assert.equal(broadcastA.command, 'resume');
|
|
assert.equal(broadcastB.type, 'command');
|
|
assert.equal(broadcastB.command, 'resume');
|
|
assert.equal(broadcastA.targetConnectionId, undefined);
|
|
assert.equal(broadcastB.targetConnectionId, undefined);
|
|
|
|
assert.equal(await runtime.sendCommandToConnection('test2', 'missing', 'pause'), 0);
|
|
assert.equal(secondConnection.clientName, 'Beta');
|
|
} finally {
|
|
clientA.close();
|
|
clientB.close();
|
|
await new Promise((resolve) => server.close(resolve));
|
|
}
|
|
}); |