# 开发者指南

写给基于 TapeRelay 做产品（网盘、视频站、网页应用）的开发者。规范的原稿在 `spec/TapeRelay.md`（英文）和 `spec/TapeRelay.zh.md`（中文），暂且作废，等重写。运营方看 `docs/OPERATORS.md`；给 AI 的版本见 [DEVELOPERS-FOR-AI.md](DEVELOPERS-FOR-AI.md)。

## 一句话

**中继网关替你存文件、传文件；权限靠电路（持有人的签名）：后台、命令行用持有人签发的开发者密钥（像 API key，最长 366 天、随时撤销）；每个文件同时存 3 家不同运营方。** TapeRelay 不替你运行程序；公开文件谁都能读，而且收不回来；密钥、密码不要放进来，要保密的内容用加密文件。

## 1. 做法

现在只有模版 A：文件记在你（开发者）的电路名下，适合网盘、内容平台，用户不用钱包，用你自己的账号体系登录。你要有一个后台（Node.js），它持有一把**开发者密钥**和一把**加密私钥**。用户的文件在浏览器里加密，直接传到节点；浏览器把要发的请求交给后台，后台核对后逐个签名，文件内容不经过后台。每个文件的钥匙另锁一份给后台，用户换了设备也能向后台要回来。删除时后台先隐藏（能恢复），到期后批量释放。示例在 `examples/template-a/`，详细做法见那里的 README。

原来还有模版 B（文件记在用户自己的电路名下，用户用自己的钱包），已经删掉。

**三条链**（2026-10-30，D-521～D-523）：电路可以在 BNB Smart Chain、X Layer、Base 上，地址写成 `eip155:<链号>:0x…`（56、196、8453）。签名（EIP-712 的 domain 写着电路的链）、写发现文件（那条链上的 SiteRegistry，手续费付那条链的币，X Layer 是 OKB）都在电路所在的链上；读链时名字按里面的区号认链（X Layer 区号 2、Base 区号 3、BNB 不带），只给 0x 地址时在三条链上同时找。付款合约每条链各一份（还没部署），用户只在自己电路所在的链上买。

示例代码在 GitHub 仓库 `Yiqsen/TapeRelay` 的 `examples/` 里。克隆后在仓库根目录先 `npm ci --ignore-scripts`（要 Node.js 20.19 以上），本文的路径都相对仓库根目录。

**模版 A 的两把密钥**：开发者密钥在控制台的「密钥」页生成，能撤销，最长一年到期。加密私钥是后台自己生成的随机数，专门保管用户文件的钥匙，丢了就打不开用户的文件，要另外备份。两把都只放在后台的密钥文件里（示例从仓库根目录的 `.env` 读）。后台被攻破，攻击者能用开发者密钥在你的节点上传、删除对象，也能打开所有用户的文件，发现了要马上在控制台撤销开发者密钥。电路持有人的钱包私钥不放在后台。

## 2. 存 3 家：怎么挑、怎么传

- **挑**：从运营方名单（官方的在 `https://www.taperelay.com/api/operators`）随机挑 3 家**不同运营方**（同一主机的几个名字算一台，名字、编号、主机有一样相同的算一家）：只挑连得上、是 TapeRelay、没宣布退出、默认配额够、价格不超过你设的上限、支持你要的功能（`/v1/info` 的 `features`）的；注册表部署之后只挑在保的（TapeStore §3，D-342）。客户端库：`parseOperatorList`、`checkOperator`、`pickOperators`。在挑好的这几家的发布站买套餐，发布站会把它的服务器写进电路的**发现文件**（链上，一笔交易）。
- **传**：同时传 3 台，**存好 2 台就算存好**，告诉用户「已存好」；第 3 台在后台隔 5 秒、30 秒、2 分钟再试（`uploadToServers`，D-343）。服务器之间不互相复制。
- **补**：慢的那台只在页面开着时接着传。之后核对、补齐：`checkReplicas` 问每台记着没有，`repairReplicas` 先认领、认领不到的从存着的台读出再传。模版 A 的后台定时补。
- **换**：一家一直不行（连不上、宣布退出），另挑一家（`pickOperators` 的 `exclude` 写在用的几台），在它的发布站买套餐（发布站把它写进发现文件），再补齐。换下来的那台上的不会自动删。
- **读**：一台不通换下一台（TapeRelay §6.2、TapeRelay §7.2）。

## 3. 权限：靠电路

服务器只认两种签名：电路持有人的钱包，和持有人授权的会话密钥（TapeRelay §8）。没有账号；像 API key 一样用的**开发者密钥**也是持有人授权的会话密钥，只是有效期长（见本节最后）。

- **会话密钥的授权**（`authorizeSession`）写明：哪几台服务器、多长时间（最长 24 小时）、**应用**（你的电路的链上名字，例如 `4246.0.tape`，只用于显示和统计，网页自己填的，不能证明是谁）、**范围**：

| 范围 | 能做什么 |
|---|---|
| `objects` | 上传、认领、列出、查用量 |
| `send` | 以这枚电路的名义交出文件通知 |
| `inbox` | 拉取收件箱、标记已读 |
| `pointers` | 写签名指针 |
| `release` | 释放（删除）对象；用它删掉的进回收站，至少留 7 天（TapeStore §4.3、TapeStore §4.5，2026-10-29 起，服务器 `features` 有 `recycle-bin` 的才认） |
| `admit` | 给你的用户签「准入」，让这台服务器替他们收发消息；也能收回。要这台的运营方把你的电路列为准入人 |

  只要你用得到的范围；钱包签名时用户看得到应用和范围。**释放（删除）只认钱包和带 `release` 范围的开发者密钥**（TapeStore §4.3）；网页的 24 小时授权根本不能有 `release`（有了整份不合格），开发者密钥也默认不勾。
- **开发者密钥**（2026-10-29，TapeRelay §8.7，D-520）：给你自己的后台、命令行、自动化流程用。持有人的钱包签一次长期授权（钱包里写着 `DeveloperKey` 和你填的用途 `note`；2026-10-30 起单独一种类型，D-526），最长 366 天（默认 90 天，选够用的最短），写成 `trk1_` 开头的一串，**只给这一次**：放进后台的环境变量（命令行认 `TAPESTORE_KEY`）或密钥管理，不要放进网页、代码库、日志。建的地方：官方控制台的「密钥」页（用浏览器钱包签）、`tapestore key create`（要持有人钱包的私钥）、客户端库 `createDeveloperKey`。默认范围只有 `objects`；要能删除另勾 `release`（「允许删除」；只有开发者密钥能给，网页的 24 小时授权删不了文件）。泄露了或不用了就撤销（控制台同一页、`tapestore key revoke`、`signRevocation`）：发给钥匙里写的全部服务器，另加现在发现文件里的。只在官方控制台、自己的命令行建它；别的网页请你签 `DeveloperKey` 的不要签。钱包里显示的 `notBefore`、`expires` 是一串数字（Unix 秒），看不出是哪天：自己建的 `notBefore` 就是签的那一刻，签名请求里的 `notBefore` 明显比现在晚的（签成将来才生效）更不要签（D-541，审查 41 的 I-426）；命令行对将来才生效的钥匙另有提醒，但那是给拿着钥匙的人看的，挡不住被骗签。只有 `features` 写 `dev-keys` 的服务器收它；钥匙快到期时命令行会提醒。
- **模版 A**：后台不发会话密钥，每个请求都用开发者密钥签。浏览器用 `remoteAuth` 把要发的请求（服务器、方法、路径、请求体的哈希、时间、随机数）交给后台。后台用 `createRemoteSigner` 核对是自己的电路、开发者密钥里的节点、只是上传和认领、时间相差 2 分钟以内，再核对是这个用户登记过的文件，才签。回复里另带开发者密钥的授权声明，浏览器把它放进请求头。签名绑定服务器和内容，300 秒内只能用一次。

## 4. 播放和下载

- 节点原来提供按文件读取的网址（`/v1/files/…`），已经删掉。
- **在自己的网页里播放**：用开发包的播放器组件 `mountPlayer(元素, { circuit, envelope, fileKey }, { servers })`。它在浏览器里解密、逐块核对，视频、音频边下边播（MP4、WebM），图片、文字直接显示，别的类型给下载。不用装 Service Worker。内容安全策略要允许 `media-src blob:`、`img-src blob:`。
- **加密文件用分享链接给人看**：链接打开默认播放器（或你复制进自己容器网站的那份），播放器在浏览器里解密、边下边播，见 `docs/FRONTENDS-DESIGN.md`。

## 5. 改和删

- **对象的内容永远不变**（Files §3.1）。「改」就是传新版本、再改指向：你自己的索引（数据库、列表文件），或签名指针（TapeRelay §7.3，D-348）。
- **删除 = 释放**（TapeStore §4.3）：持有人的钱包，或带 `release` 范围的开发者密钥，一次最多 1,000 个对象，每台各释放一次。释放之前用量照算。释放只去掉自己名下的记录，字节由运营方按政策删（没有别的电路记着的，最多再留 `/v1/info` 的 `retention.afterLastReferenceDays` 天，默认 30），删之前知道哈希的人照样能按哈希读到。要保密的内容先加密。
- **回收站**（TapeStore §4.5，2026-10-29，D-520）：用开发者密钥（带 `release` 范围）删掉的，服务器留 7 天，这期间照算你的用量（D-525：换不来空间，也就不会被提前删）；钥匙泄露、被人乱删时，先撤销它，再在 7 天之内认领回来（`tapestore restore --from 钥匙地址`、`store.restore({ sessionKey })`）。钱包直接删的不进回收站。
- **共用的对象**：块按 1 MiB 切、按哈希存，内容相同或有相同片段的文件共用对象。删一个文件时，别的文件还在用的对象不能释放（模版 A 这样做）。
- 模版 A：后台先隐藏，`releaseAfterHours` 之后定时批量释放（D-338）。

## 6. 费用

- **存储**按电路买套餐，在运营商的发布站上买（`docs/OPERATORS.md` 第二节）。套餐分存储和消息两种。付款合约的 `buy`、`buyMany` 收电路的链上名字，例如 `buy(套餐, "4246.0.tape")`，可以给任意电路买，一次付款买几家。合约只查名字的格式、不查电路在不在：付款之前先用客户端库的 `confirmCircuit` 读链确认电路存在（TapeStore §6）。每家有免费的默认配额（`/v1/info` 的 `quota`）。
- **写发现文件**要付链上的手续费，在电路所在的那条链上付。
- **下载流量**目前不收费，运营方会限流。

## 7. 做不到的

- **「确保不丢」只能降低概率**：运营方会按政策删、会关门，服务器之间不复制。
- **不能原地修改**：只能传新版本、改指向。
- **删除不等于从网络上消失**：读取不要签名，谁都能下载、认领同样的内容；释放后运营方还留一段时间。
- 在 TapeKit 外壳里运行的网页另有限制（访问链外的地址等，以 TapeKit 的说明为准）；容器网站的开通费见 `docs/FOR-TAPEOUT.md`。

## 8. 客户端库速查

`client/src/index.mjs`，普通 JavaScript，浏览器和 Node.js 都能用（只依赖 `@noble`）。浏览器里的网址布局照 `examples/static.mjs`。

**开发包（预览，2026-10-29 起，D-511、D-514；还没发布到 npm）**：同一份代码分成两个包。

- `@tapestore/client`：长期存储和给开发者的简单接口。装它会连带装上 `@taperelay/client`。
- `@taperelay/client`：身份和签名、会话密钥、发现文件、文件格式、上传认领读取、播放网址、指针、收件箱、服务器消息。

本机可以用 `node tools/pack-sdk.mjs` 出包（`dist/sdk/`，`.tgz`）。最简单的用法：

```js
import { createTapeStore } from '@tapestore/client';
const store = createTapeStore({ account, servers, privateKey });   // 私钥只在自己的后台；浏览器里用 wallet 或会话密钥的 auth
// 后台、脚本也可以：createTapeStore({ developerKey: process.env.TAPESTORE_KEY })   电路和服务器从钥匙里取
const f = await store.upload(bytes, { name: 'a.mp4', contentType: 'video/mp4' });   // f.manifest 记进自己的数据库
await store.read(f.manifest);  await store.check(f.manifest);  await store.repair(f.manifest);
await store.remove(f.manifest, { keep: [还要留着的文件的清单哈希] });   // 钱包或带 release 范围的开发者密钥；内容相同的块只记一份，keep 里的不释放
await store.recycleBin();  await store.restore({ sessionKey: '0x…' });   // 回收站：开发者密钥删掉的，7 天之内找回
```

命令行 `tapestore`（`@tapestore/cli`，同样还没发布）：`tapestore init 4246.0.tape` 从链上的发现文件读服务器写进 `tapestore.json`，之后 `tapestore upload 文件`、`download`、`check`、`repair`、`rm --keep …`、`usage`；私钥只从环境变量 `TAPESTORE_PRIVATE_KEY` 或 `--key-file` 读，不写在命令里。开发者密钥：`tapestore key create -o 文件`（要持有人的私钥）、之后设 `TAPESTORE_KEY` 就不要配置文件；`key show`、`key revoke`、`recycle`、`restore`。

两个包里，`@taperelay/client` 的 `createClient` 不带释放、列出、用量，要用 `@tapestore/client` 的 `createStoreClient`。本仓库的 `client/src/index.mjs` 照旧两样都带。

| 做什么 | 用什么 |
|---|---|
| 连服务器 | `createClient({ fetch })`（开发包里要释放、列出、用量的用 `createStoreClient`）；`client.info(名字)`、`client.usage(名字, 授权)` |
| 挑运营方 | `parseOperatorList`、`checkOperator`、`pickOperators({ count: 3, features, exclude })` |
| 发现文件 | `discoveryJson`、`serverEntryOf`、`parseDiscovery`、`writeSiteFile`（链上，交易）、`DISCOVERY_PATH` |
| 授权 | `keyWallet`（Node 里用私钥）、`eip1193Wallet`（浏览器钱包）；`authorizeSession({ app, scopes })`、`sessionAuth`、`walletAuth`；开发者密钥 `createDeveloperKey`、`developerKeyAuth`、`describeDeveloperKey`；撤销 `signRevocation` + `client.revoke` |
| 模版 A 的签名 | 浏览器 `remoteAuth({ account, endpoint })`；后台 `createRemoteSigner({ auth: developerKeyAuth(开发者密钥), servers })` 的 `check`、`sign` |
| 上传 | `planPublicFile(文件, { withManifest: true })`、`uploadToServers(client, 服务器, 授权, 文件, { plan, onProgress })` → `{ stored, ok, pending, settled }` |
| 读 | `readFile`、`readObject`（核对）、`fileObjects`（一个文件的全部对象） |
| 副本 | `checkReplicas`、`repairReplicas` |
| 删除 | `client.release(名字, 授权, 对象)`（钱包，或带 `release` 范围的）；回收站 `client.released`，找回用 `client.claim` |
| 指针 | `makePointer`、`signPointer`、`publishPointer`、`readPointer` |

### 中继网关的接口

每台中继网关都提供这些接口（规范 TapeRelay §14）。一般不用直接调：客户端库会签名、核对。权限一栏：**公开**不要签名；写范围的，认持有人的钱包或有这个范围的会话密钥；**仅钱包**只认持有人的钱包。

| 接口 | 做什么 | 客户端库 | 权限 |
|---|---|---|---|
| `GET /v1/info` | 服务器信息：配额、价格、支持的功能 | `client.info` | 公开 |
| `GET /v1/usage/{account}` | 用量和配额 | `client.usage` | `objects` |
| `POST /v1/objects` | 上传一个对象 | `client.upload` | `objects` |
| `POST /v1/objects/claim` | 认领服务器上已有的对象 | `client.claim` | `objects` |
| `GET /v1/objects/released` | 回收站：7 天之内用开发者密钥删掉的（TapeStore §4.5） | `client.released` | `objects` |
| `GET /v1/objects` | 列出记在名下的对象 | `client.list` | `objects` |
| `GET /v1/objects/{sha256}` | 读一个对象，支持 `Range` | `client.getObject` | 公开 |
| `HEAD /v1/objects/{sha256}` | 查是否存在、多大 | `client.has` | 公开 |
| `POST /v1/objects/release` | 释放（删除），一次最多 1,000 个 | `client.release` | 钱包，或带 `release` 的开发者密钥 |
| `PUT /v1/pointers/{account}/{name}` | 写签名指针 | `client.putPointer` | `pointers` |
| `GET /v1/pointers/{account}/{name}` | 读签名指针 | `client.getPointer` | 公开 |
| `POST /v1/outbox` | 发通知，由自己的服务器转交 | `client.submitNotice` | `send` |
| `GET /v1/outbox/{id}` | 查投递状态 | `client.outboxStatus` | 公开 |
| `GET /v1/inbox/{account}` | 拉取收到的通知 | `client.inbox` | `inbox` |
| `POST /v1/inbox/{account}/read` | 标记已读 | `client.markRead` | `inbox` |
| `POST /v1/self-admissions` | 给用户签准入、收回准入 | `updateAdmissions` | `admit` |
| `POST /v1/revocations` | 撤销会话密钥（持有人的签名在请求体里） | `client.revoke` | 仅钱包 |

`{account}` 是电路的链上名字（例如 `4246.0.tape`、Base 上的 `1.3.1.tape`；D-634 起协议里只认这种写法，客户端库的 `normalizeCircuitInput` 把短名、`#4246@0`、`tape://` 网址换成它，0x 容器地址要经内核反查）。按范围授权只在 `/v1/info` 的 `features` 列出 `scopes` 的服务器上能用。另有服务器之间转交通知的 `POST /v1/inbox/{account}`、给网关排行汇总用的 `GET /v1/stats/gateway`（TapeRelay §7.7，每台服务器每天的请求数、访客数、流量），和运营方自己用的 `/v1/admin/*`，开发者用不到。

## 9. 能放什么、不能放什么

| 能放 | 不能放 |
|---|---|
| 网页（HTML、JS、CSS、WebAssembly）、图片、视频、文档、数据文件 | 后台程序、数据库服务、定时任务（TapeRelay 不运行它们） |
| 只给自己或指定的人看的内容（用加密文件） | 任何密钥、密码、API Key、私钥、助记词（放进公开文件就等于公开） |

## 10. 公开文件谁都能读

- 读取不要签名：知道文件的哈希（或拿到分享链接）就能下载（TapeRelay §6.2）。链接会被转发、被搜索、被缓存。
- 上传之后**收不回来**：删除只是让运营方「可以」删掉字节（TapeStore §4.3），别人可能早已下载、缓存了。
- 所以公开文件里绝不能有密钥。这一点和放在任何网站托管、CDN 上一样：写进前端代码的 API Key，本来就会暴露给每个访客。
- 客户端库的 `scanForSecrets` 能在上传公开文件之前查常见的密钥写法（D-249），你的应用可以照着在上传前先问用户。它只认几种明确的写法、查不全（例如助记词不查），**不能代替你自己检查**。

## 11. 密钥放在哪里

1. **放在你自己的后台或云函数里**：网页去调你的后台，由后台带着密钥去调第三方服务。
2. 网页里只放本来就能公开的 Key：限定了域名、只读、额度很小的那种。
3. 节点网址里带着密钥的（例如某些区块链节点服务的网址），同样只能放在你的后台。
4. 本协议的开发者密钥（`trk1_` 开头）同样只放在后台：`scanForSecrets` 认得出它。

## 12. 要保密的内容：加密文件

- 在本机加密后上传，运营方和网络上的人只看到乱码（Files §5）；上传时可以「包给」别的电路，只有它们和你解得开。
- 加密适合「自己或指定的人看」，**不适合藏网站的秘密**：如果网页要在访客的浏览器里解密，就得把钥匙交给访客，等于公开。
- 能看出来的：文件大概多大（只把最后一块补齐）、什么时候上传、存在哪枚电路下。

## 13. 谁能写、怎样自动部署

- 只有电路持有人的钱包，或持有人授权的会话密钥，能上传、认领（TapeRelay §8）。会话密钥最长 24 小时（开发者密钥最长 366 天）、只能在授权里写的服务器上用、只能做授权里写的范围（第 3 节）、可以撤销。
- 删除（释放）要持有人的钱包直接签，或者用持有人另外勾了「允许删除」的开发者密钥（TapeStore §4.3、TapeRelay §8.7）；网页的 24 小时授权删不了：网页的会话密钥被偷，也删不了你的文件。钱包直接签的删除马上生效、不进回收站，找不回；用开发者密钥删的进回收站留 7 天，这期间照算用量、能找回。
- **自动部署用开发者密钥**，不要把钱包私钥放进部署脚本、CI 的环境变量或代码仓库。
- 要知道：网页的会话密钥**最长 24 小时**，每一把都要持有人的钱包签一次授权（TapeRelay §8.3）。自动部署不要用它：请持有人建一把开发者密钥（最长 366 天，TapeRelay §8.7），放进部署的环境变量；不要为了省事把钱包私钥放进脚本。
- 范围里有 `send` 的会话密钥能以你的电路的名义发文件通知；收件人会看到「这条通知由会话密钥签发」和授权的有效期（TapeRelay §9.7）。

## 14. 运营方能看到什么

| 能看到 | 看不到 |
|---|---|
| 谁（IP 地址）、什么时候读了哪个文件、文件多大 | 加密文件的内容 |
| 文件是哪枚电路存的、什么时候存的 | 你的钱包私钥、会话密钥的私钥 |
| 消息：谁发给谁、什么时候发的、多大 | 消息的内容（加密） |

运营方可能因法律原因下架某个文件（读取回 451，客户端换下一台，TapeRelay §6.2）：这也是要多存几家的原因之一（第 2 节、第 17 节）。

## 15. TapeRelay 能不能运行程序

**服务器上不能，也不应该。** 原因：

1. **核对不了结果**：TapeRelay 的一切都能按哈希、签名核对，所以你不必信任运营方。程序的运行结果没法这样核对，只能信运营方，就丢掉了最根本的保证。
2. **安全和滥用**：在运营方的机器上跑别人的代码，要隔离、要限资源，还要防挖矿和攻击，第三方运营方负担很重。
3. **定位**：TapeRelay 学的是 SMTP/IMAP 的分工，只管「存」和「送」；现在只卖存储和消息两种套餐，算力没法计费。

**浏览器里能。** 存进去的网页、脚本，会在访问者的浏览器里运行，跟静态网站托管一样。推荐的分工：

| 放在哪里 | 放什么 |
|---|---|
| TapeRelay | 界面（静态网页）、图片视频、公开的数据文件；要保密的数据用加密文件 |
| 链上合约 | 需要大家都信得过的逻辑和状态（转账、记账、权限） |
| 你自己的服务器 | 密钥、要调第三方服务的后台、需要保密的计算 |

## 16. 在浏览器里运行的隐患

1. **钓鱼网页**：谁都能存一个冒充别人的页面。教用户认准你的电路名字（tape:// 地址），不要只看页面长得像不像。
2. **网站之间要互相隔离**：否则一个网站的脚本可能读到另一个网站存在浏览器里的东西。这由 TapeKit 外壳和网关负责；本协议的网关送出文件时带 TapeKit 给链上网站的那组安全设置（内容安全策略、nosniff 等，TapeRelay §12.2）。收到别人发来的文件，你的应用应当只给下载，不要在自己的页面里打开（TapeRelay §12.5）。
3. **网页更新的权限**：按哈希存的网页别人改不了。用「签名指针」（TapeRelay §7.3，第 312 条起有）让一个名字换成新的版本时，谁能签指针谁就能推送更新：
   - **网站的指针只用钱包签**，不要交给会话密钥。会话密钥最长 24 小时（开发者密钥最长 366 天）、可以撤销，但读的人查不到撤销：会话密钥被偷，小偷签的指针在那份授权到期之前都算数；撤销之后等授权到期（最长 24 小时，带 `pointers` 的开发者密钥最长 366 天），再用钱包发布一次就改回来了（D-283：版号不得大于签名时间，谁都签不出能长期压住你的版号）。
   - 要「谁读到的都一定是最新的」，用链上的那种放法（TapeRelay §7.1 第 1 种）：指针存在运营方的服务器上，读的人问遍你的每台服务器、取最新的，但一台服务器能少给（只给旧的）；你的服务器都被拔掉时，读的人就读不到。
   - 电路转手之后，原来的持有人签的指针一律作废；新持有人发布时版号取当时的秒数，最多等 5 分钟。
   - **名字的位子删不掉**：每枚电路在每台服务器上最多 100 个名字，规范里还没有删名字的办法（I-232 第 3 点、I-234 第 2 点，以后再定）。会话密钥被偷（授权到期之前，最长 24 小时，开发者密钥 366 天）、或者电路转手之前的原持有人，都能把 100 个位子写满；之后你只能改指这 100 个名字，起不了新名字，撤销、转手、等到期都腾不出位子。所以要紧的名字（例如 `www`）先用钱包发布一次占住，会话密钥只交给你信得过的设备。
4. **流量**：读取目前不计费，热门网页可能把免费运营方的流量拖垮，运营方会限流。热门内容请选能承受流量的运营方，或者多存几家。
5. **直接网页托管**：节点原来能把上传的网站直接给普通浏览器打开，已经删掉。网站放在 TapeOut 的容器网站里。

## 17. 让文件一直在

- **存 3 家不同运营方**（第 2 节，D-336）：一家关了另外的还在；「核对副本」能补上缺的。
- 运营方要退出时，要提前 90 天在链上声明（D-244，合约原型已写、还没部署）；看到提醒就把文件搬到别家。
- 发现文件里的服务器名单是你在链上写的（TapeRelay §4）：换运营方就改它。

## 18. 上线前的检查清单

- [ ] 公开文件里没有密钥、密码、私钥、助记词、带密钥的网址（包括 `.env`、配置文件、源码映射 `.map`）。
- [ ] 要保密的数据用了加密文件，并确认不需要在访客浏览器里解密。
- [ ] 第三方服务的密钥放在自己的后台，网页里只有可以公开的 Key。
- [ ] 部署用开发者密钥，钱包私钥不在任何脚本、仓库、CI 里。
- [ ] 文件存了 3 家不同运营方，慢的那台有人补（模版 A 的后台定时补）。
- [ ] 会话密钥、开发者密钥只要用得到的范围；模版 A 的加密私钥另外备份好。
- [ ] 视频、图片用播放器组件在页面里解密着放（`mountPlayer`，第 4 节）。
- [ ] 想清楚运营方能看到什么（第 14 节），访问记录对你的用户是否敏感。
