Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Added preloading of paywall's resources listed in manifest #106

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
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
31 changes: 19 additions & 12 deletions superwall/src/main/java/com/superwall/sdk/config/ConfigManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.superwall.sdk.config

import android.content.Context
import android.webkit.WebView
import com.superwall.sdk.billing.GoogleBillingWrapper
import com.superwall.sdk.config.models.ConfigState
import com.superwall.sdk.config.models.getConfig
import com.superwall.sdk.config.options.SuperwallOptions
Expand Down Expand Up @@ -34,7 +33,10 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch

// TODO: Re-enable those params
Expand All @@ -47,7 +49,8 @@ open class ConfigManager(
private val paywallManager: PaywallManager,
private val factory: Factory
) {
interface Factory: RequestFactory, DeviceInfoFactory, RuleAttributesFactory {}
interface Factory : RequestFactory, DeviceInfoFactory, RuleAttributesFactory {}

var options = SuperwallOptions()

// The configuration of the Superwall dashboard
Expand Down Expand Up @@ -289,15 +292,19 @@ open class ConfigManager(
presentationSourceType = null,
retryCount = 6
)
try {
paywallManager.getPaywallViewController(
request = request,
isForPresentation = true,
isPreloading = true,
delegate = null
)
} catch (e: Exception) {
// Handle exception
val shouldSkip = paywallManager
.preloadViaPaywallArchivalAndShouldSkipViewControllerCache(request = request)
if (!shouldSkip) {
try {
paywallManager.getPaywallViewController(
request = request,
isForPresentation = true,
isPreloading = true,
delegate = null
)
} catch (e: Exception) {
// Handle exception
}
}
}
tasks.add(task)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,8 @@ class DebugViewController(
val paywallVc = factory.makePaywallViewController(
paywall = paywall,
cache = null,
delegate = null
delegate = null,
paywallArchivalManager = null
)
previewContainerView.addView(paywallVc)
previewViewContent = paywallVc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ import com.superwall.sdk.delegate.SuperwallDelegateAdapter
import com.superwall.sdk.delegate.subscription_controller.PurchaseController
import com.superwall.sdk.identity.IdentityInfo
import com.superwall.sdk.identity.IdentityManager
import com.superwall.sdk.misc.CurrentActivityTracker
import com.superwall.sdk.misc.ActivityProvider
import com.superwall.sdk.misc.AppLifecycleObserver
import com.superwall.sdk.misc.CurrentActivityTracker
import com.superwall.sdk.models.config.FeatureFlags
import com.superwall.sdk.models.events.EventData
import com.superwall.sdk.models.paywall.Paywall
Expand All @@ -35,6 +35,7 @@ import com.superwall.sdk.network.Api
import com.superwall.sdk.network.Network
import com.superwall.sdk.network.device.DeviceHelper
import com.superwall.sdk.network.device.DeviceInfo
import com.superwall.sdk.paywall.archival.PaywallArchivalManager
import com.superwall.sdk.paywall.manager.PaywallManager
import com.superwall.sdk.paywall.manager.PaywallViewControllerCache
import com.superwall.sdk.paywall.presentation.internal.PresentationRequest
Expand All @@ -52,6 +53,7 @@ import com.superwall.sdk.paywall.vc.web_view.messaging.PaywallMessageHandler
import com.superwall.sdk.paywall.vc.web_view.templating.models.JsonVariables
import com.superwall.sdk.paywall.vc.web_view.templating.models.Variables
import com.superwall.sdk.storage.EventsQueue
import com.superwall.sdk.storage.SearchPathDirectory
import com.superwall.sdk.storage.Storage
import com.superwall.sdk.store.InternalPurchaseController
import com.superwall.sdk.store.StoreKitManager
Expand All @@ -76,7 +78,8 @@ class DependencyContainer(
StoreTransactionFactory, Storage.Factory, InternalSuperwallEvent.PresentationRequest.Factory,
ViewControllerFactory, PaywallManager.Factory, OptionsFactory, TriggerFactory,
TransactionVerifierFactory, TransactionManager.Factory, PaywallViewController.Factory,
ConfigManager.Factory, AppSessionManager.Factory, DebugViewController.Factory {
ConfigManager.Factory, AppSessionManager.Factory, DebugViewController.Factory,
PaywallArchivalManagerFactory {

var network: Network
override lateinit var api: Api
Expand All @@ -95,7 +98,8 @@ class DependencyContainer(
var storeKitManager: StoreKitManager
val transactionManager: TransactionManager
val googleBillingWrapper: GoogleBillingWrapper

val paywallArchivalManager: PaywallArchivalManager =
PaywallArchivalManager(baseDirectory = SearchPathDirectory.USER_SPECIFIC_DOCUMENTS.fileDirectory(context))
init {
// TODO: Add delegate adapter

Expand Down Expand Up @@ -247,6 +251,7 @@ class DependencyContainer(
override suspend fun makePaywallViewController(
paywall: Paywall,
cache: PaywallViewControllerCache?,
paywallArchivalManager: PaywallArchivalManager?,
delegate: PaywallViewControllerDelegateAdapter?
): PaywallViewController {
return withContext(Dispatchers.Main) {
Expand Down Expand Up @@ -278,7 +283,8 @@ class DependencyContainer(
paywallManager = paywallManager,
storage = storage,
webView = webView,
eventDelegate = Superwall.instance
eventDelegate = Superwall.instance,
paywallArchivalManager = paywallArchivalManager
)
webView.delegate = paywallViewController
messageHandler.delegate = paywallViewController
Expand Down Expand Up @@ -500,4 +506,9 @@ class DependencyContainer(
override suspend fun makeTriggers(): Set<String> {
return configManager.triggersByEventName.keys
}

override fun makePaywallArchivalManager(): PaywallArchivalManager {
return this.paywallArchivalManager
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.superwall.sdk.models.product.ProductVariable
import com.superwall.sdk.network.Api
import com.superwall.sdk.network.device.DeviceHelper
import com.superwall.sdk.network.device.DeviceInfo
import com.superwall.sdk.paywall.archival.PaywallArchivalManager
import com.superwall.sdk.paywall.manager.PaywallViewControllerCache
import com.superwall.sdk.paywall.presentation.internal.PresentationRequest
import com.superwall.sdk.paywall.presentation.internal.PresentationRequestType
Expand Down Expand Up @@ -133,6 +134,7 @@ interface ViewControllerFactory {
suspend fun makePaywallViewController(
paywall: Paywall,
cache: PaywallViewControllerCache?,
paywallArchivalManager: PaywallArchivalManager?,
delegate: PaywallViewControllerDelegateAdapter?
): PaywallViewController

Expand All @@ -157,6 +159,9 @@ interface ViewControllerFactory {
// fun makeCache(): PaywallViewControllerCache
//}

interface PaywallArchivalManagerFactory {
fun makePaywallArchivalManager(): PaywallArchivalManager
}

interface CacheFactory {
fun makeCache(): PaywallViewControllerCache
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.superwall.sdk.misc

import kotlinx.coroutines.CompletableDeferred
import java.util.concurrent.ConcurrentHashMap

class RequestCoalescence<Input : Any, Output> {
private val tasks = ConcurrentHashMap<Input, CompletableDeferred<Output>>()

suspend fun get(input: Input, request: suspend (Input) -> Output): Output {
val existingTask = tasks[input]
return if (existingTask != null) {
// If there's already a task in progress, wait for it to finish
existingTask.await()
} else {
// Start a new task if one isn't already in progress
val newTask = CompletableDeferred<Output>()
tasks[input] = newTask
val output = request(input)
newTask.complete(output)
tasks.remove(input)
output
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,11 @@ data class Paywall(
/**
Surveys to potentially show when an action happens in the paywall.
*/
var surveys: List<Survey> = emptyList()
var surveys: List<Survey> = emptyList(),

// A listing of all the files referenced in a paywall
// to be able to preload the whole paywall into a web archive
val manifest: ArchivalManifest? = null

) : SerializableEntity {
// Public getter for productItems
Expand Down Expand Up @@ -244,7 +248,8 @@ data class Paywall(
paywalljsVersion = "",
isFreeTrialAvailable = false,
featureGating = FeatureGatingBehavior.NonGated,
localNotifications = arrayListOf()
localNotifications = arrayListOf(),
manifest = null
)

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.superwall.sdk.models.paywall

import com.superwall.sdk.models.serialization.URLSerializer
import kotlinx.serialization.Serializable
import java.net.URL

@Serializable
enum class ArchivalManifestUsage {
ALWAYS, NEVER, IF_AVAILABLE_ON_PAYWALL_OPEN
}

@Serializable
data class ArchivalManifest(
val use: ArchivalManifestUsage,
val document: ArchivalManifestItem,
val resources: List<ArchivalManifestItem>
)

@Serializable
data class ArchivalManifestItem(
val url: @Serializable(with = URLSerializer::class) URL,
val mimeType: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.superwall.sdk.models.paywall

import java.net.URL

data class WebArchive(
val mainResource: WebArchiveResource,
val subResources: List<WebArchiveResource>
)

class WebArchiveResource(
val url: URL,
val data: ByteArray,
val mimeType: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.superwall.sdk.paywall.archival

import com.superwall.sdk.misc.Result
import com.superwall.sdk.models.paywall.ArchivalManifestUsage
import com.superwall.sdk.models.paywall.Paywall
import com.superwall.sdk.models.paywall.WebArchive
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File

class PaywallArchivalManager(
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO),
private val ioCoroutineDispatcher: CoroutineDispatcher = Dispatchers.IO,
private var webArchiveManager: WebArchiveManager? = null,
baseDirectory: File? = null,
) {

init {
if (webArchiveManager == null && baseDirectory != null) {
webArchiveManager = WebArchiveManager(baseDirectory = baseDirectory.resolve("paywalls"))
}
}

fun preloadArchiveAndShouldSkipViewControllerCache(paywall: Paywall): Boolean {
webArchiveManager?.let { webArchiveManager ->
if (paywall.manifest != null) {
if (paywall.manifest.use == ArchivalManifestUsage.NEVER) {
return false
}
coroutineScope.launch(ioCoroutineDispatcher) {
webArchiveManager.archiveForManifest(manifest = paywall.manifest)
}
return true
}
}
return false
}

// If we should be really aggressive and wait for the archival to finish
// before we load
fun shouldWaitForWebArchiveToLoad(paywall: Paywall): Boolean {
return webArchiveManager != null && paywall.manifest?.use == ArchivalManifestUsage.ALWAYS
}

// We'll try to see if it's cached, if not we'll just
// skip it and fall back to the normal method of loading
fun cachedArchiveForPaywallImmediately(paywall: Paywall): WebArchive? {
webArchiveManager?.let { webArchiveManager ->
if (paywall.manifest != null) {
if (paywall.manifest.use == ArchivalManifestUsage.NEVER) {
return null
}
return webArchiveManager.archiveForManifestImmediately(manifest = paywall.manifest)
}
}
return null
}

suspend fun cachedArchiveForPaywall(paywall: Paywall): WebArchive? {
webArchiveManager?.let { webArchiveManager ->
if (paywall.manifest != null) {
if (paywall.manifest.use == ArchivalManifestUsage.NEVER) {
return null
}
val result = webArchiveManager.archiveForManifest(manifest = paywall.manifest)
return when (result) {
is Result.Success -> result.value
is Result.Failure -> null
}
}
}
return null
}

}
Loading