# 接收分享的内容（share target）

> 一份分步指南：如何在 Web App Manifest 中声明 share_target，将已安装的 PWA 注册为分享目标；如何处理它触发的 GET 或 POST 请求；以及如何存储分享的文件以供应用读取。

import CompatTable from '@components/CompatTable.astro';

**一句话概括：** 在 Web App Manifest 中声明 `share_target`，会将已安装的 PWA 注册到操作系统
原生的分享面板中，这样其他应用就可以直接把 URL、文本、标题和文件分享到你的应用，而不需要
你自己搭建一套单独的导入流程。

## 1. 在 manifest 中声明 share_target

根据 MDN 的说明，`share_target` 需要一个 `action`（接收分享内容的 URL）和 `params`
（分享字段到请求参数的映射）；`method` 与 `enctype` 是可选的，默认分别为 `"GET"` 与
`"application/x-www-form-urlencoded"`：

```json
{
  "share_target": {
    "action": "/share-handler",
    "method": "POST",
    "enctype": "multipart/form-data",
    "params": {
      "title": "title",
      "text": "text",
      "url": "url",
      "files": [
        { "name": "media", "accept": ["image/*", "video/*"] }
      ]
    }
  }
}
```

默认的 `GET` 且不带 `files` 就足以接收纯文本/URL 分享；接收文件则必须显式设置
`"method": "POST"` 与 `"enctype": "multipart/form-data"`，这一点来自 MDN 的说明。

## 2. 在 Service Worker 中处理请求

当操作系统在分享面板中提供了你的应用、且用户选择了它之后，浏览器会向 `action` 发送
一个 `POST`（或 `GET`）请求，其形式与表单提交完全一致。在 Service Worker 的 `fetch`
处理程序中拦截该请求并读取分享的数据。MDN 表示，POST 分享请求最好以 `303` 重定向响应，
以避免刷新时重复提交 POST；Chrome for Developers 也演示了这种模式：

```js
self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url);
  if (event.request.method === 'POST' && url.pathname === '/share-handler') {
    event.respondWith((async () => {
      const formData = await event.request.formData();
      const files = formData.getAll('media');
      const cache = await caches.open('shared-content');
      await Promise.all(
        files.map((file, i) => cache.put(`/shared-file-${i}`, new Response(file)))
      );
      // 重定向（带上应用检测分享启动所需的标记）到一个会把缓存的文件读回来的页面。
      return Response.redirect('/share-handler/view?shared=1', 303);
    })());
  }
});
```

## 3. 在应用中读取分享的数据

被重定向到的页面会读取 Service Worker 存储的内容——也就是上面缓存的文件——并在导入前
展示给用户：

```js
async function loadSharedFiles() {
  const cache = await caches.open('shared-content');
  const keys = await cache.keys();
  const files = await Promise.all(
    keys
      .filter((req) => req.url.includes('/shared-file-'))
      .map((req) => cache.match(req).then((res) => res.blob()))
  );
  return files;
}
```

## 支持情况

`share_target` 具体支持哪些浏览器、平台与版本，请参见下方的兼容性数据：

<CompatTable feature="manifest-share-target" />

## 检测重定向路径并提供回退

此示例自行在重定向中添加了 `shared` 查询参数。页面可以检测这条由应用定义的重定向路径，
并在未检测到时渲染正常视图：

```js
function isSharedRedirect() {
  return new URLSearchParams(location.search).has('shared');
}

async function loadSharedFiles() {
  if (!('caches' in window)) {
    // 当前上下文不支持 Cache Storage——没有可读回的内容。
    return [];
  }
  const cache = await caches.open('shared-content');
  const keys = await cache.keys();
  return Promise.all(
    keys
      .filter((req) => req.url.includes('/shared-file-'))
      .map((req) => cache.match(req).then((res) => res.blob()))
  );
}

if (isSharedRedirect()) {
  loadSharedFiles().then(renderSharedFiles);
} else {
  // 不存在应用定义的重定向标记——渲染正常的初始视图。
  renderDefaultView();
}
```

## 常见问题

- [ ] `share_target` 只对**已安装**的 PWA 生效——未安装的普通标签页不会出现在操作系统
      的分享面板中。
- [ ] 根据 MDN 的说明，接收文件需要 `"method": "POST"` 与
      `"enctype": "multipart/form-data"`——仅支持 GET 的目标无法接收 `files`。
- [ ] 处理 POST 后优先使用 `303` 重定向；MDN 称这是避免刷新页面时重复提交 POST 的
      理想做法。
- [ ] 为被重定向到的路由提供一个由应用定义的标记（例如查询参数），这样当它被直接打开时
      可以回退到正常界面。
- [ ] 在读取分享的文件后清除或使其过期，避免重复分享导致陈旧数据不断累积。

## 下一步

- [manifest share_target 参考](/zh/reference/manifest/share-target/) —— 完整的
  `share_target` 语法，包括 GET 与 POST 的参数映射方式。
- [manifest: share_target 支持情况](/zh/compatibility/manifest-share-target/) —— 该
  manifest 成员的按浏览器兼容性数据。