feat: configure browser receiver token

This commit is contained in:
mirivlad 2026-07-10 03:47:27 +08:00
parent 7b4db61a9c
commit aa57061769
5 changed files with 111 additions and 2 deletions

View File

@ -59,6 +59,14 @@ Headers:
- `Content-Type: application/json` - `Content-Type: application/json`
- `X-Verstak-Receiver-Token: <token>` required when the desktop receiver is in paired mode - `X-Verstak-Receiver-Token: <token>` required when the desktop receiver is in paired mode
## Pairing
1. In Verstak Desktop, open the Browser Inbox settings panel.
2. Copy the Receiver URL and Pairing Token.
3. Paste both values into the extension popup settings and select Save.
Rotating the token in Desktop invalidates the value stored by the extension.
Payload: Payload:
```json ```json

View File

@ -5,7 +5,7 @@
"description": "Verstak browser capture extension for Chromium and Firefox", "description": "Verstak browser capture extension for Chromium and Firefox",
"scripts": { "scripts": {
"build": "node scripts/build-extension.js", "build": "node scripts/build-extension.js",
"test": "node scripts/test-protocol.js", "test": "node scripts/test-protocol.js && node scripts/test-popup-settings.js",
"sign:firefox": "./scripts/sign-firefox-xpi.sh", "sign:firefox": "./scripts/sign-firefox-xpi.sh",
"release:firefox": "./scripts/release-firefox-xpi.sh" "release:firefox": "./scripts/release-firefox-xpi.sh"
}, },

View File

@ -0,0 +1,94 @@
#!/usr/bin/env node
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const vm = require('vm');
class Element {
constructor() {
this.value = '';
this.textContent = '';
this.className = '';
this.files = [];
this.listeners = {};
}
addEventListener(type, listener) {
this.listeners[type] = listener;
}
click() {
if (this.listeners.click) this.listeners.click();
}
}
const elements = {};
[
'status',
'receiver-state',
'receiver-url',
'receiver-input',
'receiver-token-input',
'file-input',
'pending-count',
'status-dot',
'capture-page',
'capture-file',
'retry',
'save-settings',
].forEach((id) => {
elements[id] = new Element();
});
let savedSettings = null;
const initialState = {
settings: {
receiverUrl: 'http://127.0.0.1:47731/api/browser-inbox/v1/captures',
receiverToken: 'persisted-token',
},
pendingCount: 0,
status: {},
};
const browser = {
runtime: {
sendMessage(message) {
if (message.action === 'getState') return Promise.resolve(initialState);
if (message.action === 'saveSettings') {
savedSettings = message.settings;
return Promise.resolve({ ...initialState, settings: message.settings });
}
return Promise.resolve(initialState);
},
},
};
const document = {
activeElement: null,
getElementById(id) {
return elements[id];
},
};
const popupPath = path.join(__dirname, '..', 'shared', 'popup', 'popup.js');
vm.runInNewContext(fs.readFileSync(popupPath, 'utf8'), { browser, console, document, Promise, btoa });
async function flush() {
for (let i = 0; i < 4; i += 1) await Promise.resolve();
}
(async () => {
await flush();
assert.strictEqual(elements['receiver-input'].value, initialState.settings.receiverUrl);
assert.strictEqual(elements['receiver-token-input'].value, initialState.settings.receiverToken);
elements['receiver-input'].value = 'http://127.0.0.1:47731/api/browser-inbox/v1/captures';
elements['receiver-token-input'].value = 'new-token';
elements['save-settings'].click();
await flush();
assert.ok(savedSettings);
assert.strictEqual(savedSettings.receiverUrl, 'http://127.0.0.1:47731/api/browser-inbox/v1/captures');
assert.strictEqual(savedSettings.receiverToken, 'new-token');
console.log('browser extension popup settings tests passed');
})().catch((error) => {
console.error(error);
process.exit(1);
});

View File

@ -44,6 +44,8 @@
<section class="settings"> <section class="settings">
<label for="receiver-input">Receiver URL</label> <label for="receiver-input">Receiver URL</label>
<input id="receiver-input" type="url" spellcheck="false"> <input id="receiver-input" type="url" spellcheck="false">
<label for="receiver-token-input">Pairing token</label>
<input id="receiver-token-input" type="password" spellcheck="false" autocomplete="off">
<button id="save-settings" class="secondary">Save</button> <button id="save-settings" class="secondary">Save</button>
</section> </section>

View File

@ -6,6 +6,7 @@
var receiverStateEl = document.getElementById('receiver-state'); var receiverStateEl = document.getElementById('receiver-state');
var receiverUrlEl = document.getElementById('receiver-url'); var receiverUrlEl = document.getElementById('receiver-url');
var receiverInputEl = document.getElementById('receiver-input'); var receiverInputEl = document.getElementById('receiver-input');
var receiverTokenInputEl = document.getElementById('receiver-token-input');
var fileInputEl = document.getElementById('file-input'); var fileInputEl = document.getElementById('file-input');
var pendingCountEl = document.getElementById('pending-count'); var pendingCountEl = document.getElementById('pending-count');
var statusDotEl = document.getElementById('status-dot'); var statusDotEl = document.getElementById('status-dot');
@ -50,6 +51,9 @@
if (document.activeElement !== receiverInputEl) { if (document.activeElement !== receiverInputEl) {
receiverInputEl.value = settings.receiverUrl || ''; receiverInputEl.value = settings.receiverUrl || '';
} }
if (document.activeElement !== receiverTokenInputEl) {
receiverTokenInputEl.value = settings.receiverToken || '';
}
if (reachable === true) { if (reachable === true) {
receiverStateEl.textContent = 'Online'; receiverStateEl.textContent = 'Online';
@ -120,6 +124,7 @@
document.getElementById('save-settings').addEventListener('click', function () { document.getElementById('save-settings').addEventListener('click', function () {
var receiverUrl = receiverInputEl.value.trim(); var receiverUrl = receiverInputEl.value.trim();
var receiverToken = receiverTokenInputEl.value.trim();
if (!/^https?:\/\//.test(receiverUrl)) { if (!/^https?:\/\//.test(receiverUrl)) {
setStatus('Receiver URL must start with http:// or https://'); setStatus('Receiver URL must start with http:// or https://');
return; return;
@ -127,7 +132,7 @@
request({ request({
type: 'verstak.capture', type: 'verstak.capture',
action: 'saveSettings', action: 'saveSettings',
settings: { receiverUrl: receiverUrl, receiverToken: '' } settings: { receiverUrl: receiverUrl, receiverToken: receiverToken }
}).then(function (state) { }).then(function (state) {
render(state); render(state);
setStatus('Saved'); setStatus('Saved');