Even if the web application is not currently opened in the browser or is not running on the device, Push API still allows the web application to receive messages pushed by the server.
- Is there good support?
- How it works
- Obtain user's permission
- How the server side works
- Receive push events
Even if the web application is not currently opened in the browser or is not running on the device, the Push API still allows the web application to receive messages pushed by the server.
Using Push API, even if the user is not browsing the site, he can send a message to the user and push the message from the server to the client.
This allows you to send notifications and content updates, enabling you to reach a larger audience.
This is huge because one of the missing pillars of mobile networks compared to native apps is the ability to receive notifications and offline support.
Is there good support?
Push API is the latest feature of the browser API, Chrome (desktop and mobile devices), Firefox and Opera have been supported by Edge since 2016, and Edge has been supported since version 17 (early 2018). For more information, please visit:https://caniuse.com/#feat=push-api
IE does not support it, andSafari has its own implementation.
Since Chrome and Firefox support this feature, approximately 60% of users can access it when browsing on a desktop computer, sosafeuse.
How it works
Overview
When a user accesses your web application, you can trigger a panel to ask whether to allow sending of updates. A kindservice personnelHas been installed and operated in the background to monitorPush event.
Push and notification are a separate concept and API, sometimes due toPush notificationTerm used in iOS. Basically, when a push event is received using the Push API, the Notifications API will be called.
yourserverSend the notification to the client, if permission is obtained, the service worker will receivePush event. The service staff reacted to this incident in the following waysTrigger notification.
Obtain user's permission
The first step in using Push API is to obtain the user's permission to receive data from you.
Many sites have poor results when implementing this panel, which is displayed when the first page loads. The user is not yet convinced that your content is good, and they will deny the permission. Do it wisely.
There are 6 steps:
- Check if the service worker is supported
- Check if Push API is supported
- Registered service staff
- Request permission from user
- Subscribe to users and get PushSubscription objects
- Send PushSubscription object to your server
Check if the service worker is supported
if (!('serviceWorker' in navigator)) {
// Service Workers are not supported. Return
return
}
Check if Push API is supported
if (!('PushManager' in window)) {
// The Push API is not supported. Return
return
}
Registered service staff
This code is registered atworker.js
The files are placed in the domain root directory:
window.addEventListener('load', () => {
navigator.serviceWorker.register('/worker.js')
.then((registration) => {
console.log('Service Worker registration completed with scope: ',
registration.scope)
}, (err) => {
console.log('Service Worker registration failed', err)
})
})
To learn more about how the service staff work in detail, please seeService staff guide.
Request permission from user
Now that the service worker is registered, you can request permission.
The API to perform this operation changes over time, from accepting a callback function as a parameter to returningcommitted to, Breaking backward and forward compatibility, we need to doBothBecause we don't know which method the user's browser implements.
The code is as follows, callingNotification.requestPermission()
.
const askPermission = () => {
return new Promise((resolve, reject) => {
const permissionResult = Notification.requestPermission((result) => {
resolve(result)
})
if (permissionResult) {
permissionResult.then(resolve, reject)
}
})
.then((permissionResult) => {
if (permissionResult !== 'granted') {
throw new Error('Permission denied')
}
})
}
ThispermissionResult
value is a string, which can have the following values:-granted
-default
-denied
This code causes the browser to display the permissions dialog:
If the user clicks "Block", you will no longer be able to request permission from that user, Unless they manually enter and unblock the website in the browser’s advanced settings panel (very unlikely).
Subscribe to users and get PushSubscription objects
If the user grants us a license, we can subscribe to that license and callregistration.pushManager.subscribe()
.
const APP_SERVER_KEY = 'XXX'
window.addEventListener(‘load’, () => {
navigator.serviceWorker.register(’/worker.js’)
.then((registration) => {
askPermission().then(() => {
const options = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(APP_SERVER_KEY)
}
return registration.pushManager.subscribe(options)
}).then((pushSubscription) => {
// we got the pushSubscription object
}
}, (err) => {
console.log(‘Service Worker registration failed’, err)
})
})
APP_SERVER_KEY
Is a string-calledApplication server keyorVAPID key-Identifies the application public key, part of the public/private key pair.
For security reasons, it will be used as part of the verification to ensure that you (only you, not others) can send push messages back to the user.
Send PushSubscription object to your server
In the previous section, we obtainedpushSubscription
Object, which contains all the content we need to send push messages to users. We need to send this information to our server so that we can send notifications later.
We first create the JSON representation of the object
const subscription = JSON.stringify(pushSubscription)
We can useExtract API:
const sendToServer = (subscription) => {
return fetch('/api/subscription', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(subscription)
})
.then((res) => {
if (!res.ok) {
throw new Error('An error occurred')
}
return res.json()
})
.then((resData) => {
if (!(resData.data && resData.data.success)) {
throw new Error('An error occurred')
}
})
}
sendToServer(subscription)
Service-Terminal/api/subscription
The endpoint receives the POST request and can store the subscription information in its storage.
How the server side works
So far, we have only discussed the client part: the permission to get user notifications in the future.
What about the server? What should it do and how should it interact with customers?
These server-side examples use Express.js (http://expressjs.com/) As a basic HTTP framework, but you can write server-side Push API handlers in any language or framework
Register a new customer subscription
When the client sends a new subscription, please remember that we used/api/subscription
HTTP POST endpoint, send the details of the PushSubscription object in JSON format in the body.
We initialize Express.js:
const express = require('express')
const app = express()
This utility function ensures that the request is valid, with body and endpoint attributes, otherwise it will return an error to the client:
const isValidSaveRequest = (req, res) => {
if (!req.body || !req.body.endpoint) {
res.status(400)
res.setHeader('Content-Type', 'application/json')
res.send(JSON.stringify({
error: {
id: 'no-endpoint',
message: 'Subscription must have an endpoint'
}
}))
return false
}
return true
}
The next utility function saves the reservation to the database and returns the resolved promise when the insertion is completed (or failed). ThisinsertToDatabase
Function is a placeholder, we won't go into details here:
const saveSubscriptionToDatabase = (subscription) => {
return new Promise((resolve, reject) => {
insertToDatabase(subscription, (err, id) => {
if (err) {
reject(err)
return
}
<span style="color:#a6e22e">resolve</span>(<span style="color:#a6e22e">id</span>)
})
})
}
We use these functions in the POST request handler below. We check if the request is valid, then save the request, and then returndata.success: true
Reply to the client, or an error occurs:
app.post('/api/subscription', (req, res) => {
if (!isValidSaveRequest(req, res)) {
return
}
saveSubscriptionToDatabase(req, res.body)
.then((subscriptionId) => {
res.setHeader(‘Content-Type’, ‘application/json’)
res.send(JSON.stringify({ data: { success: true } }))
})
.catch((err) => {
res.status(500)
res.setHeader(‘Content-Type’, ‘application/json’)
res.send(JSON.stringify({
error: {
id: ‘unable-to-save-subscription’,
message: ‘Subscription received but failed to save it’
}
}))
})
})
app.listen(3000, () => {
console.log(‘App listening on port 3000’)
})
Send push message
Now that the server has registered the client in its list, we can send Push messages to it. Let's see how it works by creating a sample code snippet that will extract all subscriptions and send a Push message to all subscriptions at the same time.
We use the library becauseWeb Push ProtocolYescomplicated, And lib allows us to abstract a lot of low-level code to ensure that we can work safely and properly handle any edge cases.
This example uses
web-push
Node.jslibrary(https://github.com/web-push-libs/web-push) Processing and sending Push messages
We first initializeweb-push
lib, we generate a tuple of private and public keys and set them as VAPID details:
const webpush = require('web-push')
const vapidKeys = webpush.generateVAPIDKeys()
const PUBLIC_KEY = ‘XXX’
const PRIVATE_KEY = ‘YYY’
const vapidKeys = {
publicKey: PUBLIC_KEY,
privateKey: PRIVATE_KEY
}
webpush.setVapidDetails(
‘mailto:[email protected]’,
vapidKeys.publicKey,
vapidKeys.privateKey
)
Then we build atriggerPush()
Method, responsible for sending the push event to the client. It's just callingwebpush.sendNotification()
And catch any errors. If the HTTP status code that returns an error is410, meaning isGone, We will delete the subscriber from the database.
const triggerPush = (subscription, dataToSend) => {
return webpush.sendNotification(subscription, dataToSend)
.catch((err) => {
if (err.statusCode === 410) {
return deleteSubscriptionFromDatabase(subscription._id)
} else {
console.log('Subscription is no longer valid: ', err)
}
})
}
We did not implement getting the subscription from the database, but kept it as a stub:
const getSubscriptionsFromDatabase = () => {
//stub
}
The essence of the code is that the POST request/api/push
Endpoint:
app.post('/api/push', (req, res) => {
return getSubscriptionsFromDatabase()
.then((subscriptions) => {
let promiseChain = Promise.resolve()
for (let i = 0; i < subscriptions.length; i++) {
const subscription = subscriptions[i]
promiseChain = promiseChain.then(() => {
return triggerPush(subscription, dataToSend)
})
}
return promiseChain
})
.then(() => {
res.setHeader('Content-Type', 'application/json')
res.send(JSON.stringify({ data: { success: true } }))
})
.catch((err) => {
res.status(500)
res.setHeader('Content-Type', 'application/json')
res.send(JSON.stringify({
error: {
id: 'unable-to-send-messages',
message: `Failed to send the push ${err.message}`
}
}))
})
})
What the above code does is: it gets all subscriptions from the database, then iterates over them, and then callstriggerPush()
The function we explained before.
After the subscription is completed, we will return a successful JSON response, unless an error occurs and a 500 error is returned.
In the real world...
Unless you have a very specific use case, or you just want to learn technology or like DIY, it is unlikely to set up your own Push server. Instead, you usually want to use OneSignal (https://onesignal.com), transparently and freely handle Push events on all platforms (including Safari and iOS).
Receive push events
When a Push event is sent from the server, how does the client get it?
This is normalJavaScriptEvent listener, inpush
Event, which runs inside the Service Worker:
self.addEventListener('push', (event) => {
// data is available in event.data
})
event.data
containPushMessageData
Object, which exposes methods for retrieving the push data sent by the server in the required format:
- arrayBuffer(): As
ArrayBuffer
purpose - blob(): as aspotpurpose
- json(): Resolves toJSON format
- text(): Plain Text
You would usually useevent.data.json()
.
Show notification
Here we are withNotification API, But this is for good reason, because one of the main use cases of Push API is to display notifications.
In uspush
In the event listener in the Service Worker, we need to display a notification to the user and tell the event to wait until the browser displays the event before the function can terminate. We extend the survival time of the incident until the browser finishes displaying the notification (until the fulfillment of the promise is resolved), otherwise the service worker may stop during processing:
self.addEventListener('push', (event) => {
const promiseChain = self.registration.showNotification('Hey!')
event.waitUntil(promiseChain)
})
More information about notificationsNotification API guide.
Download mine for freeJavaScript beginner's manual
More browser tutorials:
- HTML5 provides some useful tips
- How do I make a CMS-based website work offline
- The complete guide to progressive web applications
- Extract API
- Push API guide
- Channel Messaging API
- Service staff tutorial
- Cache API guide
- Notification API guide
- Dive into IndexedDB
- Selectors API: querySelector and querySelectorAll
- Load JavaScript efficiently with delay and asynchrony
- Document Object Model (DOM)
- Web storage API: local storage and session storage
- Understand how HTTP cookies work
- History API
- WebP image format
- XMLHttpRequest (XHR)
- In-depth SVG tutorial
- What is the data URL
- Roadmap for learning network platforms
- CORS, cross-domain resource sharing
- Network worker
- requestAnimationFrame() guide
- What is Doctype
- Use DevTools console and console API
- Speech Synthesis API
- How to wait for DOM ready event in pure JavaScript
- How to add classes to DOM elements
- How to traverse DOM elements from querySelectorAll
- How to remove classes from DOM elements
- How to check if a DOM element has a class
- How to change the DOM node value
- How to add the click event to the list of DOM elements returned from querySelectorAll
- WebRTC, real-time Web API
- How to get the scroll position of an element in JavaScript
- How to replace DOM elements
- How to only accept images in the input file field
- Why use the preview version of the browser?
- Blob object
- File object
- FileReader object
- FileList object
- ArrayBuffer
- ArrayBufferView
- URL object
- Type array
- DataView object
- BroadcastChannel API
- Streams API
- FormData object
- Navigator object
- How to use the geolocation API
- How to use getUserMedia()
- How to use the drag and drop API
- How to scroll on a web page
- Processing the form in JavaScript
- Keyboard events
- Mouse event
- Touch event
- How to remove all children from DOM element
- How to create HTML attributes using raw Javascript
- How to check if the checkbox is checked using JavaScript?
- How to copy to clipboard using JavaScript
- How to disable buttons using JavaScript
- How to make a page editable in the browser
- How to use URLSearchParams to get query string value in JavaScript
- How to delete all CSS on the page at once
- How to use insertAdjacentHTML
- Safari, warning before exit
- How to add an image to the DOM using JavaScript
- How to reset the table
- How to use Google fonts