From aa570617699cf778a1453fdfbab9df613455593c Mon Sep 17 00:00:00 2001 From: mirivlad Date: Fri, 10 Jul 2026 03:47:27 +0800 Subject: [PATCH] feat: configure browser receiver token --- README.md | 8 +++ package.json | 2 +- scripts/test-popup-settings.js | 94 ++++++++++++++++++++++++++++++++++ shared/popup/popup.html | 2 + shared/popup/popup.js | 7 ++- 5 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 scripts/test-popup-settings.js diff --git a/README.md b/README.md index 98a5be8..25a354b 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,14 @@ Headers: - `Content-Type: application/json` - `X-Verstak-Receiver-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 diff --git a/package.json b/package.json index 1b8ea2c..2fde3ab 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/scripts/test-popup-settings.js b/scripts/test-popup-settings.js new file mode 100644 index 0000000..f693ee9 --- /dev/null +++ b/scripts/test-popup-settings.js @@ -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); +}); diff --git a/shared/popup/popup.html b/shared/popup/popup.html index 6d8207a..5f8969b 100644 --- a/shared/popup/popup.html +++ b/shared/popup/popup.html @@ -44,6 +44,8 @@
+ +
diff --git a/shared/popup/popup.js b/shared/popup/popup.js index 490e618..9a86714 100644 --- a/shared/popup/popup.js +++ b/shared/popup/popup.js @@ -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');