WWDC Quick Look 💓 By SwiftGGTeam
What's new in web apps

What's new in web apps

观看原视频

Highlight

macOS Sonoma 支持将任意网站添加到 Dock 作为独立 Web App,拥有独立窗口、Cookie 和系统级权限管理;配合 Web App Manifest 可控制工具栏、链接跳转范围和 display 模式,Web Push 通知和 Badge 同时支持 macOS 和 iOS/iPadOS 的 Home Screen Web App。

核心内容

macOS 上的 Web App

macOS Sonoma 新增了 Web App 功能。在 Safari 中浏览任意网站时,选择 File > “Add to Dock…”,网站就会以独立应用的形式出现在 Dock 中。添加时会复制网站的 Cookie,所以用户通常保持登录状态。

Web App 拥有简化的工具栏,网站的主题色会融入工具栏背景。它与原生 macOS 应用一样支持 Stage Manager、Mission Control、Command+Tab 切换,可以从 Dock、Launchpad 和 Spotlight 打开。

权限管理也与原生应用一致。相机、麦克风和位置访问通过系统权限弹窗请求,在系统设置的 Privacy & Security 中管理。

00:51

Web App Manifest 定制

开发者可以通过 Web App Manifest 控制 Web App 的行为。在 HTML 的 head 中添加:

<link rel="manifest" href="/manifest.json">

Manifest 中的关键字段:

  • name:Web App 的名称(替代网页标题)
  • display:显示模式,standalone 表示无工具栏
  • start_url:Web App 首次打开时加载的 URL
  • scope:定义链接在 Web App 内打开的范围
  • id:同一域名下区分不同 Web App 的唯一标识

05:01

display: standalone 的效果在不同平台有区别:

  • macOS:不显示工具栏
  • iOS/iPadOS:以 Home Screen Web App 形式打开,拥有独立的 Cookie 和存储,与浏览器隔离

如果需要在 iOS 上使用 Web Push 和 Badge,必须使用 standalone 模式。

链接跳转和 Scope

Web App 有一个关联的 scope。Scope 内的链接在 Web App 内打开,Scope 外的链接在默认浏览器中打开。默认 scope 是创建 Web App 时网页的 host。

可以通过 manifest 中的 scope 字段进一步限制。比如将 scope 设为 example.com/browserpets,那么 example.com/webkittens 的链接就会在默认浏览器中打开。

在 iOS 的 Home Screen Web App 中,Scope 外的链接会在 Safari View Controller 中打开。

05:53

Web App 创建时会复制 Safari 中的 Cookie,所以用户通常保持登录。但 local storage 不会被复制。如果认证状态分散在 Cookie 和 local storage 中,用户需要重新登录。

建议将认证状态保存在 Cookie 中。OAuth 流程通常会在 Web App 内打开(通过启发式判断),如果发现问题可以向 Apple 反馈。window.open 打开的链接始终在 Web App 内,不受 scope 限制。

邮件中的自动登录链接会在默认浏览器中打开,不会自动登录到已有的 Web App。可以考虑在邮件中提供一次性验证码作为替代方案。更推荐的做法是采用 passkey 认证。

07:37

Web Push 通知和 Badge

macOS 上的 Web App 支持标准的 Web Push。如果已经按 web 标准实现了 Web Push,不需要额外工作即可在 macOS Web App 中使用。

通知会显示 Web App 的图标(而不是 Safari 图标),给用户正确的上下文。

声音控制遵循平台惯例:iOS/iPadOS 默认开启声音,macOS 默认关闭。可以通过 Notification API 的 silent 选项覆盖:

new Notification("New message", { silent: false });

Badge 在 macOS Web App 和 iOS Home Screen Web App 中都受支持。用户允许通知时,同时授予了 Badge 权限。Badge 可以在 Web App 打开时更新,也可以在后台处理 push 事件时更新。

10:00

Focus 模式集成

Web App 的通知与 Focus 模式集成。用户可以在不同 Focus 模式下设置不同的通知偏好,这些偏好会跨设备同步。

Manifest 中的 id 字段用于区分同一域名下的不同 Web App。比如一个域名下有 shop 和 forums 两个 Web App,可以给它们分别设置 id: "shop"id: "forums",用户就能为它们设置不同的通知偏好。

Focus 设置按 name + id 同步。用户可以在同一设备上创建同一个网站的多个实例(如”Forums”和”Forums - Work”),分别设置不同的通知和 Focus 偏好。

12:54

新增 Web API

Safari 今年新增了以下 API:

  • User Activation API:告知网站用户是否进行了瞬态或持久性激活,用于判断是否可以调用依赖用户激活的功能(如请求通知权限)
  • Fullscreen API:Safari 16.4 在 macOS 和 iPadOS 中移除了前缀,支持标准 Fullscreen API
  • Screen Orientation API:初步支持 typeangle 属性和 onchange 事件

14:29

详细内容

Web App Manifest 完整示例

{
  "name": "Browser Pets",
  "short_name": "Pets",
  "description": "A virtual pet community",
  "start_url": "/browserpets",
  "scope": "/browserpets",
  "display": "standalone",
  "theme_color": "#FF6B6B",
  "background_color": "#FFFFFF",
  "icons": [
    { "src": "/icon-192.png", "sizes": "192x192" },
    { "src": "/icon-512.png", "sizes": "512x512" }
  ],
  "id": "browserpets"
}

关键点:

  • name 用于 Dock/主屏幕显示,short_name 用于空间不足时
  • start_url 是 Web App 首次打开时加载的页面
  • scope 限制在 Web App 内打开的链接范围,必须是 manifest URL 的子目录
  • display: standalone 在 macOS 隐藏工具栏,在 iOS 创建独立的 Home Screen Web App
  • theme_color 影响 macOS 工具栏的背景色
  • id 区分同一域名下的不同 Web App,用于 Focus 模式同步

HTML 中引用 Manifest

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="theme-color" content="#FF6B6B">
  <title>Browser Pets - Your Virtual Pet Community</title>
  <link rel="manifest" href="/manifest.json">
  <link rel="apple-touch-icon" href="/icon-192.png">
</head>
<body>
  <!-- 页面内容 -->
</body>
</html>

关键点:

  • theme-color meta 标签与 manifest 中的 theme_color 保持一致
  • apple-touch-icon 用于 iOS 主屏幕图标
  • manifest link 必须放在 head

Web Push 和 Service Worker

// 注册 Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(registration => console.log('SW registered'))
    .catch(err => console.error('SW registration failed', err));
}

// 请求推送通知权限
async function subscribeToPush() {
  const registration = await navigator.serviceWorker.ready;
  
  const subscription = await registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(
      'BEl62i...'
    )
  });
  
  // 将 subscription 发送到服务器
  await fetch('/api/push-subscription', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(subscription)
  });
}

关键点:

  • Web Push 需要 Service Worker 支持
  • pushManager.subscribe 需要用户激活(点击等交互)才能调用
  • applicationServerKey 是服务器的 VAPID 公钥
  • userVisibleOnly: true 表示每条推送都会显示通知

Service Worker 中处理推送:

// sw.js
self.addEventListener('push', event => {
  const data = event.data?.json() ?? {};
  
  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: '/icon-192.png',
      badge: '/badge-72.png',
      silent: false,
      data: { url: data.url }
    })
  );
});

// 点击通知打开对应页面
self.addEventListener('notificationclick', event => {
  event.notification.close();
  event.waitUntil(
    clients.openWindow(event.notification.data.url)
  );
});

关键点:

  • showNotification 在 Service Worker 中显示系统通知
  • silent: false 覆盖 macOS 默认静音,播放通知声音
  • badge 是小图标,显示在通知栏和 Dock 角标中
  • notificationclick 处理用户点击通知的行为

Badge API 使用

// 设置 Badge 数字
navigator.setAppBadge(42);

// 清除 Badge
navigator.clearAppBadge();

// 在后台处理 push 时更新 Badge
self.addEventListener('push', event => {
  event.waitUntil(
    getUnreadCount().then(count => {
      return self.navigator.setAppBadge(count);
    })
  );
});

关键点:

  • navigator.setAppBadge() 设置 Dock(macOS)或主屏幕图标(iOS)的角标
  • 用户允许通知时自动获得 Badge 权限
  • 在 Service Worker 中通过 self.navigator.setAppBadge() 更新
  • Badge 在 Web App 前台和后台都可以更新

认证状态管理

// 推荐:将认证状态保存在 Cookie 中
// 服务器端设置
// Set-Cookie: session=abc123; Path=/; HttpOnly; Secure; SameSite=Lax

// 避免:将认证状态分散在 localStorage 中
// 这样 Web App 创建时不会复制,用户需要重新登录

// 如果必须使用 localStorage,在首次加载时检查并引导重新认证
async function checkAuthState() {
  const sessionCookie = document.cookie.match(/session=([^;]+)/);
  const localToken = localStorage.getItem('auth_token');
  
  if (sessionCookie && !localToken) {
    // Cookie 存在但 localStorage 缺失,需要重新获取令牌
    const response = await fetch('/api/refresh-token', {
      credentials: 'include'
    });
    const data = await response.json();
    localStorage.setItem('auth_token', data.token);
  }
}

关键点:

  • Web App 创建时复制 Cookie 但不复制 localStorage
  • 纯 Cookie 认证的网站用户通常保持登录
  • 混合认证需要在首次加载时检测并引导重新认证
  • Passkey 是比邮件链接更安全和便捷的认证方案

核心启发

1. 为网站添加 Web App Manifest

  • 做什么:为现有网站添加 manifest.json,配置 namedisplay: standalonetheme_coloricons
  • 为什么值得做:macOS 用户可以将你的网站添加到 Dock 作为独立应用,iOS 用户可以获得 Home Screen Web App 体验
  • 怎么开始:创建 manifest.json 文件,在 HTML head 中添加 link 标签,测试 Safari 的 “Add to Dock” 和 “Add to Home Screen” 流程

2. 实现 Web Push 通知

  • 做什么:为 Web App 添加推送通知功能,包括新消息提醒和 Badge 更新
  • 为什么值得做:即使用户没有打开 Web App,也能通过通知重新 engage,Badge 提供未读数量提示
  • 怎么开始:注册 Service Worker,使用 Push API 订阅,在服务器端集成 Web Push 协议发送通知,处理 silent 选项适配不同平台

3. 用 Passkey 替代传统认证

  • 做什么:将网站的登录方式从密码或邮件链接迁移到 Passkey
  • 为什么值得做:Passkey 在 Web App 和浏览器中都能使用,不受 Cookie 复制限制,且比邮件验证码更安全便捷
  • 怎么开始:集成 Web Authentication API,参考 WWDC22 “Meet passkeys” session 的实现指南

4. 为同一网站的多个功能创建独立 Web App

  • 做什么:如果网站有多个独立功能(如商店和论坛),为每个功能创建独立的 Web App,用 id 字段区分
  • 为什么值得做:用户可以为不同功能设置独立的通知偏好和 Focus 模式,体验更接近原生应用
  • 怎么开始:为每个功能创建独立的 manifest 文件,设置不同的 idscope,确保链接在正确的 Web App 中打开

5. 适配 Scope 边界外的链接

  • 做什么:明确网站的 scope 边界,确保外部链接(如第三方登录、合作伙伴页面)正确跳转
  • 为什么值得做:scope 外的链接会在默认浏览器中打开,如果 OAuth 流程被误判为外部链接,用户可能无法完成登录
  • 怎么开始:测试所有外部链接和 OAuth 流程,必要时使用 window.open() 确保在 Web App 内打开,向 Apple 反馈误判的 OAuth 域名

关联 Session

评论

GitHub Issues · utterances