如何处理网站和 PWA 中的 Web 推送通知

x33g5p2x  于2022-02-12 转载在 其他  
字(5.9k)|赞(0)|评价(0)|浏览(320)

推送通知在现代网络上很常见。即使您的网站实际上并未打开,它们也能让您及时向用户传达信息。用户的浏览器处理传入的推送事件并使用系统 UI 界面(如 Windows 操作中心和 Android 锁屏)显示通知。

在您的网站或 PWA中实施 Web Push需要结合两个不同的浏览器 API。负责订阅和接收通知的代码使用service workers的Push API组件。此代码在后台持续运行,并在需要处理新通知时由浏览器调用。

当接收到一个事件时,服务工作者应该使用通知 API来实际显示通知。这会通过操作系统级界面创建视觉警报。

这是让 Web Push 在您的网站上运行的完整指南。我们假设您已经拥有一个服务器端组件,可以注册推送订阅并发送您的警报。

服务工作者

让我们从服务工作者开始。服务工作者有多个角色——他们可以缓存数据以供离线使用、运行定期后台同步以及充当通知处理程序。服务工作者使用事件驱动的架构。一旦被站点注册,用户的浏览器将在其订阅的事件生成时在后台调用服务工作者。
对于 Web Push,需要一个核心事件:push. 这会接收一个PushEvent对象,该对象允许您访问从服务器推送的有效负载。

self.addEventListener("push", e => {
 
    const payload = JSON.parse(e.data.text());
 
    e.waitUntil(self.registration.showNotification(
        payload.title,
        {
            body: payload.body,
            icon: "/icon.png"
        }
    ));
 
});

上面的代码设置了一个能够对传入的推送事件做出反应的服务工作者。它期望服务器发送如下所示的 JSON 有效负载:

{
    "title": "Title text for the notification",
    "body": "This is the longer text of the notification."
}

当接收到推送事件时,服务工作者通过调用其属性showNotification()上可用的函数来显示浏览器通知。self.registration该函数包含在一个waitUntil()调用中,因此浏览器在终止服务工作者之前等待显示通知。

该showNotification()函数有两个参数:通知的标题文本和一个选项对象。在这个例子中传递了两个选项,一些较长的正文和一个在通知中显示的图标。还有许多其他选项可供您设置振动模式、自定义徽章和交互要求。并非所有浏览器和操作系统都支持 API 公开的所有功能。

通过在主 JavaScript 中注册它来完成代码的 service worker 端:

if (navigator.serviceWorker) {
    // replace with the path to your service worker file
    navigator.serviceWorker.register("/sw.js").catch(() => {
        console.error("Couldn't register the service worker.")
    });
}

此代码应在每个页面加载时运行。它确保浏览器支持服务工作者,然后注册工作者文件。只要服务器副本与当前安装的版本存在字节差异,浏览器就会自动更新服务工作者。

注册推送订阅

现在您需要订阅浏览器以推送通知。以下代码属于您的主 JavaScript 文件,在服务工作者之外。

async function subscribeToPush() {
    if (navigator.serviceWorker) {
 
        const reg = await navigator.serviceWorker.getRegistration();
 
        if (reg && reg.pushManager) {
 
            const subscription = await reg.pushManager.getSubscription();
 
            if (!subscription) {
 
                const key = await fetch("https://example.com/vapid_key");
                const keyData = await key.text();
 
                const sub = await reg.pushManager.subscribe({
                    applicationServerKey: keyData,
                    userVisibleOnly: true
                });
 
                await fetch("https://example.com/push_subscribe", {
                    method: "POST",
                    headers: {"Content-Type": "application/json"},
                    body: JSON.stringify({
                        endpoint: sub.endpoint,
                        expirationTime: sub.expirationTime,
                        keys: sub.toJSON().keys
                    })
                });
 
            }
 
        }
 
    }
}

然后调用你的函数来订阅浏览器推送通知:

await subscribeToPush();

让我们来看看订阅代码在做什么。前几行检查是否存在服务工作者,检索其注册,并检测推送通知支持。pushManager不会在不支持 Web Push 的浏览器中设置。

调用pushManager.getSubscription()会返回一个 Promise,该 Promise 解析为描述浏览器当前对您的站点的推送订阅的对象。如果这已经设置,我们不需要重新订阅用户。

真正的订阅流程从服务器的 VAPID 密钥的获取请求开始。VAPID 规范是一种让浏览器验证推送事件实际上来自您的服务器的机制。您应该公开一个提供 VAPID 密钥的服务器 API 端点。这是给pushManager.subscribe()函数的,因此浏览器知道要信任的密钥。单独的userVisibleOnly选项表示我们只会显示在屏幕上明显显示的通知。

该pushManager.subscribe()调用返回一个描述您的新订阅的PushSubscription对象。此数据在另一个获取请求中发送到服务器。在真正的应用程序中,您还需要发送活动用户的 ID,以便您可以将推送订阅链接到他们的设备。

用于向用户发送推送通知的服务器端代码应如下所示:

  1. 查询链接到目标用户的所有推送订阅的数据存储。
  2. 将通知负载发送到每个订阅所指示的端点,确保包含订阅的身份验证密钥(keys在订阅时浏览器发送的数据中)。使用您发送到浏览器的相同
    VAPID 密钥对事件进行签名。

每个订阅endpoint都将引用浏览器供应商的通知传递平台。此 URL 已包含订阅的唯一标识符。当您向端点发送有效负载时,浏览器的后台进程最终将接收数据并调用您的服务工作者。对于 Android 上的 Chrome,浏览器进程直接与系统通知守护进程集成。

何时订阅用户?

设置订阅流程时,请记住用户必须在注册完成之前确认浏览器权限提示。许多浏览器会自动隐藏或拒绝未经请求的权限请求;无论如何,要求用户在他们登陆您的网站的那一刻进行订阅可能无法提供您想要的结果。

通过将订阅请求与直接用户操作相结合,您可以获得成功注册的最佳机会。考虑提供一个应用内横幅,说明启用通知的好处并提供“立即启用”按钮。您可以使用上面显示的功能检查用户是否已经订阅并隐藏横幅pushManager.getSubscription()。

单击启用按钮应调用您的订阅函数。在浏览器设置注册并且您的网络调用完成时,该过程可能需要几秒钟。在此期间显示加载微调器将有助于让用户了解情况。

还应该为用户提供取消订阅的方法。尽管他们可以随时撤销浏览器权限,但有些用户会寻找应用内选项,尤其是当他们将您的网站安装为 PWA 时。

这是一个简单的取消订阅实现:

async function unsubscribePush() {
 
    const reg = await navigator.serviceWorker.getRegistration();
    const subscription = await reg.pushManager.getSubscription();
 
    if (subscription) {
        await subscription.unsubscribe();
        await fetch(`https://example.com/push_unsubscribe/${subscription.endpoint}`, {method: "DELETE"});
    }
    else {
        // already subscribed
    }
 
}

调用unsubscribe()aPushSubscription取消订阅,将浏览器恢复到默认状态。您的服务人员将停止接收push事件。订阅的端点被发送到您的服务器,因此您可以将其从数据存储中删除,并避免将数据发送到现在已失效的 URL。

处理到期和续订

您可能已经注意到浏览器创建expirationTime的对象的属性。PushSubscription这并不总是被设置;如果是,设备将在此时间后停止接收通知。

实际上,expirationTime目前主要浏览器中并未使用。Chrome 生成的令牌在手动取消订阅之前不会过期expirationTime,所以总是如此null。Firefox 也没有设置expirationTime,但它的通知服务可以在其生命周期内替换订阅。

pushsubscriptionchange您可以通过在您的服务工作者中实现事件来响应浏览器更改您的活动推送订阅。不幸的是,此事件有两个版本:Firefox 当前使用的原始实现,以及尚未在任何浏览器中支持的新 v2。

原始规范存在严重的可用性问题,因此难以响应事件。当您收到 v1 事件时,浏览器已删除原始订阅,您需要手动创建一个新订阅。问题是无法访问过期订阅,您无法向服务器发出“替换”请求——您无法访问旧endpointURL。

v2 规范通过提供具有oldSubscription和newSubscription属性的事件来解决这个问题。当您收到事件时,旧订阅已被取消,但您仍然可以访问其属性。新订阅现在由浏览器为您创建。

pushsubscriptionchange这是使用新规范实施的示例:

self.addEventListener("pushsubscriptionchange", e => {
    e.waitUntil(async () => {
        await fetch("https://example.com/push_change", {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify({
                auth: (e.newSubscription.toJSON().keys?.auth || null),
                endpoint: e.newSubscription.endpoint,
                endpointOld: e.oldSubscription.endpoint,
                expirationTime: e.newSubscription.expirationTime,
                p256dh: (e.newSubscription.toJSON().keys?.p256dh || null)
            })
        });
    });
});

端点是唯一的,因此您的服务器可以查找旧订阅并使用新订阅的属性更新其属性。如果您还想添加对旧规范的支持,则需要手动跟踪推送 API 之外的活动订阅端点。将其存储到localStorage或 IndexedDB 将允许您在pushsubscriptionchange处理程序中访问它,以便您可以要求服务器替换订阅。

修订后的规范比旧版本更容易实施。尽管浏览器尚不支持它,但还是值得将它添加到您的服务工作者中。几行代码将使您的推送处理针对新的浏览器版本进行验证。

添加操作按钮

推送通知可以包括让用户立即采取行动的交互式按钮。这是一个showNotification()创建一个调用:

self.registration.showNotification(
    "Notification with actions",
    {
        body: "This notification has a button.",
        actions: [
            {
                action: "/home",
                title: "Go to Homescreen",
                icon: "/home.png"
            }
        ]
    }
);

每个通知可以包含多个操作,每个操作都带有标签、图标和action. 后一个属性应该标识您的应用程序可以响应用户的按下而启动的操作。

当用户点击一个动作时,你的 service worker 会收到一个notificationclick事件:

self.addEventListener("notificationclick", e => {
    const uri = e.action;
    const notification = e.notification;
    notification.close();
    clients.openWindow(`${self.location.origin}${action}`);
});

我们使用该action属性来声明用户可以导航到的 URI。按下通知时,URI 会打开一个新选项卡。调用notification.close()确保通知也被忽略。否则,某些平台会让用户手动将其滑走。

概括

如果您以前没有使用过相关的 API,那么实现 Web 推送可能会让人望而生畏。除了技术问题之外,您还应该将用户体验放在首位,并确保您传达为什么值得启用通知。

订阅和取消订阅推送发生在应用程序的主要 JavaScript 代码中,使用navigator.serviceWorkerAPI。响应新推送事件并显示浏览器通知的代码位于 service worker 本身中。

现在大多数主要的 Web 浏览器都支持 Web Push,Safari 是一个突出的例外。请记住,通知将在每个浏览器和操作系统系列中呈现不同,因此不要假设showNotification()API 的特定功能将普遍可用。

相关文章