# PWA 接口

> 本节描述玩家端 PWA（Progressive Web App）相关接口：动态 manifest、安装态上报。
>
> 接入流程：玩家在浏览器登录 → 引用 `/common/manifest` 安装 PWA → 安装后从主屏幕打开时浏览器自动跳转到 `manifest.start_url`（已带 `?pa=<pwa_token>`）→ 前端拿到 `pa` 完成自动登录 → 调用 [`/pwa/activate`](#activate) 上报安装态。

---

## 上报 PWA 安装态 {#activate}

标记当前会员为 PWA 用户（后台「是否 PWA」筛选口径）。注册时传 `source=pwa` 的会员已自动标记；**先在浏览器注册、之后才安装 PWA** 的会员没有注册环节，必须由前端调本接口补标。**需登录态**，幂等，重复调用无副作用。

前端触发时机（任一满足即调一次，可用 localStorage 记标记避免每次启动重复请求）：

- 启动 URL 带 `pa` 参数（即经 manifest `start_url` 从主屏幕打开）；
- `window.matchMedia('(display-mode: standalone)').matches` 或 `navigator.standalone === true`。

调用 [`/pwa/subscribe`](111_pwa_push.md#subscribe) 订阅推送成功时服务端也会顺带标记，但玩家可能拒绝通知权限，不能以此替代本接口。

**URL:** `POST` /pwa/activate

**输入:** 无

**输出:** 无

---

## 获取 PWA manifest {#manifest}

动态生成 manifest.json。**未登录时**返回纯 manifest；**已登录时**会读取或为当前会员生成持久化的 `pwa_token`，并把它拼到 `start_url` 上以 `?pa=<pwa_token>` 形式返回。

> ⚠️ 与其它接口不同，本接口直接返回 W3C Web App Manifest 格式的对象（**不**走 `data`/`message` 包装），以便 `<link rel="manifest" href="...">` 直接消费。

**URL:** `GET` /common/manifest

**Query:**

字段 | 类型 | 必填 | 说明
---|---|---|---
start_url | string | 否 | PWA 启动地址，未传则默认 `/`；登录态会在末尾追加 `?pa=<pwa_token>`（已带 query 时改用 `&`）
domain | string | 否 | 用于拼接默认图标的域名（当商户未配置 `pwa_logo_192/512` 时，回退到 `<domain>/icons/icon-192x192.png`）

**请求头:**

字段 | 必填 | 说明
---|---|---
Authorization | 否 | 传入则按登录态生成（自动写入 `start_url`），不传则按匿名生成

**输出:**

字段 | 类型 | 说明
---|---|---
name | string | PWA 名称，取商户配置 `pwa_title`，回退到商户名
short_name | string | 同 `name`
description | string | 同 `name`
background_color | string | 启动背景色，固定 `black`
theme_color | string | 主题色，固定 `#0D0E27`
display | string | 显示模式，固定 `standalone`
orientation | string | 屏幕方向，固定 `portrait`
scope | string | 作用域，固定 `/`
start_url | string | 启动地址；登录态自动带 `?pa=<pwa_token>` 用于免密登录
icons | list<object\> | 图标数组，包含 192×192 与 512×512 两个尺寸（`src` / `sizes` / `type`）

**示例（登录态）:**

```json
{
  "name": "DemoSite",
  "short_name": "DemoSite",
  "description": "DemoSite",
  "background_color": "black",
  "theme_color": "#0D0E27",
  "display": "standalone",
  "orientation": "portrait",
  "scope": "/",
  "start_url": "/?pa=1234567890123456",
  "icons": [
    {"src": "https://cdn.example.com/icon-192.png", "sizes": "192x192", "type": "image/png"},
    {"src": "https://cdn.example.com/icon-512.png", "sizes": "512x512", "type": "image/png"}
  ]
}
```
