Initial commit: StreamFlow IPTV platform
This commit is contained in:
commit
73a8ae9ffd
1240 changed files with 278451 additions and 0 deletions
178
frontend/public/service-worker.js
Normal file
178
frontend/public/service-worker.js
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
const CACHE_NAME = 'streamflow-v1';
|
||||
const urlsToCache = [
|
||||
'/',
|
||||
'/index.html',
|
||||
'/manifest.json'
|
||||
];
|
||||
|
||||
// Global error handler for service worker (CWE-391 protection)
|
||||
self.addEventListener('error', (event) => {
|
||||
// Log error but don't expose to user
|
||||
console.error('Service Worker error:', event.error);
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// Handle unhandled promise rejections in service worker
|
||||
self.addEventListener('unhandledrejection', (event) => {
|
||||
// Log error but don't expose to user
|
||||
console.error('Service Worker unhandled rejection:', event.reason);
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// Install event - cache static assets
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME)
|
||||
.then((cache) => cache.addAll(urlsToCache))
|
||||
.then(() => self.skipWaiting())
|
||||
.catch((error) => {
|
||||
// Handle cache errors gracefully
|
||||
console.error('Cache installation error:', error);
|
||||
// Still skip waiting even if caching fails
|
||||
return self.skipWaiting();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Activate event - clean up old caches
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(
|
||||
caches.keys().then((cacheNames) => {
|
||||
return Promise.all(
|
||||
cacheNames.map((cacheName) => {
|
||||
if (cacheName !== CACHE_NAME) {
|
||||
return caches.delete(cacheName);
|
||||
}
|
||||
})
|
||||
);
|
||||
}).then(() => self.clients.claim())
|
||||
);
|
||||
});
|
||||
|
||||
// Fetch event - serve from cache, fallback to network
|
||||
self.addEventListener('fetch', (event) => {
|
||||
// Skip non-GET requests - let them pass through to network
|
||||
if (event.request.method !== 'GET') {
|
||||
event.respondWith(fetch(event.request));
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip API requests and streaming URLs
|
||||
if (event.request.url.includes('/api/') ||
|
||||
event.request.url.includes('m3u8') ||
|
||||
event.request.url.includes('ts')) {
|
||||
event.respondWith(fetch(event.request));
|
||||
return;
|
||||
}
|
||||
|
||||
event.respondWith(
|
||||
caches.match(event.request)
|
||||
.then((response) => {
|
||||
// Cache hit - return response
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
return fetch(event.request).then((response) => {
|
||||
// Check if valid response
|
||||
if (!response || response.status !== 200 || response.type !== 'basic') {
|
||||
return response;
|
||||
}
|
||||
|
||||
// Clone the response
|
||||
const responseToCache = response.clone();
|
||||
|
||||
caches.open(CACHE_NAME)
|
||||
.then((cache) => {
|
||||
cache.put(event.request, responseToCache);
|
||||
})
|
||||
.catch((error) => {
|
||||
// Silently handle cache write errors
|
||||
console.error('Cache write error:', error);
|
||||
});
|
||||
|
||||
return response;
|
||||
}).catch((error) => {
|
||||
// Network error - return generic offline response
|
||||
console.error('Fetch error:', error);
|
||||
// Return a basic error response instead of throwing
|
||||
return new Response('Offline', {
|
||||
status: 503,
|
||||
statusText: 'Service Unavailable'
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
// Handle any cache read errors
|
||||
console.error('Cache read error:', error);
|
||||
// Try to fetch from network as fallback
|
||||
return fetch(event.request).catch(() => {
|
||||
return new Response('Offline', {
|
||||
status: 503,
|
||||
statusText: 'Service Unavailable'
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Handle background sync
|
||||
self.addEventListener('sync', (event) => {
|
||||
if (event.tag === 'sync-playlists') {
|
||||
event.waitUntil(syncPlaylists());
|
||||
}
|
||||
});
|
||||
|
||||
// Handle push notifications
|
||||
self.addEventListener('push', (event) => {
|
||||
const data = event.data ? event.data.json() : {};
|
||||
const options = {
|
||||
body: data.body || 'New notification from StreamFlow',
|
||||
icon: '/icons/icon-192x192.svg',
|
||||
badge: '/icons/icon-72x72.svg',
|
||||
vibrate: [200, 100, 200],
|
||||
tag: data.tag || 'streamflow-notification',
|
||||
renotify: true,
|
||||
data: data.data || {},
|
||||
actions: data.actions || [
|
||||
{
|
||||
action: 'open',
|
||||
title: 'Open'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(data.title || 'StreamFlow', options)
|
||||
);
|
||||
});
|
||||
|
||||
// Handle notification clicks
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
event.notification.close();
|
||||
|
||||
const urlToOpen = event.notification.data.url || '/';
|
||||
|
||||
event.waitUntil(
|
||||
clients.matchAll({ type: 'window', includeUncontrolled: true })
|
||||
.then((clientList) => {
|
||||
// Check if there's already a window open
|
||||
for (let i = 0; i < clientList.length; i++) {
|
||||
const client = clientList[i];
|
||||
if (client.url.includes(urlToOpen) && 'focus' in client) {
|
||||
return client.focus();
|
||||
}
|
||||
}
|
||||
// If no window is open, open a new one
|
||||
if (clients.openWindow) {
|
||||
return clients.openWindow(urlToOpen);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Sync function
|
||||
async function syncPlaylists() {
|
||||
// Implementation for syncing playlists
|
||||
console.log('Syncing playlists...');
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue