From 9a2f8bdf2700b0252d049f946f97defa0199f899 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 23 Sep 2025 16:40:03 +0200 Subject: [PATCH 1/7] add `InvalidResponseAPIError` and `InvalidResponseAPICode` --- .../sourcepoint/mobile_core/models/SPError.kt | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/models/SPError.kt b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/models/SPError.kt index b929aa82..b2214e6f 100644 --- a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/models/SPError.kt +++ b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/models/SPError.kt @@ -3,9 +3,15 @@ package com.sourcepoint.mobile_core.models open class SPError( val code: String = "sp_metric_generic_mobile-core_error", val description: String = "Something went wrong in the Mobile Core", - val causedBy: SPError? = null, + val causedBy: Exception? = null, open val campaignType: SPCampaignType? = null -): Exception(description) +): Exception(description) { + companion object { + fun castToSPError(error: Exception): SPError = + error as? SPError ?: + SPError(causedBy = error, description = error.message ?: "Something went wrong in the Mobile Core") + } +} open class SPUnknownNetworkError(path: String): SPError( code = "sp_metric_unknown_network_error_${path}", @@ -76,3 +82,27 @@ open class DeleteCustomConsentGDPRException(causedBy: SPError): SPError( description = "Unable to delete CustomConsentGDPR due to ${causedBy.description}", causedBy = causedBy ) + +open class InvalidResponseAPIError(causedBy: Exception, endpoint: InvalidResponseAPICode): SPError ( + code = "sp_metric_invalid_response_api${endpoint.type}", + causedBy = castToSPError(causedBy) +) + +enum class InvalidResponseAPICode(val type: String) { + META_DATA("_meta-data"), + CONSENT_STATUS("_consent-status"), + PV_DATA("_pv-data"), + MESSAGES("_messages"), + ERROR_METRICS("_error-metrics"), + CCPA_ACTION("_CCPA-action"), + GDPR_ACTION("_GDPR-action"), + USNAT_ACTION("_USNAT-action"), + IDFA_STATUS( "_IDFA-status"), + CCPA_PRIVACY_MANAGER("_CCPA-privacy-manager"), + CHOICE_ALL("_choice-all"), + GDPR_PRIVACY_MANAGER("_GDPR-privacy-manager"), + CCPA_MESSAGE("_CCPA-message"), + GDPR_MESSAGE("_GDPR-message"), + DELETE_CUSTOM_CONSENT("_delete-custom-consent-GDPR"), + EMPTY("") +} From bae36bc152003a7060a19ee6b17590cde76548e5 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 23 Sep 2025 16:43:12 +0200 Subject: [PATCH 2/7] add `try catch` to `metaData`, `consentStatus`, `messages`, `pvData` in coordinator --- .../sourcepoint/mobile_core/Coordinator.kt | 250 ++++++++++-------- 1 file changed, 139 insertions(+), 111 deletions(-) diff --git a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt index 2c40b002..fdacde93 100644 --- a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt +++ b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt @@ -2,6 +2,8 @@ package com.sourcepoint.mobile_core import com.sourcepoint.mobile_core.models.DeleteCustomConsentGDPRException import com.sourcepoint.mobile_core.models.InvalidCustomConsentUUIDError +import com.sourcepoint.mobile_core.models.InvalidResponseAPICode +import com.sourcepoint.mobile_core.models.InvalidResponseAPIError import com.sourcepoint.mobile_core.models.LoadMessagesException import com.sourcepoint.mobile_core.models.MessageToDisplay import com.sourcepoint.mobile_core.models.PostCustomConsentGDPRException @@ -294,13 +296,19 @@ class Coordinator( } private suspend fun metaData(next: suspend () -> Unit) { - handleMetaDataResponse(spClient.getMetaData(MetaDataRequest.Campaigns( - gdpr = campaigns.gdpr?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, - ccpa = campaigns.ccpa?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, - usnat = campaigns.usnat?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, - globalcmp = campaigns.globalcmp?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, - preferences = campaigns.preferences?.let { MetaDataRequest.Campaigns.Campaign() } - ))) + try { + handleMetaDataResponse( + spClient.getMetaData( + MetaDataRequest.Campaigns( + gdpr = campaigns.gdpr?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, + ccpa = campaigns.ccpa?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, + usnat = campaigns.usnat?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, + globalcmp = campaigns.globalcmp?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, + preferences = campaigns.preferences?.let { MetaDataRequest.Campaigns.Campaign() } + ))) + } catch (error: Exception) { + throw InvalidResponseAPIError(causedBy = error, endpoint = InvalidResponseAPICode.META_DATA) + } next() } @@ -384,49 +392,53 @@ class Coordinator( suspend fun consentStatus(next: suspend () -> Unit) { if (shouldCallConsentStatus) { - handleConsentStatusResponse(spClient.getConsentStatus( - authId = authId, - metadata = ConsentStatusRequest.MetaData( - gdpr = campaigns.gdpr?.let { - ConsentStatusRequest.MetaData.Campaign( - applies = state.gdpr.consents.applies, - dateCreated = state.gdpr.consents.dateCreated, - uuid = state.gdpr.consents.uuid, - idfaStatus = idfaStatus - ) - }, - globalcmp = campaigns.globalcmp?.let { - ConsentStatusRequest.MetaData.GlobalCmpCampaign( - applies = state.globalcmp.consents.applies, - dateCreated = state.globalcmp.consents.dateCreated, - uuid = state.globalcmp.consents.uuid - ) - }, - usnat = campaigns.usnat?.let { - ConsentStatusRequest.MetaData.USNatCampaign( - applies = state.usNat.consents.applies, - dateCreated = state.usNat.consents.dateCreated, - uuid = state.usNat.consents.uuid, - idfaStatus = idfaStatus, - transitionCCPAAuth = authTransitionCCPAUSNat, - optedOut = transitionCCPAOptedOut - ) - }, - ccpa = campaigns.ccpa?.let { - ConsentStatusRequest.MetaData.Campaign( - applies = state.ccpa.consents.applies, - dateCreated = state.ccpa.consents.dateCreated, - uuid = state.ccpa.consents.uuid, - idfaStatus = idfaStatus - ) - }, - preferences = campaigns.preferences?.let { - ConsentStatusRequest.MetaData.PreferencesCampaign( - configurationId = state.preferences.metaData.configurationId - ) - } - ) - )) + try { + handleConsentStatusResponse(spClient.getConsentStatus( + authId = authId, + metadata = ConsentStatusRequest.MetaData( + gdpr = campaigns.gdpr?.let { + ConsentStatusRequest.MetaData.Campaign( + applies = state.gdpr.consents.applies, + dateCreated = state.gdpr.consents.dateCreated, + uuid = state.gdpr.consents.uuid, + idfaStatus = idfaStatus + ) + }, + globalcmp = campaigns.globalcmp?.let { + ConsentStatusRequest.MetaData.GlobalCmpCampaign( + applies = state.globalcmp.consents.applies, + dateCreated = state.globalcmp.consents.dateCreated, + uuid = state.globalcmp.consents.uuid + ) + }, + usnat = campaigns.usnat?.let { + ConsentStatusRequest.MetaData.USNatCampaign( + applies = state.usNat.consents.applies, + dateCreated = state.usNat.consents.dateCreated, + uuid = state.usNat.consents.uuid, + idfaStatus = idfaStatus, + transitionCCPAAuth = authTransitionCCPAUSNat, + optedOut = transitionCCPAOptedOut + ) + }, + ccpa = campaigns.ccpa?.let { + ConsentStatusRequest.MetaData.Campaign( + applies = state.ccpa.consents.applies, + dateCreated = state.ccpa.consents.dateCreated, + uuid = state.ccpa.consents.uuid, + idfaStatus = idfaStatus + ) + }, + preferences = campaigns.preferences?.let { + ConsentStatusRequest.MetaData.PreferencesCampaign( + configurationId = state.preferences.metaData.configurationId + ) + } + ) + )) + } catch (error: Exception) { + throw InvalidResponseAPIError(causedBy = error, endpoint = InvalidResponseAPICode.CONSENT_STATUS) + } } next() } @@ -474,66 +486,74 @@ class Coordinator( private suspend fun messages(language: SPMessageLanguage): List = if (shouldCallMessages) { - handleMessagesResponse(spClient.getMessages(MessagesRequest( - body = MessagesRequest.Body( - propertyHref = propertyName, - accountId = accountId, - campaigns = MessagesRequest.Body.Campaigns( - gdpr = campaigns.gdpr?.let { - MessagesRequest.Body.Campaigns.Campaign( - targetingParams = it.targetingParams, - hasLocalData = state.hasGDPRLocalData, - consentStatus = state.gdpr.consents.consentStatus - ) - }, - ios14 = campaigns.ios14?.let { - MessagesRequest.Body.Campaigns.IOS14Campaign( - targetingParams = it.targetingParams, - idfaStatus = idfaStatus - ) - }, - globalcmp = campaigns.globalcmp?.let { - MessagesRequest.Body.Campaigns.Campaign( - targetingParams = it.targetingParams, - hasLocalData = state.hasGlobalCmpLocalData, - consentStatus = state.globalcmp.consents.consentStatus - ) - }, - ccpa = campaigns.ccpa?.let { - MessagesRequest.Body.Campaigns.CCPACampaign( - targetingParams = it.targetingParams, - hasLocalData = state.hasCCPALocalData, - status = state.ccpa.consents.status - ) - }, - usnat = campaigns.usnat?.let { - MessagesRequest.Body.Campaigns.Campaign( - targetingParams = it.targetingParams, - hasLocalData = state.hasUSNatLocalData, - consentStatus = state.usNat.consents.consentStatus - ) - }, - preferences = campaigns.preferences?.let { - MessagesRequest.Body.Campaigns.Campaign( - targetingParams = it.targetingParams + preferencesTargetingParams(state.preferences), - hasLocalData = state.hasPreferencesLocalData - ) - } + try { + handleMessagesResponse( + spClient.getMessages( + MessagesRequest( + body = MessagesRequest.Body( + propertyHref = propertyName, + accountId = accountId, + campaigns = MessagesRequest.Body.Campaigns( + gdpr = campaigns.gdpr?.let { + MessagesRequest.Body.Campaigns.Campaign( + targetingParams = it.targetingParams, + hasLocalData = state.hasGDPRLocalData, + consentStatus = state.gdpr.consents.consentStatus + ) + }, + ios14 = campaigns.ios14?.let { + MessagesRequest.Body.Campaigns.IOS14Campaign( + targetingParams = it.targetingParams, + idfaStatus = idfaStatus + ) + }, + globalcmp = campaigns.globalcmp?.let { + MessagesRequest.Body.Campaigns.Campaign( + targetingParams = it.targetingParams, + hasLocalData = state.hasGlobalCmpLocalData, + consentStatus = state.globalcmp.consents.consentStatus + ) + }, + ccpa = campaigns.ccpa?.let { + MessagesRequest.Body.Campaigns.CCPACampaign( + targetingParams = it.targetingParams, + hasLocalData = state.hasCCPALocalData, + status = state.ccpa.consents.status + ) + }, + usnat = campaigns.usnat?.let { + MessagesRequest.Body.Campaigns.Campaign( + targetingParams = it.targetingParams, + hasLocalData = state.hasUSNatLocalData, + consentStatus = state.usNat.consents.consentStatus + ) + }, + preferences = campaigns.preferences?.let { + MessagesRequest.Body.Campaigns.Campaign( + targetingParams = it.targetingParams + preferencesTargetingParams( + state.preferences + ), + hasLocalData = state.hasPreferencesLocalData + ) + } + ), + consentLanguage = language, + campaignEnv = campaigns.environment, + idfaStatus = idfaStatus, + includeData = includeData ), - consentLanguage = language, - campaignEnv = campaigns.environment, - idfaStatus = idfaStatus, - includeData = includeData - ), - metadata = MessagesRequest.MetaData( - gdpr = MessagesRequest.MetaData.Campaign(state.gdpr.consents.applies), - usnat = MessagesRequest.MetaData.Campaign(state.usNat.consents.applies), - ccpa = MessagesRequest.MetaData.Campaign(state.ccpa.consents.applies), - globalcmp = MessagesRequest.MetaData.Campaign(state.globalcmp.consents.applies) - ), - nonKeyedLocalState = state.nonKeyedLocalState, - localState = state.localState - ))) + metadata = MessagesRequest.MetaData( + gdpr = MessagesRequest.MetaData.Campaign(state.gdpr.consents.applies), + usnat = MessagesRequest.MetaData.Campaign(state.usNat.consents.applies), + ccpa = MessagesRequest.MetaData.Campaign(state.ccpa.consents.applies), + globalcmp = MessagesRequest.MetaData.Campaign(state.globalcmp.consents.applies) + ), + nonKeyedLocalState = state.nonKeyedLocalState, + localState = state.localState + ))) + } catch (error: Exception) { + throw InvalidResponseAPIError(causedBy = error, endpoint = InvalidResponseAPICode.MESSAGES) + } } else { emptyList() } @@ -562,13 +582,21 @@ class Coordinator( private suspend fun sampleAndPvData(campaign: State.SPSampleable, request: PvDataRequest) = if (campaign.wasSampled == null) { if (sample(campaign.sampleRate)) { - handlePvDataResponse(spClient.postPvData(request)) + try { + handlePvDataResponse(spClient.postPvData(request)) + } catch (error: Exception) { + throw InvalidResponseAPIError(causedBy = error, endpoint = InvalidResponseAPICode.PV_DATA) + } true } else { false } } else if (campaign.wasSampled == true) { - handlePvDataResponse(spClient.postPvData(request)) + try { + handlePvDataResponse(spClient.postPvData(request)) + } catch (error: Exception) { + throw InvalidResponseAPIError(causedBy = error, endpoint = InvalidResponseAPICode.PV_DATA) + } true } else { false From 19d21927d0bbfb35a2aa71275ab024b1b2a5bab5 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 24 Sep 2025 15:09:57 +0200 Subject: [PATCH 3/7] refactor `try catch` --- .../sourcepoint/mobile_core/Coordinator.kt | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt index fdacde93..f96d0eec 100644 --- a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt +++ b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt @@ -3,6 +3,10 @@ package com.sourcepoint.mobile_core import com.sourcepoint.mobile_core.models.DeleteCustomConsentGDPRException import com.sourcepoint.mobile_core.models.InvalidCustomConsentUUIDError import com.sourcepoint.mobile_core.models.InvalidResponseAPICode +import com.sourcepoint.mobile_core.models.InvalidResponseAPICode.META_DATA +import com.sourcepoint.mobile_core.models.InvalidResponseAPICode.CONSENT_STATUS +import com.sourcepoint.mobile_core.models.InvalidResponseAPICode.MESSAGES +import com.sourcepoint.mobile_core.models.InvalidResponseAPICode.PV_DATA import com.sourcepoint.mobile_core.models.InvalidResponseAPIError import com.sourcepoint.mobile_core.models.LoadMessagesException import com.sourcepoint.mobile_core.models.MessageToDisplay @@ -230,6 +234,9 @@ class Coordinator( return messages } + private suspend fun executeLoadMessagesAPIRequest(endpoint: InvalidResponseAPICode, command: suspend () -> T): T = + try { command() } catch (error: Exception) { throw InvalidResponseAPIError(causedBy = error, endpoint = endpoint) } + private fun handleMetaDataResponse(response: MetaDataResponse) { response.gdpr?.let { state.gdpr = state.gdpr.resetStateIfVendorListChanges(it.vendorListId) @@ -296,18 +303,14 @@ class Coordinator( } private suspend fun metaData(next: suspend () -> Unit) { - try { - handleMetaDataResponse( - spClient.getMetaData( - MetaDataRequest.Campaigns( + executeLoadMessagesAPIRequest(META_DATA) { + handleMetaDataResponse(spClient.getMetaData(MetaDataRequest.Campaigns( gdpr = campaigns.gdpr?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, ccpa = campaigns.ccpa?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, usnat = campaigns.usnat?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, globalcmp = campaigns.globalcmp?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, preferences = campaigns.preferences?.let { MetaDataRequest.Campaigns.Campaign() } ))) - } catch (error: Exception) { - throw InvalidResponseAPIError(causedBy = error, endpoint = InvalidResponseAPICode.META_DATA) } next() } @@ -392,7 +395,7 @@ class Coordinator( suspend fun consentStatus(next: suspend () -> Unit) { if (shouldCallConsentStatus) { - try { + executeLoadMessagesAPIRequest(CONSENT_STATUS) { handleConsentStatusResponse(spClient.getConsentStatus( authId = authId, metadata = ConsentStatusRequest.MetaData( @@ -436,8 +439,6 @@ class Coordinator( } ) )) - } catch (error: Exception) { - throw InvalidResponseAPIError(causedBy = error, endpoint = InvalidResponseAPICode.CONSENT_STATUS) } } next() @@ -486,7 +487,7 @@ class Coordinator( private suspend fun messages(language: SPMessageLanguage): List = if (shouldCallMessages) { - try { + executeLoadMessagesAPIRequest(MESSAGES) { handleMessagesResponse( spClient.getMessages( MessagesRequest( @@ -551,8 +552,6 @@ class Coordinator( nonKeyedLocalState = state.nonKeyedLocalState, localState = state.localState ))) - } catch (error: Exception) { - throw InvalidResponseAPIError(causedBy = error, endpoint = InvalidResponseAPICode.MESSAGES) } } else { emptyList() @@ -582,21 +581,13 @@ class Coordinator( private suspend fun sampleAndPvData(campaign: State.SPSampleable, request: PvDataRequest) = if (campaign.wasSampled == null) { if (sample(campaign.sampleRate)) { - try { - handlePvDataResponse(spClient.postPvData(request)) - } catch (error: Exception) { - throw InvalidResponseAPIError(causedBy = error, endpoint = InvalidResponseAPICode.PV_DATA) - } + executeLoadMessagesAPIRequest(PV_DATA) { handlePvDataResponse(spClient.postPvData(request)) } true } else { false } } else if (campaign.wasSampled == true) { - try { - handlePvDataResponse(spClient.postPvData(request)) - } catch (error: Exception) { - throw InvalidResponseAPIError(causedBy = error, endpoint = InvalidResponseAPICode.PV_DATA) - } + executeLoadMessagesAPIRequest(PV_DATA) { handlePvDataResponse(spClient.postPvData(request)) } true } else { false From 17dce48334917daf7b496bd05ed10e5d4a9c7092 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 24 Sep 2025 15:38:17 +0200 Subject: [PATCH 4/7] change `SPError` parent class to `Throwable` --- .../sourcepoint/mobile_core/Coordinator.kt | 10 +++--- .../sourcepoint/mobile_core/models/SPError.kt | 35 ++++++++++--------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt index f96d0eec..ab05f7f3 100644 --- a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt +++ b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt @@ -227,7 +227,7 @@ class Coordinator( } } } catch (error: SPError) { - throw LoadMessagesException(causedBy = error) + throw LoadMessagesException(cause = error) } storeLegislationConsent(userData = userData) persistState() @@ -235,7 +235,7 @@ class Coordinator( } private suspend fun executeLoadMessagesAPIRequest(endpoint: InvalidResponseAPICode, command: suspend () -> T): T = - try { command() } catch (error: Exception) { throw InvalidResponseAPIError(causedBy = error, endpoint = endpoint) } + try { command() } catch (error: Exception) { throw InvalidResponseAPIError(cause = error, endpoint = endpoint) } private fun handleMetaDataResponse(response: MetaDataResponse) { response.gdpr?.let { @@ -1026,7 +1026,7 @@ class Coordinator( IOS14, SPCampaignType.Unknown -> {} } } catch (error: SPError) { - throw ReportActionException(causedBy = error, actionType = action.type, campaignType = action.campaignType) + throw ReportActionException(cause = error, actionType = action.type, campaignType = action.campaignType) } finally { storeLegislationConsent(userData = userData) persistState() @@ -1063,7 +1063,7 @@ class Coordinator( legIntCategories = legIntCategories )) } catch (error: SPError) { - throw PostCustomConsentGDPRException(causedBy = error) + throw PostCustomConsentGDPRException(cause = error) } } @@ -1084,7 +1084,7 @@ class Coordinator( legIntCategories = legIntCategories )) } catch (error: SPError) { - throw DeleteCustomConsentGDPRException(causedBy = error) + throw DeleteCustomConsentGDPRException(cause = error) } } diff --git a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/models/SPError.kt b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/models/SPError.kt index b2214e6f..b07b336a 100644 --- a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/models/SPError.kt +++ b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/models/SPError.kt @@ -3,13 +3,13 @@ package com.sourcepoint.mobile_core.models open class SPError( val code: String = "sp_metric_generic_mobile-core_error", val description: String = "Something went wrong in the Mobile Core", - val causedBy: Exception? = null, + override val cause: Throwable? = null, open val campaignType: SPCampaignType? = null -): Exception(description) { +): Throwable(description) { companion object { fun castToSPError(error: Exception): SPError = error as? SPError ?: - SPError(causedBy = error, description = error.message ?: "Something went wrong in the Mobile Core") + SPError(cause = error, description = error.message ?: "Something went wrong in the Mobile Core") } } @@ -58,34 +58,35 @@ open class InvalidPropertyNameError(propertyName: String): SPError( open class ReportActionException( actionType: SPActionType, campaignType: SPCampaignType?, - causedBy: SPError + cause: SPError ): SPError( code = "sp_metric_report_action_exception_${campaignType?.name}_${actionType.name}", - description = "Unable to report ${actionType.name} action for campaign ${campaignType?.name} due to ${causedBy.description}", - causedBy = causedBy + description = "Unable to report ${actionType.name} action for campaign ${campaignType?.name} due to ${cause.description}", + cause = cause ) -open class LoadMessagesException(causedBy: SPError): SPError( +open class LoadMessagesException(cause: SPError): SPError( code = "sp_metric_load_messages", - description = "Unable to loadMessages due to ${causedBy.description}", - causedBy = causedBy + description = "Unable to loadMessages due to ${cause.description}", + cause = cause ) -open class PostCustomConsentGDPRException(causedBy: SPError): SPError( +open class PostCustomConsentGDPRException(cause: SPError): SPError( code = "sp_metric_post_custom_consent_gdpr", - description = "Unable to post CustomConsentGDPR due to ${causedBy.description}", - causedBy = causedBy + description = "Unable to post CustomConsentGDPR due to ${cause.description}", + cause = cause ) -open class DeleteCustomConsentGDPRException(causedBy: SPError): SPError( +open class DeleteCustomConsentGDPRException(cause: SPError): SPError( code = "sp_metric_delete_custom_consent_gdpr", - description = "Unable to delete CustomConsentGDPR due to ${causedBy.description}", - causedBy = causedBy + description = "Unable to delete CustomConsentGDPR due to ${cause.description}", + cause = cause ) -open class InvalidResponseAPIError(causedBy: Exception, endpoint: InvalidResponseAPICode): SPError ( +open class InvalidResponseAPIError(cause: Exception, endpoint: InvalidResponseAPICode): SPError ( code = "sp_metric_invalid_response_api${endpoint.type}", - causedBy = castToSPError(causedBy) + description = "The SDK got an unexpected response from ${endpoint.name}", + cause = castToSPError(cause) ) enum class InvalidResponseAPICode(val type: String) { From b8b8f763d70eaf5c678cbc8c3c2a2a729466388b Mon Sep 17 00:00:00 2001 From: Dmytro Date: Fri, 26 Sep 2025 14:42:29 +0200 Subject: [PATCH 5/7] revert `coordinator` --- .../sourcepoint/mobile_core/Coordinator.kt | 249 ++++++++---------- 1 file changed, 115 insertions(+), 134 deletions(-) diff --git a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt index ab05f7f3..2c40b002 100644 --- a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt +++ b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt @@ -2,12 +2,6 @@ package com.sourcepoint.mobile_core import com.sourcepoint.mobile_core.models.DeleteCustomConsentGDPRException import com.sourcepoint.mobile_core.models.InvalidCustomConsentUUIDError -import com.sourcepoint.mobile_core.models.InvalidResponseAPICode -import com.sourcepoint.mobile_core.models.InvalidResponseAPICode.META_DATA -import com.sourcepoint.mobile_core.models.InvalidResponseAPICode.CONSENT_STATUS -import com.sourcepoint.mobile_core.models.InvalidResponseAPICode.MESSAGES -import com.sourcepoint.mobile_core.models.InvalidResponseAPICode.PV_DATA -import com.sourcepoint.mobile_core.models.InvalidResponseAPIError import com.sourcepoint.mobile_core.models.LoadMessagesException import com.sourcepoint.mobile_core.models.MessageToDisplay import com.sourcepoint.mobile_core.models.PostCustomConsentGDPRException @@ -227,16 +221,13 @@ class Coordinator( } } } catch (error: SPError) { - throw LoadMessagesException(cause = error) + throw LoadMessagesException(causedBy = error) } storeLegislationConsent(userData = userData) persistState() return messages } - private suspend fun executeLoadMessagesAPIRequest(endpoint: InvalidResponseAPICode, command: suspend () -> T): T = - try { command() } catch (error: Exception) { throw InvalidResponseAPIError(cause = error, endpoint = endpoint) } - private fun handleMetaDataResponse(response: MetaDataResponse) { response.gdpr?.let { state.gdpr = state.gdpr.resetStateIfVendorListChanges(it.vendorListId) @@ -303,15 +294,13 @@ class Coordinator( } private suspend fun metaData(next: suspend () -> Unit) { - executeLoadMessagesAPIRequest(META_DATA) { - handleMetaDataResponse(spClient.getMetaData(MetaDataRequest.Campaigns( - gdpr = campaigns.gdpr?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, - ccpa = campaigns.ccpa?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, - usnat = campaigns.usnat?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, - globalcmp = campaigns.globalcmp?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, - preferences = campaigns.preferences?.let { MetaDataRequest.Campaigns.Campaign() } - ))) - } + handleMetaDataResponse(spClient.getMetaData(MetaDataRequest.Campaigns( + gdpr = campaigns.gdpr?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, + ccpa = campaigns.ccpa?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, + usnat = campaigns.usnat?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, + globalcmp = campaigns.globalcmp?.let { MetaDataRequest.Campaigns.Campaign(it.groupPmId) }, + preferences = campaigns.preferences?.let { MetaDataRequest.Campaigns.Campaign() } + ))) next() } @@ -395,51 +384,49 @@ class Coordinator( suspend fun consentStatus(next: suspend () -> Unit) { if (shouldCallConsentStatus) { - executeLoadMessagesAPIRequest(CONSENT_STATUS) { - handleConsentStatusResponse(spClient.getConsentStatus( - authId = authId, - metadata = ConsentStatusRequest.MetaData( - gdpr = campaigns.gdpr?.let { - ConsentStatusRequest.MetaData.Campaign( - applies = state.gdpr.consents.applies, - dateCreated = state.gdpr.consents.dateCreated, - uuid = state.gdpr.consents.uuid, - idfaStatus = idfaStatus - ) - }, - globalcmp = campaigns.globalcmp?.let { - ConsentStatusRequest.MetaData.GlobalCmpCampaign( - applies = state.globalcmp.consents.applies, - dateCreated = state.globalcmp.consents.dateCreated, - uuid = state.globalcmp.consents.uuid - ) - }, - usnat = campaigns.usnat?.let { - ConsentStatusRequest.MetaData.USNatCampaign( - applies = state.usNat.consents.applies, - dateCreated = state.usNat.consents.dateCreated, - uuid = state.usNat.consents.uuid, - idfaStatus = idfaStatus, - transitionCCPAAuth = authTransitionCCPAUSNat, - optedOut = transitionCCPAOptedOut - ) - }, - ccpa = campaigns.ccpa?.let { - ConsentStatusRequest.MetaData.Campaign( - applies = state.ccpa.consents.applies, - dateCreated = state.ccpa.consents.dateCreated, - uuid = state.ccpa.consents.uuid, - idfaStatus = idfaStatus - ) - }, - preferences = campaigns.preferences?.let { - ConsentStatusRequest.MetaData.PreferencesCampaign( - configurationId = state.preferences.metaData.configurationId - ) - } - ) - )) - } + handleConsentStatusResponse(spClient.getConsentStatus( + authId = authId, + metadata = ConsentStatusRequest.MetaData( + gdpr = campaigns.gdpr?.let { + ConsentStatusRequest.MetaData.Campaign( + applies = state.gdpr.consents.applies, + dateCreated = state.gdpr.consents.dateCreated, + uuid = state.gdpr.consents.uuid, + idfaStatus = idfaStatus + ) + }, + globalcmp = campaigns.globalcmp?.let { + ConsentStatusRequest.MetaData.GlobalCmpCampaign( + applies = state.globalcmp.consents.applies, + dateCreated = state.globalcmp.consents.dateCreated, + uuid = state.globalcmp.consents.uuid + ) + }, + usnat = campaigns.usnat?.let { + ConsentStatusRequest.MetaData.USNatCampaign( + applies = state.usNat.consents.applies, + dateCreated = state.usNat.consents.dateCreated, + uuid = state.usNat.consents.uuid, + idfaStatus = idfaStatus, + transitionCCPAAuth = authTransitionCCPAUSNat, + optedOut = transitionCCPAOptedOut + ) + }, + ccpa = campaigns.ccpa?.let { + ConsentStatusRequest.MetaData.Campaign( + applies = state.ccpa.consents.applies, + dateCreated = state.ccpa.consents.dateCreated, + uuid = state.ccpa.consents.uuid, + idfaStatus = idfaStatus + ) + }, + preferences = campaigns.preferences?.let { + ConsentStatusRequest.MetaData.PreferencesCampaign( + configurationId = state.preferences.metaData.configurationId + ) + } + ) + )) } next() } @@ -487,72 +474,66 @@ class Coordinator( private suspend fun messages(language: SPMessageLanguage): List = if (shouldCallMessages) { - executeLoadMessagesAPIRequest(MESSAGES) { - handleMessagesResponse( - spClient.getMessages( - MessagesRequest( - body = MessagesRequest.Body( - propertyHref = propertyName, - accountId = accountId, - campaigns = MessagesRequest.Body.Campaigns( - gdpr = campaigns.gdpr?.let { - MessagesRequest.Body.Campaigns.Campaign( - targetingParams = it.targetingParams, - hasLocalData = state.hasGDPRLocalData, - consentStatus = state.gdpr.consents.consentStatus - ) - }, - ios14 = campaigns.ios14?.let { - MessagesRequest.Body.Campaigns.IOS14Campaign( - targetingParams = it.targetingParams, - idfaStatus = idfaStatus - ) - }, - globalcmp = campaigns.globalcmp?.let { - MessagesRequest.Body.Campaigns.Campaign( - targetingParams = it.targetingParams, - hasLocalData = state.hasGlobalCmpLocalData, - consentStatus = state.globalcmp.consents.consentStatus - ) - }, - ccpa = campaigns.ccpa?.let { - MessagesRequest.Body.Campaigns.CCPACampaign( - targetingParams = it.targetingParams, - hasLocalData = state.hasCCPALocalData, - status = state.ccpa.consents.status - ) - }, - usnat = campaigns.usnat?.let { - MessagesRequest.Body.Campaigns.Campaign( - targetingParams = it.targetingParams, - hasLocalData = state.hasUSNatLocalData, - consentStatus = state.usNat.consents.consentStatus - ) - }, - preferences = campaigns.preferences?.let { - MessagesRequest.Body.Campaigns.Campaign( - targetingParams = it.targetingParams + preferencesTargetingParams( - state.preferences - ), - hasLocalData = state.hasPreferencesLocalData - ) - } - ), - consentLanguage = language, - campaignEnv = campaigns.environment, - idfaStatus = idfaStatus, - includeData = includeData - ), - metadata = MessagesRequest.MetaData( - gdpr = MessagesRequest.MetaData.Campaign(state.gdpr.consents.applies), - usnat = MessagesRequest.MetaData.Campaign(state.usNat.consents.applies), - ccpa = MessagesRequest.MetaData.Campaign(state.ccpa.consents.applies), - globalcmp = MessagesRequest.MetaData.Campaign(state.globalcmp.consents.applies) + handleMessagesResponse(spClient.getMessages(MessagesRequest( + body = MessagesRequest.Body( + propertyHref = propertyName, + accountId = accountId, + campaigns = MessagesRequest.Body.Campaigns( + gdpr = campaigns.gdpr?.let { + MessagesRequest.Body.Campaigns.Campaign( + targetingParams = it.targetingParams, + hasLocalData = state.hasGDPRLocalData, + consentStatus = state.gdpr.consents.consentStatus + ) + }, + ios14 = campaigns.ios14?.let { + MessagesRequest.Body.Campaigns.IOS14Campaign( + targetingParams = it.targetingParams, + idfaStatus = idfaStatus + ) + }, + globalcmp = campaigns.globalcmp?.let { + MessagesRequest.Body.Campaigns.Campaign( + targetingParams = it.targetingParams, + hasLocalData = state.hasGlobalCmpLocalData, + consentStatus = state.globalcmp.consents.consentStatus + ) + }, + ccpa = campaigns.ccpa?.let { + MessagesRequest.Body.Campaigns.CCPACampaign( + targetingParams = it.targetingParams, + hasLocalData = state.hasCCPALocalData, + status = state.ccpa.consents.status + ) + }, + usnat = campaigns.usnat?.let { + MessagesRequest.Body.Campaigns.Campaign( + targetingParams = it.targetingParams, + hasLocalData = state.hasUSNatLocalData, + consentStatus = state.usNat.consents.consentStatus + ) + }, + preferences = campaigns.preferences?.let { + MessagesRequest.Body.Campaigns.Campaign( + targetingParams = it.targetingParams + preferencesTargetingParams(state.preferences), + hasLocalData = state.hasPreferencesLocalData + ) + } ), - nonKeyedLocalState = state.nonKeyedLocalState, - localState = state.localState - ))) - } + consentLanguage = language, + campaignEnv = campaigns.environment, + idfaStatus = idfaStatus, + includeData = includeData + ), + metadata = MessagesRequest.MetaData( + gdpr = MessagesRequest.MetaData.Campaign(state.gdpr.consents.applies), + usnat = MessagesRequest.MetaData.Campaign(state.usNat.consents.applies), + ccpa = MessagesRequest.MetaData.Campaign(state.ccpa.consents.applies), + globalcmp = MessagesRequest.MetaData.Campaign(state.globalcmp.consents.applies) + ), + nonKeyedLocalState = state.nonKeyedLocalState, + localState = state.localState + ))) } else { emptyList() } @@ -581,13 +562,13 @@ class Coordinator( private suspend fun sampleAndPvData(campaign: State.SPSampleable, request: PvDataRequest) = if (campaign.wasSampled == null) { if (sample(campaign.sampleRate)) { - executeLoadMessagesAPIRequest(PV_DATA) { handlePvDataResponse(spClient.postPvData(request)) } + handlePvDataResponse(spClient.postPvData(request)) true } else { false } } else if (campaign.wasSampled == true) { - executeLoadMessagesAPIRequest(PV_DATA) { handlePvDataResponse(spClient.postPvData(request)) } + handlePvDataResponse(spClient.postPvData(request)) true } else { false @@ -1026,7 +1007,7 @@ class Coordinator( IOS14, SPCampaignType.Unknown -> {} } } catch (error: SPError) { - throw ReportActionException(cause = error, actionType = action.type, campaignType = action.campaignType) + throw ReportActionException(causedBy = error, actionType = action.type, campaignType = action.campaignType) } finally { storeLegislationConsent(userData = userData) persistState() @@ -1063,7 +1044,7 @@ class Coordinator( legIntCategories = legIntCategories )) } catch (error: SPError) { - throw PostCustomConsentGDPRException(cause = error) + throw PostCustomConsentGDPRException(causedBy = error) } } @@ -1084,7 +1065,7 @@ class Coordinator( legIntCategories = legIntCategories )) } catch (error: SPError) { - throw DeleteCustomConsentGDPRException(cause = error) + throw DeleteCustomConsentGDPRException(causedBy = error) } } From 5cc5afbf0d333126b6911714f40b7bef28e9653a Mon Sep 17 00:00:00 2001 From: Dmytro Date: Fri, 26 Sep 2025 14:44:34 +0200 Subject: [PATCH 6/7] change `InvalidResponseAPIError` `cause` to `Throwable` --- .../kotlin/com/sourcepoint/mobile_core/models/SPError.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/models/SPError.kt b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/models/SPError.kt index b07b336a..c4d9033d 100644 --- a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/models/SPError.kt +++ b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/models/SPError.kt @@ -7,7 +7,7 @@ open class SPError( open val campaignType: SPCampaignType? = null ): Throwable(description) { companion object { - fun castToSPError(error: Exception): SPError = + fun castToSPError(error: Throwable): SPError = error as? SPError ?: SPError(cause = error, description = error.message ?: "Something went wrong in the Mobile Core") } @@ -83,7 +83,7 @@ open class DeleteCustomConsentGDPRException(cause: SPError): SPError( cause = cause ) -open class InvalidResponseAPIError(cause: Exception, endpoint: InvalidResponseAPICode): SPError ( +open class InvalidResponseAPIError(cause: Throwable, endpoint: InvalidResponseAPICode): SPError ( code = "sp_metric_invalid_response_api${endpoint.type}", description = "The SDK got an unexpected response from ${endpoint.name}", cause = castToSPError(cause) From 9c16f3ea9597a15e940069a4ee984d34e7ecafcd Mon Sep 17 00:00:00 2001 From: Dmytro Date: Fri, 26 Sep 2025 15:17:14 +0200 Subject: [PATCH 7/7] handle request exception's --- .../sourcepoint/mobile_core/Coordinator.kt | 8 +- .../sourcepoint/mobile_core/models/SPError.kt | 11 +- .../mobile_core/network/SourcepointClient.kt | 198 +++++++++++------- 3 files changed, 128 insertions(+), 89 deletions(-) diff --git a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt index 2c40b002..ced525f1 100644 --- a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt +++ b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/Coordinator.kt @@ -221,7 +221,7 @@ class Coordinator( } } } catch (error: SPError) { - throw LoadMessagesException(causedBy = error) + throw LoadMessagesException(cause = error) } storeLegislationConsent(userData = userData) persistState() @@ -1007,7 +1007,7 @@ class Coordinator( IOS14, SPCampaignType.Unknown -> {} } } catch (error: SPError) { - throw ReportActionException(causedBy = error, actionType = action.type, campaignType = action.campaignType) + throw ReportActionException(cause = error, actionType = action.type, campaignType = action.campaignType) } finally { storeLegislationConsent(userData = userData) persistState() @@ -1044,7 +1044,7 @@ class Coordinator( legIntCategories = legIntCategories )) } catch (error: SPError) { - throw PostCustomConsentGDPRException(causedBy = error) + throw PostCustomConsentGDPRException(cause = error) } } @@ -1065,7 +1065,7 @@ class Coordinator( legIntCategories = legIntCategories )) } catch (error: SPError) { - throw DeleteCustomConsentGDPRException(causedBy = error) + throw DeleteCustomConsentGDPRException(cause = error) } } diff --git a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/models/SPError.kt b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/models/SPError.kt index c4d9033d..88c24244 100644 --- a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/models/SPError.kt +++ b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/models/SPError.kt @@ -83,13 +83,13 @@ open class DeleteCustomConsentGDPRException(cause: SPError): SPError( cause = cause ) -open class InvalidResponseAPIError(cause: Throwable, endpoint: InvalidResponseAPICode): SPError ( +open class InvalidRequestAPIError(cause: Throwable, endpoint: InvalidAPICode): SPError ( code = "sp_metric_invalid_response_api${endpoint.type}", description = "The SDK got an unexpected response from ${endpoint.name}", cause = castToSPError(cause) ) -enum class InvalidResponseAPICode(val type: String) { +enum class InvalidAPICode(val type: String) { META_DATA("_meta-data"), CONSENT_STATUS("_consent-status"), PV_DATA("_pv-data"), @@ -98,12 +98,15 @@ enum class InvalidResponseAPICode(val type: String) { CCPA_ACTION("_CCPA-action"), GDPR_ACTION("_GDPR-action"), USNAT_ACTION("_USNAT-action"), + GLOBALCMP_ACTION("_GLOBALCMP-action"), + PREFERENCES_ACTION("_PREFERENCES-action"), IDFA_STATUS( "_IDFA-status"), - CCPA_PRIVACY_MANAGER("_CCPA-privacy-manager"), CHOICE_ALL("_choice-all"), + CUSTOM_CONSENT("custom-consent-GDPR"), + DELETE_CUSTOM_CONSENT("_delete-custom-consent-GDPR"), + CCPA_PRIVACY_MANAGER("_CCPA-privacy-manager"), GDPR_PRIVACY_MANAGER("_GDPR-privacy-manager"), CCPA_MESSAGE("_CCPA-message"), GDPR_MESSAGE("_GDPR-message"), - DELETE_CUSTOM_CONSENT("_delete-custom-consent-GDPR"), EMPTY("") } diff --git a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/network/SourcepointClient.kt b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/network/SourcepointClient.kt index e1bc891f..4a93cd79 100644 --- a/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/network/SourcepointClient.kt +++ b/core/src/commonMain/kotlin/com/sourcepoint/mobile_core/network/SourcepointClient.kt @@ -3,8 +3,12 @@ package com.sourcepoint.mobile_core.network import com.sourcepoint.core.BuildConfig import com.sourcepoint.mobile_core.DeviceInformation import com.sourcepoint.mobile_core.models.InvalidChoiceAllParamsError +import com.sourcepoint.mobile_core.models.InvalidAPICode +import com.sourcepoint.mobile_core.models.InvalidAPICode.* +import com.sourcepoint.mobile_core.models.InvalidRequestAPIError import com.sourcepoint.mobile_core.models.SPActionType import com.sourcepoint.mobile_core.models.SPCampaignType +import com.sourcepoint.mobile_core.models.SPClientTimeout import com.sourcepoint.mobile_core.models.SPError import com.sourcepoint.mobile_core.models.SPIDFAStatus import com.sourcepoint.mobile_core.models.SPNetworkError @@ -199,68 +203,80 @@ class SourcepointClient( private val baseWrapperUrl = "https://cdn.privacy-mgmt.com/" - override suspend fun getMetaData(campaigns: MetaDataRequest.Campaigns): MetaDataResponse = http.get( - URLBuilder(baseWrapperUrl).apply { - path("wrapper", "v2", "meta-data") - withParams( - MetaDataRequest( - accountId = accountId, - propertyId = propertyId, - metadata = campaigns - ) - ) - }.build() - ).bodyOr(::reportErrorAndThrow) + override suspend fun getMetaData(campaigns: MetaDataRequest.Campaigns): MetaDataResponse = + executeAPIRequest(META_DATA) { + http.get( + URLBuilder(baseWrapperUrl).apply { + path("wrapper", "v2", "meta-data") + withParams( + MetaDataRequest( + accountId = accountId, + propertyId = propertyId, + metadata = campaigns + ) + ) + }.build() + ).bodyOr(::reportErrorAndThrow) + } override suspend fun postPvData(request: PvDataRequest): PvDataResponse = - http.post(URLBuilder(baseWrapperUrl).apply { - path("wrapper", "v2", "pv-data") - withParams(DefaultRequest()) + executeAPIRequest(PV_DATA) { + http.post(URLBuilder(baseWrapperUrl).apply { + path("wrapper", "v2", "pv-data") + withParams(DefaultRequest()) }.build()) { contentType(ContentType.Application.Json) setBody(request) }.bodyOr(::reportErrorAndThrow) + } override suspend fun getConsentStatus(authId: String?, metadata: ConsentStatusRequest.MetaData): ConsentStatusResponse = - http.get(URLBuilder(baseWrapperUrl).apply { - path("wrapper", "v2", "consent-status") - withParams( - ConsentStatusRequest( - propertyId = propertyId, - authId = authId, - metadata = metadata - ) - ) - }.build() - ).bodyOr(::reportErrorAndThrow) + executeAPIRequest(CONSENT_STATUS) { + http.get( + URLBuilder(baseWrapperUrl).apply { + path("wrapper", "v2", "consent-status") + withParams( + ConsentStatusRequest( + propertyId = propertyId, + authId = authId, + metadata = metadata + ) + ) + }.build() + ).bodyOr(::reportErrorAndThrow) + } @Throws(SPNetworkError::class, SPUnableToParseBodyError::class, CancellationException::class, HttpRequestTimeoutException::class) private suspend inline fun genericPostChoiceAction( actionType: SPActionType, + endpoint: InvalidAPICode, fromCampaign: String, request: ChoiceRequest - ): ChoiceResponse = http.post(URLBuilder(baseWrapperUrl).apply { - path("wrapper", "v2", "choice", fromCampaign, actionType.type.toString()) - withParams(DefaultRequest()) - }.build()) { - contentType(ContentType.Application.Json) - setBody(request) - }.bodyOr(::reportErrorAndThrow) + ): ChoiceResponse = + executeAPIRequest(endpoint) { + http.post(URLBuilder(baseWrapperUrl).apply { + path("wrapper", "v2", "choice", fromCampaign, actionType.type.toString()) + withParams(DefaultRequest()) + }.build()) { + contentType(ContentType.Application.Json) + setBody(request) + }.bodyOr(::reportErrorAndThrow) + } override suspend fun postChoiceGDPRAction(actionType: SPActionType, request: GDPRChoiceRequest): GDPRChoiceResponse = - genericPostChoiceAction(actionType, "gdpr", request) + genericPostChoiceAction(actionType, GDPR_ACTION, "gdpr", request) override suspend fun postChoiceUSNatAction(actionType: SPActionType, request: USNatChoiceRequest): USNatChoiceResponse = - genericPostChoiceAction(actionType, "usnat", request) + genericPostChoiceAction(actionType, USNAT_ACTION, "usnat", request) override suspend fun postChoiceGlobalCmpAction(actionType: SPActionType, request: GlobalCmpChoiceRequest): GlobalCmpChoiceResponse = - genericPostChoiceAction(actionType, "global-cmp", request) + genericPostChoiceAction(actionType, GLOBALCMP_ACTION, "global-cmp", request) override suspend fun postChoicePreferencesAction(actionType: SPActionType, request: PreferencesChoiceRequest): PreferencesChoiceResponse = - genericPostChoiceAction(actionType, "preferences", request) + genericPostChoiceAction(actionType, PREFERENCES_ACTION, "preferences", request) override suspend fun postChoiceCCPAAction(actionType: SPActionType, request: CCPAChoiceRequest): CCPAChoiceResponse = - genericPostChoiceAction(actionType, "ccpa", request) + genericPostChoiceAction(actionType, CCPA_ACTION, "ccpa", request) override suspend fun getChoiceAll( actionType: SPActionType, @@ -271,17 +287,21 @@ class SourcepointClient( SPActionType.RejectAll -> { "reject-all" } else -> throw InvalidChoiceAllParamsError() } - return http.get(URLBuilder(baseWrapperUrl).apply { - path("wrapper", "v2", "choice", choicePath) - withParams(ChoiceAllRequest(accountId, propertyId, campaigns)) - }.build()).bodyOr(::reportErrorAndThrow) + return executeAPIRequest(CHOICE_ALL) { + return@executeAPIRequest http.get(URLBuilder(baseWrapperUrl).apply { + path("wrapper", "v2", "choice", choicePath) + withParams(ChoiceAllRequest(accountId, propertyId, campaigns)) + }.build()).bodyOr(::reportErrorAndThrow) + } } override suspend fun getMessages(request: MessagesRequest): MessagesResponse = - http.get(URLBuilder(baseWrapperUrl).apply { - path("wrapper", "v2", "messages") - withParams(request) - }.build()).bodyOr(::reportErrorAndThrow) + executeAPIRequest(MESSAGES) { + http.get(URLBuilder(baseWrapperUrl).apply { + path("wrapper", "v2", "messages") + withParams(request) + }.build()).bodyOr(::reportErrorAndThrow) + } override suspend fun postReportIdfaStatus( propertyId: Int?, @@ -293,26 +313,28 @@ class SourcepointClient( iosVersion: String, partitionUUID: String? ) { - http.post(URLBuilder(baseWrapperUrl).apply { - path("wrapper", "metrics", "v1", "apple-tracking") - withParams(DefaultRequest()) - }.build()) { - contentType(ContentType.Application.Json) - setBody( - IDFAStatusReportRequest( - accountId = accountId, - propertyId = propertyId, - uuid = uuid, - uuidType = uuidType, - requestUUID = requestUUID, - iosVersion = iosVersion, - appleTracking = IDFAStatusReportRequest.AppleTrackingPayload( - appleChoice = idfaStatus, - appleMsgId = messageId, - messagePartitionUUID = partitionUUID + executeAPIRequest(IDFA_STATUS) { + http.post(URLBuilder(baseWrapperUrl).apply { + path("wrapper", "metrics", "v1", "apple-tracking") + withParams(DefaultRequest()) + }.build()) { + contentType(ContentType.Application.Json) + setBody( + IDFAStatusReportRequest( + accountId = accountId, + propertyId = propertyId, + uuid = uuid, + uuidType = uuidType, + requestUUID = requestUUID, + iosVersion = iosVersion, + appleTracking = IDFAStatusReportRequest.AppleTrackingPayload( + appleChoice = idfaStatus, + appleMsgId = messageId, + messagePartitionUUID = partitionUUID + ) ) ) - ) + } } } @@ -323,6 +345,7 @@ class SourcepointClient( categories: List, legIntCategories: List ): GDPRConsent = + executeAPIRequest(CUSTOM_CONSENT) { http.post(URLBuilder(baseWrapperUrl).apply { path("wrapper", "tcfv2", "v1", "gdpr", "custom-consent") withParams(DefaultRequest()) @@ -335,8 +358,10 @@ class SourcepointClient( vendors = vendors, categories = categories, legIntCategories = legIntCategories - )) + ) + ) }.bodyOr(::reportErrorAndThrow) + } override suspend fun deleteCustomConsentGDPR( consentUUID: String, @@ -345,23 +370,26 @@ class SourcepointClient( categories: List, legIntCategories: List ): GDPRConsent = - http.delete(URLBuilder(baseWrapperUrl).apply { - path("consent", "tcfv2", "consent", "v3", "custom", propertyId.toString()) - withParams(DeleteCustomConsentRequest(consentUUID)) - }.build()) { - contentType(ContentType.Application.Json) - setBody( - CustomConsentRequest( - consentUUID = consentUUID, - propertyId = propertyId, - vendors = vendors, - categories = categories, - legIntCategories = legIntCategories - )) - }.bodyOr(::reportErrorAndThrow) + executeAPIRequest(DELETE_CUSTOM_CONSENT) { + http.delete(URLBuilder(baseWrapperUrl).apply { + path("consent", "tcfv2", "consent", "v3", "custom", propertyId.toString()) + withParams(DeleteCustomConsentRequest(consentUUID)) + }.build()) { + contentType(ContentType.Application.Json) + setBody( + CustomConsentRequest( + consentUUID = consentUUID, + propertyId = propertyId, + vendors = vendors, + categories = categories, + legIntCategories = legIntCategories + ) + ) + }.bodyOr(::reportErrorAndThrow) + } override suspend fun errorMetrics(error: SPError) { - try { + executeAPIRequest(ERROR_METRICS) { http.post(URLBuilder(baseWrapperUrl).apply { path("wrapper", "metrics", "v1", "custom-metrics") withParams(DefaultRequest()) @@ -379,7 +407,7 @@ class SourcepointClient( ) ) } - } catch (_: Exception) {} + } } private suspend fun reportErrorAndThrow(error: SPError): SPError { @@ -388,6 +416,14 @@ class SourcepointClient( } } +private suspend fun executeAPIRequest(endpoint: InvalidAPICode, command: suspend () -> T): T = + try { command() } catch (error: Throwable) { + when (error) { + is SPUnableToParseBodyError, is SPClientTimeout, is SPNetworkError -> throw error + else -> throw InvalidRequestAPIError(cause = error, endpoint = endpoint) + } + } + @Throws(SPUnableToParseBodyError::class, CancellationException::class, HttpRequestTimeoutException::class) internal suspend inline fun HttpResponse.bodyOr(loggingFunction: KSuspendFunction1): T = try {