openvibers/openvibe.bot Commits

A desk host's desks are seen and driven: screen, input and the live view on a desk check the desk's own capabilities (desk.screen, desk.input), not the machine's own screen, which a desk host does not have (#179)

OP OpenVibers committed 17 days ago · 7b6499e702 · 4 files, +45 −5

Changes

ddiff --git a/server/machine-screen.js b/server/machine-screen.js
iindex 6c66bba..ffe576f 100644
---- a/server/machine-screen.js
++++ b/server/machine-screen.js
@@@ -67,10 +67,12 @@ function createMachineScreen({ domain, hub, now = () => Date.now() }) {
if (!hub.isOnline(device.id)) throw new BotError(409, 'bot.device_offline', 'the machine is offline');
// Before any key is made: the machine says whether it can (Node v0.12.0's computer plugin, a capture tool).
const offered = hub.computerOf(device.id);
-- if (!(offered && offered.screen && offered.screen.stream === true)) {
++ // The person's own screen needs a live capture of it; a desk's is the desk's own (desk.screen, below): a desk
++ // host has no screen of its own to show, only its desks.
++ if (desk === 'main' && !(offered && offered.screen && offered.screen.stream === true)) {
throw new BotError(409, 'bot.computer_off', 'this machine does not show its screen live: it needs OpenVibe Node v0.12.0 or newer, with the computer plugin running in your graphical session');
}
-- if (desk !== 'main' && !(offered.desk && offered.desk.screen)) {
++ if (desk !== 'main' && !(offered && offered.desk && offered.desk.screen)) {
throw new BotError(409, 'bot.computer_off', 'this machine has no private desk to show: it needs OpenVibe Node v0.15.0 or newer on Linux, with Xvfb and allow_desk on');
}
if (desk !== 'main' && desk !== 'private' && offered.desk.named !== true) {
ddiff --git a/server/realtime.js b/server/realtime.js
iindex d0adc99..7edbc49 100644
---- a/server/realtime.js
++++ b/server/realtime.js
@@@ -47,6 +47,9 @@ const NO_DESK = 'this machine has no private desk: it needs OpenVibe Node v0.15.
const NO_NAMED_DESKS = 'this machine runs one desk only (private): several named desks need OpenVibe Node v0.19.0 or newer';
/** The desk a command names: main (the person's own screen, the default), private or desk-<name> (contracts 0.215.0). */
const deskOf = (value) => (typeof value.desk === 'string' && value.desk ? value.desk : 'main');
++// The screen and the mouse and keyboard of a desk are the desk's own (Node's desk.screen, desk.input): a desk host with
++// no screen of its own (no one signed in, Node v0.20.0 --desk-host) offers them on its desks only.
++const onDeskOnly = (kind, value) => (kind === 'screen' || kind === 'input') && deskOf(value) !== 'main';
/** A command that acts on a screen (the mouse and keyboard, or the browser doing more than reading it). */
const actsOnScreen = (kind, value) => kind === 'input' || (kind === 'browser' && !['read', 'close'].includes(value.action));
const COMPUTER_KINDS = Object.keys(COMPUTER_CAP);
@@@ -760,10 +763,11 @@ function createRealtime({ config, keys, userAuth, log = console, now = () => Dat
if (!device || !device.online) return refuse('bot.device_offline', 'the machine is offline', summary);
const offered = computerOf(deviceId);
if (screen) {
-- if (value.action === 'start' && !(offered && offered.screen && offered.screen.stream === true)) {
++ // A desk's live view is the desk's own screen (desk.screen, checked below), not the person's.
++ if (value.action === 'start' && deskOf(value) === 'main' && !(offered && offered.screen && offered.screen.stream === true)) {
return refuse('bot.computer_off', 'this machine does not show its screen live: it needs OpenVibe Node v0.12.0 or newer, with the computer plugin running in your graphical session', summary);
}
-- } else if (!offered || !offered[COMPUTER_CAP[kind]]) {
++ } else if (!offered || !(onDeskOnly(kind, value) ? (offered.desk && offered.desk[kind] === true) : offered[COMPUTER_CAP[kind]])) {
return refuse('bot.computer_off', kind === 'desk' ? NO_DESK : `this machine does not offer ${COMPUTER_CAP[kind]}: its computer plugin is off (openvibe-node, --computer)`, summary);
}
// A desk other than the person's own screen needs a machine that has desks; one other than private (a named desk,
ddiff --git a/test/machine-computer.test.js b/test/machine-computer.test.js
iindex 5d80fa4..bffbb7c 100644
---- a/test/machine-computer.test.js
++++ b/test/machine-computer.test.js
@@@ -35,7 +35,7 @@ const { boot, check, done } = require('./helpers/app');
else if (m.kind === 'input') c.send(frame('ack', { id: m.id, data: { done: true, action: m.value.action } }));
else if (m.kind === 'browser') c.send(frame('ack', { id: m.id, data: { url: m.value.url || 'https://example.org/', title: 'Example', text: 'Example page', truncated: false, links: [] } }));
else if (m.kind === 'desk') c.send(frame('ack', { id: m.id, data: { running: m.value.action !== 'stop', display: ':91', width: 1280, height: 800 } }));
-- else if (m.kind === 'screen' && m.value.desk === 'private') c.send(frame('ack', { id: m.id, data: { format: 'png', width: 1280, height: 800, content_b64: 'iVBORw0KGgo=' } }));
++ else if (m.kind === 'screen' && m.value.desk && m.value.desk !== 'main') c.send(frame('ack', { id: m.id, data: { format: 'png', width: 1280, height: 800, content_b64: 'iVBORw0KGgo=' } }));
else c.send(frame('nack', { id: m.id, fault_code: 'not_allowed', message: 'this machine allows observing only' }));
});
// Beat like a real Node while the socket is open, so a longer check never sees its machine go offline.
@@@ -198,6 +198,30 @@ const { boot, check, done } = require('./helpers/app');
single.c.close();
});
++ await check('a desk host (Node v0.20.0 --desk-host): no screen of its own, yet its desks are seen and driven', async () => {
++ // Nobody is signed in to a desk host: it offers no screen or input of its own, only its desks' (desk.screen, desk.input).
++ const { screen: _s, input: _i, ...headless } = COMPUTER;
++ const host = await machine(alex, { ...headless, desk: { level: 'trusted', window_manager: 'openbox', input: true, screen: true, named: true, max_desks: 4 } });
++ const look = await cmd(host.deviceId, { kind: 'screen', value: { desk: 'desk-task-a', max_width: 1280 } }, { user: alex });
++ assert.strictEqual(look.json.result, 'ack', look.text);
++ const click = await cmd(host.deviceId, { kind: 'input', value: { action: 'click', x: 10, y: 10, width: 1280, height: 800, desk: 'desk-task-a' } }, { user: alex });
++ assert.strictEqual(click.json.result, 'ack', click.text);
++ assert.strictEqual(seen.at(-1).value.desk, 'desk-task-a');
++ // Its own screen is still refused: there is none.
++ const own = await cmd(host.deviceId, { kind: 'screen', value: { max_width: 1280 } }, { user: alex });
++ assert.deepStrictEqual([own.status, own.json.code], [409, 'bot.computer_off']);
++ const ownClick = await cmd(host.deviceId, { kind: 'input', value: { action: 'click', x: 1, y: 1, width: 2, height: 2 } }, { user: alex });
++ assert.deepStrictEqual([ownClick.status, ownClick.json.code], [409, 'bot.computer_off']);
++ // A desk that cannot be seen (no X capture tool) is refused, never sent.
++ const blind = await machine(alex, { ...headless, desk: { level: 'trusted', window_manager: 'openbox', input: true, screen: false, named: true, max_desks: 4 } });
++ const before = seen.length;
++ const dark = await cmd(blind.deviceId, { kind: 'screen', value: { desk: 'desk-task-a' } }, { user: alex });
++ assert.deepStrictEqual([dark.status, dark.json.code], [409, 'bot.computer_off']);
++ assert.strictEqual(seen.length, before, 'nothing reached the machine');
++ host.c.close();
++ blind.c.close();
++ });
++
await check('the audit keeps what was asked in short, never stdin, arguments or content', async () => {
const rows = await t.db.many('SELECT * FROM command_audit WHERE robot_id = $1 ORDER BY id', [mine.deviceId]);
assert.ok(rows.length >= 3);
ddiff --git a/test/machine-screen.test.js b/test/machine-screen.test.js
iindex 6b88004..4f9454e 100644
---- a/test/machine-screen.test.js
++++ b/test/machine-screen.test.js
@@@ -174,6 +174,16 @@ const { boot, check, done } = require('./helpers/app');
single.c.close();
});
++ await check('a desk host (Node v0.20.0 --desk-host) shows its desks live though it has no screen of its own', async () => {
++ const host = await machine(alex, { files: SCREEN.files, desk: { level: 'trusted', window_manager: 'openbox', input: true, screen: true, named: true, max_desks: 4 } });
++ const r = await screen('POST', host.deviceId, { user: alex, body: { seconds: 60, desk: 'desk-task-a' } });
++ assert.strictEqual(r.status, 201, r.text);
++ assert.strictEqual(seen.at(-1).value.desk, 'desk-task-a');
++ const own = await screen('POST', host.deviceId, { user: alex, body: { seconds: 60 } });
++ assert.deepStrictEqual([own.status, own.json.code], [409, 'bot.computer_off'], 'its own screen: there is none');
++ host.c.close();
++ });
++
await check('watch and take over an agent\'s desk (Desks plan D4): a task\'s link opens its desk; the owner\'s mouse and keyboard go to the desk shown, as the owner; same site only', async () => {
const desk = { level: 'trusted', window_manager: 'openbox', input: true, screen: true, named: true, max_desks: 4 };
const m = await machine(alex, { ...SCREEN, input: { level: 'trusted', tool: 'xdotool' }, desk });