feat(huya): 浏览器兑换三目标直达并收窄扩展注入范围
- 操作列改为「直播间 / 精英宝典 / 电竞宝典」三个直达按钮,点哪个就按哪个目标写入登录态并打开,去掉弹窗内选择
- 新增 services/huya_exchange_targets.py 统一三个目标地址,接口支持可选 body {"target": "..."}(不传=精英宝典)
- 扩展内容脚本从 <all_urls> 收窄到后台站点白名单,service worker 校验 sender.tab.url,拒绝其它来源的注入
- 注入改为 Cookie 写完后回执(ok/written/failed/error),前端按回执提示成功或失败,不再静默
- 扩展图标兜底改用 world:'MAIN' 读取;一个 Cookie 都没写成功时不打开目标页
- 兑换材料签发写审计 huya:exchange:kit(含账号与目标)
- 控制台脚本增加 huya.com 域校验与 Secure,与扩展路径保持一致
- 修复兑换记录轮询的跨账号串扰与卸载后 setState;兑换请求加 epoch 防止过期结果覆盖弹窗
- 新增 tests/test_huya_browser_exchange.py(13 项)并同步 docs/HUYA_BROWSER_EXCHANGE.md
This commit is contained in:
@@ -2,6 +2,14 @@
|
||||
|
||||
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
|
||||
@@ -14,16 +22,66 @@ export function parseCookieString(cookieString: string): [string, string][] {
|
||||
});
|
||||
}
|
||||
|
||||
/** 内置扩展的内容脚本会在所有页面打上此标记 */
|
||||
/** 内置扩展的内容脚本会在后台站点白名单页面(见 manifest matches)打上此标记 */
|
||||
export function huyaExchangeHelperReady(): boolean {
|
||||
return document.documentElement.dataset.huyaExchangeHelper === '1';
|
||||
}
|
||||
|
||||
/** 请求内置扩展自动注入登录态;同时留存到 window 供扩展图标点击兜底读取 */
|
||||
export function requestExtensionInject(kit: HuyaBrowserExchangeKit): void {
|
||||
const payload = JSON.stringify({ target_url: kit.target_url, cookies: parseCookieString(kit.cookie_string) });
|
||||
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<ExchangeInjectResult> {
|
||||
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;
|
||||
window.dispatchEvent(new CustomEvent('huya-exchange-kit', { detail: payload }));
|
||||
|
||||
return new Promise<ExchangeInjectResult>((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<unknown>).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 避免反向标签劫持) */
|
||||
|
||||
Reference in New Issue
Block a user