push notifications
This commit is contained in:
79
app.js
79
app.js
@@ -9,6 +9,8 @@ let gameState = 'setup'; // setup, running, paused, over
|
|||||||
let carouselPosition = 0;
|
let carouselPosition = 0;
|
||||||
let startX = 0;
|
let startX = 0;
|
||||||
let currentX = 0;
|
let currentX = 0;
|
||||||
|
let pushSubscription = null;
|
||||||
|
const PUBLIC_VAPID_KEY = 'BNIXGVBzq6SNqvlDMFylw_hLTpf_J96ddbwfMa9Cn1tFQ1-vDqmz_NQS0a5UiczqAJ-uYs-6EeuwrnwaMRGtifk=';
|
||||||
|
|
||||||
// DOM Elements
|
// DOM Elements
|
||||||
const carousel = document.getElementById('carousel');
|
const carousel = document.getElementById('carousel');
|
||||||
@@ -37,6 +39,47 @@ const cameraCancelButton = document.getElementById('cameraCancelButton');
|
|||||||
|
|
||||||
let stream = null;
|
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
|
// Add sound toggle button
|
||||||
const createSoundToggleButton = () => {
|
const createSoundToggleButton = () => {
|
||||||
const headerButtons = document.querySelector('.header-buttons');
|
const headerButtons = document.querySelector('.header-buttons');
|
||||||
@@ -177,11 +220,13 @@ gameButton.addEventListener('click', () => {
|
|||||||
gameState = 'paused';
|
gameState = 'paused';
|
||||||
audioManager.play('gamePause');
|
audioManager.play('gamePause');
|
||||||
stopTimer();
|
stopTimer();
|
||||||
|
// sendPushNotification('Game Paused', 'The game timer has paused!');
|
||||||
break;
|
break;
|
||||||
case 'paused':
|
case 'paused':
|
||||||
gameState = 'running';
|
gameState = 'running';
|
||||||
audioManager.play('gameResume');
|
audioManager.play('gameResume');
|
||||||
startTimer();
|
startTimer();
|
||||||
|
// sendPushNotification('Game Resumed', 'The game timer has resumed!');
|
||||||
break;
|
break;
|
||||||
case 'over':
|
case 'over':
|
||||||
// Reset timers and start new game
|
// Reset timers and start new game
|
||||||
@@ -198,6 +243,26 @@ gameButton.addEventListener('click', () => {
|
|||||||
saveData();
|
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
|
// Timer variables
|
||||||
let timerInterval = null;
|
let timerInterval = null;
|
||||||
|
|
||||||
@@ -778,15 +843,19 @@ if ('serviceWorker' in navigator) {
|
|||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
navigator.serviceWorker.register('/sw.js')
|
navigator.serviceWorker.register('/sw.js')
|
||||||
.then(registration => {
|
.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();
|
setupDeepLinks();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.log('ServiceWorker registration failed: ', error);
|
console.log('ServiceWorker registration failed:', error);
|
||||||
|
|
||||||
// Still try to handle deep links even if service worker failed
|
|
||||||
setupDeepLinks();
|
setupDeepLinks();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
10
index.html
10
index.html
@@ -164,6 +164,16 @@
|
|||||||
.catch((err) => console.log("Service Worker Failed", err));
|
.catch((err) => console.log("Service Worker Failed", err));
|
||||||
}
|
}
|
||||||
</script>
|
</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">
|
<footer class="app-footer">
|
||||||
<div class="author-info">
|
<div class="author-info">
|
||||||
<p>Vibe coded by Martin</p>
|
<p>Vibe coded by Martin</p>
|
||||||
|
|||||||
@@ -95,5 +95,6 @@
|
|||||||
"url": "/?action=reset",
|
"url": "/?action=reset",
|
||||||
"icons": [{ "src": "/icons/reset.png", "sizes": "192x192" }]
|
"icons": [{ "src": "/icons/reset.png", "sizes": "192x192" }]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"gcm_sender_id": "103953800507"
|
||||||
}
|
}
|
||||||
29
sw.js
29
sw.js
@@ -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
|
// This helps with navigation after app is installed
|
||||||
self.addEventListener('notificationclick', event => {
|
self.addEventListener('notificationclick', event => {
|
||||||
|
console.log('[ServiceWorker] Notification click received');
|
||||||
|
|
||||||
event.notification.close();
|
event.notification.close();
|
||||||
|
|
||||||
// This looks to see if the current is already open and focuses if it is
|
// Handle the notification click
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
self.clients.matchAll({
|
self.clients.matchAll({ type: 'window' })
|
||||||
type: 'window'
|
|
||||||
})
|
|
||||||
.then(clientList => {
|
.then(clientList => {
|
||||||
// Check if there is already a window/tab open with the target URL
|
|
||||||
for (const client of clientList) {
|
for (const client of clientList) {
|
||||||
// If so, just focus it
|
|
||||||
if (client.url.startsWith(self.location.origin) && 'focus' in client) {
|
if (client.url.startsWith(self.location.origin) && 'focus' in client) {
|
||||||
return client.focus();
|
return client.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If not, open a new window/tab
|
|
||||||
if (self.clients.openWindow) {
|
if (self.clients.openWindow) {
|
||||||
return self.clients.openWindow('/');
|
return self.clients.openWindow('/');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user