11/* *
22 * Modified MIT License
33 *
4- * Copyright 2017 OneSignal
4+ * Copyright 2024 OneSignal
55 *
66 * Permission is hereby granted, free of charge, to any person obtaining a copy
77 * of this software and associated documentation files (the "Software"), to deal
3535#import " OSInAppMessagePrompt.h"
3636#import " OSInAppMessagingRequests.h"
3737#import " OneSignalWebViewManager.h"
38+ #import " OneSignalTracker.h"
3839#import < OneSignalOutcomes/OneSignalOutcomes.h>
3940#import " OSSessionManager.h"
41+ #import " OneSignalOSCore/OneSignalOSCore-Swift.h"
42+
43+ static NSInteger const DEFAULT_RETRY_AFTER_SECONDS = 1 ; // Default 1 second retry delay
44+ static NSInteger const DEFAULT_RETRY_LIMIT = 0 ; // If not returned by backend, don't retry
45+ static NSInteger const IAM_FETCH_DELAY_BUFFER = 0.5 ; // Fallback value if ryw_delay is nil: delay by 500 ms to increase the probability of getting a 200 & not having to retry
4046
4147@implementation OSInAppMessageWillDisplayEvent
4248
@@ -242,22 +248,70 @@ - (void)updateInAppMessagesFromCache {
242248}
243249
244250- (void )getInAppMessagesFromServer : (NSString *)subscriptionId {
245- [OneSignalLog onesignalLog: ONE_S_LL_VERBOSE message: @" getInAppMessagesFromServer" ];
251+ dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
252+ [OneSignalLog onesignalLog: ONE_S_LL_VERBOSE message: @" getInAppMessagesFromServer" ];
246253
247- if (!subscriptionId) {
248- [self updateInAppMessagesFromCache ];
249- return ;
250- }
251-
252- OSRequestGetInAppMessages *request = [OSRequestGetInAppMessages withSubscriptionId: subscriptionId];
253- [OneSignalCoreImpl.sharedClient executeRequest: request onSuccess: ^(NSDictionary *result) {
254+ if (!subscriptionId) {
255+ [self updateInAppMessagesFromCache ];
256+ return ;
257+ }
258+
259+ OSConsistencyManager *consistencyManager = [OSConsistencyManager shared ];
260+ NSString *onesignalId = OneSignalUserManagerImpl.sharedInstance .onesignalId ;
261+
262+ if (!onesignalId) {
263+ [OneSignalLog onesignalLog: ONE_S_LL_VERBOSE message: @" Failed to get in app messages due to no OneSignal ID" ];
264+ return ;
265+ }
266+
267+ OSIamFetchReadyCondition *condition = [OSIamFetchReadyCondition sharedInstanceWithId: onesignalId];
268+ OSReadYourWriteData *rywData = [consistencyManager getRywTokenFromAwaitableCondition: condition forId: onesignalId];
269+
270+ // We need to delay the first request by however long the backend is telling us (`ryw_delay`)
271+ // This will help avoid unnecessary retries & can be easily adjusted from the backend
272+ NSTimeInterval rywDelayInSeconds;
273+ if (rywData.rywDelay ) {
274+ rywDelayInSeconds = [rywData.rywDelay doubleValue ] / 1000.0 ;
275+ } else {
276+ rywDelayInSeconds = IAM_FETCH_DELAY_BUFFER;
277+ }
278+ dispatch_after (dispatch_time (DISPATCH_TIME_NOW, (int64_t )(rywDelayInSeconds * NSEC_PER_SEC)), dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
279+
280+ // Initial request
281+ [self attemptFetchWithRetries: subscriptionId
282+ rywData: rywData
283+ attempts: @0 // Starting with 0 attempts
284+ retryLimit: nil ]; // Retry limit to be set dynamically on first failure
285+ });
286+ });
287+ }
288+
289+
290+ - (void )attemptFetchWithRetries : (NSString *)subscriptionId
291+ rywData : (OSReadYourWriteData *)rywData
292+ attempts : (NSNumber *)attempts
293+ retryLimit : (NSNumber *)retryLimit {
294+ NSNumber *sessionDuration = @([OSSessionManager.sharedSessionManager getTimeFocusedElapsed ]);
295+ NSString *rywToken = rywData.rywToken ;
296+ NSNumber *rywDelay = rywData.rywDelay ;
297+
298+ // Create the request with the current attempt count
299+ OSRequestGetInAppMessages *request = [OSRequestGetInAppMessages withSubscriptionId: subscriptionId
300+ withSessionDuration: sessionDuration
301+ withRetryCount: attempts
302+ withRywToken: rywToken];
303+
304+ __block NSNumber *blockRetryLimit = retryLimit;
305+
306+ [OneSignalCoreImpl.sharedClient executeRequest: request
307+ onSuccess: ^(NSDictionary *result) {
254308 dispatch_async (dispatch_get_main_queue (), ^{
255309 [OneSignalLog onesignalLog: ONE_S_LL_VERBOSE message: @" getInAppMessagesFromServer success" ];
256- if (result[@" in_app_messages" ]) { // when there are no IAMs, will this still be there?
257- let messages = [NSMutableArray new ];
310+ if (result[@" in_app_messages" ]) {
311+ NSMutableArray * messages = [NSMutableArray new ];
258312
259313 for (NSDictionary *messageJson in result[@" in_app_messages" ]) {
260- let message = [OSInAppMessageInternal instanceWithJson: messageJson];
314+ OSInAppMessageInternal * message = [OSInAppMessageInternal instanceWithJson: messageJson];
261315 if (message) {
262316 [messages addObject: message];
263317 }
@@ -266,11 +320,89 @@ - (void)getInAppMessagesFromServer:(NSString *)subscriptionId {
266320 [self updateInAppMessagesFromServer: messages];
267321 return ;
268322 }
323+ });
324+ }
325+ onFailure: ^(NSError *error) {
326+ NSDictionary *errorInfo = error.userInfo [@" returned" ];
327+ NSNumber *statusCode = errorInfo[@" httpStatusCode" ];
328+ NSDictionary * responseHeaders = errorInfo[@" headers" ];
329+
330+ if (!statusCode) {
331+ [self updateInAppMessagesFromCache ];
332+ return ;
333+ }
334+
335+ [OneSignalLog onesignalLog: ONE_S_LL_VERBOSE message: [NSString stringWithFormat: @" getInAppMessagesFromServer failure: %@ " , error.localizedDescription]];
336+
337+ NSInteger code = [statusCode integerValue ];
338+ if (code == 425 || code == 429 ) { // 425 Too Early or 429 Too Many Requests
339+ NSInteger retryAfter = [responseHeaders[@" Retry-After" ] integerValue ] ?: DEFAULT_RETRY_AFTER_SECONDS;
269340
270- // TODO: Check this request and response. If no IAMs returned, should we really get from cache?
271- // This is the existing implementation but it could mean this user has no IAMs?
272-
273- // Default is using cached IAMs in the messaging controller
341+ // Dynamically set the retry limit from the header, if not already set
342+ if (!blockRetryLimit) {
343+ blockRetryLimit = @([responseHeaders[@" OneSignal-Retry-Limit" ] integerValue ] ?: DEFAULT_RETRY_LIMIT);
344+ }
345+
346+ if ([attempts integerValue ] < [blockRetryLimit integerValue ]) {
347+ NSInteger nextAttempt = [attempts integerValue ] + 1 ; // Increment attempts
348+ [self retryAfterDelay: retryAfter
349+ subscriptionId: subscriptionId
350+ rywData: rywData
351+ attempts: @(nextAttempt)
352+ retryLimit: blockRetryLimit];
353+ } else {
354+ // Final attempt without rywToken
355+ [self fetchInAppMessagesWithoutToken: subscriptionId];
356+ }
357+ } else if (code >= 500 && code <= 599 ) {
358+ [OneSignalLog onesignalLog: ONE_S_LL_VERBOSE message: @" Server error, skipping retries" ];
359+ [self updateInAppMessagesFromCache ];
360+ } else {
361+ [self updateInAppMessagesFromCache ];
362+ }
363+ }];
364+ }
365+
366+ - (void )retryAfterDelay : (NSInteger )retryAfter
367+ subscriptionId : (NSString *)subscriptionId
368+ rywData : (OSReadYourWriteData *)rywData
369+ attempts : (NSNumber *)attempts
370+ retryLimit : (NSNumber *)retryLimit {
371+
372+ dispatch_after (dispatch_time (DISPATCH_TIME_NOW, (int64_t )(retryAfter * NSEC_PER_SEC)), dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
373+
374+ [self attemptFetchWithRetries: subscriptionId
375+ rywData: rywData
376+ attempts: attempts
377+ retryLimit: retryLimit];
378+ });
379+ }
380+
381+ - (void )fetchInAppMessagesWithoutToken : (NSString *)subscriptionId {
382+ NSNumber *sessionDuration = @([OSSessionManager.sharedSessionManager getTimeFocusedElapsed ]);
383+
384+ OSRequestGetInAppMessages *request = [OSRequestGetInAppMessages withSubscriptionId: subscriptionId
385+ withSessionDuration: sessionDuration
386+ withRetryCount: nil
387+ withRywToken: nil ]; // No retries for the final attempt
388+
389+ [OneSignalCoreImpl.sharedClient executeRequest: request
390+ onSuccess: ^(NSDictionary *result) {
391+ dispatch_async (dispatch_get_main_queue (), ^{
392+ [OneSignalLog onesignalLog: ONE_S_LL_VERBOSE message: @" Final attempt without token success" ];
393+ if (result[@" in_app_messages" ]) {
394+ NSMutableArray *messages = [NSMutableArray new ];
395+
396+ for (NSDictionary *messageJson in result[@" in_app_messages" ]) {
397+ OSInAppMessageInternal *message = [OSInAppMessageInternal instanceWithJson: messageJson];
398+ if (message) {
399+ [messages addObject: message];
400+ }
401+ }
402+
403+ [self updateInAppMessagesFromServer: messages];
404+ return ;
405+ }
274406 [self updateInAppMessagesFromCache ];
275407 });
276408 } onFailure: ^(NSError *error) {
0 commit comments