feat: complete TASK-WP0-06 public cache
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
const CACHE_NAME = "dada-public-assets-v1";
|
||||
const DATABASE_NAME = "dada-public-asset-cache-v1";
|
||||
const LRU_STORE = "public_asset_lru";
|
||||
const POLICY_STORE = "client_cache_policy";
|
||||
const PUBLIC_ROUTE = /^\/api\/v1\/assets\/public\/([a-z0-9][a-z0-9._-]{0,79})\/([0-9a-f-]{36})$/i;
|
||||
|
||||
function openDatabase() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(DATABASE_NAME, 1);
|
||||
request.onupgradeneeded = () => {
|
||||
const database = request.result;
|
||||
if (!database.objectStoreNames.contains(LRU_STORE)) database.createObjectStore(LRU_STORE, { keyPath: "resource_id" });
|
||||
if (!database.objectStoreNames.contains(POLICY_STORE)) database.createObjectStore(POLICY_STORE, { keyPath: "policy_id" });
|
||||
};
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error ?? new Error("public_cache_database_open_failed"));
|
||||
});
|
||||
}
|
||||
|
||||
async function readMetadata(resourceId) {
|
||||
const database = await openDatabase();
|
||||
try {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const request = database.transaction(LRU_STORE, "readonly").objectStore(LRU_STORE).get(resourceId);
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error ?? new Error("public_cache_metadata_read_failed"));
|
||||
});
|
||||
} finally {
|
||||
database.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function touchMetadata(resourceId, releaseVersion) {
|
||||
const database = await openDatabase();
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
const transaction = database.transaction(LRU_STORE, "readwrite");
|
||||
const store = transaction.objectStore(LRU_STORE);
|
||||
const request = store.get(resourceId);
|
||||
request.onsuccess = () => {
|
||||
const entry = request.result;
|
||||
if (entry?.release_version === releaseVersion) store.put({ ...entry, last_accessed_at: Date.now() });
|
||||
};
|
||||
transaction.oncomplete = () => resolve();
|
||||
transaction.onabort = () => reject(transaction.error ?? new Error("public_cache_touch_aborted"));
|
||||
transaction.onerror = () => reject(transaction.error ?? new Error("public_cache_touch_failed"));
|
||||
});
|
||||
} finally {
|
||||
database.close();
|
||||
}
|
||||
}
|
||||
|
||||
self.addEventListener("install", () => self.skipWaiting());
|
||||
self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim()));
|
||||
self.addEventListener("fetch", (event) => {
|
||||
if (event.request.method !== "GET") return;
|
||||
const url = new URL(event.request.url);
|
||||
if (url.origin !== self.location.origin || url.search || url.hash) return;
|
||||
const route = PUBLIC_ROUTE.exec(url.pathname);
|
||||
if (!route) return;
|
||||
|
||||
event.respondWith((async () => {
|
||||
const releaseVersion = route[1];
|
||||
const resourceId = route[2];
|
||||
const metadata = await readMetadata(resourceId);
|
||||
if (!metadata || metadata.release_version !== releaseVersion) return fetch(event.request);
|
||||
const cached = await (await caches.open(CACHE_NAME)).match(event.request);
|
||||
if (!cached) return fetch(event.request);
|
||||
event.waitUntil(touchMetadata(resourceId, releaseVersion));
|
||||
return cached;
|
||||
})());
|
||||
});
|
||||
Reference in New Issue
Block a user