openvibers/openvibe.bot Commits
Each resource names its page on openvibe.bot (links.console: a robot's /r/<id>, a machine's /machines), so OpenVibe.Services links to it (#198)
Changes
@@@ -24,8 +24,11 @@ const SUBJECT_RE = /^(usr|agt)_[0-9A-HJKMNP-TV-Z]{26}$/;const DEFAULT_LIMIT = 100;const MAX_LIMIT = 1000;++/** Where the resource's own page is (links.console): https only, for OpenVibe.Services to link to. */++const consoleOf = (baseUrl, path) => (/^https:\/\//.test(String(baseUrl || '')) ? { links: { console: `${String(baseUrl).replace(/\/+$/, '')}${path}` } } : {});++/** Only the public fields in common.resource-summary@1. Person-owned robots have no OVRN. */--function robotSummary(row) {++function robotSummary(row, baseUrl = null) {const summary = {id: row.id, kind: KIND, service: SERVICE,name: row.name, state: row.estop_latched ? 'estopped' : 'ready',@@@ -35,11 +38,11 @@ function robotSummary(row) {if (USER_SUBJECT_RE.test(String(row.owner_subject || ''))) summary.owner = { type: 'user', id: row.owner_subject };const ovrn = contracts.resources.nameOf(summary);if (ovrn) summary.ovrn = ovrn;-- return summary;++ return { ...summary, ...consoleOf(baseUrl, `/r/${encodeURIComponent(row.id)}`) };}/** A person's own machine (devices.owner_subject): online while its Node is connected. */--function machineSummary(row, online) {++function machineSummary(row, online, baseUrl = null) {const summary = {id: row.id, kind: MACHINE_KIND, service: SERVICE,name: row.name || row.id, state: online ? 'online' : 'offline',@@@ -47,7 +50,7 @@ function machineSummary(row, online) {updated_at: new Date(row.updated_at).toISOString(),};if (USER_SUBJECT_RE.test(String(row.owner_subject || ''))) summary.owner = { type: 'user', id: row.owner_subject };-- return summary;++ return { ...summary, ...consoleOf(baseUrl, '/machines') };}const encodeCursor = (id, kind = KIND) => Buffer.from(JSON.stringify([kind, id])).toString('base64url');@@@ -79,7 +82,7 @@ function filtersOf(query) {return { project, kind, owner, limit, cursor };}--function router({ db, apiAuth, hub = null }) {++function router({ db, apiAuth, hub = null, baseUrl = null }) {const r = express.Router();const wrap = (fn) => (req, res, next) => Promise.resolve(fn(req, res)).catch(next);const bad = (res, detail, ctx) => contracts.http.sendProblem(res, 400, 'resources.bad_query', { detail, ctx });@@@ -104,7 +107,7 @@ function router({ db, apiAuth, hub = null }) {const rows = await db.many(`SELECT id, owner_subject, name, estop_latched, created_at, updated_atFROM robots WHERE ($1::text IS NULL OR id > $1) AND ($3::text IS NULL OR owner_subject = $3) ORDER BY id LIMIT $2`,[cur ? cur.id : null, filters.limit + 1, filters.owner]);-- out.push(...rows.map(robotSummary));++ out.push(...rows.map((row) => robotSummary(row, baseUrl)));}if ((!filters.kind || filters.kind === MACHINE_KIND) && out.length <= filters.limit) {const after = cur && cur.kind === MACHINE_KIND ? cur.id : null;@@@ -112,7 +115,7 @@ function router({ db, apiAuth, hub = null }) {FROM devices WHERE owner_subject IS NOT NULL AND revoked_at IS NULL AND ($1::text IS NULL OR id > $1)AND ($3::text IS NULL OR owner_subject = $3) ORDER BY id LIMIT $2`,[after, filters.limit + 1 - out.length, filters.owner]);-- out.push(...rows.map((row) => machineSummary(row, !!(hub && hub.isOnline(row.id)))));++ out.push(...rows.map((row) => machineSummary(row, !!(hub && hub.isOnline(row.id)), baseUrl)));}const resources = out.slice(0, filters.limit);const last = resources[resources.length - 1];@@@ -78,7 +78,7 @@ function v1Router({ domain, apiAuth, limits, hub, jobs: jobService, config, submr.use(limits.reads('bot.read'));// Control first (bot.resource.control), then the index (bot.resource.read) for everything else under /resources.r.use('/resources/control', limits('bot.control', { minute: 120, hour: 1200 }), resourceControl.router({ domain, apiAuth, hub }));-- r.use('/resources', resourceIndex.router({ db: domain.db, apiAuth, hub }));++ r.use('/resources', resourceIndex.router({ db: domain.db, apiAuth, hub, baseUrl: config.baseUrl }));const manage = limits('bot.robot.manage', { minute: 30, hour: 300 });const control = limits('bot.control', { minute: 120, hour: 1200 });const machineScreen = createMachineScreen({ domain, hub });@@@ -21,7 +21,7 @@ const { boot, check, done } = require('./helpers/app');assert.deepStrictEqual(contracts.validate('common.resource-list-result@1', response.json).errors, []);assert.deepStrictEqual(response.json.resources.map((r) => r.id), [first.id, second.id, third.id].sort());for (const summary of response.json.resources) {-- assert.deepStrictEqual(Object.keys(summary).sort(), ['created_at', 'id', 'kind', 'name', 'owner', 'service', 'state', 'updated_at']);++ assert.deepStrictEqual(Object.keys(summary).sort().filter((k) => k !== 'links'), ['created_at', 'id', 'kind', 'name', 'owner', 'service', 'state', 'updated_at']);assert.strictEqual(summary.kind, 'bot.robot');assert.strictEqual(summary.service, 'bot');assert.strictEqual(summary.ovrn, undefined);@@@ -117,6 +117,13 @@ const { boot, check, done } = require('./helpers/app');assert.deepStrictEqual(all.json.resources.map((r) => [r.kind, r.id]), [...[first.id, second.id].sort().map((id) => ['bot.robot', id]), ['bot.machine', deviceId]], 'robots, then machines');const machine = all.json.resources.find((r) => r.id === deviceId);assert.deepStrictEqual([machine.state, machine.owner, machine.service], ['online', { type: 'user', id: alice.subject }, 'bot']);++ // Its page on openvibe.bot (links.console) when Bot's address is https, for Services to link to.++ const { machineSummary, robotSummary } = require('../server/api/resource-index');++ const row = { id: deviceId, owner_subject: alice.subject, name: 'desk-pc', created_at: new Date(), updated_at: new Date() };++ assert.deepStrictEqual(machineSummary(row, true, 'https://openvibe.bot/').links, { console: 'https://openvibe.bot/machines' });++ assert.deepStrictEqual(robotSummary({ ...row, id: first.id, estop_latched: false }, 'https://openvibe.bot').links, { console: `https://openvibe.bot/r/${first.id}` });++ assert.strictEqual(machineSummary(row, true, 'http://localhost:4630').links, undefined, 'https only');++ assert.deepStrictEqual(contracts.validate('common.resource-summary@1', machineSummary(row, true, 'https://openvibe.bot')).errors, []);assert.deepStrictEqual((await get('?kind=bot.machine')).json.resources.map((r) => r.id), [deviceId]);assert.deepStrictEqual((await get('?kind=bot.robot')).json.resources.map((r) => r.kind), ['bot.robot', 'bot.robot']);assert.deepStrictEqual((await get(`?owner=${bob.subject}`)).json.resources.map((r) => r.id), [second.id], 'never another person\'s');
@@ -24,8 +24,11 @@ const SUBJECT_RE = /^(usr|agt)_[0-9A-HJKMNP-TV-Z]{26}$/;
@@ -24,8 +24,11 @@ const SUBJECT_RE = /^(usr|agt)_[0-9A-HJKMNP-TV-Z]{26}$/;
const DEFAULT_LIMIT = 100;
const DEFAULT_LIMIT = 100;
const MAX_LIMIT = 1000;
const MAX_LIMIT = 1000;
+/** Where the resource's own page is (links.console): https only, for OpenVibe.Services to link to. */
+const consoleOf = (baseUrl, path) => (/^https:\/\//.test(String(baseUrl || '')) ? { links: { console: `${String(baseUrl).replace(/\/+$/, '')}${path}` } } : {});
+
/** Only the public fields in common.resource-summary@1. Person-owned robots have no OVRN. */
/** Only the public fields in common.resource-summary@1. Person-owned robots have no OVRN. */
-function robotSummary(row) {
+function robotSummary(row, baseUrl = null) {
const summary = {
const summary = {
id: row.id, kind: KIND, service: SERVICE,
id: row.id, kind: KIND, service: SERVICE,
name: row.name, state: row.estop_latched ? 'estopped' : 'ready',
name: row.name, state: row.estop_latched ? 'estopped' : 'ready',
@@ -35,11 +38,11 @@ function robotSummary(row) {
@@ -35,11 +38,11 @@ function robotSummary(row) {
if (USER_SUBJECT_RE.test(String(row.owner_subject || ''))) summary.owner = { type: 'user', id: row.owner_subject };
if (USER_SUBJECT_RE.test(String(row.owner_subject || ''))) summary.owner = { type: 'user', id: row.owner_subject };
const ovrn = contracts.resources.nameOf(summary);
const ovrn = contracts.resources.nameOf(summary);
if (ovrn) summary.ovrn = ovrn;
if (ovrn) summary.ovrn = ovrn;
- return summary;
+ return { ...summary, ...consoleOf(baseUrl, `/r/${encodeURIComponent(row.id)}`) };
}
}
/** A person's own machine (devices.owner_subject): online while its Node is connected. */
/** A person's own machine (devices.owner_subject): online while its Node is connected. */
-function machineSummary(row, online) {
+function machineSummary(row, online, baseUrl = null) {
const summary = {
const summary = {
id: row.id, kind: MACHINE_KIND, service: SERVICE,
id: row.id, kind: MACHINE_KIND, service: SERVICE,
name: row.name || row.id, state: online ? 'online' : 'offline',
name: row.name || row.id, state: online ? 'online' : 'offline',
@@ -47,7 +50,7 @@ function machineSummary(row, online) {
@@ -47,7 +50,7 @@ function machineSummary(row, online) {
updated_at: new Date(row.updated_at).toISOString(),
updated_at: new Date(row.updated_at).toISOString(),
};
};
if (USER_SUBJECT_RE.test(String(row.owner_subject || ''))) summary.owner = { type: 'user', id: row.owner_subject };
if (USER_SUBJECT_RE.test(String(row.owner_subject || ''))) summary.owner = { type: 'user', id: row.owner_subject };
- return summary;
+ return { ...summary, ...consoleOf(baseUrl, '/machines') };
}
}
const encodeCursor = (id, kind = KIND) => Buffer.from(JSON.stringify([kind, id])).toString('base64url');
const encodeCursor = (id, kind = KIND) => Buffer.from(JSON.stringify([kind, id])).toString('base64url');
@@ -79,7 +82,7 @@ function filtersOf(query) {
@@ -79,7 +82,7 @@ function filtersOf(query) {
return { project, kind, owner, limit, cursor };
return { project, kind, owner, limit, cursor };
}
}
-function router({ db, apiAuth, hub = null }) {
+function router({ db, apiAuth, hub = null, baseUrl = null }) {
const r = express.Router();
const r = express.Router();
const wrap = (fn) => (req, res, next) => Promise.resolve(fn(req, res)).catch(next);
const wrap = (fn) => (req, res, next) => Promise.resolve(fn(req, res)).catch(next);
const bad = (res, detail, ctx) => contracts.http.sendProblem(res, 400, 'resources.bad_query', { detail, ctx });
const bad = (res, detail, ctx) => contracts.http.sendProblem(res, 400, 'resources.bad_query', { detail, ctx });
@@ -104,7 +107,7 @@ function router({ db, apiAuth, hub = null }) {
@@ -104,7 +107,7 @@ function router({ db, apiAuth, hub = null }) {
const rows = await db.many(`SELECT id, owner_subject, name, estop_latched, created_at, updated_at
const rows = await db.many(`SELECT id, owner_subject, name, estop_latched, created_at, updated_at
FROM robots WHERE ($1::text IS NULL OR id > $1) AND ($3::text IS NULL OR owner_subject = $3) ORDER BY id LIMIT $2`,
FROM robots WHERE ($1::text IS NULL OR id > $1) AND ($3::text IS NULL OR owner_subject = $3) ORDER BY id LIMIT $2`,
[cur ? cur.id : null, filters.limit + 1, filters.owner]);
[cur ? cur.id : null, filters.limit + 1, filters.owner]);
- out.push(...rows.map(robotSummary));
+ out.push(...rows.map((row) => robotSummary(row, baseUrl)));
}
}
if ((!filters.kind || filters.kind === MACHINE_KIND) && out.length <= filters.limit) {
if ((!filters.kind || filters.kind === MACHINE_KIND) && out.length <= filters.limit) {
const after = cur && cur.kind === MACHINE_KIND ? cur.id : null;
const after = cur && cur.kind === MACHINE_KIND ? cur.id : null;
@@ -112,7 +115,7 @@ function router({ db, apiAuth, hub = null }) {
@@ -112,7 +115,7 @@ function router({ db, apiAuth, hub = null }) {
FROM devices WHERE owner_subject IS NOT NULL AND revoked_at IS NULL AND ($1::text IS NULL OR id > $1)
FROM devices WHERE owner_subject IS NOT NULL AND revoked_at IS NULL AND ($1::text IS NULL OR id > $1)
AND ($3::text IS NULL OR owner_subject = $3) ORDER BY id LIMIT $2`,
AND ($3::text IS NULL OR owner_subject = $3) ORDER BY id LIMIT $2`,
[after, filters.limit + 1 - out.length, filters.owner]);
[after, filters.limit + 1 - out.length, filters.owner]);
- out.push(...rows.map((row) => machineSummary(row, !!(hub && hub.isOnline(row.id)))));
+ out.push(...rows.map((row) => machineSummary(row, !!(hub && hub.isOnline(row.id)), baseUrl)));
}
}
const resources = out.slice(0, filters.limit);
const resources = out.slice(0, filters.limit);
const last = resources[resources.length - 1];
const last = resources[resources.length - 1];
@@ -78,7 +78,7 @@ function v1Router({ domain, apiAuth, limits, hub, jobs: jobService, config, subm
@@ -78,7 +78,7 @@ function v1Router({ domain, apiAuth, limits, hub, jobs: jobService, config, subm
r.use(limits.reads('bot.read'));
r.use(limits.reads('bot.read'));
// Control first (bot.resource.control), then the index (bot.resource.read) for everything else under /resources.
// Control first (bot.resource.control), then the index (bot.resource.read) for everything else under /resources.
r.use('/resources/control', limits('bot.control', { minute: 120, hour: 1200 }), resourceControl.router({ domain, apiAuth, hub }));
r.use('/resources/control', limits('bot.control', { minute: 120, hour: 1200 }), resourceControl.router({ domain, apiAuth, hub }));
- r.use('/resources', resourceIndex.router({ db: domain.db, apiAuth, hub }));
+ r.use('/resources', resourceIndex.router({ db: domain.db, apiAuth, hub, baseUrl: config.baseUrl }));
const manage = limits('bot.robot.manage', { minute: 30, hour: 300 });
const manage = limits('bot.robot.manage', { minute: 30, hour: 300 });
const control = limits('bot.control', { minute: 120, hour: 1200 });
const control = limits('bot.control', { minute: 120, hour: 1200 });
const machineScreen = createMachineScreen({ domain, hub });
const machineScreen = createMachineScreen({ domain, hub });
@@ -21,7 +21,7 @@ const { boot, check, done } = require('./helpers/app');
@@ -21,7 +21,7 @@ const { boot, check, done } = require('./helpers/app');
assert.deepStrictEqual(contracts.validate('common.resource-list-result@1', response.json).errors, []);
assert.deepStrictEqual(contracts.validate('common.resource-list-result@1', response.json).errors, []);
assert.deepStrictEqual(response.json.resources.map((r) => r.id), [first.id, second.id, third.id].sort());
assert.deepStrictEqual(response.json.resources.map((r) => r.id), [first.id, second.id, third.id].sort());
for (const summary of response.json.resources) {
for (const summary of response.json.resources) {
- assert.deepStrictEqual(Object.keys(summary).sort(), ['created_at', 'id', 'kind', 'name', 'owner', 'service', 'state', 'updated_at']);
+ assert.deepStrictEqual(Object.keys(summary).sort().filter((k) => k !== 'links'), ['created_at', 'id', 'kind', 'name', 'owner', 'service', 'state', 'updated_at']);
assert.strictEqual(summary.kind, 'bot.robot');
assert.strictEqual(summary.kind, 'bot.robot');
assert.strictEqual(summary.service, 'bot');
assert.strictEqual(summary.service, 'bot');
assert.strictEqual(summary.ovrn, undefined);
assert.strictEqual(summary.ovrn, undefined);
@@ -117,6 +117,13 @@ const { boot, check, done } = require('./helpers/app');
@@ -117,6 +117,13 @@ const { boot, check, done } = require('./helpers/app');
assert.deepStrictEqual(all.json.resources.map((r) => [r.kind, r.id]), [...[first.id, second.id].sort().map((id) => ['bot.robot', id]), ['bot.machine', deviceId]], 'robots, then machines');
assert.deepStrictEqual(all.json.resources.map((r) => [r.kind, r.id]), [...[first.id, second.id].sort().map((id) => ['bot.robot', id]), ['bot.machine', deviceId]], 'robots, then machines');
const machine = all.json.resources.find((r) => r.id === deviceId);
const machine = all.json.resources.find((r) => r.id === deviceId);
assert.deepStrictEqual([machine.state, machine.owner, machine.service], ['online', { type: 'user', id: alice.subject }, 'bot']);
assert.deepStrictEqual([machine.state, machine.owner, machine.service], ['online', { type: 'user', id: alice.subject }, 'bot']);
+ // Its page on openvibe.bot (links.console) when Bot's address is https, for Services to link to.
+ const { machineSummary, robotSummary } = require('../server/api/resource-index');
+ const row = { id: deviceId, owner_subject: alice.subject, name: 'desk-pc', created_at: new Date(), updated_at: new Date() };
+ assert.deepStrictEqual(machineSummary(row, true, 'https://openvibe.bot/').links, { console: 'https://openvibe.bot/machines' });
+ assert.deepStrictEqual(robotSummary({ ...row, id: first.id, estop_latched: false }, 'https://openvibe.bot').links, { console: `https://openvibe.bot/r/${first.id}` });
+ assert.strictEqual(machineSummary(row, true, 'http://localhost:4630').links, undefined, 'https only');
+ assert.deepStrictEqual(contracts.validate('common.resource-summary@1', machineSummary(row, true, 'https://openvibe.bot')).errors, []);
assert.deepStrictEqual((await get('?kind=bot.machine')).json.resources.map((r) => r.id), [deviceId]);
assert.deepStrictEqual((await get('?kind=bot.machine')).json.resources.map((r) => r.id), [deviceId]);
assert.deepStrictEqual((await get('?kind=bot.robot')).json.resources.map((r) => r.kind), ['bot.robot', 'bot.robot']);
assert.deepStrictEqual((await get('?kind=bot.robot')).json.resources.map((r) => r.kind), ['bot.robot', 'bot.robot']);
assert.deepStrictEqual((await get(`?owner=${bob.subject}`)).json.resources.map((r) => r.id), [second.id], 'never another person\'s');
assert.deepStrictEqual((await get(`?owner=${bob.subject}`)).json.resources.map((r) => r.id), [second.id], 'never another person\'s');