Streaming is available in most browsers,
and in the Developer app.
-
Meet Web Push for Safari
Bring better notifications to your websites and web apps in Safari on macOS with Web Push. We'll show you how you can remotely send notifications to people through the web standards-based combination of Push API, Notifications API, and Service Workers.
Resources
- Learn more about bug reporting
- Notifications API
- Push API
- Sending web push notifications in web apps and browsers
- Service Worker API
Related Videos
WWDC23
WWDC22
-
Download
♪ ♪ Brady Eidson: Hello. My name is Brady Eidson. I'm an engineer on the WebKit Architecture team. I am thrilled to introduce you to Web Push in Safari. Web Push lets you remotely send notifications to your web application's users. Here, a notification displays from webkit.org in the upper-right of the screen. Clicking the notification opens a WebKit blog post in a new window. Before I get into other details on how this works, I want to answer a few questions up front I know many of you will have.
Web Push is supported in Mac Safari beginning with macOS Ventura. And Web Push will be coming to iOS and iPadOS next year.
Apple's Safari Push Notifications have been an option for reaching Mac Safari users for quite a while. While it will continue to work, today I'm happy to announce that we have added support for Web Push, and this really is Web Push! The same combination of various web standards as implemented in other browsers. We'll go over those standards more later, but… the most important takeaway is that if you've coded your application to web standards, you won't need to make any changes for it to work in Safari. Of course, if you exclude Safari through browser detection, then you have some work ahead of you. Now would be a great time to switch from browser detection to feature detection, which has always been the best practice. We're using the same Apple Push Notification service that powers native push on all Macs and iOS devices, but no Apple Developer account is required to reach Safari users. We are using new end point URLs for Web Push, which brings up another thing you might be doing to unintentionally exclude Safari. If you tightly manage push end points on your server, make sure you allow URLs from any subdomain of push.apple.com. Moving beyond answers to those important questions, let's get into more detail. First, we'll look at the Web Push experience in Safari from a user's perspective. Then we'll cover the entire Web Push flow, from asking for permission to handling a click on an entry in Notification Center. Finally, we'll see what it takes to add Web Push to an existing web app. But first, the Mac Safari user experience. And I can think of no better way to cover that than with a demo. Here's Safari on macOS Ventura. I have webkit.org open in this browser tab. I need to keep up-to-date with the WebKit open source project, and Web Push is a great way to do that. webkit.org is not allowed to request permission to push without the user asking with a user gesture. So I'll click this bell-shaped button here to subscribe for notifications. What you see here is the system notifications prompt– the same one you'd see for any other application. In this case, it's on behalf of webkit.org. I will click "allow," and I'm all set. webkit.org is giving me the option to be notified about new blog posts as well as new commits to the source code repository. I know being notified for every commit will distract me from important work, but I absolutely want to be notified about new blog posts. So I'll check that box now. Coincidentally, somebody must've just published the WebKit blog post about Web Push. This notification looks just like any other and is attributed to webkit.org. I can click it to activate, and there is the blog post, open in Safari. Once a user has granted permissions to a website, they maintain control over that permission. As a macOS user, I'm used to managing Notification preferences inside System Settings, and that's where I can go to configure webkit.org's notifications. The same rich configuration as I'd find for any other app or service. As a Safari user, I'm used to managing website settings from inside Safari preferences. I can also go there to turn webkit.org's permissions on or off. And that's how Web Push works for users in Mac Safari. Before we move on, I want to reiterate a few things covered in that demo. First, we don't want users to be spammed by subscription requests they haven't asked for. So a website may only request a push subscription in response to a mouse click or a keystroke. Once a website has permission to show notifications to the user, the user controls that permission. They can choose to manage it in Safari's preferences or System Settings. And the setting will stay in sync if they happen to manage it in both. Finally, if you provide notifications for different types of events, it is a best practice to provide fine-grained controls for notification types within your web app, just like other apps do. Now that you've seen Web Push in action, let's dig in to what's happening at each step. Some of you are already intimately familiar with this. But for those of you new to Web Push, I'll go step by step, referring you to the relevant standards and documentation along the way. The first thing that happens is a user visits your website in a browser tab. Here's webkit.org open in Safari. Since it is open in a tab, it can install a Service Worker. A Service Worker is a unit of JavaScript that operates on behalf of an entire domain, separate from a currently open browser tab. Once the Service Worker script is installed, your web app is eligible to request a push subscription. As already mentioned, this request must be tied to a user gesture. webkit.org requests permission when clicking this bell-shaped button, which fulfills the user gesture requirement. When your site asks for a push subscription, the user sees this system prompt. Here is where they can make the final call on granting your website this powerful ability.
It is possible the user might deny the request. Your JavaScript should be prepared to handle that. But assuming the user grants permission, your JavaScript gets back a PushSubscription object. This includes everything your server needs to send a push message to this user in this browser. Information like the exact URL end point to use. You send this PushSubscription payload back to your server in whatever manner works best for your web app. Many popular server packages have Web Push support to manage subscriptions, or you can roll your own. The same pertains to how and when to actually send a push message to the URL end points your server knows about. I can't tell you when to do so. That's up to you and your website. But once you've decided to send that push message, I can help with what happens next. Remember how push requires an installed Service Worker? Once your server has sent a push message and Safari receives it, Safari wakes up your Service Worker and sends it a JavaScript push event. Showing a notification to the user in Notification Center is a requirement while handling the push event. Receiving the push event and displaying the notification happens if your website is currently open in a browser tab. It also happens if your website is not currently open in a browser tab. In the case of Safari on macOS Ventura, this happens even if Safari is not currently running. The final step: If your user clicks on that notification, a notificationclick event is sent to your Service Worker so it can respond appropriately. For example, by opening a new window to the URL associated with that notification. With that understanding of the Web Push flow under our belt, it's time to get into even more detail by actually adding Web Push support to an existing web app. Besides webkit.org, Browser Pets is the most mission critical internal tool for the Safari and WebKit teams. Keeping everyone in the department up-to-date on their favorite WebKittens and Pups on Safari has always been the mission statement of Browser Pets, and Web Push has made that easier than ever. Our internal BrowserPets domain already had a ServiceWorker script registered to speed up page loads and synchronize between multiple tabs. At a high level, a ServiceWorker script looks a lot like this. When an engineer visits the Browser Pets page in a tab, this JavaScript excerpt either determines if the Service Worker script has already been registered, or registers it if necessary. Notice we're practicing feature detection here, previously mentioned as a best practice. With the Service Worker prerequisite taken care of, we're ready to subscribe for push. Remember, you cannot request a push subscription without an explicit user gesture. Running this script in response to a button's onclick handler is one of many ways to satisfy that requirement. Once the user clicks that button, here's code to request a push subscription. I'll go into each of these points in more detail. First, we need to configure the request for a push subscription. An important bit for that is the public key our server uses to identify themselves to Apple's push servers. Here we use the standard technology called VAPID, the same as other browsers. I won't go over the sometimes complex details of VAPID here, but there are resources on the web to help you with the best solution for your server's setup. With the VAPID key set, we're ready to configure the subscription request. Notice we are explicitly stating that we promise to always make pushes user visible. While the standard for the JavaScript Push API optionally accommodates silent JavaScript runtime in response to a push, most browsers do not support that. Safari does not support that. And like most websites, Browser Pets does not need that. Then we request permission to push. This line of JavaScript results in the permission prompt for the user to either approve or reject. Assuming the user grants permission– which all Safari team members do for Browser Pets– this gives us a PushSubscription object with the details on how to reach this user in their browser. Things like the URL end point and the key used to encrypt the push message for transit. Finally, we need to send all of those details to our server. As mentioned before, the specifics of this will vary based on your exact application. Our BrowserPets server uses WordPress, which already has a few plugins to support standard Web Push. It's likely you'll find the same is true for your backend, and there are resources on the web to help find the right solution for just about any setup. Now we need to go back to our Service Worker JavaScript code. It will need to handle a few new events, starting with the push event. When a push message makes its way from the Browser Pets server to this browser, this Service Worker has a push event sent to it. That event contains a PushMessageData object which has multiple ways of accessing the data sent by your server. We use the JSON accessor here. Remember how when we subscribed for push, our JavaScript promised they would always be user visible? That means we must always show a platform native notification in response to each push. It is best to do this as early as possible in your push event handler. We're pulling everything we need out of that JSON blob to configure the notification, including setting up an action with a URL. That will come in handy in just a moment. After the notification is shown, we need to handle the user clicking on it. One more event for our Service Worker script to handle. In this notificationclick handler, BrowserPets will take the URL from the notification that was clicked to open a new window. Take note: This is a very common pattern. That's all the JavaScript we need to write to support Web Push. Of course, it's best to have some help while developing. As usual, that's where Web Inspector comes in. In addition to helping debug your website open in a browser tab, Web Inspector can also inspect Service Worker instances and set breakpoints on event handlers. All of this together will let you inspect and debug the JavaScript that subscribes for push as well as the service worker code that handles the push event and notification events. Additionally, the Apple Push Notification servers will give you human readable errors if something goes wrong when you attempt to publish a push message. Check out the links associated with this session for further documentation. I'd also like to get into more detail on a few points that came up while writing that code, with direct regards to user privacy and power usage. Importantly–and this is not the first time I've said this– subscribing for push requires a user gesture. As with other privileged features of the web platform, it's the right thing for user trust to require that the user actually asked to enable Web Push. As mentioned when I showed you the code on how to request a push subscription, you must promise that pushes will be user visible. Handling a push event is not an invitation for your JavaScript to get silent background runtime. Doing so would violate both a user's trust and a user's battery life. When handling a push event, you are in fact required to post a notification to Notification Center. Other browsers all have countermeasures against violating the promise to make pushes user visible, and so does Safari. In the beta build of macOS Ventura, after three push events where you fail to post a notification in a timely manner, your site's push subscription will be revoked. You will need to go through the permission workflow again. That's all. We're genuinely proud to support Web Push and excited that any site can use it, no Apple Developer account required. As long as you've coded to the standards and use feature detection, so you don't unwittingly exclude Safari, your users will already get the benefit of Web Push in Safari 16 on macOS Ventura. As usual, we've added tons of other new stuff to Safari and WebKit this year, and I hope you'll check out that session to learn more. Thank you for watching. I hope you have a great rest of WWDC 2022.
-
-
8:27 - BrowserPetsWorker.js
// BrowserPetsWorker.js function handleMessageEvent(event) { // ... }; self.addEventListener('message', (event) => { handleMessageEvent(event); }); function primeCaches() { // ... }; self.addEventListener('install', (event) => { primeCaches(); }); self.addEventListener('fetch', (event) => { event.respondWith(caches.match(event.request)); });
-
8:42 - BrowserPetsMain.js
// BrowserPetsMain.js var registration; if ('serviceWorker' in navigator) { let registration = await navigator.serviceWorker.getRegistration(); if (!registration) registration = await navigator.serviceWorker.register('BrowserPetsWorker.js'); }
-
9:00 - BrowserPetsMain.js subscribeToPush()
// BrowserPetsMain.js async function subscribeToPush() { // ... } // BrowserPetsMain.html <button onclick="subscribeToPush()">Register for Updates</button>
-
9:19 - BrowserPetsMain.js subscribe
// BrowserPetsMain.js async function subscribeToPush() { let serverPublicKey = VAPID_PUBLIC_KEY; let subscriptionOptions = { userVisibleOnly: true, applicationServerKey: serverPublicKey }; let subscription = await swRegistration.pushManager.subscribe(subscriptionOptions); sendSubcriptionToServer(subscription); }
-
9:36 - BrowserPetsMain.js subscriptionOptions
// BrowserPetsMain.js async function subscribeToPush() { let serverPublicKey = VAPID_PUBLIC_KEY; let subscriptionOptions = { userVisibleOnly: true, applicationServerKey: serverPublicKey }; let subscription = await swRegistration.pushManager.subscribe(subscriptionOptions); sendSubcriptionToServer(subscription); }
-
10:21 - BrowserPetsMain.js request permission to push
// BrowserPetsMain.js async function subscribeToPush() { let serverPublicKey = VAPID_PUBLIC_KEY; let subscriptionOptions = { userVisibleOnly: true, applicationServerKey: serverPublicKey }; let subscription = await swRegistration.pushManager.subscribe(subscriptionOptions); sendSubcriptionToServer(subscription); }
-
11:13 - BrowserPetsWorker.js push
// BrowserPetsWorker.js self.addEventListener('push', (event) => { let pushMessageJSON = event.data.json(); // Our server puts everything needed to show the notification // in our JSON data. event.waitUntil(self.registration.showNotification(pushMessageJSON.title, { body: pushMessageJSON.body, tag: pushMessageJSON.tag, actions: [{ action: pushMessageJSON.actionURL, title: pushMessageJSON.actionTitle, }] })); }
-
12:06 - BrowserPetsWorker.js notification click
// BrowserPetsWorker.js self.addEventListener('notificationclick', async function(event) { if (!event.action) return; // This always opens a new browser tab, // even if the URL happens to already be open in a tab. clients.openWindow(event.action); });
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.