Merge pull request 'development' (#17) from development into main
Reviewed-on: #17
This commit is contained in:
@@ -143,7 +143,7 @@
|
|||||||
|
|
||||||
<h1 class="text-2xl font-bold mb-4 mt-2 text-center">All Events</h1>
|
<h1 class="text-2xl font-bold mb-4 mt-2 text-center">All Events</h1>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 max-w-2xl mx-auto">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 max-w-2xl mx-auto mb-10">
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<!-- Loading placeholders -->
|
<!-- Loading placeholders -->
|
||||||
{#each Array(4) as _}
|
{#each Array(4) as _}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="border border-gray-300 rounded-lg overflow-hidden h-[200px]">
|
<div class="border border-gray-300 rounded-lg overflow-hidden h-[200px] mb-4">
|
||||||
<div class="h-full flex flex-col">
|
<div class="h-full flex flex-col">
|
||||||
{#if scan_state === ScanState.scanning}
|
{#if scan_state === ScanState.scanning}
|
||||||
<div class="bg-gray-50 p-4 flex-1 flex flex-col justify-center items-center">
|
<div class="bg-gray-50 p-4 flex-1 flex flex-col justify-center items-center">
|
||||||
|
|||||||
@@ -1,18 +1,62 @@
|
|||||||
/// <reference types="@sveltejs/kit" />
|
/// <reference types="@sveltejs/kit" />
|
||||||
import { build, files, version } from '$service-worker';
|
import { build, files, version } from '$service-worker';
|
||||||
|
|
||||||
// Create a unique cache name for this deployment
|
// Create unique cache names for this deployment
|
||||||
const CACHE = `cache-${version}`;
|
const STATIC_CACHE = `static-cache-${version}`;
|
||||||
|
const SUPABASE_CACHE = `supabase-cache-${version}`;
|
||||||
|
const DYNAMIC_CACHE = `dynamic-cache-${version}`;
|
||||||
|
|
||||||
const ASSETS = [
|
const ASSETS = [
|
||||||
...build, // the app itself
|
...build, // the app itself
|
||||||
...files // everything in `static`
|
...files // everything in `static`
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Configure which Supabase endpoints to cache and for how long
|
||||||
|
const SUPABASE_CACHE_CONFIG = [
|
||||||
|
// Format: { urlPattern: RegExp, maxAgeSeconds: number, strategy: 'cache-first' | 'network-first' | 'stale-while-revalidate' }
|
||||||
|
{ urlPattern: /events/, maxAgeSeconds: 3600, strategy: 'stale-while-revalidate' },
|
||||||
|
{ urlPattern: /participants/, maxAgeSeconds: 3600, strategy: 'network-first' },
|
||||||
|
{ urlPattern: /sections/, maxAgeSeconds: 3600, strategy: 'cache-first' },
|
||||||
|
{ urlPattern: /profiles/, maxAgeSeconds: 3600, strategy: 'cache-first' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Helper to determine if a request is for Supabase
|
||||||
|
function isSupabaseRequest(url) {
|
||||||
|
return url.hostname.includes('supabase.co');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to find matching cache config for a Supabase URL
|
||||||
|
function getSupabaseCacheConfig(url) {
|
||||||
|
const urlString = url.toString();
|
||||||
|
return SUPABASE_CACHE_CONFIG.find(config => config.urlPattern.test(urlString));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to check if cached response is expired
|
||||||
|
function isCacheExpired(cachedResponse, maxAgeSeconds) {
|
||||||
|
if (!cachedResponse) return true;
|
||||||
|
|
||||||
|
const cachedAt = new Date(cachedResponse.headers.get('sw-cache-timestamp') || 0);
|
||||||
|
const now = new Date();
|
||||||
|
return (now.getTime() - cachedAt.getTime()) > (maxAgeSeconds * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to add timestamp to cached responses
|
||||||
|
function addTimestampToResponse(response) {
|
||||||
|
const clonedResponse = response.clone();
|
||||||
|
const headers = new Headers(clonedResponse.headers);
|
||||||
|
headers.set('sw-cache-timestamp', new Date().toISOString());
|
||||||
|
|
||||||
|
return new Response(clonedResponse.body, {
|
||||||
|
status: clonedResponse.status,
|
||||||
|
statusText: clonedResponse.statusText,
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
self.addEventListener('install', (event) => {
|
self.addEventListener('install', (event) => {
|
||||||
// Create a new cache and add all files to it
|
// Create a new cache and add all files to it
|
||||||
async function addFilesToCache() {
|
async function addFilesToCache() {
|
||||||
const cache = await caches.open(CACHE);
|
const cache = await caches.open(STATIC_CACHE);
|
||||||
await cache.addAll(ASSETS);
|
await cache.addAll(ASSETS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +67,9 @@ self.addEventListener('activate', (event) => {
|
|||||||
// Remove previous cached data from disk
|
// Remove previous cached data from disk
|
||||||
async function deleteOldCaches() {
|
async function deleteOldCaches() {
|
||||||
for (const key of await caches.keys()) {
|
for (const key of await caches.keys()) {
|
||||||
if (key !== CACHE) await caches.delete(key);
|
if (![STATIC_CACHE, SUPABASE_CACHE, DYNAMIC_CACHE].includes(key)) {
|
||||||
|
await caches.delete(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,53 +77,121 @@ self.addEventListener('activate', (event) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('fetch', (event) => {
|
self.addEventListener('fetch', (event) => {
|
||||||
// ignore POST requests etc
|
// ignore POST requests and other mutations
|
||||||
if (event.request.method !== 'GET') return;
|
if (event.request.method !== 'GET') return;
|
||||||
|
|
||||||
async function respond() {
|
async function respond() {
|
||||||
const url = new URL(event.request.url);
|
const url = new URL(event.request.url);
|
||||||
|
|
||||||
// Skip caching for auth routes
|
// Skip caching for auth routes and auth requests
|
||||||
if (url.pathname.startsWith('/auth/')) {
|
if (url.pathname.startsWith('/auth/') || url.pathname.includes('auth/v1')) {
|
||||||
return fetch(event.request);
|
return fetch(event.request);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cache = await caches.open(CACHE);
|
// Handle static assets
|
||||||
|
|
||||||
// `build`/`files` can always be served from the cache
|
|
||||||
if (ASSETS.includes(url.pathname)) {
|
if (ASSETS.includes(url.pathname)) {
|
||||||
const response = await cache.match(url.pathname);
|
const staticCache = await caches.open(STATIC_CACHE);
|
||||||
|
const cachedResponse = await staticCache.match(url.pathname);
|
||||||
if (response) {
|
|
||||||
return response;
|
if (cachedResponse) {
|
||||||
|
return cachedResponse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// for everything else, try the network first, but
|
// Handle Supabase requests with specific caching strategies
|
||||||
// fall back to the cache if we're offline
|
if (isSupabaseRequest(url)) {
|
||||||
|
const cacheConfig = getSupabaseCacheConfig(url);
|
||||||
|
|
||||||
|
if (!cacheConfig) {
|
||||||
|
// No specific config, use network-only for unconfigured Supabase endpoints
|
||||||
|
return fetch(event.request);
|
||||||
|
}
|
||||||
|
|
||||||
|
const supabaseCache = await caches.open(SUPABASE_CACHE);
|
||||||
|
const cachedResponse = await supabaseCache.match(event.request);
|
||||||
|
|
||||||
|
// Apply the appropriate caching strategy based on config
|
||||||
|
switch (cacheConfig.strategy) {
|
||||||
|
case 'cache-first': {
|
||||||
|
// If we have a valid cache that's not expired, use it
|
||||||
|
if (cachedResponse && !isCacheExpired(cachedResponse, cacheConfig.maxAgeSeconds)) {
|
||||||
|
return cachedResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise fetch and update cache
|
||||||
|
try {
|
||||||
|
const response = await fetch(event.request);
|
||||||
|
if (response.ok) {
|
||||||
|
const timestampedResponse = addTimestampToResponse(response);
|
||||||
|
supabaseCache.put(event.request, timestampedResponse.clone());
|
||||||
|
return timestampedResponse;
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
} catch (err) {
|
||||||
|
// If offline and we have any cached version, return it even if expired
|
||||||
|
if (cachedResponse) return cachedResponse;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'stale-while-revalidate': {
|
||||||
|
// Start network fetch
|
||||||
|
const fetchPromise = fetch(event.request)
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
const timestampedResponse = addTimestampToResponse(response);
|
||||||
|
supabaseCache.put(event.request, timestampedResponse.clone());
|
||||||
|
return timestampedResponse;
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// If network fails, we'll use cache if available
|
||||||
|
if (cachedResponse) return cachedResponse;
|
||||||
|
throw new Error('Network request failed and no cache available');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return cached response immediately if available, otherwise wait for network
|
||||||
|
return cachedResponse || fetchPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'network-first':
|
||||||
|
default: {
|
||||||
|
try {
|
||||||
|
const response = await fetch(event.request);
|
||||||
|
if (response.ok) {
|
||||||
|
const timestampedResponse = addTimestampToResponse(response);
|
||||||
|
supabaseCache.put(event.request, timestampedResponse.clone());
|
||||||
|
return timestampedResponse;
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
} catch (err) {
|
||||||
|
// If offline, try to return cached response
|
||||||
|
if (cachedResponse) return cachedResponse;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For everything else, use dynamic cache with network-first strategy
|
||||||
|
const dynamicCache = await caches.open(DYNAMIC_CACHE);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(event.request);
|
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
cache.put(event.request, response.clone());
|
dynamicCache.put(event.request, response.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const response = await cache.match(event.request);
|
const cachedResponse = await dynamicCache.match(event.request);
|
||||||
|
|
||||||
if (response) {
|
if (cachedResponse) {
|
||||||
return response;
|
return cachedResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there's no cache, then just error out
|
|
||||||
// as there is nothing we can do to respond to this request
|
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user