Passively capture, archive, and hoard your web browsing history, including the contents of the pages you visit, for later offline viewing, mirroring, and/or indexing. Your own personal private Wayback Machine that can also archive HTTP POST requests and responses, as well as most other HTTP-level data.
/*
* Common parts between `state.js` and `saved.js`.
*
* Copyright (c) 2023-2024 Jan Malakhovski <oxij@oxij.org>
*
* This file is a part of `hoardy-web` project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
let thisSessionId;
let thisTabId;
function switchToDataTabId(dataTabId) {
return browser.tabs.update(dataTabId, { active: true }).catch(logError);
}
async function showStateOfDataTabId(dataTabId) {
let tabId = dataTabId;
let tab = await getActiveTab();
if (tab !== null)
tabId = tab.id;
showState(`?tab=${dataTabId}`, "top", tabId);
}
// caches of `switchToDataTabId` and `showStateOfDataTabId` bound to a
// given argument, for efficiency
let switchFuncMap = new Map();
let showStateFuncMap = new Map();
function appendLoggable(el, loggable) {
let tr = document.createElement("tr");
let sparts = [];
let color;
if (loggable.collected === true) {
color = "collected";
sparts.push("collected");
} else if (loggable.collected === false) {
color = "discarded";
sparts.push("discarded");
} else if (loggable.picked === true) {
color = "picked";
sparts.push("picked");
} else if (loggable.picked === false) {
color = "dropped";
sparts.push("dropped");
} else {
color = "in-flight";
sparts.push("in_flight");
}
tr.classList.add(color);
if (loggable.net_state !== undefined)
sparts.push(loggable.net_state);
if (loggable.redirectUrl !== undefined)
sparts.push("redirected");
if (loggable.in_limbo === true)
sparts.push("in_limbo");
else if (loggable.was_in_limbo === true)
sparts.push("was_in_limbo");
if (loggable.problematic === true)
sparts.push("problematic!");
else if (loggable.was_problematic === true)
sparts.push("was_problematic");
function mn(node, inn, data) {
let n = document.createElement(inn);
n.innerText = data;
node.appendChild(n);
return n;
}
function mtr(data) {
return mn(tr, "td", data);
}
function mbtn(node, data, func) {
let btn = document.createElement("input");
btn.type = "button";
btn.value = data;
btn.onclick = func;
node.appendChild(btn);
return btn;
}
let dataSessionId = loggable.sessionId;
let dataTabId = loggable.tabId;
let name = loggable.fromExtension ? "ext" : (dataTabId === -1 ? "bg" : `tab #${loggable.tabId}`);
let td = document.createElement("td");
let div = document.createElement("div");
if (dataSessionId === thisSessionId)
mbtn(div, name,
cacheSingleton(switchFuncMap, dataTabId, () => switchToDataTabId.bind(undefined, dataTabId)));
else
mn(div, "span", `${name} of ${dataSessionId.toString().substr(-3)}`);
if (tabId === null)
mbtn(div, "IS",
cacheSingleton(showStateFuncMap, dataTabId, () => showStateOfDataTabId.bind(undefined, dataTabId)));
td.appendChild(div);
tr.appendChild(td);
mtr(loggable.status);
mtr(sparts.join(" "));
mtr(dateToString(loggable.requestTimeStamp));
mtr(loggable.protocol);
mtr(loggable.method);
mtr(loggable.url
+ (loggable.redirectUrl !== undefined ? " -> " + loggable.redirectUrl : "")
).className = "long";
mtr(dateToString(loggable.responseTimeStamp));
mtr(loggable.reason).className = "long";
el.appendChild(tr);
if (loggable.errors.length > 0) {
let etr = document.createElement("tr");
etr.classList.add("errors");
let etd = document.createElement("td");
etd.setAttribute("colspan", 2);
etr.appendChild(etd);
etd = document.createElement("td");
etd.classList.add(color);
etd.setAttribute("colspan", 7);
etd.setAttribute("title", "errors");
etd.innerHTML = escapeHTML(loggable.errors.join("\n")).replaceAll("\n", "<br>");
etr.appendChild(etd);
el.appendChild(etr);
}
}
function appendToLog(el, log_data, predicate) {
for (let loggable of log_data) {
if (loggable === null) {
let tr = document.createElement("tr");
tr.innerHTML = `<td colspan=9><span class="flex"><span class="center">...</span></span></td>`;
el.appendChild(tr);
} else if ((tabId === null || loggable.tabId == tabId)
&& (predicate === undefined || predicate(loggable)))
appendLoggable(el, loggable);
}
}
function resetDataNode(id, log_data, predicate) {
let newtbody = document.createElement("tbody");
newtbody.id = id;
appendToLog(newtbody, log_data, predicate);
let tbody = document.getElementById(id);
tbody.parentElement.replaceChild(newtbody, tbody);
}
const headerHTML = `
<th><span data-help="Source of this reqres: "ext" for reqres produced by extensions, "bg" for reqres produced by background tasks, "tab #N" for reqres produced by the tab with id \`N\`, optionally followed by "of S" where \`S\` is the last three digits of \`.sessionId\`. For tabs of the current session the label is a button which switches currently active tab to the tab in question. If the current page is not narrowed to a tab, then a button labled "IS" follows. That button opens this page narrowed to the tab in question.">Src</span></th>
<th><span data-help="The \`.status\` this reqres will have in \`hoardy\`: "I" or "C" character (for "Incomplete" and "Complete" respectively) representing the value of \`.request.complete\` flag followed by either "N" (for "No response") or an HTTP status code (integer, e.g. "200"), followed by "I" or "C" representing the value of \`.response.complete\` flag.">WRR</span></th>
<th><span data-help="The current reqres \`state\` followed by \`the final networking state\`, followed by "redirected" when this reqres is a redirect, followed by "was_in_limbo" when this reqres was ever in limbo, followed by either "problematic!" when this reqres is marked as problematic or "was_problematic" when this reqres was marked as problematic before (see the Help page for more info).">pWA</span></th>
<th><span data-help="Timestamp of when the first byte of HTTP request headers was sent.">Request at</span></th>
<th><span data-help="Protocol/version.">P</span></th>
<th><span data-help="Protocol method.">M</span></th>
<th><span data-help="Request URL, followed by " -> " and a redirect URL when this reqres is a redirect.">URL</span></th>
<th><span data-help="Timestamp of when the first byte of HTTP response headers was received.">Response at</span></th>
<th><span data-help="HTTP protocol response reason, if any. Note that the HTTP response code is displayed as a part of the "WRR" field.">Reason</span></th>
`;
async function commonMain() {
thisSessionId = await browser.runtime.sendMessage(["getSessionId"]);
let thisTab = await getActiveTab();
if (thisTab !== null)
thisTabId = thisTab.id;
else
thisTabId = null;
// generate UI
let body = document.body;
makeUI(body);
function firstOrMake(el, tagName, addFunc) {
let res = el.getElementsByTagName(tagName)[0];
let created = false;
if (res === undefined) {
res = document.createElement(tagName);
created = true;
}
let tr = document.createElement("tr");
tr.innerHTML = headerHTML;
res.appendChild(tr);
if (created)
addFunc(res);
}
for (let el of document.getElementsByTagName("table")) {
firstOrMake(el, "thead", (thead) => {
el.insertBefore(thead, el.firstChild);
});
firstOrMake(el, "tfoot", (tfoot) => {
el.appendChild(tfoot);
});
}
addHelp(body);
}