@@ -8,6 +8,18 @@ export const API_KEY = import.meta.env.VITE_ONESIGNAL_API_KEY as string | undefi
88const ANDROID_CHANNEL_ID = import . meta. env . VITE_ONESIGNAL_ANDROID_CHANNEL_ID as string | undefined ;
99const DEFAULT_ANDROID_CHANNEL_ID = 'b3b015d9-c050-4042-8548-dcc34aa44aa4' ;
1010
11+ function isTransientSendFailure ( data : unknown ) : boolean {
12+ if ( ! data || typeof data !== 'object' ) return false ;
13+ const record = data as { id ?: unknown ; errors ?: unknown ; recipients ?: unknown } ;
14+ const errors = record . errors ;
15+ const hasErrors =
16+ ( Array . isArray ( errors ) && errors . length > 0 ) ||
17+ ( errors != null && typeof errors === 'object' && Object . keys ( errors ) . length > 0 ) ;
18+ const missingId = typeof record . id !== 'string' || record . id . length === 0 ;
19+ const zeroRecipients = typeof record . recipients === 'number' && record . recipients === 0 ;
20+ return hasErrors || missingId || zeroRecipients ;
21+ }
22+
1123class OneSignalApiService {
1224 private static instance : OneSignalApiService ;
1325
@@ -85,9 +97,14 @@ class OneSignalApiService {
8597 const maxAttempts = 5 ;
8698 const backoffMs = ( n : number ) => 2_000 * 2 ** ( n - 1 ) ;
8799
88- // Retry on `invalid_player_ids` to absorb the brief race where the
89- // subscription has been created locally but is not yet visible to the
90- // /notifications endpoint.
100+ // Retry while the OneSignal backend hasn't yet indexed the freshly
101+ // created subscription. The /notifications endpoint reports this race in
102+ // a few different shapes, all of which return HTTP 200:
103+ // {"id":"...","recipients":0} (user just switched, push token not yet attached)
104+ // {"id":"...","errors":{"invalid_player_ids":[...]}}
105+ // {"id":"","errors":["All included players are not subscribed"]}
106+ // {"id":"","errors":[...]}
107+ // Treat any 200 response with no real id, populated errors, or recipients=0 as transient.
91108 for ( let attempt = 1 ; attempt <= maxAttempts ; attempt ++ ) {
92109 try {
93110 const response = await CapacitorHttp . post ( {
@@ -104,15 +121,12 @@ class OneSignalApiService {
104121 return false ;
105122 }
106123
107- const invalidIds = response . data ?. errors ?. invalid_player_ids ;
108- if ( Array . isArray ( invalidIds ) && invalidIds . length > 0 ) {
124+ if ( isTransientSendFailure ( response . data ) ) {
109125 if ( attempt < maxAttempts ) {
110126 await new Promise ( ( resolve ) => setTimeout ( resolve , backoffMs ( attempt ) ) ) ;
111127 continue ;
112128 }
113- console . error (
114- `Send notification failed: invalid_player_ids ${ JSON . stringify ( invalidIds ) } ` ,
115- ) ;
129+ console . error ( `Send notification failed: ${ JSON . stringify ( response . data ) } ` ) ;
116130 return false ;
117131 }
118132
0 commit comments