i’m trying to create a simple web push notif using service worker, it works well on browsers in desktop, but on android device not working!
on firefox android: pushManager.subscribe() return null
on chrome android, it doesnt even reach to subscribe stage
index.php file:
<?php
require __DIR__ . '/vendor/autoload.php';
use MinishlinkWebPushVAPID;
// Generate VAPID keys if they don't exist
$vapidFile = __DIR__ . '/vapid.json';
if (!file_exists($vapidFile)) {
$keys = VAPID::createVapidKeys();
file_put_contents($vapidFile, json_encode($keys));
} else {
$keys = json_decode(file_get_contents($vapidFile), true);
}
// Save subscription if POST request
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
var_dump(file_get_contents('php://input'));
var_dump(__DIR__);
file_put_contents(__DIR__ . '/subscription.json', file_get_contents('php://input'));
echo 'Subscription saved';
exit;
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Push Demo</title>
</head>
<body>
<h2>Web Push Demo</h2>
<button id="subscribeBtn">Subscribe for Push</button>
<script>
const publicKey = '<?= $keys['publicKey'] ?>';
function urlBase64ToUint8Array(base64String) {
// Ensure correct padding
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/-/g, '+')
.replace(/_/g, '/');
const rawData = atob(base64);
const buffer = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
buffer[i] = rawData.charCodeAt(i);
}
return buffer;
}
async function subscribe() {
try{
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
alert('Please enable notifications!');
return;
}
const reg = await navigator.serviceWorker.register('sw.js');
// Wait for activation if needed
if (!reg.active) {
await new Promise(resolve => {
if (reg.installing) {
reg.installing.onstatechange = () => {
if (reg.active) resolve();
};
} else if (reg.waiting) {
resolve(); // Skip waiting for user
}
});
}
console.log("SW now active. PushManager:", reg.pushManager);
console.log("reg object:", reg);
console.log("reg.scope: " + reg.scope);
const existingSub = await reg.pushManager.getSubscription();
if (existingSub) {
await existingSub.unsubscribe();
console.log("Already subscribed:", existingSub);
alert("Already subscribed");
} else {
console.log("Not subscribed yet.");
alert("Not subscribed yet.");
}
console.log("Key length: " + urlBase64ToUint8Array(publicKey).length);
const sub = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicKey)
});
console.log(sub);
alert(sub);
await fetch('', {
method: 'POST',
body: JSON.stringify(sub),
headers: { 'Content-Type': 'application/json' }
});
const checkSub = await reg.pushManager.getSubscription();
if (existingSub) {
console.log("subscribed");
alert("subscribed");
} else {
console.log("Not subscribed yet.");
alert("Not subscribed yet.");
}
}catch (err) {
console.error("Subscription failed:");
console.error("Error name:", err.name);
console.error("Error message:", err.message);
console.error("Error stack:", err.stack);
alert(`Subscribe failed: ${err.name} - ${err.message}`);
return null;
}
}
document.getElementById('subscribeBtn').onclick = () => {
if ('serviceWorker' in navigator && 'PushManager' in window) {
subscribe().catch(err => console.log('Error: ' + err));
} else {
alert('Push not supported');
}
};
</script>
</body>
</html>
and this is sw.js file in the same directory:
// sw.js
self.addEventListener('install', e => self.skipWaiting());
self.addEventListener('activate', e => console.log('SW activated'));
self.addEventListener('push', e => {
const body = e.data?.text() || 'No payload';
e.waitUntil(self.registration.showNotification('Push', { body }));
});
some considerations:
a. i ran this script on a valid https website
b. i used valid VAPID and it works on desktop browsers including firefox and chrome
c. i granted permissions on android device so permissions not the issue!
if you could help me on this, im gratefull…