Skip to content

Commit 103afa4

Browse files
committed
refactor: service worker
1 parent b44d2a2 commit 103afa4

File tree

5 files changed

+113
-211
lines changed

5 files changed

+113
-211
lines changed
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { cancelLoad, getCachedOrFetch } from './fetch-event';
1+
import { cancelRequest, handleRequest } from './request';
22

33
export const installBroadcastChannelListener = () => {
44
const broadcast = new BroadcastChannel('immich');
@@ -7,12 +7,12 @@ export const installBroadcastChannelListener = () => {
77
if (!event.data) {
88
return;
99
}
10-
const urlstring = event.data.url;
11-
const url = new URL(urlstring, event.origin);
10+
const urlString = event.data.url;
11+
const url = new URL(urlString, event.origin);
1212
if (event.data.type === 'cancel') {
13-
cancelLoad(url.toString());
13+
cancelRequest(url);
1414
} else if (event.data.type === 'preload') {
15-
getCachedOrFetch(url);
15+
handleRequest(url);
1616
}
1717
};
1818
};

web/src/service-worker/cache.ts

Lines changed: 27 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,42 @@
1-
import { build, files, version } from '$service-worker';
1+
import { version } from '$service-worker';
22

3-
const useCache = true;
43
const CACHE = `cache-${version}`;
54

6-
export const APP_RESOURCES = [
7-
...build, // the app itself
8-
...files, // everything in `static`
9-
];
10-
11-
let cache: Cache | undefined;
12-
export async function getCache() {
13-
if (cache) {
14-
return cache;
5+
let _cache: Cache | undefined;
6+
const getCache = async () => {
7+
if (_cache) {
8+
return _cache;
159
}
16-
cache = await caches.open(CACHE);
17-
return cache;
18-
}
19-
20-
export const isURL = (request: URL | RequestInfo): request is URL => (request as URL).href !== undefined;
21-
export const isRequest = (request: RequestInfo): request is Request => (request as Request).url !== undefined;
10+
_cache = await caches.open(CACHE);
11+
return _cache;
12+
};
2213

23-
export async function deleteOldCaches() {
24-
for (const key of await caches.keys()) {
25-
if (key !== CACHE) {
26-
await caches.delete(key);
27-
}
28-
}
29-
}
30-
31-
const pendingRequests = new Map<string, AbortController>();
32-
const canceledRequests = new Set<string>();
33-
34-
export async function cancelLoad(urlString: string) {
35-
const pending = pendingRequests.get(urlString);
36-
if (pending) {
37-
canceledRequests.add(urlString);
38-
pending.abort();
39-
pendingRequests.delete(urlString);
40-
}
41-
}
42-
43-
export async function getCachedOrFetch(request: URL | Request | string) {
44-
const response = await checkCache(request);
45-
if (response) {
46-
return response;
14+
export const get = async (key: string) => {
15+
const cache = await getCache();
16+
if (!cache) {
17+
return;
4718
}
4819

49-
const urlString = getCacheKey(request);
50-
const cancelToken = new AbortController();
20+
return cache.match(key);
21+
};
5122

52-
try {
53-
pendingRequests.set(urlString, cancelToken);
54-
const response = await fetch(request, {
55-
signal: cancelToken.signal,
56-
});
57-
58-
checkResponse(response);
59-
await setCached(response, urlString);
60-
return response;
61-
} catch (error) {
62-
if (canceledRequests.has(urlString)) {
63-
canceledRequests.delete(urlString);
64-
return new Response(undefined, {
65-
status: 499,
66-
statusText: 'Request canceled: Instructions unclear, accidentally interrupted myself',
67-
});
68-
}
69-
throw error;
70-
} finally {
71-
pendingRequests.delete(urlString);
72-
}
73-
}
74-
75-
export async function checkCache(url: URL | Request | string) {
76-
if (!useCache) {
23+
export const put = async (key: string, response: Response) => {
24+
if (response.status !== 200) {
7725
return;
7826
}
79-
const cache = await getCache();
80-
return await cache.match(url);
81-
}
8227

83-
export async function setCached(response: Response, cacheKey: URL | Request | string) {
84-
if (cache && response.status === 200) {
85-
const cache = await getCache();
86-
cache.put(cacheKey, response.clone());
28+
const cache = await getCache();
29+
if (!cache) {
30+
return;
8731
}
88-
}
8932

90-
function checkResponse(response: Response) {
91-
if (!(response instanceof Response)) {
92-
throw new TypeError('Fetch did not return a valid Response object');
93-
}
94-
}
33+
cache.put(key, response.clone());
34+
};
9535

96-
export function getCacheKey(request: URL | Request | string) {
97-
if (isURL(request)) {
98-
return request.toString();
99-
} else if (isRequest(request)) {
100-
return request.url;
101-
} else {
102-
return request;
36+
export const prune = async () => {
37+
for (const key of await caches.keys()) {
38+
if (key !== CACHE) {
39+
await caches.delete(key);
40+
}
10341
}
104-
}
42+
};

web/src/service-worker/fetch-event.ts

Lines changed: 0 additions & 113 deletions
This file was deleted.

web/src/service-worker/index.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,37 @@
33
/// <reference lib="esnext" />
44
/// <reference lib="webworker" />
55
import { installBroadcastChannelListener } from './broadcast-channel';
6-
import { deleteOldCaches } from './cache';
7-
import { handleFetchEvent } from './fetch-event';
6+
import { prune } from './cache';
7+
import { handleRequest } from './request';
8+
9+
const ASSET_REQUEST_REGEX = /^\/api\/assets\/[a-f0-9-]+\/(original|thumbnail)/;
810

911
const sw = globalThis as unknown as ServiceWorkerGlobalScope;
1012

1113
const handleActivate = (event: ExtendableEvent) => {
1214
event.waitUntil(sw.clients.claim());
13-
event.waitUntil(deleteOldCaches());
15+
event.waitUntil(prune());
1416
};
1517

1618
const handleInstall = (event: ExtendableEvent) => {
1719
event.waitUntil(sw.skipWaiting());
1820
// do not preload app resources
1921
};
2022

23+
const handleFetch = (event: FetchEvent): void => {
24+
if (event.request.method !== 'GET') {
25+
return;
26+
}
27+
28+
// Cache requests for thumbnails
29+
const url = new URL(event.request.url);
30+
if (url.origin === self.location.origin && ASSET_REQUEST_REGEX.test(url.pathname)) {
31+
event.respondWith(handleRequest(event.request));
32+
return;
33+
}
34+
};
35+
2136
sw.addEventListener('install', handleInstall, { passive: true });
2237
sw.addEventListener('activate', handleActivate, { passive: true });
23-
sw.addEventListener('fetch', handleFetchEvent, { passive: true });
38+
sw.addEventListener('fetch', handleFetch, { passive: true });
2439
installBroadcastChannelListener();

web/src/service-worker/request.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { get, put } from './cache';
2+
3+
const isURL = (request: URL | RequestInfo): request is URL => (request as URL).href !== undefined;
4+
const isRequest = (request: RequestInfo): request is Request => (request as Request).url !== undefined;
5+
6+
const assertResponse = (response: Response) => {
7+
if (!(response instanceof Response)) {
8+
throw new TypeError('Fetch did not return a valid Response object');
9+
}
10+
};
11+
12+
const getCacheKey = (request: URL | Request) => {
13+
if (isURL(request)) {
14+
return request.toString();
15+
}
16+
17+
if (isRequest(request)) {
18+
return request.url;
19+
}
20+
21+
throw new Error(`Invalid request: ${request}`);
22+
};
23+
24+
const pendingRequests = new Map<string, AbortController>();
25+
26+
export const handleRequest = async (request: URL | Request) => {
27+
const cacheKey = getCacheKey(request);
28+
29+
const cachedResponse = await get(cacheKey);
30+
if (cachedResponse) {
31+
return cachedResponse;
32+
}
33+
34+
try {
35+
const cancelToken = new AbortController();
36+
pendingRequests.set(cacheKey, cancelToken);
37+
const response = await fetch(request, { signal: cancelToken.signal });
38+
39+
assertResponse(response);
40+
put(cacheKey, response);
41+
42+
return response;
43+
} catch {
44+
return new Response(undefined, {
45+
status: 499,
46+
statusText: 'Request canceled: Instructions unclear, accidentally interrupted myself',
47+
});
48+
} finally {
49+
pendingRequests.delete(cacheKey);
50+
}
51+
};
52+
53+
export const cancelRequest = (url: URL) => {
54+
const cacheKey = getCacheKey(url);
55+
const pending = pendingRequests.get(cacheKey);
56+
if (!pending) {
57+
return;
58+
}
59+
60+
pending.abort();
61+
pendingRequests.delete(cacheKey);
62+
};

0 commit comments

Comments
 (0)