Refactor webview configuration and data clearing functions for iOS and JVM platforms

- Updated `platformWebviewConfig` function to accept an optional `pluginId` parameter for both iOS and JVM implementations.
- Enhanced data directory handling in JVM to create a separate directory for each plugin.
- Implemented `platformClearWebviewData` function for both platforms to clear webview data based on the provided `pluginId`.
- Added necessary imports for iOS and JVM specific functionalities.
This commit is contained in:
Kingkor Roy Tirtho 2026-07-30 22:53:15 +06:00
parent 14ee4f6a4c
commit fe0ebb47fe
9 changed files with 2215 additions and 2173 deletions

View File

@ -17,8 +17,20 @@
package dev.krtirtho.spotube.core.webview
import android.webkit.CookieManager
import android.webkit.WebStorage
import android.webkit.WebView
import io.github.kdroidfilter.webview.web.WebViewState
actual fun platformWebviewConfig(webView: WebViewState) {
webView.webView?.nativeWebView?.settings?.domStorageEnabled = true
actual fun platformWebviewConfig(webView: WebViewState, pluginId: String?) {
val nativeWebView = webView.webView?.nativeWebView as? WebView ?: return
nativeWebView.settings.domStorageEnabled = true
}
actual suspend fun platformClearWebviewData(pluginId: String?) {
if (pluginId == null) return
CookieManager.getInstance().removeAllCookies(null)
CookieManager.getInstance().flush()
WebStorage.getInstance().deleteAllData()
}

View File

@ -38,6 +38,8 @@ class WebViewController(val navigationCommands: NavigationCommands): KoinCompone
private var cookieManager: CookieManager? = null
private val urlFlow = MutableStateFlow("")
private val webViewCreated = MutableSharedFlow<Unit>(replay = 1)
var currentPluginId: String? = null
private set
suspend fun getCookies(url: String): List<Cookie> {
if (cookieManager == null) {
@ -104,19 +106,21 @@ class WebViewController(val navigationCommands: NavigationCommands): KoinCompone
webViewNavigator = null
}
fun navigateTo(url: String) {
fun navigateTo(url: String, pluginId: String) {
if (this.content != null) {
throw IllegalStateException("WebView is already open. Please close the current WebView before navigating to a new URL.")
}
this.currentPluginId = pluginId
this.content = url
this.isHtmlContent = false
navigationCommands.navigateTo(Routes.WebView)
}
fun navigateToHTML(html: String) {
fun navigateToHTML(html: String, pluginId: String) {
if (this.content != null) {
throw IllegalStateException("WebView is already open. Please close the current WebView before navigating to a new URL.")
}
this.currentPluginId = pluginId
this.content = html
this.isHtmlContent = true
navigationCommands.navigateTo(Routes.WebView)
@ -138,12 +142,15 @@ class WebViewController(val navigationCommands: NavigationCommands): KoinCompone
return completer.await()
}
suspend fun clearData() {
suspend fun clearData(pluginId: String? = null) {
cookieManager?.removeAllCookies()
val targetPluginId = pluginId ?: currentPluginId
platformClearWebviewData(targetPluginId)
cookieManager = null
content = null
isHtmlContent = false
webViewNavigator = null
currentPluginId = null
}
val urlChangedFlow = urlFlow.asStateFlow()

View File

@ -19,4 +19,6 @@ package dev.krtirtho.spotube.core.webview
import io.github.kdroidfilter.webview.web.WebViewState
expect fun platformWebviewConfig(webView: WebViewState)
expect fun platformWebviewConfig(webView: WebViewState, pluginId: String?)
expect suspend fun platformClearWebviewData(pluginId: String?)

View File

@ -19,7 +19,6 @@ package dev.krtirtho.spotube.core.webview
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
@ -40,30 +39,25 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import io.github.kdroidfilter.webview.jsbridge.IJsMessageHandler
import io.github.kdroidfilter.webview.jsbridge.JsMessage
import io.github.kdroidfilter.webview.jsbridge.rememberWebViewJsBridge
import io.github.kdroidfilter.webview.web.WebView
import io.github.kdroidfilter.webview.web.rememberWebViewNavigator
import io.github.kdroidfilter.webview.web.WebViewState
import io.github.kdroidfilter.webview.web.WebViewNavigator
import compose.icons.FeatherIcons
import compose.icons.feathericons.ChevronLeft
import compose.icons.feathericons.ChevronRight
import compose.icons.feathericons.X
import dev.krtirtho.spotube.core.tools.user_agents.UserAgents
import kotlinx.coroutines.launch
import io.github.kdroidfilter.webview.jsbridge.IJsMessageHandler
import io.github.kdroidfilter.webview.jsbridge.JsMessage
import io.github.kdroidfilter.webview.jsbridge.rememberWebViewJsBridge
import io.github.kdroidfilter.webview.web.WebView
import io.github.kdroidfilter.webview.web.WebViewNavigator
import io.github.kdroidfilter.webview.web.WebViewState
import io.github.kdroidfilter.webview.web.rememberWebViewNavigator
class PostMessageHandler(
private val onMessageReceived: (String) -> Unit = {}
@ -98,7 +92,7 @@ fun PlatformWebViewScreen(webViewController: WebViewController) {
)
}.apply {
this.content = webViewController.getWebContent()
platformWebviewConfig(this)
platformWebviewConfig(this, webViewController.currentPluginId)
}
val navigator = rememberWebViewNavigator()

View File

@ -141,7 +141,7 @@ open class ZiplinePluginService(
}
private val realHttpClientAPI = RealHttpClientAPI()
private val realWebViewAPI = RealWebViewAPI(scope, webViewController)
private val realWebViewAPI = RealWebViewAPI(scope, webViewController, pluginInfo.id)
private val persistedStorageAPI = RealPersistedStorageAPI(pluginInfo)
private val cryptoAPI = RealCryptoAPI(scope.coroutineContext)

View File

@ -22,26 +22,24 @@ import dev.krtirtho.plugin_interfaces.host_apis.WebViewAPI
import dev.krtirtho.spotube.core.webview.WebViewController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class RealWebViewAPI(
private val scope: CoroutineScope,
private val webViewController: WebViewController,
private val pluginId: String,
) : WebViewAPI {
override fun navigateTo(url: String) {
scope.launch(Dispatchers.Main) {
webViewController.navigateTo(url)
webViewController.navigateTo(url, pluginId)
}
}
override fun navigateToHTML(html: String) {
scope.launch {
webViewController.navigateToHTML(html)
webViewController.navigateToHTML(html, pluginId)
}
}

View File

@ -735,7 +735,7 @@ fun PluginScreen(
selectedService.use { coreAPI.logout() }
}
// should clear webview data after logout
scope.launch { webviewController.clearData() }
scope.launch { webviewController.clearData(plugin.id) }
}
} else {
null

View File

@ -18,6 +18,24 @@
package dev.krtirtho.spotube.core.webview
import io.github.kdroidfilter.webview.web.WebViewState
import kotlinx.cinterop.ExperimentalForeignApi
import platform.Foundation.NSDate
import platform.Foundation.NSHTTPCookieStorage
import platform.Foundation.distantPast
import platform.WebKit.WKWebsiteDataStore
import platform.WebKit.WKWebsiteDataTypeCookies
import platform.WebKit.WKWebsiteDataTypeLocalStorage
actual fun platformWebviewConfig(webView: WebViewState) {
actual fun platformWebviewConfig(webView: WebViewState, pluginId: String?) {
}
@OptIn(ExperimentalForeignApi::class)
actual suspend fun platformClearWebviewData(pluginId: String?) {
if (pluginId == null) return
val dataStore = WKWebsiteDataStore.defaultDataStore()
val dataTypes = setOf(WKWebsiteDataTypeCookies, WKWebsiteDataTypeLocalStorage)
dataStore.removeDataOfTypes(dataTypes, NSDate.distantPast) {}
NSHTTPCookieStorage.sharedHTTPCookieStorage.removeCookiesSinceDate(NSDate.distantPast)
}

View File

@ -19,13 +19,24 @@ package dev.krtirtho.spotube.core.webview
import dev.krtirtho.spotube.core.paths.Paths
import io.github.kdroidfilter.webview.web.WebViewState
import io.github.vinceglb.filekit.utils.div
import io.github.vinceglb.filekit.utils.toPath
import okio.FileSystem
import okio.Path.Companion.toPath
import org.koin.core.context.GlobalContext
actual fun platformWebviewConfig(webView: WebViewState) {
actual fun platformWebviewConfig(webView: WebViewState, pluginId: String?) {
val paths = GlobalContext.get().get<Paths>()
webView.webSettings.desktopWebSettings.dataDirectory =
(paths.getApplicationCacheDirPath().toPath() / "webview_data").toString()
val baseDir = "${paths.getApplicationCacheDirPath()}/webview_data".toPath()
val dataDir = if (pluginId != null) baseDir / pluginId else baseDir
webView.webSettings.desktopWebSettings.dataDirectory = dataDir.toString()
}
actual suspend fun platformClearWebviewData(pluginId: String?) {
if (pluginId == null) return
val paths = GlobalContext.get().get<Paths>()
val dataDirStr = "${paths.getApplicationCacheDirPath()}/webview_data/$pluginId"
val dataDir = dataDirStr.toPath()
if (FileSystem.SYSTEM.exists(dataDir)) {
FileSystem.SYSTEM.deleteRecursively(dataDir)
}
}