docs: update

This commit is contained in:
chaos-zhu
2026-06-07 13:00:47 +08:00
parent 32cb71c062
commit d3ac8c04e3
-386
View File
@@ -1,386 +0,0 @@
# iOS Plus 授权合规清理清单
## 目标
让 EasyNode Native 在 iOS 上架时保持清晰定位:
> App 不销售数字内容,只访问用户自托管 EasyNode 服务端上的既有 Plus 能力。Plus 授权由用户连接的服务端管理,Native 端只展示服务端返回的授权状态,不主动提供购买、获取 key 或外部购买引导。
## 背景
当前 Plus 功能授权在服务端完成:
- Native 登录用户自己的 EasyNode 服务端。
- Native 调用服务端 `/plus-info` 查看 Plus 运行时状态。
- Native 调用服务端 `/plus-conf` 提交 Plus Key。
- EasyNode 服务端再向授权服务器激活 key,并在内存中保存解密 key。
因此,iOS App 不应表现为「在 App 内销售或引导购买 Plus」。否则容易被 Apple 认为是在绕过 IAP。
## 合规原则
### iOS 端原则
1. 不展示购买入口。
2. 不展示外部购买链接。
3. 不展示价格、折扣、优惠、购买提示。
4. 不使用「获取 Plus Key」「购买 Plus」「升级 Plus」等引导性文案。
5. 只展示当前连接服务端返回的 Plus 授权状态。
6. Plus 错误提示优先使用服务端下发内容。
7. 未授权时使用中性说明:Plus 由服务端管理,请在服务端完成授权后刷新。
### 非 iOS 端原则
Android / Web 是否保留购买入口可单独决定。为了最大程度降低审核风险,本清单只要求 iOS 隐藏购买引导。
## 需要修改的 native 文件
### 1. `lib/features/settings/plus_subscription_page.dart`
#### 当前风险点
当前文件存在固定外部购买链接:
```dart
const String _plusPurchaseUrl = 'https://en.221022.xyz/buy-plus';
```
当前 AppBar 右上角展示「获取 Plus Key」按钮:
```dart
child: _FetchKeyButton(onTap: _openPurchaseUrl),
```
当前折扣 banner 可点击外部购买链接:
```dart
onDiscountTap: _openPurchaseUrl,
```
这些在 iOS 上属于高风险购买引导。
#### 清理要求
- [ ] 引入平台判断,例如 `dart:io` 的 `Platform.isIOS`,或用 Flutter 平台能力判断当前是否 iOS。
- [ ] iOS 下隐藏 AppBar 右上角「获取 Plus Key」按钮。
- [ ] iOS 下不调用 `_openPurchaseUrl()`。
- [ ] iOS 下隐藏折扣 banner,或者只展示不可点击的服务端公告;推荐直接隐藏。
- [ ] iOS 下 `_ActivateCard` 不传入购买/折扣点击回调。
- [ ] iOS 下 `discount.discount` 即使为 true,也不展示跳转购买 UI。
- [ ] 保留 Plus Key 输入和激活能力,前提是文案不引导用户去购买。
- [ ] 保留 `/plus-info` 状态展示与刷新能力。
#### 建议实现形态
在页面内部计算:
```dart
final isIOS = Platform.isIOS;
```
AppBar actions:
```dart
actions: isIOS
? const []
: [
Padding(
padding: const EdgeInsets.only(right: 12),
child: _FetchKeyButton(onTap: _openPurchaseUrl),
),
],
```
激活卡片:
```dart
_ActivateCard(
controller: _keyController,
formKey: _formKey,
discount: isIOS ? const PlusDiscount(discount: false, content: '') : _discount,
loading: _activating,
onSubmit: _activate,
onDiscountTap: isIOS ? null : _openPurchaseUrl,
)
```
如果 `onDiscountTap` 改成 nullable:
```dart
final VoidCallback? onDiscountTap;
```
折扣区域只在非 iOS 且回调存在时展示:
```dart
if (widget.onDiscountTap != null &&
widget.discount.discount &&
widget.discount.content.isNotEmpty) ...[
// discount banner
]
```
### 2. `lib/l10n/strings_zh.dart`
#### 当前风险点
当前文案:
```dart
'plus.fetchKey': '获取 Plus Key',
'plus.fetchKeyHint': '已复制购买地址到剪贴板:{0}',
'plus.activateHint': '如果你觉得好用,欢迎加入支持作者,让项目走得更远 ❤️',
'plus.discountTip': '限时优惠',
'plus.featuresTitle': 'Plus 专属功能',
```
其中 `获取 Plus Key`、`购买地址`、`限时优惠`、`支持作者` 都容易被审核理解为购买引导。
#### 清理要求
- [ ] iOS 可见页面不展示 `plus.fetchKey`。
- [ ] iOS 不触发 `plus.fetchKeyHint`。
- [ ] 将 `plus.activateHint` 改为中性文案。
- [ ] 如果 `plus.discountTip` 仅用于 iOS 隐藏区域,可以暂时保留;若 iOS 可见则必须移除。
- [ ] Plus 功能错误提示优先显示服务端返回内容,不在客户端写「请购买」「请升级」。
#### 建议中性中文文案
```dart
'plus.activateHint': 'Plus 授权由当前连接的 EasyNode 服务端管理。请在服务端完成授权后刷新状态。',
'plus.activateSuccess': '授权信息已提交,请刷新确认服务端状态',
'plus.activateFailed': '授权失败,请查看服务端返回信息',
```
如果需要新增状态说明:
```dart
'plus.iosManagedByServer': 'Plus 授权由当前连接的服务端管理,Native 端仅展示服务端返回的授权状态。',
```
### 3. `lib/l10n/strings_en.dart`
#### 当前风险点
当前文案:
```dart
'plus.fetchKey': 'Get Plus Key',
'plus.fetchKeyHint': 'Copied purchase URL to clipboard: {0}',
'plus.activateHint': 'If you find it helpful, consider supporting the author and keep the project going ❤️',
'plus.discountTip': 'Limited offer',
```
#### 清理要求
- [ ] iOS 可见页面不展示 `plus.fetchKey`。
- [ ] iOS 不触发 `plus.fetchKeyHint`。
- [ ] 将 `plus.activateHint` 改为中性文案。
- [ ] iOS 可见文案不得出现 purchase、buy、price、discount、offer、upgrade 等购买引导词。
#### 建议中性英文文案
```dart
'plus.activateHint': 'Plus authorization is managed by the connected EasyNode server. Complete authorization on the server, then refresh the status here.',
'plus.activateSuccess': 'Authorization info submitted. Refresh to confirm the server status.',
'plus.activateFailed': 'Authorization failed. Please check the server response.',
```
如需新增:
```dart
'plus.iosManagedByServer': 'Plus authorization is managed by the connected server. The native app only displays the server-reported authorization status.',
```
### 4. `lib/features/settings/models/plus_info.dart`
#### 当前状态
当前模型已有:
```dart
final String error;
```
并且说明为:
```dart
/// Human-readable error to surface when kicked / abnormal.
```
#### 清理要求
- [ ] 保留 `error` 字段。
- [ ] Plus 状态异常时优先展示服务端返回的 `error`。
- [ ] 客户端 fallback 文案保持中性,不写购买引导。
### 5. `lib/features/servers/server_form_page.dart`
#### 当前风险点
当前 iOS 可见 Plus gating 文案可能是:
```dart
'代理 / 跳板机为 Plus 专属功能'
```
这类文案本身问题不大,但不应搭配购买入口或购买链接。
#### 清理要求
- [ ] 未授权时只提示该能力由服务端 Plus 状态控制。
- [ ] 不出现「购买」「升级」「获取 Key」等引导。
- [ ] 如果能拿到服务端 Plus 错误信息,则展示服务端下发错误。
建议中文:
```dart
'该能力需要当前连接的服务端已启用 Plus。请在服务端完成授权后重试。'
```
建议英文:
```dart
'This capability requires Plus to be active on the connected server. Complete authorization on the server and try again.'
```
### 6. `lib/features/servers/servers_tab.dart`
#### 当前风险点
连接代理 / 跳板机时未授权会提示:
```dart
'代理类型错误,请激活 Plus 功能后重试'
```
#### 清理要求
- [ ] 替换为中性服务端授权文案。
- [ ] 不提示购买或获取 key。
建议中文:
```dart
'当前连接的服务端尚未启用 Plus,无法使用代理 / 跳板机。'
```
建议英文:
```dart
'Plus is not active on the connected server, so proxy / jump host cannot be used.'
```
### 7. `lib/features/scripts/script_groups_page.dart`
#### 当前状态
脚本分组受 `isPlusActiveProvider` 控制。
#### 清理要求
- [ ] 未授权提示保持中性。
- [ ] 不加入购买按钮。
- [ ] 不跳转 Plus 购买页。
建议中文:
```dart
'该能力需要当前连接的服务端已启用 Plus。'
```
建议英文:
```dart
'This capability requires Plus to be active on the connected server.'
```
## 服务端下发错误提示要求
### 当前服务端能力
`/plus-info` 当前返回:
```js
{
key,
instanceId,
active,
status,
needRestart,
tokenExpireAt,
error
}
```
文件:`easynode/server/app/controller/user.js`
客户端模型已经接收 `error`。
### 清理要求
- [ ] Plus 页面状态展示优先使用 `/plus-info` 的 `error`。
- [ ] `/plus-conf` 激活失败时继续使用服务端返回的 `msg`。
- [ ] 客户端只保留中性 fallback。
- [ ] 服务端错误文案可以说明授权状态、占用、过期、失效,但不要在 iOS 客户端表现为购买引导。
## iOS App Review 备注建议
提交审核时,在 App Review Notes 中说明:
```text
EasyNode Native is a client app for connecting to a user self-hosted EasyNode server. Plus capabilities are authorized and managed on the connected server. The iOS app does not sell digital content or provide purchase links; it only displays the authorization status reported by the user's server.
```
中文含义:
```text
EasyNode Native 是连接用户自托管 EasyNode 服务端的客户端。Plus 能力在用户连接的服务端上授权和管理。iOS App 不销售数字内容,也不提供购买链接,只展示用户服务端返回的授权状态。
```
## 验收清单
### 代码搜索验收
在 iOS 构建相关可见代码中,应确认:
- [ ] 没有可触发的外部购买 URL。
- [ ] 没有 iOS 可见的「获取 Plus Key」按钮。
- [ ] 没有 iOS 可见的折扣、优惠、限时活动入口。
- [ ] 没有 iOS 可见的 purchase / buy / discount / offer / upgrade 购买引导文案。
- [ ] Plus 未授权时没有跳转购买页面。
- [ ] Plus 未授权时只展示服务端授权状态或中性提示。
### 手动验收
在 iOS 模拟器或真机中检查:
- [ ] 打开 Plus 页面,不显示「获取 Plus Key」按钮。
- [ ] 打开 Plus 页面,不显示购买链接。
- [ ] 服务端返回折扣公告时,iOS 不显示可点击购买 banner。
- [ ] 输入已有 Plus Key 仍可提交到当前连接的服务端。
- [ ] `/plus-info` 返回 active 时,Plus 状态正常展示。
- [ ] `/plus-info` 返回 kicked / inactive / unset 时,提示文案中性。
- [ ] 使用代理 / 跳板机 / 脚本分组等 Plus 功能时,未授权提示不引导购买。
## 推荐实施顺序
1. 修改 `plus_subscription_page.dart`,加 iOS 平台判断,隐藏购买入口和折扣 banner。
2. 修改 `strings_zh.dart` / `strings_en.dart` 的 Plus 文案为中性描述。
3. 检查 Plus gating 文案:服务器代理、跳板机、脚本分组。
4. 确认 Plus 错误优先用服务端下发内容。
5. 在 iOS 模拟器跑一遍 Plus 页面和受限功能。
6. 搜索购买相关关键词,确认 iOS 可见路径无购买引导。
## 不在本次清理范围内
以下内容不建议本次一起做:
- Apple IAP。
- RevenueCat。
- App Store Server Notifications。
- 终端用户账号系统。
- 网站购买和 IAP 的统一权益系统。
- License 表增加 Apple 交易字段。
这些属于后续「C 方案」的大改造,不是本次 iOS 上架合规清理的目标。