diff --git a/src/argo-archive-list.ts b/src/argo-archive-list.ts
index 530c7da..9f66c25 100644
--- a/src/argo-archive-list.ts
+++ b/src/argo-archive-list.ts
@@ -90,9 +90,9 @@ export class ArgoArchiveList extends LitElement {
width: 20px !important;
height: 20px !important;
flex: 0 0 auto;
- object-fit: cover;
+ object-fit: contain;
border-radius: 4px;
- filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.6));
+ filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.4));
}
summary {
diff --git a/src/argo-shared-archive-list.ts b/src/argo-shared-archive-list.ts
index d205cae..7b6cf84 100644
--- a/src/argo-shared-archive-list.ts
+++ b/src/argo-shared-archive-list.ts
@@ -93,9 +93,9 @@ export class ArgoSharedArchiveList extends LitElement {
width: 20px !important;
height: 20px !important;
flex: 0 0 auto;
- object-fit: cover;
+ object-fit: contain;
border-radius: 4px;
- filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.6));
+ filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.4));
}
.title-url {
diff --git a/src/ext/bg.ts b/src/ext/bg.ts
index 0d636b5..8545d5d 100644
--- a/src/ext/bg.ts
+++ b/src/ext/bg.ts
@@ -9,6 +9,7 @@ import {
setLocalOption,
getSharedArchives,
} from "../localstorage";
+import { isValidUrl } from "../utils";
// ===========================================================================
self.recorders = {};
self.newRecId = null;
@@ -22,6 +23,7 @@ let newRecCollId = null;
let defaultCollId = null;
let autorun = false;
let isRecordingEnabled = false;
+let skipDomains = [] as string[];
const openWinMap = new Map();
@@ -32,6 +34,11 @@ const disabledCSPTabs = new Set();
// @ts-expect-error - TS7034 - Variable 'sidepanelPort' implicitly has type 'any' in some locations where its type cannot be determined.
let sidepanelPort = null;
+(async function loadSkipDomains() {
+ // @ts-expect-error
+ skipDomains = (await getLocalOption("skipDomains")) || [];
+})();
+
// ===========================================================================
function main() {
@@ -136,7 +143,7 @@ function sidepanelHandler(port) {
//@ts-expect-error tabs has any type
async (tabs) => {
for (const tab of tabs) {
- if (!isValidUrl(tab.url)) continue;
+ if (!isValidUrl(tab.url, skipDomains)) continue;
await startRecorder(
tab.id,
@@ -217,6 +224,13 @@ chrome.runtime.onMessage.addListener(
(message /*sender, sendResponse*/) => {
console.log("onMessage", message);
switch (message.msg) {
+ case "optionsChanged":
+ for (const rec of Object.values(self.recorders)) {
+ rec.initOpts();
+ rec.doUpdateStatus();
+ }
+ break;
+
case "startNew":
(async () => {
newRecUrl = message.url;
@@ -256,7 +270,7 @@ chrome.tabs.onActivated.addListener(async ({ tabId }) => {
chrome.tabs.get(tabId, resolve),
);
- if (!isValidUrl(tab.url)) return;
+ if (!isValidUrl(tab.url, skipDomains)) return;
if (!self.recorders[tabId]) {
await startRecorder(
tabId,
@@ -296,7 +310,7 @@ chrome.tabs.onCreated.addListener((tab) => {
newRecCollId = null;
} else if (
tab.openerTabId &&
- (!tab.pendingUrl || isValidUrl(tab.pendingUrl)) &&
+ (!tab.pendingUrl || isValidUrl(tab.pendingUrl, skipDomains)) &&
// @ts-expect-error - TS2339 - Property 'running' does not exist on type 'BrowserRecorder'.
self.recorders[tab.openerTabId]?.running
) {
@@ -311,7 +325,7 @@ chrome.tabs.onCreated.addListener((tab) => {
}
if (start) {
- if (openUrl && !isValidUrl(openUrl)) {
+ if (openUrl && !isValidUrl(openUrl, skipDomains)) {
return;
}
startRecorder(
@@ -337,9 +351,20 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
openWinMap.delete(changeInfo.url);
}
+ if (changeInfo.url && !isValidUrl(changeInfo.url, skipDomains)) {
+ stopRecorder(tabId);
+ delete self.recorders[tabId];
+ // let the side-panel know the ’canRecord’/UI state changed
+ // @ts-expect-error
+ if (sidepanelPort) {
+ sidepanelPort.postMessage({ type: "update" });
+ }
+ return;
+ }
+
// @ts-expect-error - TS2339 - Property 'waitForTabUpdate' does not exist on type 'BrowserRecorder'.
if (recorder.waitForTabUpdate) {
- if (isValidUrl(changeInfo.url)) {
+ if (isValidUrl(changeInfo.url, skipDomains)) {
recorder.attach();
} else {
// @ts-expect-error - TS2339 - Property 'waitForTabUpdate' does not exist on type 'BrowserRecorder'.
@@ -349,9 +374,13 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
}
}
} else if (changeInfo.url) {
+ // @ts-expect-error - TS7034 - Variable 'err' implicitly has type 'any' in some locations where its type cannot be determined.
+ if (sidepanelPort) {
+ sidepanelPort.postMessage({ type: "update" });
+ }
if (
isRecordingEnabled &&
- isValidUrl(changeInfo.url) &&
+ isValidUrl(changeInfo.url, skipDomains) &&
!self.recorders[tabId]
) {
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 3.
@@ -361,7 +390,7 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
if (openWinMap.has(changeInfo.url)) {
const collId = openWinMap.get(changeInfo.url);
openWinMap.delete(changeInfo.url);
- if (!tabId || !isValidUrl(changeInfo.url)) return;
+ if (!tabId || !isValidUrl(changeInfo.url, skipDomains)) return;
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 3.
startRecorder(tabId, { collId, autorun }, changeInfo.url);
@@ -386,7 +415,7 @@ chrome.contextMenus.onClicked.addListener((info, tab) => {
case "toggle-rec":
if (!isRecording(tab.id)) {
- if (isValidUrl(tab.url)) {
+ if (isValidUrl(tab.url, skipDomains)) {
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
startRecorder(tab.id);
}
@@ -457,17 +486,6 @@ function isRecording(tabId) {
return self.recorders[tabId]?.running;
}
-// ===========================================================================
-// @ts-expect-error - TS7006 - Parameter 'url' implicitly has an 'any' type.
-function isValidUrl(url) {
- return (
- url &&
- (url === "about:blank" ||
- url.startsWith("https:") ||
- url.startsWith("http:"))
- );
-}
-
// ===========================================================================
// @ts-expect-error - TS7006 - Parameter 'tabId' implicitly has an 'any' type.
async function disableCSPForTab(tabId) {
diff --git a/src/ext/browser-recorder.ts b/src/ext/browser-recorder.ts
index abaab74..a6ab854 100644
--- a/src/ext/browser-recorder.ts
+++ b/src/ext/browser-recorder.ts
@@ -2,6 +2,8 @@
import { BEHAVIOR_RUNNING } from "../consts";
import { Recorder } from "../recorder";
+import { isValidUrl } from "../utils";
+import { getLocalOption } from "../localstorage";
// ===========================================================================
const DEBUG = false;
@@ -333,18 +335,24 @@ class BrowserRecorder extends Recorder {
return writtenSize;
}
-
- // @ts-expect-error - TS7006 - Parameter 'pageInfo' implicitly has an 'any' type.
- _doAddPage(pageInfo) {
+ async _doAddPage(pageInfo: { url?: string; [key: string]: any }) {
if (!pageInfo.url) {
console.warn("Empty Page, Skipping");
return;
}
+
+ // @ts-expect-error
+ const skipDomains: string[] = (await getLocalOption("skipDomains")) || [];
+
+ if (!isValidUrl(pageInfo.url, skipDomains)) {
+ console.log("Skipping by policy:", pageInfo.url);
+ return;
+ }
+
// @ts-expect-error - TS2339 - Property 'db' does not exist on type 'BrowserRecorder'.
if (this.db) {
// @ts-expect-error - TS2339 - Property 'db' does not exist on type 'BrowserRecorder'.
const result = this.db.addPage(pageInfo);
-
chrome.runtime.sendMessage({ type: "pageAdded" });
return result;
}
diff --git a/src/recorder.ts b/src/recorder.ts
index f6c0fb0..1955f3d 100644
--- a/src/recorder.ts
+++ b/src/recorder.ts
@@ -1,5 +1,5 @@
import { RequestResponseInfo } from "./requestresponseinfo";
-
+import { isValidUrl, isUrlInSkipList } from "./utils";
import {
getCustomRewriter,
rewriteDASH,
@@ -60,6 +60,7 @@ class Recorder {
archiveFlash = false;
archiveScreenshots = false;
archivePDF = false;
+ skipDomains: string[] = [];
_fetchQueue: FetchEntry[] = [];
@@ -163,6 +164,8 @@ class Recorder {
this.archiveScreenshots =
(await getLocalOption("archiveScreenshots")) === "1";
this.archivePDF = (await getLocalOption("archivePDF")) === "1";
+ // @ts-expect-error
+ this.skipDomains = (await getLocalOption("skipDomains")) || [];
}
// @ts-expect-error - TS7006 - Parameter 'autorun' implicitly has an 'any' type.
@@ -1111,6 +1114,9 @@ class Recorder {
// @ts-expect-error - TS7006 - Parameter 'currPage' implicitly has an 'any' type. | TS7006 - Parameter 'domSnapshot' implicitly has an 'any' type. | TS7006 - Parameter 'finished' implicitly has an 'any' type.
commitPage(currPage, domSnapshot, finished) {
+ if (isUrlInSkipList(currPage?.url, this.skipDomains)) {
+ return;
+ }
if (!currPage?.url || !currPage.ts || currPage.url === "about:blank") {
return;
}
@@ -1135,6 +1141,10 @@ class Recorder {
// @ts-expect-error - TS7006 - Parameter 'data' implicitly has an 'any' type. | TS7006 - Parameter 'pageInfo' implicitly has an 'any' type.
async commitResource(data, pageInfo) {
+ if (isUrlInSkipList(data.url, this.skipDomains)) {
+ return;
+ }
+
const payloadSize = data.payload.length;
// @ts-expect-error - TS2339 - Property 'pageInfo' does not exist on type 'Recorder'.
pageInfo = pageInfo || this.pageInfo;
@@ -1552,11 +1562,6 @@ class Recorder {
return !status || status === 204 || (status >= 300 && status < 400);
}
- // @ts-expect-error - TS7006 - Parameter 'url' implicitly has an 'any' type.
- isValidUrl(url) {
- return url && (url.startsWith("https:") || url.startsWith("http:"));
- }
-
// @ts-expect-error - TS7006 - Parameter 'params' implicitly has an 'any' type. | TS7006 - Parameter 'sessions' implicitly has an 'any' type.
async handleLoadingFinished(params, sessions) {
const reqresp = this.removeReqResp(params.requestId);
@@ -1566,7 +1571,7 @@ class Recorder {
return;
}
- if (!this.isValidUrl(reqresp.url)) {
+ if (!isValidUrl(reqresp.url, this.skipDomains)) {
return;
}
@@ -1830,7 +1835,7 @@ class Recorder {
// @ts-expect-error - TS7006 - Parameter 'request' implicitly has an 'any' type. | TS7006 - Parameter 'sessions' implicitly has an 'any' type.
doAsyncFetch(request: FetchEntry, sessions) {
- if (!request || !this.isValidUrl(request.url)) {
+ if (!request || !isValidUrl(request.url, this.skipDomains)) {
return;
}
diff --git a/src/settings-page.ts b/src/settings-page.ts
new file mode 100644
index 0000000..c68f311
--- /dev/null
+++ b/src/settings-page.ts
@@ -0,0 +1,223 @@
+// settings-page.ts
+import { LitElement, html, css } from "lit";
+import { customElement } from "lit/decorators.js";
+import "@material/web/textfield/outlined-text-field.js";
+import "@material/web/switch/switch.js";
+import "@material/web/icon/icon.js";
+import "@material/web/iconbutton/icon-button.js";
+import { styles as typescaleStyles } from "@material/web/typography/md-typescale-styles.js";
+import { getLocalOption, setLocalOption } from "./localstorage";
+import { state } from "lit/decorators.js";
+
+@customElement("settings-page")
+export class SettingsPage extends LitElement {
+ // @ts-expect-error
+ static styles: CSSResultGroup = [
+ // @ts-expect-error
+ typescaleStyles as unknown as CSSResultGroup,
+ css`
+ :host {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ }
+ .header {
+ margin-bottom: 24px;
+
+ & nav {
+ display: flex;
+ align-items: center;
+ margin: 0 16px;
+ }
+ }
+ .content {
+ box-sizing: border-box; /* include padding in width calculations */
+ padding: 16px; /* existing top/bottom padding */
+ padding-inline-start: 16px; /* horizontal padding */
+ padding-inline-end: 16px; /* horizontal padding */
+ overflow-y: auto;
+ flex: 1;
+ }
+ .section {
+ margin: 24px 16px 0; /* 24px top, 16px left/right, 0 bottom */
+ }
+ .section-label {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+ .section-desc {
+ margin: 4px 0 8px;
+ color: rgba(0, 0, 0, 0.6);
+ }
+
+ md-outlined-text-field {
+ display: block; /* make it a block so margins apply symmetrically */
+ box-sizing: border-box; /* include padding/border in its width calculation */
+ /* override the inline width:100% you currently have on the element */
+ width: auto !important;
+ /* fill the container minus your 2×16px margins */
+ max-width: calc(100% - 32px);
+ margin: 0 16px;
+ }
+ `,
+ ];
+
+ @state()
+ private archiveCookies = false;
+ @state()
+ private archiveStorage = false;
+ @state()
+ private archiveScreenshots = false;
+ @state()
+ private skipDomains = "";
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.loadSettings();
+ }
+
+ private async loadSettings() {
+ try {
+ const cookies = await getLocalOption("archiveCookies");
+ this.archiveCookies = cookies === "1";
+ const storage = await getLocalOption("archiveStorage");
+ this.archiveStorage = storage === "1";
+ const screenshots = await getLocalOption("archiveScreenshots");
+ this.archiveScreenshots = screenshots === "1";
+ const domains = await getLocalOption("skipDomains");
+ this.skipDomains = Array.isArray(domains)
+ ? domains.join("\n")
+ : typeof domains === "string"
+ ? domains
+ : "";
+ } catch (e) {
+ console.error("Failed to load settings", e);
+ }
+ }
+
+ private async _onSkipDomainsChange(e: Event) {
+ const textarea = e.currentTarget as HTMLInputElement;
+ const value = textarea.value;
+ this.skipDomains = value; // update lit-state so UI stays in sync
+
+ // split into an array, trimming out blank lines
+ const list = value
+ .split("\n")
+ .map((d) => d.trim())
+ .filter(Boolean);
+
+ // persist and notify recorder
+ await setLocalOption("skipDomains", list);
+ chrome.runtime.sendMessage({ msg: "optionsChanged" });
+ }
+
+ private async _onArchiveCookiesChange(e: Event) {
+ // @ts-expect-error
+ const checked = (e.currentTarget as HTMLInputElement).selected;
+
+ await setLocalOption("archiveCookies", checked ? "1" : "0");
+ chrome.runtime.sendMessage({ msg: "optionsChanged" });
+ }
+
+ private async _onArchiveLocalstorageChange(e: Event) {
+ // @ts-expect-error
+ const checked = (e.currentTarget as HTMLInputElement).selected;
+ await setLocalOption("archiveStorage", checked ? "1" : "0");
+ chrome.runtime.sendMessage({ msg: "optionsChanged" });
+ }
+
+ private async _onArchiveScreenshotsChange(e: Event) {
+ // @ts-expect-error
+ const checked = (e.currentTarget as HTMLInputElement).selected;
+ await setLocalOption("archiveScreenshots", checked ? "1" : "0");
+ chrome.runtime.sendMessage({ msg: "optionsChanged" });
+ }
+
+ private _onBack() {
+ this.dispatchEvent(
+ new CustomEvent("back", { bubbles: true, composed: true }),
+ );
+ }
+
+ render() {
+ return html`
+
+ Save a thumbnail screenshot of every page on load. Screenshot will be saved as soon as page is done loading. +
++ Archiving cookies may expose private information that is normally + only shared with the site. When enabled, users should exercise + caution about sharing archived pages. +
+
+ Archiving local storage will archive information that is generally
+ always private.
+
+ Sharing content created with this setting enabled may compromise your
+ login credentials.
+