push notifications

This commit is contained in:
cpu
2025-03-26 05:04:50 +01:00
parent a0f3489656
commit fc278ed256
4 changed files with 125 additions and 30 deletions

79
app.js
View File

@@ -9,6 +9,8 @@ let gameState = 'setup'; // setup, running, paused, over
let carouselPosition = 0;
let startX = 0;
let currentX = 0;
let pushSubscription = null;
const PUBLIC_VAPID_KEY = 'BNIXGVBzq6SNqvlDMFylw_hLTpf_J96ddbwfMa9Cn1tFQ1-vDqmz_NQS0a5UiczqAJ-uYs-6EeuwrnwaMRGtifk=';
// DOM Elements
const carousel = document.getElementById('carousel');
@@ -37,6 +39,47 @@ const cameraCancelButton = document.getElementById('cameraCancelButton');
let stream = null;
async function subscribeToPushNotifications() {
if ('serviceWorker' in navigator && 'PushManager' in window) {
try {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(PUBLIC_VAPID_KEY)
});
// Send subscription to your server
await fetch('https://webpush.virtonline.eu/subscribe', {
method: 'POST',
body: JSON.stringify(subscription),
headers: {
'Content-Type': 'application/json'
}
});
pushSubscription = subscription;
console.log('Push subscription successful');
} catch (error) {
console.error('Error subscribing to push:', error);
}
}
}
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;
}
// Add sound toggle button
const createSoundToggleButton = () => {
const headerButtons = document.querySelector('.header-buttons');
@@ -177,11 +220,13 @@ gameButton.addEventListener('click', () => {
gameState = 'paused';
audioManager.play('gamePause');
stopTimer();
// sendPushNotification('Game Paused', 'The game timer has paused!');
break;
case 'paused':
gameState = 'running';
audioManager.play('gameResume');
startTimer();
// sendPushNotification('Game Resumed', 'The game timer has resumed!');
break;
case 'over':
// Reset timers and start new game
@@ -198,6 +243,26 @@ gameButton.addEventListener('click', () => {
saveData();
});
async function sendPushNotification(title, message) {
if (!pushSubscription) return;
try {
await fetch('https://webpush.virtonline.eu/flic-webhook', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: title,
body: message,
action: 'game_update'
})
});
} catch (error) {
console.error('Error sending push notification:', error);
}
}
// Timer variables
let timerInterval = null;
@@ -778,15 +843,19 @@ if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('ServiceWorker registered: ', registration);
console.log('ServiceWorker registered');
// Request notification permission and subscribe
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
subscribeToPushNotifications();
}
});
// Setup and handle deep links after service worker is ready
setupDeepLinks();
})
.catch(error => {
console.log('ServiceWorker registration failed: ', error);
// Still try to handle deep links even if service worker failed
console.log('ServiceWorker registration failed:', error);
setupDeepLinks();
});
});

View File

@@ -164,6 +164,16 @@
.catch((err) => console.log("Service Worker Failed", err));
}
</script>
<script>
// Request notification permission on page load
document.addEventListener('DOMContentLoaded', () => {
if ('Notification' in window) {
Notification.requestPermission().then(permission => {
console.log('Notification permission:', permission);
});
}
});
</script>
<footer class="app-footer">
<div class="author-info">
<p>Vibe coded by Martin</p>

View File

@@ -95,5 +95,6 @@
"url": "/?action=reset",
"icons": [{ "src": "/icons/reset.png", "sizes": "192x192" }]
}
]
],
"gcm_sender_id": "103953800507"
}

29
sw.js
View File

@@ -148,24 +148,39 @@ self.addEventListener('message', event => {
}
});
self.addEventListener('push', event => {
console.log('[ServiceWorker] Push received');
const data = event.data ? event.data.json() : {};
const title = data.title || 'Game Timer Notification';
const options = {
body: data.body || 'You have a new notification',
icon: '/icons/android-chrome-192x192.png',
badge: '/icons/android-chrome-192x192.png',
data: data
};
event.waitUntil(
self.registration.showNotification(title, options)
);
});
// This helps with navigation after app is installed
self.addEventListener('notificationclick', event => {
console.log('[ServiceWorker] Notification click received');
event.notification.close();
// This looks to see if the current is already open and focuses if it is
// Handle the notification click
event.waitUntil(
self.clients.matchAll({
type: 'window'
})
self.clients.matchAll({ type: 'window' })
.then(clientList => {
// Check if there is already a window/tab open with the target URL
for (const client of clientList) {
// If so, just focus it
if (client.url.startsWith(self.location.origin) && 'focus' in client) {
return client.focus();
}
}
// If not, open a new window/tab
if (self.clients.openWindow) {
return self.clients.openWindow('/');
}