内容
# 分享 HTML
<p align="center">
<a href="https://sharehtml.zhenjia.dev">
<img src="./public/logo.svg" alt="分享 HTML logo" width="112" height="112">
</a>
</p>
<p align="center">
上传一个 HTML 文件,然后分享一个沙盒预览链接,可以是公开的,也可以是私有的。
</p>
<p align="center">
<a href="https://sharehtml.zhenjia.dev"><img alt="实时网站" src="https://img.shields.io/badge/实时网站-sharehtml.zhenjia.dev-E85D3F?style=flat-square"></a>
<a href="https://github.com/lifeodyssey/share-html"><img alt="GitHub 源代码" src="https://img.shields.io/badge/源代码-GitHub-26322F?style=flat-square&logo=github"></a>
<a href="https://github.com/lifeodyssey/share-html/actions/workflows/ci.yml"><img alt="持续集成" src="https://img.shields.io/github/actions/workflow/status/lifeodyssey/share-html/ci.yml?branch=main&style=flat-square&label=持续集成"></a>
<img alt="Cloudflare Workers" src="https://img.shields.io/badge/Cloudflare-Workers-F38020?style=flat-square&logo=cloudflareworkers&logoColor=white">
<img alt="Supabase" src="https://img.shields.io/badge/Supabase-身份验证%20%2B%20Postgres-3FCF8E?style=flat-square&logo=supabase&logoColor=white">
<img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-5.9-3178C6?style=flat-square&logo=typescript&logoColor=white">
</p>
## 链接
- 实时应用:[https://sharehtml.zhenjia.dev](https://sharehtml.zhenjia.dev)
- GitHub 仓库:[https://github.com/lifeodyssey/share-html](https://github.com/lifeodyssey/share-html)
- Workers 备用:[https://share-html.zhenjiazhou0127.workers.dev](https://share-html.zhenjiazhou0127.workers.dev)
## 项目文档
- 产品和系统规格:[docs/project/spec.md](./docs/project/spec.md)
- 当前进度:[docs/project/progress.md](./docs/project/progress.md)
- 路线图:[docs/project/roadmap.md](./docs/project/roadmap.md)
## 功能介绍
分享 HTML 是一个小型的、由 Cloudflare 托管的工具,用于分享独立的 HTML 文件。它适用于快速原型、模型、收据、微型演示和一次性页面,这些页面需要一个 URL,而无需建立一个网站。
- 匿名上传流程,有效期为 365 天。
- Supabase 魔术链接登录,用于保留和删除分享,身份验证电子邮件由 Cloudflare 电子邮件服务处理。
- 声明令牌流程,用于将匿名上传附加到帐户稍后。
- 公开未列出和访问密钥保护的私有链接可见性模式。
- 分享页面,包含状态、风险评分和嵌入式预览,在任何所需的私有链接解锁后。
- 直接预览 URL,用于独立打开上传的 HTML。
- 轻量级扫描器,用于检测可疑的 HTML 模式。
- 报告 API 和管理员审核端点。
## URL 模型
分享 HTML 有意公开上传后的两个不同 URL 界面:
- **分享 URL** 在 `/s/:slug` 处打开包装页面。这是最佳的发送链接,因为它包含状态、安全上下文、报告操作和嵌入式预览。
- **预览 URL** 在 `/v/:slug/` 处打开沙盒 HTML 渲染。对于私有链接,它需要解锁过程中创建的短时预览 cookie。
- **声明令牌** 是私有的。在登录后,将其与分享 ID 一起使用,如果您想将匿名上传移动到您的帐户中。
每个上传也选择一种可见性:
- **`public_unlisted`** 是默认的。任何知道 URL 的人都可以读取其元数据和打开其预览。分享和预览路由被标记为 noindex,并且永远不会添加到 `sitemap.xml` 中,但未列出并不等同于身份验证。
- **`private_link`** 返回一个 32 字节的 base64url `accessKey`。返回的 `share.share_url` 在客户端 `#key=` 片段中携带密钥,以便初始 HTTP 请求不会将其发送到服务器。将完整的私有 URL 视为一个秘密。
`claimToken` 和 `accessKey` 是故意不同的能力:第一个在登录后传输所有权;第二个打开私有链接,不授予所有权。
## HTTP API
创建公开未列出的分享(默认):
```bash
curl -X POST https://sharehtml.zhenjia.dev/api/shares \
-F 'file=@page.html' \
-F 'title=我的页面'
```
创建私有链接:
```bash
curl -X POST https://sharehtml.zhenjia.dev/api/shares \
-F 'file=@page.html' \
-F 'visibility=private_link'
```
两个成功响应都使用相同的形状:
```json
{
"share": { "id": "...", "slug": "...", "share_url": "...", "preview_url": "..." },
"claimToken": "... 或 null",
"accessKey": "... 或 null",
"message": "已上传。"
}
```
`accessKey` 为 `public_unlisted` 时为 `null`。要解锁私有链接而无需浏览器包装:
```bash
curl -X POST https://sharehtml.zhenjia.dev/api/shares/SLUG/access \
-H 'content-type: application/json' \
--data '{"accessKey":"PRIVATE_ACCESS_KEY"}'
```
成功返回 `{ "share": ... }` 并设置两个短期的、仅限主机的 HttpOnly 授权:一个范围限定为 `/v/:slug`,用于预览;另一个范围限定为确切的解锁端点,以便刷新可以恢复元数据,在浏览器删除地址栏中的 `#key` 后。
## 代理访问
分享 HTML 旨在不仅供人类使用,也供 AI 代理使用:
- **`llms.txt`** — AI 可读的站点指南(也从 `/` 提供,当请求发送 `Accept: text/markdown` 时)。
- **`openapi.json`** — 机器可读的 HTTP API 描述,包括两种可见性模式和私有解锁/cookie 流。首页 HTML 还嵌入静态内容 + JSON-LD,以便非 JS 代理可以读取站点信息以及如何调用它。
- **MCP 机器端点** — [`https://share-html.zhenjiazhou0127.workers.dev/mcp`](https://share-html.zhenjiazhou0127.workers.dev/mcp) 公开 `describe_share_html`、`get_public_share` 和 `create_share`。它使用与品牌站点相同的生产 Worker,同时避免了机器到机器流量上的区域级浏览器机器人策略。页面通过 WebMCP(`navigator.modelContext`)在浏览器中公开相同的工具。
- **`/a2a`** — A2A 1.0 JSON-RPC 端点,具有已实现的 `describe_share_html` 技能;其卡片故意仅宣传该可执行 A2A 能力。
- WebMCP `create_share` 允许代理选择 `public_unlisted` 或 `private_link`;**`access_private_share`** 交换访问密钥以获取元数据以及作用域元数据和预览授权。两者都通过正常的 HTTP API 运行。
- MCP `create_share` 支持 `public_unlisted` 和 `private_link`。它通过与 Web 上传相同的匿名速率限制和风险扫描器运行;私有创建返回其密钥一次。
## 获取循环
该产品现在有四个可爬取的第一方入口页面,而不是仅依赖发现文件:
- [`/html-preview`](https://sharehtml.zhenjia.dev/html-preview) 解释了单个文件预览边界,并包含上传 CTA。
- [`/private-html-sharing`](https://sharehtml.zhenjia.dev/private-html-sharing) 解释了访问密钥隐私及其限制。
- [`/examples`](https://sharehtml.zhenjia.dev/examples) 提供了三个可以预加载到上传器的自包含文件。
- [`/agents`](https://sharehtml.zhenjia.dev/agents) 提供了可用的 MCP、HTTP、OpenAPI、WebMCP、技能和 A2A 入口点。
只有这些页面和首页被提交到 `sitemap.xml`。上传的 `/s/:slug` 和 `/v/:slug/` URL 保持 noindex,并从所有发现提交中排除。包装页面包含可见的“分享你的 HTML”CTA;其 `source=shared_preview` 值仅存储在现有的 `created` 事件元数据中,连同 `visibility`,这使得产品驱动的激活在不添加新的跟踪服务的情况下是可衡量的。
## 堆栈
- Cloudflare Workers 静态资产用于应用壳和 API。
- Cloudflare R2 用于上传的 HTML 对象。
- Cloudflare 电子邮件服务用于品牌身份验证电子邮件投递。
- Supabase Auth 用于魔术链接用户。
- Supabase Postgres 用于元数据、报告、事件和审核状态。
- React、Vite 和 TypeScript 用于前端。
## 本地设置
```bash
npm install
cp .env.example .dev.vars
npm run build
npm run dev
```
在生产部署之前设置这些 Cloudflare Worker 机密:
```bash
printf '%s' '<legacy-anon-key>' | npx wrangler secret put SUPABASE_REST_KEY
printf '%s' '<worker-api-secret>' | npx wrangler secret put WORKER_API_SECRET
printf '%s' '<random-salt>' | npx wrangler secret put IP_HASH_SALT
printf '%s' '<independent-random-32-byte-secret>' | npx wrangler secret put SHARE_ACCESS_PEPPER_V1
printf '%s' '<different-random-32-byte-secret>' | npx wrangler secret put PREVIEW_GRANT_SIGNING_KEY_V1
printf '%s' '<8-to-128-character-key>' | npx wrangler secret put INDEXNOW_KEY
printf '%s' 'v1,whsec_...' | npx wrangler secret put SUPABASE_SEND_EMAIL_HOOK_SECRET
```
`SHARE_ACCESS_PEPPER_V1` 派生存储的访问密钥哈希;`PREVIEW_GRANT_SIGNING_KEY_V1` 签署短期的元数据和预览授权。独立生成它们,不要在替换 V1 值时直接替换:添加新版本并在更改派生方案时迁移存储的 `access_key_version` 值,并在轮换授权时支持签名密钥重叠窗口。
## 部署
生产环境运行为一个名为 `share-html` 的 Cloudflare Worker。部署的源代码是 [`wrangler.jsonc`](./wrangler.jsonc):它定义了 Worker 入口点、静态资产、自定义域名、R2 存储桶绑定和公共运行时变量。
部署由 GitHub 分支保护和 Cloudflare Workers Builds 执行:
- 拉取请求运行 `npm ci`、`npm run build` 和 `npx wrangler deploy --dry-run`。
- `main` 分支在更改可以落地之前需要 `Build` 检查。
- Cloudflare Workers Builds 监视 `main`,构建该分支,并在合并后部署 Worker。
这使得 Cloudflare 部署凭证远离 GitHub。GitHub Actions 中不需要 Cloudflare API 令牌或部署钩子机密。
## 安全说明
上传的 HTML 不会被净化。相反,它是隔离的:
- 预览页面通过专用 `/v/:slug/` 路由流式传输。
- 分享页面在 iframe 沙盒中嵌入预览。
- R2 对象是私有的,只能由 Worker 读取。
- 对预览响应应用了限制性响应头。
- 响应级 CSP `sandbox` 省略了 `allow-same-origin`,因此上传的脚本即使在有人在顶级选项卡中打开预览时,也能保持不透明的源。
- 在流式传输上传内容之前会检查审核状态。
- 私有链接元数据需要访问密钥、所有者会话或仍然有效的元数据授权;预览需要其单独的短期、路径范围的 HttpOnly 授权。
- 私有访问密钥在分享 URL 片段中用于浏览器交接;片段不会在 HTTP 请求中发送,但完整的 URL 仍应作为机密处理。
- Supabase RLS 需要私有 Worker 机密头进行服务器端元数据写入。