This commit is contained in:
Giancarmine Salucci
2026-02-18 01:21:44 +01:00
parent 54321fd7c9
commit 49bccf8f15
84 changed files with 14474 additions and 13925 deletions

View File

@@ -7,287 +7,284 @@ import { build, files, version } from '$service-worker';
declare let self: ServiceWorkerGlobalScope;
// Create a unique cache name for this deployment
// Create a unique cache name for this deployment
const CACHE = `cache-${version}`;
const ASSETS = [
...build, // the app itself
...files // everything in `static`
...build, // the app itself
...files // everything in `static`
];
// Global error handlers (preserve existing)
self.addEventListener('error', (event) => {
console.error('[SW] Global error:', event.error);
console.error('[SW] Error details:', {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error
});
console.error('[SW] Global error:', event.error);
console.error('[SW] Error details:', {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error
});
});
self.addEventListener('unhandledrejection', (event) => {
console.error('[SW] Unhandled promise rejection:', event.reason);
event.preventDefault(); // Prevent default browser behavior
console.error('[SW] Unhandled promise rejection:', event.reason);
event.preventDefault(); // Prevent default browser behavior
});
console.log('[SW] Service worker script loading...');
// Install event - cache all assets
self.addEventListener('install', (event) => {
console.log('[SW] Installing service worker...');
async function addFilesToCache() {
const cache = await caches.open(CACHE);
await cache.addAll(ASSETS);
console.log(`[SW] Cached ${ASSETS.length} assets`);
}
console.log('[SW] Installing service worker...');
event.waitUntil(addFilesToCache());
async function addFilesToCache() {
const cache = await caches.open(CACHE);
await cache.addAll(ASSETS);
console.log(`[SW] Cached ${ASSETS.length} assets`);
}
event.waitUntil(addFilesToCache());
});
// Activate event - clean up old caches
self.addEventListener('activate', (event) => {
console.log('[SW] Activating service worker...');
async function deleteOldCaches() {
for (const key of await caches.keys()) {
if (key !== CACHE) {
console.log('[SW] Deleting old cache:', key);
await caches.delete(key);
}
}
}
console.log('[SW] Activating service worker...');
event.waitUntil(deleteOldCaches());
async function deleteOldCaches() {
for (const key of await caches.keys()) {
if (key !== CACHE) {
console.log('[SW] Deleting old cache:', key);
await caches.delete(key);
}
}
}
event.waitUntil(deleteOldCaches());
});
// Fetch event - serve from cache with network fallback
self.addEventListener('fetch', (event) => {
// ignore POST requests etc
if (event.request.method !== 'GET') return;
// ignore POST requests etc
if (event.request.method !== 'GET') return;
async function respond() {
const url = new URL(event.request.url);
const cache = await caches.open(CACHE);
async function respond() {
const url = new URL(event.request.url);
const cache = await caches.open(CACHE);
// `build`/`files` can always be served from the cache
if (ASSETS.includes(url.pathname)) {
const response = await cache.match(url.pathname);
if (response) {
return response;
}
}
// `build`/`files` can always be served from the cache
if (ASSETS.includes(url.pathname)) {
const response = await cache.match(url.pathname);
if (response) {
return response;
}
}
// for everything else, try the network first, but
// fall back to the cache if we're offline
try {
const response = await fetch(event.request);
// if we're offline, fetch can return a value that is not a Response
// instead of throwing - and we can't pass this non-Response to respondWith
if (!(response instanceof Response)) {
throw new Error('invalid response from fetch');
}
// for everything else, try the network first, but
// fall back to the cache if we're offline
try {
const response = await fetch(event.request);
if (response.status === 200) {
cache.put(event.request, response.clone());
}
return response;
} catch (err) {
const response = await cache.match(event.request);
if (response) {
return response;
}
// if there's no cache, then just error out
// as there is nothing we can do to respond to this request
throw err;
}
}
// if we're offline, fetch can return a value that is not a Response
// instead of throwing - and we can't pass this non-Response to respondWith
if (!(response instanceof Response)) {
throw new Error('invalid response from fetch');
}
event.respondWith(respond());
if (response.status === 200) {
cache.put(event.request, response.clone());
}
return response;
} catch (err) {
const response = await cache.match(event.request);
if (response) {
return response;
}
// if there's no cache, then just error out
// as there is nothing we can do to respond to this request
throw err;
}
}
event.respondWith(respond());
});
// Push notification handling
self.addEventListener('push', (event) => {
console.log('[SW] Push event received:', event);
if (!event.data) {
console.log('[SW] Push event but no data');
return;
}
console.log('[SW] Push event received:', event);
let data;
try {
data = event.data.json();
} catch (e) {
console.error('[SW] Failed to parse push data:', e);
return;
}
if (!event.data) {
console.log('[SW] Push event but no data');
return;
}
console.log('[SW] Push data:', data);
let data;
try {
data = event.data.json();
} catch (e) {
console.error('[SW] Failed to parse push data:', e);
return;
}
const options: NotificationOptions = {
body: data.body || 'Recipe processing update',
icon: '/favicon.png',
badge: '/favicon.png',
data: data,
requireInteraction: data.requireInteraction || false,
silent: false,
tag: data.tag || 'recipe-update',
timestamp: Date.now(),
actions: []
};
console.log('[SW] Push data:', data);
// Add actions based on notification type
if (data.type === 'success' && data.itemId) {
options.actions = [
{
action: 'view',
title: 'View Recipe',
icon: '/favicon.png'
},
{
action: 'dismiss',
title: 'Dismiss'
}
];
} else if (data.type === 'error' && data.itemId) {
options.actions = [
{
action: 'retry',
title: 'Retry',
icon: '/favicon.png'
},
{
action: 'view',
title: 'View Details'
}
];
}
const options: NotificationOptions = {
body: data.body || 'Recipe processing update',
icon: '/favicon.png',
badge: '/favicon.png',
data: data,
requireInteraction: data.requireInteraction || false,
silent: false,
tag: data.tag || 'recipe-update',
timestamp: Date.now(),
actions: []
};
const title = data.title || getNotificationTitle(data.type, data);
// Add actions based on notification type
if (data.type === 'success' && data.itemId) {
options.actions = [
{
action: 'view',
title: 'View Recipe',
icon: '/favicon.png'
},
{
action: 'dismiss',
title: 'Dismiss'
}
];
} else if (data.type === 'error' && data.itemId) {
options.actions = [
{
action: 'retry',
title: 'Retry',
icon: '/favicon.png'
},
{
action: 'view',
title: 'View Details'
}
];
}
event.waitUntil(
self.registration.showNotification(title, options)
);
const title = data.title || getNotificationTitle(data.type, data);
event.waitUntil(self.registration.showNotification(title, options));
});
// Handle notification clicks
self.addEventListener('notificationclick', (event) => {
console.log('[SW] Notification click received:', event);
event.notification.close();
console.log('[SW] Notification click received:', event);
const data = event.notification.data;
const action = event.action;
event.notification.close();
let url = '/';
const data = event.notification.data;
const action = event.action;
if (action === 'view' && data?.itemId) {
url = `/?highlight=${data.itemId}`;
} else if (action === 'retry' && data?.itemId) {
// Navigate to dashboard and trigger retry via postMessage
url = `/?highlight=${data.itemId}&action=retry`;
} else if (data?.itemId) {
url = `/?highlight=${data.itemId}`;
}
let url = '/';
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true })
.then((clientsList) => {
// Check if there's already a window/tab open
for (const client of clientsList) {
if (client.url.includes(self.location.origin) && 'focus' in client) {
return client.focus().then(() => {
// Send message to the client about the action
return client.postMessage({
type: 'notification-action',
action: action,
data: data
});
});
}
}
// If no window is open, open a new one
if (clients.openWindow) {
return clients.openWindow(url);
}
})
);
if (action === 'view' && data?.itemId) {
url = `/?highlight=${data.itemId}`;
} else if (action === 'retry' && data?.itemId) {
// Navigate to dashboard and trigger retry via postMessage
url = `/?highlight=${data.itemId}&action=retry`;
} else if (data?.itemId) {
url = `/?highlight=${data.itemId}`;
}
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientsList) => {
// Check if there's already a window/tab open
for (const client of clientsList) {
if (client.url.includes(self.location.origin) && 'focus' in client) {
return client.focus().then(() => {
// Send message to the client about the action
return client.postMessage({
type: 'notification-action',
action: action,
data: data
});
});
}
}
// If no window is open, open a new one
if (clients.openWindow) {
return clients.openWindow(url);
}
})
);
});
// Handle notification close
self.addEventListener('notificationclose', (event) => {
console.log('[SW] Notification closed:', event);
// Track notification dismissals if needed
const data = event.notification.data;
if (data?.analytics) {
// Could send analytics event here
console.log('[SW] Notification dismissed:', data);
}
console.log('[SW] Notification closed:', event);
// Track notification dismissals if needed
const data = event.notification.data;
if (data?.analytics) {
// Could send analytics event here
console.log('[SW] Notification dismissed:', data);
}
});
// Background sync for retry operations
self.addEventListener('sync', (event) => {
console.log('[SW] Background sync:', event.tag);
if (event.tag === 'retry-queue-item') {
event.waitUntil(handleRetrySync());
}
console.log('[SW] Background sync:', event.tag);
if (event.tag === 'retry-queue-item') {
event.waitUntil(handleRetrySync());
}
});
// Helper functions
function getNotificationTitle(type: string, data: any): string {
switch (type) {
case 'success':
return data.recipeName
? `✅ Recipe Ready: ${data.recipeName}`
: '✅ Recipe extraction complete';
case 'error':
return '❌ Recipe extraction failed';
case 'progress':
return `🔄 Processing recipe...`;
default:
return '📱 InstaRecipe Update';
}
switch (type) {
case 'success':
return data.recipeName
? `✅ Recipe Ready: ${data.recipeName}`
: '✅ Recipe extraction complete';
case 'error':
return '❌ Recipe extraction failed';
case 'progress':
return `🔄 Processing recipe...`;
default:
return '📱 InstaRecipe Update';
}
}
async function handleRetrySync() {
try {
// Get retry items from IndexedDB or localStorage if needed
console.log('[SW] Handling retry sync');
// This could implement background retry logic
// For now, we'll let the main app handle retries
return Promise.resolve();
} catch (error) {
console.error('[SW] Retry sync failed:', error);
throw error;
}
try {
// Get retry items from IndexedDB or localStorage if needed
console.log('[SW] Handling retry sync');
// This could implement background retry logic
// For now, we'll let the main app handle retries
return Promise.resolve();
} catch (error) {
console.error('[SW] Retry sync failed:', error);
throw error;
}
}
// Message handling for communication with main app
self.addEventListener('message', (event) => {
console.log('[SW] Message received:', event.data);
console.log('[SW] Message received:', event.data);
const { type, data } = event.data;
const { type, data } = event.data;
switch (type) {
case 'SKIP_WAITING':
self.skipWaiting();
break;
case 'GET_VERSION':
event.ports[0].postMessage({ version: '1.0.0' });
break;
case 'QUEUE_RETRY':
// Queue a background sync for retry
self.registration.sync.register('retry-queue-item');
break;
default:
console.log('[SW] Unknown message type:', type);
}
});
switch (type) {
case 'SKIP_WAITING':
self.skipWaiting();
break;
case 'GET_VERSION':
event.ports[0].postMessage({ version: '1.0.0' });
break;
case 'QUEUE_RETRY':
// Queue a background sync for retry
self.registration.sync.register('retry-queue-item');
break;
default:
console.log('[SW] Unknown message type:', type);
}
});