Skip to content

fix: Android: webConsents is stale after customConsentGDPR / deleteCustomConsentTo #347

Description

@cimperator

Description

On Android, after calling customConsentGDPR (or deleteCustomConsentTo), the returned SPConsent is internally inconsistent:

gdpr.grants (the structured map) is correct — the updated vendor has granted = true.
webConsents is stale — inside gdpr.webConsentPayload, the same vendor still has vendorGrant: false and is missing from customVendorsResponse.consentedVendors.
Since preloadConsent injects webConsents into the WebView, embeds that depend on the vendor keep showing the consent placeholder after the grant — i.e. the consent doesn't appear to persist on WebView reopen, even though the native consent state is updated. iOS is not affected.

Root cause

In SourcePointUnifiedCmpData.kt:

fun SPConsents.toHostAPISPConsent() = HostAPISPConsent(
    gdpr = gdpr?.toHostAPIGDPRConsent(),
    webConsents = this.toWebViewConsentsJsonObject().toString()
)

toWebViewConsentsJsonObject() (from the SourcePoint native SDK) serializes from the SDK's cached state, which doesn't reflect the just-applied custom consent. So gdpr.grants and webConsents are derived inconsistently from the same object.

Proposed fix

Re-derive the webConsentPayload.grants from the authoritative structured grants:

fun SPConsents.toHostAPISPConsent() = HostAPISPConsent(
    gdpr = gdpr?.toHostAPIGDPRConsent(),
    webConsents = this.toWebViewConsentsJsonObject()
        .syncGrantsWith(gdpr?.consent?.grants)
        .toString()
)
private fun JSONObject.syncGrantsWith(grants: Map<String, GDPRPurposeGrants>?): JSONObject {
    if (grants == null) return this
    val gdpr = optJSONObject("gdpr") ?: return this
    val payloadStr = gdpr.optString("webConsentPayload").takeIf { it.isNotEmpty() } ?: return this
    val payload = JSONObject(payloadStr)
    val newGrants = JSONObject()
    val consentedVendors = JSONArray()
    grants.forEach { (vendorId, grant) ->
        newGrants.put(vendorId, JSONObject().apply {
            put("vendorGrant", grant.granted)
            put("purposeGrants", JSONObject(grant.purposeGrants as Map<*, *>))
        })
        if (grant.granted) consentedVendors.put(JSONObject().put("_id", vendorId))
    }
    payload.put("grants", newGrants)
    payload.optJSONObject("customVendorsResponse")?.put("consentedVendors", consentedVendors)
    gdpr.put("webConsentPayload", payload.toString())
    return this
}

This makes customConsentGDPR and deleteCustomConsentTo return a webConsents blob consistent with gdpr.grants, matching the iOS behavior. Ideally the underlying issue should also be reported to SourcePoint so toWebViewConsentsJsonObject() reflects custom consent natively.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions