openvibers/openvibe.bot Commits

Bot: a Network-paired Node's status is accepted (drivers as objects keep their names) (#100)

OP OpenVibers committed 30 days ago · 2fb6ee4919 · 2 files, +14 −3

Found while proving a worker machine end to end on production (plan T16): every `status` frame from a Network-paired OpenVibe.Node was refused with `bot.bad_frame`, so its declared kind, drivers and version never reached the device row.

**Cause:** the Node's status lists its drivers as objects (`{ name, driver, state, … }`, OpenVibe.Node `internal/protocol` `Status`). Its pairing request lists them by name. Bot accepted names only.

**Fix:** `persistDeclared` takes either shape and keeps the names (`driver`, else `name`). A driver object with neither is still refused.

**Tests:** `node-principal` sends a status in the Node's own shape: the names are kept and no error comes back.

Changes

ddiff --git a/server/realtime.js b/server/realtime.js
iindex 9c4f31a..9444b22 100644
---- a/server/realtime.js
++++ b/server/realtime.js
@@@ -224,14 +224,18 @@ function createRealtime({ config, keys, userAuth, log = console, now = () => Dat
* `status` of a Network-paired device: device_kind, drivers, capabilities and agent_version are persisted
* when they differ from the row. Any invalid one → bot.bad_frame and the row unchanged.
*/
-- async function persistDeclared(conn, msg) {
++ async function persistDeclared(conn, frame) {
const d = conn.device;
++ // OpenVibe.Node's status lists its drivers as objects ({ name, driver, state, … }, internal/protocol Status)
++ // where its pairing request lists their names: the row keeps the names either way.
++ const driverName = (x) => (typeof x === 'string' ? x : (x && typeof x === 'object' && typeof (x.driver || x.name) === 'string' ? x.driver || x.name : null));
++ const msg = Array.isArray(frame.drivers) ? { ...frame, drivers: frame.drivers.map(driverName) } : frame;
const has = (k) => msg[k] !== undefined;
if ((has('device_kind') && !DEVICE_KINDS.has(msg.device_kind))
|| (has('drivers') && !(Array.isArray(msg.drivers) && msg.drivers.every((x) => typeof x === 'string')))
|| (has('capabilities') && !(msg.capabilities && typeof msg.capabilities === 'object' && !Array.isArray(msg.capabilities)))
|| (has('agent_version') && !(typeof msg.agent_version === 'string' && msg.agent_version.length <= MAX_AGENT_VERSION))) {
-- return sendError(conn, 'bot.bad_frame', `device_kind must be one of ${[...DEVICE_KINDS].join('|')}, drivers an array of strings, capabilities an object, agent_version a string of at most ${MAX_AGENT_VERSION}`);
++ return sendError(conn, 'bot.bad_frame', `device_kind must be one of ${[...DEVICE_KINDS].join('|')}, drivers an array of names or { name } objects, capabilities an object, agent_version a string of at most ${MAX_AGENT_VERSION}`);
}
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
const change = {};
ddiff --git a/test/node-principal.test.js b/test/node-principal.test.js
iindex 0f2111e..d7c7374 100644
---- a/test/node-principal.test.js
++++ b/test/node-principal.test.js
@@@ -102,7 +102,7 @@ const WHIP_BASE = 'https://whip.test/ingest';
assert.deepStrictEqual(d.drivers, ['pca9685', 'picamera2']);
assert.deepStrictEqual(d.capabilities, { camera: true });
assert.strictEqual(d.agent_version, '0.3.1');
-- for (const bad of [{ device_kind: 'toaster' }, { drivers: 'pca9685' }, { drivers: [1] }, { agent_version: 'x'.repeat(41) }, { capabilities: [] }]) {
++ for (const bad of [{ device_kind: 'toaster' }, { drivers: 'pca9685' }, { drivers: [1] }, { drivers: [{ state: 'up' }] }, { agent_version: 'x'.repeat(41) }, { capabilities: [] }]) {
const before = c.messages.filter((m) => m.type === 'error').length;
c.send({ v: 1, seq: 2, ts: Date.now(), type: 'status', ...bad });
await c.waitFor(() => c.messages.filter((m) => m.type === 'error').length > before);
@@@ -113,6 +113,13 @@ const WHIP_BASE = 'https://whip.test/ingest';
assert.strictEqual(after.kind, 'bridge');
assert.deepStrictEqual(after.drivers, ['pca9685', 'picamera2']);
assert.strictEqual(after.agent_version, '0.3.1');
++ // The Node's own status frame lists drivers as objects (OpenVibe.Node internal/protocol Status): the names stay.
++ const errors = c.messages.filter((m) => m.type === 'error').length;
++ c.send({ v: 1, seq: 3, ts: Date.now(), type: 'status', firmware: 'x', device_kind: 'bridge', drivers: [{ name: 'adeept', driver: 'adeept_adr036', state: 'up' }, { name: 'dryrun', state: 'down' }], faults: [] });
++ let named;
++ for (let i = 0; i < 40; i++) { named = await deviceRow(hello.device_id); if (named.drivers.length && named.drivers[0] === 'adeept_adr036') break; await t.wait(25); }
++ assert.deepStrictEqual(named.drivers, ['adeept_adr036', 'dryrun']);
++ assert.strictEqual(c.messages.filter((m) => m.type === 'error').length, errors, 'no bad_frame for the Node\'s own status');
c.close();
});