From fc77504c196c74273a79ea9c99bcb7d60bd94641 Mon Sep 17 00:00:00 2001 From: Nick H Date: Thu, 13 Aug 2026 13:02:14 -0400 Subject: [PATCH 01/11] Fixed push notification tests data structure --- tests/src/TestBCPushNotifications.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/src/TestBCPushNotifications.cpp b/tests/src/TestBCPushNotifications.cpp index 3c904bfe9..f851ae91a 100644 --- a/tests/src/TestBCPushNotifications.cpp +++ b/tests/src/TestBCPushNotifications.cpp @@ -110,8 +110,8 @@ TEST_F(TestBCPushNotifications, ScheduleRichPushNotificationUTC) Json::FastWriter fw; Json::Value data; - data["body"] = "asdf"; - data["title"] = "asdf"; + data["0"] = "asdf"; + data["1"] = "asdf"; uint64_t startTime = 0; @@ -140,8 +140,8 @@ TEST_F(TestBCPushNotifications, ScheduleRichPushNotificationMinutes) Json::FastWriter fw; Json::Value data; - data["body"] = "asdf"; - data["title"] = "asdf"; + data["0"] = "asdf"; + data["1"] = "asdf"; int32_t minutesFromNow = 0; From 024680830581882bee3c2298384120fce2d90e8b Mon Sep 17 00:00:00 2001 From: Steve Jones Date: Fri, 18 Sep 2026 09:24:10 -0400 Subject: [PATCH 02/11] Updated ACL checking --- src/BrainCloudCustomEntity.cpp | 12 +++++++++--- src/BrainCloudEntity.cpp | 12 +++++++++--- src/BrainCloudGlobalEntity.cpp | 20 +++++++++++++++----- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/BrainCloudCustomEntity.cpp b/src/BrainCloudCustomEntity.cpp index 9043f029a..ef911adf6 100644 --- a/src/BrainCloudCustomEntity.cpp +++ b/src/BrainCloudCustomEntity.cpp @@ -22,7 +22,9 @@ namespace BrainCloud Json::Value message; message[OperationParam::CustomEntityServiceEntityType.getValue()] = entityType; message[OperationParam::CustomEntityServiceData.getValue()] = JsonUtil::jsonStringToValue(jsonEntityData); - message[OperationParam::CustomEntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + if (StringUtil::IsOptionalParameterValid(jsonEntityAcl)) { + message[OperationParam::CustomEntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + } message[OperationParam::CustomEntityServiceTimeToLive.getValue()] = (Json::Int64) timeToLive; message[OperationParam::CustomEntityServiceIsOwned.getValue()] = isOwned; @@ -106,7 +108,9 @@ namespace BrainCloud message[OperationParam::CustomEntityServiceEntityId.getValue()] = entityId; message[OperationParam::CustomEntityServiceVersion.getValue()] = version; message[OperationParam::CustomEntityServiceData.getValue()] = JsonUtil::jsonStringToValue(jsonEntityData); - message[OperationParam::CustomEntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + if (StringUtil::IsOptionalParameterValid(jsonEntityAcl)) { + message[OperationParam::CustomEntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + } message[OperationParam::CustomEntityServiceTimeToLive.getValue()] = (Json::Int64) timeToLive; ServerCall * sc = new ServerCall(ServiceName::CustomEntity, ServiceOperation::UpdateEntity, message, callback); @@ -164,7 +168,9 @@ namespace BrainCloud message[OperationParam::CustomEntityServiceEntityType.getValue()] = entityType; message[OperationParam::CustomEntityServiceVersion.getValue()] = version; message[OperationParam::CustomEntityServiceData.getValue()] = JsonUtil::jsonStringToValue(dataJson); - message[OperationParam::CustomEntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(acl); + if (StringUtil::IsOptionalParameterValid(acl)) { + message[OperationParam::CustomEntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(acl); + } message[OperationParam::CustomEntityServiceTimeToLive.getValue()] = (Json::Int64) timeToLive; ServerCall * sc = new ServerCall(ServiceName::CustomEntity, ServiceOperation::UpdateSingleton, message, callback); diff --git a/src/BrainCloudEntity.cpp b/src/BrainCloudEntity.cpp index e7ec8642b..0b7c3fca8 100644 --- a/src/BrainCloudEntity.cpp +++ b/src/BrainCloudEntity.cpp @@ -22,7 +22,9 @@ namespace BrainCloud Json::Value message; message[OperationParam::EntityServiceEntityType.getValue()] = entityType; message[OperationParam::EntityServiceData.getValue()] = JsonUtil::jsonStringToValue(jsonEntityData); - message[OperationParam::EntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + if (StringUtil::IsOptionalParameterValid(jsonEntityAcl)) { + message[OperationParam::EntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + } ServerCall * sc = new ServerCall(ServiceName::Entity, ServiceOperation::Create, message, callback); m_client->sendRequest(sc); @@ -61,7 +63,9 @@ namespace BrainCloud message[OperationParam::EntityServiceEntityId.getValue()] = entityId; message[OperationParam::EntityServiceEntityType.getValue()] = entityType; message[OperationParam::EntityServiceData.getValue()] = JsonUtil::jsonStringToValue(jsonEntityData); - message[OperationParam::EntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + if (StringUtil::IsOptionalParameterValid(jsonEntityAcl)) { + message[OperationParam::EntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + } message[OperationParam::EntityServiceVersion.getValue()] = (Json::Int64) version; ServerCall * sc = new ServerCall(ServiceName::Entity, ServiceOperation::Update, message, callback); @@ -73,7 +77,9 @@ namespace BrainCloud Json::Value message; message[OperationParam::EntityServiceEntityType.getValue()] = entityType; message[OperationParam::EntityServiceData.getValue()] = JsonUtil::jsonStringToValue(jsonEntityData); - message[OperationParam::EntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + if (StringUtil::IsOptionalParameterValid(jsonEntityAcl)) { + message[OperationParam::EntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + } message[OperationParam::EntityServiceVersion.getValue()] = (Json::Int64) version; ServerCall * sc = new ServerCall(ServiceName::Entity, ServiceOperation::UpdateSingleton, message, callback); diff --git a/src/BrainCloudGlobalEntity.cpp b/src/BrainCloudGlobalEntity.cpp index 515af5307..0253ec00a 100644 --- a/src/BrainCloudGlobalEntity.cpp +++ b/src/BrainCloudGlobalEntity.cpp @@ -24,7 +24,9 @@ namespace BrainCloud message[OperationParam::GlobalEntityServiceEntityType.getValue()] = entityType; message[OperationParam::GlobalEntityServiceTimeToLive.getValue()] = (Json::Int64) timeToLive; message[OperationParam::GlobalEntityServiceData.getValue()] = JsonUtil::jsonStringToValue(jsonEntityData); - message[OperationParam::GlobalEntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + if (StringUtil::IsOptionalParameterValid(jsonEntityAcl)) { + message[OperationParam::GlobalEntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + } ServerCall * sc = new ServerCall(ServiceName::GlobalEntity, ServiceOperation::Create, message, callback); m_client->sendRequest(sc); @@ -38,7 +40,9 @@ namespace BrainCloud message[OperationParam::GlobalEntityServiceIndexedId.getValue()] = indexedId; message[OperationParam::GlobalEntityServiceTimeToLive.getValue()] = (Json::Int64) timeToLive; message[OperationParam::GlobalEntityServiceData.getValue()] = JsonUtil::jsonStringToValue(jsonEntityData); - message[OperationParam::GlobalEntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + if (StringUtil::IsOptionalParameterValid(jsonEntityAcl)) { + message[OperationParam::GlobalEntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + } ServerCall * sc = new ServerCall(ServiceName::GlobalEntity, ServiceOperation::CreateWithIndexedId, message, callback); m_client->sendRequest(sc); @@ -60,7 +64,9 @@ namespace BrainCloud Json::Value message; message[OperationParam::GlobalEntityServiceEntityId.getValue()] = entityId; message[OperationParam::GlobalEntityServiceVersion.getValue()] = (Json::Int64) version; - message[OperationParam::GlobalEntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + if (StringUtil::IsOptionalParameterValid(jsonEntityAcl)) { + message[OperationParam::GlobalEntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + } ServerCall * sc = new ServerCall(ServiceName::GlobalEntity, ServiceOperation::UpdateAcl, message, callback); m_client->sendRequest(sc); @@ -204,7 +210,9 @@ namespace BrainCloud message[OperationParam::GlobalEntityServiceEntityId.getValue()] = entityId; message[OperationParam::GlobalEntityServiceVersion.getValue()] = (Json::Int64) version; message[OperationParam::OwnerId.getValue()] = ownerId; - message[OperationParam::GlobalEntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + if (StringUtil::IsOptionalParameterValid(jsonEntityAcl)) { + message[OperationParam::GlobalEntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + } ServerCall * sc = new ServerCall(ServiceName::GlobalEntity, ServiceOperation::UpdateEntityOwnerAndAcl, message, callback); m_client->sendRequest(sc); @@ -215,7 +223,9 @@ namespace BrainCloud Json::Value message; message[OperationParam::GlobalEntityServiceEntityId.getValue()] = entityId; message[OperationParam::GlobalEntityServiceVersion.getValue()] = (Json::Int64) version; - message[OperationParam::GlobalEntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + if (StringUtil::IsOptionalParameterValid(jsonEntityAcl)) { + message[OperationParam::GlobalEntityServiceAcl.getValue()] = JsonUtil::jsonStringToValue(jsonEntityAcl); + } ServerCall * sc = new ServerCall(ServiceName::GlobalEntity, ServiceOperation::MakeSystemEntity, message, callback); m_client->sendRequest(sc); From 035cd7c3e5213be1e688ad0ebc96ebd953c25b5c Mon Sep 17 00:00:00 2001 From: Michael Costa Date: Thu, 24 Sep 2026 17:10:45 -0400 Subject: [PATCH 03/11] BCLOUD-14907, BCLOUD-14918 --- include/braincloud/AuthenticationType.h | 1 + include/braincloud/BrainCloudAppStore.h | 26 ++- include/braincloud/BrainCloudAuthentication.h | 17 +- include/braincloud/BrainCloudIdentity.h | 48 +++++ include/braincloud/BrainCloudWrapper.h | 170 +++++++++++------- src/AuthenticationType.cpp | 5 + src/BrainCloudAuthentication.cpp | 5 + src/BrainCloudIdentity.cpp | 15 ++ src/BrainCloudWrapper.cpp | 36 +++- 9 files changed, 237 insertions(+), 86 deletions(-) diff --git a/include/braincloud/AuthenticationType.h b/include/braincloud/AuthenticationType.h index 49475c1fd..cf75e881d 100644 --- a/include/braincloud/AuthenticationType.h +++ b/include/braincloud/AuthenticationType.h @@ -26,6 +26,7 @@ namespace BrainCloud static const AuthenticationType & GameCenter; static const AuthenticationType & Steam; static const AuthenticationType & Apple; + static const AuthenticationType & EpicGames; static const AuthenticationType & Google; static const AuthenticationType & GoogleOpenId; static const AuthenticationType & Twitter; diff --git a/include/braincloud/BrainCloudAppStore.h b/include/braincloud/BrainCloudAppStore.h index adeae755f..38040743d 100644 --- a/include/braincloud/BrainCloudAppStore.h +++ b/include/braincloud/BrainCloudAppStore.h @@ -34,6 +34,8 @@ namespace BrainCloud * - windows * - windowsPhone * - googlePlay + * - epicGames + * - xsolla * @param receiptData the specific store data required * @param callback The method to be invoked when the server response is received */ @@ -64,6 +66,8 @@ namespace BrainCloud * - windows * - windowsPhone * - googlePlay + * - epicGames + * - xsolla * @param userCurrency The currency type to retrieve the sales inventory for. * @param callback The method to be invoked when the server response is received */ @@ -84,6 +88,8 @@ namespace BrainCloud * - windows * - windowsPhone * - googlePlay + * - epicGames + * - xsolla * @param userCurrency The currency type to retrieve the sales inventory for. * @param category The product category * @param callback The method to be invoked when the server response is received @@ -96,14 +102,7 @@ namespace BrainCloud * Service Name - appStore * Service Operation - START_PURCHASE * - * @param storeId The store platform. Valid stores are: - * - itunes - * - facebook - * - appworld - * - steam - * - windows - * - windowsPhone - * - googlePlay + * @param storeId The store id. Currently only accepts "steam". * @param purchaseData specific data for purchasing 2 staged purchases * @param callback The method to be invoked when the server response is received */ @@ -115,14 +114,7 @@ namespace BrainCloud * Service Name - appStore * Service Operation - FINALIZE_PURCHASE * - * @param storeId The store platform. Valid stores are: - * - itunes - * - facebook - * - appworld - * - steam - * - windows - * - windowsPhone - * - googlePlay + * @param storeId The store id. Currently only accepts "steam". * @param transactionId the transactionId returned from start Purchase * @param transactionData specific data for purchasing 2 staged purchases * @param callback The method to be invoked when the server response is received @@ -154,6 +146,8 @@ namespace BrainCloud * - windows * - windowsPhone * - googlePlay + * - epicGames + * - xsolla * @param transactionId the transactionId returned from start Purchase * @param transactionData specific data for purchasing 2 staged purchases * @param callback The method to be invoked when the server response is received diff --git a/include/braincloud/BrainCloudAuthentication.h b/include/braincloud/BrainCloudAuthentication.h index 49e481e16..e4a951be6 100644 --- a/include/braincloud/BrainCloudAuthentication.h +++ b/include/braincloud/BrainCloudAuthentication.h @@ -195,18 +195,31 @@ namespace BrainCloud void authenticateSteam(const char * userId, const char * sessionticket, bool forceCreate, IServerCallback * callback = NULL); /** - * Authenticate the user using a google userid(email address) and google authentication token. + * Authenticate the user using their Apple account and identityToken. * * Service Name - authenticationV2 * Service Operation - AUTHENTICATE * * @param appleUserId String of the apple accounts user Id OR email - * @param identityToken The authentication token confirming users identity + * @param identityToken The identityToken confirming users identity * @param forceCreate Should a new profile be created for this user if the account does not exist? * @param callback The method to be invoked when the server response is received */ void authenticateApple(const char * appleUserId, const char * identityToken, bool forceCreate, IServerCallback * callback = NULL); + /** + * Authenticate the user using an epicAccountId and their authIdToken. + * + * Service Name - authenticationV2 + * Service Operation - AUTHENTICATE + * + * @param epicAccountId LocalUserId retrieved from the EOS AuthInterface's Login method. + * @param authIdToken IdToken string from the EOS AuthInterface's CopyIdToken method. + * @param forceCreate Should a new profile be created for this user if the account does not exist? + * @param callback The method to be invoked when the server response is received + */ + void authenticateEpicGames(const char * epicAccountId, const char * authIdToken, bool forceCreate, IServerCallback * callback = NULL); + /** * Authenticate the user using a google userid(email address) and google authentication token. * diff --git a/include/braincloud/BrainCloudIdentity.h b/include/braincloud/BrainCloudIdentity.h index 93b728b44..0539abea7 100644 --- a/include/braincloud/BrainCloudIdentity.h +++ b/include/braincloud/BrainCloudIdentity.h @@ -294,6 +294,54 @@ namespace BrainCloud */ void detachAppleIdentity(const char * appleId, bool continueAnon, IServerCallback * callback = NULL); + /** + * Attach the user's EpicGames credentials to the current profile. + * + * Service Name - identity + * Service Operation - ATTACH + * + * @param epicAccountId LocalUserId retrieved from the EOS AuthInterface's Login method. + * @param authIdToken IdToken string from the EOS AuthInterface's CopyIdToken method. + * @param callback The method to be invoked when the server response is received + * + * Errors to watch for: SWITCHING_PROFILES - this means that the EpicGames identity you provided + * already points to a different profile. You will likely want to offer the user the + * choice to *SWITCH* to that profile, or *MERGE* the profiles. + * + * To switch profiles, call ClearSavedProfileID() and call AuthenticateEpicGames(). + */ + void attachEpicGamesIdentity(const char * epicAccountId, const char * authIdToken, IServerCallback * callback = NULL); + + /** + * Merge the profile associated with the provided EpicGames credentials with the + * current profile. + * + * Service Name - identity + * Service Operation - MERGE + * + * @param epicAccountId LocalUserId retrieved from the EOS AuthInterface's Login method. + * @param authIdToken IdToken string from the EOS AuthInterface's CopyIdToken method. + * @param callback The method to be invoked when the server response is received + * + */ + void mergeEpicGamesIdentity(const char * epicAccountId, const char * authIdToken, IServerCallback * callback = NULL); + + /** + * Detach the EpicGames identity from this profile. + * + * Service Name - identity + * Service Operation - DETACH + * + * @param epicAccountId LocalUserId retrieved from the EOS AuthInterface's Login method. + * @param continueAnon Proceed even if the profile will revert to anonymous? + * @param callback The method to be invoked when the server response is received + * + * Watch for DOWNGRADING_TO_ANONYMOUS_ERROR - occurs if you set continueAnon to false, and + * disconnecting this identity would result in the profile being anonymous (which means that + * the profile wouldn't be retrievable if the user loses their device) + */ + void detachEpicGamesIdentity(const char * epicAccountId, bool continueAnon, IServerCallback * callback = NULL); + /** * Attach the user's Ultra credentials to the current profile. * diff --git a/include/braincloud/BrainCloudWrapper.h b/include/braincloud/BrainCloudWrapper.h index 4b1e50b14..e7d1de20d 100644 --- a/include/braincloud/BrainCloudWrapper.h +++ b/include/braincloud/BrainCloudWrapper.h @@ -281,19 +281,19 @@ namespace BrainCloud { * @param gameCenterId The user's Game Center Id which can be the playerId, gamePlayerId, or teamPlayerId from the localPlayer object. * @param forceCreate Should a new profile be created for this user if the account does not exist? * @param timestamp The timestamp value returned as part of the identity verification signature fetch from Game Center. - * Required for modern Game Center verification. - * @param publicKeyUrl The publicKeyUrl value returned as part of the identity verification signature fetch from Game Center. - * Required for modern Game Center verification. - * @param signature The raw signature bytes returned as part of the identity verification signature fetch from Game Center. - * Required for modern Game Center verification. - * @param signatureLength The length of the returned identity verification signature. - * Required for modern Game Center verification. - * @param salt The raw salt bytes returned as part of the identity verification signature fetch from Game Center. - * Required for modern Game Center verification. - * @param saltLength The length of the returned identity verification salt. - * Required for modern Game Center verification. - * @param teamPlayerId Optional for Game Center verification; only required when gameCenterId is set to a value other than teamPlayerId (e.g. playerId), - * so that brainCloud can still associate the user with their team-scoped identity. + * Required for modern Game Center verification. + * @param publicKeyUrl The publicKeyUrl value returned as part of the identity verification signature fetch from Game Center. + * Required for modern Game Center verification. + * @param signature The raw signature bytes returned as part of the identity verification signature fetch from Game Center. + * Required for modern Game Center verification. + * @param signatureLength The length of the returned identity verification signature. + * Required for modern Game Center verification. + * @param salt The raw salt bytes returned as part of the identity verification signature fetch from Game Center. + * Required for modern Game Center verification. + * @param saltLength The length of the returned identity verification salt. + * Required for modern Game Center verification. + * @param teamPlayerId Optional for Game Center verification; only required when gameCenterId is set to a value other than teamPlayerId (e.g. playerId), + * so that brainCloud can still associate the user with their team-scoped identity. * @param callback The method to be invoked when the server response is received. * * @returns Performs the success callback on success, failure callback on failure. @@ -301,22 +301,38 @@ namespace BrainCloud { */ void authenticateGameCenter(const char* gameCenterId, bool forceCreate, uint64_t timestamp, const std::string& publicKeyUrl, const uint8_t* signature, size_t signatureLength, const uint8_t* salt, size_t saltLength, const std::string& teamPlayerId = "", IServerCallback* callback = NULL); - /** - * Authenticate the user using a google userid(email address) and google authentication token. - * - * Service Name - authenticationV2 - * Service Operation - AUTHENTICATE - * - * @param appleUserId String of the apple accounts user Id OR email - * @param identityToken The authentication token confirming users identity - * @param forceCreate Should a new profile be created for this user if the account does not exist? - * @param callback The method to be invoked when the server response is received + /** + * Authenticate the user using a google userid(email address) and google authentication token. + * + * Service Name - authenticationV2 + * Service Operation - AUTHENTICATE + * + * @param appleUserId String of the apple accounts user Id OR email + * @param identityToken The authentication token confirming users identity + * @param forceCreate Should a new profile be created for this user if the account does not exist? + * @param callback The method to be invoked when the server response is received * * @returns Performs the success callback on success, failure callback on failure. * - */ + */ void authenticateApple(const char * appleUserId, const char * identityToken, bool forceCreate, IServerCallback * callback = NULL); + /** + * Authenticate the user using an epicAccountId and their authIdToken. + * + * Service Name - authenticationV2 + * Service Operation - AUTHENTICATE + * + * @param epicAccountId LocalUserId retrieved from the EOS AuthInterface's Login method. + * @param authIdToken IdToken string from the EOS AuthInterface's CopyIdToken method. + * @param forceCreate Should a new profile be created for this user if the account does not exist? + * @param callback The method to be invoked when the server response is received + * + * @returns Performs the success callback on success, failure callback on failure. + * + */ + void authenticateEpicGames(const char * epicAccountId, const char * authIdToken, bool forceCreate, IServerCallback * callback = NULL); + /** * Authenticate the user using a google userid(email address) and google authentication token. * @@ -436,8 +452,8 @@ namespace BrainCloud { /** * Smart Switch Authenticate will logout of the current profile, and switch to the new authentication type. - * In event the current session was previously an anonymous account, the smart switch will delete that profile. - * Use this function to keep a clean designflow from anonymous to signed profiles + * In event the current session was previously an anonymous account, the smart switch will delete that profile. + * Use this function to keep a clean designflow from anonymous to signed profiles * * Authenticate the user with a custom Email and Password. Note that the client app * is responsible for collecting (and storing) the e-mail and potentially password @@ -462,8 +478,8 @@ namespace BrainCloud { /** * Smart Switch Authenticate will logout of the current profile, and switch to the new authentication type. - * In event the current session was previously an anonymous account, the smart switch will delete that profile. - * Use this function to keep a clean designflow from anonymous to signed profiles + * In event the current session was previously an anonymous account, the smart switch will delete that profile. + * Use this function to keep a clean designflow from anonymous to signed profiles * * Authenticate the user via cloud code (which in turn validates the supplied credentials against an external system). * This allows the developer to extend brainCloud authentication to support other backend authentication systems. @@ -484,8 +500,8 @@ namespace BrainCloud { /** * Smart Switch Authenticate will logout of the current profile, and switch to the new authentication type. - * In event the current session was previously an anonymous account, the smart switch will delete that profile. - * Use this function to keep a clean designflow from anonymous to signed profiles + * In event the current session was previously an anonymous account, the smart switch will delete that profile. + * Use this function to keep a clean designflow from anonymous to signed profiles * * Authenticate the user with brainCloud using their Facebook Credentials * @@ -505,8 +521,8 @@ namespace BrainCloud { /** * Smart Switch Authenticate will logout of the current profile, and switch to the new authentication type. - * In event the current session was previously an anonymous account, the smart switch will delete that profile. - * Use this function to keep a clean designflow from anonymous to signed profiles + * In event the current session was previously an anonymous account, the smart switch will delete that profile. + * Use this function to keep a clean designflow from anonymous to signed profiles * * Authenticate the user with brainCloud using their FacebookLimited Credentials * @@ -526,8 +542,8 @@ namespace BrainCloud { /** * Smart Switch Authenticate will logout of the current profile, and switch to the new authentication type. - * In event the current session was previously an anonymous account, the smart switch will delete that profile. - * Use this function to keep a clean designflow from anonymous to signed profiles + * In event the current session was previously an anonymous account, the smart switch will delete that profile. + * Use this function to keep a clean designflow from anonymous to signed profiles * * Authenticate the user with brainCloud using their Oculus Credentials * @@ -546,8 +562,8 @@ namespace BrainCloud { /** * Smart Switch Authenticate will logout of the current profile, and switch to the new authentication type. - * In event the current session was previously an anonymous account, the smart switch will delete that profile. - * Use this function to keep a clean designflow from anonymous to signed profiles + * In event the current session was previously an anonymous account, the smart switch will delete that profile. + * Use this function to keep a clean designflow from anonymous to signed profiles * * Authenticate the user using their Game Center Id (legacy support only, not recommended). * @@ -569,8 +585,8 @@ namespace BrainCloud { /** * Smart Switch Authenticate will logout of the current profile, and switch to the new authentication type. - * In event the current session was previously an anonymous account, the smart switch will delete that profile. - * Use this function to keep a clean designflow from anonymous to signed profiles + * In event the current session was previously an anonymous account, the smart switch will delete that profile. + * Use this function to keep a clean designflow from anonymous to signed profiles * * Authenticate the user using their Game Center Id and identity verification signature. * @@ -580,19 +596,19 @@ namespace BrainCloud { * @param gameCenterId The user's Game Center Id which can be the playerId, gamePlayerId, or teamPlayerId from the localPlayer object. * @param forceCreate Should a new profile be created for this user if the account does not exist? * @param timestamp The timestamp value returned as part of the identity verification signature fetch from Game Center. - * Required for modern Game Center verification. - * @param publicKeyUrl The publicKeyUrl value returned as part of the identity verification signature fetch from Game Center. - * Required for modern Game Center verification. - * @param signature The raw signature bytes returned as part of the identity verification signature fetch from Game Center. - * Required for modern Game Center verification. - * @param signatureLength The length of the returned identity verification signature. - * Required for modern Game Center verification. - * @param salt The raw salt bytes returned as part of the identity verification signature fetch from Game Center. - * Required for modern Game Center verification. - * @param saltLength The length of the returned identity verification salt. - * Required for modern Game Center verification. - * @param teamPlayerId Optional for Game Center verification; only required when gameCenterId is set to a value other than teamPlayerId (e.g. playerId), - * so that brainCloud can still associate the user with their team-scoped identity. + * Required for modern Game Center verification. + * @param publicKeyUrl The publicKeyUrl value returned as part of the identity verification signature fetch from Game Center. + * Required for modern Game Center verification. + * @param signature The raw signature bytes returned as part of the identity verification signature fetch from Game Center. + * Required for modern Game Center verification. + * @param signatureLength The length of the returned identity verification signature. + * Required for modern Game Center verification. + * @param salt The raw salt bytes returned as part of the identity verification signature fetch from Game Center. + * Required for modern Game Center verification. + * @param saltLength The length of the returned identity verification salt. + * Required for modern Game Center verification. + * @param teamPlayerId Optional for Game Center verification; only required when gameCenterId is set to a value other than teamPlayerId (e.g. playerId), + * so that brainCloud can still associate the user with their team-scoped identity. * @param callback The method to be invoked when the server response is received. * * @returns Performs the success callback on success, failure callback on failure. @@ -602,8 +618,8 @@ namespace BrainCloud { /** * Smart Switch Authenticate will logout of the current profile, and switch to the new authentication type. - * In event the current session was previously an anonymous account, the smart switch will delete that profile. - * Use this function to keep a clean designflow from anonymous to signed profiles + * In event the current session was previously an anonymous account, the smart switch will delete that profile. + * Use this function to keep a clean designflow from anonymous to signed profiles * * Authenticate the user using a google userid(email address) and google authentication token. * @@ -622,8 +638,8 @@ namespace BrainCloud { /** * Smart Switch Authenticate will logout of the current profile, and switch to the new authentication type. - * In event the current session was previously an anonymous account, the smart switch will delete that profile. - * Use this function to keep a clean designflow from anonymous to signed profiles + * In event the current session was previously an anonymous account, the smart switch will delete that profile. + * Use this function to keep a clean designflow from anonymous to signed profiles * * Authenticate the user using a google userid(email address) and google authentication token. * @@ -642,8 +658,8 @@ namespace BrainCloud { /** * Smart Switch Authenticate will logout of the current profile, and switch to the new authentication type. - * In event the current session was previously an anonymous account, the smart switch will delete that profile. - * Use this function to keep a clean designflow from anonymous to signed profiles + * In event the current session was previously an anonymous account, the smart switch will delete that profile. + * Use this function to keep a clean designflow from anonymous to signed profiles * * Authenticate the user using a google userid(email address) and google authentication token. * @@ -662,8 +678,28 @@ namespace BrainCloud { /** * Smart Switch Authenticate will logout of the current profile, and switch to the new authentication type. - * In event the current session was previously an anonymous account, the smart switch will delete that profile. - * Use this function to keep a clean designflow from anonymous to signed profiles + * In event the current session was previously an anonymous account, the smart switch will delete that profile. + * Use this function to keep a clean designflow from anonymous to signed profiles + * + * Authenticate the user using an epicAccountId and their authIdToken. + * + * Service Name - authenticationV2 + * Service Operation - AUTHENTICATE + * + * @param epicAccountId LocalUserId retrieved from the EOS AuthInterface's Login method. + * @param authIdToken IdToken string from the EOS AuthInterface's CopyIdToken method. + * @param forceCreate Should a new profile be created for this user if the account does not exist? + * @param callback The method to be invoked when the server response is received + * + * @returns Performs the success callback on success, failure callback on failure. + * + */ + void smartSwitchAuthenticateEpicGames(const char* epicAccountId, const char* authIdToken, bool forceCreate, IServerCallback* callback = NULL); + + /** + * Smart Switch Authenticate will logout of the current profile, and switch to the new authentication type. + * In event the current session was previously an anonymous account, the smart switch will delete that profile. + * Use this function to keep a clean designflow from anonymous to signed profiles * * Authenticate the user using a steam userid and session ticket (without any validation on the userid). * @@ -682,8 +718,8 @@ namespace BrainCloud { /** * Smart Switch Authenticate will logout of the current profile, and switch to the new authentication type. - * In event the current session was previously an anonymous account, the smart switch will delete that profile. - * Use this function to keep a clean designflow from anonymous to signed profiles + * In event the current session was previously an anonymous account, the smart switch will delete that profile. + * Use this function to keep a clean designflow from anonymous to signed profiles * * Authenticate the user using a Twitter userid, authentication token, and secret from Twitter. * @@ -703,8 +739,8 @@ namespace BrainCloud { /** * Smart Switch Authenticate will logout of the current profile, and switch to the new authentication type. - * In event the current session was previously an anonymous account, the smart switch will delete that profile. - * Use this function to keep a clean designflow from anonymous to signed profiles + * In event the current session was previously an anonymous account, the smart switch will delete that profile. + * Use this function to keep a clean designflow from anonymous to signed profiles * * Authenticate the user using a userid and password (without any validation on the userid). * Similar to AuthenticateEmailPassword - except that that method has additional features to @@ -725,8 +761,8 @@ namespace BrainCloud { /** * Smart Switch Authenticate will logout of the current profile, and switch to the new authentication type. - * In event the current session was previously an anonymous account, the smart switch will delete that profile. - * Use this function to keep a clean designflow from anonymous to signed profiles + * In event the current session was previously an anonymous account, the smart switch will delete that profile. + * Use this function to keep a clean designflow from anonymous to signed profiles * * Authenticate the user for Ultra. * @@ -745,8 +781,8 @@ namespace BrainCloud { /** * Smart Switch Authenticate will logout of the current profile, and switch to the new authentication type. - * In event the current session was previously an anonymous account, the smart switch will delete that profile. - * Use this function to keep a clean designflow from anonymous to signed profiles + * In event the current session was previously an anonymous account, the smart switch will delete that profile. + * Use this function to keep a clean designflow from anonymous to signed profiles * * A generic Authenticate method that translates to the same as calling a specific one, except it takes an extraJson * that will be passed along to pre- or post- hooks. diff --git a/src/AuthenticationType.cpp b/src/AuthenticationType.cpp index c4d897711..c44cf42e0 100644 --- a/src/AuthenticationType.cpp +++ b/src/AuthenticationType.cpp @@ -12,6 +12,7 @@ namespace BrainCloud const AuthenticationType & AuthenticationType::GameCenter = AuthenticationType("GameCenter"); const AuthenticationType & AuthenticationType::Steam = AuthenticationType("Steam"); const AuthenticationType & AuthenticationType::Apple = AuthenticationType("Apple"); + const AuthenticationType & AuthenticationType::EpicGames = AuthenticationType("EpicGames"); const AuthenticationType & AuthenticationType::Google = AuthenticationType("Google"); const AuthenticationType & AuthenticationType::GoogleOpenId = AuthenticationType("GoogleOpenId"); const AuthenticationType & AuthenticationType::Twitter = AuthenticationType("Twitter"); @@ -67,6 +68,10 @@ namespace BrainCloud { return AuthenticationType::Apple; } + if (AuthenticationType == EpicGames) + { + return AuthenticationType::EpicGames; + } if (AuthenticationType == Google) { return AuthenticationType::Google; diff --git a/src/BrainCloudAuthentication.cpp b/src/BrainCloudAuthentication.cpp index 4a4c59086..513f196bc 100644 --- a/src/BrainCloudAuthentication.cpp +++ b/src/BrainCloudAuthentication.cpp @@ -148,6 +148,11 @@ namespace BrainCloud { authenticate(appleUserId, identityToken, AuthenticationType::Apple, NULL, forceCreate, "", callback); } + void BrainCloudAuthentication::authenticateEpicGames(const char * epicAccountId, const char * authIdToken, bool forceCreate, IServerCallback * callback) + { + authenticate(epicAccountId, authIdToken, AuthenticationType::EpicGames, NULL, forceCreate, "", callback); + } + void BrainCloudAuthentication::authenticateGoogle(const char * googleUserId, const char * serverAuthToken, bool forceCreate, IServerCallback * callback) { authenticate(googleUserId, serverAuthToken, AuthenticationType::Google, NULL, forceCreate, "", callback); diff --git a/src/BrainCloudIdentity.cpp b/src/BrainCloudIdentity.cpp index 6635a5978..7ef412c0c 100644 --- a/src/BrainCloudIdentity.cpp +++ b/src/BrainCloudIdentity.cpp @@ -135,6 +135,21 @@ namespace BrainCloud detachIdentity(appleId, AuthenticationType::Apple, continueAnon, callback); } + void BrainCloudIdentity::attachEpicGamesIdentity(const char * epicAccountId, const char * authIdToken, IServerCallback * callback) + { + attachIdentity(epicAccountId, authIdToken, AuthenticationType::EpicGames, callback); + } + + void BrainCloudIdentity::mergeEpicGamesIdentity(const char * epicAccountId, const char * authIdToken, IServerCallback * callback) + { + mergeIdentity(epicAccountId, authIdToken, AuthenticationType::EpicGames, callback); + } + + void BrainCloudIdentity::detachEpicGamesIdentity(const char * epicAccountId, bool continueAnon, IServerCallback * callback) + { + detachIdentity(epicAccountId, AuthenticationType::EpicGames, continueAnon, callback); + } + void BrainCloudIdentity::attachUltraIdentity(const std::string &ultraUsername, const std::string &ultraIdToken, IServerCallback * callback) { attachIdentity(ultraUsername.c_str(), ultraIdToken.c_str(), AuthenticationType::Ultra, callback); diff --git a/src/BrainCloudWrapper.cpp b/src/BrainCloudWrapper.cpp index 40d1e6bde..6156df9ce 100644 --- a/src/BrainCloudWrapper.cpp +++ b/src/BrainCloudWrapper.cpp @@ -254,6 +254,15 @@ namespace BrainCloud { client->getAuthenticationService()->authenticateApple(appleUserId, identityToken, forceCreate, this); } + void BrainCloudWrapper::authenticateEpicGames(const char* epicAccountId, const char* authIdToken, bool forceCreate, IServerCallback* callback) + { + m_authenticateCallback = callback; + + initializeIdentity(); + + client->getAuthenticationService()->authenticateEpicGames(epicAccountId, authIdToken, forceCreate, this); + } + void BrainCloudWrapper::authenticateGoogle(const char* googleUserId, const char* serverAuthCode, bool forceCreate, IServerCallback* callback) { m_authenticateCallback = callback; @@ -640,7 +649,7 @@ namespace BrainCloud { getIdentitiesCallback(smartCallback); } - void BrainCloudWrapper::smartSwitchAuthenticateApple(const char* userid, const char* token, bool forceCreate, IServerCallback* callback) + void BrainCloudWrapper::smartSwitchAuthenticateApple(const char* userid, const char* token, bool forceCreate, IServerCallback* callback) { class SmartSwitchAuthenticateCallback : public SmartSwitchCallback { @@ -665,6 +674,31 @@ namespace BrainCloud { getIdentitiesCallback(smartCallback); } + void BrainCloudWrapper::smartSwitchAuthenticateEpicGames(const char* epicAccountId, const char* authIdToken, bool forceCreate, IServerCallback* callback) + { + class SmartSwitchAuthenticateCallback : public SmartSwitchCallback + { + public: + SmartSwitchAuthenticateCallback(BrainCloudWrapper* wrapper, const char* epicAccountId, const char* authIdToken, bool forceCreate, IServerCallback* callback) : SmartSwitchCallback(wrapper, callback) { + this->epicAccountId = epicAccountId; + this->authIdToken = authIdToken; + this->forceCreate = forceCreate; + } + + std::string epicAccountId; std::string authIdToken; bool forceCreate; + + void serverCallback(ServiceName serviceName, ServiceOperation serviceOperation, std::string const & jsonData) + { + wrapper->clearIds(); + wrapper->client->getAuthenticationService()->authenticateEpicGames(epicAccountId.c_str(), authIdToken.c_str(), forceCreate, callback); + delete this; + } + }; + + SmartSwitchAuthenticateCallback *smartCallback = new SmartSwitchAuthenticateCallback(this, epicAccountId, authIdToken, forceCreate, callback); + getIdentitiesCallback(smartCallback); + } + void BrainCloudWrapper::smartSwitchAuthenticateSteam(const char* userid, const char* sessionticket, bool forceCreate, IServerCallback* callback) { class SmartSwitchAuthenticateCallback : public SmartSwitchCallback From 9b9629169520883e104cb22137dab2929c2c5947 Mon Sep 17 00:00:00 2001 From: Michael Costa Date: Fri, 25 Sep 2026 13:50:59 -0400 Subject: [PATCH 04/11] Comment & parameter name fixes --- include/braincloud/BrainCloudWrapper.h | 10 +++++----- src/BrainCloudWrapper.cpp | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/braincloud/BrainCloudWrapper.h b/include/braincloud/BrainCloudWrapper.h index e7d1de20d..59ae4a011 100644 --- a/include/braincloud/BrainCloudWrapper.h +++ b/include/braincloud/BrainCloudWrapper.h @@ -302,7 +302,7 @@ namespace BrainCloud { void authenticateGameCenter(const char* gameCenterId, bool forceCreate, uint64_t timestamp, const std::string& publicKeyUrl, const uint8_t* signature, size_t signatureLength, const uint8_t* salt, size_t saltLength, const std::string& teamPlayerId = "", IServerCallback* callback = NULL); /** - * Authenticate the user using a google userid(email address) and google authentication token. + * Authenticate the user using their Apple account and identityToken. * * Service Name - authenticationV2 * Service Operation - AUTHENTICATE @@ -661,20 +661,20 @@ namespace BrainCloud { * In event the current session was previously an anonymous account, the smart switch will delete that profile. * Use this function to keep a clean designflow from anonymous to signed profiles * - * Authenticate the user using a google userid(email address) and google authentication token. + * Authenticate the user using their Apple account and identityToken. * * Service Name - authenticationV2 * Service Operation - AUTHENTICATE * - * @param userid String representation of apple+ userid (email) - * @param token The authentication token derived via the apple apis. + * @param appleUserId String of the apple accounts user Id OR email + * @param identityToken The identityToken confirming users identity * @param forceCreate Should a new profile be created for this user if the account does not exist? * @param callback The method to be invoked when the server response is received * * @returns Performs the success callback on success, failure callback on failure. * */ - void smartSwitchAuthenticateApple(const char* userid, const char* token, bool forceCreate, IServerCallback* callback = NULL); + void smartSwitchAuthenticateApple(const char* appleUserId, const char* identityToken, bool forceCreate, IServerCallback* callback = NULL); /** * Smart Switch Authenticate will logout of the current profile, and switch to the new authentication type. diff --git a/src/BrainCloudWrapper.cpp b/src/BrainCloudWrapper.cpp index 6156df9ce..a1a32531f 100644 --- a/src/BrainCloudWrapper.cpp +++ b/src/BrainCloudWrapper.cpp @@ -649,28 +649,28 @@ namespace BrainCloud { getIdentitiesCallback(smartCallback); } - void BrainCloudWrapper::smartSwitchAuthenticateApple(const char* userid, const char* token, bool forceCreate, IServerCallback* callback) + void BrainCloudWrapper::smartSwitchAuthenticateApple(const char* appleUserId, const char* identityToken, bool forceCreate, IServerCallback* callback) { class SmartSwitchAuthenticateCallback : public SmartSwitchCallback { public: - SmartSwitchAuthenticateCallback(BrainCloudWrapper* wrapper, const char* userid, const char* token, bool forceCreate, IServerCallback* callback) : SmartSwitchCallback(wrapper, callback) { - this->userid = userid; - this->token = token; + SmartSwitchAuthenticateCallback(BrainCloudWrapper* wrapper, const char* appleUserId, const char* identityToken, bool forceCreate, IServerCallback* callback) : SmartSwitchCallback(wrapper, callback) { + this->appleUserId = appleUserId; + this->identityToken = identityToken; this->forceCreate = forceCreate; } - std::string userid; std::string token; bool forceCreate; + std::string appleUserId; std::string identityToken; bool forceCreate; void serverCallback(ServiceName serviceName, ServiceOperation serviceOperation, std::string const & jsonData) { wrapper->clearIds(); - wrapper->client->getAuthenticationService()->authenticateApple(userid.c_str(), token.c_str(), forceCreate, callback); + wrapper->client->getAuthenticationService()->authenticateApple(appleUserId.c_str(), identityToken.c_str(), forceCreate, callback); delete this; } }; - SmartSwitchAuthenticateCallback *smartCallback = new SmartSwitchAuthenticateCallback(this, userid, token, forceCreate, callback); + SmartSwitchAuthenticateCallback *smartCallback = new SmartSwitchAuthenticateCallback(this, appleUserId, identityToken, forceCreate, callback); getIdentitiesCallback(smartCallback); } From 453ed57fe29aef28426ff562ed79fdeeadc9d45f Mon Sep 17 00:00:00 2001 From: Michael Costa Date: Thu, 1 Oct 2026 15:01:44 -0400 Subject: [PATCH 05/11] Include metaHorizon in AppStore Comment --- include/braincloud/BrainCloudAppStore.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/braincloud/BrainCloudAppStore.h b/include/braincloud/BrainCloudAppStore.h index 38040743d..16f3e8f3c 100644 --- a/include/braincloud/BrainCloudAppStore.h +++ b/include/braincloud/BrainCloudAppStore.h @@ -34,6 +34,7 @@ namespace BrainCloud * - windows * - windowsPhone * - googlePlay + * - metaHorizon * - epicGames * - xsolla * @param receiptData the specific store data required @@ -66,6 +67,7 @@ namespace BrainCloud * - windows * - windowsPhone * - googlePlay + * - metaHorizon * - epicGames * - xsolla * @param userCurrency The currency type to retrieve the sales inventory for. @@ -88,6 +90,7 @@ namespace BrainCloud * - windows * - windowsPhone * - googlePlay + * - metaHorizon * - epicGames * - xsolla * @param userCurrency The currency type to retrieve the sales inventory for. @@ -146,6 +149,7 @@ namespace BrainCloud * - windows * - windowsPhone * - googlePlay + * - metaHorizon * - epicGames * - xsolla * @param transactionId the transactionId returned from start Purchase From d46858370d74e6a39f375c1f7570dc0f4c823b29 Mon Sep 17 00:00:00 2001 From: Nick H Date: Mon, 5 Oct 2026 17:45:24 -0400 Subject: [PATCH 06/11] Removed authenticate ultra obsolete test --- tests/src/TestBCAuth.cpp | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/tests/src/TestBCAuth.cpp b/tests/src/TestBCAuth.cpp index 56099c134..635325184 100644 --- a/tests/src/TestBCAuth.cpp +++ b/tests/src/TestBCAuth.cpp @@ -289,38 +289,6 @@ TEST_F(TestBCAuth, AuthenticateAdvanced) Logout(); } -TEST_F(TestBCAuth, AuthenticateUltra) -{ - if (TestFixtureBase::getServerUrl().find("api.internal.braincloudservers.com") == std::string::npos || - TestFixtureBase::getServerUrl().find("api.internala.braincloudservers.com") == std::string::npos || - TestFixtureBase::getServerUrl().find("api.internalg.braincloudservers.com") == std::string::npos) - { - printf("This env doesn't support Ultra authentication type\n"); - return; - } - - TestResult tr; - - // Auth universal - m_bc->getAuthenticationService()->authenticateUniversal(GetUser(UserA)->m_id, GetUser(UserA)->m_password, true, &tr); - tr.run(m_bc); - - // Run a cloud script to grab the ultra's JWT token - m_bc->getScriptService()->runScript("getUltraToken", "{}", &tr); - tr.run(m_bc); - auto id_token = tr.m_response["data"]["response"]["data"]["json"]["id_token"].asString(); - - // Logout - Logout(); - - // Auth Ultra using the token - m_bc->getAuthenticationService()->authenticateUltra("braincloud1", id_token, true, &tr); - tr.run(m_bc); - - // Logout again (This test fixture doesn't do it) - Logout(); -} - class TestBCAutoReconnectCallback final : public IAutoReconnectCallback { public: From 63990ba8f4feb9b74c1778be708fc5da832c3776 Mon Sep 17 00:00:00 2001 From: Steve Jones Date: Thu, 22 Oct 2026 12:48:52 -0400 Subject: [PATCH 07/11] Going away from current time, for the monotonic clock improving midnight, dst, and other time roll overs --- .../braincloud/internal/IBrainCloudComms.h | 6 +++++ include/braincloud/internal/RelayComms.h | 10 +++---- include/braincloud/internal/TimeUtil.h | 2 ++ src/DefaultBrainCloudComms.cpp | 8 +++--- src/IBrainCloudComms.cpp | 26 +++++++++++++++++++ src/RTTComms.cpp | 6 ++--- src/RelayComms.cpp | 16 ++++++------ src/TimeUtil.cpp | 6 +++++ 8 files changed, 60 insertions(+), 20 deletions(-) diff --git a/include/braincloud/internal/IBrainCloudComms.h b/include/braincloud/internal/IBrainCloudComms.h index a068f4788..d86006b3c 100644 --- a/include/braincloud/internal/IBrainCloudComms.h +++ b/include/braincloud/internal/IBrainCloudComms.h @@ -154,6 +154,12 @@ namespace BrainCloud bool _isInitialized; bool _isAuthenticated; int _heartbeatInterval; + + // server time = monotonic ms + offset, synced from server_time so device clock changes can't skew it + int64_t _serverTimeOffsetMs; + bool _isServerTimeSynced; + int64_t getServerTimeMs() const; + void syncServerTime(const Json::Value& data); int _maxBundleSize; bool _immediateRetryOnError; bool _loggingEnabled; diff --git a/include/braincloud/internal/RelayComms.h b/include/braincloud/internal/RelayComms.h index a9b5f5343..e64194cfc 100644 --- a/include/braincloud/internal/RelayComms.h +++ b/include/braincloud/internal/RelayComms.h @@ -98,8 +98,8 @@ namespace BrainCloud int netId; std::vector data; double resendInterval; - std::chrono::time_point lastResendTime; - std::chrono::time_point timeSinceFirstSend; + std::chrono::time_point lastResendTime; + std::chrono::time_point timeSinceFirstSend; static int allocCount; Packet() { ++allocCount; } @@ -181,7 +181,7 @@ namespace BrainCloud bool m_isSocketConnected = false; bool m_resendConnectRequest = false; bool m_endMatchRequested = false; - std::chrono::time_point m_lastConnectResendTime; + std::chrono::time_point m_lastConnectResendTime; bool m_isConnected = false; // Events @@ -194,8 +194,8 @@ namespace BrainCloud // Ping int m_ping = 999; std::chrono::seconds m_pingInterval; - std::chrono::time_point m_lastPingTime; - std::chrono::time_point m_lastRecvTime; // For UDP timeout + std::chrono::time_point m_lastPingTime; + std::chrono::time_point m_lastRecvTime; // For UDP timeout // Profile/Cx/Net IDs int m_netId = -1; diff --git a/include/braincloud/internal/TimeUtil.h b/include/braincloud/internal/TimeUtil.h index af0ed9bf6..1b96fa3f1 100644 --- a/include/braincloud/internal/TimeUtil.h +++ b/include/braincloud/internal/TimeUtil.h @@ -15,6 +15,8 @@ namespace BrainCloud { { public: static int64_t getCurrentTimeMillis(); + // monotonic; use for heartbeats/timeouts so midnight, DST and clock changes can't skew them + static int64_t getMonotonicTimeMillis(); static void sleepMillis(int millis); }; diff --git a/src/DefaultBrainCloudComms.cpp b/src/DefaultBrainCloudComms.cpp index 4159f8aef..b8bb195ed 100644 --- a/src/DefaultBrainCloudComms.cpp +++ b/src/DefaultBrainCloudComms.cpp @@ -111,7 +111,7 @@ namespace BrainCloud //TODO: Move this into the request. Like we did in IXMLHttpRequestLoader which has a timeout in a thread // iwhttp doesn't have a timeout mechanism so we have to monitor ourselves // and cancel the request if it goes over the retry timeout - int64_t currentTimeMillis = TimeUtil::getCurrentTimeMillis(); + int64_t currentTimeMillis = TimeUtil::getMonotonicTimeMillis(); int64_t retryTimeout = (int64_t)(getRetryTimeoutMillis(_retryCount)); if (currentTimeMillis >= _packetSendTimeMillis + retryTimeout) { @@ -128,7 +128,7 @@ namespace BrainCloud } else { - int64_t currentTimeMillis = TimeUtil::getCurrentTimeMillis(); + int64_t currentTimeMillis = TimeUtil::getMonotonicTimeMillis(); _queueMutex.lock(); bool queueHasMessages = _queue.size() > 0; @@ -840,7 +840,7 @@ namespace BrainCloud { // We haven't reached the maximum number of retries yet. Try again. - int64_t currentTime = TimeUtil::getCurrentTimeMillis(); + int64_t currentTime = TimeUtil::getMonotonicTimeMillis(); int64_t delta = currentTime - _packetSendTimeMillis; int64_t sleepTime = (int64_t)(getRetryTimeoutMillis(_retryCount)); @@ -1188,7 +1188,7 @@ namespace BrainCloud _loader->load(_request); } - _packetSendTimeMillis = TimeUtil::getCurrentTimeMillis(); + _packetSendTimeMillis = TimeUtil::getMonotonicTimeMillis(); _retryTimeMillis = RETRY_TIME_NOT_RETRYING; } diff --git a/src/IBrainCloudComms.cpp b/src/IBrainCloudComms.cpp index 6b32fd2cf..eba24c7c2 100644 --- a/src/IBrainCloudComms.cpp +++ b/src/IBrainCloudComms.cpp @@ -2,6 +2,7 @@ #include "braincloud/internal/IBrainCloudComms.h" #include "braincloud/BrainCloudClient.h" +#include "braincloud/internal/TimeUtil.h" #include #include @@ -29,6 +30,8 @@ namespace BrainCloud { , _isInitialized(false) , _isAuthenticated(false) , _heartbeatInterval(0) + , _serverTimeOffsetMs(TimeUtil::getCurrentTimeMillis() - TimeUtil::getMonotonicTimeMillis()) + , _isServerTimeSynced(false) , _immediateRetryOnError(false) , _loggingEnabled(false) , _authenticationTimeoutMillis(DEFAULT_AUTHENTICATION_TIMEOUT_MILLIS) @@ -233,6 +236,21 @@ namespace BrainCloud { } } + int64_t IBrainCloudComms::getServerTimeMs() const + { + return TimeUtil::getMonotonicTimeMillis() + _serverTimeOffsetMs; + } + + void IBrainCloudComms::syncServerTime(const Json::Value& data) + { + const Json::Value& serverTime = data["server_time"]; + if (serverTime.isNumeric() && serverTime.asInt64() > 0) + { + _serverTimeOffsetMs = serverTime.asInt64() - TimeUtil::getMonotonicTimeMillis(); + _isServerTimeSynced = true; + } + } + void IBrainCloudComms::filterIncomingMessages(const ServerCall* servercall, const Json::Value& response) { //This is a hook to perform processing on any messages that come in, before they are sent to the calling application. @@ -274,8 +292,16 @@ namespace BrainCloud { { _killSwitchThreshold = response["data"]["maxKillCount"].asInt(); } + + syncServerTime(response["data"]); } } + else if (servercall->getService() == ServiceName::Time && + servercall->getOperation() == ServiceOperation::Read && + response["data"] != Json::nullValue) + { + syncServerTime(response["data"]); + } else if (servercall->getService() == ServiceName::PlayerState && (servercall->getOperation() == ServiceOperation::FullReset || servercall->getOperation() == ServiceOperation::Logout)) diff --git a/src/RTTComms.cpp b/src/RTTComms.cpp index 1287f51fa..02dd2a985 100644 --- a/src/RTTComms.cpp +++ b/src/RTTComms.cpp @@ -464,7 +464,7 @@ namespace BrainCloud _rttConnectionStatus = BrainCloudRTT::RTTConnectionStatus::Connected; } - _lastHeartbeatTime = TimeUtil::getCurrentTimeMillis(); + _lastHeartbeatTime = TimeUtil::getMonotonicTimeMillis(); if (_loggingEnabled) { @@ -596,7 +596,7 @@ namespace BrainCloud while (isRTTEnabled()) { - int64_t sleepTime = ((int64_t)_heartbeatSeconds * 1000) - (TimeUtil::getCurrentTimeMillis() - _lastHeartbeatTime); + int64_t sleepTime = ((int64_t)_heartbeatSeconds * 1000) - (TimeUtil::getMonotonicTimeMillis() - _lastHeartbeatTime); if (sleepTime > 0) { _heartbeatCondition.wait_for(lock, std::chrono::milliseconds(sleepTime)); @@ -604,7 +604,7 @@ namespace BrainCloud else { send(jsonHeartbeat); - _lastHeartbeatTime = TimeUtil::getCurrentTimeMillis(); + _lastHeartbeatTime = TimeUtil::getMonotonicTimeMillis(); } } diff --git a/src/RelayComms.cpp b/src/RelayComms.cpp index 426b74582..f6d5d343d 100644 --- a/src/RelayComms.cpp +++ b/src/RelayComms.cpp @@ -176,7 +176,7 @@ namespace BrainCloud case eRelayConnectionType::UDP: { m_pSocket = IRelayUDPSocket::create(host, port, MAX_PACKET_SIZE); - m_lastRecvTime = std::chrono::system_clock::now(); + m_lastRecvTime = std::chrono::steady_clock::now(); break; } #if (!defined(TARGET_OS_WATCH) || TARGET_OS_WATCH == 0) @@ -420,7 +420,7 @@ namespace BrainCloud if (reliable) { pPacket->id = packetId; - pPacket->lastResendTime = std::chrono::system_clock::now(); + pPacket->lastResendTime = std::chrono::steady_clock::now(); pPacket->timeSinceFirstSend = pPacket->lastResendTime; pPacket->resendInterval = RELIABLE_RESEND_INTERVALS[(int)channel]; uint64_t ackId = *(uint64_t *)(pPacket->data.data() + 3); @@ -443,7 +443,7 @@ namespace BrainCloud void RelayComms::sendPing() { - m_lastPingTime = std::chrono::system_clock::now(); + m_lastPingTime = std::chrono::steady_clock::now(); uint8_t data[5]; *(uint16_t *)(data) = (uint16_t)htons((u_short)5); @@ -486,7 +486,7 @@ namespace BrainCloud void RelayComms::onRecv(const uint8_t *data, int size) { - m_lastRecvTime = std::chrono::system_clock::now(); + m_lastRecvTime = std::chrono::steady_clock::now(); if (size < 3) { @@ -638,7 +638,7 @@ namespace BrainCloud m_netId = netId; m_ownerCxId = json["ownerCxId"].asString(); m_ownerProfileId = extractProfileIdFromCxId(m_ownerCxId); - m_lastPingTime = std::chrono::system_clock::now(); + m_lastPingTime = std::chrono::steady_clock::now(); m_isConnected = true; m_resendConnectRequest = false; queueConnectSuccessEvent(jsonString); @@ -700,7 +700,7 @@ namespace BrainCloud void RelayComms::onPONG() { - m_ping = (int)std::chrono::duration_cast(std::chrono::system_clock::now() - m_lastPingTime).count(); + m_ping = (int)std::chrono::duration_cast(std::chrono::steady_clock::now() - m_lastPingTime).count(); if (m_ping > 999) m_ping = 999; #if VERBOSE_LOG @@ -916,7 +916,7 @@ namespace BrainCloud void RelayComms::runCallbacks() { - auto now = std::chrono::system_clock::now(); + auto now = std::chrono::steady_clock::now(); // Update socket if (m_pSocket) @@ -970,7 +970,7 @@ namespace BrainCloud for (auto it = m_reliables.begin(); it != m_reliables.end(); ++it) { auto pPacket = it->second; - if (pPacket->timeSinceFirstSend - now > std::chrono::seconds(10)) + if (now - pPacket->timeSinceFirstSend > std::chrono::seconds(10)) { socketCleanup(); queueErrorEvent("Relay disconnected, too many packet lost"); diff --git a/src/TimeUtil.cpp b/src/TimeUtil.cpp index c5c75cadf..9c5bbd7e6 100644 --- a/src/TimeUtil.cpp +++ b/src/TimeUtil.cpp @@ -75,6 +75,7 @@ int gettimeofday(struct timeval *tv, char* NULLPTR) } #include "braincloud/internal/TimeUtil.h" +#include namespace BrainCloud @@ -92,6 +93,11 @@ namespace BrainCloud } + int64_t TimeUtil::getMonotonicTimeMillis() + { + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + } + void TimeUtil::sleepMillis(int millis) { #if __cplusplus >= 201103L && !defined(__ANDROID__) From 9fa2e22ae9ed6fa0a359eb5a0cfea46ddcb89c04 Mon Sep 17 00:00:00 2001 From: pierrepbitheads Date: Thu, 22 Oct 2026 15:02:05 -0400 Subject: [PATCH 08/11] Fixed RelayComms compile errors on newer MSVC versions by adding missing include --- include/braincloud/internal/RelayComms.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/braincloud/internal/RelayComms.h b/include/braincloud/internal/RelayComms.h index a9b5f5343..34e85d620 100644 --- a/include/braincloud/internal/RelayComms.h +++ b/include/braincloud/internal/RelayComms.h @@ -12,6 +12,7 @@ #include "braincloud/internal/IRelaySocket.h" #include +#include #include #include #include From 00612fd5ccaa988fe7208255449cdd4ac5b1f30d Mon Sep 17 00:00:00 2001 From: Pierre Proulx Date: Thu, 22 Oct 2026 17:03:28 -0400 Subject: [PATCH 09/11] BCLOUD-15136 Implement AppStore.GetStoreInfo and AppStore.GetUserToken --- include/braincloud/BrainCloudAppStore.h | 24 ++++++++++++++++++++++++ include/braincloud/ServiceOperation.h | 2 ++ src/BrainCloudAppStore.cpp | 18 ++++++++++++++++++ src/ServiceOperation.cpp | 2 ++ tests/src/TestBCAppStore.cpp | 16 +++++++++++++++- 5 files changed, 61 insertions(+), 1 deletion(-) diff --git a/include/braincloud/BrainCloudAppStore.h b/include/braincloud/BrainCloudAppStore.h index 16f3e8f3c..088478c4a 100644 --- a/include/braincloud/BrainCloudAppStore.h +++ b/include/braincloud/BrainCloudAppStore.h @@ -158,6 +158,30 @@ namespace BrainCloud */ void cachePurchasePayloadContext(const std::string& storeId, const std::string& iapId, const std::string& payload, IServerCallback* callback = NULL); + /** + * Returns the store's client-side configuration for the given store. + * + * Service Name - appStore + * Service Operation - GET_STORE_INFO + * + * @param storeId The store to query. Currently only returns info for "xsolla", all other storeIds will return empty data. + * @param callback The method to be invoked when the server response is received + */ + void getStoreInfo(const std::string& storeId, IServerCallback* callback = NULL); + + /** + * Returns a store user token (JWT) for the current user, generated server-side. + * Currently only Xsolla is supported. The userToken is null if the required + * Xsolla integration settings are missing or the Xsolla call fails. + * + * Service Name - appStore + * Service Operation - GET_USER_TOKEN + * + * @param storeId The store to get the token. Currently only returns info for "xsolla", all other storeIds will return empty data. + * @param callback The method to be invoked when the server response is received + */ + void getUserToken(const std::string& storeId, IServerCallback* callback = NULL); + private: BrainCloudClient * m_client; }; diff --git a/include/braincloud/ServiceOperation.h b/include/braincloud/ServiceOperation.h index a70c787e5..d18a188e6 100644 --- a/include/braincloud/ServiceOperation.h +++ b/include/braincloud/ServiceOperation.h @@ -536,6 +536,8 @@ namespace BrainCloud static const ServiceOperation AppStoreStartPurchase; static const ServiceOperation AppStoreFinalizePurchase; static const ServiceOperation AppStoreRefreshPromotions; + static const ServiceOperation GetStoreInfo; + static const ServiceOperation GetUserToken; // Blockchain static const ServiceOperation GetBlockchainItems; diff --git a/src/BrainCloudAppStore.cpp b/src/BrainCloudAppStore.cpp index 75d7c72ff..86928fef2 100644 --- a/src/BrainCloudAppStore.cpp +++ b/src/BrainCloudAppStore.cpp @@ -100,4 +100,22 @@ namespace BrainCloud ServerCall* sc = new ServerCall(ServiceName::AppStore, ServiceOperation::AppStoreCachePurchaseContext, message, callback); m_client->sendRequest(sc); } + + void BrainCloudAppStore::getStoreInfo(const std::string& storeId, IServerCallback* callback) + { + Json::Value message; + message[OperationParam::AppStoreStoreId.getValue()] = storeId; + + ServerCall* sc = new ServerCall(ServiceName::AppStore, ServiceOperation::GetStoreInfo, message, callback); + m_client->sendRequest(sc); + } + + void BrainCloudAppStore::getUserToken(const std::string& storeId, IServerCallback* callback) + { + Json::Value message; + message[OperationParam::AppStoreStoreId.getValue()] = storeId; + + ServerCall* sc = new ServerCall(ServiceName::AppStore, ServiceOperation::GetUserToken, message, callback); + m_client->sendRequest(sc); + } } diff --git a/src/ServiceOperation.cpp b/src/ServiceOperation.cpp index d8a73c709..e74a98b74 100644 --- a/src/ServiceOperation.cpp +++ b/src/ServiceOperation.cpp @@ -156,6 +156,8 @@ namespace BrainCloud const ServiceOperation ServiceOperation::ConsumeVC = ServiceOperation("CONSUME_VC"); const ServiceOperation ServiceOperation::GetPlayerVC = ServiceOperation("GET_PLAYER_VC"); const ServiceOperation ServiceOperation::ResetPlayerVC = ServiceOperation("RESET_PLAYER_VC"); + const ServiceOperation ServiceOperation::GetStoreInfo = ServiceOperation("GET_STORE_INFO"); + const ServiceOperation ServiceOperation::GetUserToken = ServiceOperation("GET_USER_TOKEN"); const ServiceOperation ServiceOperation::AwardParentVC = ServiceOperation("AWARD_PARENT_VC"); const ServiceOperation ServiceOperation::ConsumeParentVC = ServiceOperation("CONSUME_PARENT_VC"); diff --git a/tests/src/TestBCAppStore.cpp b/tests/src/TestBCAppStore.cpp index 3ce9f36b8..20fcfa384 100644 --- a/tests/src/TestBCAppStore.cpp +++ b/tests/src/TestBCAppStore.cpp @@ -61,4 +61,18 @@ TEST_F(TestBCAppStore, cachePurchasePayloadContext) TestResult tr; m_bc->getAppStoreService()->cachePurchasePayloadContext("_invalid_store_id_", "_invalid_transaction_id_", "{}", &tr); tr.runExpectFail(m_bc, HTTP_BAD_REQUEST, INVALID_STORE_ID); -} \ No newline at end of file +} + +TEST_F(TestBCAppStore, GetStoreInfo) +{ + TestResult tr; + m_bc->getAppStoreService()->getStoreInfo("_invalid_store_id_", &tr); + tr.runExpectFail(m_bc, HTTP_BAD_REQUEST, INVALID_STORE_ID); +} + +TEST_F(TestBCAppStore, GetUserToken) +{ + TestResult tr; + m_bc->getAppStoreService()->getUserToken("_invalid_store_id_", &tr); + tr.runExpectFail(m_bc, HTTP_BAD_REQUEST, INVALID_STORE_ID); +} From 5f41d4b2dc56d880e28c29220cd8299887a75215 Mon Sep 17 00:00:00 2001 From: Pierre Proulx Date: Fri, 23 Oct 2026 09:44:24 -0400 Subject: [PATCH 10/11] Added 2 more successful tests with "xsolla". --- tests/src/TestBCAppStore.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/src/TestBCAppStore.cpp b/tests/src/TestBCAppStore.cpp index 20fcfa384..f134b42ce 100644 --- a/tests/src/TestBCAppStore.cpp +++ b/tests/src/TestBCAppStore.cpp @@ -64,6 +64,13 @@ TEST_F(TestBCAppStore, cachePurchasePayloadContext) } TEST_F(TestBCAppStore, GetStoreInfo) +{ + TestResult tr; + m_bc->getAppStoreService()->getStoreInfo("xsolla", &tr); + tr.run(m_bc); +} + +TEST_F(TestBCAppStore, GetStoreInfoFail) { TestResult tr; m_bc->getAppStoreService()->getStoreInfo("_invalid_store_id_", &tr); @@ -71,6 +78,13 @@ TEST_F(TestBCAppStore, GetStoreInfo) } TEST_F(TestBCAppStore, GetUserToken) +{ + TestResult tr; + m_bc->getAppStoreService()->getUserToken("xsolla", &tr); + tr.run(m_bc); +} + +TEST_F(TestBCAppStore, GetUserTokenFail) { TestResult tr; m_bc->getAppStoreService()->getUserToken("_invalid_store_id_", &tr); From c33173881ad66eeaccd4e0bc5f37e17e5d4c42ac Mon Sep 17 00:00:00 2001 From: Nick H Date: Fri, 23 Oct 2026 13:00:41 -0400 Subject: [PATCH 11/11] Updated version to 6.1.0 --- BrainCloudCpp.podspec | 2 +- src/BrainCloudClient.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BrainCloudCpp.podspec b/BrainCloudCpp.podspec index 703ce205b..783091a47 100644 --- a/BrainCloudCpp.podspec +++ b/BrainCloudCpp.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| s.name = "BrainCloudCpp" - s.version = "6.0.1" + s.version = "6.1.0" s.summary = "The C++ client library for brainCloud." s.homepage = "http://getbraincloud.com/" diff --git a/src/BrainCloudClient.cpp b/src/BrainCloudClient.cpp index 446ef828a..f5d35de66 100644 --- a/src/BrainCloudClient.cpp +++ b/src/BrainCloudClient.cpp @@ -27,7 +27,7 @@ namespace BrainCloud "Singleton usage is disabled. If called by mistake, use your own variable that holds an instance of the bcWrapper/bcClient."; BrainCloudClient * BrainCloudClient::_instance = NULL; - std::string BrainCloudClient::s_brainCloudClientVersion = "6.0.0"; + std::string BrainCloudClient::s_brainCloudClientVersion = "6.1.0"; const char* BC_SERVER_URL = "https://api.braincloudservers.com/dispatcherv2"; /**