Files
Oxicloud/static/sw.js
T
Edouard Vanbelle 05037e7491 feat(ui): permit drag&drop to the operating system
a drop outside of the browser will:
  - upload the file if only 1 file selected
  - upload a .zip of the directory or multiple selection (browsers do not permit multiple upload yet)

note: I had to create a new handler because a post request to /api/batch/download is possible via JS but it will create a memory blob in the browser, during the drag action.

This may exhaust the browser's memory if heavy files
This will initiate zip creation from the server even if drop is canceled
The best approach is to add a handler supporting GET calls, this call will be triggered by the browser on drop action outside of it's window
2026-05-04 22:32:31 +02:00

122 lines
4.3 KiB
JavaScript

// OxiCloud Service Worker
// FIXME: generate cache name according build ?
const CACHE_NAME = 'oxicloud-cache-v21';
// Only cache static assets — NOT HTML files.
// HTML files are served network-first so browsers always get the latest
// script/link references. Caching HTML causes stale entry points.
const ASSETS_TO_CACHE = [
'/js/core/icons.js',
'/js/core/i18n.js',
'/js/core/languageSelector.js',
'/js/core/notifications.js',
'/locales/en.json',
'/locales/es.json',
'/locales/fa.json',
'/locales/de.json',
'/locales/nl.json',
'/favicon.ico'
];
// HTML paths that should always be fetched from the network
const HTML_PATHS = ['/', '/index.html', '/login', '/login.html', '/admin', '/admin.html', '/profile', '/profile.html'];
// Install event - cache assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches
.open(CACHE_NAME)
.then((cache) => {
console.log('Cache opened');
return cache.addAll(ASSETS_TO_CACHE);
})
.then(() => self.skipWaiting()) // Activate immediately
);
});
// Activate event - clean old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches
.keys()
.then((cacheNames) => {
return Promise.all(
cacheNames
.filter((cacheName) => {
return cacheName !== CACHE_NAME;
})
.map((cacheName) => {
return caches.delete(cacheName);
})
);
})
.then(() => self.clients.claim()) // Take control of clients
);
});
// Fetch event - serve from cache, update cache from network
self.addEventListener('fetch', (event) => {
// Don't intercept API requests - let them go straight to the network
if (event.request.url.includes('/api/')) {
return;
}
// HTML pages: always network-first so entry points are never stale
const pathname = new URL(event.request.url).pathname;
const isHtml = HTML_PATHS.includes(pathname) || pathname.endsWith('.html') || event.request.headers.get('accept')?.includes('text/html');
if (isHtml) {
event.respondWith(fetch(event.request).catch(() => caches.match(event.request)));
return;
}
event.respondWith(
caches.match(event.request).then((response) => {
// Cache hit - return the response from the cached version
if (response) {
// For non-core assets, still fetch from network for updates
if (!ASSETS_TO_CACHE.includes(new URL(event.request.url).pathname)) {
fetch(event.request)
.then((networkResponse) => {
if (networkResponse && networkResponse.status === 200) {
const clonedResponse = networkResponse.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, clonedResponse);
});
}
})
.catch(() => {
// Ignore network fetch errors - we already have a cached version
});
}
return response;
}
// Not in cache - get from network and add to cache
return fetch(event.request).then((response) => {
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// Clone the response as it's a stream and can only be consumed once
const responseToCache = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseToCache);
});
return response;
});
})
);
});
// Background sync for failed requests
self.addEventListener('sync', (event) => {
if (event.tag === 'oxicloud-sync') {
event.waitUntil(
// Implement background sync for pending file operations
Promise.resolve() // Placeholder for actual implementation
);
}
});