@@ -123,9 +123,13 @@ private async Task<bool> SendAsync(
123123
124124 const int maxAttempts = 5 ;
125125
126- // Retry on `invalid_player_ids` to absorb the brief race where the
127- // subscription has been created locally but is not yet visible to the
128- // /notifications endpoint.
126+ // Retry while the OneSignal backend hasn't yet indexed the freshly
127+ // created subscription. The /notifications endpoint reports this race
128+ // in a few different shapes, all of which return HTTP 200:
129+ // {"errors":{"invalid_player_ids":[...]}}
130+ // {"id":"","errors":["All included players are not subscribed"]}
131+ // {"id":"","errors":[...]}
132+ // Treat any 200 response without a real notification id as transient.
129133 for ( var attempt = 1 ; attempt <= maxAttempts ; attempt ++ )
130134 {
131135 try
@@ -143,18 +147,15 @@ private async Task<bool> SendAsync(
143147 return false ;
144148 }
145149
146- var invalidIds = GetInvalidPlayerIds ( responseJson ) ;
147- if ( invalidIds . Count > 0 )
150+ if ( IsTransientSendFailure ( responseJson ) )
148151 {
149152 if ( attempt < maxAttempts )
150153 {
151154 var delayMs = 2000 * ( 1 << ( attempt - 1 ) ) ;
152155 await Task . Delay ( delayMs ) ;
153156 continue ;
154157 }
155- Debug . WriteLine (
156- $ "Send notification failed: invalid_player_ids [{ string . Join ( ", " , invalidIds ) } ]"
157- ) ;
158+ Debug . WriteLine ( $ "Send notification failed: { responseJson } ") ;
158159 return false ;
159160 }
160161
@@ -170,38 +171,46 @@ private async Task<bool> SendAsync(
170171 return false ;
171172 }
172173
173- private static List < string > GetInvalidPlayerIds ( string responseJson )
174+ private static bool IsTransientSendFailure ( string responseJson )
174175 {
175- var result = new List < string > ( ) ;
176176 if ( string . IsNullOrWhiteSpace ( responseJson ) )
177- return result ;
177+ return false ;
178178 try
179179 {
180180 using var doc = JsonDocument . Parse ( responseJson ) ;
181- if (
182- doc . RootElement . ValueKind == JsonValueKind . Object
183- && doc . RootElement . TryGetProperty ( "errors" , out var errors )
184- && errors . ValueKind == JsonValueKind . Object
185- && errors . TryGetProperty ( "invalid_player_ids" , out var invalidIds )
186- && invalidIds . ValueKind == JsonValueKind . Array
187- )
181+ if ( doc . RootElement . ValueKind != JsonValueKind . Object )
182+ return false ;
183+
184+ var hasErrors = false ;
185+ if ( doc . RootElement . TryGetProperty ( "errors" , out var errors ) )
188186 {
189- foreach ( var id in invalidIds . EnumerateArray ( ) )
187+ if ( errors . ValueKind == JsonValueKind . Array )
190188 {
191- if ( id . ValueKind == JsonValueKind . String )
192- {
193- var s = id . GetString ( ) ;
194- if ( ! string . IsNullOrEmpty ( s ) )
195- result . Add ( s ) ;
196- }
189+ hasErrors = errors . GetArrayLength ( ) > 0 ;
197190 }
191+ else if ( errors . ValueKind == JsonValueKind . Object )
192+ {
193+ var props = errors . EnumerateObject ( ) ;
194+ hasErrors = props . MoveNext ( ) ;
195+ }
196+ }
197+
198+ var missingId = true ;
199+ if (
200+ doc . RootElement . TryGetProperty ( "id" , out var id )
201+ && id . ValueKind == JsonValueKind . String
202+ && ! string . IsNullOrEmpty ( id . GetString ( ) )
203+ )
204+ {
205+ missingId = false ;
198206 }
207+
208+ return hasErrors || missingId ;
199209 }
200210 catch
201211 {
202- // Ignore malformed bodies; treat as success since status was 2xx.
212+ return false ;
203213 }
204- return result ;
205214 }
206215
207216 public async Task < UserData ? > FetchUserAsync ( string onesignalId )
0 commit comments