{"record":{"id":"868e777d027dd5e9","repo":"maboloshi/github-chinese","slug":"http-res-status-url","errorCode":null,"errorMessage":"HTTP ${res.status} ${url}","messagePattern":"HTTP \\$\\{res\\.status\\} \\$\\{url\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"vscode-extension/src/extension.ts","lineNumber":98,"sourceCode":"\nlet _statusBar: vscode.StatusBarItem;\nconst _tabs = new Map<BrowserTabLike, TabEntry>();\nlet _source: string | null = null;\n\n// ─── browser API 检测 ────────────────────────────────────────\n\nfunction isBrowserApiAvailable(): boolean {\n    const w = vscode.window as any;\n    return typeof w.browserTabs !== 'undefined'\n        && typeof w.onDidOpenBrowserTab === 'function'\n        && typeof w.onDidCloseBrowserTab === 'function';\n}\n\n// ─── 注入源码构建 ─────────────────────────────────────────────\n\nasync function fetchText(url: string): Promise<string> {\n    const res = await (globalThis as any).fetch(url);\n    if (!res.ok) { throw new Error(`HTTP ${res.status} ${url}`); }\n    return res.text();\n}\n\n/**\n * 构建注入源码：GM_* 兼容层 + 等 DOM 就绪后执行词库与主脚本（document-end 语义）。\n * 通过 CDP Page.addScriptToEvaluateOnNewDocument 直接注入（不 eval），\n * 规避 github.com CSP 对 eval 的限制（IBE 因用 new Function 被 CSP 拦截）。\n */\nasync function refreshSource(): Promise<string | null> {\n    try {\n        const [locals, main] = await Promise.all([fetchText(LOCALS_URL), fetchText(MAIN_URL)]);\n        _source = `(function () {\n'use strict';\n/* ---- GM_* 兼容层（无 IBE 时补齐页面端 GM API）---- */\nvar gm = {\n    addStyle: function (css) {\n        var el = document.createElement('style');\n        el.textContent = css;","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/maboloshi/github-chinese/blob/1db777260aeaeba52b39ebc37e1097e0c7053198/vscode-extension/src/extension.ts#L80-L116","documentation":"fetchText() is the VS Code extension's helper that pulls the translation bundle (LOCALS_URL) and the main script (MAIN_URL) from the NJU mirror over globalThis.fetch at extension-activate time, bypassing page CSP. On any non-2xx response it throws HTTP <status> <url>. refreshSource() catches this and returns null, which leaves _source null and makes the status bar show '词库 ❌ 加载失败'; the error message appears in the extension host log.","triggerScenarios":"At activation (or when the refresh command runs) one of the two mirror URLs returns non-ok: a 404 if the mirror path changed, a 5xx while the mirror is down, or fetch throws (DNS/TLS/proxy/timeout) which surfaces as a rejected promise inside refreshSource's try. The thrown HTTP string is what the catch logs.","commonSituations":"Running VS Code behind a corporate proxy without http.proxy configured; the NJU mirror being down or rate-limiting; offline/cold start with no cached _source; a mirror path rename; a self-hosted environment that cannot reach mirror.nju.edu.cn.","solutions":["Check VS Code's proxy: set 'http.proxy' and 'http.proxyStrictSSL' appropriately, then reload the window so refreshSource re-fetches.","Open https://mirror.nju.edu.cn/github-chinese/locals.js and .../main.user.js in a browser to confirm the mirror is up and the paths still resolve.","If the NJU mirror is persistently unreachable, change LOCALS_URL/MAIN_URL in extension.ts to a reachable mirror (e.g. raw.githubusercontent gh-pages) and rebuild the extension.","Run the extension's refresh command again once network is restored; injection is guarded on _source truthiness so it self-heals on a successful re-fetch."],"exampleFix":"// before\nasync function fetchText(url: string): Promise<string> {\n    const res = await (globalThis as any).fetch(url);\n    if (!res.ok) { throw new Error(`HTTP ${res.status} ${url}`); }\n    return res.text();\n}\n// after — retry transient 5xx/network errors with backoff\nasync function fetchText(url: string, tries = 3): Promise<string> {\n    let last: Error | null = null;\n    for (let i = 0; i < tries; i++) {\n        try {\n            const res = await (globalThis as any).fetch(url);\n            if (res.ok) return res.text();\n            last = new Error(`HTTP ${res.status} ${url}`);\n            if (res.status < 500) throw last; // 4xx won't fix themselves\n        } catch (e) { last = e as Error; }\n        await new Promise(r => setTimeout(r, 500 * (i + 1)));\n    }\n    throw last ?? new Error(`fetch failed ${url}`);\n}","handlingStrategy":"retry","validationCode":"// Before activation relies on the mirror, confirm reachability:\nasync function mirrorReachable(url: string): Promise<boolean> {\n    try {\n        const r = await (globalThis as any).fetch(url, { method: 'HEAD' });\n        return r.ok;\n    } catch {\n        return false;\n    }\n}\n// usage: if (!(await mirrorReachable(LOCALS_URL))) { showProxyHint(); }","typeGuard":"null","tryCatchPattern":"let src: string | null = null;\ntry {\n    src = await refreshSource();\n} catch (e) {\n    console.error('[github-chinese] source refresh failed:', e);\n    src = null;\n}\nif (!src) {\n    vscode.window.showWarningMessage('GitHub 汉化资源拉取失败，请检查网络/代理/镜像');\n    return;\n}","preventionTips":["Configure VS Code http.proxy (and http.proxyStrictSSL) in firewalled environments.","Guard every injection on _source truthiness — the code already does this at the inject site.","Add a fallback mirror URL and prefer whichever resolves first.","Pre-fetch resources on cold start and cache _source so a transient outage doesn't disable translation."],"tags":["vscode-extension","network","fetch","mirror","csp-bypass","typescript"],"backgroundTag":null,"analyzedSha":"1db777260aeaeba52b39ebc37e1097e0c7053198","analyzedAt":"2026-08-13T05:58:27.900Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}