diff --git a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.m b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.m index d8418d5ed..fa331ee3b 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.m +++ b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.m @@ -1,7 +1,7 @@ /** * Modified MIT License * - * Copyright 2017 OneSignal + * Copyright 2024 OneSignal * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -35,8 +35,13 @@ #import "OSInAppMessagePrompt.h" #import "OSInAppMessagingRequests.h" #import "OneSignalWebViewManager.h" +#import "OneSignalTracker.h" #import #import "OSSessionManager.h" +#import "OneSignalOSCore/OneSignalOSCore-Swift.h" + +static NSInteger const DEFAULT_RETRY_AFTER_SECONDS = 1; // Default 1 second retry delay +static NSInteger const DEFAULT_RETRY_LIMIT = 3; // Default retry limit @implementation OSInAppMessageWillDisplayEvent @@ -248,16 +253,48 @@ - (void)getInAppMessagesFromServer:(NSString *)subscriptionId { [self updateInAppMessagesFromCache]; return; } - - OSRequestGetInAppMessages *request = [OSRequestGetInAppMessages withSubscriptionId:subscriptionId]; - [OneSignalCoreImpl.sharedClient executeRequest:request onSuccess:^(NSDictionary *result) { + + OSConsistencyManager *consistencyManager = [OSConsistencyManager shared]; + NSString *onesignalId = OneSignalUserManagerImpl.sharedInstance.onesignalId; + + if (!onesignalId) { + [OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:@"Failed to get in app messages due to no OneSignal ID"]; + return; + } + + OSIamFetchReadyCondition *condition = [[OSIamFetchReadyCondition alloc] initWithId:onesignalId]; + NSString *newestOffset = [consistencyManager registerCondition:condition forId:onesignalId]; + + NSNumber *sessionDuration = @([OSSessionManager getTimeFocusedElapsed]); + + // Initial request + [self attemptFetchWithRetries:subscriptionId + rywToken:newestOffset + sessionDuration:sessionDuration + attempts:1]; +} + +- (void)attemptFetchWithRetries:(NSString *)subscriptionId + rywToken:(NSString *)rywToken + sessionDuration:(NSNumber *)sessionDuration + attempts:(NSInteger)attempts { + + NSInteger retryCount = (attempts > 1) ? (attempts - 1) : 0; + + OSRequestGetInAppMessages *request = [OSRequestGetInAppMessages withSubscriptionId:subscriptionId + withSessionDuration:sessionDuration + withRetryCount:@(retryCount) + withRywToken:rywToken]; + + [OneSignalCoreImpl.sharedClient executeRequest:request + onSuccess:^(NSDictionary *result) { dispatch_async(dispatch_get_main_queue(), ^{ [OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:@"getInAppMessagesFromServer success"]; - if (result[@"in_app_messages"]) { // when there are no IAMs, will this still be there? - let messages = [NSMutableArray new]; + if (result[@"in_app_messages"]) { + NSMutableArray *messages = [NSMutableArray new]; for (NSDictionary *messageJson in result[@"in_app_messages"]) { - let message = [OSInAppMessageInternal instanceWithJson:messageJson]; + OSInAppMessageInternal *message = [OSInAppMessageInternal instanceWithJson:messageJson]; if (message) { [messages addObject:message]; } @@ -266,11 +303,80 @@ - (void)getInAppMessagesFromServer:(NSString *)subscriptionId { [self updateInAppMessagesFromServer:messages]; return; } - - // TODO: Check this request and response. If no IAMs returned, should we really get from cache? - // This is the existing implementation but it could mean this user has no IAMs? - - // Default is using cached IAMs in the messaging controller + [self updateInAppMessagesFromCache]; + }); + } + onFailure:^(NSError *error) { + NSDictionary *errorInfo = error.userInfo[@"returned"]; + NSNumber *statusCode = errorInfo[@"httpStatusCode"]; + + if (!statusCode) { + [self updateInAppMessagesFromCache]; + return; + } + + [OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"getInAppMessagesFromServer failure: %@", error.localizedDescription]]; + + NSInteger code = [statusCode integerValue]; + if (code == 425 || code == 429) { // 425 Too Early or 429 Too Many Requests + NSInteger retryAfter = [errorInfo[@"OneSignal-Retry-After"] integerValue] ?: DEFAULT_RETRY_AFTER_SECONDS; + NSInteger retryLimit = [errorInfo[@"OneSignal-Retry-Limit"] integerValue] ?: DEFAULT_RETRY_LIMIT; + + if (attempts <= retryLimit) { + [self retryAfterDelay:retryAfter + subscriptionId:subscriptionId + rywToken:rywToken + sessionDuration:sessionDuration + attempts:attempts]; + } else { + // Final attempt with offset = 0 + [self fetchInAppMessagesWithoutOffset:subscriptionId sessionDuration:sessionDuration]; + } + } else { + [self updateInAppMessagesFromCache]; + } + }]; +} + +- (void)retryAfterDelay:(NSInteger)retryAfter + subscriptionId:(NSString *)subscriptionId + rywToken:(NSString *)rywToken + sessionDuration:(NSNumber *)sessionDuration + attempts:(NSInteger)attempts { + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(retryAfter * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self attemptFetchWithRetries:subscriptionId + rywToken:rywToken + sessionDuration:sessionDuration + attempts:attempts + 1]; // No more delayTime argument + }); +} + +- (void)fetchInAppMessagesWithoutOffset:(NSString *)subscriptionId + sessionDuration:(NSNumber *)sessionDuration { + + OSRequestGetInAppMessages *request = [OSRequestGetInAppMessages withSubscriptionId:subscriptionId + withSessionDuration:sessionDuration + withRetryCount:@0 + withRywToken:nil]; // No retries for the final attempt + + [OneSignalCoreImpl.sharedClient executeRequest:request + onSuccess:^(NSDictionary *result) { + dispatch_async(dispatch_get_main_queue(), ^{ + [OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:@"Final attempt without offset success"]; + if (result[@"in_app_messages"]) { + NSMutableArray *messages = [NSMutableArray new]; + + for (NSDictionary *messageJson in result[@"in_app_messages"]) { + OSInAppMessageInternal *message = [OSInAppMessageInternal instanceWithJson:messageJson]; + if (message) { + [messages addObject:message]; + } + } + + [self updateInAppMessagesFromServer:messages]; + return; + } [self updateInAppMessagesFromCache]; }); } onFailure:^(NSError *error) {