# 衡量 PWA 显示、安装与离线事件

> 记录当前显示模式、成功的 appinstalled 事件及在线或离线切换，同时避免把这些信号当作安装证明。

PWA 埋点可以记录当前显示模式和当前页面收到的成功安装事件 `appinstalled`。浏览器报告的在线和离线切换也可以记录。分别记录这些信号；它们都不能可靠地跨浏览器判断 PWA 是否已安装。

## 浏览器与平台支持

CSS `display-mode` 媒体特性可以区分 `browser` 和 `standalone` 等模式，也能区分
`minimal-ui` 和 `fullscreen`。当前模式不等于安装状态：MDN 指出，已安装 PWA 仍可在浏览器标签页中
打开，普通网页也可进入全屏。

浏览器成功把页面安装为应用时会触发 `appinstalled`，但 MDN 将其标为有限可用，收不到事件也不能认定应用未安装。

`online` 和 `offline` 随 `navigator.onLine` 变化而触发。这个值只提示网络状态，分析端点是否可达还要看请求结果。

## 选择事件结构

让采集字段与文档定义的信号保持一致。示例发送事件名，附上 ISO 时间戳和一个小型详情对象。账户标识和 URL 查询不随事件发送，
表单值也不发送。部署分析端点前，添加符合产品隐私政策的采集与保留规则。

:::caution
[创建独立应用](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/How_to/Create_a_standalone_app)
说明，不存在可靠的跨浏览器方法来判断 PWA 是否已安装。把 `display-mode` 当作当前呈现模式，
把 `appinstalled` 当作一次事件，而不是持久安装标记。
:::

## 示例

先把记录存入本地队列，等应用提交。

### 在页面加载时记录显示模式

按顺序测试文档定义的模式，并以 `browser` 作为回退。示例还用 `matchMedia` 监听后续模式变化。

```js
const displayQueries = [
  ['window-controls-overlay', '(display-mode: window-controls-overlay)'],
  ['fullscreen', '(display-mode: fullscreen)'],
  ['standalone', '(display-mode: standalone)'],
  ['minimal-ui', '(display-mode: minimal-ui)'],
];

function activeDisplayMode() {
  if (!('matchMedia' in window)) return 'browser';
  return displayQueries.find(([, query]) => matchMedia(query).matches)?.[0] ?? 'browser';
}

function record(name, detail = {}) {
  const event = { name, detail, occurredAt: new Date().toISOString() };
  const backlog = JSON.parse(localStorage.getItem('pwa-analytics') ?? '[]');
  backlog.push(event);
  localStorage.setItem('pwa-analytics', JSON.stringify(backlog));
}

record('pwa_display_mode', { mode: activeDisplayMode() });

if ('matchMedia' in window) {
  for (const [, query] of displayQueries) {
    matchMedia(query).addEventListener('change', () => {
      record('pwa_display_mode', { mode: activeDisplayMode() });
    });
  }
}
```

记录先存入本地待发送队列。用应用自己的接收与重试策略替换 `record()`。

### 记录安装与连接事件

先检测 `appinstalled`，再添加监听器；无法监听时，记录的是无法观察事件，不能记成安装失败。在线状态则直接采用浏览器报告的值。

```js
if ('onappinstalled' in window) {
  window.addEventListener('appinstalled', () => {
    record('pwa_appinstalled');
  });
} else {
  record('pwa_appinstalled_unavailable');
}

if ('ononline' in window && 'onoffline' in window) {
  window.addEventListener('online', () => {
    record('pwa_connectivity', { state: 'online' });
  });
  window.addEventListener('offline', () => {
    record('pwa_connectivity', { state: 'offline' });
  });
  record('pwa_connectivity', {
    state: navigator.onLine ? 'online' : 'offline',
  });
} else {
  record('pwa_connectivity_unavailable');
}
```

由于分析服务器可能不可达，`offline` 记录应留在本地队列中。后续应用请求可提交积压记录，并且
只删除服务器已经接受的记录。

## 解读信号

- `pwa_display_mode` 描述采样时顶层浏览上下文的显示方式。
- `pwa_appinstalled` 表示当前页面收到了文档定义的成功安装事件。
- `pwa_appinstalled_unavailable` 表示当前页面无法添加该事件的监听器。
- `pwa_connectivity` 反映 `navigator.onLine` 及其事件，不代表对应用或分析服务器的请求成功。

在报表中保持这些含义相互独立。例如，不要把 `standalone` 样本改名为“已安装用户”，也不要
把缺少 `pwa_appinstalled` 当作卸载。

## 另请参阅

- [让 PWA 可安装](/zh/guides/installable/)
- [Manifest display 浏览器支持](/zh/compatibility/manifest-display/)
- [离线策略](/zh/guides/offline/)
- [display-mode](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/%40media/display-mode) (MDN)
- [Window: appinstalled event](https://developer.mozilla.org/en-US/docs/Web/API/Window/appinstalled_event) (MDN)
- [Navigator: onLine property](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine) (MDN)