Web-Push notification script working on Firefox but not on Chrome


#1

I am building a web push notification system and I am using the concept used in this example:

https://github.com/Minishlink/web-push-php-example

I have the following code in my JS file. It checks for API support, checks if notifications are not disabled, registers the service worker, asks for permission to display notifications, if allowed subscribes the user and sends the details to the server. If the user is already subscribed, it updates the endpoint value in the DB.

When I run this on Firefox 61, it works fine, but when I run it on Chrome 67 I get this error:

Uncaught (in promise) TypeError: Cannot read property 'getKey' of null
    at pushSubscribe (notification.js:48)
    at navigator.serviceWorker.ready.then.then.subscription (notification.js:30)

My understanding is that Chrome does not detect the subscription when the service worker is registered and the user is subscribed, hence it gives the error. Any ideas how to fix this?

Thanks.

document.addEventListener('DOMContentLoaded', () => {
    // Feature detection
	if (!('serviceWorker' in navigator)) {
		alert('Service Worker API isn’t supported.');
	} else if (!('PushManager' in window)) {
		alert('Push API isn’t supported.');
	} else if (!('Notification' in window)) {
		alert('Notifications API isn’t supported.');
	} else if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
		alert('Notifications aren’t supported.');
	
	// Check permission
	} else if (Notification.permission == 'denied') {
		alert('Notifications are disabled.');
	} else {
		
		// Register service worker
		navigator.serviceWorker.register('/service-worker.js').then(() => {
			navigator.serviceWorker.ready.then(serviceWorkerRegistration => serviceWorkerRegistration.pushManager.getSubscription()).then(subscription => {
				if (!subscription) {
					// Subscribe user
					navigator.serviceWorker.ready.then(serviceWorkerRegistration => serviceWorkerRegistration.pushManager.subscribe({
						userVisibleOnly: true,
						applicationServerKey: urlBase64ToUint8Array('key-goes-here'),
					})).then(subscription => {
						return pushSubscribe(subscription);
					});
				}
				// Update endpoint
				return pushSubscribe(subscription);
			});
		});
		
		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 pushSubscribe(subscription) {
			const key = subscription.getKey('p256dh');
			const token = subscription.getKey('auth');
			const contentEncoding = (PushManager.supportedContentEncodings || ['aesgcm'])[0];

			return fetch('/scripts/notification-subscribe.php', {
				method: 'POST',
				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,
					user: userId, // generated beforehand
				}),
			}).then(() => subscription);
		}
	}
});

#2

Cleaning the code up a bit for readability...

if (!subscription) {
	// Subscribe user
	navigator.serviceWorker.ready
    .then(serviceWorkerRegistration => serviceWorkerRegistration.pushManager.subscribe({ stuff}))
	.then(subscription => {
		return pushSubscribe(subscription);
	});
}
// Update endpoint
return pushSubscribe(subscription);

So... if subscription is NOT defined... push a registration and then call pushSubscribe. But... regardless of if it existed or not, we call pushSubscribe immediately (while the if is busy processing its stuff), handing it... the undefined thing. And so the browser correctly catches that you're trying to getKey of a null object.


#3

Thank you, I'm now using an if-else check as follows, which got rid of the error and it works fine now.

if (!subscription) {
	// Subscribe user
	navigator.serviceWorker.ready
	.then(serviceWorkerRegistration => serviceWorkerRegistration.pushManager.subscribe({
		userVisibleOnly: true,
		applicationServerKey: urlBase64ToUint8Array('BNwjaNBKGM13IAef-gJr7C95W3yLJe2F5X0zLnwacN3zCnZK15Vqf3ijeHl9k7K0yBFX3ZwxAmldPoVDpi6iETA'),}))
	.then(subscription => {
		return pushSubscribe(subscription);
	});
} else {
	// Update endpoint
	return pushSubscribe(subscription);
}