Hotdry.
application-security

PWA 安装提示与 Service Worker:离线缓存、后台同步与推送通知工程实践

通过 Web App Manifest 和 Service Worker 实现 PWA 安装提示,支持离线缓存、后台同步及推送通知,提供无应用商店依赖的原生应用体验。

PWA(Progressive Web App)作为网页应用的升级形式,能够提供类似原生应用的安装、离线使用和通知功能,而无需依赖应用商店。通过 Web App Manifest 配置安装提示,并结合 Service Worker 实现资源缓存、后台同步与推送通知,即可构建高度可靠的用户体验。本文聚焦工程实践,给出具体参数配置、代码清单和监控要点,帮助开发者快速落地。

Web App Manifest:安装提示的核心配置

PWA 的安装提示首先依赖有效的 Web App Manifest 文件。该 JSON 文件定义应用的名称、图标、启动 URL 和显示模式,是浏览器判断是否可安装的关键。

最小 Manifest 配置清单

{
  "name": "我的 PWA 应用",
  "short_name": "PWA App",
  "icons": [
    {
      "src": "/icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "start_url": "/",
  "display": "standalone",
  "theme_color": "#ffffff",
  "background_color": "#ffffff",
  "prefer_related_applications": false
}
  • 必需字段nameshort_nameicons(至少 192px 和 512px)、start_urldisplay(推荐 standalone 以隐藏浏览器 UI)。
  • HTTPS 要求:应用必须在 HTTPS 或 localhost 下服务,否则无法触发安装。
  • 浏览器触发条件:用户互动 ≥1 次、停留 ≥30 秒,且未安装过。满足后,浏览器显示地址栏安装按钮,或触发 beforeinstallprompt 事件。

自定义安装按钮: 监听 beforeinstallprompt 事件,提供应用内按钮:

let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
  e.preventDefault();
  deferredPrompt = e;
  showInstallButton();  // 显示自定义按钮
});

installBtn.addEventListener('click', async () => {
  if (deferredPrompt) {
    deferredPrompt.prompt();
    const { outcome } = await deferredPrompt.userChoice;
    console.log(outcome);  // 'accepted' 或 'dismissed'
  }
});

此机制提升转化率,避免浏览器默认提示被忽略。

落地参数

  • 图标尺寸:优先 PNG,备 WebP;多尺寸覆盖(72x72、96x96、128x128、144x144、152x152、192x192、384x384、512x512)。
  • display 选项:standalone(推荐,原生感强)、minimal-ui(保留最小导航)。
  • 测试工具:Chrome DevTools > Application > Manifest,验证 installability。

Service Worker:离线缓存与生命周期管理

Service Worker 是 PWA 的代理层,负责资源拦截、缓存和后台任务。安装阶段缓存核心资产,确保离线可用。

SW 注册与生命周期

// app.js
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js', { scope: '/' })
    .then(reg => console.log('SW 注册成功', reg.scope));
}

SW 核心代码(sw.js)

const CACHE_NAME = 'pwa-v1';
const urlsToCache = [
  '/',
  '/index.html',
  '/styles.css',
  '/script.js',
  '/icons/icon-192.png',
  '/icons/icon-512.png'
];

// 安装:预缓存资产
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(urlsToCache))
  );
  self.skipWaiting();  // 立即激活新 SW
});

// 激活:清理旧缓存
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cacheName => {
          if (cacheName !== CACHE_NAME) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
  self.clients.claim();  // 立即接管页面
});

// Fetch:缓存优先策略
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then(response => response || fetch(event.request)
        .then(networkResponse => {
          const responseClone = networkResponse.clone();
          caches.open(CACHE_NAME).then(cache => cache.put(event.request, responseClone));
          return networkResponse;
        })
      )
      .catch(() => caches.match('/offline.html'))  // 离线回退页
  );
});

缓存策略要点

  • Cache-First:静态资源优先缓存,动态 API 网络优先。
  • 版本管理:CACHE_NAME 增量更新(如 'pwa-v2'),激活时删除旧版。
  • 导航预加载:激活时启用 self.registration.navigationPreload.enable(),加速页面加载。
  • 监控指标:Chrome DevTools > Application > Cache Storage,检查命中率;Lighthouse 审计离线分数 ≥90。

风险控制

  • 缓存上限:浏览器默认~50MB,避免 addAll 失败。
  • 更新策略:检测新 SW 后,提示用户刷新(registration.update())。

后台同步(Background Sync):离线数据可靠提交

后台同步允许离线操作,后续网络恢复时自动执行。适用于表单提交、消息同步。

注册同步

// 离线提交后
navigator.serviceWorker.ready.then(reg => {
  reg.sync.register('sync-data').then(() => console.log('同步注册'));
});

SW 处理

self.addEventListener('sync', (event) => {
  if (event.tag === 'sync-data') {
    event.waitUntil(syncPendingData());  // IndexedDB 或 localStorage 数据同步
  }
});

async function syncPendingData() {
  const pending = await getPendingItems();  // 从 IDB 读取
  for (const item of pending) {
    try {
      await fetch('/api/sync', { method: 'POST', body: JSON.stringify(item) });
      removePendingItem(item.id);
    } catch (e) {
      console.error('同步失败,重试');
    }
  }
}

参数配置

  • 标签唯一:'sync-messages'、'sync-orders'。
  • 重试阈值:浏览器自动重试,失败后延迟指数退避(1s、2s、4s...)。
  • 浏览器支持:Chrome/Edge 全支持,Firefox/iOS 有限。

推送通知(Push Notifications):后台唤醒用户

推送依赖 VAPID 密钥,实现服务器向客户端消息投递。

订阅

// app.js
navigator.serviceWorker.ready.then(reg => {
  reg.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
  }).then(sub => {
    // 发送 sub 到服务器
    fetch('/api/subscribe', { method: 'POST', body: JSON.stringify(sub) });
  });
});

SW 处理推送

self.addEventListener('push', (event) => {
  const data = event.data ? event.data.json() : { title: '默认标题' };
  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: '/icons/icon-192.png',
      badge: '/icons/badge-72.png'
    })
  );
});

VAPID 生成(Node.js web-push):

web-push generate-vapid-keys

公钥嵌入前端,私钥服务器端加密 payload。

工程参数

  • TTL:推送有效期 4-7 天。
  • 图标:badge 72x72 单色。
  • 配额:Firefox 限 100 / 天(通知豁免),Chrome 无上限。

集成监控与回滚

Lighthouse 审计

  • PWA 分数 ≥100:安装、离线、快速启动。
  • 监控:Chrome UX Report,First Input Delay <100ms。

回滚策略

  • SW 更新失败:保留旧版,日志 Sentry。
  • Manifest 变更:增量图标,避免破坏安装。

通过以上配置,PWA 可实现 100% 离线可用、可靠同步和实时通知,媲美原生 app。实际部署中,从简单 Manifest + Cache-First 开始迭代。

资料来源

  1. MDN: Making PWAs installable - 浏览器安装标准包括 HTTPS 和 Manifest 必需字段。
  2. MDN: Using Service Workers - SW 生命周期确保缓存一致性。
  3. web.dev: Install criteria - 用户互动 heuristics 为 30 秒停留。

(正文字数:1256)

查看归档