release: v1.6.0

This commit is contained in:
coso
2026-04-09 00:08:21 +08:00
parent a057d76e6a
commit db3bfae4ff
864 changed files with 307221 additions and 1689 deletions
+52 -5
View File
@@ -2,6 +2,8 @@ const HEARTBEAT_INTERVAL_MS = 30000;
const RECONNECT_MIN_DELAY_MS = 1000;
const RECONNECT_MAX_DELAY_MS = 30000;
const PAGE_CAPTURE_RETRY_LIMIT = 3;
const TAB_COMMAND_RETRY_LIMIT = 3;
const TAB_COMMAND_RETRY_DELAY_MS = 250;
const KEEPALIVE_ALARM_NAME = "limeBridgeKeepAlive";
const KEEPALIVE_PERIOD_MINUTES = 1;
const DEBUGGER_PROTOCOL_VERSION = "1.3";
@@ -84,6 +86,10 @@ function logWarn(message, payload) {
}
}
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function readSettings() {
return new Promise((resolve) => {
chrome.storage.local.get(Object.keys(DEFAULT_SETTINGS), (stored) => {
@@ -1167,12 +1173,41 @@ async function resolveTargetTabId() {
}
async function sendCommandToTab(tabId, payload) {
try {
return await chrome.tabs.sendMessage(tabId, payload);
} catch (_) {
await injectContentScript(tabId);
return await chrome.tabs.sendMessage(tabId, payload);
let lastError = null;
for (let attempt = 0; attempt <= TAB_COMMAND_RETRY_LIMIT; attempt += 1) {
try {
return await chrome.tabs.sendMessage(tabId, payload);
} catch (sendError) {
lastError = sendError;
try {
await injectContentScript(tabId);
return await chrome.tabs.sendMessage(tabId, payload);
} catch (injectError) {
lastError = injectError;
}
}
if (
attempt >= TAB_COMMAND_RETRY_LIMIT ||
!looksLikeTransientTabCommandError(lastError)
) {
throw lastError;
}
const delayMs = TAB_COMMAND_RETRY_DELAY_MS * (attempt + 1);
logWarn("标签页命令执行命中瞬态错误,准备重试", {
tabId,
attempt: attempt + 1,
delayMs,
payloadType: payload?.type || null,
command: payload?.data?.command || null,
error: lastError?.message || String(lastError),
});
await delay(delayMs);
}
throw lastError || new Error("标签页命令执行失败");
}
async function injectContentScript(tabId) {
@@ -1183,6 +1218,18 @@ async function injectContentScript(tabId) {
});
}
function looksLikeTransientTabCommandError(error) {
const normalized = String(error?.message || error || "").toLowerCase();
return (
normalized.includes("frame with id") ||
normalized.includes("frame was removed") ||
normalized.includes("receiving end does not exist") ||
normalized.includes("could not establish connection") ||
normalized.includes("message port closed") ||
normalized.includes("extension context invalidated")
);
}
async function triggerPageCapture(reason, retry = 0) {
if (!monitoringEnabled && reason !== "manual") {
return;
@@ -515,7 +515,8 @@
const ARTICLE_CONTENT_SELECTOR =
'[data-testid="longformRichTextComponent"] [data-contents="true"], [data-testid="longformRichTextComponent"]';
const IMAGE_SELECTOR = '[data-testid="tweetPhoto"] img';
const CODE_BLOCK_SELECTOR = '[data-testid="markdown-code-block"]';
const CODE_BLOCK_SELECTOR =
'[data-testid="markdown-code-block"], [data-testid="prism-code-block"], pre';
function normalizeText(value) {
return String(value || "")
@@ -581,6 +582,39 @@
}
}
function resolveSrcsetUrl(rawSrcset) {
const entries = String(rawSrcset || "")
.split(",")
.map((entry) => entry.trim())
.filter(Boolean);
if (entries.length === 0) {
return "";
}
const lastEntry = entries[entries.length - 1] || "";
const [url] = lastEntry.split(/\s+/, 1);
return String(url || "").trim();
}
function resolveImageSource(imageElement) {
const picture = imageElement.closest("picture");
const pictureSources = picture
? Array.from(picture.querySelectorAll("source"))
: [];
const candidate = [
imageElement.currentSrc,
imageElement.getAttribute("src"),
imageElement.getAttribute("data-src"),
imageElement.getAttribute("data-image-url"),
resolveSrcsetUrl(imageElement.getAttribute("srcset")),
...pictureSources.map((source) =>
resolveSrcsetUrl(source.getAttribute("srcset")),
),
].find((value) => normalizeText(value));
return String(candidate || "");
}
function resolveSuggestedImageName(rawUrl, fallbackIndex) {
try {
const url = new URL(rawUrl, location.href);
@@ -626,10 +660,7 @@
}
function registerImage(state, imageElement) {
const rawUrl =
imageElement.getAttribute("src") ||
imageElement.getAttribute("data-src") ||
"";
const rawUrl = resolveImageSource(imageElement);
const normalizedUrl = normalizeImageUrl(rawUrl);
if (!normalizedUrl) {
return "";