Skip to content

Commit 98731db

Browse files
committed
feat: PWA service worker 拦截主文档时用 HEAD fetch 兜底拉响应头
诊断 nodeseek.com 响应头一直为 0 时跑了一段临时 webRequest listener,硬刷新页面后 console 输出全是 type=xmlhttprequest tabId=-1,**没有 main_frame**。这说明 nodeseek 注册了网站自己的 service worker(PWA),主文档 navigation 被 SW 的 fetch handler 拦截后用 cache 返回——chrome.webRequest 文档明确写过:当 service worker 用 respondWith 返回非网络响应时,onHeadersReceived 不触发。这是 chrome 平台行为,不是我们代码问题。 不能改 chrome,只能扩展自己兜底拉响应头。新增 fetchMainHeadersFallback: fetch(url, { method: 'HEAD', credentials: 'omit', cache: 'no-store', redirect: 'follow' }) 扩展 service worker 在扩展上下文跑,发起的 fetch 不会被网站 service worker 拦截(两个 SW 隔离),直接走真实网络。HEAD 请求只取 header 不要 body,省带宽。如果 HEAD 返回非 200/405(少数站不支持 HEAD),降级到 GET 重试,依然只读 header 不读 body 让 GC 回收。 response.headers.forEach 转成 chrome.webRequest details 兼容的 [{name,value}] 数组,喂给现有 buildHeaderRecord 复用 normalizeHeaders / pickHeaders / detectFromHeaders 整套链路,输出与正常 webRequest 路径完全一致的 record,包括 normalizedHeaderCount 字段。 调用时机放在 runActivePageDetection 里 page-detector 跑完之后:检查 data.main 是不是空,空就拿 page.url 去 fallback fetch 一次。这样: - webRequest 正常拿到响应头的站(绝大多数):data.main 已存在,跳过 fallback,零额外请求 - service worker 拦截主文档的 PWA(nodeseek / Twitter Web App / 一些 vue-pwa 站):data.main 为空,触发 HEAD fetch 兜底,用户看到的响应头数从 0 变成真实数字 - credentials: 'omit' 不带页面 cookie,可能与浏览器实际请求看到的响应头有细微差异(比如登录态相关的 Set-Cookie),但 server / x-powered-by / cf-ray / content-type 等共性技术线索 header 是一致的,足够识别用 插件版本升级到 1.1.4。
1 parent a03ab7f commit 98731db

3 files changed

Lines changed: 34 additions & 1 deletion

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "stackprism",
33
"private": true,
4-
"version": "1.1.3",
4+
"version": "1.1.4",
55
"type": "module",
66
"description": "StackPrism 用于检测网页前端、后端、CDN、SaaS、广告营销、统计、登录、支付、网站程序和主题模板线索。",
77
"scripts": {

src/background/detection.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { augmentPageWithWordPressThemeStyles } from './wordpress'
22
import { buildPopupCacheRecord, cleanPageDetectionRecord } from './popup-cache'
3+
import { fetchMainHeadersFallback } from './headers'
34
import { clearBadge, getTabData, getTabSnapshot, updateBadgeForTab, writeTabData } from './tab-store'
45
import { buildEffectivePageRules, loadDetectorSettings, loadTechRules } from './detector-settings'
56

@@ -53,6 +54,12 @@ export const runActivePageDetection = async (tabId: number) => {
5354

5455
const augmentedPage = await augmentPageWithWordPressThemeStyles(page)
5556
data.page = cleanPageDetectionRecord(augmentedPage)
57+
58+
if (!data.main) {
59+
const fallback = await fetchMainHeadersFallback((page as any).url || '', rules.headers || {}, settings)
60+
if (fallback) data.main = fallback
61+
}
62+
5663
data.updatedAt = Date.now()
5764
await saveTabDataAndBadge(tabId, data, settings)
5865
} catch {

src/background/headers.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,32 @@ const detectFromHeaders = (headers: Record<string, string>, url: string, headerR
112112
return technologies
113113
}
114114

115+
export const fetchMainHeadersFallback = async (url: string, headerRules: any, settings: any) => {
116+
if (!url || !/^https?:/i.test(url)) return null
117+
try {
118+
let response = await fetch(url, { method: 'HEAD', credentials: 'omit', cache: 'no-store', redirect: 'follow' })
119+
if (!response.ok && response.status !== 405 && response.status !== 0) {
120+
response = await fetch(url, { method: 'GET', credentials: 'omit', cache: 'no-store', redirect: 'follow' })
121+
}
122+
const responseHeaders: Array<{ name: string; value: string }> = []
123+
response.headers.forEach((value, name) => responseHeaders.push({ name, value }))
124+
if (!responseHeaders.length) return null
125+
return buildHeaderRecord(
126+
{
127+
url: response.url || url,
128+
type: 'main_frame',
129+
method: 'HEAD',
130+
statusCode: response.status,
131+
responseHeaders
132+
},
133+
headerRules,
134+
settings
135+
)
136+
} catch {
137+
return null
138+
}
139+
}
140+
115141
export const buildHeaderRecord = (details: any, headerRules: any, settings: any) => {
116142
const normalizedHeaders = normalizeHeaders(details.responseHeaders)
117143
const headers = pickHeaders(normalizedHeaders, headerRules.interestingHeaders || [])

0 commit comments

Comments
 (0)