Answer the question
In order to leave comments, you need to log in
How to work with web push notifications?
I am learning web push notification technology. Experimenting with code based on a small javascript code. I took the basis for it from the repository https://github.com/Minishlink/web-push-php-example I found suspicious logic in the code and would like to clarify the situation.
1. On the DOMContentLoaded event, the worker is registered. It doesn't matter if the script is run for the first time or the 100th. It registers the worker every time. Is this the optimal solution? After all, the worker must be stored in the browser's memory. It would be easier to first check if this worker is already registered.
2. When the worker is registered, the push_updateSubscription function is called, which monitors the change of the token after registration. I don't know what scenarios can lead to this token changing, but I understand that each page load with such logic will lead to a call to the database to update the subscription token. This logic doesn't look good at all. How is it supposed to work in reality?
3. Did I understand correctly that when we update the subscription, we identify the user by the endpoint url, which replaces the device id for us?
document.addEventListener('DOMContentLoaded', () => {
const applicationServerKey =
'публичный_ключ';
let isPushEnabled = false;
if (!('serviceWorker' in navigator)) {
console.warn('Service workers are not supported by this browser');
//changePushButtonState('incompatible');
return;
}
if (!('PushManager' in window)) {
console.warn('Push notifications are not supported by this browser');
//changePushButtonState('incompatible');
return;
}
if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
console.warn('Notifications are not supported by this browser');
//changePushButtonState('incompatible');
return;
}
// Check the current Notification permission.
// If its denied, the button should appears as such, until the user changes the permission manually
if (Notification.permission === 'denied') {
console.warn('Notifications are denied by the user');
//changePushButtonState('incompatible');
return;
}
navigator.serviceWorker.register('/local/web-push/serviceWorker.js').then(
async() => {
console.log('[SW] Service worker has been registered');
let result = await push_updateSubscription();
console.log(result);
},
e => {
console.error('[SW] Service worker registration failed', e);
//changePushButtonState('incompatible');
}
);
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
function checkNotificationPermission() {
return new Promise((resolve, reject) => {
if (Notification.permission === 'denied') {
return reject(new Error('Push messages are blocked.'));
}
if (Notification.permission === 'granted') {
return resolve();
}
if (Notification.permission === 'default') {
return Notification.requestPermission().then(result => {
if (result !== 'granted') {
reject(new Error('Bad permission result'));
}
resolve();
});
}
});
}
async function push_subscribe() {
//changePushButtonState('computing');
return checkNotificationPermission()
.then(() => navigator.serviceWorker.ready)
.then(serviceWorkerRegistration =>
serviceWorkerRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(applicationServerKey),
})
)
.then(subscription => {
// Subscription was successful
// create subscription on your server
return push_sendSubscriptionToServer(subscription, 'POST');
})
.catch(e => {
if (Notification.permission === 'denied') {
// The user denied the notification permission which
// means we failed to subscribe and the user will need
// to manually change the notification permission to
// subscribe to push messages
console.warn('Notifications are denied by the user.');
changePushButtonState('incompatible');
} else {
// A problem occurred with the subscription; common reasons
// include network errors or the user skipped the permission
console.error('Impossible to subscribe to push notifications', e);
changePushButtonState('disabled');
}
});
}
async function push_updateSubscription() {
return navigator.serviceWorker.ready
.then(serviceWorkerRegistration => serviceWorkerRegistration.pushManager.getSubscription())
.then(subscription => {
if (!subscription) {
return push_subscribe();
}
// Keep your server in sync with the latest endpoint
return push_sendSubscriptionToServer(subscription, 'PUT');
})
.catch(e => {
console.error('Error when updating the subscription', e);
});
}
async function push_sendSubscriptionToServer(subscription, method) {
const key = subscription.getKey('p256dh');
const token = subscription.getKey('auth');
const contentEncoding = (PushManager.supportedContentEncodings || ['aesgcm'])[0];
return fetch('/local/web-push/push_subscription.php', {
method,
body: JSON.stringify({
endpoint: subscription.endpoint,
publicKey: key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : null,
authToken: token ? btoa(String.fromCharCode.apply(null, new Uint8Array(token))) : null,
contentEncoding,
}),
});
}
});
Answer the question
In order to leave comments, you need to log in
1. In the demo, we took the path of least resistance, i.e. in your application, you can set your own rules when you need to register a worker. Understanding the intricacies of JS and its worker service is not my forte, but a worker is registered once after it is called and lives until it is killed (manually or by code). There were no problems with this in my production application)
2. Scenario - complete clearing of the browser cache (ie, deleting the service worker and the subscription endpoint). This is necessary if you want to consider such a user as your old subscriber, and not as a new one, "from scratch".
3. Yes
Re-registration of the service worker is due to the fact that the service worker must always be "fresh". You can set up a re-registration only when the service worker code has actually changed, for example by comparing the version of your code or in some other way.
Refreshing the data is necessary when the user clears the browser cache and unregisters the service worker.
Thus, when the user logs in again, new keys will be written out instead of invalid old ones.
Endpoint url is not a device ID. If you need to track that the signed device has already been signed before, use special libraries (for example , fingerprint ) and, passing the fingerprint along with the subscription, match the data on the backend.
However, the device's old subscription becomes invalid when a new one appears, so don't be afraid that a few pushes will come to the device.
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question