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

ChatGPT & Codex

用 Codex 为 Supabase 和 Firebase 应用梳理并实现账号删除

两大商店都会拒绝那些能注册却不能注销的应用。这个功能看起来很小,但一次正确的删除会涉及身份验证、每一个集合或数据表、文件存储、订阅以及 Sign in with Apple 的 token,这正是 Codex 在拿到一份数据清单后最擅长的跨模块改动。下面是一个分两轮的工作流程:Codex 先在只读会话中梳理数据,然后带着测试实现清除逻辑。

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

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

商店的要求,以及审核人员会点什么

Apple(准则 5.1.1(v)):如果应用支持创建账号,用户就必须能在应用内发起账号删除,而删除指的是删除账号及其数据,而不是打上一个停用标记。加一个确认步骤是可以的。如果用户可以用 Apple 登录,Apple 期望您在用户删除账号时通过 Sign in with Apple REST API 撤销他们的 token。Google Play:除了应用内路径,还需要一个网页,让用户无需重新安装应用就能申请删除,并在 Play Console 的数据安全部分填写该网页的 URL。

审核人员实际会做的是:注册一个账号或使用您的演示账号,打开设置,寻找“删除账号”,有时还会真的走完整个流程。在 Google Play 上,网页链接也会被打开;如果它需要登录或返回 404,就会被拒(Play 拒审详解)。

为什么要和 Codex 分两轮进行:删除会悄无声息地失败。被遗忘的集合或存储文件夹不会报错,它们只是继续留在那里。在写代码之前先梳理数据,才能让结果完整。示例使用 React、Express、Firebase Auth、Firestore 和 Cloud Storage;Supabase 的对应做法见下表。账号删除要求指南为不使用编程代理的开发者讲解了同样的规则,isNativeApp() 辅助函数来自 AGENTS.md 模板。

第一轮:在只读会话中梳理数据

read-only 沙盒允许 Codex 读取文件并运行 rg 之类的无害命令,但不能修改任何内容。这让第一轮保持纯粹:它只能看。

bash
# Pass 1: inventory only. Codex can read, search and answer, but not edit.
cd my-app
codex --sandbox read-only --ask-for-approval on-request

# Paste this into the session:
#   Read AGENTS.md. Find every place this codebase stores or sends data about a
#   user: auth providers, Firestore collections and subcollections (and the field
#   that links each document to the user), Cloud Storage paths, server logs,
#   third parties that receive user data (RevenueCat, Stripe, OneSignal, email,
#   analytics, error tracking) and what the web app keeps on the device
#   (cookies, localStorage, IndexedDB, values written with writevalue://).
#   Output one Markdown table: location | link to the user | delete or retain | reason.
#   Mark anything you are unsure about. Do not propose code yet.

# When the table is right, switch to write access inside the session with
# /permissions and ask Codex to save it as docs/user-data-map.md. Commit it.

一次完整的删除涵盖哪些内容

位置Firebase(Express API)Supabase
Auth 用户使用 firebase-admin 调用 getAuth().deleteUser(uid),作为最后一步在 Edge Function 中使用 service role key 调用 supabase.auth.admin.deleteUser(id),作为最后一步
数据库对用户文档及其子集合使用 recursiveDelete(),再加上按所有者字段的查询指向 auth.users 的外键设置 on delete cascade,或在调用 auth 之前显式删除
文件getStorage().bucket().deleteFiles({ prefix: "users/UID/" })列出该用户的文件夹,然后调用 storage.from(bucket).remove(paths)
Sign in with Apple在用户通过 Apple 重新验证身份后调用 revokeAccessToken(auth, token);Firebase 不保存 Apple token不会自动处理。Apple 的撤销接口需要一个来自登录过程的 token;让 Codex 检查您的流程能拿到什么,并标出缺口
商店订阅您无法替用户取消订阅。在对话框中说明这一点,并提供 cancelinapppurchase://;也可以在服务器端删除 RevenueCat 客户相同
推送、邮件、分析删除推送设备记录,从您的邮件工具中移除该联系人,使用各服务商的删除 API相同
设备本身reset:// 让 WebViewGold 清除 Cookie 和缓存;用 writevalue:// 保存的内容在清除缓存后依然存在,需要覆盖掉相同

在过去5年我们尝试过的所有应用平台中,WebViewGold为我们带来了有史以来最短、最高效的上市时间。我不知道为什么没有更早发现你们,但我真的非常满意。目前我们的应用在iOS App Store和Google Play商店的下载量已超过6000次,没有任何问题!

安东尼奥·达维拉 · CodeCanyon

第二轮:实现提示词

用 codex --sandbox workspace-write --ask-for-approval on-request 开启一个全新的会话,让数据地图而不是之前的对话成为唯一依据。如果使用 Supabase,请把第 3 步替换为一个 Edge Function:验证 JWT、删除存储对象、依靠级联外键,并调用 auth.admin.deleteUser。

粘贴到 Codex(workspace-write,按需审批)
请阅读 AGENTS.md 和 docs/user-data-map.md。实现符合 App Store 准则 5.1.1(v) 和 Google Play 账号删除政策的账号删除功能。技术栈:React 前端、Express API、Firebase Auth、Firestore、Cloud Storage。这个 Web 应用也运行在用 WebViewGold 构建的原生 iOS 和 Android 应用中;仅限应用内的行为使用 isNativeApp() 判断。

1. 设置 > 账号 > 删除账号。确认面板说明哪些内容会被删除、哪些内容我们会保留以及保留多久(依据数据地图),并说明商店订阅在取消之前会继续由 Apple 或 Google 扣费。如果用户有有效的商店订阅且 isNativeApp() 为 true,显示一个跳转到 cancelinapppurchase:// 的“管理订阅”按钮。
2. 近期登录:如果服务器返回 recent-login-required,就重新验证身份。对于使用 apple.com 提供方的用户,一律用 OAuthProvider("apple.com") 重新验证身份,用 OAuthProvider.credentialFromResult 读取 access token,并在调用服务器之前调用 revokeAccessToken(auth, token)。
3. Express 中的 DELETE /api/account:用 checkRevoked 验证 ID token,要求 auth_time 在最近 5 分钟内,然后按以下顺序删除:users/{uid}/ 下的 Storage 文件、数据地图中的每一个 Firestore 位置(子集合使用 recursiveDelete,包括 pushDevices)、Stripe 客户,以及(如果数据地图要求)RevenueCat 客户(放在功能开关之后,密钥从环境变量读取),最后删除 Auth 用户。如果请求中途失败,每一步都必须可以安全地重新执行。
4. 成功之后:退出登录;当 isNativeApp() 为 true 时跳转到 reset://,让 WebViewGold 清除 Cookie 和缓存;在浏览器中则清除 localStorage。然后显示告别页面。
5. 一个无需登录即可访问的公开 /delete-account 页面:说明应用内的操作步骤,并为已经没有该应用的用户提供申请表单(电子邮件地址)。申请写入 deletionRequests 集合,并触发一封发往[支持邮箱]的邮件。
6. 在 /privacy 中添加一小节关于删除和数据保留的说明。
7. 测试:如果项目已经在使用 Firebase 模拟器就用模拟器,否则使用 mock。删除操作会清除数据地图中的每一个位置,第二次调用不产生任何效果,过期的 token 会被拒绝。
最后报告修改过的文件、需要填入 Play Console 的 URL,以及我必须配置的内容。

在 diff 中要检查的代码

逻辑集中在两个文件中。服务器端的顺序很重要:先删数据,最后删 Auth 用户,这样失败的请求可以直接重试。Apple 撤销由客户端处理,因为 Firebase 不保存 Apple token,而用户反正也需要重新验证身份。

javascript
// server/routes/account.js (Express + firebase-admin)
import express from "express";
import { getAuth } from "firebase-admin/auth";
import { getFirestore } from "firebase-admin/firestore";
import { getStorage } from "firebase-admin/storage";

export const accountRouter = express.Router();
const OWNED = [["projects", "ownerId"], ["comments", "authorId"], ["pushDevices", "uid"]]; // from docs/user-data-map.md

accountRouter.delete("/api/account", async (req, res) => {
  const idToken = (req.get("Authorization") || "").replace(/^Bearer /, "");
  let user;
  try {
    user = await getAuth().verifyIdToken(idToken, true); // true = reject revoked tokens
  } catch {
    return res.status(401).json({ error: "unauthenticated" });
  }
  if (Date.now() / 1000 - user.auth_time > 300) {
    return res.status(403).json({ error: "recent-login-required" });
  }
  const uid = user.uid;
  const db = getFirestore();

  await getStorage().bucket().deleteFiles({ prefix: "users/" + uid + "/" });
  for (const [name, field] of OWNED) {
    const owned = await db.collection(name).where(field, "==", uid).get();
    for (const doc of owned.docs) await db.recursiveDelete(doc.ref);
  }
  await db.recursiveDelete(db.collection("users").doc(uid));
  await getAuth().deleteUser(uid); // last: everything above is safe to re-run
  res.status(204).end();
});

// src/lib/deleteAccount.js (client, Firebase JS SDK)
import { OAuthProvider, reauthenticateWithPopup, revokeAccessToken } from "firebase/auth";
import { isNativeApp } from "./platform";

export async function deleteAccount(auth) {
  const user = auth.currentUser;
  if (user.providerData.some((p) => p.providerId === "apple.com")) {
    const result = await reauthenticateWithPopup(user, new OAuthProvider("apple.com"));
    const credential = OAuthProvider.credentialFromResult(result);
    await revokeAccessToken(auth, credential.accessToken); // Apple 5.1.1(v)
  }
  const res = await fetch("/api/account", {
    method: "DELETE",
    headers: { Authorization: "Bearer " + (await user.getIdToken(true)) }
  });
  if (res.status === 403) throw new Error("recent-login-required");
  if (!res.ok) throw new Error("delete-failed");
  await auth.signOut();
  if (isNativeApp()) window.location.href = "reset://"; // WebViewGold clears cookies and cache
}

端到端工作流程

  1. 梳理

    在只读沙盒中运行第一轮,亲自修正表格(Codex 无法知道哪些数据出于税务或法律原因必须保留),然后提交 docs/user-data-map.md。

  2. 构建

    在一个全新的会话中运行第二轮。接受改动之前先执行 /review,并对照数据地图逐行阅读服务器路由。

  3. 配置

    在 Play Console 的数据安全部分填写公开的 /delete-account URL。在隐私政策中说明删除和数据保留(隐私政策需要包含什么)。

  4. 验证

    用一个临时账号,在 TestFlight 构建版本和内部测试构建版本上逐项检查下面的清单。

  5. 保护演示账号

    审核人员可能会把它删掉。每次提交前都重新创建它,并在审核备注中说明欢迎他们新建一个账号来测试删除功能。发布流程的更多内容见 Codex 工作流程指南。

像审核人员一样验证

  • 一个在数据地图中每个位置都有数据的测试账号(一个项目、一条评论、一个上传的文件、一台推送设备)。
  • 在应用内删除:该用户已从 Firebase Authentication 中消失,且不再残留该用户的任何 Firestore 文档或 Storage 文件。
  • 删除之后,Sign in with Apple 测试用户在其 Apple 账号设置的“使用 Apple 登录”应用列表中不再看到您的应用。
  • 应用回到干净的未登录状态;重新打开也不会恢复旧会话。
  • /delete-account 页面在浏览器无痕窗口中无需登录即可打开,申请表单能够送达。
  • 调用删除路由两次,或在模拟中途失败后再次调用,最终都处于同样干净的状态。

常见陷阱

  • 停用而不是删除。在 5.1.1(v) 下,deleted: true 标记不算删除。
  • 在客户端通过安全规则执行删除。这种方式天生就不完整(没有管理员权限、无法删除 Auth 用户、无法处理其他用户的引用);请在服务器端完成。
  • 先删除 Auth 用户。如果随后请求失败,数据就成了孤儿,再也没有人能通过身份验证来完成剩下的工作。
  • 在应用内使用弹窗。如果 reauthenticateWithPopup 在 WebView 中表现异常,请使用 reauthenticateWithRedirect,并用 getRedirectResult 读取结果。
  • 忘了订阅。删除账号并不会停止 App Store 或 Google Play 的扣费;请在用户确认之前告知他们。
  • /delete-account 页面需要登录,或者在 Supabase 的客户端代码中放了 service role key。这两种情况都会很快被发现。

常见问题

允许设置宽限期吗?

允许。只要您在确认步骤和隐私政策中说明,就可以把永久删除安排在稍后的一段短时间之后(例如 14 天),并允许用户通过登录来取消。但删除本身必须真正执行。

可以改用 Firebase 的 Delete User Data 扩展吗?

它是一个不错的基础:在 Auth 用户被删除时,它会删除配置好的 Firestore 路径和 Storage 文件。您仍然需要应用内按钮、Apple token 撤销、公开的申请页面,并检查它的配置是否与您的数据地图一致。

网页申请页面必须自动删除吗?

不必。Google Play 要求的是一种在应用之外申请删除的方式。用一个表单生成申请、再由您处理是可以接受的,前提是您确实在承诺的时间内处理。

我也必须删除 RevenueCat 和 Stripe 中的数据吗?

删除或匿名化您不需要保留的数据。付款记录可能有法定保留期限;请在隐私政策中列出您保留了什么以及原因。取消商店订阅仍然需要用户自己操作。

为此应该让 Codex 访问我的生产数据库吗?

不应该。Codex 针对模拟器或 mock 编写代码和测试;您通过已部署的路由在生产环境中执行删除。让生产环境的凭据远离代理会话和云端环境。

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

结果相同,只是您需要投入的工作量不同。三种方式都附带审核通过保证和 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 天内提出申请。