Initial checkin
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 192" width="192" height="192">
|
||||
<rect width="192" height="192" rx="32" fill="#1d4ed8"/>
|
||||
<text x="96" y="130" font-size="110" text-anchor="middle" font-family="serif">🪣</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 245 B |
@@ -0,0 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Notification + Push test</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; max-width: 32rem; margin: 2rem auto; padding: 0 1rem; line-height: 1.5; }
|
||||
button { display: block; width: 100%; padding: .9rem; margin: .6rem 0; font-size: 1rem; border: 1px solid #999; border-radius: .5rem; background: #f5f5f5; }
|
||||
button:active { background: #e5e5e5; }
|
||||
h2 { margin-top: 1.5rem; font-size: 1rem; color: #555; border-bottom: 1px solid #ddd; padding-bottom: .25rem; }
|
||||
#status { padding: .75rem; border-radius: .5rem; background: #eef; white-space: pre-wrap; font-size: .9rem; }
|
||||
code { background: #eee; padding: 0 .25rem; border-radius: .25rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Notification + Push test</h1>
|
||||
|
||||
<h2>Local (page open)</h2>
|
||||
<button id="register">1. Register service worker</button>
|
||||
<button id="permission">2. Request permission</button>
|
||||
<button id="notify">3. Fire a local notification</button>
|
||||
|
||||
<h2>Push (server-sent, works with tab closed)</h2>
|
||||
<button id="subscribe">4. Subscribe to push</button>
|
||||
<button id="sendTest">5. Ask server to push now</button>
|
||||
|
||||
<div id="status">Status will appear here.</div>
|
||||
|
||||
<script>
|
||||
const statusEl = document.getElementById('status');
|
||||
function log(msg) {
|
||||
statusEl.textContent = msg + '\n\n' +
|
||||
'Permission: ' + Notification.permission + '\n' +
|
||||
'SW controller: ' + (navigator.serviceWorker.controller ? 'yes' : 'no');
|
||||
}
|
||||
|
||||
// VAPID public key arrives base64url-encoded; the browser needs a Uint8Array.
|
||||
function urlBase64ToUint8Array(base64String) {
|
||||
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
||||
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
|
||||
const raw = atob(base64);
|
||||
return Uint8Array.from([...raw].map(c => c.charCodeAt(0)));
|
||||
}
|
||||
|
||||
document.getElementById('register').addEventListener('click', async () => {
|
||||
if (!('serviceWorker' in navigator)) return log('No service worker support.');
|
||||
try {
|
||||
await navigator.serviceWorker.register('sw.js');
|
||||
await navigator.serviceWorker.ready;
|
||||
log('Service worker registered and ready.');
|
||||
} catch (e) {
|
||||
log('Registration failed: ' + e);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('permission').addEventListener('click', async () => {
|
||||
const result = await Notification.requestPermission();
|
||||
log('Permission result: ' + result);
|
||||
});
|
||||
|
||||
document.getElementById('notify').addEventListener('click', async () => {
|
||||
if (Notification.permission !== 'granted') return log('Permission not granted yet (run step 2).');
|
||||
|
||||
const reg = await navigator.serviceWorker.ready; // Android Chrome needs the SW, not new Notification()
|
||||
await reg.showNotification('OGLAS trough alert!', {
|
||||
body: 'Trough 21 appears to be empty',
|
||||
icon: '/sh3d/icon.svg',
|
||||
badge: 'https://dummyimage.com/96x96/333/fff.png&text=!',
|
||||
vibrate: [120, 60, 120],
|
||||
tag: 'test', renotify: true,
|
||||
actions: [ { action: 'open', title: 'Open' }, { action: 'dismiss', title: 'Dismiss' } ]
|
||||
});
|
||||
log('Local notification fired.');
|
||||
});
|
||||
|
||||
document.getElementById('subscribe').addEventListener('click', async () => {
|
||||
if (Notification.permission !== 'granted') return log('Grant permission first (step 2).');
|
||||
try {
|
||||
const reg = await navigator.serviceWorker.ready;
|
||||
const key = await (await fetch('/sh3d/api/vapidPublicKey')).text();
|
||||
const sub = await reg.pushManager.subscribe({
|
||||
userVisibleOnly: true, // required on Chrome
|
||||
applicationServerKey: urlBase64ToUint8Array(key)
|
||||
});
|
||||
await fetch('/sh3d/api/subscribe', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(sub)
|
||||
});
|
||||
log('Subscribed and sent subscription to server.');
|
||||
} catch (e) {
|
||||
log('Subscribe failed: ' + e);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('sendTest').addEventListener('click', async () => {
|
||||
try {
|
||||
const res = await fetch('/sh3d/api/send', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title: 'Server push', body: 'If you see this, the loop works.' })
|
||||
});
|
||||
const data = await res.json();
|
||||
log('Server reports sent to ' + data.sent + ' subscription(s).\nNow background the tab and send again to prove it works closed.');
|
||||
} catch (e) {
|
||||
log('Send request failed: ' + e);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
// sw.js — service worker for the notification + push test page.
|
||||
|
||||
self.addEventListener('install', () => self.skipWaiting());
|
||||
self.addEventListener('activate', (event) => event.waitUntil(self.clients.claim()));
|
||||
|
||||
// Server-sent push lands here even when every tab is closed.
|
||||
self.addEventListener('push', (event) => {
|
||||
const data = event.data ? event.data.json() : {};
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(data.title || 'Push', {
|
||||
body: data.body || '',
|
||||
icon: 'https://dummyimage.com/192x192/47a/fff.png&text=%E2%86%93',
|
||||
vibrate: [120, 60, 120],
|
||||
tag: 'push'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Handle taps on the notification or its action buttons.
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
const action = event.action; // '', 'open', or 'dismiss'
|
||||
event.notification.close();
|
||||
if (action === 'dismiss') return;
|
||||
|
||||
event.waitUntil(
|
||||
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((windows) => {
|
||||
for (const win of windows) {
|
||||
if ('focus' in win) return win.focus();
|
||||
}
|
||||
if (self.clients.openWindow) return self.clients.openWindow('/');
|
||||
})
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user