From b956fafb411fdb4f4ab5d6ed0d417bc3a14c656e Mon Sep 17 00:00:00 2001 From: Fuwn Date: Sat, 28 Mar 2026 06:25:30 +0000 Subject: fix(notifications): stabilize browser subscription identity --- src/trigger/notifications.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'src/trigger') diff --git a/src/trigger/notifications.ts b/src/trigger/notifications.ts index 5b2453c2..30a50a12 100644 --- a/src/trigger/notifications.ts +++ b/src/trigger/notifications.ts @@ -14,6 +14,15 @@ const isExpiredSubscriptionError = (error: unknown) => { return statusCode === 404 || statusCode === 410; }; +const subscriptionEndpoint = (subscription: unknown) => + typeof subscription === "object" && + subscription !== null && + "endpoint" in subscription && + typeof subscription.endpoint === "string" && + subscription.endpoint.length + ? subscription.endpoint + : null; + export const notificationsTask = schedules.task({ id: "notifications", run: async (_payload, { ctx }) => { @@ -56,6 +65,7 @@ export const notificationsTask = schedules.task({ }; const transientErrors: unknown[] = []; + const seenEndpoints = new Set(); webpush.setVapidDetails( ( @@ -81,7 +91,15 @@ export const notificationsTask = schedules.task({ ).value, ); - for (const subscription of await getUserSubscriptions()) + for (const subscription of await getUserSubscriptions()) { + const endpoint = subscriptionEndpoint(subscription.subscription); + + if (endpoint) { + if (seenEndpoints.has(endpoint)) continue; + + seenEndpoints.add(endpoint); + } + try { await webpush.sendNotification(subscription.subscription, "."); } catch (error) { @@ -94,6 +112,7 @@ export const notificationsTask = schedules.task({ console.error(error); } + } if (transientErrors.length > 0) throw new Error("Transient push delivery failures occurred", { -- cgit v1.2.3