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.
Files changed (27) hide show
  1. package/changelog.md +17 -0
  2. package/dist/native/androidProject/.idea/misc.xml +4 -0
  3. package/dist/native/androidProject/app/build.gradle.kts +6 -30
  4. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/BridgeMessageValidator.kt +36 -0
  5. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +94 -13
  6. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +290 -67
  7. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/DownloadUtils.kt +6 -3
  8. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/BridgeMessageValidatorTest.kt +34 -1
  9. package/dist/native/androidProject/gradle/libs.versions.toml +0 -4
  10. package/dist/native/assets/catalyst.png +0 -0
  11. package/dist/native/bridge/hooks.js +13 -26
  12. package/dist/native/bridge/utils/NativeBridge.js +2 -2
  13. package/dist/native/buildAppAndroid.js +1 -12
  14. package/dist/native/buildAppIos.js +1 -1
  15. package/dist/native/iosnativeWebView/iosnativeWebView/BridgeCommandHandler.swift +5 -3
  16. package/dist/native/iosnativeWebView/iosnativeWebView/BridgeDelegateHandler.swift +5 -60
  17. package/dist/native/iosnativeWebView/iosnativeWebView/BridgeMessageValidator.swift +47 -1
  18. package/dist/native/iosnativeWebView/iosnativeWebView/FilePickerHandler.swift +393 -111
  19. package/dist/native/iosnativeWebView/iosnativeWebView/NativeBridge.swift +2 -2
  20. package/dist/native/iosnativeWebView/iosnativeWebView/WebViewNavigationDelegate.swift +30 -0
  21. package/package.json +2 -2
  22. package/dist/native/androidProject/.idea/deviceManager.xml +0 -13
  23. package/dist/native/androidProject/app/.editorconfig +0 -17
  24. package/dist/native/androidProject/app/detekt-config.yml +0 -562
  25. package/dist/native/androidProject/app/src/main/assets/webview_config.properties +0 -36
  26. package/dist/native/iosnativeWebView/.swiftlint.yml +0 -118
  27. 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
 
@@ -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 (!isUrlAllowed(url, allowedUrls)) {
523
- if (BuildConfig.DEBUG) {
524
- Log.w(TAG, "🚫 STEP_1_FAIL: Network request blocked by access control: $url")
525
- Log.w(TAG, "🚫 STEP_1_FAIL: Allowed URLs: $allowedUrls")
526
- }
527
- // Return an empty response to block the request
528
- return WebResourceResponse("text/plain", "utf-8", null)
529
- } else {
530
- if (BuildConfig.DEBUG) {
531
- Log.d(TAG, "✅ STEP_1_PASS: URL allowed by access control: $url")
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
+ }