catalyst-core 0.0.3-canary.9 → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -5
- package/bin/catalyst.js +28 -15
- package/changelog.md +139 -30
- package/dist/native/androidProject/app/build.gradle.kts +125 -15
- package/dist/native/androidProject/app/proguard-rules.pro +85 -14
- package/dist/native/androidProject/app/src/debug/res/xml/network_security_config.xml +8 -0
- package/dist/native/androidProject/app/src/main/AndroidManifest.xml +10 -3
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/BridgeMessageValidator.kt +552 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CatalystConstants.kt +109 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +605 -124
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +405 -47
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MetricsMonitor.kt +341 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +954 -71
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/SplashActivity.kt +111 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/URLWhitelistManager.kt +231 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/Utils.kt +49 -55
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/WebCacheManager.kt +81 -8
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/design/DesignTokens.kt +307 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/EmulatorDetector.kt +203 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/FridaDetector.kt +187 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityAlertHandler.kt +170 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityAlertUI.kt +366 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityBottomSheet.kt +138 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityCheckManager.kt +511 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityCheckScheduler.kt +68 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +47 -11
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/DeviceInfoUtils.kt +38 -13
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/DownloadUtils.kt +36 -14
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FileUtils.kt +3 -2
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +57 -8
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/IntentUtils.kt +31 -6
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/KeyboardUtil.kt +82 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/NetworkUtils.kt +105 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/NotificationConstants.kt +28 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/NotificationUtils.kt +639 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/SafeAreaUtils.kt +62 -0
- package/dist/native/androidProject/app/src/main/res/drawable/splashscreen_fallback.png +0 -0
- package/dist/native/androidProject/app/src/main/res/layout/activity_main.xml +1 -0
- package/dist/native/androidProject/app/src/main/res/layout/activity_splash.xml +20 -0
- package/dist/native/androidProject/app/src/main/res/values/themes.xml +23 -0
- package/dist/native/androidProject/app/src/main/res/values-night/themes.xml +11 -0
- package/dist/native/androidProject/app/src/main/res/xml/network_security_config.xml +12 -0
- package/dist/native/androidProject/app/src/noFcm/java/io/yourname/androidproject/utils/NotificationManager.kt +166 -0
- package/dist/native/androidProject/app/src/noFcm/java/io/yourname/androidproject/utils/PushNotificationUtils.kt +94 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/BridgeMessageValidatorTest.kt +267 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/BridgeUtilsTest.kt +286 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CameraUtilsTest.kt +296 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/ConfigMappingTest.kt +594 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CustomWebviewTest.kt +466 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/DeviceInfoUtilsTest.kt +187 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FileUtilsTest.kt +304 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NativeBridgeTest.kt +424 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NotificationUtilsTest.kt +469 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/SecurityBridgeTest.kt +199 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/URLWhitelistManagerTest.kt +462 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/WebCacheManagerTest.kt +391 -0
- package/dist/native/androidProject/app/src/withFcm/java/io/yourname/androidproject/utils/NotificationManager.kt +224 -0
- package/dist/native/androidProject/app/src/withFcm/java/io/yourname/androidproject/utils/PushNotificationUtils.kt +399 -0
- package/dist/native/androidProject/build.gradle.kts +6 -0
- package/dist/native/androidProject/gradle/libs.versions.toml +8 -2
- package/dist/native/androidSetup.js +4 -3
- package/dist/native/assets/catalyst.png +0 -0
- package/dist/native/bridge/WebBridge.js +16 -3
- package/dist/native/bridge/constants/NativeInterfaces.js +28 -10
- package/dist/native/bridge/errors.js +1 -1
- package/dist/native/bridge/hooks.js +76 -16
- package/dist/native/bridge/safeArea.js +5 -0
- package/dist/native/bridge/standardizedHooks.js +23 -5
- package/dist/native/bridge/useBaseHook.js +7 -9
- package/dist/native/bridge/utils/CameraUtils.js +58 -0
- package/dist/native/bridge/utils/FileObjectConverter.js +40 -0
- package/dist/native/bridge/utils/NativeBridge.js +57 -8
- package/dist/native/buildAppAndroid.js +115 -43
- package/dist/native/buildAppIos.js +291 -26
- package/dist/native/iosnativeWebView/Configurations/Debug.xcconfig +21 -0
- package/dist/native/iosnativeWebView/Configurations/Release.xcconfig +24 -0
- package/dist/native/iosnativeWebView/Configurations/Shared.xcconfig +50 -0
- package/dist/native/iosnativeWebView/Package.resolved +32 -0
- package/dist/native/iosnativeWebView/Package.swift +37 -0
- package/dist/native/iosnativeWebView/Sources/CatalystNotifications/LocalNotificationHandler.swift +293 -0
- package/dist/native/iosnativeWebView/Sources/CatalystNotifications/NotificationManager.swift +299 -0
- package/dist/native/iosnativeWebView/Sources/CatalystNotifications/PushNotificationHandler.swift +199 -0
- package/dist/native/iosnativeWebView/Sources/Core/Constants/CatalystConstants.swift +125 -0
- package/dist/native/iosnativeWebView/Sources/Core/Constants/ConfigConstants.swift +70 -0
- package/dist/native/iosnativeWebView/Sources/Core/Notifications/NotificationModels.swift +156 -0
- package/dist/native/iosnativeWebView/Sources/Core/Protocols/NotificationHandlerProtocol.swift +35 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/BootTimingUtility.swift +26 -0
- package/dist/native/iosnativeWebView/{iosnativeWebView → Sources/Core/Utils}/CacheManager.swift +69 -36
- package/dist/native/iosnativeWebView/Sources/Core/Utils/CameraTransportUtils.swift +363 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/DeviceInfoUtils.swift +67 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/FilePickerHandler.swift +1053 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/FrameworkServerUtils.swift +901 -0
- package/dist/native/iosnativeWebView/{iosnativeWebView → Sources/Core/Utils}/ImageHandler.swift +206 -20
- package/dist/native/iosnativeWebView/Sources/Core/Utils/NetworkMonitor.swift +112 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/NotificationHandlerProvider.swift +20 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/NullNotificationHandler.swift +85 -0
- package/dist/native/iosnativeWebView/{iosnativeWebView → Sources/Core/Utils}/ResourceURLProtocol.swift +60 -25
- package/dist/native/iosnativeWebView/Sources/Core/Utils/SafeAreaUtils.swift +132 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/ScreenSecureManager.swift +131 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/URLWhitelistManager.swift +224 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/WebKitConfig.swift +43 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeCommandHandler.swift +991 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeDelegateHandler.swift +166 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeFileHandler.swift +340 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeJavaScriptInterface.swift +149 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +611 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +334 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashView.swift +95 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashViewModel.swift +86 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +181 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewModel.swift +182 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +395 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/AppDelegate.swift +84 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/Assets.xcassets/AccentColor.colorset/Contents.json +8 -8
- package/dist/native/iosnativeWebView/iosnativeWebView/Assets.xcassets/AppIcon.appiconset/Contents.json +10 -10
- package/dist/native/iosnativeWebView/iosnativeWebView/Assets.xcassets/Contents.json +4 -4
- package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +65 -10
- package/dist/native/iosnativeWebView/iosnativeWebView/Info-Release.plist +21 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/Info.plist +13 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/Preview Content/Preview Assets.xcassets/Contents.json +4 -4
- package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebView.entitlements +12 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebViewApp.swift +80 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/localhost.p12 +0 -0
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +118 -100
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +69 -0
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/xcshareddata/xcschemes/iosnativeWebView.xcscheme +9 -4
- package/dist/native/iosnativeWebView/iosnativeWebView.xctestplan +47 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/BootTimingUtilityTests.swift +236 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeCommandHandlerSecurityTests.swift +212 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeMessageValidatorTests.swift +418 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/CacheManagerTests.swift +390 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/ConfigMappingTests.swift +299 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/FilePickerHandlerTests.swift +383 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/FrameworkServerUtilsTests.swift +391 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/NativeBridgeTests.swift +581 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/NotificationHandlerTests.swift +430 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/ScreenSecureManagerTests.swift +121 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/URLWhitelistManagerTests.swift +367 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/WebViewTests.swift +440 -0
- package/dist/native/renameAndroidProject.js +80 -70
- package/dist/native/setupEmulatorIos.js +22 -5
- package/dist/native/setupServer.js +11 -10
- package/dist/native/terminalProgress.js +10 -10
- package/dist/native/utils.js +4 -5
- package/dist/otel.js +4 -0
- package/dist/scripts/build.js +2 -2
- package/dist/scripts/devBuild.js +2 -2
- package/dist/scripts/loadEnvironmentVariables.js +3 -1
- package/dist/scripts/scriptUtils.js +2 -2
- package/dist/sentry.js +4 -1
- package/dist/server/renderer/document/Body.js +4 -4
- package/dist/server/renderer/handler.js +7 -5
- package/dist/webpack/development.client.babel.js +1 -1
- package/mcp_v2/conversion-tasks.json +326 -0
- package/mcp_v2/knowledge-base.json +1068 -0
- package/mcp_v2/lib/helpers.js +165 -0
- package/mcp_v2/mcp.js +563 -0
- package/mcp_v2/package.json +13 -0
- package/mcp_v2/schema.sql +88 -0
- package/mcp_v2/setup.js +278 -0
- package/mcp_v2/tools/build.js +686 -0
- package/mcp_v2/tools/config.js +453 -0
- package/mcp_v2/tools/conversion.js +799 -0
- package/mcp_v2/tools/debug.js +113 -0
- package/mcp_v2/tools/knowledge.js +219 -0
- package/mcp_v2/tools/sync.js +23 -0
- package/mcp_v2/tools/tasks.js +945 -0
- package/package.json +11 -15
- package/dist/native/androidProject/.idea/.name +0 -1
- package/dist/native/androidProject/.idea/AndroidProjectSystem.xml +0 -6
- package/dist/native/androidProject/.idea/compiler.xml +0 -6
- package/dist/native/androidProject/.idea/deploymentTargetSelector.xml +0 -10
- package/dist/native/androidProject/.idea/gradle.xml +0 -18
- package/dist/native/androidProject/.idea/migrations.xml +0 -10
- package/dist/native/androidProject/.idea/misc.xml +0 -10
- package/dist/native/androidProject/.idea/runConfigurations.xml +0 -17
- package/dist/native/androidProject/.idea/vcs.xml +0 -6
- package/dist/native/iosnativeWebView/iosnativeWebView/NativeBridge.swift +0 -217
- package/dist/native/iosnativeWebView/iosnativeWebView/WebView.swift +0 -96
- package/dist/native/iosnativeWebView/iosnativeWebView/WebViewModel.swift +0 -47
- package/dist/native/iosnativeWebView/iosnativeWebView/WebViewNavigationDelegate.swift +0 -140
- package/license +0 -10
package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/SplashActivity.kt
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
package io.yourname.androidproject
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.graphics.Color
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import android.os.Bundle
|
|
8
|
+
import android.os.Handler
|
|
9
|
+
import android.os.Looper
|
|
10
|
+
import android.util.TypedValue
|
|
11
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
12
|
+
import androidx.core.content.ContextCompat
|
|
13
|
+
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
|
|
14
|
+
import androidx.core.graphics.drawable.toBitmap
|
|
15
|
+
import io.yourname.androidproject.databinding.ActivitySplashBinding
|
|
16
|
+
import java.util.Properties
|
|
17
|
+
|
|
18
|
+
class SplashActivity : AppCompatActivity() {
|
|
19
|
+
|
|
20
|
+
private lateinit var binding: ActivitySplashBinding
|
|
21
|
+
private lateinit var properties: Properties
|
|
22
|
+
private var isFinishing = false
|
|
23
|
+
|
|
24
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
25
|
+
super.onCreate(savedInstanceState)
|
|
26
|
+
|
|
27
|
+
properties = Properties()
|
|
28
|
+
try {
|
|
29
|
+
assets.open("webview_config.properties").use { properties.load(it) }
|
|
30
|
+
} catch (e: Exception) {
|
|
31
|
+
startMainActivity()
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!properties.getProperty("splashScreen.enabled", "false").toBoolean()) {
|
|
36
|
+
startMainActivity()
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
binding = ActivitySplashBinding.inflate(layoutInflater)
|
|
41
|
+
setContentView(binding.root)
|
|
42
|
+
|
|
43
|
+
configureSplashScreen()
|
|
44
|
+
startSplashTimer()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private fun configureSplashScreen() {
|
|
48
|
+
val backgroundColor = properties.getProperty("splashScreen.backgroundColor", "#ffffff")
|
|
49
|
+
val imageWidth = properties.getProperty("splashScreen.imageWidth", "120").toIntOrNull() ?: 120
|
|
50
|
+
val imageHeight = properties.getProperty("splashScreen.imageHeight", "120").toIntOrNull() ?: 120
|
|
51
|
+
val cornerRadius = properties.getProperty("splashScreen.cornerRadius", "20").toFloatOrNull() ?: 20f
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
binding.splashContainer.setBackgroundColor(Color.parseColor(backgroundColor))
|
|
55
|
+
} catch (e: Exception) {
|
|
56
|
+
binding.splashContainer.setBackgroundColor(Color.WHITE)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
val widthPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, imageWidth.toFloat(), resources.displayMetrics).toInt()
|
|
60
|
+
val heightPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, imageHeight.toFloat(), resources.displayMetrics).toInt()
|
|
61
|
+
|
|
62
|
+
binding.splashImage.layoutParams.apply {
|
|
63
|
+
width = widthPx
|
|
64
|
+
height = heightPx
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
val drawable = ContextCompat.getDrawable(this,
|
|
68
|
+
resources.getIdentifier("splashscreen", "drawable", packageName).takeIf { it != 0 }
|
|
69
|
+
?: R.mipmap.ic_launcher
|
|
70
|
+
) ?: return
|
|
71
|
+
|
|
72
|
+
if (cornerRadius > 0) {
|
|
73
|
+
val radiusPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, cornerRadius, resources.displayMetrics)
|
|
74
|
+
val roundedDrawable = RoundedBitmapDrawableFactory.create(resources, drawable.toBitmap(widthPx, heightPx))
|
|
75
|
+
roundedDrawable.cornerRadius = radiusPx
|
|
76
|
+
binding.splashImage.setImageDrawable(roundedDrawable)
|
|
77
|
+
} else {
|
|
78
|
+
binding.splashImage.setImageDrawable(drawable)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private fun startSplashTimer() {
|
|
83
|
+
val duration = properties.getProperty("splashScreen.duration")?.toLongOrNull() ?: 1000
|
|
84
|
+
Handler(Looper.getMainLooper()).postDelayed({ dismissSplash() }, duration)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private fun dismissSplash() {
|
|
88
|
+
if (isFinishing) return
|
|
89
|
+
isFinishing = true
|
|
90
|
+
startActivity(Intent(this, MainActivity::class.java))
|
|
91
|
+
finish()
|
|
92
|
+
|
|
93
|
+
// Use modern API for Android 14+ (API 34+)
|
|
94
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
95
|
+
overrideActivityTransition(
|
|
96
|
+
Activity.OVERRIDE_TRANSITION_CLOSE,
|
|
97
|
+
android.R.anim.fade_in,
|
|
98
|
+
android.R.anim.fade_out
|
|
99
|
+
)
|
|
100
|
+
} else {
|
|
101
|
+
// Fallback to deprecated API for Android 13 and below
|
|
102
|
+
@Suppress("DEPRECATION")
|
|
103
|
+
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private fun startMainActivity() {
|
|
108
|
+
startActivity(Intent(this, MainActivity::class.java))
|
|
109
|
+
finish()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
package io.yourname.androidproject
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Manager for URL whitelisting and access control
|
|
7
|
+
* Provides centralized URL validation with pattern matching support
|
|
8
|
+
*
|
|
9
|
+
* Pattern matching rules:
|
|
10
|
+
* - *text* or text (no wildcard) -> checks if URL contains "text"
|
|
11
|
+
* - text* -> checks if URL starts with "text"
|
|
12
|
+
* - *text -> checks if URL ends with "text"
|
|
13
|
+
*
|
|
14
|
+
* URL processing:
|
|
15
|
+
* - Query parameters and hash fragments are stripped before matching
|
|
16
|
+
* - Matching is case-insensitive
|
|
17
|
+
* - Ports are kept as-is (user must include port in pattern if URL has explicit port)
|
|
18
|
+
*/
|
|
19
|
+
object URLWhitelistManager {
|
|
20
|
+
|
|
21
|
+
private const val TAG = "URLWhitelistManager"
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Data class to hold categorized URL patterns for efficient matching
|
|
25
|
+
*/
|
|
26
|
+
private data class CategorizedPatterns(
|
|
27
|
+
val contains: List<String>,
|
|
28
|
+
val prefix: List<String>,
|
|
29
|
+
val suffix: List<String>
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
@Volatile
|
|
33
|
+
private var accessControlEnabled: Boolean = false
|
|
34
|
+
|
|
35
|
+
@Volatile
|
|
36
|
+
private var patterns: CategorizedPatterns? = null
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Initialize the whitelist manager with configuration
|
|
40
|
+
* Should be called once during app initialization
|
|
41
|
+
*/
|
|
42
|
+
@Synchronized
|
|
43
|
+
fun initialize(enabled: Boolean, allowedUrls: List<String>) {
|
|
44
|
+
this.accessControlEnabled = enabled
|
|
45
|
+
this.patterns = categorizePatterns(allowedUrls)
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
if (BuildConfig.DEBUG) {
|
|
49
|
+
Log.d(TAG, "URLWhitelistManager initialized")
|
|
50
|
+
Log.d(TAG, "Access Control Enabled: $enabled")
|
|
51
|
+
Log.d(TAG, "Allowed URLs: $allowedUrls")
|
|
52
|
+
patterns?.let {
|
|
53
|
+
Log.d(TAG, "Categorized - Contains: ${it.contains}")
|
|
54
|
+
Log.d(TAG, "Categorized - Prefix: ${it.prefix}")
|
|
55
|
+
Log.d(TAG, "Categorized - Suffix: ${it.suffix}")
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch (e: RuntimeException) {
|
|
59
|
+
// Log not available in unit tests - silently ignore
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check if access control is enabled
|
|
65
|
+
*/
|
|
66
|
+
fun isAccessControlEnabled(): Boolean {
|
|
67
|
+
return accessControlEnabled
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check if a URL is allowed according to the whitelist patterns
|
|
72
|
+
* If access control is disabled, all URLs are allowed
|
|
73
|
+
*/
|
|
74
|
+
fun isUrlAllowed(url: String): Boolean {
|
|
75
|
+
// If access control is disabled, allow everything
|
|
76
|
+
if (!accessControlEnabled) {
|
|
77
|
+
return true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Always allow framework server URLs (localhost with /framework- pattern)
|
|
81
|
+
// These are internal infrastructure URLs used for large file handling
|
|
82
|
+
val frameworkUrlPattern = Regex("^https?://(?:localhost|127\\.0\\.0\\.1)(?::\\d+)?/framework-[a-zA-Z0-9_-]+", RegexOption.IGNORE_CASE)
|
|
83
|
+
if (frameworkUrlPattern.containsMatchIn(url)) {
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
val patterns = this.patterns ?: return false
|
|
88
|
+
|
|
89
|
+
// If all pattern lists are empty, block everything
|
|
90
|
+
if (patterns.contains.isEmpty() && patterns.prefix.isEmpty() && patterns.suffix.isEmpty()) {
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
val cleanUrl = cleanUrl(url)
|
|
95
|
+
|
|
96
|
+
// Check contains patterns
|
|
97
|
+
if (patterns.contains.any { cleanUrl.contains(it) }) {
|
|
98
|
+
try {
|
|
99
|
+
if (BuildConfig.DEBUG) {
|
|
100
|
+
Log.d(TAG, "✅ URL allowed (contains match): $url")
|
|
101
|
+
}
|
|
102
|
+
} catch (e: RuntimeException) { /* Ignore in tests */ }
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check prefix patterns
|
|
107
|
+
if (patterns.prefix.any { cleanUrl.startsWith(it) }) {
|
|
108
|
+
try {
|
|
109
|
+
if (BuildConfig.DEBUG) {
|
|
110
|
+
Log.d(TAG, "✅ URL allowed (prefix match): $url")
|
|
111
|
+
}
|
|
112
|
+
} catch (e: RuntimeException) { /* Ignore in tests */ }
|
|
113
|
+
return true
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check suffix patterns
|
|
117
|
+
if (patterns.suffix.any { cleanUrl.endsWith(it) }) {
|
|
118
|
+
try {
|
|
119
|
+
if (BuildConfig.DEBUG) {
|
|
120
|
+
Log.d(TAG, "✅ URL allowed (suffix match): $url")
|
|
121
|
+
}
|
|
122
|
+
} catch (e: RuntimeException) { /* Ignore in tests */ }
|
|
123
|
+
return true
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
if (BuildConfig.DEBUG) {
|
|
128
|
+
Log.w(TAG, "🚫 URL blocked by access control: $url")
|
|
129
|
+
Log.w(TAG, "🚫 Clean URL: $cleanUrl")
|
|
130
|
+
}
|
|
131
|
+
} catch (e: RuntimeException) { /* Ignore in tests */ }
|
|
132
|
+
|
|
133
|
+
return false
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Check if a URL is from an external domain (not in whitelist)
|
|
138
|
+
* If access control is disabled, nothing is considered external
|
|
139
|
+
*/
|
|
140
|
+
fun isExternalDomain(url: String): Boolean {
|
|
141
|
+
// If access control is disabled, nothing is external
|
|
142
|
+
if (!accessControlEnabled) {
|
|
143
|
+
return false
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Simply the inverse of isUrlAllowed
|
|
147
|
+
return !isUrlAllowed(url)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Clean URL by removing query parameters and hash, then lowercase for case-insensitive matching
|
|
152
|
+
* Also decodes URL-encoded characters to prevent bypass attempts
|
|
153
|
+
*/
|
|
154
|
+
private fun cleanUrl(url: String): String {
|
|
155
|
+
// Decode URL-encoded characters first to prevent bypass (e.g., %3F for ?, %23 for #)
|
|
156
|
+
val decoded = try {
|
|
157
|
+
java.net.URLDecoder.decode(url, "UTF-8")
|
|
158
|
+
} catch (e: Exception) {
|
|
159
|
+
// If decode fails, use original URL
|
|
160
|
+
url
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
var cleanUrl = decoded
|
|
164
|
+
|
|
165
|
+
// Remove hash fragment (# and everything after)
|
|
166
|
+
val hashIndex = cleanUrl.indexOf('#')
|
|
167
|
+
if (hashIndex != -1) {
|
|
168
|
+
cleanUrl = cleanUrl.substring(0, hashIndex)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Remove query parameters (? and everything after)
|
|
172
|
+
val queryIndex = cleanUrl.indexOf('?')
|
|
173
|
+
if (queryIndex != -1) {
|
|
174
|
+
cleanUrl = cleanUrl.substring(0, queryIndex)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return cleanUrl.lowercase()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Categorize patterns into contains, prefix, and suffix for efficient matching
|
|
182
|
+
*/
|
|
183
|
+
private fun categorizePatterns(allowedUrls: List<String>): CategorizedPatterns {
|
|
184
|
+
val contains = mutableListOf<String>()
|
|
185
|
+
val prefix = mutableListOf<String>()
|
|
186
|
+
val suffix = mutableListOf<String>()
|
|
187
|
+
|
|
188
|
+
allowedUrls.forEach { pattern ->
|
|
189
|
+
if (pattern.isEmpty()) return@forEach
|
|
190
|
+
|
|
191
|
+
val startsWithWildcard = pattern.startsWith('*')
|
|
192
|
+
val endsWithWildcard = pattern.endsWith('*')
|
|
193
|
+
|
|
194
|
+
when {
|
|
195
|
+
startsWithWildcard && endsWithWildcard -> {
|
|
196
|
+
// *text* -> contains check
|
|
197
|
+
if (pattern.length <= 2) return@forEach // Pattern is just * or ** - ignore
|
|
198
|
+
val extracted = pattern.substring(1, pattern.length - 1).lowercase()
|
|
199
|
+
if (extracted.isNotEmpty()) {
|
|
200
|
+
contains.add(extracted)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
endsWithWildcard -> {
|
|
204
|
+
// text* -> prefix check
|
|
205
|
+
if (pattern.length <= 1) return@forEach // Pattern is just * - ignore
|
|
206
|
+
val extracted = pattern.substring(0, pattern.length - 1).lowercase()
|
|
207
|
+
if (extracted.isNotEmpty()) {
|
|
208
|
+
prefix.add(extracted)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
startsWithWildcard -> {
|
|
212
|
+
// *text -> suffix check
|
|
213
|
+
if (pattern.length <= 1) return@forEach // Pattern is just * - ignore
|
|
214
|
+
val extracted = pattern.substring(1).lowercase()
|
|
215
|
+
if (extracted.isNotEmpty()) {
|
|
216
|
+
suffix.add(extracted)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else -> {
|
|
220
|
+
// No wildcard -> treat as contains check
|
|
221
|
+
val extracted = pattern.lowercase()
|
|
222
|
+
if (extracted.isNotEmpty()) {
|
|
223
|
+
contains.add(extracted)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return CategorizedPatterns(contains, prefix, suffix)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -2,63 +2,57 @@ package io.yourname.androidproject
|
|
|
2
2
|
|
|
3
3
|
import android.net.Uri
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Checks if a URL matches any of the cache patterns
|
|
7
|
+
* Handles query parameters, hashes, and version strings
|
|
8
|
+
*
|
|
9
|
+
* Examples:
|
|
10
|
+
* - Pattern "*.css" matches:
|
|
11
|
+
* - "http://example.com/app.css"
|
|
12
|
+
* - "http://example.com/app.d4e5dea6.css"
|
|
13
|
+
* - "http://example.com/app.css?v=123"
|
|
14
|
+
* - "http://example.com/path/to/style.css#section"
|
|
15
|
+
*
|
|
16
|
+
* - Pattern "*.js" matches:
|
|
17
|
+
* - "http://example.com/bundle.js"
|
|
18
|
+
* - "http://example.com/bundle.abc123.js"
|
|
19
|
+
* - "http://example.com/bundle.js?t=456"
|
|
20
|
+
*/
|
|
21
|
+
fun matchesCachePattern(url: String, cachePatterns: List<String>): Boolean {
|
|
22
|
+
if (cachePatterns.isEmpty()) return false
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
val parsedUrl = Uri.parse(url) ?: return false
|
|
26
|
+
// Get the path without query params or hash
|
|
27
|
+
val path = parsedUrl.path ?: return false
|
|
28
|
+
|
|
29
|
+
// Extract filename from path
|
|
30
|
+
val filename = path.substringAfterLast('/')
|
|
31
|
+
if (filename.isEmpty()) return false
|
|
32
|
+
|
|
33
|
+
return cachePatterns.any { pattern ->
|
|
34
|
+
when {
|
|
35
|
+
pattern.contains("*") -> {
|
|
36
|
+
// Convert wildcard pattern to regex
|
|
37
|
+
// *.css becomes .*\.css$
|
|
38
|
+
val regexPattern = pattern
|
|
39
|
+
.replace(".", "\\.") // Escape dots
|
|
40
|
+
.replace("*", ".*") // Convert * to .*
|
|
41
|
+
.let { "^$it$" } // Anchor at start and end
|
|
42
|
+
|
|
43
|
+
val regex = regexPattern.toRegex(RegexOption.IGNORE_CASE)
|
|
31
44
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return !allowedUrls.any { pattern ->
|
|
41
|
-
if (pattern.startsWith("*.")) {
|
|
42
|
-
val domain = pattern.substring(2)
|
|
43
|
-
urlHost.equals(domain, ignoreCase = true) || urlHost.endsWith(".$domain", ignoreCase = true)
|
|
44
|
-
} else {
|
|
45
|
-
val patternUri = Uri.parse(pattern)
|
|
46
|
-
val patternHost = patternUri?.host ?: return@any false
|
|
47
|
-
val patternScheme = patternUri?.scheme ?: return@any false
|
|
48
|
-
val patternPort = patternUri?.port ?: -1
|
|
49
|
-
|
|
50
|
-
val schemeMatches = urlScheme.equals(patternScheme, ignoreCase = true)
|
|
51
|
-
val hostMatches = if (patternHost.startsWith("*.")) {
|
|
52
|
-
val domain = patternHost.substring(2)
|
|
53
|
-
urlHost.equals(domain, ignoreCase = true) || urlHost.endsWith(".$domain", ignoreCase = true)
|
|
54
|
-
} else {
|
|
55
|
-
urlHost.equals(patternHost, ignoreCase = true)
|
|
45
|
+
// Try matching against filename
|
|
46
|
+
regex.matches(filename)
|
|
47
|
+
}
|
|
48
|
+
else -> {
|
|
49
|
+
// Exact match against filename
|
|
50
|
+
filename.equals(pattern, ignoreCase = true)
|
|
51
|
+
}
|
|
56
52
|
}
|
|
57
|
-
val portMatches = patternPort == -1 || urlPort == patternPort ||
|
|
58
|
-
(urlPort == -1 && ((patternPort == 443 && patternScheme.equals("https", ignoreCase = true)) ||
|
|
59
|
-
(patternPort == 80 && patternScheme.equals("http", ignoreCase = true))))
|
|
60
|
-
|
|
61
|
-
schemeMatches && hostMatches && portMatches
|
|
62
53
|
}
|
|
54
|
+
} catch (e: Exception) {
|
|
55
|
+
// If parsing fails, return false
|
|
56
|
+
return false
|
|
63
57
|
}
|
|
64
58
|
}
|
package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/WebCacheManager.kt
CHANGED
|
@@ -12,27 +12,40 @@ import java.net.URL
|
|
|
12
12
|
import java.security.MessageDigest
|
|
13
13
|
import java.util.concurrent.TimeUnit
|
|
14
14
|
|
|
15
|
-
class WebCacheManager(private val context: Context) {
|
|
15
|
+
class WebCacheManager(private val context: Context, private val properties: java.util.Properties? = null) {
|
|
16
16
|
private val TAG = "WebCacheManager"
|
|
17
17
|
private val cacheDir = File(context.cacheDir, "webview_cache")
|
|
18
18
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
19
19
|
private val BUFFER_SIZE = 32 * 1024
|
|
20
20
|
|
|
21
|
-
// Cache timing configurations
|
|
22
|
-
private val maxAge = TimeUnit.HOURS.toMillis(
|
|
23
|
-
|
|
21
|
+
// Cache timing configurations - now configurable via properties
|
|
22
|
+
private val maxAge = properties?.getProperty("cache.maxAge", "24")?.toLongOrNull()?.let { TimeUnit.HOURS.toMillis(it) }
|
|
23
|
+
?: TimeUnit.HOURS.toMillis(24) // Default 24 hours
|
|
24
|
+
private val staleWhileRevalidate = properties?.getProperty("cache.staleWhileRevalidate", "1")?.toLongOrNull()?.let { TimeUnit.HOURS.toMillis(it) }
|
|
25
|
+
?: TimeUnit.HOURS.toMillis(1) // Default 1 hour
|
|
26
|
+
private val maxCacheSize = properties?.getProperty("cache.maxSize", "100")?.toLongOrNull()?.let { it * 1024 * 1024 }
|
|
27
|
+
?: (100 * 1024 * 1024) // Default 100MB
|
|
28
|
+
private val memoryFraction = properties?.getProperty("cache.memoryFraction", "8")?.toIntOrNull() ?: 8 // Default 1/8 of memory
|
|
24
29
|
|
|
25
30
|
// Track ongoing revalidations to prevent duplicate requests
|
|
26
31
|
private val ongoingRevalidations = mutableSetOf<String>()
|
|
27
32
|
|
|
28
|
-
// Memory cache
|
|
33
|
+
// Memory cache - now configurable
|
|
29
34
|
private val memoryCache: LruCache<String, CacheEntry> = LruCache<String, CacheEntry>(
|
|
30
|
-
(Runtime.getRuntime().maxMemory() / 1024 /
|
|
35
|
+
(Runtime.getRuntime().maxMemory() / 1024 / memoryFraction).toInt()
|
|
31
36
|
)
|
|
32
37
|
|
|
33
38
|
init {
|
|
34
39
|
try {
|
|
35
40
|
cacheDir.mkdirs()
|
|
41
|
+
if (BuildConfig.DEBUG) {
|
|
42
|
+
Log.d(TAG, "📦 Cache configuration:")
|
|
43
|
+
Log.d(TAG, " - Max age: ${maxAge / (1000 * 60 * 60)} hours")
|
|
44
|
+
Log.d(TAG, " - Stale while revalidate: ${staleWhileRevalidate / (1000 * 60 * 60)} hours")
|
|
45
|
+
Log.d(TAG, " - Max cache size: ${maxCacheSize / (1024 * 1024)} MB")
|
|
46
|
+
Log.d(TAG, " - Memory cache size: ${memoryCache.maxSize() / 1024} KB")
|
|
47
|
+
Log.d(TAG, " - Cache directory: ${cacheDir.absolutePath}")
|
|
48
|
+
}
|
|
36
49
|
} catch (e: Exception) {
|
|
37
50
|
Log.e(TAG, "Failed to create cache directory: ${e.message}")
|
|
38
51
|
}
|
|
@@ -45,6 +58,54 @@ class WebCacheManager(private val context: Context) {
|
|
|
45
58
|
val lastModified: String? = null
|
|
46
59
|
)
|
|
47
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Synchronous cache check - only returns cached content if immediately available
|
|
63
|
+
* Does not perform network requests or background revalidation
|
|
64
|
+
* Used by ServiceWorker to avoid blocking
|
|
65
|
+
*/
|
|
66
|
+
fun getCachedResponseSync(url: String, headers: Map<String, String>): WebResourceResponse? {
|
|
67
|
+
return try {
|
|
68
|
+
val cacheKey = generateCacheKey(url)
|
|
69
|
+
val currentTime = System.currentTimeMillis()
|
|
70
|
+
|
|
71
|
+
// Check memory cache first
|
|
72
|
+
val memoryCacheEntry = memoryCache.get(cacheKey)
|
|
73
|
+
if (memoryCacheEntry != null) {
|
|
74
|
+
val age = currentTime - memoryCacheEntry.timestamp
|
|
75
|
+
// Only return if fresh or within stale-while-revalidate window
|
|
76
|
+
if (age <= maxAge + staleWhileRevalidate) {
|
|
77
|
+
if (BuildConfig.DEBUG) {
|
|
78
|
+
Log.d(TAG, "✅ Serving from memory cache (sync): $url")
|
|
79
|
+
}
|
|
80
|
+
return memoryCacheEntry.response
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check disk cache synchronously
|
|
85
|
+
val cacheFile = File(cacheDir, cacheKey)
|
|
86
|
+
if (cacheFile.exists()) {
|
|
87
|
+
val fileAge = currentTime - cacheFile.lastModified()
|
|
88
|
+
if (fileAge <= maxAge + staleWhileRevalidate) {
|
|
89
|
+
val metadata = loadMetadata(cacheKey)
|
|
90
|
+
val response = createResponseFromCache(cacheFile, metadata)
|
|
91
|
+
if (BuildConfig.DEBUG) {
|
|
92
|
+
Log.d(TAG, "✅ Serving from disk cache (sync): $url")
|
|
93
|
+
}
|
|
94
|
+
return response
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// No valid cache available
|
|
99
|
+
if (BuildConfig.DEBUG) {
|
|
100
|
+
Log.d(TAG, "❌ No valid cache available (sync): $url")
|
|
101
|
+
}
|
|
102
|
+
null
|
|
103
|
+
} catch (e: Exception) {
|
|
104
|
+
Log.e(TAG, "Error in getCachedResponseSync for URL: $url: ${e.message}")
|
|
105
|
+
null
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
48
109
|
suspend fun getCachedResponse(url: String, headers: Map<String, String>): WebResourceResponse? =
|
|
49
110
|
withContext(Dispatchers.IO) {
|
|
50
111
|
try {
|
|
@@ -117,11 +178,23 @@ class WebCacheManager(private val context: Context) {
|
|
|
117
178
|
}
|
|
118
179
|
}
|
|
119
180
|
|
|
181
|
+
fun clearAll() {
|
|
182
|
+
try {
|
|
183
|
+
memoryCache.evictAll()
|
|
184
|
+
cacheDir.listFiles()?.forEach { it.delete() }
|
|
185
|
+
if (BuildConfig.DEBUG) {
|
|
186
|
+
Log.d(TAG, "clearAll: memory cache and disk cache wiped")
|
|
187
|
+
}
|
|
188
|
+
} catch (e: Exception) {
|
|
189
|
+
Log.e(TAG, "clearAll failed: ${e.message}")
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
120
193
|
suspend fun cleanup() {
|
|
121
194
|
withContext(Dispatchers.IO) {
|
|
122
195
|
try {
|
|
123
196
|
val currentTime = System.currentTimeMillis()
|
|
124
|
-
|
|
197
|
+
// Use configurable max cache size
|
|
125
198
|
var totalSize = 0L
|
|
126
199
|
|
|
127
200
|
// Get all cache files sorted by last modified time (oldest first)
|
|
@@ -274,7 +347,7 @@ class WebCacheManager(private val context: Context) {
|
|
|
274
347
|
response = WebResourceResponse(
|
|
275
348
|
mimeType,
|
|
276
349
|
encoding,
|
|
277
|
-
ByteArrayInputStream(responseBytes.
|
|
350
|
+
ByteArrayInputStream(responseBytes.copyOf())
|
|
278
351
|
),
|
|
279
352
|
eTag = eTag,
|
|
280
353
|
lastModified = lastModified
|