fix: storage不可用时用URL参数作降级标记,防无痕模式刷新死循环

AI Review 二次意见:Safari 无痕/禁用 Cookie 环境 sessionStorage 读写都抛异常,
标记写不进去,刷新后 getChunkReloadedAt 仍返回 null,会陷入
'检测到chunk失效→刷新→再检测→再刷新'死循环。

- getChunkReloadedAt 双重判定:先查 URL ?chunkreload=1 参数(刷新后仍在),
  再查 sessionStorage 标记
- reloadForChunkError 优先用 location.replace 带上 URL 标记跳转,
  URL 构造失败再退回 window.location.reload
- goHomeRecover 跳首页天然清掉 URL 参数
- 补 2 例:URL 参数判定为已刷新;storage 全不可用时走 URL 标记不崩溃
This commit is contained in:
xiaoxia
2026-09-06 13:38:06 +08:00
parent 320aa89751
commit aea012fb74
2 changed files with 49 additions and 1 deletions
@@ -77,4 +77,25 @@ describe("reload 标记", () => {
spy.mockRestore()
setSpy.mockRestore()
})
it("URL 带 chunkreload 参数时视为已刷新过(storage 不可用的降级标记)", () => {
window.history.replaceState({}, "", "/app/assets?chunkreload=1")
expect(getChunkReloadedAt()).not.toBeNull()
window.history.replaceState({}, "", "/")
})
it("storage 完全不可用时,reloadForChunkError 走 URL 标记跳转(防无痕死循环)", () => {
const setSpy = vi.spyOn(Storage.prototype, "setItem").mockImplementation(() => {
throw new Error("Storage disabled")
})
const getSpy = vi.spyOn(Storage.prototype, "getItem").mockImplementation(() => {
throw new Error("Storage disabled")
})
window.history.replaceState({}, "", "/app/assets")
// reloadForChunkError 应走 location.replace 带上 ?chunkreload=1jsdom 不会真导航)
expect(() => reloadForChunkError()).not.toThrow()
setSpy.mockRestore()
getSpy.mockRestore()
window.history.replaceState({}, "", "/")
})
})
+28 -1
View File
@@ -57,8 +57,26 @@ export const isChunkLoadError = (error: unknown): boolean => {
)
}
/** 读取上次自动刷新时间戳;过期或不存在返回 null */
/** URL 降级标记参数:storage 不可用(无痕/禁 Cookie)时靠它在刷新后保留"已刷新"状态 */
const RELOAD_QUERY_KEY = "chunkreload"
/** 判断当前 URL 是否已带"为 chunk 失效刷新过"标记 */
const urlHasReloadFlag = (): boolean => {
try {
return new URLSearchParams(window.location.search).has(RELOAD_QUERY_KEY)
} catch {
return false
}
}
/**
* 读取"已自动刷新过"状态:storage 与 URL 参数双重判定。
* storage 在 Safari 无痕等环境可能完全不可用,此时刷新后标记丢失会陷入
* "检测到 chunk 失效 → 刷新 → 再检测 → 再刷新"死循环;URL 参数刷新后仍在,
* 作为降级标记兜住这种环境。
*/
export const getChunkReloadedAt = (): number | null => {
if (urlHasReloadFlag()) return Date.now()
const raw = safeStorage.getItem(RELOAD_FLAG_KEY)
if (!raw) return null
const ts = Number(raw)
@@ -70,6 +88,15 @@ export const getChunkReloadedAt = (): number | null => {
/** 标记"已为 chunk 失效自动刷新过",然后刷新页面 */
export const reloadForChunkError = (): void => {
safeStorage.setItem(RELOAD_FLAG_KEY, String(Date.now()))
// 同时在 URL 上带标记:storage 写不进(无痕模式)时刷新后仍能识别已刷新过
try {
const url = new URL(window.location.href)
url.searchParams.set(RELOAD_QUERY_KEY, "1")
window.location.replace(url.toString())
return
} catch {
/* URL 构造失败则退回普通刷新 */
}
window.location.reload()
}