Added a bit more of the workflow on the client side
This commit is contained in:
212
web-push.md
212
web-push.md
@@ -13,11 +13,12 @@ PROVIDER is FCM (Firebase Cloud Messaging) which is owned by Google.
|
|||||||
3) The Web Application that a user is visiting from their web browser. Let's
|
3) The Web Application that a user is visiting from their web browser. Let's
|
||||||
call this the SERVICE (short for Web Push application service)
|
call this the SERVICE (short for Web Push application service)
|
||||||
[4) A Custom Web Push Intermediary Service, either third party or self-hosted.
|
[4) A Custom Web Push Intermediary Service, either third party or self-hosted.
|
||||||
Called INTERMEDIARY here.]
|
Called INTERMEDIARY here. FCM also may fit in this category if the SERVICE
|
||||||
|
has an API key from FCM.]
|
||||||
|
|
||||||
The workflow works like this:
|
The workflow works like this:
|
||||||
|
|
||||||
BROWSER visits a website which has a SERVICE.
|
BROWSER visits a website which hosts a SERVICE.
|
||||||
|
|
||||||
The SERVICE asks BROWSER for its permission to subscribe to messages coming
|
The SERVICE asks BROWSER for its permission to subscribe to messages coming
|
||||||
from the SERVICE.
|
from the SERVICE.
|
||||||
@@ -25,10 +26,10 @@ from the SERVICE.
|
|||||||
The SERVICE will provide context and obtain explicit permission before prompting
|
The SERVICE will provide context and obtain explicit permission before prompting
|
||||||
for notification permission:
|
for notification permission:
|
||||||
|
|
||||||
In orer to provide this context and explict permission a two-step opt-in process
|
In order to provide this context and explict permission a two-step opt-in process
|
||||||
where the user is first presented with a pre-permission dialog box that explains
|
where the user is first presented with a pre-permission dialog box that explains
|
||||||
what the notifications are for and why they are useful. This may help reduce the
|
what the notifications are for and why they are useful. This may help reduce the
|
||||||
possibility of users clicking "don't allow.
|
possibility of users clicking "don't allow".
|
||||||
|
|
||||||
Now, to explain what happens in Typescript, we can activate a browser's
|
Now, to explain what happens in Typescript, we can activate a browser's
|
||||||
permission dialogue in this manner:
|
permission dialogue in this manner:
|
||||||
@@ -52,8 +53,15 @@ function askPermission(): Promise<NotificationPermission> {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If the user grants permission, the client application registers a service worker
|
The Notification.permission property indicates the permission level for the
|
||||||
using the `ServiceWorkerRegistration` API.
|
current session and returns one of the following string values:
|
||||||
|
|
||||||
|
'granted': The user has granted permission for notifications.
|
||||||
|
'denied': The user has denied permission for notifications.
|
||||||
|
'default': The user has not made a choice yet.
|
||||||
|
|
||||||
|
Once the user has granted permission, the client application registers a service
|
||||||
|
worker using the `ServiceWorkerRegistration` API.
|
||||||
|
|
||||||
The `ServiceWorkerRegistration` API is accessible via the browser's `navigator`
|
The `ServiceWorkerRegistration` API is accessible via the browser's `navigator`
|
||||||
object and the `navigator.serviceWorker` child object and ultimately directly
|
object and the `navigator.serviceWorker` child object and ultimately directly
|
||||||
@@ -77,8 +85,8 @@ navigator.serviceWorker.register('sw.js', { scope: '/' })
|
|||||||
```
|
```
|
||||||
|
|
||||||
The `sw.js` file contains the logic for what a service worker should do.
|
The `sw.js` file contains the logic for what a service worker should do.
|
||||||
It executes in a separate thread from the web page but provides a means
|
It executes in a separate thread of execution from the web page but provides a
|
||||||
of communicating between itself and the web page via messages.
|
means of communicating between itself and the web page via messages.
|
||||||
|
|
||||||
Note that there is a scope can specify what network requests it may
|
Note that there is a scope can specify what network requests it may
|
||||||
intercept.
|
intercept.
|
||||||
@@ -86,11 +94,11 @@ intercept.
|
|||||||
The Vue project already has its own service worker but it is possible to
|
The Vue project already has its own service worker but it is possible to
|
||||||
create multiple service worker files by registering them on different scopes.
|
create multiple service worker files by registering them on different scopes.
|
||||||
|
|
||||||
It is useful architecturally to specify a separate server worker.
|
It is useful architecturally to specify a separate server worker file.
|
||||||
|
|
||||||
In the case of web push, the path of the scope only has reference to the domain
|
In the case of web push, the path of the scope only has reference to the domain
|
||||||
of the service worker and no relationship to the pathing for the web push
|
of the service worker and no relationship to the pathing for the web push
|
||||||
server. In order to specify different server workers they need to be on
|
server. In order to specify more than one server workers each needs to be on
|
||||||
different scope paths!
|
different scope paths!
|
||||||
|
|
||||||
Here's a version which can be used for testing locally. Note there can be
|
Here's a version which can be used for testing locally. Note there can be
|
||||||
@@ -126,22 +134,59 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Once we have the service worker registered and the ServiceWorkerRegistration is
|
||||||
|
returned, we then have access to a `pushManager` property object. This property
|
||||||
|
allows us to continue with the web push work flow.
|
||||||
|
|
||||||
In the next step, BROWSER requests a data structure from SERVICE called a VAPID (Voluntary
|
In the next step, BROWSER requests a data structure from SERVICE called a VAPID
|
||||||
Application Server Identification) which is the public key from a key-pair.
|
(Voluntary Application Server Identification) which is the public key from a
|
||||||
|
key-pair.
|
||||||
|
|
||||||
The VAPID is a specification used to identify the application server (i.e. the SERVICE
|
The VAPID is a specification used to identify the application server (i.e. the
|
||||||
server) that is sending push messages to a push service. It's an authentication
|
SERVICE server) that is sending push messages through a push PROVIDER. It's an
|
||||||
mechanism that allows the server to demonstrate its identity to the push service, by use
|
authentication mechanism that allows the server to demonstrate its identity to
|
||||||
of a public and private key pair. These keys are used by the SERVICE in encrypting
|
the push PROVIDER, by use of a public and private key pair. These keys are used
|
||||||
messages being sent to the BROWSER, as well as being used by the BROWSER in
|
by the SERVICE in encrypting messages being sent to the BROWSER, as well as
|
||||||
decrypting the messages coming from the SERVICE.
|
being used by the BROWSER in decrypting the messages coming from the SERVICE.
|
||||||
|
|
||||||
|
The VAPID (Voluntary Application Server Identification) key provides more
|
||||||
|
security and authenticity for web push notifications in the following ways:
|
||||||
|
|
||||||
|
Identifying the Application Server:
|
||||||
|
|
||||||
|
The VAPID key is used to identify the application server that is sending
|
||||||
|
the push notifications. This ensures that the push notifications are
|
||||||
|
authentic and not sent by a malicious third party.
|
||||||
|
|
||||||
|
Encrypting the Messages:
|
||||||
|
|
||||||
|
The VAPID key is used to sign the push notifications sent by the
|
||||||
|
application server, ensuring that they are not tampered with during
|
||||||
|
transmission. This provides an additional layer of security and
|
||||||
|
authenticity for the push notifications.
|
||||||
|
|
||||||
|
Adding Contact Information:
|
||||||
|
|
||||||
|
The VAPID key allows a web application to add contact information to
|
||||||
|
the push messages sent to the browser push service. This enables the
|
||||||
|
push service to contact the application server in case of need or
|
||||||
|
provide additional debug information about the push messages.
|
||||||
|
|
||||||
|
Improving Delivery Rates:
|
||||||
|
|
||||||
|
Using the VAPID key can help improve the overall performance of web push
|
||||||
|
notifications, specifically improving delivery rates. By streamlining the
|
||||||
|
delivery process, the chance of delivery errors along the way is lessened.
|
||||||
|
|
||||||
If the BROWSER accepts and grants permission to subscribe to receiving from the
|
If the BROWSER accepts and grants permission to subscribe to receiving from the
|
||||||
SERVICE Web Push messages, then the BROWSER makes a subscription request to
|
SERVICE Web Push messages, then the BROWSER makes a subscription request to
|
||||||
PROVIDER which creates and stores a special URL for that BROWSER.
|
PROVIDER which creates and stores a special URL for that BROWSER.
|
||||||
|
|
||||||
const applicationServerKey = urlBase64ToUint8Array('BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U');
|
Here's a bit of code describing the above process:
|
||||||
|
|
||||||
|
// b64 is the VAPID
|
||||||
|
b64 = 'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U';
|
||||||
|
const applicationServerKey = urlBase64ToUint8Array(b64);
|
||||||
const options: PushSubscriptionOptions = {
|
const options: PushSubscriptionOptions = {
|
||||||
userVisibleOnly: true,
|
userVisibleOnly: true,
|
||||||
applicationServerKey: applicationServerKey
|
applicationServerKey: applicationServerKey
|
||||||
@@ -155,68 +200,111 @@ registration.pushManager.subscribe(options)
|
|||||||
console.error('Push subscription failed:', error);
|
console.error('Push subscription failed:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
In this example, the `applicationServerKey` variable contains the VAPID public key,
|
In this example, the `applicationServerKey` variable contains the VAPID public
|
||||||
which is converted to a Uint8Array using the `urlBase64ToUint8Array()` function from the
|
key, which is converted to a `Uint8Array` using a function such as this:
|
||||||
convert-vapid-public-key package. The options object is of type PushSubscriptionOptions,
|
|
||||||
which includes the `userVisibleOnly` and `applicationServerKey` (ie VAPID public key)
|
|
||||||
properties. The subscribe() method returns a `Promise` that resolves to a `PushSubscription`
|
|
||||||
object containing details of the subscription, such as the endpoint URL and the public key.
|
|
||||||
|
|
||||||
The VAPID (Voluntary Application Server Identification) key provides more security and
|
```
|
||||||
authenticity for web push notifications in the following ways:
|
export function toUint8Array(base64String: string, atobFn: typeof atob): Uint8Array {
|
||||||
|
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
||||||
|
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
||||||
|
|
||||||
Identifying the Application Server:
|
const rawData = atobFn(base64);
|
||||||
|
const outputArray = new Uint8Array(rawData.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < rawData.length; ++i) {
|
||||||
|
outputArray[i] = rawData.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return outputArray;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The options object is of type `PushSubscriptionOptions`, which includes the
|
||||||
|
`userVisibleOnly` and `applicationServerKey` (ie VAPID public key) properties.
|
||||||
|
|
||||||
|
The subscribe() method returns a `Promise` that resolves to a `PushSubscription`
|
||||||
|
object containing details of the subscription, such as the endpoint URL and the
|
||||||
|
public key. The returned data would have a form like this:
|
||||||
|
|
||||||
|
{
|
||||||
|
"endpoint": "https://some.pushservice.com/some/unique/identifier",
|
||||||
|
"expirationTime": null,
|
||||||
|
"keys": {
|
||||||
|
"p256dh": "some_base64_encoded_string",
|
||||||
|
"auth": "some_other_base64_encoded_string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint: A string representing the endpoint URL for the push service. This
|
||||||
|
URL is essentially the push service address to which the push message would
|
||||||
|
be sent for this particular subscription.
|
||||||
|
|
||||||
|
expirationTime: A DOMHighResTimeStamp (which is basically a number or null)
|
||||||
|
representing the subscription's expiration time in milliseconds since
|
||||||
|
01 January, 1970 UTC. This can be null if the subscription never expires.
|
||||||
|
|
||||||
|
options: An object that contains the options used for creating the
|
||||||
|
subscription. This object itself has the following sub-properties:
|
||||||
|
|
||||||
The VAPID key is used to identify the application server that is sending the push notifications.
|
applicationServerKey: A public key your push service uses for application
|
||||||
This ensures that the push notifications are authentic and not sent by a malicious third party.
|
server identification. This is normally a Uint8Array.
|
||||||
|
|
||||||
Encrypting the Messages:
|
|
||||||
|
|
||||||
The VAPID key is used to sign the push notifications sent by the application server,
|
userVisibleOnly: A boolean value indicating that the push messages that
|
||||||
ensuring that they are not tampered with during transmission. This provides an additional
|
are sent should be made visible to the user through a notification.
|
||||||
layer of security and authenticity for the push notifications.
|
This is often set to true.
|
||||||
|
|
||||||
Adding Contact Information:
|
The BROWSER will, internally, then use that URL to check for incoming messages
|
||||||
|
by way of the service worker we described earlier. The BROWSER also sends this
|
||||||
|
URL back to SERVICE which will use that URL to send messages to the BROWSER via
|
||||||
|
the PROVIDER.
|
||||||
|
|
||||||
The VAPID key allows a web application to add contact information to the push messages sent to the browser push service.
|
Ultimately, the actual internal process of receiving messages varies from BROWSER
|
||||||
This enables the push service to contact the application server in case of need or provide additional debug information about the push messages.
|
to BROWSER. Approaches vary from long-polling HTTP connections to WebSockets. A
|
||||||
|
|
||||||
Improving Delivery Rates:
|
|
||||||
|
|
||||||
Using the VAPID key can help improve the overall performance of web push notifications, specifically improving delivery rates.
|
|
||||||
By streamlining the delivery process, the chance of delivery errors along the way is lessened.
|
|
||||||
|
|
||||||
|
|
||||||
The PROVIDER sends this URL back to the BROWSER. The BROWSER will then use that
|
|
||||||
URL to check for incoming messages by way of a special software named a "service
|
|
||||||
worker". The BROWSER also sends this URL back to SERVICE which will use that
|
|
||||||
URL to send messages to the BROWSER via the PROVIDER.
|
|
||||||
|
|
||||||
Ultimately, the actual process of receiving messages varies from BROWSER to
|
|
||||||
BROWSER. Approaches vary from long-polling HTTP connections to WebSockets. A
|
|
||||||
lot of handwaving and voodoo magic. The bottom line is that the BROWSER itself
|
lot of handwaving and voodoo magic. The bottom line is that the BROWSER itself
|
||||||
manages the connection to the PROVIDER whilst the SERVICE must send messages
|
manages the connection to the PROVIDER whilst the SERVICE must send messages
|
||||||
via the PROVIDER so that they reach the BROWSER.
|
via the PROVIDER so that they reach the BROWSER service worker.
|
||||||
|
|
||||||
|
Just to remind us that in our service worker our code for receiving messages
|
||||||
|
will look something like this:
|
||||||
|
|
||||||
|
self.addEventListener('push', function(event: PushEvent) {
|
||||||
|
console.log('Received a push message', event);
|
||||||
|
|
||||||
|
const title = 'Push message';
|
||||||
|
const body = 'The message body';
|
||||||
|
const icon = '/images/icon-192x192.png';
|
||||||
|
const tag = 'simple-push-demo-notification-tag';
|
||||||
|
|
||||||
|
event.waitUntil(
|
||||||
|
self.registration.showNotification(title, {
|
||||||
|
body: body,
|
||||||
|
icon: icon,
|
||||||
|
tag: tag
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Now to address the issue of receiving notification messages on mobile devices.
|
Now to address the issue of receiving notification messages on mobile devices.
|
||||||
It should be noted that Web Push messages are only received when BROWSER is
|
It should be noted that Web Push messages are only received when BROWSER is
|
||||||
open, except in the cases of Chrome and Firefox mobile BROWSERS. In iOS the
|
open, except in the cases of Chrome and Firefox mobile BROWSERS. In iOS, the
|
||||||
mobile application (in our case a PWA) must be added to the Home Screen and
|
mobile application (in our case a PWA) must be added to the Home Screen and
|
||||||
permissions must be explicitly granted that allow the application to receive push
|
permissions must be explicitly granted that allow the application to receive
|
||||||
notifications. Further, with an iOS device the user must enable wake on notification to
|
push notifications. Further, with an iOS device the user must enable wake on
|
||||||
have their device light-up when it receives a notification (https://support.apple.com/enus/HT208081).
|
notification to have their device light-up when it receives a notification
|
||||||
|
(https://support.apple.com/enus/HT208081).
|
||||||
|
|
||||||
So what about #4? - The INTERMEDIARY. Well, It is possible under very special
|
So what about #4? - The INTERMEDIARY. Well, It is possible under very special
|
||||||
circumstances to create your own Web Push PROVIDER. The only case I've found so
|
circumstances to create your own Web Push PROVIDER. The only case I've found so
|
||||||
far relates to making an Android Custom ROM. (An Android Custom ROM is a
|
far relates to making an Android Custom ROM. (An Android Custom ROM is a
|
||||||
customized version of the Android Operating System.) There are open source
|
customized version of the Android Operating System.) There are open source
|
||||||
IMTERMEDIARY products such as UnifiedPush (https://unifiedpush.org/) which can
|
IMTERMEDIARY products such as UnifiedPush (https://unifiedpush.org/) which can
|
||||||
fulfill this role. If you are using iOS you are not permitted to make or use your own
|
fulfill this role. If you are using iOS you are not permitted to make or use
|
||||||
custom Web Push PROVIDER. Apple will never allow anyone to do that. Apple has
|
your own custom Web Push PROVIDER. Apple will never allow anyone to do that.
|
||||||
none of its own.
|
Apple has none of its own.
|
||||||
|
|
||||||
It is, however, possible to have a sort of proxy working between your SERVICE and
|
It is, however, possible to have a sort of proxy working between your SERVICE
|
||||||
FCM (or iOS). Services that mash up various Push notification services (like
|
and FCM (or iOS). Services that mash up various Push notification services (like
|
||||||
OneSignal) can perform in the role of such proxies.
|
OneSignal) can perform in the role of such proxies.
|
||||||
|
|
||||||
#4 -The INTERMEDIARY- doesn't appear to be anything we should be spending our
|
#4 -The INTERMEDIARY- doesn't appear to be anything we should be spending our
|
||||||
|
|||||||
Reference in New Issue
Block a user