Files
openclaw/ui/public/sw.js

134 lines
3.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// OpenClaw Control Service Worker
// Handles offline caching and push notifications.
const CACHE_PREFIX = "openclaw-control-";
const EMBEDDED_CACHE_VERSION = "__OPENCLAW_CONTROL_UI_BUILD_ID__";
const URL_CACHE_VERSION = new URL(self.location.href).searchParams
.get("v")
?.replace(/[^a-zA-Z0-9._-]/g, "-");
const CACHE_VERSION =
(EMBEDDED_CACHE_VERSION !== "__OPENCLAW_CONTROL_UI_BUILD_ID__"
? EMBEDDED_CACHE_VERSION
: URL_CACHE_VERSION) || "dev";
const CACHE_NAME = `${CACHE_PREFIX}${CACHE_VERSION}`;
const CONTROL_CACHE_LIMIT = 3;
// Minimal app-shell files to precache.
const PRECACHE_URLS = ["./"];
self.addEventListener("install", (event) => {
event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS)));
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
// Keep a small prior-build window so open tabs can still load old hashed chunks after updates.
event.waitUntil(
Promise.all([
self.clients.claim(),
caches.keys().then((keys) => {
const controlKeys = keys.filter((key) => key.startsWith(CACHE_PREFIX));
const priorCacheLimit = Math.max(0, CONTROL_CACHE_LIMIT - 1);
const retained = new Set([
...controlKeys.filter((key) => key !== CACHE_NAME).slice(-priorCacheLimit),
CACHE_NAME,
]);
return Promise.all(
controlKeys.filter((key) => !retained.has(key)).map((key) => caches.delete(key)),
);
}),
]),
);
});
self.addEventListener("fetch", (event) => {
const url = new URL(event.request.url);
// Skip non-GET and cross-origin requests.
if (event.request.method !== "GET" || url.origin !== self.location.origin) {
return;
}
// Skip non-UI routes — API, RPC, and plugin routes should never be cached.
if (
url.pathname.startsWith("/api/") ||
url.pathname.startsWith("/rpc") ||
url.pathname.startsWith("/plugins/")
) {
return;
}
// Cache-first for hashed assets; network-first for HTML/other.
if (url.pathname.includes("/assets/")) {
event.respondWith(
caches.match(event.request).then(
(cached) =>
cached ||
fetch(event.request).then((response) => {
if (response.ok) {
const clone = response.clone();
void caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
}
return response;
}),
),
);
} else {
event.respondWith(
fetch(event.request)
.then((response) => {
if (response.ok) {
const clone = response.clone();
void caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
}
return response;
})
.catch(() => caches.match(event.request)),
);
}
});
// --- Web Push ---
self.addEventListener("push", (event) => {
if (!event.data) {
return;
}
let data;
try {
data = event.data.json();
} catch {
data = { title: "OpenClaw", body: event.data.text() };
}
const title = data.title || "OpenClaw";
const options = {
body: data.body || "",
icon: "./apple-touch-icon.png",
badge: "./favicon-32.png",
tag: data.tag || "openclaw-notification",
data: { url: data.url || "./" },
};
event.waitUntil(self.registration.showNotification(title, options));
});
self.addEventListener("notificationclick", (event) => {
event.notification.close();
const targetUrl = event.notification.data?.url || "./";
event.waitUntil(
self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clients) => {
// Focus an existing window if one is open.
for (const client of clients) {
if (new URL(client.url).pathname === new URL(targetUrl, self.location.origin).pathname) {
return client.focus();
}
}
return self.clients.openWindow(targetUrl);
}),
);
});