/** 虎牙浏览器兑换:与内置扩展(web/extension/huya-exchange-helper)的通信桥。 */ import type { HuyaBrowserExchangeKit } from '../api/modules'; /** 扩展注入回执 (huya-exchange-result) */ export interface ExchangeInjectResult { ok: boolean; written: number; failed: number; error: string; } /** 解析后端 Cookie 字符串为 [name, value] 数组,供兑换扩展注入 */ export function parseCookieString(cookieString: string): [string, string][] { return cookieString .split(';') .map((part) => part.trim()) .filter((part) => part.includes('=')) .map((part) => { const idx = part.indexOf('='); return [part.slice(0, idx), part.slice(idx + 1)]; }); } /** 内置扩展的内容脚本会在后台站点白名单页面(见 manifest matches)打上此标记 */ export function huyaExchangeHelperReady(): boolean { return document.documentElement.dataset.huyaExchangeHelper === '1'; } function createRequestId(): string { return `hx-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`; } /** * 请求内置扩展自动注入登录态并打开目标页,等待扩展回执(4 秒超时)。 * 同时留存到 window 供扩展图标点击兜底读取。 * 注意:事件 detail 必须是 JSON 字符串(跨 world 传递限制),不要改成对象。 */ export function requestExtensionInject(kit: HuyaBrowserExchangeKit): Promise { const requestId = createRequestId(); const payload = JSON.stringify({ request_id: requestId, target_url: kit.target_url, cookies: parseCookieString(kit.cookie_string), }); (window as unknown as { __huyaExchangeKit?: string }).__huyaExchangeKit = payload; return new Promise((resolve) => { let timer = 0; let settled = false; function finish(result: ExchangeInjectResult): void { if (settled) return; settled = true; window.clearTimeout(timer); window.removeEventListener('huya-exchange-result', onResult); resolve(result); } function onResult(event: Event): void { const detail = (event as CustomEvent).detail; if (typeof detail !== 'string') return; let parsed: { request_id?: string; ok?: boolean; written?: number; failed?: number; error?: string }; try { parsed = JSON.parse(detail) as typeof parsed; } catch { return; } if (!parsed || parsed.request_id !== requestId) return; finish({ ok: parsed.ok === true, written: typeof parsed.written === 'number' ? parsed.written : 0, failed: typeof parsed.failed === 'number' ? parsed.failed : 0, error: typeof parsed.error === 'string' ? parsed.error : '', }); } timer = window.setTimeout(() => { finish({ ok: false, written: 0, failed: 0, error: '扩展未响应(未安装或安装后未刷新本页)' }); }, 4000); window.addEventListener('huya-exchange-result', onResult); window.dispatchEvent(new CustomEvent('huya-exchange-kit', { detail: payload })); }); } /** 在新标签页打开兑换页面(noopener 避免反向标签劫持) */ export function openExchangeTarget(targetUrl: string): void { window.open(targetUrl, '_blank', 'noopener,noreferrer'); }