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`
- `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:
```json

View File

@ -5,7 +5,7 @@
"description": "Verstak browser capture extension for Chromium and Firefox",
"scripts": {
"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",
"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">
<label for="receiver-input">Receiver URL</label>
<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>
</section>

View File

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