From 30e0bb2dabd622dc9cdde10a31ecf81cf86d2ee1 Mon Sep 17 00:00:00 2001 From: Ben Muthalaly Date: Mon, 17 Mar 2025 14:40:33 -0500 Subject: [PATCH 01/10] Add local capturing of page and S3 configuration. - save screenshot and DOM to OPFS - add tab to configure S3 --- background.js | 151 +++++++++++++++++++++++++++++++++++++++++++++++++- manifest.json | 8 ++- options.html | 95 ++++++++++++++++++++++++++++++- options.js | 2 + popup.js | 40 ++++++++++++- utils.js | 107 +++++++++++++++++++++++++++++++++++ 6 files changed, 399 insertions(+), 4 deletions(-) diff --git a/background.js b/background.js index 0c06e9e..15d3d58 100755 --- a/background.js +++ b/background.js @@ -1,6 +1,82 @@ // background.js -import { addToArchiveBox } from "./utils.js"; +import { addToArchiveBox, captureScreenshot, captureDom } from "./utils.js"; + +import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; + +async function getS3Config() { + return new Promise((resolve) => { + chrome.storage.sync.get(['s3config'], (result) => { + resolve(result.s3config || {}); + }); + }); +} + +async function uploadToS3(fileName, data, contentType = "image/png") { + try { + const s3Config = await getS3Config(); + + if (!s3Config.endpoint || !s3Config.bucket || !s3Config.accessKeyId || !s3Config.secretAccessKey) { + throw new Error("S3 configuration is incomplete"); + } + + const client = new S3Client({ + endpoint: s3Config.endpoint, + credentials: { + accessKeyId: s3Config.accessKeyId, + secretAccessKey: s3Config.secretAccessKey, + }, + region: s3Config.region, + forcePathStyle: true, + requestChecksumCalculation: "WHEN_REQUIRED", + }); + + // Use custom headers for our requests + client.middlewareStack.add( + (next) => + async (args) => { + const request = args.request; + + const headers = request.headers; + delete headers["x-amz-checksum-crc32"]; + delete headers["x-amz-checksum-crc32c"]; + delete headers["x-amz-checksum-sha1"]; + delete headers["x-amz-checksum-sha256"]; + request.headers = headers; + + Object.entries(request.headers).forEach( + ([key, value]) => { + if (!request.headers) { + request.headers = {}; + } + (request.headers)[key] = value; + } + ); + + return next(args); + }, + { step: "build", name: "customHeaders" } + ); + + // Send to S3 + const command = new PutObjectCommand({ + Bucket: s3Config.bucket, + Key: fileName, + Body: data, + ContentType: contentType, + }); + + try { + await client.send(command); + return `${s3Config.endpoint}/${s3Config.bucket}/${fileName}`; + } catch (err) { + console.error("Upload failed:", err); + throw err; + } + } catch (err) { + console.log("upload failed: ", err) + } +} chrome.runtime.onMessage.addListener(async (message) => { const options_url = chrome.runtime.getURL('options.html') + `?search=${message.id}`; @@ -39,9 +115,81 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { return true; }); +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type === 'capture_screenshot') { + (async ()=> { + try { + const {fileName, path} = await captureScreenshot(); + sendResponse({ok: true, fileName, path}); + } catch (error) { + console.log("failed to capture screenshot: ", error); + sendResponse({ok: false}); + } + })(); + } + return true; +}); + +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type === 'capture_dom') { + (async ()=> { + try { + const {fileName, path} = await captureDom() + sendResponse({ok: true, fileName, path}); + } catch (error) { + console.log("failed to capture dom: ", error); + sendResponse({ok: false}); + } + })(); + } + return true; +}); + +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type === 'testS3Credentials') { + (async () => { + // upload test file + try { + const fileName = `.connection_test_${Date.now()}.txt`; + const randomContent = Math.random().toString(36).substring(2, 15); + const testData = new TextEncoder().encode(randomContent); + + const s3Url = await uploadToS3(fileName, testData, 'text/plain'); + + // verify test file matches + try { + const response = await fetch(s3Url); + console.log("verification response", response); + if (response.ok) { + const responseText = await response.text(); + const testPassed = responseText === randomContent; + sendResponse(testPassed ? 'success' : 'failure'); + } else { + sendResponse('failure'); + } + } catch (fetchError) { + console.error('Error verifying S3 upload:', fetchError); + // Even if verification fails, the upload might have succeeded + sendResponse('success'); + } + } catch (error) { + console.error('S3 credential test failed:', error); + sendResponse('failure'); + } + })(); + return true; + } +}); chrome.contextMenus.onClicked.addListener(onClickContextMenuSave); +chrome.runtime.onInstalled.addListener(function () { + chrome.contextMenus.create({ + id: 'save_to_archivebox_ctxmenu', + title: 'Save to ArchiveBox', + }); +}); + // A generic onclick callback function. async function onClickContextMenuSave(item, tab) { const entry = { @@ -64,6 +212,7 @@ async function onClickContextMenuSave(item, tab) { files: ['popup.js'] }); } + chrome.runtime.onInstalled.addListener(function () { chrome.contextMenus.create({ id: 'save_to_archivebox_ctxmenu', diff --git a/manifest.json b/manifest.json index 0451feb..203706f 100755 --- a/manifest.json +++ b/manifest.json @@ -18,6 +18,12 @@ "optional_host_permissions": [ "*://*\/*" ], + "content_scripts": [ + { + "matches": [""], + "js": ["content.js"] + } + ], "icons": { "16": "16.png", "32": "32.png", @@ -35,7 +41,7 @@ }, "options_page": "options.html", "background": { - "service_worker": "background.js", + "service_worker": "dist/background.js", "type": "module" }, "web_accessible_resources": [{ diff --git a/options.html b/options.html index 73f27e6..96816e5 100755 --- a/options.html +++ b/options.html @@ -124,6 +124,9 @@ +
@@ -469,6 +472,95 @@
Import Browser Cookies to Archiving Profile
+ + +
+
+
+
+
+ + +
+ The endpoint URL for your S3 service. For AWS S3, use the regional endpoint (e.g., https://s3.us-west-2.amazonaws.com). + For other S3-compatible services, use their specific endpoint. +
+
+ +
+ + +
+ The region where your S3 bucket is located (e.g., us-east-1, eu-west-1). +
+
+ +
+ + +
+ The name of the S3 bucket where screenshots and archives will be stored. +
+
+ +
+ + +
+ Your S3 access key ID. It's recommended to use a key with limited permissions. +
+
+ +
+ + +
+ Your S3 secret access key. This will be stored securely in your browser. +
+
+ +
+ + +
+
+ + +
+
+ +
+ +

Captured Screenshots

+
+ + + + + + + + + + + + +
ThumbnailDate/TimeTab URLS3 URL
+
+
+
+