mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:30:43 +00:00
Adds browser PWA manifest and service worker support for the Control UI, plus gateway RPC methods and persisted Web Push subscription handling.
Maintainer verification:
- OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test src/infra/push-web.test.ts src/gateway/server-methods/push.test.ts src/gateway/control-ui.test.ts src/gateway/protocol/push.test.ts
- pnpm check:changed passed before final GitHub update-branch merge commit
- pnpm build
Source head: 0720024368
115 lines
3.0 KiB
JavaScript
115 lines
3.0 KiB
JavaScript
// OpenClaw Control – Service Worker
|
||
// Handles offline caching and push notifications.
|
||
|
||
const CACHE_NAME = "openclaw-control-v1";
|
||
|
||
// 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) => {
|
||
event.waitUntil(
|
||
caches
|
||
.keys()
|
||
.then((keys) =>
|
||
Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))),
|
||
),
|
||
);
|
||
self.clients.claim();
|
||
});
|
||
|
||
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);
|
||
}),
|
||
);
|
||
});
|