Files
OpenFlare/docs/superpowers/specs/2026-08-08-service-worker-offline-design.md
T
ryan ca21ff3a5b feat(option): add sw offline
fix(openresty): scope sw injection per cert partition

fix(lint): satisfy revive and biome format for sw offline feature

docs: sw offline scope changelog

fix(frontend): use scoped query key for sw scope zones

fix(frontend): hide preview link in sw contact page editor

feat(frontend): add sw scope domain picker and contact page fields

refactor(frontend): generalize html editor workspace for reuse

feat(openresty): scope sw offline injection by route domains

feat(openresty): add sw offline domains snapshot field

feat(option): add sw offline domains scope option

docs: fill html editor workspace generalization detail

docs: sw offline scope implementation plan

docs: sw offline scope design

test(openresty): assert single merged access block in sw enabled servers

fix(openresty): restrict sw intercept to https server blocks

fix(openresty): version sw offline cache by html content

fix(agent): escape redir in sw challenge page to prevent xss

fix(agent): return sw.runtime module table and add lua spec

docs: sw offline fallback changelog

fix(frontend): memoize option map to preserve unsaved contact page edits

feat(frontend): add response pages module with contact page tab

feat(agent): ship sw offline lua assets and placeholder substitution

feat(config): wire sw offline options into config snapshot

feat(openresty): render sw offline assets and challenge intercept

feat(openresty): add sw offline ConfigSnapshot fields and placeholder

feat(db): seed sw offline options

feat(option): add sw offline config keys and validation

docs: add service worker offline fallback implementation plan

docs: adopt global-option pattern for SW offline fallback (matches origin error page)

docs: unify offline contact page with error pages as response pages

docs: service worker offline fallback design (issue #23)
2026-08-08 20:14:28 +08:00

5.4 KiB
Raw Blame History

Service Worker 离线兜底设计(issue #23)

  • 日期:2026-08-08
  • 状态:设计已确认
  • 范围:Proxy Route(反代)+ Pages 静态托管 全覆盖

1. 背景与目标

当 CDN 域名被墙、浏览器对所有网络请求失败时,用户会直接流失。本功能通过给网站下发 Service Worker,缓存一个"联系站长"离线页;域名被墙后,SW 从缓存吐出该页,保留用户并引导联系站长。

核心约束:

  • 平台一键批量下发,避免逐个 Agent 配置。
  • 不改源页代码,全部在 OpenResty 边缘层完成。
  • 覆盖反代(Proxy Route)与 Pages 静态托管两种网站类型。

2. 机制总览

采用「首次挑战页 + Cookie 放行 + UA 白名单」模式,替代 sub_filter 响应体重写。

环节 行为
真实浏览器 UA(含特征版本,如 Chrome/120)首次访问首页 返回 SW 挑战页(内嵌 register('/sw.js') 与离线页预缓存),设置长过期 Cookie
带 Cookie 的请求 直接放行到上游,正常返回真实页面
未知 UA(爬虫、curl,无真实浏览器特征) 直接放过,交给 WAF 处理,拿到真实内容

为什么不用 sub_filter

sub_filter 需处理上游 gzip / Content-Type / 大响应扫描 / 流式缓冲等多处坑。本方案不改上游 body,整体替换首次响应,以上问题全部规避;且爬虫(不匹配真实浏览器 UA)天然绕过挑战页,不伤 SEO。

3. 分层职责

apps/proxy_route ─┐
apps/pages        ─┼─ model → repository → 渲染(pkg/render/openresty) → Agent(OpenResty)
前端设置卡        ─┘                                              ↑ SW 挑战页 + sw.js/offline 落盘

后端数据(全局 Option,与 origin error page 同模式)

sw_offline 相关配置作为全局 SystemConfig / OpenRestyConfig snapshot 字段,对所有启用 HTTPS 的路由生效,实现"一键批量下发"。新增字段:

  • sw_offline_enabled:是否启用 SW 离线兜底
  • sw_offline_html:联系站长离线页 HTML 内容(默认提供内置模板)

渲染层(pkg/render/openresty)

新增 renderServiceWorkerChallenger(cfg ConfigSnapshot) 工具,为真实提供内容的 HTTPS server 块(sw_offline_enabled 且 EnableHTTPS 时)输出:

# SW 脚本 + 离线页(作为 support file 落盘)
location = /sw.js        { alias .../sw.js;        add_header Service-Worker-Allowed /; }
location = /offline.html { alias .../offline.html; }

# 仅首页拦截:真实浏览器 UA 且无 cookie → 返回 SW 挑战页
# 否则(带 cookie / 未知 UA)→ 放行到上游
location = / {
    if (真实浏览器UA && 无cookie) { content_by_lua 返回 SW 挑战页; }
    放行到上游;
}
  • SW 逻辑:install 阶段缓存 /offline.html;fetch 事件在网络失败时返回 caches.match('/offline.html')。
  • 仅在 EnableHTTPS 时注入(SW 要求 HTTPS 安全上下文)。
  • 多域名 server 块:/sw.js、/offline.html、挑战页在各 server_name 下同源可达。
  • 仅对首页 location = / 触发;js/css/图片/API/子页面请求不拦,零额外开销。

4. 数据流

用户首次访问首页(真实UA, 无cookie)
  → OpenResty 判断:真实UA && 无cookie
      → 返回 SW 挑战页 (内嵌 register + 预缓存 offline.html)
      → 浏览器执行 → 注册 SW → 设置长过期 cookie
  → 用户再次请求(带cookie)
      → 放行到上游,正常返回真实页面
域名被墙后
  → 所有请求失败 → SW fetch 兜底 → 从缓存返回 /offline.html(联系页)

5. 边界与风险

项 处理
首次即被墙的用户 SW 未注册,兜底无效(所有 SW 方案共性,接受)
HTTP-only 站点 跳过注入(SW 需 HTTPS)
反代多域名 各域名同源提供 sw.js / offline.html / 挑战页
Cookie 过期 设长过期(约 1 年),过期后重新走一次挑战页
未知 UA 放过并交给 WAF 处理,不重复拦截
资源/API 请求 不拦,仅首页触发

6. 测试

  • 渲染层单元测试:
    • sw_offline_enabled 时输出 sw.js / offline.html / 挑战页 location
    • 非 HTTPS 或未启用时不输出
    • 仅首页触发,子路径/资源不触发
  • UA 判定:真实浏览器 / 爬虫 / curl 三种 UA 的放行分支。
  • Cookie 有无的放行分支。
  • 现有 config snapshot checksum / rebind 测试不回归。

7. 前端命名与入口

离线联系页设置与现有 origin error page 设置合并为同一个功能模块,命名为**「响应页面」**(路由 responses),内含两个 tab:

  • 错误页设置:源站错误兜底页(现有 origin error page)
  • 联系页设置:SW 离线兜底联系页(本功能)

两者同属「边缘层兜底展示页」语义,统一管理与入口。

8. 待实现确认项(写 plan 时细化)

  • SW 挑战页与 sw.js 的具体 Lua 实现与落盘路径(对齐现有 support file 机制)。
  • sw_offline_html 默认内置模板样式(参考 origin error page 内置模板)。
  • 「响应页面」前端模块下错误页/联系页两个 tab 的具体位置与交互。
  • UA 白名单默认真实浏览器特征集合(Chrome / Firefox / Safari / Edge + 版本号正则)。
  • SW 落盘路径:sw.js / offline.html 通过 SupportFile 下发,Agent 替换占位符(类似 ErrorPageTmplPlaceholder 机制)。