OverviewFilesCommitsIssuesDocsReleasesPeers

apds commits / bedfefb

more notifications code
bedfefb781ea44009564055f02834ee08b327e05
by Everett Bogue <ev@evbogue.com> · 2025-12-13T19:24:20.000Z

Changed files (1) +48 -13

serve.js +48 -13
……@@ -113,31 +113,65 @@ let nextAllowedPushAt = 0
113113let flushTimer = undefined
114114const queuedPushSigs = new Set()
115115
116const broadcastPush = async (payloadObj) => {
116const redactEndpoint = (endpoint) => {
117 try {
118 const u = new URL(endpoint)
119 const p = u.pathname || ''
120 const suffix = p.length > 14 ? p.slice(-14) : p
121 return `${u.origin}${suffix ? '/…' + suffix : ''}`
122 } catch {
123 const s = String(endpoint || '')
124 return s.length > 32 ? s.slice(0, 32) + '…' : s
125 }
126}
127
128const broadcastPushWithResults = async (payloadObj) => {
117129 const store = await readSubscriptions()
118130 const endpoints = Object.keys(store.subscriptions ?? {})
119 if (endpoints.length === 0) return
120131
121132 const dead = new Set()
122 const results = await Promise.allSettled(
133 const results = await Promise.all(
123134 endpoints.map(async (endpoint) => {
124 const entry = store.subscriptions[endpoint]
125 if (!entry?.subscription) return
135 const entry = store.subscriptions?.[endpoint]
136 const subscription = entry?.subscription
137 if (!subscription) {
138 return { endpoint: redactEndpoint(endpoint), skipped: true }
139 }
140
126141 try {
127 const res = await sendWebPush(entry.subscription, payloadObj)
128 if (res.status === 404 || res.status === 410) dead.add(endpoint)
129 } catch {
130 // Best-effort.
142 const res = await sendWebPush(subscription, payloadObj)
143 const status = res.status
144 if (status === 404 || status === 410) dead.add(endpoint)
145 if (!res.ok) {
146 console.log('webpush send failed', { endpoint: redactEndpoint(endpoint), status })
147 }
148 return { endpoint: redactEndpoint(endpoint), status, ok: res.ok }
149 } catch (err) {
150 console.log('webpush send error', { endpoint: redactEndpoint(endpoint), err: String(err?.message || err) })
151 return { endpoint: redactEndpoint(endpoint), error: String(err?.message || err) }
131152 }
132153 })
133154 )
134 void results
135155
136156 if (dead.size > 0) {
137157 for (const endpoint of dead) delete store.subscriptions[endpoint]
138158 store.updatedAt = Date.now()
139159 await writeSubscriptions(store)
140160 }
161
162 const okCount = results.filter(r => r.ok).length
163 const failCount = results.length - okCount
164 return {
165 stored: endpoints.length,
166 ok: okCount,
167 failed: failCount,
168 pruned: dead.size,
169 results
170 }
171}
172
173const broadcastPush = async (payloadObj) => {
174 await broadcastPushWithResults(payloadObj)
141175}
142176
143177const scheduleFlush = (delayMs = 0) => {
……@@ -444,7 +478,7 @@ const directory = async (r) => {
444478 store.updatedAt = Date.now()
445479 await writeSubscriptions(store)
446480
447 return new Response(JSON.stringify({ ok: true }), { status: 201, headers: header })
481 return new Response(JSON.stringify({ ok: true, stored: Object.keys(store.subscriptions).length }), { status: 201, headers: header })
448482 }
449483
450484 if (url.pathname === '/push/vapidPublicKey') {
……@@ -460,8 +494,9 @@ const directory = async (r) => {
460494 if (r.method !== 'POST') {
461495 return new Response(JSON.stringify({ error: 'Method Not Allowed' }), { status: 405, headers: header })
462496 }
463 await broadcastPush({ type: 'test', ts: Date.now() })
464 return new Response(JSON.stringify({ ok: true }), { status: 200, headers: header })
497 const now = Date.now()
498 const report = await broadcastPushWithResults({ type: 'test', ts: now })
499 return new Response(JSON.stringify({ ok: true, ts: now, ...report }), { status: 200, headers: header })
465500 }
466501
467502 const key = url.pathname.substring(1)