Push without notifications

On the first day of Indie Web Camp Berlin, I led a session on going offline with service workers. This covered all the usual use-cases: pre-caching; custom offline pages; saving pages for offline reading.

But on the second day, Sebastiaan spent a fair bit of time investigating a more complex use of service workers with the Push API.

The Push API is what makes push notifications possible on the web. There are a lot of moving parts—browser, server, service worker—and, frankly, it’s way over my head. But I’m familiar with the general gist of how it works. Here’s a typical flow:

  1. A website prompts the user for permission to send push notifications.
  2. The user grants permission.
  3. A whole lot of complicated stuff happens behinds the scenes.
  4. Next time the website publishes something relevant, it fires a push message containing the details of the new URL.
  5. The user’s service worker receives the push message (even if the site isn’t open).
  6. The service worker creates a notification linking to the URL, interrupting the user, and generally adding to the weight of information overload.

Here’s what Sebastiaan wanted to investigate: what if that last step weren’t so intrusive? Here’s the alternate flow he wanted to test:

  1. A website prompts the user for permission to send push notifications.
  2. The user grants permission.
  3. A whole lot of complicated stuff happens behinds the scenes.
  4. Next time the website publishes something relevant, it fires a push message containing the details of the new URL.
  5. The user’s service worker receives the push message (even if the site isn’t open).
  6. The service worker fetches the contents of the URL provided in the push message and caches the page. Silently.

It worked.

I think this could be a real game-changer. I don’t know about you, but I’m very, very wary of granting websites the ability to send me push notifications. In fact, I don’t think I’ve ever given a website permission to interrupt me with push notifications.

You’ve seen the annoying permission dialogues, right?

In Firefox, it looks like this:

Will you allow name-of-website to send notifications?

[Not Now] [Allow Notifications]

In Chrome, it’s:

name-of-website wants to

Show notifications

[Block] [Allow]

But in actual fact, these dialogues are asking for permission to do two things:

  1. Receive messages pushed from the server.
  2. Display notifications based on those messages.

There’s no way to ask for permission just to do the first part. That’s a shame. While I’m very unwilling to grant permission to be interrupted by intrusive notifications, I’d be more than willing to grant permission to allow a website to silently cache timely content in the background. It would be a more calm technology.

Think of the use cases:

  • I grant push permission to a magazine. When the magazine publishes a new article, it’s cached on my device.
  • I grant push permission to a podcast. Whenever a new episode is published, it’s cached on my device.
  • I grant push permission to a blog. When there’s a new blog post, it’s cached on my device.

Then when I’m on a plane, or in the subway, or in any other situation without a network connection, I could still visit these websites and get content that’s fresh to me. It’s kind of like background sync in reverse.

There’s plenty of opportunity for abuse—the cache could get filled with content. But websites can already do that, and they don’t need to be granted any permissions to do so; just by visiting a website, it can add multiple files to a cache.

So it seems that the reason for the permissions dialogue is all about displaying notifications …not so much about receiving push messages from the server.

I wish there were a way to implement this background-caching pattern without requiring the user to grant permission to a dialogue that contains the word “notification.”

I wonder if the act of adding a site to the home screen could implicitly grant permission to allow use of the Push API without notifications?

In the meantime, the proposal for periodic synchronisation (using background sync) could achieve similar results, but in a less elegant way; periodically polling for new content instead of receiving a push message when new content is published. Also, it requires permission. But at least in this case, the permission dialogue should be more specific, and wouldn’t include the word “notification” anywhere.

Have you published a response to this? :

Responses

danq.me

While hanging out at IndieWebCamp Oxford, Dan adds progressively-enhanced panoramic WebVR technology to his blog.

# Sunday, September 23rd, 2018 at 9:22pm

danq.me

While hanging out at IndieWebCamp Oxford, Dan adds progressively-enhanced panoramic WebVR technology to his blog.

# Sunday, September 23rd, 2018 at 9:22pm

john holt ripley

“While I’m very unwilling to grant permission to be interrupted by intrusive notifications, I’d be more than willing to grant permission to allow a website to silently cache timely content in the background. It would be a more calm technology.”adactio.com/journal/14511

Jannis Borgers

While I like the idea of cached web content being pushed silently to my devices, monitoring these subscriptions will be the most difficult task to fulfill for reasons of privacy. Cognitive overload versus naive comfort. You still want control, in some way. adactio.com/journal/14511

Frank Meeuwsen

Jeremy Keith writes about some new ideas regarding push notifications on the web. These little blurbs that popup on your screen when a site publishes something new. In a distant past you gave permission to a particuar site to interrupt you when they publish something important (to them) and you get these notifications.

The solution Jeremy and Sebastiaan examine, reminds of the old Push technology, way back in the early days of the web. The desktop was essentially your workplace and you could have dynamic widgets on it with news, weather and other tidbits. There was a great company here in the Netherlands called Bitmagic that provided little nudges of entertainment on your desktop. It would download in the background (back when we just had dial-up modems) and you could watch it offline. This was before we had a daily tsunami of information, washing away clear thinking and processing of what is happening around us.

Jeremy divides the problem in two parts which is actually quite good. Because yes, I’d love to be notified by sites when something happens and they don’t offer RSS or JSON feeds. But I would not like to get a notification on my desktop every time this happens. On a personal level, I don’t believe in the caching solution Jeremy proposes. This is too much of a personal usecase which I don’t see fit for a larger use. But providing options beyond just notification on your desktop is a great way forward.

Originele link

https://seblog.nl/

Recently I’ve been to IndieWebCamp Berlin, where I spend the Hack Day on abusing the PushAPI to update ServiceWorker caches.

I would like to start with a small section on what and why, but while I was procrastinating on writing this blog post (the pressure is high), no one less than Jeremy Keith wrote a blogpost about it. Since that’s a perfect what and why, there are just two things to do for me here: demo and how.

Demo

I did a demo in Berlin, but the demo-gods where unforgiving. It did not work at all, but when I got back to my seat, it started working again. What happened? My Mac tried to be nice and turned off notifications while I was presenting.

But, as to make up for it, the new macOS Mojave shipped with a screen capturing tool. So here is a retry of the demo in under 5 minutes:

The how

This might not be the most interesting part of it, but it’s nice to share work. It’s not a full comprehensive guide on how to do this stuff, because that would just take way too long. See it as a quick guide behind the different API’s involved.

I googeled it all anyway. You can google along.

Oh and if you want to skip ahead: there are some use cases at the end.

Showing a local notification

Like with any Javascript, you should check support before you ask something. There is a list of things to ask in the below code example: we want Notifications, it should not be denied, there has to be ServiceWorker support, and for the part later on, there should be a PushManager too.

Once we prompted the user and got permission, it’s as simple as getting our ServiceWorker registration and ask it to show a notification. As you can see: this involves the ServiceWorker, but it does not involve any other servers.

function activateNotifications() { Notification.requestPermission() .then(status => this.status = status)
}, function supportsNotifications() { return ('Notification' in window) && (this.status !== 'denied') && ('serviceWorker' in navigator) && ('PushManager' in window)
} async function sendTestNotification() { const reg = await navigator.serviceWorker.getRegistration() return reg.showNotification('Hallo, test!')
}

Note: the demo code is using Vue, which I leave out in this blogpost to simplify things. But that’s where this points to: a collection of variables on the Vue instance.

Subscribing for the PushAPI

Once the user clicks the button ‘Subscribe’, the following function gets triggered. In here, we again get the ServiceWorker registration, and then access the PushManager on it, which we tell to subscribe.

async function subscribe() { const reg = await navigator.serviceWorker.getRegistration() const sub = await reg.pushManager.subscribe({ userVisibleOnly: true, // required for Chrome applicationServerKey: urlB64ToUint8Array(this.publicVapidKey) }) this.notifications = true const key = sub.getKey('p256dh') const token = sub.getKey('auth') await this.$http.post('/subscriptions', { endpoint: sub.endpoint, key: key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : null, token: token ? btoa(String.fromCharCode.apply(null, new Uint8Array(token))) : null }) this.subscribed = sub !== null
},

Some browsers have their own way of doing authentication, but the most universal is with a Vapid key pair. The package I use for the backend came with a way of creating them. We give the public key to the PushManager, which will give us a Subscription object.

In the end, we send the Subscription’s key, token and endpoint to the server via a POST request.

Note: my HTTP library of choise is axios and the urlB64ToUint8Array() function can be found here

Storing the Subscription

For the backend, I’m using a Laravel package for WebPush, which allows me to save the endpoint with very minimal code:

public function update(Request $request)
{ $this->validate($request, ['endpoint' => 'required']); $request->user()->updatePushSubscription( $request->endpoint, $request->key, $request->token ); return response()->json(null, 201);
}

As you can see, it is using the user to associate the data with. (I fake the auth in the demo, which I do not recommend.) It ends up in a database, with four main columns: user_id, endpoint, public_key, auth_token.

In theory, you can go without users, but you will need to store the other parts. The token and key look like random strings, but the endpoint is an actual URL, on a subdomain of either Mozilla or Google, depending on the browser. (No support on Safari yet, mind you.)

These endpoints and tokens can expire, so you will need to keep an eye on the table.

Sending the notification

I can be short about this part: I have no idea. The following code is all it takes to trigger it:

Notification::send( User::all(), new NewBlogPostCreated($content, $notify)
);

… where $content is the content of the post, and $notify a boolean, telling my ServiceWorker whether or not to show a notification (we’ll get to that).

The NewBlogPostCreated class extends Laravel’s build-in Notification class and has these two methods:

public function via($notifiable)
{ return [WebPushChannel::class];
} public function toWebPush($notifiable, $notification)
{ return (new WebPushMessage) ->title($this->notify ? 'notify' : 'update-cache') ->body($this->content);
}

There is a lot of magic behind the scenes here. I have no idea. In the end, they send a POST request to the endpoints of those users, after signing the right things with the right keys.

Receiving the notification and then don’t

Next, we’re back in Javascript-land, however, this is the ServiceWorker-province. The ServiceWorker, once installed, is a script, written in Javascript, but completely decoupled from any window. It lives in your browser and represents not one page, but your whole website.

It’s quite hard to wrap your head around at first, but, I think the PushAPI makes it easier: there is no window involved with a push message, and there is no page involved with a push message. There is only your ServiceWorker, which acts for your whole website.

The ServiceWorker-script itself consists of a series of callbacks, that are executed whenever things happen. In the case of a push message, the 'push' event is triggered:

(function() { 'use strict'; self.addEventListener('push', function (e) { const data = e.data.json() self.caches.open('manual') .then(cache => cache.put('hello', new Response(data.body))) if (data.title == 'notify') { e.waitUntil( self.registration.showNotification( 'New content!', {body: data.body} ) ); } }); })();

That’s all I need for receiving push notifications. I first retrieve the data from the message. Then I open the cache named ‘manual’ and I put the body of the message in that cache as the content of a URL (in this case ‘offline.test/hello’). It is made for pages, but I use it as a key-value store here.

Then I check the title field, which I have abused for this purpose. If it is set to the magic string ‘notify’, I will trigger the notification. If it’s something else I will do nothing.

This shows that I don’t have to: I can leave the notification out, but I still get a ServiceWorker activation and I can do whatever I want with it.

Use cases

I think this can be used for creepy things (can I occasionally ping my ServiceWorkers and ask for data like ‘how many windows are open?’ and phone that home?), but I also think there are nice uses for this as well.

As Jeremy wrote: this can be used for magazines, podcasts and blogs to push new content to my phone, to read on a plain or in the subway when I’m offline. I see a nice feature for a web-based IndieWeb Reader too: it can push me copies of posts it collected.

I think the Reader is a nice place to use this. With great power comes great responsibility. Do I want to grand that great power to that weird magazine, that dubious podcast, that blog I visit once or twice a month? I might know you well, I might not. Do I trust you, pushing megabytes on my phone without me noticing?

Web apps like a Reader are easier to bond with. Plus: once I know my Reader supports reading offline, I might visit it in the subway. Will I remember the magazine?

The last bonus of the IndieWeb Reader specificly: it can send me posts from any magazine or postcast or blog, whether they support offline reading or not. But that’s more specific to the Reader than it is to Push.

I’m also very curious to know how things will evolve if ServiceWorkers get even more superpowers. How well will those pair with a free ServiceWorker activation? Lot’s of exploring to do!

Laurence Hughes

Enjoyed this, and was timely – I just added web push notifications to the @greenhill_ journal so thinking about them a lot. I like the idea of just silently cacheing a new blog post (for example), but as publisher wouldn’t you still want to notify the person that it had landed?

Matteo Fogli

while cleaning my reading list I hit a great read by @adactio on the need for more permissive push features while shielding users from the annoyance of push notifications. SW paired with reverse background sync provide great ux and no distractions adactio.com/journal/14511

2 Bookmarks

# Thursday, January 1st, 1970 at 12:00am

# Thursday, January 1st, 1970 at 12:00am