openvibers/openvibe.extensions Commits
Rename Extensions machine references to Link (#20)
Changes
@@@ -150,7 +150,7 @@ browser/lib/api.js the only code that makes requests to the Coupons APIlib/network-api.js the Dock's state on the Network (GET/PUT /api/v1/me/dock), with the person's own tokenlib/auth.js the Dock's one-click OAuth sign-in: PKCE, state, refresh, sign-out-- lib/controller.js popup logic without the DOM (tested in Node)++ lib/controller.js popup logic without the DOM (tested in Link)lib/platform.js chrome.* / browser.*: tabs.query, tabs.create (hand-off pages only), storage.local, permissionslib/format.js wording ("Expiry unknown", "No restrictions stated", …)icons/ generated by scripts/icons.js@@@ -162,7 +162,7 @@ scripts/icons.js draws the icons (no dependencies)```--## Tests (Node 22, no browser, no dependencies)++## Tests (Link 22, no browser, no dependencies)```bashnpm test@@@ -250,7 +250,7 @@ shows "Allow access to openvibe.coupons".- [OpenVibe.Live](https://github.com/OpenVibers/OpenVibe.Live) `/embed/agent/<ags_id>?vt=…`, theagent view's frame. Its `frame-ancestors` list is first-party site origins; an extension origin isnot on it yet.--- No npm dependencies. Tests and scripts use Node's standard library.++- No npm dependencies. Tests and scripts use Link's standard library.---@@@ -93,7 +93,7 @@}if (action === 'seal-fill') {if (!fillApi || m.clicked !== true || !FILL_REQUEST_ID.test(String(m.request_id || '')) || typeof m.origin !== 'string' || !EXACT_HTTPS_ORIGIN.test(m.origin) || origin(m.origin) !== m.origin-- || !/^nod_[0-9A-HJKMNP-TV-Z]{26}$/.test(String(m.machine_id || '')) || typeof m.item_id !== 'string') throw new Error('Choose a valid sign-in request.');++ || !/^lnk_[0-9A-HJKMNP-TV-Z]{26}$/.test(String(m.machine_id || '')) || typeof m.item_id !== 'string') throw new Error('Choose a valid sign-in request.');const s = await unlocked();const requests = await fillApi.requests();const pending = (requests.requests || []).find((r) => r.id === m.request_id && r.origin === m.origin && r.machine_node_id === m.machine_id && r.expires_at > now());@@@ -107,7 +107,7 @@if (m.include_code === true && login.totp) grant.code = await passwords.totp(login.totp);const plain = new TextEncoder().encode(JSON.stringify(grant));try {-- const envelope = await core.seal(plain, { lock: 'people', keys: [{ subject: { type: 'node', id: m.machine_id }, publicKey: target.public_key }] });++ const envelope = await core.seal(plain, { lock: 'people', keys: [{ subject: { type: 'link', id: m.machine_id }, publicKey: target.public_key }] });await still(s);let binary = ''; for (const b of envelope.bytes) binary += String.fromCharCode(b);return { ok: true, envelope: btoa(binary), thumbprint: target.thumbprint };@@@ -2,8 +2,8 @@Scripts for streaming from and controlling hardware devices (Raspberry Pi, single-board computers, etc.) with OpenVibe.Live.--> **Connecting a new robot? Use [OpenVibe.Bot](https://openvibe.bot) and the [OpenVibe Node](https://github.com/OpenVibers/OpenVibe.Node).**--> The Node pairs with a one-time code (no stream key) and runs your driver as a plugin++> **Connecting a new robot? Use [OpenVibe.Bot](https://openvibe.bot) and the [OpenVibe Link](https://github.com/OpenVibers/OpenVibe.Link).**++> The Link pairs with a one-time code (no stream key) and runs your driver as a plugin> ([write a driver](https://openvibe.bot/docs/drivers)); Bot gives the robot a panel built from its profile, roles, a> queue, limits and a latched e-stop, and a Live channel can show that panel under its stream. The controller here> uses Live's stream-key hardware socket, which keeps working for existing setups and is retired once they have movednnew file mode 100644@@@ -0,0 +1,10 @@++'use strict';++const assert = require('node:assert/strict');++const { spawnSync } = require('node:child_process');++const path = require('node:path');++const root = path.resolve(__dirname, '..');++const oldName = ['OpenVibe[ .-]No' + 'de', 'openvibe-' + 'node', 'no' + 'd_', 'node[.]openvibe[.]network', 'node:lnk_', 'openvibe[.](bot|my|network)/mach' + 'ines'].join('|');++const result = spawnSync('git', ['grep', '-n', '-I', '-i', '-E', oldName, '--', '.', ':!migrations/**', ':!test/link-brand.test.js'], { cwd: root, encoding: 'utf8' });++assert.ok(result.status === 0 || result.status === 1, result.stderr || 'git grep failed');++assert.equal(result.status, 1, `Old Link brand or principal returned:\n${(result.stdout || '').split('\n').slice(0, 10).join('\n')}`);++console.log(' ok old Link brand and principal IDs are absent');@@@ -24,7 +24,7 @@ async function fixture() {content: async (id) => ciphertexts.get(id), save: async (bytes) => { ciphertexts.set(String(ciphertexts.size + 1), bytes); return { id: '1' }; },};const storage = { getSession: async (k) => memory.get(k), setSession: async (obj) => { writes.push(obj); for (const [k, v] of Object.entries(obj)) memory.set(k, v); }, removeSession: async (keys) => keys.forEach((k) => memory.delete(k)) };-- const options = { core, payload, passwords, api, fillApi: { requests: async () => ({ requests: [{ id: 'fr_01JAB2C3D4E5F6G7H8J9K0MNPQ', origin: 'https://example.test', machine_node_id: 'nod_01JAB2C3D4E5F6G7H8J9K0MNPQ', expires_at: 60_000 }] }), targets: async () => ({ machines: [{ id: 'nod_01JAB2C3D4E5F6G7H8J9K0MNPQ', public_key: options.nodeKey, thumbprint: 'network-thumbprint' }] }), grant: async () => ({ outcome: 'ok' }) }, storage, scheduleLock: async () => {}, cancelLock: async () => {}, tabs: { active: async () => active }, fill: async (...args) => { fills.push(args); return { ok: true }; }, clipboard: { write: async (v) => clipboard.push(v) }, account: async () => account, now: () => clock };++ const options = { core, payload, passwords, api, fillApi: { requests: async () => ({ requests: [{ id: 'fr_01JAB2C3D4E5F6G7H8J9K0MNPQ', origin: 'https://example.test', machine_node_id: 'lnk_01JAB2C3D4E5F6G7H8J9K0MNPQ', expires_at: 60_000 }] }), targets: async () => ({ machines: [{ id: 'lnk_01JAB2C3D4E5F6G7H8J9K0MNPQ', public_key: options.nodeKey, thumbprint: 'network-thumbprint' }] }), grant: async () => ({ outcome: 'ok' }) }, storage, scheduleLock: async () => {}, cancelLock: async () => {}, tabs: { active: async () => active }, fill: async (...args) => { fills.push(args); return { ok: true }; }, clipboard: { write: async (v) => clipboard.push(v) }, account: async () => account, now: () => clock };const vault = create(options);const ask = (type, values = {}, sender = { url: POPUP }) => vault.handle({ type: 'locker:' + type, tabId: 1, site: 'https://example.test', ...values }, sender, POPUP);return { vault, ask, memory, fills, clipboard, api, options, writes, pass, recovery, ciphertexts,@@@ -90,16 +90,16 @@ test('locking during a pending decrypt cancels the fill', async () => {test('Companion seals a selected login to one machine and returns ciphertext only', async () => {const f = await fixture(); await f.ask('unlock', { clicked: true, secret: f.pass });await f.ask('save', { clicked: true, username: 'alice', password: 'fixture password', totp: 'otpauth://totp/x?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ' });-- const node = await core.generatePersonKeys(), machine = 'nod_01JAB2C3D4E5F6G7H8J9K0MNPQ';++ const node = await core.generatePersonKeys(), machine = 'lnk_01JAB2C3D4E5F6G7H8J9K0MNPQ';f.options.nodeKey = node.ecdhPublic;await assert.rejects(f.ask('seal-fill', { clicked: true, request_id: 'fr_01JAB2C3D4E5F6G7H8J9K0MNPQ', origin: 'http://example.test', machine_id: machine, public_key: node.ecdhPublic, item_id: '1', include_code: true, submit: false }), /valid sign-in request/);const other = await core.generatePersonKeys();const sealed = await f.ask('seal-fill', { clicked: true, request_id: 'fr_01JAB2C3D4E5F6G7H8J9K0MNPQ', origin: 'https://example.test', machine_id: machine, public_key: other.ecdhPublic, item_id: '1', include_code: true, submit: false });assert.equal(sealed.ok, true);assert.equal(sealed.thumbprint, 'network-thumbprint');-- await assert.rejects(core.open(Uint8Array.from(Buffer.from(sealed.envelope, 'base64')), { privateKey: other.ecdh.privateKey, subject: { type: 'node', id: machine } }));++ await assert.rejects(core.open(Uint8Array.from(Buffer.from(sealed.envelope, 'base64')), { privateKey: other.ecdh.privateKey, subject: { type: 'link', id: machine } }));assert.ok(!sealed.envelope.includes('fixture password'));-- const plain = await core.open(Uint8Array.from(Buffer.from(sealed.envelope, 'base64')), { privateKey: node.ecdh.privateKey, subject: { type: 'node', id: machine } });++ const plain = await core.open(Uint8Array.from(Buffer.from(sealed.envelope, 'base64')), { privateKey: node.ecdh.privateKey, subject: { type: 'link', id: machine } });const grant = JSON.parse(new TextDecoder().decode(plain));assert.deepEqual({ request_id: grant.request_id, origin: grant.origin, username: grant.username, password: grant.password, submit: grant.submit }, {request_id: 'fr_01JAB2C3D4E5F6G7H8J9K0MNPQ', origin: 'https://example.test', username: 'alice', password: 'fixture password', submit: false,@@@ -111,7 +111,7 @@ test('Companion seals a selected login to one machine and returns ciphertext onltest('seal-fill rechecks pending request and owned target before creating an envelope', async () => {const f = await fixture(); await f.ask('unlock', { clicked: true, secret: f.pass });await f.ask('save', { clicked: true, username: 'alice', password: 'fixture password' });-- const machine = 'nod_01JAB2C3D4E5F6G7H8J9K0MNPQ';++ const machine = 'lnk_01JAB2C3D4E5F6G7H8J9K0MNPQ';f.options.nodeKey = (await core.generatePersonKeys()).ecdhPublic;f.options.fillApi.requests = async () => ({ requests: [] });await assert.rejects(f.ask('seal-fill', { clicked: true, request_id: 'fr_01JAB2C3D4E5F6G7H8J9K0MNPQ', origin: 'https://example.test', machine_id: machine, item_id: '1' }), /no longer waiting/);
@@ -150,7 +150,7 @@ browser/
@@ -150,7 +150,7 @@ browser/
lib/api.js the only code that makes requests to the Coupons API
lib/api.js the only code that makes requests to the Coupons API
lib/network-api.js the Dock's state on the Network (GET/PUT /api/v1/me/dock), with the person's own token
lib/network-api.js the Dock's state on the Network (GET/PUT /api/v1/me/dock), with the person's own token
lib/auth.js the Dock's one-click OAuth sign-in: PKCE, state, refresh, sign-out
lib/auth.js the Dock's one-click OAuth sign-in: PKCE, state, refresh, sign-out
- lib/controller.js popup logic without the DOM (tested in Node)
+ lib/controller.js popup logic without the DOM (tested in Link)
lib/platform.js chrome.* / browser.*: tabs.query, tabs.create (hand-off pages only), storage.local, permissions
lib/platform.js chrome.* / browser.*: tabs.query, tabs.create (hand-off pages only), storage.local, permissions
lib/format.js wording ("Expiry unknown", "No restrictions stated", …)
lib/format.js wording ("Expiry unknown", "No restrictions stated", …)
icons/ generated by scripts/icons.js
icons/ generated by scripts/icons.js
@@ -162,7 +162,7 @@ scripts/
@@ -162,7 +162,7 @@ scripts/
icons.js draws the icons (no dependencies)
icons.js draws the icons (no dependencies)
```
```
-## Tests (Node 22, no browser, no dependencies)
+## Tests (Link 22, no browser, no dependencies)
```bash
```bash
npm test
npm test
@@ -250,7 +250,7 @@ shows "Allow access to openvibe.coupons".
@@ -250,7 +250,7 @@ shows "Allow access to openvibe.coupons".
- [OpenVibe.Live](https://github.com/OpenVibers/OpenVibe.Live) `/embed/agent/<ags_id>?vt=…`, the
- [OpenVibe.Live](https://github.com/OpenVibers/OpenVibe.Live) `/embed/agent/<ags_id>?vt=…`, the
agent view's frame. Its `frame-ancestors` list is first-party site origins; an extension origin is
agent view's frame. Its `frame-ancestors` list is first-party site origins; an extension origin is
not on it yet.
not on it yet.
-- No npm dependencies. Tests and scripts use Node's standard library.
+- No npm dependencies. Tests and scripts use Link's standard library.
---
---
@@ -93,7 +93,7 @@
@@ -93,7 +93,7 @@
}
}
if (action === 'seal-fill') {
if (action === 'seal-fill') {
if (!fillApi || m.clicked !== true || !FILL_REQUEST_ID.test(String(m.request_id || '')) || typeof m.origin !== 'string' || !EXACT_HTTPS_ORIGIN.test(m.origin) || origin(m.origin) !== m.origin
if (!fillApi || m.clicked !== true || !FILL_REQUEST_ID.test(String(m.request_id || '')) || typeof m.origin !== 'string' || !EXACT_HTTPS_ORIGIN.test(m.origin) || origin(m.origin) !== m.origin
- || !/^nod_[0-9A-HJKMNP-TV-Z]{26}$/.test(String(m.machine_id || '')) || typeof m.item_id !== 'string') throw new Error('Choose a valid sign-in request.');
+ || !/^lnk_[0-9A-HJKMNP-TV-Z]{26}$/.test(String(m.machine_id || '')) || typeof m.item_id !== 'string') throw new Error('Choose a valid sign-in request.');
const s = await unlocked();
const s = await unlocked();
const requests = await fillApi.requests();
const requests = await fillApi.requests();
const pending = (requests.requests || []).find((r) => r.id === m.request_id && r.origin === m.origin && r.machine_node_id === m.machine_id && r.expires_at > now());
const pending = (requests.requests || []).find((r) => r.id === m.request_id && r.origin === m.origin && r.machine_node_id === m.machine_id && r.expires_at > now());
@@ -107,7 +107,7 @@
@@ -107,7 +107,7 @@
if (m.include_code === true && login.totp) grant.code = await passwords.totp(login.totp);
if (m.include_code === true && login.totp) grant.code = await passwords.totp(login.totp);
const plain = new TextEncoder().encode(JSON.stringify(grant));
const plain = new TextEncoder().encode(JSON.stringify(grant));
try {
try {
- const envelope = await core.seal(plain, { lock: 'people', keys: [{ subject: { type: 'node', id: m.machine_id }, publicKey: target.public_key }] });
+ const envelope = await core.seal(plain, { lock: 'people', keys: [{ subject: { type: 'link', id: m.machine_id }, publicKey: target.public_key }] });
await still(s);
await still(s);
let binary = ''; for (const b of envelope.bytes) binary += String.fromCharCode(b);
let binary = ''; for (const b of envelope.bytes) binary += String.fromCharCode(b);
return { ok: true, envelope: btoa(binary), thumbprint: target.thumbprint };
return { ok: true, envelope: btoa(binary), thumbprint: target.thumbprint };
@@ -2,8 +2,8 @@
@@ -2,8 +2,8 @@
Scripts for streaming from and controlling hardware devices (Raspberry Pi, single-board computers, etc.) with OpenVibe.Live.
Scripts for streaming from and controlling hardware devices (Raspberry Pi, single-board computers, etc.) with OpenVibe.Live.
-> **Connecting a new robot? Use [OpenVibe.Bot](https://openvibe.bot) and the [OpenVibe Node](https://github.com/OpenVibers/OpenVibe.Node).**
-> The Node pairs with a one-time code (no stream key) and runs your driver as a plugin
+> **Connecting a new robot? Use [OpenVibe.Bot](https://openvibe.bot) and the [OpenVibe Link](https://github.com/OpenVibers/OpenVibe.Link).**
+> The Link pairs with a one-time code (no stream key) and runs your driver as a plugin
> ([write a driver](https://openvibe.bot/docs/drivers)); Bot gives the robot a panel built from its profile, roles, a
> ([write a driver](https://openvibe.bot/docs/drivers)); Bot gives the robot a panel built from its profile, roles, a
> queue, limits and a latched e-stop, and a Live channel can show that panel under its stream. The controller here
> queue, limits and a latched e-stop, and a Live channel can show that panel under its stream. The controller here
> uses Live's stream-key hardware socket, which keeps working for existing setups and is retired once they have moved
> uses Live's stream-key hardware socket, which keeps working for existing setups and is retired once they have moved
new file mode 100644
new file mode 100644
@@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
+'use strict';
+const assert = require('node:assert/strict');
+const { spawnSync } = require('node:child_process');
+const path = require('node:path');
+const root = path.resolve(__dirname, '..');
+const oldName = ['OpenVibe[ .-]No' + 'de', 'openvibe-' + 'node', 'no' + 'd_', 'node[.]openvibe[.]network', 'node:lnk_', 'openvibe[.](bot|my|network)/mach' + 'ines'].join('|');
+const result = spawnSync('git', ['grep', '-n', '-I', '-i', '-E', oldName, '--', '.', ':!migrations/**', ':!test/link-brand.test.js'], { cwd: root, encoding: 'utf8' });
+assert.ok(result.status === 0 || result.status === 1, result.stderr || 'git grep failed');
+assert.equal(result.status, 1, `Old Link brand or principal returned:\n${(result.stdout || '').split('\n').slice(0, 10).join('\n')}`);
+console.log(' ok old Link brand and principal IDs are absent');
@@ -24,7 +24,7 @@ async function fixture() {
@@ -24,7 +24,7 @@ async function fixture() {
content: async (id) => ciphertexts.get(id), save: async (bytes) => { ciphertexts.set(String(ciphertexts.size + 1), bytes); return { id: '1' }; },
content: async (id) => ciphertexts.get(id), save: async (bytes) => { ciphertexts.set(String(ciphertexts.size + 1), bytes); return { id: '1' }; },
};
};
const storage = { getSession: async (k) => memory.get(k), setSession: async (obj) => { writes.push(obj); for (const [k, v] of Object.entries(obj)) memory.set(k, v); }, removeSession: async (keys) => keys.forEach((k) => memory.delete(k)) };
const storage = { getSession: async (k) => memory.get(k), setSession: async (obj) => { writes.push(obj); for (const [k, v] of Object.entries(obj)) memory.set(k, v); }, removeSession: async (keys) => keys.forEach((k) => memory.delete(k)) };
- const options = { core, payload, passwords, api, fillApi: { requests: async () => ({ requests: [{ id: 'fr_01JAB2C3D4E5F6G7H8J9K0MNPQ', origin: 'https://example.test', machine_node_id: 'nod_01JAB2C3D4E5F6G7H8J9K0MNPQ', expires_at: 60_000 }] }), targets: async () => ({ machines: [{ id: 'nod_01JAB2C3D4E5F6G7H8J9K0MNPQ', public_key: options.nodeKey, thumbprint: 'network-thumbprint' }] }), grant: async () => ({ outcome: 'ok' }) }, storage, scheduleLock: async () => {}, cancelLock: async () => {}, tabs: { active: async () => active }, fill: async (...args) => { fills.push(args); return { ok: true }; }, clipboard: { write: async (v) => clipboard.push(v) }, account: async () => account, now: () => clock };
+ const options = { core, payload, passwords, api, fillApi: { requests: async () => ({ requests: [{ id: 'fr_01JAB2C3D4E5F6G7H8J9K0MNPQ', origin: 'https://example.test', machine_node_id: 'lnk_01JAB2C3D4E5F6G7H8J9K0MNPQ', expires_at: 60_000 }] }), targets: async () => ({ machines: [{ id: 'lnk_01JAB2C3D4E5F6G7H8J9K0MNPQ', public_key: options.nodeKey, thumbprint: 'network-thumbprint' }] }), grant: async () => ({ outcome: 'ok' }) }, storage, scheduleLock: async () => {}, cancelLock: async () => {}, tabs: { active: async () => active }, fill: async (...args) => { fills.push(args); return { ok: true }; }, clipboard: { write: async (v) => clipboard.push(v) }, account: async () => account, now: () => clock };
const vault = create(options);
const vault = create(options);
const ask = (type, values = {}, sender = { url: POPUP }) => vault.handle({ type: 'locker:' + type, tabId: 1, site: 'https://example.test', ...values }, sender, POPUP);
const ask = (type, values = {}, sender = { url: POPUP }) => vault.handle({ type: 'locker:' + type, tabId: 1, site: 'https://example.test', ...values }, sender, POPUP);
return { vault, ask, memory, fills, clipboard, api, options, writes, pass, recovery, ciphertexts,
return { vault, ask, memory, fills, clipboard, api, options, writes, pass, recovery, ciphertexts,
@@ -90,16 +90,16 @@ test('locking during a pending decrypt cancels the fill', async () => {
@@ -90,16 +90,16 @@ test('locking during a pending decrypt cancels the fill', async () => {
test('Companion seals a selected login to one machine and returns ciphertext only', async () => {
test('Companion seals a selected login to one machine and returns ciphertext only', async () => {
const f = await fixture(); await f.ask('unlock', { clicked: true, secret: f.pass });
const f = await fixture(); await f.ask('unlock', { clicked: true, secret: f.pass });
await f.ask('save', { clicked: true, username: 'alice', password: 'fixture password', totp: 'otpauth://totp/x?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ' });
await f.ask('save', { clicked: true, username: 'alice', password: 'fixture password', totp: 'otpauth://totp/x?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ' });
- const node = await core.generatePersonKeys(), machine = 'nod_01JAB2C3D4E5F6G7H8J9K0MNPQ';
+ const node = await core.generatePersonKeys(), machine = 'lnk_01JAB2C3D4E5F6G7H8J9K0MNPQ';
f.options.nodeKey = node.ecdhPublic;
f.options.nodeKey = node.ecdhPublic;
await assert.rejects(f.ask('seal-fill', { clicked: true, request_id: 'fr_01JAB2C3D4E5F6G7H8J9K0MNPQ', origin: 'http://example.test', machine_id: machine, public_key: node.ecdhPublic, item_id: '1', include_code: true, submit: false }), /valid sign-in request/);
await assert.rejects(f.ask('seal-fill', { clicked: true, request_id: 'fr_01JAB2C3D4E5F6G7H8J9K0MNPQ', origin: 'http://example.test', machine_id: machine, public_key: node.ecdhPublic, item_id: '1', include_code: true, submit: false }), /valid sign-in request/);
const other = await core.generatePersonKeys();
const other = await core.generatePersonKeys();
const sealed = await f.ask('seal-fill', { clicked: true, request_id: 'fr_01JAB2C3D4E5F6G7H8J9K0MNPQ', origin: 'https://example.test', machine_id: machine, public_key: other.ecdhPublic, item_id: '1', include_code: true, submit: false });
const sealed = await f.ask('seal-fill', { clicked: true, request_id: 'fr_01JAB2C3D4E5F6G7H8J9K0MNPQ', origin: 'https://example.test', machine_id: machine, public_key: other.ecdhPublic, item_id: '1', include_code: true, submit: false });
assert.equal(sealed.ok, true);
assert.equal(sealed.ok, true);
assert.equal(sealed.thumbprint, 'network-thumbprint');
assert.equal(sealed.thumbprint, 'network-thumbprint');
- await assert.rejects(core.open(Uint8Array.from(Buffer.from(sealed.envelope, 'base64')), { privateKey: other.ecdh.privateKey, subject: { type: 'node', id: machine } }));
+ await assert.rejects(core.open(Uint8Array.from(Buffer.from(sealed.envelope, 'base64')), { privateKey: other.ecdh.privateKey, subject: { type: 'link', id: machine } }));
assert.ok(!sealed.envelope.includes('fixture password'));
assert.ok(!sealed.envelope.includes('fixture password'));
- const plain = await core.open(Uint8Array.from(Buffer.from(sealed.envelope, 'base64')), { privateKey: node.ecdh.privateKey, subject: { type: 'node', id: machine } });
+ const plain = await core.open(Uint8Array.from(Buffer.from(sealed.envelope, 'base64')), { privateKey: node.ecdh.privateKey, subject: { type: 'link', id: machine } });
const grant = JSON.parse(new TextDecoder().decode(plain));
const grant = JSON.parse(new TextDecoder().decode(plain));
assert.deepEqual({ request_id: grant.request_id, origin: grant.origin, username: grant.username, password: grant.password, submit: grant.submit }, {
assert.deepEqual({ request_id: grant.request_id, origin: grant.origin, username: grant.username, password: grant.password, submit: grant.submit }, {
request_id: 'fr_01JAB2C3D4E5F6G7H8J9K0MNPQ', origin: 'https://example.test', username: 'alice', password: 'fixture password', submit: false,
request_id: 'fr_01JAB2C3D4E5F6G7H8J9K0MNPQ', origin: 'https://example.test', username: 'alice', password: 'fixture password', submit: false,
@@ -111,7 +111,7 @@ test('Companion seals a selected login to one machine and returns ciphertext onl
@@ -111,7 +111,7 @@ test('Companion seals a selected login to one machine and returns ciphertext onl
test('seal-fill rechecks pending request and owned target before creating an envelope', async () => {
test('seal-fill rechecks pending request and owned target before creating an envelope', async () => {
const f = await fixture(); await f.ask('unlock', { clicked: true, secret: f.pass });
const f = await fixture(); await f.ask('unlock', { clicked: true, secret: f.pass });
await f.ask('save', { clicked: true, username: 'alice', password: 'fixture password' });
await f.ask('save', { clicked: true, username: 'alice', password: 'fixture password' });
- const machine = 'nod_01JAB2C3D4E5F6G7H8J9K0MNPQ';
+ const machine = 'lnk_01JAB2C3D4E5F6G7H8J9K0MNPQ';
f.options.nodeKey = (await core.generatePersonKeys()).ecdhPublic;
f.options.nodeKey = (await core.generatePersonKeys()).ecdhPublic;
f.options.fillApi.requests = async () => ({ requests: [] });
f.options.fillApi.requests = async () => ({ requests: [] });
await assert.rejects(f.ask('seal-fill', { clicked: true, request_id: 'fr_01JAB2C3D4E5F6G7H8J9K0MNPQ', origin: 'https://example.test', machine_id: machine, item_id: '1' }), /no longer waiting/);
await assert.rejects(f.ask('seal-fill', { clicked: true, request_id: 'fr_01JAB2C3D4E5F6G7H8J9K0MNPQ', origin: 'https://example.test', machine_id: machine, item_id: '1' }), /no longer waiting/);