Working history
This commit is contained in:
158
background.js
158
background.js
@@ -128,17 +128,6 @@ function maybeClearTabState(tabId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toHistoryRow(entry) {
|
|
||||||
return {
|
|
||||||
sampleId: entry.sampleId || "",
|
|
||||||
previewUrl: entry.previewUrl || "",
|
|
||||||
fullUrl: entry.fullUrl || "",
|
|
||||||
url: entry.fullUrl || entry.previewUrl || "",
|
|
||||||
source: entry.source || "",
|
|
||||||
capturedAt: entry.capturedAt || Date.now()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mergeEntry(tabState, inputEntry, source) {
|
function mergeEntry(tabState, inputEntry, source) {
|
||||||
const sampleId = inputEntry.sampleId || extractSampleIdFromUrl(inputEntry.previewUrl || inputEntry.fullUrl || "");
|
const sampleId = inputEntry.sampleId || extractSampleIdFromUrl(inputEntry.previewUrl || inputEntry.fullUrl || "");
|
||||||
if (!sampleId) {
|
if (!sampleId) {
|
||||||
@@ -241,6 +230,36 @@ async function appendHistoryRows(rows) {
|
|||||||
await browser.storage.local.set({ [HISTORY_KEY]: combined.slice(-MAX_HISTORY_ITEMS) });
|
await browser.storage.local.set({ [HISTORY_KEY]: combined.slice(-MAX_HISTORY_ITEMS) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function appendDownloadHistory(entry, result) {
|
||||||
|
const resolvedEntry = result && result.resolvedEntry ? result.resolvedEntry : entry;
|
||||||
|
const historyRow = {
|
||||||
|
sampleId: resolvedEntry.sampleId || "",
|
||||||
|
previewUrl: resolvedEntry.previewUrl || "",
|
||||||
|
fullUrl: resolvedEntry.fullUrl || "",
|
||||||
|
url: result.historyUrl || resolvedEntry.fullUrl || "",
|
||||||
|
source: result.mode === "full" ? "full_download" : "decoded_preview",
|
||||||
|
capturedAt: Date.now(),
|
||||||
|
mode: result.mode
|
||||||
|
};
|
||||||
|
await appendHistoryRows([historyRow]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeDecodedPlaceholderRow(entry, source) {
|
||||||
|
const sampleId = entry.sampleId || "";
|
||||||
|
if (!sampleId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
sampleId,
|
||||||
|
previewUrl: entry.previewUrl || "",
|
||||||
|
fullUrl: entry.fullUrl || "",
|
||||||
|
url: `decoded://${sampleId}`,
|
||||||
|
source: source || entry.source || "captured_preview",
|
||||||
|
capturedAt: entry.capturedAt || Date.now(),
|
||||||
|
mode: "pending_decoded"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function decodePass(startIdx, arr, encodeBlk, endIdx) {
|
function decodePass(startIdx, arr, encodeBlk, endIdx) {
|
||||||
let blockIdx = 0;
|
let blockIdx = 0;
|
||||||
for (let i = startIdx; i < endIdx; i += 1) {
|
for (let i = startIdx; i < endIdx; i += 1) {
|
||||||
@@ -292,7 +311,7 @@ async function downloadDirectUrl(url, entry) {
|
|||||||
filename,
|
filename,
|
||||||
conflictAction: "uniquify"
|
conflictAction: "uniquify"
|
||||||
});
|
});
|
||||||
return { ok: true, mode: "full", filename };
|
return { ok: true, mode: "full", filename, historyUrl: url };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadDecodedPreview(previewUrl, entry) {
|
async function downloadDecodedPreview(previewUrl, entry) {
|
||||||
@@ -315,7 +334,12 @@ async function downloadDecodedPreview(previewUrl, entry) {
|
|||||||
} finally {
|
} finally {
|
||||||
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 30000);
|
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 30000);
|
||||||
}
|
}
|
||||||
return { ok: true, mode: "decoded", filename };
|
return {
|
||||||
|
ok: true,
|
||||||
|
mode: "decoded",
|
||||||
|
filename,
|
||||||
|
historyUrl: `decoded://${entry.sampleId || Date.now()}`
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractAudioUrlsFromObject(input, output) {
|
function extractAudioUrlsFromObject(input, output) {
|
||||||
@@ -387,27 +411,76 @@ async function queryBackendForFullUrl(entry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findEntryInTabStates(sampleId) {
|
||||||
|
for (const state of TAB_CAPTURED.values()) {
|
||||||
|
if (!state || !state.entriesBySample) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const entry = state.entriesBySample.get(sampleId);
|
||||||
|
if (entry && (entry.previewUrl || entry.fullUrl)) {
|
||||||
|
return {
|
||||||
|
sampleId: entry.sampleId || sampleId,
|
||||||
|
previewUrl: normalizeUrl(entry.previewUrl || ""),
|
||||||
|
fullUrl: normalizeUrl(entry.fullUrl || "")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function hydrateEntryFromHistory(sampleId) {
|
||||||
|
const data = await browser.storage.local.get(HISTORY_KEY);
|
||||||
|
const rows = Array.isArray(data[HISTORY_KEY]) ? data[HISTORY_KEY] : [];
|
||||||
|
for (let i = rows.length - 1; i >= 0; i -= 1) {
|
||||||
|
const row = rows[i];
|
||||||
|
if (!row || row.sampleId !== sampleId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const previewUrl = normalizeUrl(row.previewUrl || "");
|
||||||
|
const fullUrl = normalizeUrl(row.fullUrl || "");
|
||||||
|
if (previewUrl || fullUrl) {
|
||||||
|
return { sampleId, previewUrl, fullUrl };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
async function resolveAndDownloadEntry(entryInput) {
|
async function resolveAndDownloadEntry(entryInput) {
|
||||||
const entry = entryInput || {};
|
const entry = entryInput || {};
|
||||||
const sampleId = entry.sampleId || extractSampleIdFromUrl(entry.previewUrl || entry.fullUrl || "");
|
const sampleId = entry.sampleId || extractSampleIdFromUrl(entry.previewUrl || entry.fullUrl || "");
|
||||||
const normalizedEntry = {
|
let normalizedEntry = {
|
||||||
sampleId,
|
sampleId,
|
||||||
previewUrl: normalizeUrl(entry.previewUrl || ""),
|
previewUrl: normalizeUrl(entry.previewUrl || ""),
|
||||||
fullUrl: normalizeUrl(entry.fullUrl || "")
|
fullUrl: normalizeUrl(entry.fullUrl || "")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (normalizedEntry.sampleId && !normalizedEntry.previewUrl && !normalizedEntry.fullUrl) {
|
||||||
|
const fromTabState = findEntryInTabStates(normalizedEntry.sampleId);
|
||||||
|
if (fromTabState) {
|
||||||
|
normalizedEntry = fromTabState;
|
||||||
|
} else {
|
||||||
|
const fromHistory = await hydrateEntryFromHistory(normalizedEntry.sampleId);
|
||||||
|
if (fromHistory) {
|
||||||
|
normalizedEntry = fromHistory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (normalizedEntry.fullUrl && isFullUrl(normalizedEntry.fullUrl)) {
|
if (normalizedEntry.fullUrl && isFullUrl(normalizedEntry.fullUrl)) {
|
||||||
return downloadDirectUrl(normalizedEntry.fullUrl, normalizedEntry);
|
const result = await downloadDirectUrl(normalizedEntry.fullUrl, normalizedEntry);
|
||||||
|
return { ...result, resolvedEntry: normalizedEntry };
|
||||||
}
|
}
|
||||||
|
|
||||||
const discoveredFull = await queryBackendForFullUrl(normalizedEntry);
|
const discoveredFull = await queryBackendForFullUrl(normalizedEntry);
|
||||||
if (discoveredFull) {
|
if (discoveredFull) {
|
||||||
normalizedEntry.fullUrl = discoveredFull;
|
normalizedEntry.fullUrl = discoveredFull;
|
||||||
return downloadDirectUrl(discoveredFull, normalizedEntry);
|
const result = await downloadDirectUrl(discoveredFull, normalizedEntry);
|
||||||
|
return { ...result, resolvedEntry: normalizedEntry };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedEntry.previewUrl) {
|
if (normalizedEntry.previewUrl) {
|
||||||
return downloadDecodedPreview(normalizedEntry.previewUrl, normalizedEntry);
|
const result = await downloadDecodedPreview(normalizedEntry.previewUrl, normalizedEntry);
|
||||||
|
return { ...result, resolvedEntry: normalizedEntry };
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error("No preview or full URL available for this sample.");
|
throw new Error("No preview or full URL available for this sample.");
|
||||||
@@ -457,10 +530,13 @@ browser.runtime.onMessage.addListener((message, sender) => {
|
|||||||
for (const context of contexts) {
|
for (const context of contexts) {
|
||||||
const merged = upsertPreviewContext(tabId, context, "graphql_preview");
|
const merged = upsertPreviewContext(tabId, context, "graphql_preview");
|
||||||
if (merged) {
|
if (merged) {
|
||||||
rows.push(toHistoryRow(merged));
|
const row = makeDecodedPlaceholderRow(merged, "graphql_preview");
|
||||||
|
if (row) {
|
||||||
|
rows.push(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return appendHistoryRows(rows).then(() => ({ ok: true, added: rows.length }));
|
}
|
||||||
|
return appendHistoryRows(rows).then(() => ({ ok: true, added: contexts.length }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.type === "CAPTURE_URLS") {
|
if (message.type === "CAPTURE_URLS") {
|
||||||
@@ -469,11 +545,14 @@ browser.runtime.onMessage.addListener((message, sender) => {
|
|||||||
const rows = [];
|
const rows = [];
|
||||||
for (const raw of urls) {
|
for (const raw of urls) {
|
||||||
const merged = upsertUrl(tabId, raw, message.source || "network_preview");
|
const merged = upsertUrl(tabId, raw, message.source || "network_preview");
|
||||||
if (merged) {
|
if (merged && merged.previewUrl) {
|
||||||
rows.push(toHistoryRow(merged));
|
const row = makeDecodedPlaceholderRow(merged, message.source || "network_preview");
|
||||||
|
if (row) {
|
||||||
|
rows.push(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return appendHistoryRows(rows).then(() => ({ ok: true, added: rows.length }));
|
}
|
||||||
|
return appendHistoryRows(rows).then(() => ({ ok: true, added: urls.length }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.type === "GET_TAB_URLS") {
|
if (message.type === "GET_TAB_URLS") {
|
||||||
@@ -491,14 +570,30 @@ browser.runtime.onMessage.addListener((message, sender) => {
|
|||||||
if (message.type === "GET_HISTORY") {
|
if (message.type === "GET_HISTORY") {
|
||||||
return browser.storage.local.get(HISTORY_KEY).then((data) => {
|
return browser.storage.local.get(HISTORY_KEY).then((data) => {
|
||||||
const rows = Array.isArray(data[HISTORY_KEY]) ? data[HISTORY_KEY] : [];
|
const rows = Array.isArray(data[HISTORY_KEY]) ? data[HISTORY_KEY] : [];
|
||||||
rows.sort((a, b) => (b.capturedAt || 0) - (a.capturedAt || 0));
|
const filtered = rows.filter((row) => {
|
||||||
return { ok: true, history: rows };
|
const url = typeof row.url === "string" ? row.url : "";
|
||||||
|
if (url.startsWith("decoded://")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (typeof row.fullUrl === "string" && isFullUrl(row.fullUrl)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return isFullUrl(url);
|
||||||
|
});
|
||||||
|
filtered.sort((a, b) => (b.capturedAt || 0) - (a.capturedAt || 0));
|
||||||
|
return { ok: true, history: filtered };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.type === "DOWNLOAD_ENTRY") {
|
if (message.type === "DOWNLOAD_ENTRY") {
|
||||||
return resolveAndDownloadEntry(message.entry)
|
return resolveAndDownloadEntry(message.entry)
|
||||||
.then((result) => result)
|
.then(async (result) => {
|
||||||
|
if (result && result.ok) {
|
||||||
|
const entry = message.entry || {};
|
||||||
|
await appendDownloadHistory(entry, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
})
|
||||||
.catch((error) => ({ ok: false, error: error.message || "Download failed." }));
|
.catch((error) => ({ ok: false, error: error.message || "Download failed." }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,7 +604,12 @@ browser.runtime.onMessage.addListener((message, sender) => {
|
|||||||
return Promise.resolve({ ok: false, error: "No captured sample is available yet." });
|
return Promise.resolve({ ok: false, error: "No captured sample is available yet." });
|
||||||
}
|
}
|
||||||
return resolveAndDownloadEntry(latest)
|
return resolveAndDownloadEntry(latest)
|
||||||
.then((result) => result)
|
.then(async (result) => {
|
||||||
|
if (result && result.ok) {
|
||||||
|
await appendDownloadHistory(latest, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
})
|
||||||
.catch((error) => ({ ok: false, error: error.message || "Download failed." }));
|
.catch((error) => ({ ok: false, error: error.message || "Download failed." }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,11 +629,7 @@ function handleWebRequestCapture(details) {
|
|||||||
|
|
||||||
const tabId = typeof details.tabId === "number" ? details.tabId : -1;
|
const tabId = typeof details.tabId === "number" ? details.tabId : -1;
|
||||||
const merged = upsertUrl(tabId, url, "webrequest");
|
const merged = upsertUrl(tabId, url, "webrequest");
|
||||||
if (merged) {
|
void merged;
|
||||||
appendHistoryRows([toHistoryRow(merged)]).catch(() => {
|
|
||||||
// Ignore history write failures.
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
browser.webRequest.onBeforeRequest.addListener(
|
browser.webRequest.onBeforeRequest.addListener(
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function buildItem(entry) {
|
|||||||
|
|
||||||
const url = document.createElement("p");
|
const url = document.createElement("p");
|
||||||
url.className = "history-url";
|
url.className = "history-url";
|
||||||
url.textContent = entry.fullUrl || entry.previewUrl || entry.url || "";
|
url.textContent = entry.fullUrl || entry.url || "";
|
||||||
|
|
||||||
const meta = document.createElement("p");
|
const meta = document.createElement("p");
|
||||||
meta.className = "history-meta";
|
meta.className = "history-meta";
|
||||||
|
|||||||
Reference in New Issue
Block a user