From 8cdab3c14c30a7bfa0ec6c2e7fff8c5d4da7555f Mon Sep 17 00:00:00 2001
From: MB-X Bilibili Pipeline <mbx-bili-pipeline@localhost>
Date: Sat, 05 Sep 2026 22:24:01 +0800
Subject: [PATCH] chore(project-info): archive Bilibili dynamic 13f0e65539cd
---
dev/project-dev/bili_authenticated_extension/background.js | 1918 +++++++++++++++++++++++++++++++++++++++++++++++++----------
1 files changed, 1,583 insertions(+), 335 deletions(-)
diff --git a/dev/project-dev/bili_authenticated_extension/background.js b/dev/project-dev/bili_authenticated_extension/background.js
index 049ae99..bef33c4 100644
--- a/dev/project-dev/bili_authenticated_extension/background.js
+++ b/dev/project-dev/bili_authenticated_extension/background.js
@@ -1,388 +1,1636 @@
-const TARGET = "BV1HA3o6oEJJ";
-const CANONICAL_URL = "https://www.bilibili.com/video/BV1HA3o6oEJJ";
-const TARGET_PATH = "/video/BV1HA3o6oEJJ";
-const HOST = "com.project_info.bili_auth_ingress";
-const SCHEMA = 2;
-const EXTENSION_BUILD = "project-info-bili-auth-ingress/1.0.0+20260805.v002";
-const HOST_BUILD = "project-info-bili-auth-native-host/1.0.0+20260805.v002";
-const SIDEPANEL_URL = chrome.runtime.getURL("sidepanel.html");
-const EXPECTED_DURATION_MS = 3133950;
-const DURATION_TOLERANCE_MS = 3134;
+const HOST_NAME = "com.project_info.bili_auth_ingress";
+const SCHEMA = 3;
+const EXTENSION_BUILD = "project-info-bili-auth-ingress/1.2.25+20260829.generic.v027";
+const OWNED_TAB_STORAGE_KEY = "bili_auth_job_owned_tab_v1";
+const OWNED_TAB_SCHEMA = 1;
+const BVID_RE = /^BV1[1-9A-HJ-NP-Za-km-z]{9}$/;
+const CREATOR_RE = /^[1-9][0-9]{0,19}$/;
+const HEX32_RE = /^[0-9a-f]{32}$/;
+const HEX64_RE = /^[0-9a-f]{64}$/;
+const UPPER_HEX64_RE = /^[0-9A-F]{64}$/;
+const MESSAGE_ID_RE = /^msg_[0-9]{17}_[0-9a-f]{8}$/;
+const HANDOFF_ID_RE = /^HANDOFF-[A-Z0-9-]{8,240}$/;
+const AUDIT_ID_RE = /^DEV-AUDIT-[A-Z0-9-]{8,240}$/;
+const ERROR_CODE_RE = /^E_[A-Z0-9_]{1,48}$/;
+const SAFE_PHASES = new Set([
+ "IDLE", "READY", "CHECKING", "DOWNLOADING", "MERGING", "VALIDATING",
+ "PUBLISHING", "MEDIA_COMPLETE", "POSTPROCESS_PENDING", "POSTPROCESS_FAILED",
+ "COMPLETE", "FAILED", "CANCELED", "RELOAD_REQUIRED"
+]);
+const RESPONSE_KEYS = [
+ "error_code", "formal_filename", "host_build", "job", "lease_id", "maintenance",
+ "mapping_filename", "phase", "prepare_id", "progress", "schema", "type"
+];
+const JOB_KEYS = ["bvid", "canonical_url", "creator_uid", "expected_duration_ms", "job_id", "published_at", "title"];
+const LINEAGE_KEYS = [
+ "authorization_handoff_id", "authorization_message_id", "authorization_sha256",
+ "predecessor_job_id", "predecessor_terminal_error_code", "repair_audit_bytes",
+ "repair_audit_id", "repair_audit_sha256", "repair_review_result_message_id", "retry_generation"
+];
+const PREPARELESS_TERMINAL_DIAGNOSTIC_CONTRACT = new Map([
+ ["E_DRM", ["PAGE_REJECTED", new Set(["PAGE_DRM_REJECTED"])]],
+ ["E_DURATION", ["PAGE_REJECTED", new Set(["PAGE_DURATION_REJECTED"])]],
+ ["E_MULTI_PART", ["PAGE_REJECTED", new Set(["PAGE_MULTIPART_REJECTED"])]],
+ ["E_OWNER", ["PAGE_REJECTED", new Set(["PAGE_IDENTITY_REJECTED", "PAGE_OWNER_REJECTED"])]],
+ ["E_PAGE_ACCESS_CONTROL", ["PAGE_REJECTED", new Set(["PAGE_ACCESS_REJECTED"])]],
+ ["E_PAGE_DUPLICATE_TAB", ["TAB_IDENTITY", new Set(["TAB_IDENTITY_DRIFT"])]],
+ ["E_PAGE_DIMENSIONS_UNAVAILABLE", ["DIMENSIONS_PENDING", new Set(["VIDEO_DIMENSIONS_PENDING"])]],
+ ["E_PAGE_IDENTITY_UNAVAILABLE", ["IDENTITY_PENDING", new Set([
+ "PAGE_DOM_IDENTITY_PENDING", "PAGE_NAVIGATION_PENDING", "PAGE_SCRIPTING_PENDING",
+ "PAGE_TAB_ACTIVATION_PENDING", "PAGE_URL_PENDING", "PAGE_VISIBILITY_PENDING",
+ "PAGE_WINDOW_FOCUS_PENDING", "PAGE_WINDOW_MINIMIZED_PENDING", "PAGE_WINDOW_STATE_PENDING"
+ ])]],
+ ["E_PAGE_METADATA_UNAVAILABLE", ["METADATA_NOT_READY", new Set([
+ "INITIAL_STATE_ASSIGNMENT_ABSENT", "INITIAL_STATE_JSON_INVALID",
+ "INITIAL_STATE_VIDEO_DATA_ABSENT"
+ ])]],
+ ["E_PAGE_OWNER_ANCHOR_UNAVAILABLE", ["OWNER_PENDING", new Set([
+ "OWNER_ANCHOR_ABSENT", "OWNER_ANCHOR_AMBIGUOUS", "OWNER_ANCHOR_MISMATCH"
+ ])]],
+ ["E_PAGE_PROOF", ["PAGE_REJECTED", new Set([
+ "PAGE_BVID_REJECTED", "PAGE_IDENTITY_REJECTED", "PAGE_ORIGIN_REJECTED", "PAGE_URL_REJECTED"
+ ])]],
+ ["E_PAGE_SCRIPT_TIMEOUT", ["SCRIPT_TIMEOUT", new Set(["SCRIPT_INVOCATION_FAILED"])]],
+ ["E_PAGE_STABILITY_TIMEOUT", ["READY_UNSTABLE", new Set(["STABLE_SNAPSHOTS_PENDING"])]],
+ ["E_PAGE_TAB_DRIFT", ["TAB_IDENTITY", new Set(["TAB_IDENTITY_DRIFT"])]],
+ ["E_PAGE_VIDEO_ABSENT", ["VIDEO_ABSENT", new Set(["VIDEO_ELEMENT_ABSENT"])]],
+]);
+const PREPARELESS_REJECT_CODES = new Set(PREPARELESS_TERMINAL_DIAGNOSTIC_CONTRACT.keys());
+const PAGE_PROOF_DEADLINE_MS = 60000;
+const PAGE_EXTENSION_ACTIVATION_GRACE_MS = 5000;
+const PAGE_PROOF_INVOCATION_TIMEOUT_MS = 5000;
+const PAGE_PROOF_POLL_MS = 500;
+const PAGE_PROOF_STABLE_SNAPSHOTS = 3;
+const PAGE_PROOF_KEYS = [
+ "bvid", "canonical_url", "creator_uid", "eme_present", "job_id", "observed_at_unix_ms",
+ "metadata_source", "observed_duration_ms", "ready_state", "task_nonce", "video_height", "video_width"
+];
+const PAGE_PENDING_STATES = new Set([
+ "DIMENSIONS_PENDING", "IDENTITY_PENDING", "METADATA_NOT_READY", "OWNER_PENDING", "VIDEO_ABSENT"
+]);
+const PAGE_DIAGNOSTIC_STATES = new Set([
+ ...PAGE_PENDING_STATES, "PAGE_REJECTED", "READY_UNSTABLE", "SCRIPT_TIMEOUT", "TAB_IDENTITY"
+]);
+const PAGE_DIAGNOSTIC_REASONS = new Set([
+ "DEADLINE_EXHAUSTED", "METADATA_FIELDS_PENDING", "OWNER_ANCHOR_ABSENT",
+ "OWNER_ANCHOR_AMBIGUOUS", "OWNER_ANCHOR_MISMATCH",
+ "INITIAL_STATE_ASSIGNMENT_ABSENT", "INITIAL_STATE_JSON_INVALID",
+ "INITIAL_STATE_VIDEO_DATA_ABSENT",
+ "PAGE_ACCESS_REJECTED", "PAGE_BVID_REJECTED", "PAGE_DOM_IDENTITY_PENDING",
+ "PAGE_DRM_REJECTED", "PAGE_DURATION_REJECTED", "PAGE_IDENTITY_REJECTED",
+ "PAGE_MULTIPART_REJECTED", "PAGE_NAVIGATION_PENDING", "PAGE_ORIGIN_REJECTED",
+ "PAGE_OWNER_REJECTED", "PAGE_SCRIPTING_PENDING", "PAGE_TAB_ACTIVATION_PENDING",
+ "PAGE_URL_PENDING", "PAGE_URL_REJECTED", "PAGE_VISIBILITY_PENDING",
+ "PAGE_WINDOW_FOCUS_PENDING", "PAGE_WINDOW_MINIMIZED_PENDING", "PAGE_WINDOW_STATE_PENDING",
+ "SCRIPT_INVOCATION_FAILED", "STABLE_SNAPSHOTS_PENDING",
+ "TAB_IDENTITY_DRIFT", "VIDEO_DIMENSIONS_PENDING", "VIDEO_ELEMENT_ABSENT"
+]);
+const PAGE_METADATA_UNAVAILABLE_REASONS = new Set([
+ "INITIAL_STATE_ASSIGNMENT_ABSENT", "INITIAL_STATE_JSON_INVALID",
+ "INITIAL_STATE_VIDEO_DATA_ABSENT"
+]);
+const PAGE_PENDING_TERMINAL_CONTRACT = new Map([
+ ["E_PAGE_DIMENSIONS_UNAVAILABLE", ["DIMENSIONS_PENDING", new Set(["VIDEO_DIMENSIONS_PENDING"])]],
+ ["E_PAGE_IDENTITY_UNAVAILABLE", ["IDENTITY_PENDING", new Set([
+ "PAGE_DOM_IDENTITY_PENDING", "PAGE_NAVIGATION_PENDING", "PAGE_SCRIPTING_PENDING",
+ "PAGE_TAB_ACTIVATION_PENDING", "PAGE_URL_PENDING", "PAGE_VISIBILITY_PENDING",
+ "PAGE_WINDOW_FOCUS_PENDING", "PAGE_WINDOW_MINIMIZED_PENDING", "PAGE_WINDOW_STATE_PENDING"
+ ])]],
+ ["E_PAGE_METADATA_NOT_READY", ["METADATA_NOT_READY", PAGE_METADATA_UNAVAILABLE_REASONS]],
+ ["E_PAGE_OWNER_ANCHOR_UNAVAILABLE", ["OWNER_PENDING", new Set([
+ "OWNER_ANCHOR_ABSENT", "OWNER_ANCHOR_AMBIGUOUS", "OWNER_ANCHOR_MISMATCH"
+ ])]],
+ ["E_PAGE_VIDEO_ABSENT", ["VIDEO_ABSENT", new Set(["VIDEO_ELEMENT_ABSENT"])]],
+]);
+const MAX_COOKIE_COUNT = 128;
+const COOKIE_KEYS = [
+ "domain", "expiration_unix", "host_only", "http_only", "name", "partition_key",
+ "path", "same_site", "secure", "session", "store_id", "value"
+];
+const COOKIE_SAME_SITE = new Set(["no_restriction", "lax", "strict", "unspecified"]);
+const COOKIE_CONTROL_RE = /\p{C}/u;
+const STORE_ID_RE = /^[0-9]{1,8}$/;
+const AUTH_COOKIE_NAME = "SESSDATA";
+const REQUIRED_AUTH_COOKIE_NAMES = new Set([AUTH_COOKIE_NAME]);
+const TRANSFER_COOKIE_NAMES = new Set([AUTH_COOKIE_NAME]);
+const COOKIE_ACCESS_REASONS = new Set([
+ "COOKIE_API_ERROR", "COOKIE_API_SHAPE", "COOKIE_AUTH_SET_EMPTY",
+ "COOKIE_EQUAL_PRECEDENCE_CONFLICT",
+ "COOKIE_RELEVANCE_AMBIGUOUS", "COOKIE_SELECTED_INVALID",
+ "COOKIE_SELECTION_EXCEPTION", "COOKIE_TRANSFER_INVALID",
+ "COOKIE_TRANSFER_OVERFLOW"
+]);
+let drainLease = false;
+let lastDrainErrorCode = null;
+let lastCookieAccessReason = null;
+let lastTabDriftReason = null;
+let lastPageReadinessDiagnostic = null;
+let lastExtensionDiagnostic = null;
+let lastBrowserLifecycleDiagnostic = null;
-let currentProof = null;
-let currentTabId = null;
-let currentTaskNonce = null;
-let nativePort = null;
-let nativeReady = false;
-let nativeRevision = 0;
-let lastNativeType = null;
-let preparedTaskNonce = null;
-let preparedLeaseId = null;
-let activeStartLease = null;
-const prepareResponses = new Map();
-let safeState = {
- phase: "IDLE",
- progress: 0,
- error_code: null,
- formal_filename: null,
- mapping_filename: null
-};
+const BROWSER_LIFECYCLE_DIAGNOSTIC_CONTRACT = new Map([
+ ["OWNED_TAB_RECORDED", "BROWSER_OWNERSHIP"],
+ ["OWNED_TAB_ADOPTED", "BROWSER_OWNERSHIP"],
+ ["OWNED_TAB_RECORD_INVALID", "BROWSER_OWNERSHIP"],
+ ["OWNED_TAB_STORAGE_FAILED", "BROWSER_OWNERSHIP"],
+ ["OWNED_TAB_CLOSED", "BROWSER_CLEANUP"],
+ ["OWNED_TAB_ALREADY_CLOSED", "BROWSER_CLEANUP"],
+ ["OWNED_TAB_IDENTITY_DRIFT", "BROWSER_CLEANUP"],
+ ["OWNED_TAB_CLOSE_FAILED", "BROWSER_CLEANUP"],
+ ["OWNED_TAB_AUTOPLAY_STOPPED", "BROWSER_CLEANUP"]
+]);
-chrome.runtime.onInstalled.addListener(() => {
- chrome.sidePanel.setPanelBehavior({openPanelOnActionClick: true});
-});
+const EXTENSION_DIAGNOSTIC_CONTRACT = new Map([
+ ["DOCUMENT_FOCUS_FALSE", "SCRIPTING"],
+ ["DOCUMENT_FOCUS_TRUE", "SCRIPTING"],
+ ["DOCUMENT_FOCUS_UNAVAILABLE", "SCRIPTING"],
+ ["NATIVE_CONNECT_FAILED", "NATIVE_MESSAGING"],
+ ["NATIVE_DISCONNECT_FAILED", "NATIVE_MESSAGING"],
+ ["SCRIPTING_EXECUTE_FAILED", "SCRIPTING"],
+ ["SERVICE_WORKER_ERROR", "SERVICE_WORKER"],
+ ["SERVICE_WORKER_UNHANDLED_REJECTION", "SERVICE_WORKER"],
+ ["TABS_CREATE_FAILED", "TABS"],
+ ["TABS_GET_FAILED", "TABS"],
+ ["TABS_QUERY_FAILED", "TABS"],
+ ["TABS_UPDATE_FAILED", "TABS"],
+ ["WINDOWS_GET_FAILED", "WINDOWS"],
+ ["WINDOWS_UPDATE_FAILED", "WINDOWS"],
+ ["WINDOW_FOCUS_FALSE", "WINDOWS"],
+ ["WINDOW_FOCUS_TRUE", "WINDOWS"],
+ ["DOCUMENT_VISIBILITY_HIDDEN", "SCRIPTING"],
+ ["DOCUMENT_VISIBILITY_VISIBLE", "SCRIPTING"],
+ ["OS_FOREGROUND_LAUNCH_FAILED", "NATIVE_FOREGROUND"],
+ ["OS_FOREGROUND_LOCKED", "NATIVE_FOREGROUND"],
+ ["OS_FOREGROUND_PLATFORM_UNSUPPORTED", "NATIVE_FOREGROUND"],
+ ["OS_FOREGROUND_READY", "NATIVE_FOREGROUND"],
+ ["OS_FOREGROUND_REPLAY", "NATIVE_FOREGROUND"],
+ ["OS_FOREGROUND_WINDOW_ABSENT", "NATIVE_FOREGROUND"],
+ ["OS_FOREGROUND_WINDOW_AMBIGUOUS", "NATIVE_FOREGROUND"]
+]);
+const DOCUMENT_FOCUS_DIAGNOSTICS = new Set([
+ "DOCUMENT_FOCUS_FALSE", "DOCUMENT_FOCUS_TRUE", "DOCUMENT_FOCUS_UNAVAILABLE"
+]);
+const DOCUMENT_VISIBILITY_DIAGNOSTICS = new Set([
+ "DOCUMENT_VISIBILITY_HIDDEN", "DOCUMENT_VISIBILITY_VISIBLE"
+]);
+const FOREGROUND_ERROR_DIAGNOSTICS = new Map([
+ ["E_FOREGROUND_LAUNCH_FAILED", "OS_FOREGROUND_LAUNCH_FAILED"],
+ ["E_FOREGROUND_LOCKED", "OS_FOREGROUND_LOCKED"],
+ ["E_FOREGROUND_PLATFORM_UNSUPPORTED", "OS_FOREGROUND_PLATFORM_UNSUPPORTED"],
+ ["E_FOREGROUND_REPLAY", "OS_FOREGROUND_REPLAY"],
+ ["E_FOREGROUND_WINDOW_ABSENT", "OS_FOREGROUND_WINDOW_ABSENT"],
+ ["E_FOREGROUND_WINDOW_AMBIGUOUS", "OS_FOREGROUND_WINDOW_AMBIGUOUS"]
+]);
+const FOREGROUND_ELIGIBLE_CODES = new Set([
+ "E_PAGE_DIMENSIONS_UNAVAILABLE", "E_PAGE_IDENTITY_UNAVAILABLE",
+ "E_PAGE_METADATA_UNAVAILABLE", "E_PAGE_OWNER_ANCHOR_UNAVAILABLE",
+ "E_PAGE_SCRIPT_TIMEOUT", "E_PAGE_STABILITY_TIMEOUT", "E_PAGE_VIDEO_ABSENT"
+]);
-function randomNonce() {
- const bytes = new Uint8Array(16);
- crypto.getRandomValues(bytes);
- return Array.from(bytes, value => value.toString(16).padStart(2, "0")).join("");
+function recordExtensionDiagnostic(reason) {
+ const phase = EXTENSION_DIAGNOSTIC_CONTRACT.get(reason);
+ if (typeof phase !== "string") return;
+ lastExtensionDiagnostic = Object.freeze({phase, reason});
}
-function fixedError(code) {
+function recordBrowserLifecycleDiagnostic(reason) {
+ const phase = BROWSER_LIFECYCLE_DIAGNOSTIC_CONTRACT.get(reason);
+ if (typeof phase !== "string") return;
+ lastBrowserLifecycleDiagnostic = Object.freeze({phase, reason});
+}
+
+function exactOwnedTabRecord(value) {
+ const keys = [
+ "schema", "job_id", "lease_id", "bvid", "tab_id", "window_id", "cookie_store_id"
+ ];
+ if (!isPlainObject(value) || !exactKeys(value, keys) || value.schema !== OWNED_TAB_SCHEMA ||
+ !HEX64_RE.test(value.job_id || "") || !HEX32_RE.test(value.lease_id || "") ||
+ !BVID_RE.test(value.bvid || "") || !Number.isInteger(value.tab_id) || value.tab_id < 0 ||
+ !Number.isInteger(value.window_id) || value.window_id < 0 ||
+ !STORE_ID_RE.test(value.cookie_store_id || "")) return null;
+ return Object.freeze({...value});
+}
+
+async function storageSessionCall(methodName, args) {
+ if (!chrome.storage?.session) throw new Error("E_OWNED_TAB_STORAGE");
+ return chromeApiCall(
+ chrome.storage.session, methodName, args,
+ "E_OWNED_TAB_STORAGE", "OWNED_TAB_STORAGE_FAILED"
+ );
+}
+
+async function loadOwnedTabRecord() {
+ try {
+ const value = await storageSessionCall("get", [OWNED_TAB_STORAGE_KEY]);
+ const raw = isPlainObject(value) ? value[OWNED_TAB_STORAGE_KEY] : null;
+ if (raw === undefined || raw === null) return null;
+ const record = exactOwnedTabRecord(raw);
+ if (record !== null) return record;
+ recordBrowserLifecycleDiagnostic("OWNED_TAB_RECORD_INVALID");
+ await storageSessionCall("remove", [OWNED_TAB_STORAGE_KEY]);
+ } catch {
+ recordBrowserLifecycleDiagnostic("OWNED_TAB_STORAGE_FAILED");
+ }
+ return null;
+}
+
+async function persistOwnedTabRecord(job, leaseId, binding) {
+ const record = exactOwnedTabRecord({
+ schema: OWNED_TAB_SCHEMA, job_id: job.job_id, lease_id: leaseId, bvid: job.bvid,
+ tab_id: binding.tabId, window_id: binding.windowId, cookie_store_id: binding.cookieStoreId
+ });
+ if (record === null || binding.managed !== true) throw new Error("E_OWNED_TAB_STORAGE");
+ try {
+ await storageSessionCall("set", [{[OWNED_TAB_STORAGE_KEY]: record}]);
+ } catch {
+ recordBrowserLifecycleDiagnostic("OWNED_TAB_STORAGE_FAILED");
+ throw new Error("E_OWNED_TAB_STORAGE");
+ }
+ recordBrowserLifecycleDiagnostic("OWNED_TAB_RECORDED");
+ return record;
+}
+
+async function clearOwnedTabRecord() {
+ try {
+ await storageSessionCall("remove", [OWNED_TAB_STORAGE_KEY]);
+ return true;
+ } catch {
+ recordBrowserLifecycleDiagnostic("OWNED_TAB_STORAGE_FAILED");
+ return false;
+ }
+}
+
+async function cleanupOwnedTab(recordValue = null) {
+ const record = exactOwnedTabRecord(recordValue) || await loadOwnedTabRecord();
+ if (record === null) return true;
+ let tab;
+ try {
+ tab = await chromeApiCall(
+ chrome.tabs, "get", [record.tab_id], "E_OWNED_TAB_CLOSE", "TABS_GET_FAILED"
+ );
+ } catch {
+ await clearOwnedTabRecord();
+ recordBrowserLifecycleDiagnostic("OWNED_TAB_ALREADY_CLOSED");
+ return true;
+ }
+ if (!isPlainObject(tab) || tab.id !== record.tab_id || tab.windowId !== record.window_id ||
+ String(tab.cookieStoreId ?? "0") !== record.cookie_store_id ||
+ (!locationMatches(tab.url, record.bvid) && !locationMatches(tab.pendingUrl, record.bvid) &&
+ !isManagedIdentityPendingUrl(tab.url, record.bvid))) {
+ await clearOwnedTabRecord();
+ recordBrowserLifecycleDiagnostic("OWNED_TAB_IDENTITY_DRIFT");
+ return false;
+ }
+ // Retire the durable ownership capability before the non-atomic remove. A
+ // failed remove must never leave a record that a restarted service worker
+ // could apply to a same-id/same-BVID user replacement.
+ if (!await clearOwnedTabRecord()) {
+ recordBrowserLifecycleDiagnostic("OWNED_TAB_CLOSE_FAILED");
+ return false;
+ }
+ try {
+ await chromeApiCall(
+ chrome.tabs, "remove", [record.tab_id], "E_OWNED_TAB_CLOSE", "TABS_REMOVE_FAILED"
+ );
+ recordBrowserLifecycleDiagnostic("OWNED_TAB_CLOSED");
+ return true;
+ } catch {
+ // tabs.remove is not an atomic compare-and-close operation. Once it fails,
+ // the numeric tab id may already name a replacement user tab, including a
+ // replacement on the same BVID. No subsequent tabs.update/remove mutation
+ // can prove object ownership. The durable capability was already retired,
+ // so surface only a fixed, sanitized lifecycle result and never retry it.
+ recordBrowserLifecycleDiagnostic("OWNED_TAB_CLOSE_FAILED");
+ return false;
+ }
+}
+
+function extensionApiError(code, reason) {
+ recordExtensionDiagnostic(reason);
+ return new Error(code);
+}
+
+function chromeApiCall(receiver, methodName, args, code, reason) {
+ return new Promise((resolve, reject) => {
+ let settled = false;
+ const settle = (error, value) => {
+ if (settled) return;
+ settled = true;
+ if (error) reject(extensionApiError(code, reason)); else resolve(value);
+ };
+ const callback = value => {
+ let failed = false;
+ try { failed = chrome.runtime.lastError != null; } catch { failed = true; }
+ settle(failed, value);
+ };
+ let returned;
+ try {
+ returned = receiver[methodName](...args, callback);
+ } catch {
+ settle(true);
+ return;
+ }
+ if (returned && typeof returned.then === "function") {
+ returned.then(value => settle(false, value), () => settle(true));
+ }
+ });
+}
+
+if (typeof globalThis.addEventListener === "function") {
+ globalThis.addEventListener("error", () => recordExtensionDiagnostic("SERVICE_WORKER_ERROR"));
+ globalThis.addEventListener("unhandledrejection", () =>
+ recordExtensionDiagnostic("SERVICE_WORKER_UNHANDLED_REJECTION"));
+}
+
+function isPlainObject(value) {
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
+ const prototype = Object.getPrototypeOf(value);
+ return prototype === Object.prototype || prototype === null;
+}
+
+function exactKeys(value, keys) {
+ return isPlainObject(value) && Object.keys(value).sort().join("\0") === [...keys].sort().join("\0");
+}
+
+function safeErrorCode(error, fallback = "E_EXTENSION") {
+ const candidate = typeof error?.message === "string" ? error.message : "";
+ return ERROR_CODE_RE.test(candidate) ? candidate : fallback;
+}
+
+function canonicalUrl(bvid) {
+ return `https://www.bilibili.com/video/${bvid}`;
+}
+
+function locationMatches(rawUrl, bvid) {
+ if (!BVID_RE.test(bvid) || typeof rawUrl !== "string") return false;
+ let parsed;
+ try { parsed = new URL(rawUrl); } catch { return false; }
+ const path = `/video/${bvid}`;
+ return parsed.origin === "https://www.bilibili.com" &&
+ (parsed.pathname === path || parsed.pathname === `${path}/`) &&
+ (parsed.search === "" || /^\?vd_source=[0-9a-f]{32}$/.test(parsed.search)) &&
+ parsed.hash === "";
+}
+
+function isManagedIdentityPendingUrl(rawUrl, bvid) {
+ if (rawUrl === "about:blank") return true;
+ if (typeof rawUrl !== "string" || !BVID_RE.test(bvid)) return false;
+ let parsed;
+ try { parsed = new URL(rawUrl); } catch { return true; }
+ if (parsed.origin !== "https://www.bilibili.com") return false;
+ const videoMatch = /^\/video\/(BV1[1-9A-HJ-NP-Za-km-z]{9})\/?$/.exec(parsed.pathname);
+ return videoMatch === null || videoMatch[1] === bvid;
+}
+
+function durationToleranceMs(expected) {
+ return Math.max(1500, Math.min(10000, Math.ceil(expected / 1000)));
+}
+
+async function sha256Hex(text) {
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
+ return Array.from(new Uint8Array(digest), value => value.toString(16).padStart(2, "0")).join("");
+}
+
+async function validateJob(job) {
+ const successor = isPlainObject(job) && Object.prototype.hasOwnProperty.call(job, "lineage");
+ if (!exactKeys(job, successor ? [...JOB_KEYS, "lineage"] : JOB_KEYS) || !HEX64_RE.test(job.job_id) || !BVID_RE.test(job.bvid) ||
+ !CREATOR_RE.test(job.creator_uid) || job.canonical_url !== canonicalUrl(job.bvid) ||
+ !Number.isSafeInteger(job.expected_duration_ms) || job.expected_duration_ms < 1000 ||
+ job.expected_duration_ms > 86400000 || typeof job.title !== "string" || !job.title.trim() ||
+ new TextEncoder().encode(job.title).length > 600 || typeof job.published_at !== "string" ||
+ job.published_at.length > 64 || !Number.isFinite(Date.parse(job.published_at))) return false;
+ let stable;
+ if (successor) {
+ const lineage = job.lineage;
+ if (!exactKeys(lineage, LINEAGE_KEYS) || !HEX64_RE.test(lineage.predecessor_job_id) ||
+ !Number.isSafeInteger(lineage.retry_generation) || lineage.retry_generation < 1 ||
+ lineage.retry_generation > 1000000 || !ERROR_CODE_RE.test(lineage.predecessor_terminal_error_code) ||
+ !MESSAGE_ID_RE.test(lineage.authorization_message_id) ||
+ !HANDOFF_ID_RE.test(lineage.authorization_handoff_id) ||
+ !UPPER_HEX64_RE.test(lineage.authorization_sha256) ||
+ !MESSAGE_ID_RE.test(lineage.repair_review_result_message_id) ||
+ !AUDIT_ID_RE.test(lineage.repair_audit_id) ||
+ !Number.isSafeInteger(lineage.repair_audit_bytes) || lineage.repair_audit_bytes < 1 ||
+ !UPPER_HEX64_RE.test(lineage.repair_audit_sha256)) return false;
+ stable = await sha256Hex([
+ "bili-auth-job-v2", job.creator_uid, job.bvid, lineage.predecessor_job_id,
+ String(lineage.retry_generation), lineage.predecessor_terminal_error_code,
+ lineage.authorization_message_id, lineage.authorization_handoff_id,
+ lineage.authorization_sha256, lineage.repair_review_result_message_id,
+ lineage.repair_audit_id, String(lineage.repair_audit_bytes), lineage.repair_audit_sha256
+ ].join("\0"));
+ } else {
+ stable = await sha256Hex(`bili-auth-job-v1\0${job.creator_uid}\0${job.bvid}`);
+ }
+ return stable === job.job_id;
+}
+
+function validateMaintenance(value) {
+ return exactKeys(value, ["reload_required", "reload_token", "required_extension_build", "retry_after_unix_ms"]) &&
+ typeof value.required_extension_build === "string" && value.required_extension_build.length <= 128 &&
+ typeof value.reload_required === "boolean" &&
+ (value.reload_token === null || HEX32_RE.test(value.reload_token)) &&
+ Number.isSafeInteger(value.retry_after_unix_ms) && value.retry_after_unix_ms >= 0;
+}
+
+async function validateResponse(value, expectedType) {
+ if (!exactKeys(value, RESPONSE_KEYS) || value.schema !== SCHEMA || value.type !== expectedType ||
+ typeof value.host_build !== "string" || !SAFE_PHASES.has(value.phase) ||
+ !Number.isInteger(value.progress) || value.progress < 0 || value.progress > 100 ||
+ (value.error_code !== null && !/^E_[A-Z0-9_]{1,48}$/.test(value.error_code)) ||
+ !validateMaintenance(value.maintenance)) return false;
+ if (value.job !== null && !(await validateJob(value.job))) return false;
+ if (value.lease_id !== null && !HEX32_RE.test(value.lease_id)) return false;
+ for (const name of ["formal_filename", "mapping_filename", "prepare_id"]) {
+ if (value[name] !== null && typeof value[name] !== "string") return false;
+ }
+ return true;
+}
+
+function nativeSession() {
+ let port;
+ try {
+ port = chrome.runtime.connectNative(HOST_NAME);
+ } catch {
+ throw extensionApiError("E_HOST_CONNECT", "NATIVE_CONNECT_FAILED");
+ }
+ if (!isPlainObject(port) && (port === null || typeof port !== "object")) {
+ throw extensionApiError("E_HOST_CONNECT", "NATIVE_CONNECT_FAILED");
+ }
+ const messages = [];
+ const waiters = [];
+ let disconnected = false;
+ let closeRequested = false;
+ let lastDeterministicError = null;
+ const rejectWaiters = code => {
+ while (waiters.length) {
+ const waiter = waiters.shift();
+ clearTimeout(waiter.timer);
+ waiter.reject(new Error(code));
+ }
+ };
+ port.onMessage.addListener(message => {
+ if (isPlainObject(message) && ERROR_CODE_RE.test(message.error_code || "")) {
+ lastDeterministicError = message.error_code;
+ }
+ const waiter = waiters.shift();
+ if (waiter) {
+ clearTimeout(waiter.timer);
+ waiter.resolve(message);
+ } else messages.push(message);
+ });
+ port.onDisconnect.addListener(() => {
+ if (disconnected) return;
+ disconnected = true;
+ let runtimeFailure = false;
+ try { runtimeFailure = chrome.runtime.lastError != null; } catch { runtimeFailure = true; }
+ if (runtimeFailure) recordExtensionDiagnostic("NATIVE_DISCONNECT_FAILED");
+ rejectWaiters(lastDeterministicError || "E_HOST_DISCONNECT");
+ });
+ const next = timeoutMs => new Promise((resolve, reject) => {
+ if (messages.length) { resolve(messages.shift()); return; }
+ if (disconnected) { reject(new Error(lastDeterministicError || "E_HOST_DISCONNECT")); return; }
+ const waiter = {resolve, reject, timer: null};
+ waiters.push(waiter);
+ waiter.timer = setTimeout(() => {
+ const index = waiters.indexOf(waiter);
+ if (index >= 0) {
+ waiters.splice(index, 1);
+ reject(new Error("E_HOST_TIMEOUT"));
+ }
+ }, timeoutMs);
+ });
+ return {
+ async send(message, timeoutMs = 20000) {
+ if (disconnected) throw new Error(lastDeterministicError || "E_HOST_DISCONNECT");
+ const response = next(timeoutMs);
+ try {
+ port.postMessage(message);
+ } catch (error) {
+ rejectWaiters(safeErrorCode(error, "E_HOST_SEND"));
+ }
+ return response;
+ },
+ disconnect() {
+ if (closeRequested) return;
+ closeRequested = true;
+ if (!disconnected) {
+ try { port.disconnect(); } catch {}
+ }
+ }
+ };
+}
+
+function randomHex(bytes) {
+ const data = new Uint8Array(bytes);
+ crypto.getRandomValues(data);
+ return Array.from(data, value => value.toString(16).padStart(2, "0")).join("");
+}
+
+function isPageReadinessDiagnostic(value) {
+ return exactKeys(value, ["attempts", "elapsed_ms", "reason", "state"]) &&
+ Number.isInteger(value.attempts) && value.attempts >= 0 && value.attempts <= 10000 &&
+ Number.isInteger(value.elapsed_ms) && value.elapsed_ms >= 0 && value.elapsed_ms <= 7200000 &&
+ PAGE_DIAGNOSTIC_STATES.has(value.state) && PAGE_DIAGNOSTIC_REASONS.has(value.reason);
+}
+
+function isCanonicalPreparelessTerminal(code, diagnostic) {
+ if (!isPageReadinessDiagnostic(diagnostic)) return false;
+ const contract = PREPARELESS_TERMINAL_DIAGNOSTIC_CONTRACT.get(code);
+ return Array.isArray(contract) && contract.length === 2 && diagnostic.state === contract[0] &&
+ contract[1] instanceof Set && contract[1].has(diagnostic.reason);
+}
+
+function pageReadinessError(code, attempts, startedAtUnixMs, state, reason) {
+ const diagnostic = {
+ attempts,
+ elapsed_ms: Math.max(0, Math.min(7200000, Date.now() - startedAtUnixMs)),
+ state,
+ reason
+ };
+ if (!isCanonicalPreparelessTerminal(code, diagnostic)) return new Error("E_REJECT");
+ lastPageReadinessDiagnostic = diagnostic;
const error = new Error(code);
- error.code = code;
+ error.page_readiness_diagnostic = diagnostic;
return error;
}
-async function activeTargetTab() {
- const tabs = await chrome.tabs.query({active: true, currentWindow: true});
- if (tabs.length !== 1 || !Number.isInteger(tabs[0].id)) {
- throw fixedError("E_PAGE_PROOF");
- }
- const parsed = new URL(tabs[0].url || "about:blank");
- if (parsed.protocol !== "https:" || parsed.hostname !== "www.bilibili.com" || parsed.pathname !== TARGET_PATH) {
- throw fixedError("E_PAGE_PROOF");
- }
- return tabs[0];
+function fallbackPageReadinessDiagnostic(code) {
+ const contract = PREPARELESS_TERMINAL_DIAGNOSTIC_CONTRACT.get(code);
+ if (!Array.isArray(contract) || contract.length !== 2 || !(contract[1] instanceof Set) ||
+ contract[1].size !== 1) return null;
+ const value = {attempts: 0, elapsed_ms: 0, state: contract[0], reason: [...contract[1]][0]};
+ lastPageReadinessDiagnostic = value;
+ return value;
}
-async function observePage(tab, nonce) {
- const results = await chrome.scripting.executeScript({
- target: {tabId: tab.id},
- world: "ISOLATED",
- args: [TARGET, CANONICAL_URL, nonce],
- func: (target, canonicalUrl, taskNonce) => {
- const videos = Array.from(document.querySelectorAll("video")).filter(video =>
- video.readyState >= 1 && video.videoWidth > 0 && video.videoHeight > 0
- );
- if (location.hostname !== "www.bilibili.com" || location.pathname !== `/video/${target}` || videos.length !== 1) {
- return null;
- }
- const video = videos[0];
- return {
- target,
- canonical_url: canonicalUrl,
- task_nonce: taskNonce,
- observed_at_unix_ms: Date.now(),
- observed_duration_ms: Math.round(video.duration * 1000),
- video_width: video.videoWidth,
- video_height: video.videoHeight,
- ready_state: video.readyState,
- eme_present: video.mediaKeys !== null
- };
- }
+function tabDriftError(reason) {
+ lastTabDriftReason = reason;
+ const error = new Error("E_PAGE_TAB_DRIFT");
+ error.reason_code = reason;
+ return error;
+}
+
+function tabIdentityReason(tab, binding) {
+ if (!isPlainObject(tab) || !Number.isInteger(tab.id)) return "TAB_ID_INVALID";
+ if (tab.id !== binding.tabId) return "TAB_ID_CHANGED";
+ if (!Number.isInteger(tab.windowId) || tab.windowId !== binding.windowId) return "TAB_WINDOW_CHANGED";
+ if (String(tab.cookieStoreId ?? "0") !== binding.cookieStoreId) return "TAB_STORE_CHANGED";
+ return null;
+}
+
+function bindTab(tab, bvid, managed) {
+ if (!isPlainObject(tab) || !Number.isInteger(tab.id) || !Number.isInteger(tab.windowId)) {
+ throw tabDriftError("TAB_IDENTITY_INVALID");
+ }
+ const exactUrl = locationMatches(tab.url, bvid);
+ const exactPendingUrl = managed === true && locationMatches(tab.pendingUrl, bvid);
+ if (!exactUrl && !exactPendingUrl) throw tabDriftError("TAB_URL_UNBOUND");
+ return Object.freeze({
+ tabId: tab.id,
+ windowId: tab.windowId,
+ cookieStoreId: String(tab.cookieStoreId ?? "0"),
+ managed: managed === true
});
- if (results.length !== 1 || !results[0].result) {
- throw fixedError("E_PAGE_PROOF");
- }
- const proof = results[0].result;
- const now = Date.now();
- const exactKeys = [
- "canonical_url", "eme_present", "observed_at_unix_ms", "observed_duration_ms",
- "ready_state", "target", "task_nonce", "video_height", "video_width"
- ];
- if (Object.keys(proof).sort().join("|") !== exactKeys.sort().join("|") ||
- proof.target !== TARGET || proof.canonical_url !== CANONICAL_URL ||
- proof.task_nonce !== nonce || proof.eme_present !== false ||
- !Number.isSafeInteger(proof.observed_at_unix_ms) ||
- proof.observed_at_unix_ms < now - 60000 || proof.observed_at_unix_ms > now + 5000 ||
- !Number.isSafeInteger(proof.observed_duration_ms) ||
- Math.abs(proof.observed_duration_ms - EXPECTED_DURATION_MS) > DURATION_TOLERANCE_MS ||
- !Number.isInteger(proof.video_width) || proof.video_width < 1 || proof.video_width > 7680 ||
- !Number.isInteger(proof.video_height) || proof.video_height < 1 || proof.video_height > 4320 ||
- !Number.isInteger(proof.ready_state) || proof.ready_state < 1 || proof.ready_state > 4) {
- throw fixedError(proof.eme_present ? "E_DRM" : "E_PAGE_PROOF");
- }
- return proof;
}
-async function validateCurrentPage({leaseId = null} = {}) {
- if (activeStartLease !== null && activeStartLease !== leaseId) {
- throw fixedError("E_BUSY");
+async function revalidateBoundTab(binding, bvid) {
+ let current;
+ try {
+ current = await chromeApiCall(
+ chrome.tabs, "get", [binding.tabId], "E_PAGE_TAB_DRIFT", "TABS_GET_FAILED"
+ );
+ } catch {
+ throw tabDriftError("TAB_REMOVED_OR_UNAVAILABLE");
}
- const tab = await activeTargetTab();
- const nonce = randomNonce();
- const proof = await observePage(tab, nonce);
- currentProof = proof;
- currentTabId = tab.id;
- if (nativePort) {
- const previousPort = nativePort;
- nativePort = null;
- nativeReady = false;
- preparedTaskNonce = null;
- preparedLeaseId = null;
- prepareResponses.clear();
- previousPort.disconnect();
- }
- await awaitNativeReady();
- safeState = {...safeState, phase: "READY", error_code: null};
- return safeState;
+ const identityReason = tabIdentityReason(current, binding);
+ if (identityReason !== null) throw tabDriftError(identityReason);
+ if (!locationMatches(current.url, bvid)) throw tabDriftError("TAB_URL_CHANGED");
+ return current;
}
-function connectNative() {
- if (nativePort) {
- return;
+async function activateBoundTab(binding, tab, bvid, startedAtUnixMs) {
+ const identityReason = tabIdentityReason(tab, binding);
+ if (identityReason !== null) throw tabDriftError(identityReason);
+ let focusedWindow;
+ try {
+ focusedWindow = await chromeApiCall(
+ chrome.windows, "update", [binding.windowId, {state: "normal", focused: true}],
+ "E_PAGE_IDENTITY_UNAVAILABLE", "WINDOWS_UPDATE_FAILED"
+ );
+ } catch {
+ throw pageReadinessError(
+ "E_PAGE_IDENTITY_UNAVAILABLE", 0, startedAtUnixMs,
+ "IDENTITY_PENDING", "PAGE_WINDOW_FOCUS_PENDING"
+ );
}
- nativeReady = false;
- preparedTaskNonce = null;
- preparedLeaseId = null;
- prepareResponses.clear();
- const port = chrome.runtime.connectNative(HOST);
- nativePort = port;
- port.onMessage.addListener(message => {
- const safeKeys = new Set([
- "schema", "type", "host_build", "target", "phase", "progress", "error_code",
- "formal_filename", "mapping_filename", "prepare_id"
- ]);
- const basename = value => value === null || (typeof value === "string" && value.length > 0 && !/[\\/]/.test(value));
- const prepareIdValid = message?.prepare_id === null ||
- (typeof message?.prepare_id === "string" && /^[0-9a-f]{32}$/.test(message.prepare_id));
- if (!message || Object.keys(message).some(key => !safeKeys.has(key)) ||
- message.schema !== SCHEMA || message.host_build !== HOST_BUILD || message.target !== TARGET ||
- !["IDLE", "READY", "CHECKING", "DOWNLOADING", "MERGING", "VALIDATING", "PUBLISHING", "COMPLETE", "FAILED", "CANCELED"].includes(message.phase) ||
- !Number.isInteger(message.progress) || message.progress < 0 || message.progress > 100 ||
- !basename(message.formal_filename) || !basename(message.mapping_filename) || !prepareIdValid ||
- (message.type === "prepare" ? message.prepare_id === null : message.prepare_id !== null)) {
- safeState = {...safeState, phase: "FAILED", error_code: "E_PROTOCOL"};
- return;
- }
- nativeReady = nativeReady || (message.type === "hello" && message.phase === "READY");
- lastNativeType = message.type;
- if (message.type === "prepare") {
- prepareResponses.set(message.prepare_id, {
- phase: message.phase,
- error_code: message.error_code
- });
- }
- safeState = {
- phase: message.phase,
- progress: message.progress,
- error_code: message.error_code,
- formal_filename: message.formal_filename,
- mapping_filename: message.mapping_filename
+ if (!isPlainObject(focusedWindow) || focusedWindow.id !== binding.windowId) {
+ throw tabDriftError("TAB_WINDOW_CHANGED");
+ }
+ let updated;
+ try {
+ updated = await chromeApiCall(
+ chrome.tabs, "update", [binding.tabId, {active: true}],
+ "E_PAGE_IDENTITY_UNAVAILABLE", "TABS_UPDATE_FAILED"
+ );
+ } catch {
+ throw pageReadinessError(
+ "E_PAGE_IDENTITY_UNAVAILABLE", 0, startedAtUnixMs,
+ "IDENTITY_PENDING", "PAGE_TAB_ACTIVATION_PENDING"
+ );
+ }
+ const updatedIdentityReason = tabIdentityReason(updated, binding);
+ if (updatedIdentityReason !== null) throw tabDriftError(updatedIdentityReason);
+ if (!locationMatches(updated.url, bvid) &&
+ !(binding.managed && (locationMatches(updated.pendingUrl, bvid) ||
+ isManagedIdentityPendingUrl(updated.url, bvid)))) {
+ throw tabDriftError("TAB_MANAGED_URL_DRIFT");
+ }
+ return updated;
+}
+
+async function waitForComplete(binding, tab, bvid, startedAtUnixMs, deadlineUnixMs) {
+ const identityReason = tabIdentityReason(tab, binding);
+ if (identityReason !== null) throw tabDriftError(identityReason);
+ if (tab.status === "complete" && locationMatches(tab.url, bvid)) {
+ return revalidateBoundTab(binding, bvid);
+ }
+ if (!locationMatches(tab.url, bvid) &&
+ !(binding.managed && (locationMatches(tab.pendingUrl, bvid) ||
+ isManagedIdentityPendingUrl(tab.url, bvid)))) {
+ throw tabDriftError("TAB_URL_UNBOUND");
+ }
+ return new Promise((resolve, reject) => {
+ let done = false;
+ let timer = null;
+ const finish = (error, value) => {
+ if (done) return;
+ done = true;
+ chrome.tabs.onUpdated.removeListener(onUpdated);
+ chrome.tabs.onRemoved.removeListener(onRemoved);
+ if (timer !== null) clearTimeout(timer);
+ if (error) reject(error); else resolve(value);
};
- nativeRevision += 1;
- });
- port.onDisconnect.addListener(() => {
- if (nativePort === port) {
- nativePort = null;
- nativeReady = false;
- preparedTaskNonce = null;
- preparedLeaseId = null;
- prepareResponses.clear();
- if (!["COMPLETE", "FAILED", "CANCELED"].includes(safeState.phase)) {
- safeState = {...safeState, phase: "FAILED", error_code: "E_NATIVE_DISCONNECT"};
+ const onUpdated = (tabId, change, current) => {
+ if (tabId !== binding.tabId) return;
+ const currentIdentityReason = tabIdentityReason(current, binding);
+ if (currentIdentityReason !== null) {
+ finish(tabDriftError(currentIdentityReason));
+ return;
}
+ if (typeof change?.url === "string" && !locationMatches(change.url, bvid) &&
+ !(binding.managed && isManagedIdentityPendingUrl(change.url, bvid))) {
+ finish(tabDriftError("TAB_MANAGED_URL_DRIFT"));
+ return;
+ }
+ if (locationMatches(current.url, bvid) && current.status === "complete") {
+ finish(null, current);
+ return;
+ }
+ if (!locationMatches(current.url, bvid) &&
+ !(binding.managed && (locationMatches(current.pendingUrl, bvid) ||
+ isManagedIdentityPendingUrl(current.url, bvid)))) {
+ finish(tabDriftError("TAB_URL_CHANGED"));
+ }
+ };
+ const onRemoved = tabId => {
+ if (tabId === binding.tabId) finish(tabDriftError("TAB_REMOVED_OR_UNAVAILABLE"));
+ };
+ chrome.tabs.onUpdated.addListener(onUpdated);
+ chrome.tabs.onRemoved.addListener(onRemoved);
+ timer = setTimeout(() => finish(pageReadinessError(
+ "E_PAGE_IDENTITY_UNAVAILABLE", 0, startedAtUnixMs,
+ "IDENTITY_PENDING", "PAGE_URL_PENDING"
+ )), Math.max(1, deadlineUnixMs - Date.now()));
+ });
+}
+
+async function waitForActiveNonMinimizedBoundTab(binding, bvid, startedAtUnixMs, deadlineUnixMs) {
+ let pendingReason = "PAGE_WINDOW_STATE_PENDING";
+ while (Date.now() < deadlineUnixMs) {
+ const current = await revalidateBoundTab(binding, bvid);
+ let currentWindow;
+ try {
+ currentWindow = await chromeApiCall(
+ chrome.windows, "get", [binding.windowId],
+ "E_PAGE_IDENTITY_UNAVAILABLE", "WINDOWS_GET_FAILED"
+ );
+ } catch {
+ currentWindow = null;
}
- });
- port.postMessage({
- schema: SCHEMA,
- type: "hello",
- extension_build: EXTENSION_BUILD,
- target: TARGET
- });
-}
-
-async function freshNativeStatus() {
- const prior = nativeRevision;
- nativePort.postMessage({schema: SCHEMA, type: "status", target: TARGET});
- const deadline = Date.now() + 5000;
- while (nativeRevision === prior && Date.now() < deadline) {
- await new Promise(resolve => setTimeout(resolve, 25));
- }
- if (nativeRevision === prior) {
- throw fixedError("E_NATIVE_CONNECT");
- }
- return safeState;
-}
-
-async function awaitNativeReady() {
- connectNative();
- const deadline = Date.now() + 5000;
- while (!nativeReady && Date.now() < deadline) {
- if (safeState.error_code) {
- throw fixedError(safeState.error_code);
+ if (!isPlainObject(currentWindow) || currentWindow.id !== binding.windowId) {
+ pendingReason = "PAGE_WINDOW_STATE_PENDING";
+ } else if (typeof currentWindow.state !== "string") {
+ pendingReason = "PAGE_WINDOW_STATE_PENDING";
+ } else if (currentWindow.state === "minimized") {
+ pendingReason = "PAGE_WINDOW_MINIMIZED_PENDING";
+ } else if (!["normal", "maximized", "fullscreen", "locked-fullscreen"].includes(currentWindow.state)) {
+ pendingReason = "PAGE_WINDOW_STATE_PENDING";
+ } else if (current.active !== true) {
+ pendingReason = "PAGE_TAB_ACTIVATION_PENDING";
+ } else {
+ recordExtensionDiagnostic(currentWindow.focused === true ?
+ "WINDOW_FOCUS_TRUE" : "WINDOW_FOCUS_FALSE");
+ return current;
}
- await new Promise(resolve => setTimeout(resolve, 25));
+ const remainingMs = deadlineUnixMs - Date.now();
+ if (remainingMs <= 0) break;
+ await new Promise(resolve => setTimeout(resolve, Math.min(PAGE_PROOF_POLL_MS, remainingMs)));
}
- if (!nativeReady) {
- throw fixedError("E_NATIVE_CONNECT");
+ throw pageReadinessError(
+ "E_PAGE_IDENTITY_UNAVAILABLE", 0, startedAtUnixMs,
+ "IDENTITY_PENDING", pendingReason
+ );
+}
+
+function foregroundFallbackEligible(error) {
+ return FOREGROUND_ELIGIBLE_CODES.has(error?.message) &&
+ isPageReadinessDiagnostic(error?.page_readiness_diagnostic);
+}
+
+function exactWindowBounds(value) {
+ if (!isPlainObject(value)) return null;
+ const bounds = {};
+ for (const key of ["left", "top", "width", "height"]) {
+ if (!Number.isSafeInteger(value[key])) return null;
+ bounds[key] = value[key];
+ }
+ if (bounds.width < 1 || bounds.height < 1) return null;
+ return bounds;
+}
+
+async function foregroundAndRebind(session, job, leaseId, selected, flow, sourceError) {
+ if (flow.foregroundUsed) throw sourceError;
+ flow.foregroundUsed = true;
+ let windowValue;
+ try {
+ windowValue = await chromeApiCall(
+ chrome.windows, "get", [selected.binding.windowId],
+ "E_PAGE_IDENTITY_UNAVAILABLE", "WINDOWS_GET_FAILED"
+ );
+ } catch {
+ throw sourceError;
+ }
+ const windowBounds = exactWindowBounds(windowValue);
+ if (windowBounds === null) throw sourceError;
+ const response = await session.send({
+ schema: SCHEMA, type: "foreground", job_id: job.job_id, lease_id: leaseId,
+ tab_id: selected.binding.tabId, window_id: selected.binding.windowId,
+ window_bounds: windowBounds
+ }, 20000);
+ if (!(await validateResponse(response, "foreground")) ||
+ response.job?.job_id !== job.job_id || response.lease_id !== leaseId) {
+ throw sourceError;
+ }
+ if (response.phase !== "READY" || response.error_code !== null) {
+ const diagnosticReason = FOREGROUND_ERROR_DIAGNOSTICS.get(response.error_code);
+ if (typeof diagnosticReason === "string") recordExtensionDiagnostic(diagnosticReason);
+ throw sourceError;
+ }
+ recordExtensionDiagnostic("OS_FOREGROUND_READY");
+ let pendingError = sourceError;
+ while (Date.now() < flow.deadlineUnixMs) {
+ let tabs;
+ try {
+ tabs = await chromeApiCall(
+ chrome.tabs, "query", [{url: ["https://www.bilibili.com/video/*"], windowType: "normal"}],
+ "E_PAGE_IDENTITY_UNAVAILABLE", "TABS_QUERY_FAILED"
+ );
+ } catch {
+ tabs = null;
+ }
+ if (Array.isArray(tabs)) {
+ const exactCandidates = tabs.filter(item =>
+ item?.incognito !== true && locationMatches(item?.url, job.bvid));
+ if (exactCandidates.length > 1) throw new Error("E_PAGE_DUPLICATE_TAB");
+ if (exactCandidates.length === 1) {
+ const preservesOwnership = selected.binding.managed === true &&
+ exactCandidates[0].id === selected.binding.tabId &&
+ exactCandidates[0].windowId === selected.binding.windowId;
+ const rebound = {
+ binding: bindTab(exactCandidates[0], job.bvid, preservesOwnership), tab: exactCandidates[0]
+ };
+ const active = await waitForActiveNonMinimizedBoundTab(
+ rebound.binding, job.bvid, flow.startedAtUnixMs, flow.deadlineUnixMs
+ );
+ return {binding: rebound.binding, tab: active, activeNonMinimized: active};
+ }
+ pendingError = pageReadinessError(
+ "E_PAGE_IDENTITY_UNAVAILABLE", 0, flow.startedAtUnixMs,
+ "IDENTITY_PENDING", "PAGE_URL_PENDING"
+ );
+ }
+ const remainingMs = flow.deadlineUnixMs - Date.now();
+ if (remainingMs <= 0) break;
+ await new Promise(resolve => setTimeout(resolve, Math.min(PAGE_PROOF_POLL_MS, remainingMs)));
+ }
+ throw pendingError;
+}
+
+async function targetTab(session, job, leaseId, flow) {
+ const startedAtUnixMs = Date.now();
+ let tabs;
+ try {
+ tabs = await chromeApiCall(
+ chrome.tabs, "query", [{
+ url: ["https://www.bilibili.com/video/*"], windowType: "normal"
+ }], "E_PAGE_IDENTITY_UNAVAILABLE", "TABS_QUERY_FAILED"
+ );
+ } catch {
+ throw pageReadinessError(
+ "E_PAGE_IDENTITY_UNAVAILABLE", 0, startedAtUnixMs,
+ "IDENTITY_PENDING", "PAGE_DOM_IDENTITY_PENDING"
+ );
+ }
+ if (!Array.isArray(tabs)) throw new Error("E_PAGE_PROOF");
+ const storedOwnership = await loadOwnedTabRecord();
+ const exactCandidates = [];
+ for (const candidate of tabs.filter(item => item?.incognito !== true && locationMatches(item?.url, job.bvid))) {
+ const adopted = storedOwnership !== null && storedOwnership.job_id === job.job_id &&
+ storedOwnership.lease_id === leaseId && storedOwnership.bvid === job.bvid &&
+ storedOwnership.tab_id === candidate.id && storedOwnership.window_id === candidate.windowId &&
+ storedOwnership.cookie_store_id === String(candidate.cookieStoreId ?? "0");
+ const provisional = bindTab(candidate, job.bvid, adopted);
+ const current = await revalidateBoundTab(provisional, job.bvid);
+ exactCandidates.push({binding: bindTab(current, job.bvid, adopted), tab: current});
+ if (adopted) recordBrowserLifecycleDiagnostic("OWNED_TAB_ADOPTED");
+ }
+ if (exactCandidates.length > 1) throw new Error("E_PAGE_DUPLICATE_TAB");
+ let selected;
+ if (exactCandidates.length === 1) {
+ selected = exactCandidates[0];
+ } else {
+ let created;
+ try {
+ created = await chromeApiCall(
+ chrome.tabs, "create", [{url: canonicalUrl(job.bvid), active: false}],
+ "E_PAGE_IDENTITY_UNAVAILABLE", "TABS_CREATE_FAILED"
+ );
+ } catch {
+ throw pageReadinessError(
+ "E_PAGE_IDENTITY_UNAVAILABLE", 0, startedAtUnixMs,
+ "IDENTITY_PENDING", "PAGE_NAVIGATION_PENDING"
+ );
+ }
+ selected = {binding: bindTab(created, job.bvid, true), tab: created};
+ await persistOwnedTabRecord(job, leaseId, selected.binding);
+ }
+ try {
+ const activated = await activateBoundTab(
+ selected.binding, selected.tab, job.bvid, startedAtUnixMs
+ );
+ const activationDeadline = Math.min(
+ flow.deadlineUnixMs, Date.now() + PAGE_EXTENSION_ACTIVATION_GRACE_MS
+ );
+ const ready = await waitForComplete(
+ selected.binding, activated, job.bvid, startedAtUnixMs, activationDeadline
+ );
+ const activeNonMinimized = await waitForActiveNonMinimizedBoundTab(
+ selected.binding, job.bvid, startedAtUnixMs, activationDeadline
+ );
+ return {binding: selected.binding, tab: ready, activeNonMinimized};
+ } catch (error) {
+ if (!foregroundFallbackEligible(error)) throw error;
+ return foregroundAndRebind(session, job, leaseId, selected, flow, error);
}
}
-async function freshNativePrepare(proof, leaseId) {
- await awaitNativeReady();
- if (activeStartLease !== leaseId) {
- throw fixedError("E_BUSY");
+async function acquirePageProof(session, job, leaseId, selected, flow) {
+ const remainingMs = flow.deadlineUnixMs - Date.now();
+ if (remainingMs <= 0) {
+ throw pageReadinessError(
+ "E_PAGE_IDENTITY_UNAVAILABLE", 0, flow.startedAtUnixMs,
+ "IDENTITY_PENDING", "PAGE_URL_PENDING"
+ );
}
- preparedTaskNonce = null;
- preparedLeaseId = null;
- const prepareId = randomNonce();
- prepareResponses.delete(prepareId);
- nativePort.postMessage({
- schema: SCHEMA,
- type: "prepare",
- extension_build: EXTENSION_BUILD,
- target: TARGET,
- prepare_id: prepareId,
- page_proof: proof
- });
- const deadline = Date.now() + 120000;
- while (!prepareResponses.has(prepareId) && Date.now() < deadline) {
- await new Promise(resolve => setTimeout(resolve, 25));
+ const initialBudgetMs = flow.foregroundUsed ? remainingMs :
+ Math.min(PAGE_EXTENSION_ACTIVATION_GRACE_MS, remainingMs);
+ try {
+ return await observe(job, selected.binding, initialBudgetMs);
+ } catch (error) {
+ if (flow.foregroundUsed || !foregroundFallbackEligible(error)) throw error;
+ const rebound = await foregroundAndRebind(session, job, leaseId, selected, flow, error);
+ const finalBudgetMs = flow.deadlineUnixMs - Date.now();
+ if (finalBudgetMs <= 0) throw error;
+ return observe(job, rebound.binding, finalBudgetMs);
}
- const response = prepareResponses.get(prepareId);
- prepareResponses.delete(prepareId);
- if (!response || response.phase !== "READY" || response.error_code) {
- throw fixedError(response?.error_code || "E_PREPARE");
- }
- preparedTaskNonce = proof.task_nonce;
- preparedLeaseId = leaseId;
- return prepareId;
}
-async function currentCookieStore(tabId) {
- const stores = await chrome.cookies.getAllCookieStores();
- const matches = stores.filter(store => Array.isArray(store.tabIds) && store.tabIds.includes(tabId));
- if (matches.length !== 1 || !/^[0-9]{1,8}$/.test(matches[0].id)) {
- throw fixedError("E_SECRET_INPUT");
+function injectedObservation(job, taskNonce) {
+ const path = `/video/${job.bvid}`;
+ if (location.href === "about:blank" ||
+ (typeof document.readyState === "string" && document.readyState !== "complete")) {
+ return {status: "PENDING", terminal_error_code: "E_PAGE_IDENTITY_UNAVAILABLE",
+ state: "IDENTITY_PENDING", reason: "PAGE_URL_PENDING"};
}
- return matches[0].id;
-}
-
-function projectCookie(cookie, storeId) {
- if (cookie.partitionKey !== undefined) {
- throw fixedError("E_SECRET_INPUT");
+ const documentVisibilityDiagnostic = document.visibilityState === "visible" ?
+ "DOCUMENT_VISIBILITY_VISIBLE" : "DOCUMENT_VISIBILITY_HIDDEN";
+ const documentFocusDiagnostic = typeof document.hasFocus !== "function" ?
+ "DOCUMENT_FOCUS_UNAVAILABLE" :
+ (document.hasFocus() === true ? "DOCUMENT_FOCUS_TRUE" : "DOCUMENT_FOCUS_FALSE");
+ if (location.origin !== "https://www.bilibili.com") {
+ return {status: "REJECT", error_code: "E_PAGE_PROOF",
+ state: "PAGE_REJECTED", reason: "PAGE_ORIGIN_REJECTED"};
}
- if (!cookie.name || !cookie.value) {
- return null;
+ const observedBvid = /^\/video\/(BV1[1-9A-HJ-NP-Za-km-z]{9})\/?$/.exec(location.pathname)?.[1] ?? null;
+ if (observedBvid !== null && observedBvid !== job.bvid) {
+ return {status: "REJECT", error_code: "E_PAGE_PROOF",
+ state: "PAGE_REJECTED", reason: "PAGE_BVID_REJECTED"};
}
- const session = Boolean(cookie.session);
- const expiration = session ? null : Math.floor(cookie.expirationDate);
- if (!session && (!Number.isSafeInteger(expiration) || expiration <= 0)) {
- throw fixedError("E_SECRET_INPUT");
+ if ((location.pathname !== path && location.pathname !== `${path}/`) ||
+ (location.search !== "" && !/^\?vd_source=[0-9a-f]{32}$/.test(location.search)) ||
+ location.hash !== "") {
+ return {status: "REJECT", error_code: "E_PAGE_PROOF",
+ state: "PAGE_REJECTED", reason: "PAGE_URL_REJECTED"};
+ }
+ if (document.querySelectorAll(
+ ".error-container,.error-body,[data-bili-access-state='denied']"
+ ).length > 0) {
+ return {status: "REJECT", error_code: "E_PAGE_ACCESS_CONTROL",
+ state: "PAGE_REJECTED", reason: "PAGE_ACCESS_REJECTED"};
+ }
+ const videos = Array.from(document.querySelectorAll("video"));
+ if (videos.length === 0) return {status: "PENDING", terminal_error_code: "E_PAGE_VIDEO_ABSENT",
+ state: "VIDEO_ABSENT", reason: "VIDEO_ELEMENT_ABSENT"};
+ if (videos.length > 1) return {status: "REJECT", error_code: "E_MULTI_PART",
+ state: "PAGE_REJECTED", reason: "PAGE_MULTIPART_REJECTED"};
+ const video = videos[0];
+ if (video.mediaKeys !== null) return {status: "REJECT", error_code: "E_DRM",
+ state: "PAGE_REJECTED", reason: "PAGE_DRM_REJECTED"};
+ let initialVideo = null;
+ let initialStateReason = "INITIAL_STATE_ASSIGNMENT_ABSENT";
+ for (const script of Array.from(document.querySelectorAll("script"))) {
+ const raw = script?.textContent;
+ if (typeof raw !== "string" || raw.length < 30 || raw.length > 2_000_000) continue;
+ const trimmed = raw.trim();
+ const assignment = /^window\.__INITIAL_STATE__\s*=\s*/.exec(trimmed);
+ if (assignment === null) continue;
+ initialStateReason = "INITIAL_STATE_JSON_INVALID";
+ let encoded = trimmed.slice(assignment[0].length);
+ const runtimeMarker = encoded.lastIndexOf(";(function(){");
+ if (runtimeMarker >= 0) encoded = encoded.slice(0, runtimeMarker);
+ else if (encoded.endsWith(";")) encoded = encoded.slice(0, -1);
+ try {
+ const parsed = JSON.parse(encoded);
+ const candidate = parsed?.videoData;
+ initialStateReason = "INITIAL_STATE_VIDEO_DATA_ABSENT";
+ if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) {
+ if (initialVideo !== null) return {status: "REJECT", error_code: "E_PAGE_PROOF",
+ state: "PAGE_REJECTED", reason: "PAGE_URL_REJECTED"};
+ initialVideo = candidate;
+ initialStateReason = "METADATA_FIELDS_PENDING";
+ }
+ } catch {}
+ }
+ const ownerAnchorIdentities = new Set();
+ const ownerProofRoots = document.querySelectorAll(
+ "#v_upinfo,.up-info-container,.up-panel-container,.video-owner"
+ );
+ for (const root of ownerProofRoots) {
+ if (root === null || typeof root.querySelectorAll !== "function") continue;
+ for (const anchor of root.querySelectorAll("a[href]")) {
+ try {
+ const href = new URL(anchor.href, location.href);
+ if (href.origin !== "https://space.bilibili.com" ||
+ !/^\/[1-9][0-9]{0,19}\/?$/.test(href.pathname)) {
+ continue;
+ }
+ const ownerUid = href.pathname.replace(/^\//, "").replace(/\/$/, "");
+ ownerAnchorIdentities.add(`https://space.bilibili.com/${ownerUid}`);
+ } catch {}
+ }
+ }
+ const canonicalOwnerUrl = `https://space.bilibili.com/${job.creator_uid}`;
+ const ownerAnchorExact = ownerAnchorIdentities.size === 1 &&
+ ownerAnchorIdentities.has(canonicalOwnerUrl);
+ const ownerAnchorReason = ownerAnchorExact ? null :
+ (ownerAnchorIdentities.size === 0 ? "OWNER_ANCHOR_ABSENT" :
+ (ownerAnchorIdentities.size === 1 ?
+ "OWNER_ANCHOR_MISMATCH" : "OWNER_ANCHOR_AMBIGUOUS"));
+ let initialMetadata = null;
+ if (initialVideo !== null) {
+ if (initialVideo.bvid !== job.bvid) return {status: "REJECT", error_code: "E_PAGE_PROOF",
+ state: "PAGE_REJECTED", reason: "PAGE_BVID_REJECTED"};
+ if (!Array.isArray(initialVideo.pages) || initialVideo.pages.length === 0) {
+ return {status: "REJECT", error_code: "E_PAGE_PROOF",
+ state: "PAGE_REJECTED", reason: "PAGE_URL_REJECTED"};
+ }
+ if (initialVideo.pages.length > 1) {
+ return {status: "REJECT", error_code: "E_MULTI_PART",
+ state: "PAGE_REJECTED", reason: "PAGE_MULTIPART_REJECTED"};
+ }
+ const initialPage = initialVideo.pages[0];
+ const initialDimension = initialVideo.dimension;
+ const pageDimension = initialPage?.dimension;
+ const positiveSafeInteger = value => typeof value === "number" &&
+ Number.isSafeInteger(value) && value > 0;
+ const positiveFiniteNumber = value => typeof value === "number" &&
+ Number.isFinite(value) && value > 0;
+ if (!initialPage || typeof initialPage !== "object" || Array.isArray(initialPage) ||
+ !Object.prototype.hasOwnProperty.call(initialPage, "cid") ||
+ !Object.prototype.hasOwnProperty.call(initialPage, "page") ||
+ !Object.prototype.hasOwnProperty.call(initialPage, "duration") ||
+ !Object.prototype.hasOwnProperty.call(initialPage, "dimension") ||
+ !initialDimension || typeof initialDimension !== "object" || Array.isArray(initialDimension) ||
+ !pageDimension || typeof pageDimension !== "object" || Array.isArray(pageDimension) ||
+ !positiveSafeInteger(initialVideo.cid) || !positiveSafeInteger(initialPage.cid) ||
+ initialPage.cid !== initialVideo.cid || !positiveSafeInteger(initialPage.page) ||
+ initialPage.page !== 1 || !positiveFiniteNumber(initialVideo.duration) ||
+ !positiveFiniteNumber(initialPage.duration) || initialPage.duration !== initialVideo.duration ||
+ !positiveSafeInteger(initialDimension.width) ||
+ !positiveSafeInteger(initialDimension.height) ||
+ !positiveSafeInteger(pageDimension.width) ||
+ !positiveSafeInteger(pageDimension.height) ||
+ pageDimension.width !== initialDimension.width ||
+ pageDimension.height !== initialDimension.height) {
+ return {status: "REJECT", error_code: "E_PAGE_PROOF",
+ state: "PAGE_REJECTED", reason: "PAGE_URL_REJECTED"};
+ }
+ const initialOwner = String(initialVideo.owner?.mid ?? "");
+ if (initialOwner !== "" && initialOwner !== job.creator_uid) {
+ return {status: "REJECT", error_code: "E_OWNER",
+ state: "PAGE_REJECTED", reason: "PAGE_OWNER_REJECTED"};
+ }
+ initialMetadata = {
+ duration: initialVideo.duration,
+ width: initialDimension.width,
+ height: initialDimension.height,
+ owner: initialOwner
+ };
+ }
+ let metadataSource = "HTML_MEDIA_ELEMENT";
+ let durationSeconds = video.duration;
+ let videoWidth = video.videoWidth;
+ let videoHeight = video.videoHeight;
+ let readyState = video.readyState;
+ if (initialMetadata?.owner === "" && !ownerAnchorExact) {
+ return {status: "PENDING", terminal_error_code: "E_PAGE_OWNER_ANCHOR_UNAVAILABLE",
+ state: "OWNER_PENDING", reason: ownerAnchorReason};
+ }
+ if (!Number.isFinite(durationSeconds) || durationSeconds <= 0 || readyState < 1 ||
+ videoWidth <= 0 || videoHeight <= 0) {
+ if (initialMetadata !== null &&
+ (initialMetadata.owner === job.creator_uid || (initialMetadata.owner === "" && ownerAnchorExact))) {
+ metadataSource = "BILIBILI_INITIAL_STATE";
+ durationSeconds = initialMetadata.duration;
+ videoWidth = initialMetadata.width;
+ videoHeight = initialMetadata.height;
+ readyState = 1;
+ } else if (!Number.isFinite(durationSeconds) || durationSeconds <= 0 || readyState < 1) {
+ if (!PAGE_METADATA_UNAVAILABLE_REASONS.has(initialStateReason)) {
+ return {status: "REJECT", error_code: "E_PAGE_PROOF",
+ state: "PAGE_REJECTED", reason: "PAGE_URL_REJECTED"};
+ }
+ return {status: "PENDING", terminal_error_code: "E_PAGE_METADATA_NOT_READY",
+ state: "METADATA_NOT_READY", reason: initialStateReason};
+ } else {
+ return {status: "PENDING", terminal_error_code: "E_PAGE_DIMENSIONS_UNAVAILABLE",
+ state: "DIMENSIONS_PENDING", reason: "VIDEO_DIMENSIONS_PENDING"};
+ }
+ }
+ if (!ownerAnchorExact && String(initialVideo?.owner?.mid ?? "") !== job.creator_uid) {
+ return {status: "PENDING", terminal_error_code: "E_PAGE_OWNER_ANCHOR_UNAVAILABLE",
+ state: "OWNER_PENDING", reason: ownerAnchorReason};
+ }
+ const durationMs = Math.round(durationSeconds * 1000);
+ const tolerance = Math.max(1500, Math.min(10000, Math.ceil(job.expected_duration_ms / 1000)));
+ if (Math.abs(durationMs - job.expected_duration_ms) > tolerance) {
+ return {status: "REJECT", error_code: "E_DURATION",
+ state: "PAGE_REJECTED", reason: "PAGE_DURATION_REJECTED"};
}
return {
- name: String(cookie.name),
- value: String(cookie.value),
- domain: String(cookie.domain),
- host_only: Boolean(cookie.hostOnly),
- path: String(cookie.path),
- secure: Boolean(cookie.secure),
- http_only: Boolean(cookie.httpOnly),
- same_site: String(cookie.sameSite || "unspecified"),
- session,
- expiration_unix: expiration,
- store_id: storeId,
- partition_key: null
+ status: "READY",
+ document_focus_diagnostic: documentFocusDiagnostic,
+ document_visibility_diagnostic: documentVisibilityDiagnostic,
+ proof: {
+ job_id: job.job_id,
+ bvid: job.bvid,
+ creator_uid: job.creator_uid,
+ canonical_url: job.canonical_url,
+ task_nonce: taskNonce,
+ observed_at_unix_ms: Date.now(),
+ metadata_source: metadataSource,
+ observed_duration_ms: durationMs,
+ video_width: videoWidth,
+ video_height: videoHeight,
+ ready_state: readyState,
+ eme_present: video.mediaKeys !== null
+ }
};
}
-async function startExactTask({retry = false} = {}) {
- if (activeStartLease !== null) {
- throw fixedError("E_BUSY");
- }
- const leaseId = randomNonce();
- activeStartLease = leaseId;
- try {
- if (retry) {
- await validateCurrentPage({leaseId});
+async function observe(job, binding, deadlineMs = PAGE_PROOF_DEADLINE_MS) {
+ const taskNonce = randomHex(16);
+ const startedAtUnixMs = Date.now();
+ const deadlineUnixMs = startedAtUnixMs + deadlineMs;
+ let pendingTerminalCode = "E_PAGE_PROOF";
+ let pendingState = "PAGE_REJECTED";
+ let pendingReason = "PAGE_IDENTITY_REJECTED";
+ let attempts = 0;
+ let stableSnapshots = 0;
+ let stableSignature = null;
+ let stableRejectSnapshots = 0;
+ let stableRejectSignature = null;
+ const deadlineCode = () => pendingTerminalCode === "E_PAGE_METADATA_NOT_READY" ?
+ "E_PAGE_METADATA_UNAVAILABLE" : pendingTerminalCode;
+ while (true) {
+ await revalidateBoundTab(binding, job.bvid);
+ const remainingBeforeInvocationMs = deadlineUnixMs - Date.now();
+ if (remainingBeforeInvocationMs <= 0) {
+ throw pageReadinessError(
+ deadlineCode(), attempts, startedAtUnixMs, pendingState, pendingReason
+ );
}
- const tab = await activeTargetTab();
- if (!currentProof || currentTabId !== tab.id || Date.now() - currentProof.observed_at_unix_ms > 60000) {
- throw fixedError("E_PAGE_PROOF");
- }
- const proof = currentProof;
- const prepareId = await freshNativePrepare(proof, leaseId);
- if (activeStartLease !== leaseId || preparedLeaseId !== leaseId || preparedTaskNonce !== proof.task_nonce) {
- throw fixedError("E_PREPARE");
- }
- const storeId = await currentCookieStore(tab.id);
- const rawCookies = await chrome.cookies.getAll({url: "https://www.bilibili.com/", storeId});
- let records = [];
+ const invocationBudgetMs = Math.min(PAGE_PROOF_INVOCATION_TIMEOUT_MS, remainingBeforeInvocationMs);
+ let deadlineTimer = null;
+ let results;
+ attempts += 1;
try {
- records = rawCookies.map(cookie => projectCookie(cookie, storeId)).filter(Boolean);
- if (records.length < 1 || records.length > 128) {
- throw fixedError("E_SECRET_INPUT");
+ results = await Promise.race([
+ chromeApiCall(chrome.scripting, "executeScript", [{
+ target: {tabId: binding.tabId, frameIds: [0]}, world: "ISOLATED", func: injectedObservation,
+ args: [job, taskNonce]
+ }], "E_PAGE_IDENTITY_UNAVAILABLE", "SCRIPTING_EXECUTE_FAILED"),
+ new Promise((resolve, reject) => {
+ deadlineTimer = setTimeout(() => reject(new Error("E_PAGE_SCRIPT_TIMEOUT")), invocationBudgetMs);
+ })
+ ]);
+ } catch (error) {
+ if (error?.message === "E_PAGE_SCRIPT_TIMEOUT") {
+ pendingTerminalCode = "E_PAGE_SCRIPT_TIMEOUT";
+ pendingState = "SCRIPT_TIMEOUT";
+ pendingReason = "SCRIPT_INVOCATION_FAILED";
+ } else {
+ pendingTerminalCode = "E_PAGE_IDENTITY_UNAVAILABLE";
+ pendingState = "IDENTITY_PENDING";
+ pendingReason = "PAGE_SCRIPTING_PENDING";
}
- nativePort.postMessage({
- schema: SCHEMA,
- type: "start",
- extension_build: EXTENSION_BUILD,
- target: TARGET,
- canonical_url: CANONICAL_URL,
- cookie_store_id: storeId,
- prepare_id: prepareId,
- page_proof: proof,
- cookies: records
- });
- currentTaskNonce = proof.task_nonce;
- preparedTaskNonce = null;
- preparedLeaseId = null;
- safeState = {...safeState, phase: "CHECKING", progress: 0, error_code: null};
- currentProof = null;
- currentTabId = null;
- return safeState;
+ stableSnapshots = 0;
+ stableSignature = null;
+ stableRejectSnapshots = 0;
+ stableRejectSignature = null;
+ continue;
} finally {
- for (const record of records) {
- if (record) {
- record.name = "";
- record.value = "";
- }
+ if (deadlineTimer !== null) clearTimeout(deadlineTimer);
+ }
+ if (Date.now() >= deadlineUnixMs) {
+ throw pageReadinessError(
+ deadlineCode(), attempts, startedAtUnixMs, pendingState, pendingReason
+ );
+ }
+ if (!Array.isArray(results) || results.length !== 1 || !isPlainObject(results[0]) ||
+ !isPlainObject(results[0].result)) {
+ pendingTerminalCode = "E_PAGE_IDENTITY_UNAVAILABLE";
+ pendingState = "IDENTITY_PENDING";
+ pendingReason = "PAGE_DOM_IDENTITY_PENDING";
+ stableSnapshots = 0;
+ stableSignature = null;
+ stableRejectSnapshots = 0;
+ stableRejectSignature = null;
+ continue;
+ }
+ const result = results[0].result;
+ if (result.status === "READY" &&
+ exactKeys(result, ["document_focus_diagnostic", "document_visibility_diagnostic", "proof", "status"]) &&
+ DOCUMENT_FOCUS_DIAGNOSTICS.has(result.document_focus_diagnostic) &&
+ DOCUMENT_VISIBILITY_DIAGNOSTICS.has(result.document_visibility_diagnostic) &&
+ exactKeys(result.proof, PAGE_PROOF_KEYS)) {
+ if (result.document_focus_diagnostic !== "DOCUMENT_FOCUS_TRUE") {
+ recordExtensionDiagnostic(result.document_focus_diagnostic);
}
- records.fill(null);
- rawCookies.fill(null);
+ if (result.document_visibility_diagnostic !== "DOCUMENT_VISIBILITY_VISIBLE") {
+ recordExtensionDiagnostic(result.document_visibility_diagnostic);
+ }
+ const signature = JSON.stringify(PAGE_PROOF_KEYS.filter(
+ key => key !== "observed_at_unix_ms" && key !== "task_nonce"
+ ).map(key => result.proof[key]));
+ stableSnapshots = signature === stableSignature ? stableSnapshots + 1 : 1;
+ stableSignature = signature;
+ stableRejectSnapshots = 0;
+ stableRejectSignature = null;
+ if (stableSnapshots >= PAGE_PROOF_STABLE_SNAPSHOTS) return result.proof;
+ pendingTerminalCode = "E_PAGE_STABILITY_TIMEOUT";
+ pendingState = "READY_UNSTABLE";
+ pendingReason = "STABLE_SNAPSHOTS_PENDING";
+ } else if (result.status === "REJECT" &&
+ exactKeys(result, ["error_code", "reason", "state", "status"]) &&
+ PREPARELESS_REJECT_CODES.has(result.error_code) &&
+ isCanonicalPreparelessTerminal(result.error_code, {
+ attempts, elapsed_ms: Math.max(0, Math.min(7200000, Date.now() - startedAtUnixMs)),
+ state: result.state, reason: result.reason
+ })) {
+ const stableIdentityReject = new Set([
+ "E_OWNER", "E_PAGE_ACCESS_CONTROL", "E_PAGE_PROOF"
+ ]).has(result.error_code);
+ if (!stableIdentityReject) {
+ throw pageReadinessError(
+ result.error_code, attempts, startedAtUnixMs, result.state, result.reason
+ );
+ }
+ pendingTerminalCode = result.error_code;
+ pendingState = result.state;
+ pendingReason = result.reason;
+ stableSnapshots = 0;
+ stableSignature = null;
+ const rejectSignature = `${result.error_code}\0${result.state}\0${result.reason}`;
+ stableRejectSnapshots = rejectSignature === stableRejectSignature ?
+ stableRejectSnapshots + 1 : 1;
+ stableRejectSignature = rejectSignature;
+ if (stableRejectSnapshots >= PAGE_PROOF_STABLE_SNAPSHOTS) {
+ throw pageReadinessError(
+ result.error_code, attempts, startedAtUnixMs, result.state, result.reason
+ );
+ }
+ } else if (result.status !== "PENDING" ||
+ !exactKeys(result, ["reason", "state", "status", "terminal_error_code"]) ||
+ !PAGE_PENDING_TERMINAL_CONTRACT.has(result.terminal_error_code)) {
+ throw new Error("E_PAGE_PROOF");
+ } else {
+ const [expectedState, allowedReasons] = PAGE_PENDING_TERMINAL_CONTRACT.get(result.terminal_error_code);
+ if (!PAGE_PENDING_STATES.has(result.state) || result.state !== expectedState ||
+ !PAGE_DIAGNOSTIC_REASONS.has(result.reason) || !allowedReasons.has(result.reason)) {
+ throw new Error("E_PAGE_PROOF");
+ }
+ pendingTerminalCode = result.terminal_error_code;
+ pendingState = result.state;
+ pendingReason = result.reason;
+ stableSnapshots = 0;
+ stableSignature = null;
+ stableRejectSnapshots = 0;
+ stableRejectSignature = null;
}
- } finally {
- if (activeStartLease === leaseId) {
- activeStartLease = null;
+ const remainingMs = deadlineUnixMs - Date.now();
+ if (remainingMs <= 0) {
+ throw pageReadinessError(
+ deadlineCode(), attempts, startedAtUnixMs, pendingState, pendingReason
+ );
}
+ await new Promise(resolve => setTimeout(resolve, Math.min(PAGE_PROOF_POLL_MS, remainingMs)));
}
}
-async function requestStatus() {
- await awaitNativeReady();
- return freshNativeStatus();
-}
-
-async function cancelTask() {
- if (!currentTaskNonce) {
- throw fixedError("E_CANCEL");
- }
- await awaitNativeReady();
- nativePort.postMessage({schema: SCHEMA, type: "cancel", target: TARGET, task_nonce: currentTaskNonce});
- return safeState;
-}
-
-chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
- if (sender.id !== chrome.runtime.id || sender.url !== SIDEPANEL_URL || !message || typeof message.action !== "string") {
- return false;
- }
- const actions = {
- validate: () => validateCurrentPage(),
- start: () => startExactTask(),
- retry: () => startExactTask({retry: true}),
- status: () => requestStatus(),
- cancel: () => cancelTask()
+function normalizeCookie(cookie, storeId) {
+ return {
+ name: cookie.name,
+ value: cookie.value,
+ domain: cookie.domain,
+ host_only: cookie.hostOnly,
+ path: cookie.path,
+ secure: cookie.secure,
+ http_only: cookie.httpOnly,
+ same_site: cookie.sameSite,
+ session: cookie.session,
+ expiration_unix: cookie.session === true ? null : Math.floor(cookie.expirationDate),
+ store_id: cookie.storeId,
+ partition_key: cookie.partitionKey === undefined ? null : cookie.partitionKey
};
- const action = actions[message.action];
- if (!action) {
- sendResponse({ok: false, error_code: "E_PROTOCOL"});
+}
+
+function cookiePathMatches(path, bvid) {
+ const wanted = `/video/${bvid}`;
+ return path === "/" || (wanted.startsWith(path) &&
+ (path.endsWith("/") || path.length === wanted.length || wanted[path.length] === "/"));
+}
+
+function safeCookieString(value, minimum, maximumUtf8) {
+ if (typeof value !== "string" || COOKIE_CONTROL_RE.test(value)) return false;
+ const bytes = new TextEncoder().encode(value).length;
+ return bytes >= minimum && bytes <= maximumUtf8;
+}
+
+function validBrowserCookie(cookie, storeId, proof, bvid, partitioned) {
+ return exactKeys(cookie, COOKIE_KEYS) &&
+ safeCookieString(cookie.name, 1, 256) && safeCookieString(cookie.value, 1, 4096) &&
+ typeof cookie.host_only === "boolean" &&
+ cookie.domain === (cookie.host_only ? "www.bilibili.com" : ".bilibili.com") &&
+ safeCookieString(cookie.path, 1, 1024) &&
+ cookie.path.startsWith("/") && cookiePathMatches(cookie.path, bvid) &&
+ typeof cookie.secure === "boolean" && typeof cookie.http_only === "boolean" &&
+ typeof cookie.session === "boolean" && COOKIE_SAME_SITE.has(cookie.same_site) &&
+ STORE_ID_RE.test(cookie.store_id) && cookie.store_id === storeId &&
+ (partitioned ? validPartitionKeyDescriptor(cookie.partition_key) : cookie.partition_key === null) &&
+ (cookie.session
+ ? cookie.expiration_unix === null
+ : Number.isSafeInteger(cookie.expiration_unix) && cookie.expiration_unix >= 1 &&
+ cookie.expiration_unix > Math.floor(proof.observed_at_unix_ms / 1000));
+}
+
+function validCookie(cookie, storeId, proof, bvid) {
+ return validBrowserCookie(cookie, storeId, proof, bvid, false);
+}
+
+function validCookieSet(cookies, storeId, proof, bvid) {
+ return Array.isArray(cookies) && cookies.length >= 1 && cookies.length <= MAX_COOKIE_COUNT &&
+ cookies.every(cookie => validCookie(cookie, storeId, proof, bvid));
+}
+
+function validPartitionKeyDescriptor(value) {
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
+ const keys = Object.keys(value).sort().join("\0");
+ if (!["", "hasCrossSiteAncestor", "topLevelSite", "hasCrossSiteAncestor\0topLevelSite"].includes(keys)) {
return false;
}
- action().then(
- state => sendResponse({ok: true, state}),
- error => sendResponse({ok: false, error_code: error.code || "E_EXTENSION"})
+ if (Object.prototype.hasOwnProperty.call(value, "hasCrossSiteAncestor") &&
+ typeof value.hasCrossSiteAncestor !== "boolean") return false;
+ if (!Object.prototype.hasOwnProperty.call(value, "topLevelSite")) return true;
+ if (!safeCookieString(value.topLevelSite, 1, 2048)) return false;
+ try {
+ const site = new URL(value.topLevelSite);
+ return (site.protocol === "https:" || site.protocol === "http:") &&
+ site.origin === value.topLevelSite && site.username === "" && site.password === "" &&
+ site.pathname === "/" && site.search === "" && site.hash === "";
+ } catch {
+ return false;
+ }
+}
+
+function validRawSessionExpiration(cookie, proof) {
+ if (typeof cookie.session !== "boolean") return false;
+ if (cookie.session) return cookie.expirationDate === undefined;
+ if (typeof cookie.expirationDate !== "number" || !Number.isFinite(cookie.expirationDate)) return false;
+ const expirationUnix = Math.floor(cookie.expirationDate);
+ return Number.isSafeInteger(expirationUnix) && expirationUnix >= 1 &&
+ expirationUnix > Math.floor(proof.observed_at_unix_ms / 1000);
+}
+
+function selectedCookieRelevance(cookie, storeId, bvid, proof) {
+ if (cookie === null || typeof cookie !== "object" || Array.isArray(cookie) ||
+ !safeCookieString(cookie.name, 1, 256)) return "AMBIGUOUS";
+ if (!TRANSFER_COOKIE_NAMES.has(cookie.name)) return "IRRELEVANT_NAME";
+ if (cookie.partitionKey !== undefined) {
+ if (!validRawSessionExpiration(cookie, proof)) return "AMBIGUOUS";
+ let normalized = null;
+ try {
+ normalized = normalizeCookie(cookie, storeId);
+ return validBrowserCookie(normalized, storeId, proof, bvid, true)
+ ? "IRRELEVANT_PARTITIONED" : "AMBIGUOUS";
+ } catch {
+ return "AMBIGUOUS";
+ } finally {
+ if (normalized !== null) {
+ normalized.name = "";
+ normalized.value = "";
+ }
+ }
+ }
+ return "SELECTED";
+}
+
+function selectTransferCookies(rawCookies, storeId, proof, bvid) {
+ const selected = [];
+ const requiredNames = new Set();
+ if (!Array.isArray(rawCookies)) return {ok: false, reason: "COOKIE_API_SHAPE", cookies: selected};
+ if (rawCookies.length > MAX_COOKIE_COUNT) {
+ return {ok: false, reason: "COOKIE_TRANSFER_OVERFLOW", cookies: selected};
+ }
+ for (const rawCookie of rawCookies) {
+ const relevance = selectedCookieRelevance(rawCookie, storeId, bvid, proof);
+ if (relevance === "IRRELEVANT_NAME" || relevance === "IRRELEVANT_PARTITIONED") continue;
+ if (relevance !== "SELECTED") {
+ return {ok: false, reason: "COOKIE_RELEVANCE_AMBIGUOUS", cookies: selected};
+ }
+ const cookie = normalizeCookie(rawCookie, storeId);
+ selected.push(cookie);
+ if (!validCookie(cookie, storeId, proof, bvid)) {
+ return {ok: false, reason: "COOKIE_SELECTED_INVALID", cookies: selected};
+ }
+ if (REQUIRED_AUTH_COOKIE_NAMES.has(cookie.name)) requiredNames.add(cookie.name);
+ }
+ if (selected.length === 0) return {ok: false, reason: "COOKIE_AUTH_SET_EMPTY", cookies: selected};
+ for (const requiredName of REQUIRED_AUTH_COOKIE_NAMES) {
+ if (!requiredNames.has(requiredName)) {
+ return {ok: false, reason: "COOKIE_AUTH_SET_EMPTY", cookies: selected};
+ }
+ }
+ const pathSpecificity = cookie => new TextEncoder().encode(cookie.path).length;
+ const bestSpecificity = Math.max(...selected.map(pathSpecificity));
+ const highestPrecedence = selected.filter(cookie => pathSpecificity(cookie) === bestSpecificity);
+ const firstValue = highestPrecedence[0].value;
+ if (highestPrecedence.some(cookie => cookie.value !== firstValue)) {
+ return {ok: false, reason: "COOKIE_EQUAL_PRECEDENCE_CONFLICT", cookies: selected};
+ }
+ highestPrecedence.sort((left, right) =>
+ Number(right.host_only) - Number(left.host_only) ||
+ left.domain.localeCompare(right.domain) || left.path.localeCompare(right.path) ||
+ left.same_site.localeCompare(right.same_site) || Number(right.secure) - Number(left.secure) ||
+ Number(right.http_only) - Number(left.http_only) || Number(right.session) - Number(left.session)
);
- return true;
+ const chosen = highestPrecedence[0];
+ for (const cookie of selected) {
+ if (cookie !== chosen) {
+ cookie.name = "";
+ cookie.value = "";
+ }
+ }
+ const transfer = [chosen];
+ selected.length = 0;
+ const valid = validCookieSet(transfer, storeId, proof, bvid);
+ return {ok: valid, reason: valid ? "COOKIE_TRANSFER_READY" : "COOKIE_TRANSFER_INVALID", cookies: transfer};
+}
+
+function zeroizeCookies(rawCookies, cookies) {
+ for (const cookie of Array.isArray(cookies) ? cookies : []) {
+ cookie.name = "";
+ cookie.value = "";
+ }
+ for (const cookie of Array.isArray(rawCookies) ? rawCookies : []) {
+ if (cookie !== null && typeof cookie === "object") {
+ try { cookie.name = ""; } catch {}
+ try { cookie.value = ""; } catch {}
+ }
+ }
+ if (Array.isArray(cookies)) cookies.length = 0;
+ if (Array.isArray(rawCookies)) rawCookies.length = 0;
+}
+
+async function abortPreparedForCookieAccess(session, job, leaseId, prepareId, rawCookies, cookies, reason) {
+ lastCookieAccessReason = COOKIE_ACCESS_REASONS.has(reason) ? reason : "COOKIE_SELECTION_EXCEPTION";
+ zeroizeCookies(rawCookies, cookies);
+ const aborted = await session.send({
+ schema: SCHEMA, type: "abort_prepare", job_id: job.job_id, lease_id: leaseId,
+ prepare_id: prepareId, error_code: "E_COOKIE_ACCESS", error_reason: lastCookieAccessReason
+ });
+ if (!(await validateResponse(aborted, "abort_prepare")) || aborted.phase !== "FAILED" ||
+ aborted.error_code !== "E_COOKIE_ACCESS" || aborted.prepare_id !== prepareId ||
+ aborted.job?.job_id !== job.job_id || aborted.lease_id !== leaseId) {
+ throw new Error("E_PREPARE");
+ }
+ return aborted;
+}
+
+async function runJobCore(session, job, leaseId, lifecycle) {
+ lastTabDriftReason = null;
+ lastCookieAccessReason = null;
+ lastPageReadinessDiagnostic = null;
+ let selectedTab;
+ let proof;
+ const flowStartedAtUnixMs = Date.now();
+ const foregroundFlow = {
+ startedAtUnixMs: flowStartedAtUnixMs,
+ deadlineUnixMs: flowStartedAtUnixMs + PAGE_PROOF_DEADLINE_MS,
+ foregroundUsed: false
+ };
+ try {
+ selectedTab = await targetTab(session, job, leaseId, foregroundFlow);
+ lifecycle.binding = selectedTab.binding;
+ proof = await acquirePageProof(session, job, leaseId, selectedTab, foregroundFlow);
+ await revalidateBoundTab(selectedTab.binding, job.bvid);
+ } catch (error) {
+ const code = typeof error?.message === "string" && PREPARELESS_REJECT_CODES.has(error.message)
+ ? error.message : null;
+ if (code === null) throw error;
+ const candidate = isPageReadinessDiagnostic(error?.page_readiness_diagnostic)
+ ? error.page_readiness_diagnostic
+ : isPageReadinessDiagnostic(lastPageReadinessDiagnostic)
+ ? lastPageReadinessDiagnostic
+ : fallbackPageReadinessDiagnostic(code);
+ const diagnostic = isCanonicalPreparelessTerminal(code, candidate)
+ ? candidate : fallbackPageReadinessDiagnostic(code);
+ if (!isCanonicalPreparelessTerminal(code, diagnostic)) throw new Error("E_REJECT");
+ const rejected = await session.send({
+ schema: SCHEMA, type: "reject", job_id: job.job_id, lease_id: leaseId,
+ error_code: code, diagnostic
+ });
+ if (!(await validateResponse(rejected, "reject")) || rejected.phase !== "FAILED" ||
+ rejected.error_code !== code || rejected.job?.job_id !== job.job_id || rejected.lease_id !== leaseId) {
+ throw new Error("E_REJECT");
+ }
+ return rejected;
+ }
+ const prepareId = randomHex(16);
+ const prepared = await session.send({
+ schema: SCHEMA, type: "prepare", extension_build: EXTENSION_BUILD,
+ lease_id: leaseId, prepare_id: prepareId, job, page_proof: proof
+ }, 150000);
+ const preparedValid = await validateResponse(prepared, "prepare");
+ if (preparedValid && prepared.phase === "COMPLETE" && prepared.prepare_id === prepareId &&
+ prepared.job?.job_id === job.job_id && prepared.lease_id === leaseId &&
+ prepared.progress === 100 && prepared.error_code === null &&
+ prepared.formal_filename === `${job.bvid}.mkv` &&
+ prepared.mapping_filename === `${job.bvid}.download.json`) {
+ return prepared;
+ }
+ if (!preparedValid || prepared.phase !== "READY" || prepared.prepare_id !== prepareId) {
+ const code = preparedValid && prepared.phase === "FAILED" && ERROR_CODE_RE.test(prepared.error_code || "")
+ ? prepared.error_code : "E_PREPARE";
+ throw new Error(code);
+ }
+ const storeId = selectedTab.binding.cookieStoreId;
+ let rawCookies = [];
+ let cookies = [];
+ try {
+ rawCookies = await chrome.cookies.getAll({
+ url: canonicalUrl(job.bvid),
+ name: AUTH_COOKIE_NAME,
+ storeId
+ });
+ } catch {
+ return abortPreparedForCookieAccess(session, job, leaseId, prepareId, rawCookies, cookies, "COOKIE_API_ERROR");
+ }
+ let selection;
+ try {
+ selection = selectTransferCookies(rawCookies, storeId, proof, job.bvid);
+ } catch {
+ return abortPreparedForCookieAccess(
+ session, job, leaseId, prepareId, rawCookies, cookies, "COOKIE_SELECTION_EXCEPTION"
+ );
+ }
+ cookies = selection.cookies;
+ if (!selection.ok) {
+ return abortPreparedForCookieAccess(
+ session, job, leaseId, prepareId, rawCookies, cookies, selection.reason
+ );
+ }
+ let started;
+ try {
+ started = await session.send({
+ schema: SCHEMA, type: "start", extension_build: EXTENSION_BUILD,
+ lease_id: leaseId, prepare_id: prepareId, job, cookie_store_id: storeId,
+ page_proof: proof, cookies
+ }, 30000);
+ } finally {
+ zeroizeCookies(rawCookies, cookies);
+ }
+ if (!(await validateResponse(started, "status")) || started.job?.job_id !== job.job_id) {
+ throw new Error("E_START");
+ }
+ let state = started;
+ while (!["COMPLETE", "FAILED", "POSTPROCESS_FAILED", "CANCELED"].includes(state.phase)) {
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ state = await session.send({schema: SCHEMA, type: "status", job_id: job.job_id, lease_id: leaseId});
+ if (!(await validateResponse(state, "status")) || state.job?.job_id !== job.job_id) throw new Error("E_STATUS");
+ }
+ return state;
+}
+
+async function runJob(session, job, leaseId) {
+ const lifecycle = {binding: null};
+ try {
+ return await runJobCore(session, job, leaseId, lifecycle);
+ } finally {
+ // The durable session record is the authority. This also closes a tab when
+ // a restart/rebind path lost its in-memory binding after Host start.
+ await cleanupOwnedTab();
+ }
+}
+
+async function drainQueue() {
+ if (drainLease) return;
+ drainLease = true;
+ lastDrainErrorCode = null;
+ let session;
+ try {
+ session = nativeSession();
+ const hello = await session.send({schema: SCHEMA, type: "hello", extension_build: EXTENSION_BUILD});
+ const helloValid = await validateResponse(hello, "hello");
+ if (!helloValid) throw new Error("E_HELLO");
+ if (hello.phase === "FAILED") throw new Error(hello.error_code || "E_HOST");
+ if (hello.maintenance.reload_required) {
+ const token = hello.maintenance.reload_token;
+ if (!HEX32_RE.test(token || "")) return;
+ const response = await session.send({schema: SCHEMA, type: "reload_begin", extension_build: EXTENSION_BUILD, reload_token: token});
+ if (!(await validateResponse(response, "reload_begin")) || response.phase !== "RELOAD_REQUIRED") return;
+ session.disconnect();
+ session = null;
+ chrome.runtime.reload();
+ return;
+ }
+ const polled = await session.send({schema: SCHEMA, type: "poll"});
+ const pollValid = await validateResponse(polled, "poll");
+ if (!pollValid) throw new Error("E_POLL");
+ const owned = await loadOwnedTabRecord();
+ const continuesOwnedJob = polled.phase === "READY" && polled.job && polled.lease_id &&
+ owned !== null && owned.job_id === polled.job.job_id && owned.lease_id === polled.lease_id;
+ if (owned !== null && !continuesOwnedJob) await cleanupOwnedTab(owned);
+ if (["FAILED", "POSTPROCESS_FAILED"].includes(polled.phase)) {
+ throw new Error(polled.error_code || "E_HOST");
+ }
+ if (polled.phase === "COMPLETE") {
+ if (!polled.job || !polled.lease_id || polled.progress !== 100 || polled.error_code !== null ||
+ polled.formal_filename !== `${polled.job.bvid}.mkv` ||
+ polled.mapping_filename !== `${polled.job.bvid}.download.json`) {
+ throw new Error("E_POLL");
+ }
+ return;
+ }
+ if (polled.phase !== "READY" || !polled.job || !polled.lease_id) return;
+ await runJob(session, polled.job, polled.lease_id);
+ } catch (error) {
+ // Memory-only safe code: never persist secrets, URLs, protocol payloads, or Host diagnostics here.
+ lastDrainErrorCode = safeErrorCode(error);
+ } finally {
+ if (session) session.disconnect();
+ drainLease = false;
+ }
+}
+
+function schedule() {
+ chrome.alarms.create("bili-auth-queue", {periodInMinutes: 1});
+ void drainQueue();
+}
+
+chrome.runtime.onInstalled.addListener(schedule);
+chrome.runtime.onStartup.addListener(schedule);
+chrome.alarms.onAlarm.addListener(alarm => {
+ if (alarm?.name === "bili-auth-queue") void drainQueue();
});
--
Gitblit v1.9.3