protected async Task <IMessage> HandleResponse(RequestMessage requestMessage, ResponseMessage responseMessage) { if (responseMessage.Header.ResponseStatusCode == HttpStatusCode.OK && responseMessage.Body.Failure == 0 && responseMessage.Body.CanonicalIds == 0) { return(null); } //Body(FCM response content) can be null when Satus Code is Unauthorized (401) or BadRequest (400) //because FCM response content contains reason phrase as html //and as such is invalid json and Body is null. In this case, there is nothing to handle for unauthorized responses //and the only cause is the wrong server key or missing registration token or request containing invalid data not proper with FCM protocol. //The API consumer must make sure the right server key is provided in their AppSettings or the request is valid if (responseMessage.Body == null || (responseMessage.Body.Failure == 0 && responseMessage.Body.CanonicalIds == 0)) { return(null); } List <string> tokensForBackOffPush = new List <string>(); for (var i = 0; i < responseMessage.Body.Results.Length; i++) { var result = responseMessage.Body.Results[i]; if (result.Error == null && result.RegistrationId == null) { continue; } if (!string.IsNullOrWhiteSpace(result.MessageId) && !string.IsNullOrWhiteSpace(result.RegistrationId)) { if (requestMessage.IsBulk) { updateTokenFunc?.Invoke(requestMessage.Body.RegistrationIds[i], result.RegistrationId); } else { updateTokenFunc?.Invoke(requestMessage.Body.To, result.RegistrationId); } } else if (result.Error == ResponseMessageError.NotRegistered) { if (requestMessage.IsBulk) { deleteTokenFunc?.Invoke(requestMessage.Body.RegistrationIds[i]); } else { deleteTokenFunc?.Invoke(requestMessage.Body.To); } } else if (result.Error == ResponseMessageError.Unavailable || result.Error == ResponseMessageError.InternalServerError || result.Error == ResponseMessageError.DeviceMessageRateExceeded || result.Error == ResponseMessageError.TopicsMessageRateExceeded) { if (requestMessage.IsBulk) { tokensForBackOffPush.Add(requestMessage.Body.RegistrationIds[i]); } else { tokensForBackOffPush.Add(requestMessage.Body.To); break; } } //else {} = check for more response errors and act accordingly //BadRequest or Unauthorized - there is nothing to handle here, check the status code and reason phrase from CloudResponseMessage object //but the API consumer must make sure, it provides correct and valid data //as of now all possible errors are checked through CloudRequestMessageBody property inforcements and //CloudRequestMessage ValidateRequest() method } ResponseMessage responseMessageFromBackOffPush = new ResponseMessage(); if (tokensForBackOffPush.Count > 0) { RequestMessage newRequestMessage = (RequestMessage)requestMessage.DeepCopy(); if (tokensForBackOffPush.Count == 1) { newRequestMessage.Body.RegistrationIds = null; newRequestMessage.Body.To = tokensForBackOffPush[0]; } else { newRequestMessage.Body.To = null; newRequestMessage.Body.RegistrationIds = tokensForBackOffPush.ToArray(); } var status = await InitExponentialBackOffPush(newRequestMessage, responseMessage.Header.RetryAfterInMilliSeconds); if (status != null) { responseMessageFromBackOffPush = (ResponseMessage)status; } else { responseMessageFromBackOffPush = null; } } if (responseMessageFromBackOffPush != null) { return(responseMessageFromBackOffPush); } else { return(responseMessage); } }
/// <summary> /// This function provides retry mechanism for Firebase http request messages /// </summary> /// <param name="func">The provided function to execute</param> /// <param name="maxRetries">The maximum number of retries</param> /// <param name="waitBetweenRetrySec">The wait time in seconds between retries</param> /// <param name="retryType">The retry approach to execute the provided 'func' function</param> /// <param name="retryAfter">Start this method after 'retryAfter' which is in milliseconds</param> /// <returns></returns> public async Task <ResponseMessage> ExecFuncWithRetry(Func <IMessage, Task <ResponseMessage> > func, IMessage funcParamRequestMessage, int maxRetries, int waitBetweenRetrySec, BackoffType retryType, int?retryAfter = null) { if (func == null || funcParamRequestMessage == null) { throw new ArgumentNullException("No function provided or its IMessage parameter is null"); } if (maxRetries <= 0) { throw new ArgumentException("maxRetries is smaller than 1. Please provide maxRetries > 0"); } RequestMessage paramRequestMessage = new RequestMessage(); List <string> tokensForBackOffPush = new List <string>(); int retryCount = 1; if (retryAfter != null && retryAfter > 0) { Thread.Sleep((int)retryAfter); } while (retryCount <= maxRetries) { ResponseMessage = await func(funcParamRequestMessage); paramRequestMessage = (RequestMessage)funcParamRequestMessage; tokensForBackOffPush = new List <string>(); if (ResponseMessage.Header.ResponseStatusCode == System.Net.HttpStatusCode.OK && ResponseMessage.Body.Failure == 0 && ResponseMessage.Body.CanonicalIds == 0) { return(ResponseMessage); } else { for (var i = 0; i < ResponseMessage.Body.Results.Length; i++) { var result = ResponseMessage.Body.Results[i]; if (result.Error == null && result.RegistrationId == null) { continue; } if (!string.IsNullOrWhiteSpace(result.MessageId) && !string.IsNullOrWhiteSpace(result.RegistrationId)) { if (paramRequestMessage.IsBulk) { PushNotificationService.updateTokenFunc?.Invoke(paramRequestMessage.Body.RegistrationIds[i], result.RegistrationId); } else { PushNotificationService.updateTokenFunc?.Invoke(paramRequestMessage.Body.To, result.RegistrationId); } } else if (result.Error == ResponseMessageError.NotRegistered) { if (paramRequestMessage.IsBulk) { PushNotificationService.deleteTokenFunc?.Invoke(paramRequestMessage.Body.RegistrationIds[i]); } else { PushNotificationService.deleteTokenFunc?.Invoke(paramRequestMessage.Body.To); } } else if (result.Error == ResponseMessageError.Unavailable || result.Error == ResponseMessageError.InternalServerError || result.Error == ResponseMessageError.DeviceMessageRateExceeded || result.Error == ResponseMessageError.TopicsMessageRateExceeded) { if (paramRequestMessage.IsBulk) { tokensForBackOffPush.Add(paramRequestMessage.Body.RegistrationIds[i]); } else { tokensForBackOffPush.Add(paramRequestMessage.Body.To); break; } } } if (tokensForBackOffPush.Count > 0) { TimeSpan sleepTime; if (retryType == BackoffType.Linear) { sleepTime = TimeSpan.FromSeconds(waitBetweenRetrySec); } else { sleepTime = TimeSpan.FromSeconds(waitBetweenRetrySec * retryCount); } Thread.Sleep(sleepTime); retryCount++; if (tokensForBackOffPush.Count < ResponseMessage.Body.Results.Length && retryCount <= maxRetries) { //deep copy is not neccessary, if remove - do more testing RequestMessage newRequestMessage = (RequestMessage)paramRequestMessage.DeepCopy(); if (tokensForBackOffPush.Count == 1) { newRequestMessage.Body.RegistrationIds = null; newRequestMessage.Body.To = tokensForBackOffPush[0]; } else { newRequestMessage.Body.To = null; newRequestMessage.Body.RegistrationIds = tokensForBackOffPush.ToArray(); } funcParamRequestMessage = newRequestMessage; } } else { break; } } } //At this point ResponseMessage == paramRequestMessage (registrationToken-Count wise) //if tokensForBackOffPush has elements(registrationTokens), than those failed to be sent a notification //add registrationId to ResponseMessage.Body.Results[i].OriginalRegistrationId and RequestRetryStatus, for debugging or logging purposes //otherwise all notifications were sent successfully #if TRACK_FAILED_REQUESTS_ENABLED if (tokensForBackOffPush.Count > 0) { if (paramRequestMessage.IsBulk) { for (int i = 0; i < tokensForBackOffPush.Count; i++) { for (int j = 0; j < paramRequestMessage.Body.RegistrationIds.Length; j++) { if (tokensForBackOffPush[i] == paramRequestMessage.Body.RegistrationIds[j]) { if (retryCount >= maxRetries) { ResponseMessage.Body.Results[j].RequestRetryStatus = PushMessageStatus.FailedWithMaxRetries; } else { ResponseMessage.Body.Results[j].RequestRetryStatus = PushMessageStatus.FailedWithPartialRetries; } ResponseMessage.Body.Results[j].OriginalRegistrationId = paramRequestMessage.Body.RegistrationIds[j]; break; } } } } else { if (retryCount >= maxRetries) { ResponseMessage.Body.Results[0].RequestRetryStatus = PushMessageStatus.FailedWithMaxRetries; } else { ResponseMessage.Body.Results[0].RequestRetryStatus = PushMessageStatus.FailedWithPartialRetries; } ResponseMessage.Body.Results[0].OriginalRegistrationId = paramRequestMessage.Body.To; } } #endif return(ResponseMessage); }