Working history

This commit is contained in:
2026-08-05 21:47:52 -05:00
parent b8d7e732d5
commit cd1bb5460d
2 changed files with 128 additions and 32 deletions

View File

@@ -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(

View File

@@ -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";