Files
game-timer/sw.js

223 lines
7.2 KiB
JavaScript

// Service Worker version
const CACHE_VERSION = 'v1.0.2';
const CACHE_NAME = `game-timer-${CACHE_VERSION}`;
// Files to cache
const CACHE_FILES = [
'/',
'/sw.js',
'/index.html',
'/manifest.json',
'/css/styles.css',
'/favicon.ico',
'/icons/android-chrome-192x192.png',
'/icons/android-chrome-512x512.png',
'/icons/apple-touch-icon.png',
'/icons/favicon-32x32.png',
'/icons/favicon-16x16.png',
'/js/app.js',
'/js/config.js',
'/js/env-loader.js',
'/js/ui/audio.js',
'/js/ui/camera.js',
'/js/ui/ui.js',
'/js/core/state.js',
'/js/core/timer.js',
'/js/core/gameActions.js',
'/js/core/playerManager.js',
'/js/core/eventHandlers.js',
'/js/services/pushFlicIntegration.js',
'/js/services/serviceWorkerManager.js'
];
// Install event - Cache files
self.addEventListener('install', event => {
console.log('[ServiceWorker] Install');
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('[ServiceWorker] Caching app shell');
return cache.addAll(CACHE_FILES);
})
.then(() => {
console.log('[ServiceWorker] Skip waiting on install');
return self.skipWaiting();
})
);
});
// Activate event - Clean old caches
self.addEventListener('activate', event => {
console.log('[ServiceWorker] Activate');
event.waitUntil(
caches.keys().then(keyList => {
return Promise.all(keyList.map(key => {
if (key !== CACHE_NAME) {
console.log('[ServiceWorker] Removing old cache', key);
return caches.delete(key);
}
}));
})
.then(() => {
console.log('[ServiceWorker] Claiming clients');
return self.clients.claim();
})
);
});
// Helper function to determine if a response should be cached
function shouldCacheResponse(request, response) {
// Only cache GET requests
if (request.method !== 'GET') return false;
// Don't cache errors
if (!response || response.status !== 200) return false;
// Check if URL should be cached
const url = new URL(request.url);
// Don't cache query parameters (except common ones for content)
if (url.search && !url.search.match(/\?(v|version|cache)=/)) return false;
return true;
}
self.addEventListener('push', event => {
console.log('[ServiceWorker] Push received');
let pushData = {
title: 'Flic Action',
body: 'Button pressed!',
data: {
action: 'Unknown',
button: 'Unknown',
batteryLevel: undefined,
timestamp: new Date().toISOString()
}
};
// --- Attempt to parse data payload ---
if (event.data) {
try {
const parsedData = event.data.json();
console.log('[ServiceWorker] Push data:', parsedData);
// Use parsed data for notification and message
pushData = {
title: parsedData.title || pushData.title,
body: parsedData.body || pushData.body,
data: parsedData.data || pushData.data // Expecting { action: 'SingleClick', button: 'game-button', batteryLevel: 75 }
};
// Ensure all required fields are present in data
pushData.data = pushData.data || {};
if (!pushData.data.timestamp) {
pushData.data.timestamp = new Date().toISOString();
}
} catch (e) {
console.error('[ServiceWorker] Error parsing push data:', e);
// Use default notification if parsing fails
pushData.body = event.data.text() || pushData.body; // Fallback to text
}
} else {
console.log('[ServiceWorker] Push event but no data');
}
// --- Send message to client(s) ---
const messagePayload = {
type: 'flic-action', // Custom message type
action: pushData.data.action || 'Unknown', // e.g., 'SingleClick', 'DoubleClick', 'Hold'
button: pushData.data.button || 'Unknown', // e.g., the button name
timestamp: pushData.data.timestamp || new Date().toISOString(), // e.g., the timestamp of the action
batteryLevel: pushData.data.batteryLevel // e.g., the battery level percentage
};
console.log('[ServiceWorker] Preparing message payload:', messagePayload);
// Check if this is a low battery alert that needs a notification
const isBatteryAlert = messagePayload.batteryLevel !== undefined &&
messagePayload.batteryLevel < 50; // Use the same threshold as in the app
if (isBatteryAlert) {
console.log(`[ServiceWorker] Low battery alert detected: ${messagePayload.batteryLevel}%`);
// Change notification title/body for battery alerts
pushData.title = 'Flic Button Low Battery';
pushData.body = `Battery level is ${messagePayload.batteryLevel}%. Please replace batteries soon.`;
}
// Send message to all open PWA windows controlled by this SW
event.waitUntil(
self.clients.matchAll({
type: 'window', // Only target window clients
includeUncontrolled: true // Include clients that might not be fully controlled yet
}).then(clientList => {
if (!clientList || clientList.length === 0) {
console.log('[ServiceWorker] No client windows found to send message to.');
// If no window is open AND this is a battery alert, show a notification
if (isBatteryAlert) {
return self.registration.showNotification(pushData.title, {
body: pushData.body,
icon: '/icons/android-chrome-192x192.png', // Updated path
data: pushData.data // Pass data if needed when notification is clicked
});
}
// Otherwise, don't show notification for regular button presses
return Promise.resolve();
}
// Post message to each client with improved reliability
let messageSent = false;
const sendPromises = clientList.map(client => {
console.log(`[ServiceWorker] Posting message to client: ${client.id}`, messagePayload);
try {
// Try to send the message and mark it as sent
client.postMessage(messagePayload);
messageSent = true;
// Just return true to indicate message was sent
return Promise.resolve(true);
} catch (error) {
console.error('[ServiceWorker] Error posting message to client:', error);
return Promise.resolve(false);
}
});
return Promise.all(sendPromises).then(() => {
// Only show a notification if this is a battery alert
if (isBatteryAlert) {
return self.registration.showNotification(pushData.title, {
body: pushData.body,
icon: '/icons/android-chrome-192x192.png',
data: pushData.data
});
}
// For regular button presses, don't show notifications
return Promise.resolve();
});
})
);
});
// This helps with navigation after app is installed
self.addEventListener('notificationclick', event => {
console.log('[ServiceWorker] Notification click received');
event.notification.close();
// Handle the notification click
event.waitUntil(
self.clients.matchAll({ type: 'window' })
.then(clientList => {
for (const client of clientList) {
if (client.url.startsWith(self.location.origin) && 'focus' in client) {
return client.focus();
}
}
if (self.clients.openWindow) {
return self.clients.openWindow('/');
}
})
);
});