diff --git a/src/infra/push-web.ts b/src/infra/push-web.ts index 00bbfc87ab9..7cec29330ff 100644 --- a/src/infra/push-web.ts +++ b/src/infra/push-web.ts @@ -110,7 +110,8 @@ export async function resolveVapidKeys(baseDir?: string): Promise return { publicKey: existing.publicKey, privateKey: existing.privateKey, - subject: existing.subject || resolveVapidSubjectFromEnv(), + // Env var always wins so operators can change subject without deleting vapid-keys.json. + subject: resolveVapidSubjectFromEnv(), }; } @@ -305,10 +306,10 @@ export async function broadcastWebPush( }, ); - // Clean up expired subscriptions (HTTP 410 Gone) per Web Push spec. + // Clean up expired subscriptions (HTTP 410 Gone or 404 Not Found) per Web Push spec. const expiredEndpoints = mapped .map((result, i) => ({ result, sub: subscriptions[i] })) - .filter(({ result }) => !result.ok && result.statusCode === 410) + .filter(({ result }) => !result.ok && (result.statusCode === 410 || result.statusCode === 404)) .map(({ sub }) => sub.endpoint); if (expiredEndpoints.length > 0) { diff --git a/ui/public/sw.js b/ui/public/sw.js index 6796eb8080d..6c8fcbb3a20 100644 --- a/ui/public/sw.js +++ b/ui/public/sw.js @@ -30,8 +30,12 @@ self.addEventListener("fetch", (event) => { return; } - // Skip API requests — they should never be cached. - if (url.pathname.startsWith("/api/") || url.pathname.startsWith("/rpc")) { + // 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; } diff --git a/ui/src/ui/app.ts b/ui/src/ui/app.ts index 1323a8f5c31..3f3f67f987c 100644 --- a/ui/src/ui/app.ts +++ b/ui/src/ui/app.ts @@ -972,15 +972,18 @@ export class OpenClawApp extends LitElement { /** Re-register local push subscription with the gateway after connect. */ async reconcileWebPushState() { - if (!this.webPushSubscribed || !this.client) { + if (!this.client) { return; } try { + // Always check PushManager directly — initWebPushState may not have finished + // yet if gateway connected quickly. const { getExistingSubscription } = await import("./push-subscription.ts"); const existing = await getExistingSubscription(); if (!existing) { return; } + this.webPushSubscribed = true; const subJson = existing.toJSON(); if (subJson.endpoint && subJson.keys?.p256dh && subJson.keys?.auth) { await this.client.request("push.web.subscribe", {