@@ -24,6 +24,21 @@ interface PushNotificationPayload {
2424 isAdmin ?: boolean ;
2525}
2626
27+ interface WebPushError extends Error {
28+ statusCode ?: number ;
29+ status ?: number ;
30+ body ?: string | unknown ;
31+ response ?: {
32+ body ?: string | unknown ;
33+ } ;
34+ errors ?: {
35+ statusCode ?: number ;
36+ status ?: number ;
37+ message ?: string ;
38+ body ?: string | unknown ;
39+ } [ ] ;
40+ }
41+
2742class WebPushAgent
2843 extends BaseAgent < NotificationAgentConfig >
2944 implements NotificationAgent
@@ -188,7 +203,34 @@ class WebPushAgent
188203 notificationPayload
189204 ) ;
190205 } catch ( e ) {
191- const statusCode = ( e as any ) . statusCode || ( e as any ) . status ;
206+ // Extract status code from error or nested errors (for AggregateError)
207+ const webPushError = e as WebPushError ;
208+ let statusCode = webPushError . statusCode || webPushError . status ;
209+ let errorMessage = webPushError . message || String ( e ) ;
210+ let errorBody = webPushError . body || webPushError . response ?. body ;
211+
212+ // Handle AggregateError - check nested errors for status codes
213+ if ( e instanceof AggregateError || webPushError . errors ) {
214+ const errors = webPushError . errors || [ ] ;
215+ for ( const nestedError of errors ) {
216+ const nestedStatusCode =
217+ nestedError ?. statusCode || nestedError ?. status ;
218+ if ( nestedStatusCode ) {
219+ statusCode = nestedStatusCode ;
220+ }
221+ if ( nestedError ?. message && ! errorMessage ) {
222+ errorMessage = nestedError . message ;
223+ }
224+ if ( nestedError ?. body && ! errorBody ) {
225+ errorBody = nestedError . body ;
226+ }
227+ }
228+ }
229+
230+ // Permanent failure status codes per RFC 8030:
231+ // - 410 Gone: Subscription expired/invalid (Section 6.2)
232+ // - 404 Not Found: Subscription expired (Section 7.3)
233+ // All other errors (429 rate limiting, network issues, etc.) are transient
192234 const isPermanentFailure = statusCode === 410 || statusCode === 404 ;
193235
194236 logger . error (
@@ -200,15 +242,13 @@ class WebPushAgent
200242 recipient : pushSub . user . displayName ,
201243 type : Notification [ type ] ,
202244 subject : payload . subject ,
203- errorMessage : ( e as Error ) . message || String ( e ) ,
245+ errorMessage,
204246 statusCode : statusCode || 'unknown' ,
205- errorBody : ( e as any ) . body || ( e as any ) . response ?. body ,
206- endpoint : pushSub . endpoint . substring ( 0 , 50 ) + '...' ,
207247 }
208248 ) ;
209249
210250 // Only remove subscription for permanent failures
211- // Transient errors should not remove the subscription
251+ // Transient errors (rate limiting, network issues, etc.) should not remove the subscription
212252 if ( isPermanentFailure ) {
213253 await userPushSubRepository . remove ( pushSub ) ;
214254 }
0 commit comments