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
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ dokka = "2.1.0"
detekt = "1.23.7"
robolectric = "4.13"
activity_ktx_version = "1.10.1"
webkit = "1.14.0"
[libraries]

# SQL
Expand Down Expand Up @@ -88,6 +89,7 @@ play_review_ktx = { module = "com.google.android.play:review", version.ref = "pl
reviewplay_review_ktx = { module = "com.google.android.play:review-ktx", version.ref = "play_review" }
activity_ktx = { module = "androidx.activity:activity-ktx", version.ref = "activity_ktx_version" }
activity = { module = "androidx.activity:activity", version.ref = "activity_ktx_version" }
webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" }

# Coroutines
kotlinx_coroutines_core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx_coroutines_core_version" }
Expand Down
1 change: 1 addition & 0 deletions superwall/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ dependencies {

// Browser
implementation(libs.browser)
implementation(libs.webkit)

// Core
implementation(libs.core)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.superwall.sdk.models.triggers.Trigger
import com.superwall.sdk.network.SuperwallAPI
import com.superwall.sdk.network.awaitUntilNetworkExists
import com.superwall.sdk.network.device.DeviceHelper
import com.superwall.sdk.paywall.archive.WebArchiveLibrary
import com.superwall.sdk.paywall.manager.PaywallManager
import com.superwall.sdk.storage.DisableVerboseEvents
import com.superwall.sdk.storage.LatestConfig
Expand Down Expand Up @@ -68,6 +69,7 @@ open class ConfigManager(
private val awaitUtilNetwork: suspend () -> Unit = {
context.awaitUntilNetworkExists()
},
private val webArchiveLibrary: WebArchiveLibrary,
) {
interface Factory :
RequestFactory,
Expand Down
128 changes: 93 additions & 35 deletions superwall/src/main/java/com/superwall/sdk/config/PaywallPreload.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ package com.superwall.sdk.config

import android.content.Context
import com.superwall.sdk.analytics.internal.trackable.InternalSuperwallEvent
import com.superwall.sdk.dependencies.OptionsFactory
import com.superwall.sdk.dependencies.RequestFactory
import com.superwall.sdk.dependencies.RuleAttributesFactory
import com.superwall.sdk.misc.IOScope
import com.superwall.sdk.misc.launchWithTracking
import com.superwall.sdk.models.config.Config
import com.superwall.sdk.models.paywall.CacheKey
import com.superwall.sdk.models.paywall.Paywall
import com.superwall.sdk.models.paywall.PaywallIdentifier
import com.superwall.sdk.models.triggers.Trigger
import com.superwall.sdk.paywall.archive.WebArchiveLibrary
import com.superwall.sdk.paywall.manager.PaywallManager
import com.superwall.sdk.paywall.presentation.rule_logic.javascript.RuleEvaluator
import com.superwall.sdk.paywall.request.ResponseIdentifiers
Expand All @@ -26,9 +29,21 @@ class PaywallPreload(
val storage: LocalStorage,
val assignments: Assignments,
val paywallManager: PaywallManager,
val webArchiveLibrary: WebArchiveLibrary,
private val track: suspend (InternalSuperwallEvent) -> Unit,
) {
val ignoredArchiveUrls =
listOf(
"webflow.com",
"webflow.io",
"builder-templates",
"apple.com",
"templates.superwall.com",
"interceptor.superwallapp.com",
)

interface Factory :
OptionsFactory,
RequestFactory,
RuleAttributesFactory,
RuleEvaluator.Factory
Expand Down Expand Up @@ -60,7 +75,10 @@ class PaywallPreload(
expressionEvaluator = expressionEvaluator,
)

preloadPaywalls(paywallIdentifiers = paywallIds)
preloadPaywalls(
paywallIdentifiers = paywallIds,
paywalls = config.paywalls.filter { it.identifier in paywallIds },
)
currentPreloadingTask = null
}
}
Expand All @@ -76,11 +94,17 @@ class PaywallPreload(
config,
triggersToPreload.toSet(),
)
preloadPaywalls(triggerPaywallIdentifiers)
preloadPaywalls(
triggerPaywallIdentifiers,
config.paywalls.filter { it.identifier in triggerPaywallIdentifiers },
)
}

// Preloads paywalls referenced by triggers.
private suspend fun preloadPaywalls(paywallIdentifiers: Set<String>) {
private suspend fun preloadPaywalls(
paywallIdentifiers: Set<String>,
paywalls: List<Paywall>,
) {
val paywallCount = paywallIdentifiers.size
track(
InternalSuperwallEvent.PaywallPreload(
Expand All @@ -90,42 +114,64 @@ class PaywallPreload(
)

val webviewExists = webViewExists()

val filteredPaywalls =
paywalls
.filter { it.identifier in paywallIdentifiers }
.distinctBy { it.identifier }
.filter {
!ignoredArchiveUrls.any { url -> it.url.value.contains(url) }
}

val shouldArchive = factory.makeSuperwallOptions().paywalls.shouldArchive
val shouldPreload = factory.makeSuperwallOptions().paywalls.shouldPreload

val identifiersToDownload =
if (shouldArchive) filteredPaywalls.map { it.identifier } else emptyList()

if (webviewExists) {
scope.launchWithTracking {
// List to hold all the Deferred objects
val tasks = mutableListOf<Deferred<Any>>()

for (identifier in paywallIdentifiers) {
val task =
async {
// Your asynchronous operation
val request =
factory.makePaywallRequest(
eventData = null,
responseIdentifiers =
ResponseIdentifiers(
paywallId = identifier,
experiment = null,
),
overrides = null,
isDebuggerLaunched = false,
presentationSourceType = null,
)
try {
paywallManager.getPaywallView(
request = request,
isForPresentation = true,
isPreloading = true,
delegate = null,
)
} catch (e: Exception) {
// Handle exception
// If archiving is enabled, cache the available paywalls first
if (shouldArchive) {
async {
cachePaywallsFromManifest(filteredPaywalls.toSet())
}.await()
}
// If preloading is enabled, preload the paywalls after archiving them
if (shouldPreload) {
val tasks = mutableListOf<Deferred<Any>>()
for (identifier in paywallIdentifiers.filter { it !in identifiersToDownload }) {
val task =
async {
val request =
factory.makePaywallRequest(
eventData = null,
responseIdentifiers =
ResponseIdentifiers(
paywallId = identifier,
experiment = null,
),
overrides = null,
isDebuggerLaunched = false,
presentationSourceType = null,
)

try {
paywallManager.getPaywallView(
request = request,
isForPresentation = true,
isPreloading = true,
delegate = null,
)
} catch (e: Exception) {
// Handle exception
}
}
}
tasks.add(task)
tasks.add(task)
}
// Await all tasks
tasks.awaitAll()
}
// Await all tasks
tasks.awaitAll()
track(
InternalSuperwallEvent.PaywallPreload(
state = InternalSuperwallEvent.PaywallPreload.State.Complete,
Expand Down Expand Up @@ -185,4 +231,16 @@ class PaywallPreload(
paywallManager.removePaywallView(it)
}
}

private suspend fun cachePaywallsFromManifest(paywalls: Set<Paywall>) {
paywalls
.distinctBy { it.identifier }
.filter {
!ignoredArchiveUrls.any { url -> it.url.value.contains(url) }
}.map {
scope.async {
webArchiveLibrary.downloadManifest(it.identifier, it.url.value, it.manifest)
}
}.awaitAll()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class PaywallOptions {
// or ``Superwall/preloadPaywalls(forEvents:)``
var shouldPreload: Boolean = true

var shouldArchive: Boolean = false

// Loads paywall template websites from disk, if available. Defaults to `true`.
//
// When you save a change to your paywall in the Superwall dashboard, a key is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import com.superwall.sdk.models.paywall.LocalNotificationType
import com.superwall.sdk.models.paywall.Paywall
import com.superwall.sdk.models.product.ProductVariable
import com.superwall.sdk.network.Api
import com.superwall.sdk.network.ArchiveService
import com.superwall.sdk.network.BaseHostService
import com.superwall.sdk.network.CollectorService
import com.superwall.sdk.network.EnrichmentService
Expand All @@ -64,6 +65,11 @@ import com.superwall.sdk.network.SubscriptionService
import com.superwall.sdk.network.device.DeviceHelper
import com.superwall.sdk.network.device.DeviceInfo
import com.superwall.sdk.network.session.CustomHttpUrlConnection
import com.superwall.sdk.paywall.archive.Base64ArchiveEncoder
import com.superwall.sdk.paywall.archive.CachedArchiveLibrary
import com.superwall.sdk.paywall.archive.ManifestDownloader
import com.superwall.sdk.paywall.archive.StreamArchiveCompressor
import com.superwall.sdk.paywall.archive.WebArchiveLibrary
import com.superwall.sdk.paywall.manager.PaywallManager
import com.superwall.sdk.paywall.manager.PaywallViewCache
import com.superwall.sdk.paywall.presentation.CustomCallbackRegistry
Expand Down Expand Up @@ -194,6 +200,7 @@ class DependencyContainer(
internal val userPermissions: UserPermissions
internal val customCallbackRegistry: CustomCallbackRegistry

var archive: WebArchiveLibrary
var entitlements: Entitlements
internal lateinit var customerInfoManager: CustomerInfoManager
lateinit var reedemer: WebPaywallRedeemer
Expand Down Expand Up @@ -348,6 +355,7 @@ class DependencyContainer(
factory = this,
customHttpUrlConnection = httpConnection,
),
archiveService = ArchiveService(httpConnection),
factory = this,
)
errorTracker = ErrorTracker(scope = ioScope, cache = storage)
Expand Down Expand Up @@ -380,13 +388,21 @@ class DependencyContainer(
ioScope,
)

archive =
CachedArchiveLibrary(
storage,
ManifestDownloader(IOScope(), network),
StreamArchiveCompressor(encoder = Base64ArchiveEncoder()),
)

paywallPreload =
PaywallPreload(
factory = this,
storage = storage,
assignments = assignments,
paywallManager = paywallManager,
scope = ioScope,
webArchiveLibrary = archive,
track = {
Superwall.instance.track(it)
},
Expand All @@ -410,6 +426,7 @@ class DependencyContainer(
},
entitlements = entitlements,
webPaywallRedeemer = { reedemer },
webArchiveLibrary = archive,
)
identityManager =
IdentityManager(
Expand Down Expand Up @@ -949,6 +966,8 @@ class DependencyContainer(

override fun makeSuperwallOptions(): SuperwallOptions = configManager.options

override fun webArchive(): WebArchiveLibrary = archive

override suspend fun makeTriggers(): Set<String> = configManager.triggersByEventName.keys

override suspend fun provideRuleEvaluator(context: Context): ExpressionEvaluating = evaluator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.superwall.sdk.network.Api
import com.superwall.sdk.network.JsonFactory
import com.superwall.sdk.network.device.DeviceHelper
import com.superwall.sdk.network.device.DeviceInfo
import com.superwall.sdk.paywall.archive.WebArchiveLibrary
import com.superwall.sdk.paywall.manager.PaywallViewCache
import com.superwall.sdk.paywall.presentation.PaywallInfo
import com.superwall.sdk.paywall.presentation.internal.PresentationRequest
Expand Down Expand Up @@ -228,6 +229,8 @@ interface ExperimentalPropertiesFactory {

interface OptionsFactory {
fun makeSuperwallOptions(): SuperwallOptions

fun webArchive(): WebArchiveLibrary
}

interface AttributesFactory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ enum class LogScope {
paywallView,
nativePurchaseController,
cache,
webarchive,
deepLinks,
all,
;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ data class Paywall(
val isScrollEnabled: Boolean? = true,
@SerialName("reroute_back_button")
val rerouteBackButton: ToggleMode? = null,
@SerialName("manifest")
val manifest: WebArchiveManifest? = null,
) : SerializableEntity {
val playStoreProducts: List<CrossplatformProduct>
get() =
Expand Down Expand Up @@ -314,6 +316,12 @@ data class Paywall(
isScrollEnabled = true,
rerouteBackButton = ToggleMode.DISABLED,
_productItemsV3 = emptyList(),
manifest =
WebArchiveManifest(
WebArchiveManifest.Usage.NEVER,
WebArchiveManifest.Document("", ""),
emptyList(),
),
)
}

Expand Down
Loading
Loading