Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
@file:JvmSynthetic

package com.revenuecat.purchases.ui.revenuecatui.components.webview

import com.revenuecat.purchases.ui.revenuecatui.PaywallWebViewValue
import com.revenuecat.purchases.ui.revenuecatui.helpers.Logger

/**
* Builds the `variables` payload sent to a `web_view` component in response to `rc:request-variables`.
*
* Produces a flat map shaped like:
* ```json
* { "locale": "en-US" }
* ```
*
* Only safe, already-available system context is exposed: the paywall locale. The paywall's
* dashboard-defined custom variables are intentionally NOT passed across the bridge in v1. API keys,
* email addresses, the Purchases SDK instance, and storage are never included.
*
* [KEY_LOCALE] is a reserved SDK-managed top-level key: app-provided variables may not overwrite it.
*/
internal object PaywallWebViewVariablesProvider {

const val KEY_LOCALE: String = "locale"

val reservedKeys: Set<String> = setOf(KEY_LOCALE)

/**
* The SDK-managed system variables exposed to the web view.
*
* @param locale a BCP-47-ish locale string, e.g. `en-US`.
*/
fun sdkManagedVariables(
locale: String,
): Map<String, PaywallWebViewValue> = linkedMapOf(
KEY_LOCALE to PaywallWebViewValue.String(locale),
)

/**
* Removes reserved SDK-managed top-level keys from app-provided variables so they cannot overwrite
* SDK values, logging a warning for any that were dropped.
*/
fun sanitizeAppProvidedVariables(
variables: Map<String, PaywallWebViewValue>,
): Map<String, PaywallWebViewValue> {
val sanitized = variables.filterKeys { key ->
val reserved = key in reservedKeys
if (reserved) {
Logger.w(
"Ignoring reserved web view variable key '$key'. Reserved SDK-managed keys cannot be " +
"overwritten.",
)
}
!reserved
}
return sanitized
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.revenuecat.purchases.ui.revenuecatui.components.webview

import com.revenuecat.purchases.ui.revenuecatui.PaywallWebViewValue
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
internal class PaywallWebViewVariablesProviderTest {

@Test
fun `sdkManagedVariables exposes only the locale system variable`() {
val variables = PaywallWebViewVariablesProvider.sdkManagedVariables(locale = "en-US")

// Only the locale system variable is exposed; nothing else is passed across the bridge in v1.
assertThat(variables).hasSize(1)
assertThat(variables[PaywallWebViewVariablesProvider.KEY_LOCALE])
.isEqualTo(PaywallWebViewValue.String("en-US"))
}

@Test
fun `sanitizeAppProvidedVariables drops reserved keys`() {
val sanitized = PaywallWebViewVariablesProvider.sanitizeAppProvidedVariables(
mapOf(
"locale" to PaywallWebViewValue.String("zz-ZZ"),
"app_segment" to PaywallWebViewValue.String("high_intent"),
),
)

assertThat(sanitized).doesNotContainKey("locale")
assertThat(sanitized).containsKey("app_segment")
}
}