catalyst-core 0.0.3-canary.12 → 0.0.3-canary.13
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/changelog.md +17 -0
- package/dist/native/androidProject/.idea/misc.xml +4 -0
- package/dist/native/androidProject/app/build.gradle.kts +6 -30
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/BridgeMessageValidator.kt +36 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +94 -13
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +290 -67
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/DownloadUtils.kt +6 -3
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/BridgeMessageValidatorTest.kt +34 -1
- package/dist/native/androidProject/gradle/libs.versions.toml +0 -4
- package/dist/native/assets/catalyst.png +0 -0
- package/dist/native/bridge/hooks.js +13 -26
- package/dist/native/bridge/utils/NativeBridge.js +2 -2
- package/dist/native/buildAppAndroid.js +1 -12
- package/dist/native/buildAppIos.js +1 -1
- package/dist/native/iosnativeWebView/iosnativeWebView/BridgeCommandHandler.swift +5 -3
- package/dist/native/iosnativeWebView/iosnativeWebView/BridgeDelegateHandler.swift +5 -60
- package/dist/native/iosnativeWebView/iosnativeWebView/BridgeMessageValidator.swift +47 -1
- package/dist/native/iosnativeWebView/iosnativeWebView/FilePickerHandler.swift +393 -111
- package/dist/native/iosnativeWebView/iosnativeWebView/NativeBridge.swift +2 -2
- package/dist/native/iosnativeWebView/iosnativeWebView/WebViewNavigationDelegate.swift +30 -0
- package/package.json +2 -2
- package/dist/native/androidProject/.idea/deviceManager.xml +0 -13
- package/dist/native/androidProject/app/.editorconfig +0 -17
- package/dist/native/androidProject/app/detekt-config.yml +0 -562
- package/dist/native/androidProject/app/src/main/assets/webview_config.properties +0 -36
- package/dist/native/iosnativeWebView/.swiftlint.yml +0 -118
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.xcworkspace/xcuserdata/mayankmahavar.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
package/changelog.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.3-canary.13] - 2025-09-28
|
|
4
|
+
|
|
5
|
+
- Multi file support in useFilePicker hook
|
|
6
|
+
- Android multiple app icon support
|
|
7
|
+
- Android whitelisting toggle support
|
|
8
|
+
|
|
9
|
+
## [0.0.3-canary.12] - 2025-09-26
|
|
10
|
+
|
|
11
|
+
- Universal App Context: Added comprehensive context support for building universal mobile applications
|
|
12
|
+
- OpenTelemetry SDK Integration: Built-in support for observability and performance monitoring with Otel SDK
|
|
13
|
+
|
|
14
|
+
## [0.0.3-canary.11] - 2025-09-26
|
|
15
|
+
|
|
16
|
+
- Build organization: Store APKs and iOS builds organized by generation date and time for better tracking and management
|
|
17
|
+
- Enhanced Android native support: Add support for native keyboard and webview resizing in Android
|
|
18
|
+
- Device info API web support: Web support in device info API
|
|
19
|
+
|
|
3
20
|
## [0.0.3-canary.10] - 2025-09-11
|
|
4
21
|
|
|
5
22
|
- **Sentry import bug fix**
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
1
2
|
<project version="4">
|
|
2
3
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
|
3
4
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK" />
|
|
5
|
+
<component name="ProjectType">
|
|
6
|
+
<option name="id" value="Android" />
|
|
7
|
+
</component>
|
|
4
8
|
</project>
|
|
@@ -26,8 +26,6 @@ fun getLocalIpAddress(): String {
|
|
|
26
26
|
plugins {
|
|
27
27
|
alias(libs.plugins.android.application)
|
|
28
28
|
alias(libs.plugins.jetbrains.kotlin.android)
|
|
29
|
-
alias(libs.plugins.ktlint)
|
|
30
|
-
alias(libs.plugins.detekt)
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
android {
|
|
@@ -144,18 +142,15 @@ dependencies {
|
|
|
144
142
|
implementation("androidx.webkit:webkit:1.12.1")
|
|
145
143
|
implementation("org.json:json:20231013")
|
|
146
144
|
implementation("androidx.core:core-splashscreen:1.0.1")
|
|
147
|
-
|
|
145
|
+
|
|
148
146
|
// Ktor Server dependencies for FrameworkServer (~200KB total)
|
|
149
147
|
implementation("io.ktor:ktor-server-core:2.3.7")
|
|
150
148
|
implementation("io.ktor:ktor-server-netty:2.3.7")
|
|
151
149
|
implementation("io.ktor:ktor-server-content-negotiation:2.3.7")
|
|
152
150
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
|
|
153
|
-
|
|
151
|
+
|
|
154
152
|
// SLF4J simple logger for Ktor (optional, can be excluded if needed)
|
|
155
153
|
implementation("org.slf4j:slf4j-simple:2.0.9")
|
|
156
|
-
|
|
157
|
-
// Detekt plugins for additional checks
|
|
158
|
-
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.7")
|
|
159
154
|
}
|
|
160
155
|
|
|
161
156
|
// Task to verify local IP
|
|
@@ -325,34 +320,15 @@ tasks.register("createAppBundle") {
|
|
|
325
320
|
println("""
|
|
326
321
|
=======================================================
|
|
327
322
|
App Bundle created successfully!
|
|
328
|
-
|
|
323
|
+
|
|
329
324
|
Location: ${project.buildDir}/outputs/bundle/release/app-release.aab
|
|
330
|
-
|
|
325
|
+
|
|
331
326
|
Next steps:
|
|
332
|
-
1. Test your bundle with:
|
|
327
|
+
1. Test your bundle with:
|
|
333
328
|
bundletool build-apks --bundle=app/build/outputs/bundle/release/app-release.aab --output=test.apks
|
|
334
|
-
|
|
329
|
+
|
|
335
330
|
2. Upload to Play Console: https://play.google.com/console
|
|
336
331
|
=======================================================
|
|
337
332
|
""".trimIndent())
|
|
338
333
|
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// ktlint configuration
|
|
342
|
-
ktlint {
|
|
343
|
-
android.set(true)
|
|
344
|
-
ignoreFailures.set(false)
|
|
345
|
-
reporters {
|
|
346
|
-
reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.PLAIN)
|
|
347
|
-
reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE)
|
|
348
|
-
reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.HTML)
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// detekt configuration
|
|
353
|
-
detekt {
|
|
354
|
-
buildUponDefaultConfig = true
|
|
355
|
-
allRules = false
|
|
356
|
-
config.setFrom("$projectDir/detekt-config.yml")
|
|
357
|
-
baseline = file("$projectDir/detekt-baseline.xml")
|
|
358
334
|
}
|
|
@@ -113,9 +113,21 @@ object BridgeMessageValidator {
|
|
|
113
113
|
properties = mapOf(
|
|
114
114
|
"mimeType" to PropertySchema(type = "string"),
|
|
115
115
|
"multiple" to PropertySchema(type = "boolean"),
|
|
116
|
+
"minFileSize" to PropertySchema(
|
|
117
|
+
type = "number",
|
|
118
|
+
minimum = 0
|
|
119
|
+
),
|
|
116
120
|
"maxFileSize" to PropertySchema(
|
|
117
121
|
type = "number",
|
|
118
122
|
minimum = 0
|
|
123
|
+
),
|
|
124
|
+
"minFiles" to PropertySchema(
|
|
125
|
+
type = "number",
|
|
126
|
+
minimum = 1
|
|
127
|
+
),
|
|
128
|
+
"maxFiles" to PropertySchema(
|
|
129
|
+
type = "number",
|
|
130
|
+
minimum = 1
|
|
119
131
|
)
|
|
120
132
|
),
|
|
121
133
|
additionalProperties = false
|
|
@@ -408,6 +420,30 @@ object BridgeMessageValidator {
|
|
|
408
420
|
}
|
|
409
421
|
}
|
|
410
422
|
|
|
423
|
+
if (commandName == "pickFile") {
|
|
424
|
+
val minFiles = if (obj.has("minFiles") && !obj.isNull("minFiles")) obj.optInt("minFiles") else null
|
|
425
|
+
val maxFiles = if (obj.has("maxFiles") && !obj.isNull("maxFiles")) obj.optInt("maxFiles") else null
|
|
426
|
+
if (minFiles != null && maxFiles != null && minFiles > maxFiles) {
|
|
427
|
+
BridgeUtils.logError(TAG, "minFiles cannot be greater than maxFiles for pickFile command")
|
|
428
|
+
return BridgeValidationError(
|
|
429
|
+
message = "minFiles cannot be greater than maxFiles",
|
|
430
|
+
code = "INVALID_FILE_PICKER_OPTIONS",
|
|
431
|
+
eventName = "ON_FILE_PICK_ERROR"
|
|
432
|
+
)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
val minFileSize = if (obj.has("minFileSize") && !obj.isNull("minFileSize")) obj.optLong("minFileSize") else null
|
|
436
|
+
val maxFileSize = if (obj.has("maxFileSize") && !obj.isNull("maxFileSize")) obj.optLong("maxFileSize") else null
|
|
437
|
+
if (minFileSize != null && maxFileSize != null && minFileSize > maxFileSize) {
|
|
438
|
+
BridgeUtils.logError(TAG, "minFileSize cannot be greater than maxFileSize for pickFile command")
|
|
439
|
+
return BridgeValidationError(
|
|
440
|
+
message = "minFileSize cannot be greater than maxFileSize",
|
|
441
|
+
code = "INVALID_FILE_PICKER_OPTIONS",
|
|
442
|
+
eventName = "ON_FILE_PICK_ERROR"
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
411
447
|
return null
|
|
412
448
|
}
|
|
413
449
|
|
package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt
CHANGED
|
@@ -45,6 +45,7 @@ class CustomWebView(
|
|
|
45
45
|
private var buildOptimisation: Boolean = false // Added property for build optimization
|
|
46
46
|
private lateinit var assetLoader: WebViewAssetLoader
|
|
47
47
|
private var allowedUrls: List<String> = emptyList()
|
|
48
|
+
private var accessControlEnabled: Boolean = false
|
|
48
49
|
|
|
49
50
|
// Counters for asset loading statistics
|
|
50
51
|
private var assetLoadAttempts = 0
|
|
@@ -88,6 +89,11 @@ class CustomWebView(
|
|
|
88
89
|
.map { it.trim() }
|
|
89
90
|
.filter { it.isNotEmpty() }
|
|
90
91
|
|
|
92
|
+
// Access control toggle
|
|
93
|
+
accessControlEnabled = properties
|
|
94
|
+
.getProperty("accessControl.enabled", "true")
|
|
95
|
+
.equals("true", ignoreCase = true)
|
|
96
|
+
|
|
91
97
|
// Set initial flags based on buildOptimisation
|
|
92
98
|
if (buildOptimisation) {
|
|
93
99
|
isInitialApiCalled = false
|
|
@@ -103,6 +109,7 @@ class CustomWebView(
|
|
|
103
109
|
Log.d(TAG, "Cache Pattern: $cachePatterns")
|
|
104
110
|
Log.d(TAG, "API Base URL: $apiBaseUrl")
|
|
105
111
|
Log.d(TAG, "Build Optimisation: $buildOptimisation")
|
|
112
|
+
Log.d(TAG, "Access Control Enabled: $accessControlEnabled")
|
|
106
113
|
Log.d(TAG, "Allowed URLs: $allowedUrls")
|
|
107
114
|
Log.d(TAG, "Initial API Called: $isInitialApiCalled")
|
|
108
115
|
Log.d(TAG, "Initial Page Loaded: $isInitialPageLoaded")
|
|
@@ -250,7 +257,69 @@ class CustomWebView(
|
|
|
250
257
|
return shouldCache
|
|
251
258
|
}
|
|
252
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Handle special URL schemes (tel:, mailto:, sms:)
|
|
262
|
+
*/
|
|
263
|
+
private fun handleSpecialScheme(url: String): Boolean {
|
|
264
|
+
val uri = Uri.parse(url)
|
|
265
|
+
val scheme = uri.scheme?.lowercase() ?: return false
|
|
266
|
+
|
|
267
|
+
// Only handle tel, mailto, sms
|
|
268
|
+
if (scheme !in listOf("tel", "mailto", "sms")) {
|
|
269
|
+
return false
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
val intent = when (scheme) {
|
|
274
|
+
"tel" -> Intent(Intent.ACTION_DIAL, uri)
|
|
275
|
+
"mailto" -> Intent(Intent.ACTION_SENDTO, uri)
|
|
276
|
+
"sms" -> Intent(Intent.ACTION_VIEW, uri)
|
|
277
|
+
else -> return false
|
|
278
|
+
}
|
|
253
279
|
|
|
280
|
+
// For mailto, check if any apps can handle it before showing chooser
|
|
281
|
+
if (scheme == "mailto") {
|
|
282
|
+
val packageManager = context.packageManager
|
|
283
|
+
val activities = packageManager.queryIntentActivities(intent, 0)
|
|
284
|
+
|
|
285
|
+
if (activities.isNotEmpty()) {
|
|
286
|
+
// Apps available - show chooser
|
|
287
|
+
val chooser = Intent.createChooser(intent, "Send email")
|
|
288
|
+
chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
289
|
+
context.startActivity(chooser)
|
|
290
|
+
} else {
|
|
291
|
+
// No email apps - try Gmail app, then browser
|
|
292
|
+
try {
|
|
293
|
+
val gmailIntent = Intent(Intent.ACTION_SENDTO, uri).apply {
|
|
294
|
+
setPackage("com.google.android.gm")
|
|
295
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
296
|
+
}
|
|
297
|
+
context.startActivity(gmailIntent)
|
|
298
|
+
if (BuildConfig.DEBUG) {
|
|
299
|
+
Log.d(TAG, "Opened Gmail app")
|
|
300
|
+
}
|
|
301
|
+
} catch (e: android.content.ActivityNotFoundException) {
|
|
302
|
+
// Gmail app not available, open in browser
|
|
303
|
+
openInInAppBrowser("https://mail.google.com")
|
|
304
|
+
if (BuildConfig.DEBUG) {
|
|
305
|
+
Log.d(TAG, "Opened Gmail in browser")
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
context.startActivity(intent)
|
|
311
|
+
}
|
|
312
|
+
return true
|
|
313
|
+
} catch (e: android.content.ActivityNotFoundException) {
|
|
314
|
+
if (BuildConfig.DEBUG) {
|
|
315
|
+
Log.d(TAG, "No app to handle $scheme")
|
|
316
|
+
}
|
|
317
|
+
return true
|
|
318
|
+
} catch (e: Exception) {
|
|
319
|
+
Log.e(TAG, "Error handling $scheme: ${e.message}")
|
|
320
|
+
return true
|
|
321
|
+
}
|
|
322
|
+
}
|
|
254
323
|
|
|
255
324
|
private fun openInInAppBrowser(url: String) {
|
|
256
325
|
try {
|
|
@@ -341,7 +410,7 @@ class CustomWebView(
|
|
|
341
410
|
}
|
|
342
411
|
|
|
343
412
|
// Check if URL is allowed
|
|
344
|
-
if (!isUrlAllowed(url, allowedUrls)) {
|
|
413
|
+
if (accessControlEnabled && !isUrlAllowed(url, allowedUrls)) {
|
|
345
414
|
if (BuildConfig.DEBUG) {
|
|
346
415
|
Log.w(TAG, "🙫 SERVICE_WORKER: Request blocked by access control: $url")
|
|
347
416
|
}
|
|
@@ -477,6 +546,14 @@ class CustomWebView(
|
|
|
477
546
|
request?.url?.let { url ->
|
|
478
547
|
val urlString = url.toString()
|
|
479
548
|
|
|
549
|
+
// Handle special URL schemes (tel:, mailto:, sms:) HERE to prevent page loading
|
|
550
|
+
if (handleSpecialScheme(urlString)) {
|
|
551
|
+
if (BuildConfig.DEBUG) {
|
|
552
|
+
Log.d(TAG, "📞 Special scheme handled in shouldOverrideUrlLoading: $urlString")
|
|
553
|
+
}
|
|
554
|
+
return true // Prevent WebView from trying to load the URL
|
|
555
|
+
}
|
|
556
|
+
|
|
480
557
|
// Check if URL is an external domain
|
|
481
558
|
if (url.scheme in listOf("http", "https") && isExternalDomain(urlString, allowedUrls)) {
|
|
482
559
|
if (BuildConfig.DEBUG) {
|
|
@@ -487,7 +564,7 @@ class CustomWebView(
|
|
|
487
564
|
}
|
|
488
565
|
|
|
489
566
|
// Check if URL is allowed for internal navigation
|
|
490
|
-
if (!isUrlAllowed(urlString, allowedUrls)) {
|
|
567
|
+
if (accessControlEnabled && !isUrlAllowed(urlString, allowedUrls)) {
|
|
491
568
|
if (BuildConfig.DEBUG) {
|
|
492
569
|
Log.w(TAG, "🚫 URL blocked by access control: $urlString")
|
|
493
570
|
}
|
|
@@ -519,17 +596,21 @@ class CustomWebView(
|
|
|
519
596
|
Log.d(TAG, "🔄 INTERCEPT: isInitialPageLoaded: $isInitialPageLoaded")
|
|
520
597
|
}
|
|
521
598
|
|
|
522
|
-
if (
|
|
523
|
-
if (
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
599
|
+
if (accessControlEnabled) {
|
|
600
|
+
if (!isUrlAllowed(url, allowedUrls)) {
|
|
601
|
+
if (BuildConfig.DEBUG) {
|
|
602
|
+
Log.w(TAG, "🚫 STEP_1_FAIL: Network request blocked by access control: $url")
|
|
603
|
+
Log.w(TAG, "🚫 STEP_1_FAIL: Allowed URLs: $allowedUrls")
|
|
604
|
+
}
|
|
605
|
+
// Return an empty response to block the request
|
|
606
|
+
return WebResourceResponse("text/plain", "utf-8", null)
|
|
607
|
+
} else {
|
|
608
|
+
if (BuildConfig.DEBUG) {
|
|
609
|
+
Log.d(TAG, "✅ STEP_1_PASS: URL allowed by access control: $url")
|
|
610
|
+
}
|
|
532
611
|
}
|
|
612
|
+
} else if (BuildConfig.DEBUG) {
|
|
613
|
+
Log.d(TAG, "⚙️ STEP_1_SKIP: Access control disabled, allowing URL: $url")
|
|
533
614
|
}
|
|
534
615
|
|
|
535
616
|
// Handle the initial route request - intercept first request regardless of host
|
|
@@ -863,4 +944,4 @@ class CustomWebView(
|
|
|
863
944
|
// Only enable WebView debugging in debug builds
|
|
864
945
|
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
|
|
865
946
|
}
|
|
866
|
-
}
|
|
947
|
+
}
|