| … | … | @@ -113,31 +113,65 @@ let nextAllowedPushAt = 0 |
| 113 | 113 | let flushTimer = undefined |
| 114 | 114 | const queuedPushSigs = new Set() |
| 115 | 115 | |
| 116 | | const broadcastPush = async (payloadObj) => { |
| 116 | const 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 | |
| 128 | const broadcastPushWithResults = async (payloadObj) => { |
| 117 | 129 | const store = await readSubscriptions() |
| 118 | 130 | const endpoints = Object.keys(store.subscriptions ?? {}) |
| 119 | | if (endpoints.length === 0) return |
| 120 | 131 | |
| 121 | 132 | const dead = new Set() |
| 122 | | const results = await Promise.allSettled( |
| 133 | const results = await Promise.all( |
| 123 | 134 | 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 | |
| 126 | 141 | 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) } |
| 131 | 152 | } |
| 132 | 153 | }) |
| 133 | 154 | ) |
| 134 | | void results |
| 135 | 155 | |
| 136 | 156 | if (dead.size > 0) { |
| 137 | 157 | for (const endpoint of dead) delete store.subscriptions[endpoint] |
| 138 | 158 | store.updatedAt = Date.now() |
| 139 | 159 | await writeSubscriptions(store) |
| 140 | 160 | } |
| 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 | |
| 173 | const broadcastPush = async (payloadObj) => { |
| 174 | await broadcastPushWithResults(payloadObj) |
| 141 | 175 | } |
| 142 | 176 | |
| 143 | 177 | const scheduleFlush = (delayMs = 0) => { |
| … | … | @@ -444,7 +478,7 @@ const directory = async (r) => { |
| 444 | 478 | store.updatedAt = Date.now() |
| 445 | 479 | await writeSubscriptions(store) |
| 446 | 480 | |
| 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 }) |
| 448 | 482 | } |
| 449 | 483 | |
| 450 | 484 | if (url.pathname === '/push/vapidPublicKey') { |
| … | … | @@ -460,8 +494,9 @@ const directory = async (r) => { |
| 460 | 494 | if (r.method !== 'POST') { |
| 461 | 495 | return new Response(JSON.stringify({ error: 'Method Not Allowed' }), { status: 405, headers: header }) |
| 462 | 496 | } |
| 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 }) |
| 465 | 500 | } |
| 466 | 501 | |
| 467 | 502 | const key = url.pathname.substring(1) |