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;
|
|
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"]
|
]);
|
|
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 recordExtensionDiagnostic(reason) {
|
const phase = EXTENSION_DIAGNOSTIC_CONTRACT.get(reason);
|
if (typeof phase !== "string") return;
|
lastExtensionDiagnostic = Object.freeze({phase, reason});
|
}
|
|
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.page_readiness_diagnostic = diagnostic;
|
return error;
|
}
|
|
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;
|
}
|
|
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
|
});
|
}
|
|
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 identityReason = tabIdentityReason(current, binding);
|
if (identityReason !== null) throw tabDriftError(identityReason);
|
if (!locationMatches(current.url, bvid)) throw tabDriftError("TAB_URL_CHANGED");
|
return current;
|
}
|
|
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"
|
);
|
}
|
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);
|
};
|
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;
|
}
|
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;
|
}
|
const remainingMs = deadlineUnixMs - Date.now();
|
if (remainingMs <= 0) break;
|
await new Promise(resolve => setTimeout(resolve, Math.min(PAGE_PROOF_POLL_MS, remainingMs)));
|
}
|
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 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"
|
);
|
}
|
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);
|
}
|
}
|
|
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"};
|
}
|
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"};
|
}
|
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"};
|
}
|
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 {
|
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 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 invocationBudgetMs = Math.min(PAGE_PROOF_INVOCATION_TIMEOUT_MS, remainingBeforeInvocationMs);
|
let deadlineTimer = null;
|
let results;
|
attempts += 1;
|
try {
|
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";
|
}
|
stableSnapshots = 0;
|
stableSignature = null;
|
stableRejectSnapshots = 0;
|
stableRejectSignature = null;
|
continue;
|
} finally {
|
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);
|
}
|
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;
|
}
|
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)));
|
}
|
}
|
|
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
|
};
|
}
|
|
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;
|
}
|
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)
|
);
|
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();
|
});
|