/// <summary> /// Handles the response bundle and calls registered callbacks. /// </summary> /// <param name="in_jsonData">The received message bundle.</param> private void HandleResponseBundle(string in_jsonData) { m_brainCloudClientRef.Log("INCOMING: " + in_jsonData); JsonResponseBundleV2 bundleObj = JsonReader.Deserialize <JsonResponseBundleV2>(in_jsonData); long receivedPacketId = (long)bundleObj.packetId; if (m_expectedIncomingPacketId == NO_PACKET_EXPECTED || m_expectedIncomingPacketId != receivedPacketId) { m_brainCloudClientRef.Log("Dropping duplicate packet"); return; } m_expectedIncomingPacketId = NO_PACKET_EXPECTED; Dictionary <string, object>[] responseBundle = bundleObj.responses; Dictionary <string, object> response = null; Exception firstThrownException = null; int numExceptionsThrown = 0; for (int j = 0; j < responseBundle.Length; ++j) { response = responseBundle[j]; int statusCode = (int)response["status"]; string data = ""; // // It's important to note here that a user error callback *might* call // ResetCommunications() based on the error being returned. // ResetCommunications will clear the m_serviceCallsInProgress List // effectively removing all registered callbacks for this message bundle. // It's also likely that the developer will want to call authenticate next. // We need to ensure that this is supported as it's the best way to // reset the brainCloud communications after a session invalid or network // error is triggered. // // This is safe to do from the main thread but just in case someone // calls this method from another thread, we lock on m_serviceCallsWaiting // ServerCall sc = null; lock (m_serviceCallsWaiting) { if (m_serviceCallsInProgress.Count > 0) { sc = m_serviceCallsInProgress[0] as ServerCall; m_serviceCallsInProgress.RemoveAt(0); } } // its a success response if (statusCode == 200) { if (response[OperationParam.ServiceMessageData.Value] != null) { Dictionary <string, object> responseData = (Dictionary <string, object>)response[OperationParam.ServiceMessageData.Value]; // send the data back as not formatted data = JsonWriter.Serialize(response); // save the session ID try { if (getJsonString(responseData, OperationParam.ServiceMessageSessionId.Value, null) != null) { m_sessionID = (string)responseData[OperationParam.ServiceMessageSessionId.Value]; m_isAuthenticated = true; // TODO confirm authentication } // save the profile ID if (getJsonString(responseData, OperationParam.ServiceMessageProfileId.Value, null) != null) { m_brainCloudClientRef.AuthenticationService.ProfileId = (string)responseData[OperationParam.ServiceMessageProfileId.Value]; } } catch (Exception e) { m_brainCloudClientRef.Log("SessionId or ProfileId do not exist " + e.ToString()); } } // now try to execute the callback if (sc != null) { if (sc.GetService().Equals(ServiceName.PlayerState.Value) && (sc.GetOperation().Equals(ServiceOperation.FullReset.Value) || sc.GetOperation().Equals(ServiceOperation.Reset.Value) || sc.GetOperation().Equals(ServiceOperation.Logout.Value))) { // we reset the current player or logged out // we are no longer authenticated m_isAuthenticated = false; m_brainCloudClientRef.AuthenticationService.ProfileId = null; } else if (sc.GetService().Equals(ServiceName.Authenticate.Value) && sc.GetOperation().Equals(ServiceOperation.Authenticate.Value)) { ProcessAuthenticate(data); } // // only process callbacks that are real if (sc.GetCallback() != null) { try { sc.GetCallback().OnSuccessCallback(data); } catch (Exception e) { m_brainCloudClientRef.Log(e.StackTrace); ++numExceptionsThrown; if (firstThrownException == null) { firstThrownException = e; } } } } } else if (statusCode >= 400 || statusCode == 202) { object reasonCodeObj = null, statusMessageObj = null; int reasonCode = 0; string statusMessage = ""; if (response.TryGetValue("reason_code", out reasonCodeObj)) { reasonCode = (int)reasonCodeObj; } if (response.TryGetValue("status_message", out statusMessageObj)) { statusMessage = (string)statusMessageObj; } if (reasonCode == ReasonCodes.SESSION_EXPIRED || reasonCode == ReasonCodes.SESSION_NOT_FOUND_ERROR) { m_isAuthenticated = false; m_brainCloudClientRef.Log("Received session expired or not found, need to re-authenticate"); } // now try to execute the callback if (sc != null && sc.GetCallback() != null) { try { sc.GetCallback().OnErrorCallback(statusCode, reasonCode, statusMessage); } catch (Exception e) { m_brainCloudClientRef.Log(e.StackTrace); ++numExceptionsThrown; if (firstThrownException == null) { firstThrownException = e; } } } } } if (firstThrownException != null) { m_activeRequest = null; // to make sure we don't reprocess this message throw new Exception("User callback handlers threw " + numExceptionsThrown + " exception(s)." + " See the Unity log for callstacks or inner exception for first exception thrown.", firstThrownException); } }
/// <summary> /// The update method needs to be called periodically to send/receive responses /// and run the associated callbacks. /// </summary> public void Update() { // basic flow here is to: // 1- process existing requests // 2- send next request // 3- handle heartbeat/timeouts if (!m_initialized) { return; } if (!m_enabled) { return; } // process current request if (m_activeRequest != null) { eWebRequestStatus status = GetWebRequestStatus(m_activeRequest); if (status == eWebRequestStatus.STATUS_ERROR) { // do nothing with the error right now - let the timeout code handle it } else if (status == eWebRequestStatus.STATUS_DONE) { ResetIdleTimer(); // note that active request is set to null if exception is to be thrown HandleResponseBundle(GetWebRequestResponse(m_activeRequest)); m_activeRequest = null; } } // send the next message if we're ready if (m_activeRequest == null) { m_activeRequest = CreateAndSendNextRequestBundle(); } // is it time for a retry? if (m_activeRequest != null) { if (DateTime.Now.Subtract(m_activeRequest.TimeSent) >= GetPacketTimeout(m_activeRequest.Retries)) { if (!ResendMessage(m_activeRequest)) { // we've reached the retry limit - send timeout error to all client callbacks eWebRequestStatus status = GetWebRequestStatus(m_activeRequest); if (status == eWebRequestStatus.STATUS_ERROR) { m_brainCloudClientRef.Log("Timeout with network error: " + GetWebRequestResponse(m_activeRequest)); } else { m_brainCloudClientRef.Log("Timeout no reply from server"); } m_activeRequest = null; // Fake a message bundle to keep the callback logic in one place TriggerCommsError(StatusCodes.CLIENT_NETWORK_ERROR, ReasonCodes.CLIENT_NETWORK_ERROR_TIMEOUT, "Timeout trying to reach brainCloud server"); } } } // is it time for a heartbeat? if (Authenticated) { if (DateTime.Now.Subtract(m_lastTimePacketSent) >= m_idleTimeout) { SendHeartbeat(); } } }
/// <summary> /// Creates the request state object and sends the message bundle /// </summary> /// <returns>The and send next request bundle.</returns> private RequestState CreateAndSendNextRequestBundle() { RequestState requestState = null; lock (m_serviceCallsWaiting) { if (m_serviceCallsWaiting.Count > 0) { int numMessagesWaiting = m_serviceCallsWaiting.Count; if (numMessagesWaiting > MAX_MESSAGES_IN_BUNDLE) { numMessagesWaiting = MAX_MESSAGES_IN_BUNDLE; } if (m_serviceCallsInProgress.Count > 0) { // this should never happen m_brainCloudClientRef.Log("ERROR - in progress queue is not empty but we're ready for the next message!"); m_serviceCallsInProgress.Clear(); } m_serviceCallsInProgress = m_serviceCallsWaiting.GetRange(0, numMessagesWaiting); m_serviceCallsWaiting.RemoveRange(0, numMessagesWaiting); } if (m_serviceCallsInProgress.Count > 0) { requestState = new RequestState(); // prepare json data for server List <object> messageList = new List <object>(); ServerCall scIndex; for (int i = 0; i < m_serviceCallsInProgress.Count; ++i) { scIndex = m_serviceCallsInProgress[i] as ServerCall; Dictionary <string, object> message = new Dictionary <string, object>(); message[OperationParam.ServiceMessageService.Value] = scIndex.Service; message[OperationParam.ServiceMessageOperation.Value] = scIndex.Operation; message[OperationParam.ServiceMessageData.Value] = scIndex.GetJsonData(); messageList.Add(message); if (scIndex.GetOperation().Equals(ServiceOperation.Authenticate.Value)) { requestState.PacketNoRetry = true; } if (scIndex.GetOperation().Equals(ServiceOperation.FullReset.Value) || scIndex.GetOperation().Equals(ServiceOperation.Logout.Value)) { requestState.PacketRequiresLongTimeout = true; } } SendMessage(requestState, messageList); } } // unlock m_serviceCallsWaiting return(requestState); }
/// <summary> /// Method staggers the packet timeout value based on the currentRetry /// </summary> /// <returns>The packet timeout.</returns> /// <param name="currentRetryNumber">Current retry number.</param> private TimeSpan GetPacketTimeout(RequestState in_requestState) { int currentRetry = in_requestState.Retries; TimeSpan ret; // if this is a delete player or logout we change the // timeout behaviour if (in_requestState.IsSessionTerminatingPacket) { switch (currentRetry) { case 0: ret = TimeSpan.FromSeconds(15); break; case 1: ret = TimeSpan.FromSeconds(15); break; case 2: ret = TimeSpan.FromSeconds(2); break; case 3: ret = TimeSpan.FromSeconds(2); break; case 4: default: ret = TimeSpan.FromSeconds(1); break; } } else { switch (currentRetry) { case 0: ret = TimeSpan.FromSeconds(3); break; case 1: ret = TimeSpan.FromSeconds(5); break; case 2: ret = TimeSpan.FromSeconds(5); break; case 3: ret = TimeSpan.FromSeconds(10); break; case 4: default: ret = TimeSpan.FromSeconds(10); break; } } return(ret); }
/// <summary> /// Handles the response bundle and calls registered callbacks. /// </summary> /// <param name="in_jsonData">The received message bundle.</param> private void HandleResponseBundle(string in_jsonData) { m_brainCloudClientRef.Log("INCOMING: " + in_jsonData); JsonResponseBundleV2 bundleObj = JsonReader.Deserialize <JsonResponseBundleV2>(in_jsonData); long receivedPacketId = (long)bundleObj.packetId; if (m_expectedIncomingPacketId == NO_PACKET_EXPECTED || m_expectedIncomingPacketId != receivedPacketId) { m_brainCloudClientRef.Log("Dropping duplicate packet"); return; } m_expectedIncomingPacketId = NO_PACKET_EXPECTED; Dictionary <string, object>[] responseBundle = bundleObj.responses; Dictionary <string, object> response = null; IList <Exception> exceptions = new List <Exception>(); for (int j = 0; j < responseBundle.Length; ++j) { response = responseBundle[j]; int statusCode = (int)response["status"]; string data = ""; // // It's important to note here that a user error callback *might* call // ResetCommunications() based on the error being returned. // ResetCommunications will clear the m_serviceCallsInProgress List // effectively removing all registered callbacks for this message bundle. // It's also likely that the developer will want to call authenticate next. // We need to ensure that this is supported as it's the best way to // reset the brainCloud communications after a session invalid or network // error is triggered. // // This is safe to do from the main thread but just in case someone // calls this method from another thread, we lock on m_serviceCallsWaiting // ServerCall sc = null; lock (m_serviceCallsWaiting) { if (m_serviceCallsInProgress.Count > 0) { sc = m_serviceCallsInProgress[0] as ServerCall; m_serviceCallsInProgress.RemoveAt(0); } } // its a success response if (statusCode == 200) { Dictionary <string, object> responseData = null; if (response[OperationParam.ServiceMessageData.Value] != null) { responseData = (Dictionary <string, object>)response[OperationParam.ServiceMessageData.Value]; // send the data back as not formatted data = JsonWriter.Serialize(response); // save the session ID try { if (getJsonString(responseData, OperationParam.ServiceMessageSessionId.Value, null) != null) { m_sessionID = (string)responseData[OperationParam.ServiceMessageSessionId.Value]; m_isAuthenticated = true; // TODO confirm authentication } // save the profile ID if (getJsonString(responseData, OperationParam.ServiceMessageProfileId.Value, null) != null) { m_brainCloudClientRef.AuthenticationService.ProfileId = (string)responseData[OperationParam.ServiceMessageProfileId.Value]; } } catch (Exception e) { m_brainCloudClientRef.Log("SessionId or ProfileId do not exist " + e.ToString()); } } // now try to execute the callback if (sc != null) { if (sc.GetService().Equals(ServiceName.PlayerState.Value) && (sc.GetOperation().Equals(ServiceOperation.FullReset.Value) || sc.GetOperation().Equals(ServiceOperation.Logout.Value))) { // we reset the current player or logged out // we are no longer authenticated m_isAuthenticated = false; m_brainCloudClientRef.AuthenticationService.ClearSavedProfileID(); } else if (sc.GetService().Equals(ServiceName.Authenticate.Value) && sc.GetOperation().Equals(ServiceOperation.Authenticate.Value)) { ProcessAuthenticate(data); } // // only process callbacks that are real if (sc.GetCallback() != null) { try { sc.GetCallback().OnSuccessCallback(data); } catch (Exception e) { m_brainCloudClientRef.Log(e.StackTrace); exceptions.Add(e); } } // now deal with rewards if (m_rewardCallback != null && responseData != null) { try { Dictionary <string, object> rewards = null; // it's an operation that return a reward if (sc.GetService().Equals(ServiceName.Authenticate.Value) && sc.GetOperation().Equals(ServiceOperation.Authenticate.Value)) { object objRewards = null; if (responseData.TryGetValue("rewards", out objRewards)) { Dictionary <string, object> outerRewards = (Dictionary <string, object>)objRewards; if (outerRewards.TryGetValue("rewards", out objRewards)) { Dictionary <string, object> innerRewards = (Dictionary <string, object>)objRewards; if (innerRewards.Count > 0) { // we found rewards rewards = outerRewards; } } } } else if ((sc.GetService().Equals(ServiceName.PlayerStatistics.Value) && sc.GetOperation().Equals(ServiceOperation.Update.Value)) || (sc.GetService().Equals(ServiceName.PlayerStatisticsEvent.Value) && (sc.GetOperation().Equals(ServiceOperation.Trigger.Value) || sc.GetOperation().Equals(ServiceOperation.TriggerMultiple.Value)))) { object objRewards = null; if (responseData.TryGetValue("rewards", out objRewards)) { Dictionary <string, object> innerRewards = (Dictionary <string, object>)objRewards; if (innerRewards.Count > 0) { // we found rewards rewards = responseData; } } } if (rewards != null) { Dictionary <string, object> theReward = new Dictionary <string, object>(); theReward["rewards"] = rewards; theReward["service"] = sc.GetService(); theReward["operation"] = sc.GetOperation(); Dictionary <string, object> apiRewards = new Dictionary <string, object>(); List <object> rewardList = new List <object>(); rewardList.Add(theReward); apiRewards["apiRewards"] = rewardList; string rewardsAsJson = JsonWriter.Serialize(apiRewards); m_rewardCallback(rewardsAsJson); } } catch (Exception e) { m_brainCloudClientRef.Log(e.StackTrace); exceptions.Add(e); } } } } else if (statusCode >= 400 || statusCode == 202) { object reasonCodeObj = null, statusMessageObj = null; int reasonCode = 0; string errorJson = ""; if (response.TryGetValue("reason_code", out reasonCodeObj)) { reasonCode = (int)reasonCodeObj; } if (m_oldStyleStatusResponseInErrorCallback) { if (response.TryGetValue("status_message", out statusMessageObj)) { errorJson = (string)statusMessageObj; } } else { errorJson = JsonWriter.Serialize(response); } if (reasonCode == ReasonCodes.PLAYER_SESSION_EXPIRED || reasonCode == ReasonCodes.NO_SESSION) { m_isAuthenticated = false; m_brainCloudClientRef.Log("Received session expired or not found, need to re-authenticate"); } if (sc != null && sc.GetOperation().Equals(ServiceOperation.Logout.Value)) { if (reasonCode == ReasonCodes.CLIENT_NETWORK_ERROR_TIMEOUT) { m_isAuthenticated = false; m_brainCloudClientRef.Log("Could not communicate with the server on logout due to network timeout"); } } // now try to execute the callback if (sc != null && sc.GetCallback() != null) { try { sc.GetCallback().OnErrorCallback(statusCode, reasonCode, errorJson); } catch (Exception e) { m_brainCloudClientRef.Log(e.StackTrace); exceptions.Add(e); } } } } if (bundleObj.events != null && m_eventCallback != null) { Dictionary <string, Dictionary <string, object>[]> eventsJsonObj = new Dictionary <string, Dictionary <string, object>[]>(); eventsJsonObj["events"] = bundleObj.events; string eventsAsJson = JsonWriter.Serialize(eventsJsonObj); try { m_eventCallback(eventsAsJson); } catch (Exception e) { m_brainCloudClientRef.Log(e.StackTrace); exceptions.Add(e); } } if (exceptions.Count > 0) { m_activeRequest = null; // to make sure we don't reprocess this message throw new Exception("User callback handlers threw " + exceptions.Count + " exception(s)." + " See the Unity log for callstacks or inner exception for first exception thrown.", exceptions[0]); } }