Scaffold browser capture extension

This commit is contained in:
2026-06-27 13:44:19 +08:00
parent fecf61a375
commit 44ca183f50
15 changed files with 599 additions and 1 deletions
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const root = path.resolve(__dirname, '..');
const dist = path.join(root, 'dist');
const shared = path.join(root, 'shared');
function rm(dir) {
fs.rmSync(dir, { recursive: true, force: true });
}
function mkdir(dir) {
fs.mkdirSync(dir, { recursive: true });
}
function copy(src, dest) {
mkdir(path.dirname(dest));
fs.copyFileSync(src, dest);
}
function concat(files, dest) {
mkdir(path.dirname(dest));
fs.writeFileSync(dest, files.map((file) => fs.readFileSync(file, 'utf8')).join('\n\n'), 'utf8');
}
function copyPopup(destRoot) {
const popupDir = path.join(shared, 'popup');
for (const name of ['popup.html', 'popup.css', 'popup.js']) {
copy(path.join(popupDir, name), path.join(destRoot, 'popup', name));
}
}
rm(dist);
const chromiumDist = path.join(dist, 'chromium');
mkdir(chromiumDist);
copy(path.join(root, 'chromium', 'manifest.json'), path.join(chromiumDist, 'manifest.json'));
concat([
path.join(shared, 'protocol.js'),
path.join(shared, 'api.js'),
path.join(shared, 'queue.js'),
path.join(shared, 'background.js'),
], path.join(chromiumDist, 'background.js'));
copyPopup(chromiumDist);
const firefoxDist = path.join(dist, 'firefox');
mkdir(firefoxDist);
copy(path.join(root, 'firefox', 'manifest.json'), path.join(firefoxDist, 'manifest.json'));
for (const name of ['protocol.js', 'api.js', 'queue.js', 'background.js']) {
copy(path.join(shared, name), path.join(firefoxDist, name));
}
copyPopup(firefoxDist);
console.log('built dist/chromium and dist/firefox');
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env node
const assert = require('assert');
const protocol = require('../shared/protocol');
require('../shared/api');
const queueApi = require('../shared/queue');
const page = protocol.buildCapture({
kind: 'page',
captureId: 'test-capture-id',
url: 'https://example.com/docs',
title: 'Example Docs'
});
assert.equal(page.schemaVersion, 1);
assert.equal(page.captureId, 'test-capture-id');
assert.equal(page.page.domain, 'example.com');
assert.equal(protocol.validateCapture(page), true);
const selection = protocol.buildCapture({
kind: 'selection',
url: 'https://example.com/docs',
title: 'Example Docs',
selectionText: ' selected text '
});
assert.equal(selection.selection.text, 'selected text');
assert.equal(protocol.validateCapture(selection), true);
assert.throws(() => protocol.validateCapture({ schemaVersion: 1, kind: 'link', captureId: 'x', capturedAt: 'now', page: { url: 'https://example.com' } }), /link.url/);
let request;
const fetchOk = (url, options) => {
request = { url, options };
return Promise.resolve({ status: 202, json: () => Promise.resolve({ status: 'accepted' }) });
};
globalThis.VerstakBrowser.sendCapture('http://127.0.0.1:47731/api/browser-inbox/v1/captures', 'token', page, fetchOk)
.then((result) => {
assert.equal(result.status, 'accepted');
assert.equal(request.url, 'http://127.0.0.1:47731/api/browser-inbox/v1/captures');
assert.equal(request.options.headers['X-Verstak-Receiver-Token'], 'token');
assert.equal(JSON.parse(request.options.body).captureId, 'test-capture-id');
})
.then(() => {
const queue = new queueApi.CaptureQueue(queueApi.createMemoryStorage());
return queue.enqueue(page)
.then(() => queue.enqueue(selection))
.then(() => queue.retry((payload) => {
if (payload.kind === 'page') return Promise.resolve();
return Promise.reject(new Error('offline'));
}))
.then((result) => {
assert.deepEqual(result, { sent: 1, pending: 1 });
return queue.list();
})
.then((items) => {
assert.equal(items.length, 1);
assert.equal(items[0].kind, 'selection');
});
})
.then(() => {
console.log('browser extension protocol tests passed');
})
.catch((err) => {
console.error(err);
process.exit(1);
});