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.
Files changed (182) hide show
  1. package/README.md +4 -5
  2. package/bin/catalyst.js +28 -15
  3. package/changelog.md +139 -30
  4. package/dist/native/androidProject/app/build.gradle.kts +125 -15
  5. package/dist/native/androidProject/app/proguard-rules.pro +85 -14
  6. package/dist/native/androidProject/app/src/debug/res/xml/network_security_config.xml +8 -0
  7. package/dist/native/androidProject/app/src/main/AndroidManifest.xml +10 -3
  8. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/BridgeMessageValidator.kt +552 -0
  9. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CatalystConstants.kt +109 -0
  10. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +605 -124
  11. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +405 -47
  12. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MetricsMonitor.kt +341 -0
  13. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +954 -71
  14. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/SplashActivity.kt +111 -0
  15. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/URLWhitelistManager.kt +231 -0
  16. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/Utils.kt +49 -55
  17. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/WebCacheManager.kt +81 -8
  18. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/design/DesignTokens.kt +307 -0
  19. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/EmulatorDetector.kt +203 -0
  20. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/FridaDetector.kt +187 -0
  21. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityAlertHandler.kt +170 -0
  22. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityAlertUI.kt +366 -0
  23. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityBottomSheet.kt +138 -0
  24. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityCheckManager.kt +511 -0
  25. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityCheckScheduler.kt +68 -0
  26. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +47 -11
  27. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/DeviceInfoUtils.kt +38 -13
  28. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/DownloadUtils.kt +36 -14
  29. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FileUtils.kt +3 -2
  30. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +57 -8
  31. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/IntentUtils.kt +31 -6
  32. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/KeyboardUtil.kt +82 -0
  33. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/NetworkUtils.kt +105 -0
  34. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/NotificationConstants.kt +28 -0
  35. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/NotificationUtils.kt +639 -0
  36. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/SafeAreaUtils.kt +62 -0
  37. package/dist/native/androidProject/app/src/main/res/drawable/splashscreen_fallback.png +0 -0
  38. package/dist/native/androidProject/app/src/main/res/layout/activity_main.xml +1 -0
  39. package/dist/native/androidProject/app/src/main/res/layout/activity_splash.xml +20 -0
  40. package/dist/native/androidProject/app/src/main/res/values/themes.xml +23 -0
  41. package/dist/native/androidProject/app/src/main/res/values-night/themes.xml +11 -0
  42. package/dist/native/androidProject/app/src/main/res/xml/network_security_config.xml +12 -0
  43. package/dist/native/androidProject/app/src/noFcm/java/io/yourname/androidproject/utils/NotificationManager.kt +166 -0
  44. package/dist/native/androidProject/app/src/noFcm/java/io/yourname/androidproject/utils/PushNotificationUtils.kt +94 -0
  45. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/BridgeMessageValidatorTest.kt +267 -0
  46. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/BridgeUtilsTest.kt +286 -0
  47. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CameraUtilsTest.kt +296 -0
  48. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/ConfigMappingTest.kt +594 -0
  49. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CustomWebviewTest.kt +466 -0
  50. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/DeviceInfoUtilsTest.kt +187 -0
  51. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FileUtilsTest.kt +304 -0
  52. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NativeBridgeTest.kt +424 -0
  53. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NotificationUtilsTest.kt +469 -0
  54. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/SecurityBridgeTest.kt +199 -0
  55. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/URLWhitelistManagerTest.kt +462 -0
  56. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/WebCacheManagerTest.kt +391 -0
  57. package/dist/native/androidProject/app/src/withFcm/java/io/yourname/androidproject/utils/NotificationManager.kt +224 -0
  58. package/dist/native/androidProject/app/src/withFcm/java/io/yourname/androidproject/utils/PushNotificationUtils.kt +399 -0
  59. package/dist/native/androidProject/build.gradle.kts +6 -0
  60. package/dist/native/androidProject/gradle/libs.versions.toml +8 -2
  61. package/dist/native/androidSetup.js +4 -3
  62. package/dist/native/assets/catalyst.png +0 -0
  63. package/dist/native/bridge/WebBridge.js +16 -3
  64. package/dist/native/bridge/constants/NativeInterfaces.js +28 -10
  65. package/dist/native/bridge/errors.js +1 -1
  66. package/dist/native/bridge/hooks.js +76 -16
  67. package/dist/native/bridge/safeArea.js +5 -0
  68. package/dist/native/bridge/standardizedHooks.js +23 -5
  69. package/dist/native/bridge/useBaseHook.js +7 -9
  70. package/dist/native/bridge/utils/CameraUtils.js +58 -0
  71. package/dist/native/bridge/utils/FileObjectConverter.js +40 -0
  72. package/dist/native/bridge/utils/NativeBridge.js +57 -8
  73. package/dist/native/buildAppAndroid.js +115 -43
  74. package/dist/native/buildAppIos.js +291 -26
  75. package/dist/native/iosnativeWebView/Configurations/Debug.xcconfig +21 -0
  76. package/dist/native/iosnativeWebView/Configurations/Release.xcconfig +24 -0
  77. package/dist/native/iosnativeWebView/Configurations/Shared.xcconfig +50 -0
  78. package/dist/native/iosnativeWebView/Package.resolved +32 -0
  79. package/dist/native/iosnativeWebView/Package.swift +37 -0
  80. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/LocalNotificationHandler.swift +293 -0
  81. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/NotificationManager.swift +299 -0
  82. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/PushNotificationHandler.swift +199 -0
  83. package/dist/native/iosnativeWebView/Sources/Core/Constants/CatalystConstants.swift +125 -0
  84. package/dist/native/iosnativeWebView/Sources/Core/Constants/ConfigConstants.swift +70 -0
  85. package/dist/native/iosnativeWebView/Sources/Core/Notifications/NotificationModels.swift +156 -0
  86. package/dist/native/iosnativeWebView/Sources/Core/Protocols/NotificationHandlerProtocol.swift +35 -0
  87. package/dist/native/iosnativeWebView/Sources/Core/Utils/BootTimingUtility.swift +26 -0
  88. package/dist/native/iosnativeWebView/{iosnativeWebView → Sources/Core/Utils}/CacheManager.swift +69 -36
  89. package/dist/native/iosnativeWebView/Sources/Core/Utils/CameraTransportUtils.swift +363 -0
  90. package/dist/native/iosnativeWebView/Sources/Core/Utils/DeviceInfoUtils.swift +67 -0
  91. package/dist/native/iosnativeWebView/Sources/Core/Utils/FilePickerHandler.swift +1053 -0
  92. package/dist/native/iosnativeWebView/Sources/Core/Utils/FrameworkServerUtils.swift +901 -0
  93. package/dist/native/iosnativeWebView/{iosnativeWebView → Sources/Core/Utils}/ImageHandler.swift +206 -20
  94. package/dist/native/iosnativeWebView/Sources/Core/Utils/NetworkMonitor.swift +112 -0
  95. package/dist/native/iosnativeWebView/Sources/Core/Utils/NotificationHandlerProvider.swift +20 -0
  96. package/dist/native/iosnativeWebView/Sources/Core/Utils/NullNotificationHandler.swift +85 -0
  97. package/dist/native/iosnativeWebView/{iosnativeWebView → Sources/Core/Utils}/ResourceURLProtocol.swift +60 -25
  98. package/dist/native/iosnativeWebView/Sources/Core/Utils/SafeAreaUtils.swift +132 -0
  99. package/dist/native/iosnativeWebView/Sources/Core/Utils/ScreenSecureManager.swift +131 -0
  100. package/dist/native/iosnativeWebView/Sources/Core/Utils/URLWhitelistManager.swift +224 -0
  101. package/dist/native/iosnativeWebView/Sources/Core/Utils/WebKitConfig.swift +43 -0
  102. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeCommandHandler.swift +991 -0
  103. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeDelegateHandler.swift +166 -0
  104. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeFileHandler.swift +340 -0
  105. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeJavaScriptInterface.swift +149 -0
  106. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +611 -0
  107. package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +334 -0
  108. package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashView.swift +95 -0
  109. package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashViewModel.swift +86 -0
  110. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +181 -0
  111. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewModel.swift +182 -0
  112. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +395 -0
  113. package/dist/native/iosnativeWebView/iosnativeWebView/AppDelegate.swift +84 -0
  114. package/dist/native/iosnativeWebView/iosnativeWebView/Assets.xcassets/AccentColor.colorset/Contents.json +8 -8
  115. package/dist/native/iosnativeWebView/iosnativeWebView/Assets.xcassets/AppIcon.appiconset/Contents.json +10 -10
  116. package/dist/native/iosnativeWebView/iosnativeWebView/Assets.xcassets/Contents.json +4 -4
  117. package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +65 -10
  118. package/dist/native/iosnativeWebView/iosnativeWebView/Info-Release.plist +21 -0
  119. package/dist/native/iosnativeWebView/iosnativeWebView/Info.plist +13 -0
  120. package/dist/native/iosnativeWebView/iosnativeWebView/Preview Content/Preview Assets.xcassets/Contents.json +4 -4
  121. package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebView.entitlements +12 -0
  122. package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebViewApp.swift +80 -0
  123. package/dist/native/iosnativeWebView/iosnativeWebView/localhost.p12 +0 -0
  124. package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +118 -100
  125. package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +69 -0
  126. package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/xcshareddata/xcschemes/iosnativeWebView.xcscheme +9 -4
  127. package/dist/native/iosnativeWebView/iosnativeWebView.xctestplan +47 -0
  128. package/dist/native/iosnativeWebView/iosnativeWebViewTests/BootTimingUtilityTests.swift +236 -0
  129. package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeCommandHandlerSecurityTests.swift +212 -0
  130. package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeMessageValidatorTests.swift +418 -0
  131. package/dist/native/iosnativeWebView/iosnativeWebViewTests/CacheManagerTests.swift +390 -0
  132. package/dist/native/iosnativeWebView/iosnativeWebViewTests/ConfigMappingTests.swift +299 -0
  133. package/dist/native/iosnativeWebView/iosnativeWebViewTests/FilePickerHandlerTests.swift +383 -0
  134. package/dist/native/iosnativeWebView/iosnativeWebViewTests/FrameworkServerUtilsTests.swift +391 -0
  135. package/dist/native/iosnativeWebView/iosnativeWebViewTests/NativeBridgeTests.swift +581 -0
  136. package/dist/native/iosnativeWebView/iosnativeWebViewTests/NotificationHandlerTests.swift +430 -0
  137. package/dist/native/iosnativeWebView/iosnativeWebViewTests/ScreenSecureManagerTests.swift +121 -0
  138. package/dist/native/iosnativeWebView/iosnativeWebViewTests/URLWhitelistManagerTests.swift +367 -0
  139. package/dist/native/iosnativeWebView/iosnativeWebViewTests/WebViewTests.swift +440 -0
  140. package/dist/native/renameAndroidProject.js +80 -70
  141. package/dist/native/setupEmulatorIos.js +22 -5
  142. package/dist/native/setupServer.js +11 -10
  143. package/dist/native/terminalProgress.js +10 -10
  144. package/dist/native/utils.js +4 -5
  145. package/dist/otel.js +4 -0
  146. package/dist/scripts/build.js +2 -2
  147. package/dist/scripts/devBuild.js +2 -2
  148. package/dist/scripts/loadEnvironmentVariables.js +3 -1
  149. package/dist/scripts/scriptUtils.js +2 -2
  150. package/dist/sentry.js +4 -1
  151. package/dist/server/renderer/document/Body.js +4 -4
  152. package/dist/server/renderer/handler.js +7 -5
  153. package/dist/webpack/development.client.babel.js +1 -1
  154. package/mcp_v2/conversion-tasks.json +326 -0
  155. package/mcp_v2/knowledge-base.json +1068 -0
  156. package/mcp_v2/lib/helpers.js +165 -0
  157. package/mcp_v2/mcp.js +563 -0
  158. package/mcp_v2/package.json +13 -0
  159. package/mcp_v2/schema.sql +88 -0
  160. package/mcp_v2/setup.js +278 -0
  161. package/mcp_v2/tools/build.js +686 -0
  162. package/mcp_v2/tools/config.js +453 -0
  163. package/mcp_v2/tools/conversion.js +799 -0
  164. package/mcp_v2/tools/debug.js +113 -0
  165. package/mcp_v2/tools/knowledge.js +219 -0
  166. package/mcp_v2/tools/sync.js +23 -0
  167. package/mcp_v2/tools/tasks.js +945 -0
  168. package/package.json +11 -15
  169. package/dist/native/androidProject/.idea/.name +0 -1
  170. package/dist/native/androidProject/.idea/AndroidProjectSystem.xml +0 -6
  171. package/dist/native/androidProject/.idea/compiler.xml +0 -6
  172. package/dist/native/androidProject/.idea/deploymentTargetSelector.xml +0 -10
  173. package/dist/native/androidProject/.idea/gradle.xml +0 -18
  174. package/dist/native/androidProject/.idea/migrations.xml +0 -10
  175. package/dist/native/androidProject/.idea/misc.xml +0 -10
  176. package/dist/native/androidProject/.idea/runConfigurations.xml +0 -17
  177. package/dist/native/androidProject/.idea/vcs.xml +0 -6
  178. package/dist/native/iosnativeWebView/iosnativeWebView/NativeBridge.swift +0 -217
  179. package/dist/native/iosnativeWebView/iosnativeWebView/WebView.swift +0 -96
  180. package/dist/native/iosnativeWebView/iosnativeWebView/WebViewModel.swift +0 -47
  181. package/dist/native/iosnativeWebView/iosnativeWebView/WebViewNavigationDelegate.swift +0 -140
  182. package/license +0 -10
@@ -12,7 +12,7 @@
12
12
  </intent>
13
13
  </queries>
14
14
  <application
15
- android:allowBackup="true"
15
+ android:allowBackup="${allowBackup}"
16
16
  android:dataExtractionRules="@xml/data_extraction_rules"
17
17
  android:fullBackupContent="@xml/backup_rules"
18
18
  android:hardwareAccelerated="true"
@@ -21,11 +21,13 @@
21
21
  android:roundIcon="@mipmap/ic_launcher_round"
22
22
  android:supportsRtl="true"
23
23
  android:theme="@style/Theme.AndroidProject"
24
- android:usesCleartextTraffic="${allowCleartextTraffic}"
24
+ android:usesCleartextTraffic="true"
25
+ android:networkSecurityConfig="@xml/network_security_config"
25
26
  tools:targetApi="31">
26
27
  <activity
27
- android:name="io.yourname.androidproject.MainActivity"
28
+ android:name="io.yourname.androidproject.SplashActivity"
28
29
  android:exported="true"
30
+ android:theme="@style/Theme.AndroidProject.Splash"
29
31
  android:screenOrientation="portrait">
30
32
  <intent-filter>
31
33
  <action android:name="android.intent.action.MAIN" />
@@ -33,6 +35,11 @@
33
35
  <category android:name="android.intent.category.LAUNCHER" />
34
36
  </intent-filter>
35
37
  </activity>
38
+ <activity
39
+ android:name="io.yourname.androidproject.MainActivity"
40
+ android:exported="false"
41
+ android:screenOrientation="portrait">
42
+ </activity>
36
43
  <provider
37
44
  android:name="androidx.core.content.FileProvider"
38
45
  android:authorities="io.yourname.androidproject.fileprovider"
@@ -0,0 +1,552 @@
1
+ package io.yourname.androidproject
2
+
3
+ import android.util.Log
4
+ import io.yourname.androidproject.utils.BridgeUtils
5
+ import org.json.JSONObject
6
+ import org.json.JSONException
7
+
8
+ /**
9
+ * Bridge message validation using custom schema validation
10
+ * Mirrors iOS BridgeMessageValidator.swift for cross-platform consistency
11
+ * Zero external dependencies for minimal APK impact
12
+ */
13
+
14
+ // MARK: - Validation Result Data Classes
15
+
16
+ /**
17
+ * Result of bridge message validation
18
+ */
19
+ data class BridgeValidationResult(
20
+ val isValid: Boolean,
21
+ val command: String?,
22
+ val params: Any?,
23
+ val body: JSONObject?,
24
+ val error: BridgeValidationError?
25
+ )
26
+
27
+ /**
28
+ * Validation error details
29
+ */
30
+ data class BridgeValidationError(
31
+ val message: String,
32
+ val code: String,
33
+ val eventName: String
34
+ )
35
+
36
+ // MARK: - Schema Definition Classes
37
+
38
+ /**
39
+ * Property schema definition
40
+ */
41
+ data class PropertySchema(
42
+ val type: String,
43
+ val enum: List<String>? = null,
44
+ val minimum: Number? = null,
45
+ val maximum: Number? = null
46
+ )
47
+
48
+ /**
49
+ * Object schema definition
50
+ */
51
+ data class SchemaDefinition(
52
+ val type: String = "object",
53
+ val properties: Map<String, PropertySchema>,
54
+ val required: List<String> = emptyList(),
55
+ val additionalProperties: Boolean = true
56
+ )
57
+
58
+ // MARK: - Bridge Message Validator
59
+
60
+ object BridgeMessageValidator {
61
+
62
+ private const val TAG = "BridgeMessageValidator"
63
+ private const val MAX_MESSAGE_SIZE = 10 * 1024 * 1024 // 10MB (match iOS)
64
+
65
+ // Valid commands — single source of truth from CatalystConstants
66
+ private val validCommands = CatalystConstants.Bridge.VALID_COMMANDS
67
+
68
+ // MARK: - Schema Definitions (mirror iOS schemas)
69
+
70
+ private val schemas = mapOf(
71
+ "openCamera" to SchemaDefinition(
72
+ properties = mapOf(
73
+ "quality" to PropertySchema(
74
+ type = "string",
75
+ enum = listOf("high", "medium", "low")
76
+ ),
77
+ "allowsEditing" to PropertySchema(type = "boolean"),
78
+ "preferredCameraType" to PropertySchema(
79
+ type = "string",
80
+ enum = listOf("front", "back")
81
+ ),
82
+ "flashMode" to PropertySchema(
83
+ type = "string",
84
+ enum = listOf("auto", "on", "off")
85
+ ),
86
+ "videoMaximumDuration" to PropertySchema(
87
+ type = "number",
88
+ minimum = 0,
89
+ maximum = 3600
90
+ )
91
+ ),
92
+ additionalProperties = false
93
+ ),
94
+
95
+ "requestCameraPermission" to SchemaDefinition(
96
+ properties = mapOf(
97
+ "showRationale" to PropertySchema(type = "boolean"),
98
+ "fallbackToSettings" to PropertySchema(type = "boolean"),
99
+ "includeDetails" to PropertySchema(type = "boolean")
100
+ ),
101
+ additionalProperties = false
102
+ ),
103
+
104
+ "pickFile" to SchemaDefinition(
105
+ properties = mapOf(
106
+ "mimeType" to PropertySchema(type = "string"),
107
+ "multiple" to PropertySchema(type = "boolean"),
108
+ "minFileSize" to PropertySchema(
109
+ type = "number",
110
+ minimum = 0
111
+ ),
112
+ "maxFileSize" to PropertySchema(
113
+ type = "number",
114
+ minimum = 0
115
+ ),
116
+ "minFiles" to PropertySchema(
117
+ type = "number",
118
+ minimum = 1
119
+ ),
120
+ "maxFiles" to PropertySchema(
121
+ type = "number",
122
+ minimum = 1
123
+ )
124
+ ),
125
+ additionalProperties = false
126
+ ),
127
+
128
+ "requestHapticFeedback" to SchemaDefinition(
129
+ properties = mapOf(
130
+ "type" to PropertySchema(
131
+ type = "string",
132
+ enum = listOf(
133
+ "light", "medium", "heavy", "selection",
134
+ "impact", "notification", "VIRTUAL_KEY",
135
+ "LONG_PRESS", "DEFAULT"
136
+ )
137
+ ),
138
+ "intensity" to PropertySchema(
139
+ type = "number",
140
+ minimum = 0,
141
+ maximum = 1
142
+ )
143
+ ),
144
+ required = listOf("type"),
145
+ additionalProperties = false
146
+ ),
147
+
148
+ "openFileWithIntent" to SchemaDefinition(
149
+ properties = mapOf(
150
+ "url" to PropertySchema(type = "string"),
151
+ "filename" to PropertySchema(type = "string"),
152
+ "mimeType" to PropertySchema(type = "string"),
153
+ "data" to PropertySchema(type = "string")
154
+ ),
155
+ additionalProperties = false
156
+ ),
157
+
158
+ "getDeviceInfo" to SchemaDefinition(
159
+ properties = emptyMap(),
160
+ additionalProperties = false
161
+ ),
162
+
163
+ "logger" to SchemaDefinition(
164
+ properties = emptyMap(),
165
+ additionalProperties = false
166
+ )
167
+ )
168
+
169
+ // Commands that support flexible parameter formats (string or object)
170
+ // Mirror iOS flexible commands for backward compatibility
171
+ private val flexibleCommands = setOf(
172
+ "openCamera",
173
+ "requestCameraPermission",
174
+ "pickFile",
175
+ "requestHapticFeedback",
176
+ "openFileWithIntent"
177
+ )
178
+
179
+ // MARK: - Public Validation Interface
180
+
181
+ /**
182
+ * Validate a bridge message
183
+ * @param body The message body as JSONObject
184
+ * @return BridgeValidationResult with validation status and error details
185
+ */
186
+ fun validate(body: JSONObject): BridgeValidationResult {
187
+ BridgeUtils.logDebug(TAG, "Starting validation for bridge message")
188
+
189
+ // Step 1: Validate message size
190
+ try {
191
+ val messageSize = body.toString().toByteArray(Charsets.UTF_8).size
192
+ if (messageSize > MAX_MESSAGE_SIZE) {
193
+ BridgeUtils.logError(TAG, "Message size exceeds limit: $messageSize > $MAX_MESSAGE_SIZE")
194
+ return BridgeValidationResult(
195
+ isValid = false,
196
+ command = null,
197
+ params = null,
198
+ body = body,
199
+ error = BridgeValidationError(
200
+ message = "Message too large",
201
+ code = "MESSAGE_TOO_LARGE",
202
+ eventName = "BRIDGE_ERROR"
203
+ )
204
+ )
205
+ }
206
+ } catch (e: Exception) {
207
+ BridgeUtils.logError(TAG, "Error checking message size", e)
208
+ }
209
+
210
+ // Step 2: Validate root message structure
211
+ val rootValidationResult = validateRootMessageStructure(body)
212
+ if (rootValidationResult == null) {
213
+ return BridgeValidationResult(
214
+ isValid = false,
215
+ command = null,
216
+ params = null,
217
+ body = body,
218
+ error = BridgeValidationError(
219
+ message = "Invalid root message structure",
220
+ code = "INVALID_ROOT_STRUCTURE",
221
+ eventName = "BRIDGE_ERROR"
222
+ )
223
+ )
224
+ }
225
+
226
+ val command = rootValidationResult.first
227
+ val params = rootValidationResult.second
228
+
229
+ // Step 3: Validate command is supported
230
+ if (!validCommands.contains(command)) {
231
+ BridgeUtils.logError(TAG, "Unsupported command: $command")
232
+ return BridgeValidationResult(
233
+ isValid = false,
234
+ command = command,
235
+ params = params,
236
+ body = body,
237
+ error = BridgeValidationError(
238
+ message = "Unsupported command: $command",
239
+ code = "UNSUPPORTED_COMMAND",
240
+ eventName = "BRIDGE_ERROR"
241
+ )
242
+ )
243
+ }
244
+
245
+ // Step 4: Validate command-specific parameters
246
+ if (params != null) {
247
+ val validationError = validateCommandParameters(command, params)
248
+ if (validationError != null) {
249
+ return BridgeValidationResult(
250
+ isValid = false,
251
+ command = command,
252
+ params = params,
253
+ body = body,
254
+ error = validationError
255
+ )
256
+ }
257
+ }
258
+
259
+ BridgeUtils.logDebug(TAG, "Message validation successful for command: $command")
260
+ return BridgeValidationResult(
261
+ isValid = true,
262
+ command = command,
263
+ params = params,
264
+ body = body,
265
+ error = null
266
+ )
267
+ }
268
+
269
+ // MARK: - Private Validation Helpers
270
+
271
+ /**
272
+ * Validate root message structure (command + data fields)
273
+ * @return Pair of (command, params) or null if invalid
274
+ */
275
+ private fun validateRootMessageStructure(body: JSONObject): Pair<String, Any?>? {
276
+ // Validate required command field
277
+ val command = body.optString("command", "")
278
+ if (command.isEmpty()) {
279
+ BridgeUtils.logError(TAG, "Root message missing 'command' field")
280
+ return null
281
+ }
282
+
283
+ // Optional: Validate timestamp if present
284
+ if (body.has("timestamp")) {
285
+ val timestamp = body.optString("timestamp")
286
+ if (timestamp.isNotEmpty()) {
287
+ // Basic ISO8601 format check (simplified)
288
+ if (!timestamp.matches(Regex("\\d{4}-\\d{2}-\\d{2}T.*"))) {
289
+ BridgeUtils.logWarning(TAG, "Invalid timestamp format in message: $timestamp")
290
+ }
291
+ }
292
+ }
293
+
294
+ // Optional: Validate requestId if present
295
+ if (body.has("requestId")) {
296
+ val requestId = body.optString("requestId")
297
+ if (requestId.isEmpty()) {
298
+ BridgeUtils.logWarning(TAG, "Empty requestId in message")
299
+ }
300
+ }
301
+
302
+ // Extract params from 'data' field
303
+ val params = if (body.has("data")) {
304
+ val data = body.get("data")
305
+ when (data) {
306
+ JSONObject.NULL -> null
307
+ is JSONObject -> data
308
+ is String -> data
309
+ else -> data
310
+ }
311
+ } else {
312
+ null
313
+ }
314
+
315
+ return Pair(command, params)
316
+ }
317
+
318
+ /**
319
+ * Validate command-specific parameters against schema
320
+ */
321
+ private fun validateCommandParameters(command: String, params: Any): BridgeValidationError? {
322
+ // Handle flexible commands (allow string, object, or null for legacy compatibility)
323
+ if (flexibleCommands.contains(command)) {
324
+ // Allow string or null parameters
325
+ if (params is String || params == null) {
326
+ BridgeUtils.logDebug(TAG, "Command '$command' received string/nil parameters - allowing for legacy compatibility")
327
+ return null
328
+ }
329
+
330
+ // If it's an object, validate against schema if available
331
+ if (params is JSONObject) {
332
+ val schema = schemas[command]
333
+ if (schema == null) {
334
+ BridgeUtils.logDebug(TAG, "No schema defined for command: $command, allowing object parameters")
335
+ return null
336
+ }
337
+
338
+ return validateObjectAgainstSchema(params, schema, command)
339
+ }
340
+
341
+ BridgeUtils.logWarning(TAG, "Command '$command' received unexpected parameter type: ${params.javaClass.simpleName}")
342
+ return null // Allow for backward compatibility
343
+ }
344
+
345
+ // For strict commands, require object parameters and validate against schema
346
+ val schema = schemas[command]
347
+ if (schema == null) {
348
+ BridgeUtils.logDebug(TAG, "No schema defined for command: $command, allowing all parameters")
349
+ return null
350
+ }
351
+
352
+ if (params !is JSONObject) {
353
+ BridgeUtils.logError(TAG, "Parameters for command '$command' must be an object")
354
+ return BridgeValidationError(
355
+ message = "Invalid parameters format for $command",
356
+ code = "INVALID_PARAMS",
357
+ eventName = "BRIDGE_ERROR"
358
+ )
359
+ }
360
+
361
+ return validateObjectAgainstSchema(params, schema, command)
362
+ }
363
+
364
+ /**
365
+ * Validate a JSON object against a schema definition
366
+ */
367
+ private fun validateObjectAgainstSchema(
368
+ obj: JSONObject,
369
+ schema: SchemaDefinition,
370
+ commandName: String
371
+ ): BridgeValidationError? {
372
+ // Validate schema type
373
+ if (schema.type != "object") {
374
+ BridgeUtils.logWarning(TAG, "Schema for $commandName is not of type 'object'")
375
+ }
376
+
377
+ // Validate required fields
378
+ for (requiredField in schema.required) {
379
+ if (!obj.has(requiredField) || obj.isNull(requiredField)) {
380
+ BridgeUtils.logError(TAG, "Required field '$requiredField' missing for command $commandName")
381
+ return BridgeValidationError(
382
+ message = "Missing required field: $requiredField",
383
+ code = "MISSING_REQUIRED_FIELD",
384
+ eventName = "BRIDGE_ERROR"
385
+ )
386
+ }
387
+ }
388
+
389
+ // Validate each property
390
+ val keys = obj.keys()
391
+ while (keys.hasNext()) {
392
+ val key = keys.next()
393
+ val value = obj.get(key)
394
+
395
+ if (value == JSONObject.NULL) {
396
+ continue // Skip null values
397
+ }
398
+
399
+ val propertySchema = schema.properties[key]
400
+ if (propertySchema != null) {
401
+ val error = validatePropertyAgainstSchema(value, propertySchema, key, commandName)
402
+ if (error != null) {
403
+ return error
404
+ }
405
+ } else if (!schema.additionalProperties) {
406
+ BridgeUtils.logError(TAG, "Additional property '$key' not allowed for command $commandName")
407
+ return BridgeValidationError(
408
+ message = "Additional property '$key' not allowed",
409
+ code = "ADDITIONAL_PROPERTY_NOT_ALLOWED",
410
+ eventName = "BRIDGE_ERROR"
411
+ )
412
+ }
413
+ }
414
+
415
+ if (commandName == "pickFile") {
416
+ val minFiles = if (obj.has("minFiles") && !obj.isNull("minFiles")) obj.optInt("minFiles") else null
417
+ val maxFiles = if (obj.has("maxFiles") && !obj.isNull("maxFiles")) obj.optInt("maxFiles") else null
418
+ if (minFiles != null && maxFiles != null && minFiles > maxFiles) {
419
+ BridgeUtils.logError(TAG, "minFiles cannot be greater than maxFiles for pickFile command")
420
+ return BridgeValidationError(
421
+ message = "minFiles cannot be greater than maxFiles",
422
+ code = "INVALID_FILE_PICKER_OPTIONS",
423
+ eventName = "ON_FILE_PICK_ERROR"
424
+ )
425
+ }
426
+
427
+ val minFileSize = if (obj.has("minFileSize") && !obj.isNull("minFileSize")) obj.optLong("minFileSize") else null
428
+ val maxFileSize = if (obj.has("maxFileSize") && !obj.isNull("maxFileSize")) obj.optLong("maxFileSize") else null
429
+ if (minFileSize != null && maxFileSize != null && minFileSize > maxFileSize) {
430
+ BridgeUtils.logError(TAG, "minFileSize cannot be greater than maxFileSize for pickFile command")
431
+ return BridgeValidationError(
432
+ message = "minFileSize cannot be greater than maxFileSize",
433
+ code = "INVALID_FILE_PICKER_OPTIONS",
434
+ eventName = "ON_FILE_PICK_ERROR"
435
+ )
436
+ }
437
+ }
438
+
439
+ return null
440
+ }
441
+
442
+ /**
443
+ * Validate a single property against its schema
444
+ */
445
+ private fun validatePropertyAgainstSchema(
446
+ value: Any,
447
+ schema: PropertySchema,
448
+ propertyName: String,
449
+ commandName: String
450
+ ): BridgeValidationError? {
451
+ when (schema.type) {
452
+ "string" -> {
453
+ if (value !is String) {
454
+ BridgeUtils.logError(TAG, "Property '$propertyName' must be a string for command $commandName")
455
+ return BridgeValidationError(
456
+ message = "Property '$propertyName' must be a string",
457
+ code = "INVALID_TYPE",
458
+ eventName = "BRIDGE_ERROR"
459
+ )
460
+ }
461
+
462
+ // Validate enum values
463
+ if (schema.enum != null && !schema.enum.contains(value)) {
464
+ BridgeUtils.logError(TAG, "Property '$propertyName' value '$value' not in allowed enum values")
465
+ return BridgeValidationError(
466
+ message = "Invalid value for $propertyName: $value",
467
+ code = "INVALID_ENUM_VALUE",
468
+ eventName = "BRIDGE_ERROR"
469
+ )
470
+ }
471
+ }
472
+
473
+ "number" -> {
474
+ val numberValue = when (value) {
475
+ is Number -> value.toDouble()
476
+ is String -> value.toDoubleOrNull()
477
+ else -> null
478
+ }
479
+
480
+ if (numberValue == null) {
481
+ BridgeUtils.logError(TAG, "Property '$propertyName' must be a number for command $commandName")
482
+ return BridgeValidationError(
483
+ message = "Property '$propertyName' must be a number",
484
+ code = "INVALID_TYPE",
485
+ eventName = "BRIDGE_ERROR"
486
+ )
487
+ }
488
+
489
+ // Validate minimum
490
+ if (schema.minimum != null && numberValue < schema.minimum.toDouble()) {
491
+ BridgeUtils.logError(TAG, "Property '$propertyName' value $numberValue is below minimum ${schema.minimum}")
492
+ return BridgeValidationError(
493
+ message = "Value for $propertyName below minimum",
494
+ code = "VALUE_BELOW_MINIMUM",
495
+ eventName = "BRIDGE_ERROR"
496
+ )
497
+ }
498
+
499
+ // Validate maximum
500
+ if (schema.maximum != null && numberValue > schema.maximum.toDouble()) {
501
+ BridgeUtils.logError(TAG, "Property '$propertyName' value $numberValue is above maximum ${schema.maximum}")
502
+ return BridgeValidationError(
503
+ message = "Value for $propertyName above maximum",
504
+ code = "VALUE_ABOVE_MAXIMUM",
505
+ eventName = "BRIDGE_ERROR"
506
+ )
507
+ }
508
+ }
509
+
510
+ "boolean" -> {
511
+ if (value !is Boolean) {
512
+ BridgeUtils.logError(TAG, "Property '$propertyName' must be a boolean for command $commandName")
513
+ return BridgeValidationError(
514
+ message = "Property '$propertyName' must be a boolean",
515
+ code = "INVALID_TYPE",
516
+ eventName = "BRIDGE_ERROR"
517
+ )
518
+ }
519
+ }
520
+
521
+ else -> {
522
+ BridgeUtils.logWarning(TAG, "Unknown schema type '${schema.type}' for property $propertyName")
523
+ }
524
+ }
525
+
526
+ return null
527
+ }
528
+
529
+ // MARK: - Schema Management
530
+
531
+ /**
532
+ * Add a custom schema for a command
533
+ */
534
+ fun addCustomSchema(command: String, schema: SchemaDefinition) {
535
+ // Note: Since schemas is immutable, we'd need to make it mutable if dynamic schemas are needed
536
+ BridgeUtils.logInfo(TAG, "Custom schema support not yet implemented for runtime additions")
537
+ }
538
+
539
+ /**
540
+ * Get all available commands that have schemas
541
+ */
542
+ fun getAvailableCommands(): Set<String> {
543
+ return schemas.keys
544
+ }
545
+
546
+ /**
547
+ * Check if a command has a schema defined
548
+ */
549
+ fun hasSchema(command: String): Boolean {
550
+ return schemas.containsKey(command)
551
+ }
552
+ }
@@ -0,0 +1,109 @@
1
+ package io.yourname.androidproject
2
+
3
+ /**
4
+ * CatalystConstants.kt
5
+ * Unified configuration constants for the Android native web bridge
6
+ *
7
+ * Mirrors the iOS-side constants to keep behavior consistent across platforms
8
+ */
9
+ object CatalystConstants {
10
+
11
+ // MARK: - File Transport
12
+ object FileTransport {
13
+ // Max size to inline over the JS bridge as Base64 (2 MB)
14
+ const val BASE64_SIZE_LIMIT: Long = 2 * 1024 * 1024L
15
+ const val BASE64_SIZE_LIMIT_MB: Int = 2
16
+
17
+ // Max size supported overall via the framework server (100 MB)
18
+ const val FRAMEWORK_SERVER_SIZE_LIMIT: Long = 100 * 1024 * 1024L
19
+ const val FRAMEWORK_SERVER_SIZE_LIMIT_MB: Int = 100
20
+ }
21
+
22
+ // MARK: - Image Processing
23
+ object ImageProcessing {
24
+ object Quality {
25
+ const val HIGH: Int = 90
26
+ const val MEDIUM: Int = 70
27
+ const val LOW: Int = 50
28
+ }
29
+ // Default JPEG compression quality
30
+ const val DEFAULT_QUALITY: Int = Quality.MEDIUM
31
+ }
32
+
33
+ // MARK: - Network Server (Framework Server)
34
+ object NetworkServer {
35
+ // Port range to probe for starting the local HTTP server
36
+ const val PORT_RANGE_START: Int = 18080
37
+ const val PORT_RANGE_END: Int = 18110
38
+
39
+ // Session/file timeout and cleanup cadence
40
+ const val SESSION_TIMEOUT_MINUTES: Long = 10L // 10 minutes
41
+ const val SESSION_TIMEOUT_MS: Long = SESSION_TIMEOUT_MINUTES * 60 * 1000L
42
+ const val CLEANUP_INTERVAL_SECONDS: Long = 60L // 1 minute
43
+ const val CLEANUP_INTERVAL_MS: Long = CLEANUP_INTERVAL_SECONDS * 1000L
44
+
45
+ // Connection policies
46
+ const val MAX_CONNECTIONS: Int = 16
47
+ const val CONNECTION_TIMEOUT_SECONDS: Int = 30
48
+ }
49
+
50
+ // MARK: - Error Codes
51
+ object ErrorCodes {
52
+ const val BAD_REQUEST: Int = 400
53
+ const val FILE_NOT_FOUND: Int = 404
54
+ const val INTERNAL_SERVER_ERROR: Int = 500
55
+ }
56
+
57
+ // MARK: - Bridge Limits / Validation
58
+ object Bridge {
59
+ // Safety limit for inbound JS message size (128 KB)
60
+ const val MAX_MESSAGE_SIZE: Int = 128 * 1024
61
+
62
+ // Command execution timeout window
63
+ const val COMMAND_TIMEOUT_SECONDS: Int = 30
64
+
65
+ // Whitelisted commands the bridge will accept
66
+ val VALID_COMMANDS: Set<String> = setOf(
67
+ "openCamera",
68
+ "requestCameraPermission",
69
+ "getDeviceInfo",
70
+ "logger",
71
+ "pickFile",
72
+ "openFileWithIntent",
73
+ "requestHapticFeedback",
74
+ "setScreenSecure",
75
+ "getScreenSecure",
76
+ "clearWebData"
77
+ )
78
+ }
79
+
80
+ // MARK: - Caching
81
+ object Cache {
82
+ // Fresh/stale windows for SWR behavior
83
+ const val FRESH_WINDOW_SECONDS: Long = 60L // seconds
84
+ const val STALE_WINDOW_SECONDS: Long = 5 * 60L // seconds
85
+
86
+ // URLCache capacities (bytes)
87
+ const val MEMORY_CAPACITY: Long = 10 * 1024 * 1024L // 10 MB
88
+ const val DISK_CAPACITY: Long = 50 * 1024 * 1024L // 50 MB
89
+ }
90
+
91
+ // MARK: - File Provider
92
+ object FileProvider {
93
+ const val AUTHORITY: String = "io.yourname.androidproject.fileprovider"
94
+ }
95
+
96
+ // MARK: - Logging Configuration
97
+ object Logging {
98
+ object Categories {
99
+ const val NATIVE_BRIDGE = "NativeBridge"
100
+ const val BRIDGE_UTILS = "BridgeUtils"
101
+ const val MESSAGE_VALIDATOR = "BridgeMessageValidator"
102
+ const val FILE_UTILS = "FileUtils"
103
+ const val INTENT_UTILS = "IntentUtils"
104
+ const val CAMERA_UTILS = "CameraUtils"
105
+ const val DOWNLOAD_UTILS = "DownloadUtils"
106
+ const val FRAMEWORK_SERVER_UTILS = "FrameworkServerUtils"
107
+ }
108
+ }
109
+ }