+48
-13
serve.js
+48
-13
serve.js
···
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
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
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)