跳至内容
WebViewGold 从 Vibe Coding 到 App Store
中文
获取 WebViewGold

ChatGPT & Codex

经得起 App 审核的离线模式和错误状态:面向 WebViewGold 应用的 Codex 工作流程

有些审核人员会打开飞行模式,而每位审核人员迟早都会遇到一个很慢的请求。封装的 Web 应用有两层离线处理:WebViewGold 负责页面根本无法加载的那一刻,而您的 Web 应用必须处理页面加载之后每一个失败的请求。Codex 非常适合第二层,因为它能列出代码库中的每一个数据调用,并用切断网络的测试验证每一种错误状态。

每个平台一次性 $109 起,或由我们的团队全程代劳,$698 起。

  • 审核通过保证 – 否则 14 天内退款
  • 无需编程
  • 一次性付费,免费更新
  • 7×24 小时人工支持
  • 2014 年起德国出品 · ISO 27001

目标与审核视角

离线问题最终都会落到准则 2.1(应用完整性)上:永远转个不停的加载图标、请求失败后的白屏、卡片上原样显示的“TypeError: Failed to fetch”、一个会丢掉您输入的所有内容的表单(准则 2.1 拒审详解)。Google Play 则把同样的问题归为功能故障。这些问题都不需要飞行模式就会发生:闲置后会休眠的免费托管方案、冷启动的 serverless 函数,或者酒店里信号很差的网络,都会导致同样的结果。

目标是:应用永远不会显示空白或卡死的页面。在没有网络的情况下启动时,会出现一个带重试按钮的原生离线页面。在使用过程中,每个页面要么显示一个会结束的加载状态,要么显示一个带“重试”按钮的错误状态,要么显示缓存数据并提示内容可能不是最新的。

离线模式专题对比了各种原生方案,ChatGPT 和 Codex 应用的离线模式介绍了 Codex 项目的设置方法。

两层必不可少,两层可以选配

原生离线页面(WebViewGold)

当应用无法加载您的 URL 时显示。在 iOS 上,对话框和页面文字取自 offlinetitle、offlinemsg、screen1、screen2 和 offlinebuttontext,而 offlinescreenautoretry 会每秒自动重试一次,无需点按。在 Android 上,OFFLINE_SCREEN_BACKGROUND_COLOR 可以让它与您的品牌配色一致。不涉及任何 Web 代码。

Web 应用中的错误状态(Codex)

每个请求都有超时、带重试的错误状态和一条通俗易懂的提示信息。这是审核人员最常碰到的一层,也是本指南要自动化的一层。

内置备用页面(可选)

开启 offlinelocalhtmlswitch(iOS)或 FALLBACK_USE_LOCAL_HTML_FOLDER_IF_OFFLINE(Android)后,应用在离线时会显示项目 local-html 文件夹中的一个页面,而一个跳转到 reconnect:// 的按钮会重新加载最后一个在线 URL(Reconnect API)。在 iOS 上,zipfiledownloadfromserver 和 zipfileremoteurl 可以从您的服务器更新这个备用页面。

缓存数据(可选,Firebase)

Firestore 的持久化本地缓存会在离线时提供最后已知的文档,因此列表依然能显示,并附上“最后更新时间”提示;写入操作会排队,直到网络恢复。

AGENTS.md 中的错误状态约定

把规则作为不变的约定,一次性写进 Web 应用的 AGENTS.md。之后的每个任务(“添加一个报表页面”)都会自动带上错误状态,而 Review guidelines 部分能让 Codex 代码审查在 pull request 中发现回归问题。

markdown
## Network and error states (every screen)
- All HTTP calls go through src/lib/request.ts: request(path, { timeoutMs = 10000 }).
  It uses AbortSignal.timeout, retries idempotent GETs once with backoff, and throws
  typed errors (OfflineError, TimeoutError, HttpError with status) that carry a
  user-facing message. Components never call fetch directly.
- Every data view renders exactly one of: loading (skeleton, ends after 10 s at the
  latest), content, empty (helpful text and a next action), error (message and a
  "Try again" button).
- No raw error text in the UI: no "TypeError", no stack traces, no JSON.
- Offline banner: listen to the online and offline events and render
  <div role="status">You are offline. Changes will sync when you are back.</div>
  Do not rely on navigator.onLine alone; a failed request also counts as offline.
- Forms keep their input when a submit fails. Submit buttons show progress and
  re-enable on error. Never send the same mutation twice without an idempotency key.
- Firestore: initializeFirestore with persistentLocalCache. Show "Last updated"
  when snapshot.metadata.fromCache is true.
- Express: error middleware returns JSON { error, message } with the right status,
  never an HTML error page. GET /healthz answers 200 without touching the database.
- No service worker for offline inside the native app. WebViewGold's offline screen
  and the bundled fallback page handle "no network at launch".

## Review guidelines
- P1: fetch() or axios outside src/lib/request.ts, or any request without a timeout.
- P1: a catch block that swallows an error without showing an error state.
- P1: a loading state with no exit (no timeout, no error branch).

只要有一个优化良好的Web应用版本,几分钟内您就能拥有一个出色的移动应用。除此之外,客户服务非常友好且乐于助人!我只能说:推荐WebViewGold :-)

朱利安·科赫(Julian Koch) · 联合创始人 Dueldonkey · CodeCanyon

盘点与修复:Codex 任务

这个任务很适合作为云端任务运行,因为结果是一个规模大但很机械的 diff,您会希望以 pull request 的形式阅读它。在云端环境中,把 npx playwright install --with-deps chromium 加到设置脚本里,让浏览器在代理开始工作前就准备好;在 CLI 中,Codex 需要联网安装时会请求审批。

粘贴到 Codex(云端任务、CLI 或 IDE 扩展)
请阅读 AGENTS.md,尤其是“Network and error states”部分。这个 Web 应用运行在用 WebViewGold 构建的原生 iOS 和 Android 应用中,App 审核会在糟糕的网络条件下测试它。

1. 盘点:找出每一个网络调用(fetch、axios、Firestore 的 getDoc、getDocs 和 onSnapshot、server-sent events 或 WebSocket、文件上传)。编写 docs/network-inventory.md,每个调用一行:文件、页面、调用、当前的加载、空状态和错误处理方式。
2. 按照 AGENTS.md 中的规定创建 src/lib/request.ts,并把所有 HTTP 调用迁移到它上面。
3. 为清单中的每个页面实现四种状态:最迟 10 秒后结束的加载状态、内容、空状态,以及带“重试”的错误状态。复用同一个 ErrorState 组件和同一个 EmptyState 组件。
4. 添加带 role="status" 的离线横幅,由 online 和 offline 事件以及 request() 抛出的 OfflineError 驱动。
5. 表单:失败时保留用户的输入,在提交按钮旁显示错误,并重新启用按钮。
6. Firestore:切换到 persistentLocalCache,当 snapshot.metadata.fromCache 为 true 时显示“最后更新时间”。
7. Express:一个返回 JSON 的错误中间件、一个针对 /api 返回 JSON 的 404 处理器,以及 GET /healthz。
8. 创建 native-fallback/index.html:一个自包含的文件(内联 CSS、以内联 SVG 或 data URI 形式嵌入的标志、不发起任何外部请求),包含应用名称、文字“您已离线”,以及一个跳转到 reconnect:// 的“重试”按钮。我会把它复制到原生项目的 local-html 文件夹中。
9. Playwright 测试:API 请求被中止、API 请求永不响应,以及浏览器上下文切换到离线再切回在线。每种情况都必须在 12 秒内进入错误或离线状态,且不显示任何原始错误文本。如果缺少 Playwright 就安装它(先询问)。
最后给出盘点清单、改动过的页面列表,以及所有您无法覆盖的内容。

证明它有效的测试

三种网络故障涵盖了审核人员可能遇到的大部分情况。它们可以在 Codex 沙盒和 CI 中运行,因为只需要本地开发服务器。

javascript
// tests/offline.spec.js (Playwright)
import { test, expect } from "@playwright/test";

test("failed API shows a retry, not a white screen", async ({ page }) => {
  await page.route("**/api/**", (route) => route.abort("internetdisconnected"));
  await page.goto("/dashboard");
  await expect(page.getByRole("button", { name: /try again/i })).toBeVisible({ timeout: 12000 });
  await expect(page.getByText(/TypeError|Failed to fetch/)).toHaveCount(0);
});

test("hanging API never leaves an endless spinner", async ({ page }) => {
  await page.route("**/api/**", () => { /* never answer: a server that hangs */ });
  await page.goto("/dashboard");
  await expect(page.getByRole("button", { name: /try again/i })).toBeVisible({ timeout: 12000 });
});

test("offline banner appears and clears", async ({ page, context }) => {
  await page.goto("/dashboard");
  await context.setOffline(true);
  await expect(page.getByRole("status")).toContainText(/offline/i);
  await context.setOffline(false);
  await expect(page.getByRole("status")).toHaveCount(0);
});

WebViewGold 离线设置一览

目标iOS(Config.swift)Android(Config.java)
离线文字和按钮标签offlinetitle、offlinemsg、screen1、screen2、offlinebuttontext(可通过 alternatelanguage1 和 alternatelanguage2 翻译)Config.java 中没有文档记载的文字配置项;背景色通过 OFFLINE_SCREEN_BACKGROUND_COLOR 设置
无需点按即可重试offlinescreenautoretry没有文档记载的选项;请使用带 reconnect:// 的备用页面
内置备用页面offlinelocalhtmlswitch 设为 true,页面放在 local-html 文件夹中FALLBACK_USE_LOCAL_HTML_FOLDER_IF_OFFLINE 设为 true,页面放在 local-html 文件夹中
从备用页面重试reconnect://reconnect://
远程更新备用页面zipfiledownloadfromserver 和 zipfileremoteurl随构建版本一起发布
应用从后台返回时刷新autoRefreshEnabledAUTO_REFRESH_ENABLED

发布上线

  1. 把约定加入 AGENTS.md

    粘贴上面的段落,并根据您的项目调整路径和超时时间。在任务运行之前提交,这样云端任务才能看到它。

  2. 运行盘点与修复任务

    作为云端任务,您会得到一个 diff 和一个 pull request;在 CLI 中则使用 codex --sandbox workspace-write --ask-for-approval on-request。无论哪种方式,Playwright 测试都必须通过。

  3. 对照清单审阅

    docs/network-inventory.md 中的每一行都应对应一个具备四种状态的页面。在 pull request 中评论 @codex review,再应用一次审查规则。

  4. 配置原生层

    用符合您应用风格和语言的措辞编写离线文字,决定是否开启 offlinescreenautoretry,设置 Android 背景色;如果需要备用页面,就把 native-fallback/index.html 复制为 local-html/index.html,并开启 offlinelocalhtmlswitch 或 FALLBACK_USE_LOCAL_HTML_FOLDER_IF_OFFLINE(Android Reconnect 文档)。

  5. 故意切断网络

    在真机上分别在启动时和使用过程中打开飞行模式。在 iPhone 上,开发者设置中的 Network Link Conditioner(设备连接过 Xcode 后可用)可以模拟极差的网络和完全断网;在 Android 上,使用飞行模式或模拟器的网络设置。

  6. 告知审核人员

    如果应用需要网络连接,请在审核备注中说明,同时说明应用离线时会显示什么。提交的其余步骤见 Codex 发布工作流程。

飞行模式测试脚本(赶在审核人员之前跑一遍)

  • 在飞行模式下冷启动:出现带品牌元素、清晰易读的原生离线页面,网络恢复后“重试”有效。
  • 使用过程中打开飞行模式:出现离线横幅,当前页面保留其内容,各项操作都显示清晰的提示。
  • 恢复联网:横幅消失,“重试”会重新加载,且没有任何内容被重复提交。
  • 服务器挂起(停止您的 API,或在 Network Link Conditioner 中使用 100% 丢包配置):每个加载图标都在约十秒内结束。
  • 备用页面(如已启用):无需网络即可加载,没有损坏的图片或字体,reconnect:// 会返回到最后一个页面。
  • 让应用在后台停留一段时间再返回:开启 autoRefreshEnabled 或 AUTO_REFRESH_ENABLED 时页面会刷新;不开启时,过时的页面依然表现正常。
  • 已登录的会话在离线启动后依然保持;不会有人因为网络错误被登出。

常见陷阱

  • 相信 navigator.onLine。在 Wi-Fi 认证门户之后或连接已断开时,它仍然是 true;请把失败和超时的请求也视为离线。
  • 备用页面加载了网络字体、CDN 脚本或远程标志。离线时这些请求都会失败,页面看起来就是坏的。请把所有内容内联。
  • 重试风暴:每点一次就发出好几个请求的按钮,或者没有退避机制的自动重试,不断冲击一个本就不堪重负的 API。
  • Express 或代理返回 HTML 错误页面,而客户端试图把它当作 JSON 解析(“Unexpected token <”)。JSON 错误中间件可以从源头解决这个问题。
  • 把离线方案建立在 Service Worker 上。在 iOS 上,WKWebView 通常只为配置了 App-Bound Domains 的应用运行 Service Worker,所以只把它当作给浏览器用户的额外福利。
  • 会休眠的免费托管方案:安静一段时间后的第一个请求耗时太久,看起来就像卡住了。提交前请让 API 保持唤醒状态,或者升级方案。

常见问题

我的应用必须完全离线可用才能通过审核吗?

不必。需要服务器的应用可以要求联网,只要在没有网络时能妥善处理:清晰的离线页面、带重试的错误状态、没有卡死或空白的页面。请在审核备注中说明应用需要网络连接。

离线页面和备用页面有什么区别?

离线页面是原生的,通过几段文字和选项来配置。备用页面是您自己放在项目 local-html 文件夹中的 HTML,在开启 offlinelocalhtmlswitch(iOS)或 FALLBACK_USE_LOCAL_HTML_FOLDER_IF_OFFLINE(Android)时代替离线页面显示,并且可以提供一个 reconnect:// 按钮。

Codex 能测试离线行为吗?

可以,针对 Web 层。Playwright 可以中止请求、让请求一直挂起,以及把浏览器上下文切换到离线,这些都能在 Codex 沙盒中完成。原生离线页面和备用页面仍然需要在真机上用飞行模式测试。

我应该开启 Firestore 离线持久化吗?

对于以读取为主的应用,应该开启:列表从缓存中渲染,写入稍后同步。请标明数据何时来自缓存,并在缓存敏感数据之前考虑共用设备的情况。

应用内支持 AbortSignal.timeout 吗?

在当前的系统 WebView 中支持;Safari 从 16 版开始支持。如果您需要支持更旧的 iOS 版本,请让 Codex 在 request.ts 中用 AbortController 加 setTimeout 作为回退方案。

让您的应用上架商店的三种方式

结果相同,只是您需要投入的工作量不同。三种方式都附带审核通过保证和 7×24 小时人工支持。

自己动手

$109 每个平台,一次性付费

WebViewGold 许可证:一个现成的 Xcode 或 Android Studio 项目。粘贴您的 URL,设置名称和图标,开启所需功能,构建并提交。永久免费更新。

在主页查看方案

Cloud Builder

$349 每个许可证,一次性附加购买

无需 Mac、Xcode 或 Android Studio:在浏览器中完成配置,我们在云端构建两个应用,并将其交付到 App Store Connect 和 Play Console。

Cloud Builder 如何运作 试用免费演示

Full Service

$698 每个平台 $798

把您的 URL 和图标发给我们。我们的团队在 72 小时内完成配置、构建并交付您的应用,白标,附源码项目。两个平台共 $1,158。

Full Service 详情 服务提供方: WeSetupYourWebViewApp

冬季特惠价格有效期至 2026年12月31日。

准备好发布您的应用了吗?

App Store & Google Play 审核通过保证 – 否则 14 天内退款

想直接交给我们?Full Service:我们在 72 小时内完成配置、构建并交付,每个平台 $698 起。有疑问?通过 7×24 小时在线聊天联系我们。

退款依据我们 FAQ 中列出的条件:应用因非内容原因被 Apple 和/或 Google 拒审,并在购买后 14 天内提出申请。