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:
@@ -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=1(jsdom 不会真导航)
|
||||
expect(() => reloadForChunkError()).not.toThrow()
|
||||
setSpy.mockRestore()
|
||||
getSpy.mockRestore()
|
||||
window.history.replaceState({}, "", "/")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user