catalyst-core 0.1.0-canary.7 → 0.1.0-canary.9

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 (29) hide show
  1. package/README.md +4 -4
  2. package/changelog.md +26 -0
  3. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/BridgeMessageValidator.kt +2 -10
  4. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/SecurityBridgeTest.kt +199 -0
  5. package/dist/native/bridge/hooks.js +4 -4
  6. package/dist/native/bridge/useBaseHook.js +5 -4
  7. package/dist/native/bridge/utils/NativeBridge.js +4 -4
  8. package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +13 -2
  9. package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +36 -0
  10. package/dist/native/iosnativeWebView/iosnativeWebView.xctestplan +1 -0
  11. package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeCommandHandlerSecurityTests.swift +212 -0
  12. package/dist/native/iosnativeWebView/iosnativeWebViewTests/FrameworkServerUtilsTests.swift +14 -4
  13. package/dist/native/iosnativeWebView/iosnativeWebViewTests/ScreenSecureManagerTests.swift +121 -0
  14. package/dist/native/iosnativeWebView/iosnativeWebViewTests/WebViewTests.swift +9 -21
  15. package/mcp_v2/conversion-tasks.json +324 -369
  16. package/mcp_v2/knowledge-base.json +1067 -1449
  17. package/mcp_v2/lib/helpers.js +125 -105
  18. package/mcp_v2/mcp.js +487 -290
  19. package/mcp_v2/package.json +11 -11
  20. package/mcp_v2/setup.js +207 -191
  21. package/mcp_v2/tools/build.js +629 -392
  22. package/mcp_v2/tools/config.js +417 -226
  23. package/mcp_v2/tools/conversion.js +749 -442
  24. package/mcp_v2/tools/debug.js +95 -44
  25. package/mcp_v2/tools/knowledge.js +171 -165
  26. package/mcp_v2/tools/sync.js +15 -13
  27. package/mcp_v2/tools/tasks.js +754 -653
  28. package/package.json +6 -13
  29. package/license +0 -10
package/README.md CHANGED
@@ -7,8 +7,8 @@
7
7
 
8
8
  ## Table of Contents
9
9
 
10
- - Overview
11
- - Installation
10
+ - Overview
11
+ - Installation
12
12
 
13
13
  ## Overview
14
14
 
@@ -20,8 +20,8 @@ This version adds support for Universal app for both Android and iOS. Please che
20
20
 
21
21
  **System Requirements**
22
22
 
23
- - Node version 20.4.0 or later
24
- - Compatible with **macOS** and **Linux**
23
+ - Node version 20.4.0 or later
24
+ - Compatible with **macOS** and **Linux**
25
25
 
26
26
  **Automatic Installation**
27
27
 
package/changelog.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.0-beta.2] - 2026-05-06
4
+
5
+ - Moved Catalyst into a monorepo structure with `catalyst-core`, `create-catalyst-app`, the Catalyst docs app, and the Catalyst core test app managed from one repository.
6
+ - Replaced the internal-package pre-release flow with a local release sandbox that scaffolds a real app using current-branch CCA and a locally packed current-branch `catalyst-core`.
7
+
8
+ ## [0.1.0-beta.1] - 2026-04-15
9
+
10
+ - Promoted `0.1.0-canary.7` to the first proper beta release after stabilization.
11
+ - No code changes from `0.1.0-canary.7`; this release marks the same build as production-ready beta.
12
+
13
+ ## [0.1.0-canary.7] - 2026-03-31
14
+
15
+ - Introduced Catalyst MCP v2 with a new setup flow, database schema, knowledge base, and source-aware migration tooling for stronger project guidance and conversion workflows.
16
+ - Expanded MCP/framework knowledge coverage across SEO, observability, webpack, React Compiler, CLI, file conventions, and native hooks, while improving setup and verification messaging across supported MCP clients.
17
+
18
+ ## [0.1.0-canary.6] - 2026-03-09
19
+
20
+ - Hardened URL whitelisting with thread-safety improvements, broader test coverage, and related iOS build fixes to make access-control behavior more reliable.
21
+ - Improved compatibility and runtime resilience by softening bridge environment mismatch failures and preserving backward compatibility for `useDataProtection` on older native binaries.
22
+
23
+ ## [0.1.0-canary.5] - 2026-02-27
24
+
25
+ - Strengthened native app security with backup restrictions, screen-capture protection, web data clearing, and related Android/iOS test coverage.
26
+ - Improved universal app runtime behavior with safe-area inset support, edge-to-edge rendering, and notification permission override fixes.
27
+ - Expanded platform support with offline fallback handling, notification/access-control refinements, localhost HTTP allowances for local development, and file-picker/HTTPS server improvements.
28
+
3
29
  ## [0.1.0-canary.4] - 2026-02-12
4
30
 
5
31
  - Added Google Sign-In support for both Android and iOS in Catalyst, enabling a unified native authentication experience for apps built on the framework.
@@ -62,16 +62,8 @@ object BridgeMessageValidator {
62
62
  private const val TAG = "BridgeMessageValidator"
63
63
  private const val MAX_MESSAGE_SIZE = 10 * 1024 * 1024 // 10MB (match iOS)
64
64
 
65
- // Valid commands (should match iOS CatalystConstants.Bridge.validCommands)
66
- private val validCommands = setOf(
67
- "openCamera",
68
- "requestCameraPermission",
69
- "pickFile",
70
- "requestHapticFeedback",
71
- "openFileWithIntent",
72
- "getDeviceInfo",
73
- "logger"
74
- )
65
+ // Valid commands — single source of truth from CatalystConstants
66
+ private val validCommands = CatalystConstants.Bridge.VALID_COMMANDS
75
67
 
76
68
  // MARK: - Schema Definitions (mirror iOS schemas)
77
69
 
@@ -0,0 +1,199 @@
1
+ package io.yourname.androidproject
2
+
3
+ import org.json.JSONObject
4
+ import org.junit.Assert.*
5
+ import org.junit.Test
6
+
7
+ /**
8
+ * Unit tests for security bridge command routing and JSON injection safety.
9
+ *
10
+ * Coverage:
11
+ * - setScreenSecure command routing (5 tests)
12
+ * - getScreenSecure command routing (2 tests)
13
+ * - clearWebData command routing (2 tests)
14
+ * - JSON injection safety in error responses (3 tests)
15
+ *
16
+ * Total: 12 tests
17
+ *
18
+ * Note: These tests operate on static parsing/validation logic and JSON data
19
+ * structures. Full lifecycle (FLAG_SECURE, CookieManager) is exercised in
20
+ * device integration tests; here we confirm command acceptance, param parsing,
21
+ * and that error payloads produced via JSONObject cannot carry injected content.
22
+ */
23
+ class SecurityBridgeTest {
24
+
25
+ // ============================================================
26
+ // CATEGORY 1: setScreenSecure COMMAND ROUTING (5 tests)
27
+ // ============================================================
28
+
29
+ @Test
30
+ fun `test setScreenSecure command is accepted by validator`() {
31
+ val messageJson = """{"command": "setScreenSecure", "data": {"enable": true}}"""
32
+
33
+ val result = NativeBridge.parseAndValidateMessage(messageJson)
34
+
35
+ assertTrue("setScreenSecure should be a valid command", result.isValid)
36
+ assertEquals("setScreenSecure", result.command)
37
+ assertNull(result.error)
38
+ }
39
+
40
+ @Test
41
+ fun `test setScreenSecure enable true parsed from params`() {
42
+ val params = JSONObject().apply { put("enable", true) }
43
+
44
+ val enable = params.optBoolean("enable", false)
45
+
46
+ assertTrue("enable should be true", enable)
47
+ }
48
+
49
+ @Test
50
+ fun `test setScreenSecure enable false parsed from params`() {
51
+ val params = JSONObject().apply { put("enable", false) }
52
+
53
+ val enable = params.optBoolean("enable", true)
54
+
55
+ assertFalse("enable should be false", enable)
56
+ }
57
+
58
+ @Test
59
+ fun `test setScreenSecure defaults to true when params are malformed`() {
60
+ // NativeBridge.setScreenSecure falls back to true when JSON parse fails
61
+ val malformedParams = "not-json"
62
+ val enable = try {
63
+ JSONObject(malformedParams).optBoolean("enable", true)
64
+ } catch (e: Exception) {
65
+ true
66
+ }
67
+
68
+ assertTrue("Malformed params should default to enable=true", enable)
69
+ }
70
+
71
+ @Test
72
+ fun `test setScreenSecure success response shape`() {
73
+ val enable = true
74
+ val response = JSONObject().apply {
75
+ put("secure", enable)
76
+ put("success", true)
77
+ }
78
+
79
+ assertTrue(response.getBoolean("secure"))
80
+ assertTrue(response.getBoolean("success"))
81
+ // Verify no extra fields bleed in
82
+ assertEquals(2, response.length())
83
+ }
84
+
85
+ // ============================================================
86
+ // CATEGORY 2: getScreenSecure COMMAND ROUTING (2 tests)
87
+ // ============================================================
88
+
89
+ @Test
90
+ fun `test getScreenSecure command is accepted by validator`() {
91
+ val messageJson = """{"command": "getScreenSecure", "data": {}}"""
92
+
93
+ val result = NativeBridge.parseAndValidateMessage(messageJson)
94
+
95
+ assertTrue("getScreenSecure should be a valid command", result.isValid)
96
+ assertEquals("getScreenSecure", result.command)
97
+ assertNull(result.error)
98
+ }
99
+
100
+ @Test
101
+ fun `test getScreenSecure status response shape`() {
102
+ // Validate that the JSON payload produced by getScreenSecure is well-formed
103
+ val isSecure = false
104
+ val response = JSONObject().apply {
105
+ put("secure", isSecure)
106
+ put("success", true)
107
+ }
108
+
109
+ assertFalse(response.getBoolean("secure"))
110
+ assertTrue(response.getBoolean("success"))
111
+ assertEquals(2, response.length())
112
+ }
113
+
114
+ // ============================================================
115
+ // CATEGORY 3: clearWebData COMMAND ROUTING (2 tests)
116
+ // ============================================================
117
+
118
+ @Test
119
+ fun `test clearWebData command is accepted by validator`() {
120
+ val messageJson = """{"command": "clearWebData", "data": {}}"""
121
+
122
+ val result = NativeBridge.parseAndValidateMessage(messageJson)
123
+
124
+ assertTrue("clearWebData should be a valid command", result.isValid)
125
+ assertEquals("clearWebData", result.command)
126
+ assertNull(result.error)
127
+ }
128
+
129
+ @Test
130
+ fun `test clearWebData success response shape`() {
131
+ val cookiesRemoved = true
132
+ val response = JSONObject().apply {
133
+ put("success", true)
134
+ put("cookiesRemoved", cookiesRemoved)
135
+ }
136
+
137
+ assertTrue(response.getBoolean("success"))
138
+ assertTrue(response.getBoolean("cookiesRemoved"))
139
+ assertEquals(2, response.length())
140
+ }
141
+
142
+ // ============================================================
143
+ // CATEGORY 4: JSON INJECTION SAFETY (3 tests)
144
+ // ============================================================
145
+
146
+ @Test
147
+ fun `test error message with injection characters is safe via JSONObject`() {
148
+ // Simulate an exception message that contains characters dangerous in naive JS
149
+ // string interpolation: double-quotes and backslashes.
150
+ // JSONObject must escape them so the serialized JSON stays valid and cannot
151
+ // break out of the surrounding JS string literal in notifyWebJson.
152
+ val injectionPayload = "failed\"; window[\"evil\"]=\"injected"
153
+
154
+ val response = JSONObject().apply {
155
+ put("success", false)
156
+ put("error", injectionPayload)
157
+ }
158
+
159
+ val serialized = response.toString()
160
+
161
+ // The raw unescaped double-quote sequence that would break JS must not appear
162
+ assertFalse(
163
+ "Unescaped double-quote injection sequence must not appear in serialized JSON",
164
+ serialized.contains("\"evil\"")
165
+ )
166
+ // The value must survive a round-trip intact
167
+ assertEquals(injectionPayload, JSONObject(serialized).getString("error"))
168
+ }
169
+
170
+ @Test
171
+ fun `test error message with backslash sequences is safe via JSONObject`() {
172
+ val backslashPayload = "err\\\"injected\\\""
173
+
174
+ val response = JSONObject().apply {
175
+ put("success", false)
176
+ put("error", backslashPayload)
177
+ }
178
+
179
+ // Round-trip must survive without throwing
180
+ val roundTripped = JSONObject(response.toString()).getString("error")
181
+ assertEquals(backslashPayload, roundTripped)
182
+ }
183
+
184
+ @Test
185
+ fun `test setScreenSecure and clearWebData commands are in VALID_COMMANDS whitelist`() {
186
+ assertTrue(
187
+ "setScreenSecure must be whitelisted",
188
+ CatalystConstants.Bridge.VALID_COMMANDS.contains("setScreenSecure")
189
+ )
190
+ assertTrue(
191
+ "getScreenSecure must be whitelisted",
192
+ CatalystConstants.Bridge.VALID_COMMANDS.contains("getScreenSecure")
193
+ )
194
+ assertTrue(
195
+ "clearWebData must be whitelisted",
196
+ CatalystConstants.Bridge.VALID_COMMANDS.contains("clearWebData")
197
+ )
198
+ }
199
+ }
@@ -103,12 +103,12 @@ if(_NativeBridge.default.isAvailable()){try{// Call native bridge directly witho
103
103
  if(_NativeBridge.default.isAndroid&&window.NativeBridge?.checkNotificationPermissionStatus){window.NativeBridge.checkNotificationPermissionStatus(null);}else if(_NativeBridge.default.isIOS&&window.webkit?.messageHandlers?.NativeBridge){window.webkit.messageHandlers.NativeBridge.postMessage({command:"checkNotificationPermissionStatus",data:null});}}catch(error){console.error("🔔 Error checking notification permission status:",error);}}window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.LOCAL_NOTIFICATION_SCHEDULED,data=>{try{const result=typeof data==="string"?JSON.parse(data):data;if(result?.success===false||result?.error){base.handleNativeError(result.error||result);return;}base.setDataAndComplete(result);}catch(error){base.handleNativeError(error);}});window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.LOCAL_NOTIFICATION_CANCELLED,data=>{try{const result=typeof data==="string"?JSON.parse(data):data;if(result?.success===false||result?.error){base.handleNativeError(result.error||result);return;}base.setDataAndComplete(result);}catch(error){base.handleNativeError(error);}});window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.PUSH_NOTIFICATION_TOKEN,data=>{try{const result=typeof data==="string"?JSON.parse(data):data;if(result?.error){base.handleNativeError(result.error);return;}setPushToken(result.token);base.setDataAndComplete(result);}catch(error){base.handleNativeError(error);}});window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.NOTIFICATION_RECEIVED,data=>{const notification=typeof data==="string"?JSON.parse(data):data;setLastNotification(notification);});window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.NOTIFICATION_TAPPED,data=>{try{const result=typeof data==="string"?JSON.parse(data):data;base.setDataAndComplete(result);}catch(error){base.handleNativeError(error);}});window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.NOTIFICATION_ACTION_PERFORMED,data=>{const action=typeof data==="string"?JSON.parse(data):data;base.setDataAndComplete(action);});window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.TOPIC_SUBSCRIPTION_RESULT,data=>{try{const result=typeof data==="string"?JSON.parse(data):data;if(result?.error){base.handleNativeError(result.error);return;}const success=result?.success;if(success===false){base.handleNativeError(result);return;}base.setDataAndComplete(result);}catch(error){base.handleNativeError(error);}});window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.SUBSCRIBED_TOPICS_RESULT,data=>{try{const result=typeof data==="string"?JSON.parse(data):data;if(result?.error){base.handleNativeError(result.error);return;}if(result?.success===false){base.handleNativeError(result);return;}setSubscribedTopics(result.topics||[]);base.setDataAndComplete(result);}catch(error){base.handleNativeError(error);}});return()=>{// Cleanup - unregister from shared callback system
104
104
  unregister();// Cleanup other callbacks
105
105
  window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.LOCAL_NOTIFICATION_SCHEDULED);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.LOCAL_NOTIFICATION_CANCELLED);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.PUSH_NOTIFICATION_TOKEN);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.NOTIFICATION_RECEIVED);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.NOTIFICATION_TAPPED);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.NOTIFICATION_ACTION_PERFORMED);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.TOPIC_SUBSCRIPTION_RESULT);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.SUBSCRIBED_TOPICS_RESULT);};},[]);// Local notification scheduling with all supported styles
106
- const scheduleLocal=config=>{base.executeOperation(()=>{_NativeBridge.default.notification.scheduleLocal(config);},"schedule local notification");};const cancelLocal=notificationId=>{base.executeOperation(()=>{_NativeBridge.default.notification.cancelLocal(notificationId);},"cancel notification");};const registerForPush=()=>{base.executeOperation(()=>{_NativeBridge.default.notification.registerForPush();},"register for push");};const updateBadge=count=>{_NativeBridge.default.notification.updateBadge(count);setBadges(count);};const subscribeToTopic=topic=>{base.executeOperation(()=>{_NativeBridge.default.notification.subscribeToTopic(topic);},"subscribe to topic");};const unsubscribeFromTopic=topic=>{base.executeOperation(()=>{_NativeBridge.default.notification.unsubscribeFromTopic(topic);},"unsubscribe from topic");};const getSubscribedTopics=()=>{base.executeOperation(()=>{_NativeBridge.default.notification.getSubscribedTopics();},"get subscribed topics");};return{// Standardized interface
106
+ const scheduleLocal=config=>{base.executeOperation(()=>{_NativeBridge.default.notification.scheduleLocal(config);},"schedule local notification");};const cancelLocal=notificationId=>{base.executeOperation(()=>{_NativeBridge.default.notification.cancelLocal(notificationId);},"cancel notification");};const registerForPush=()=>{base.executeOperation(()=>{_NativeBridge.default.notification.registerForPush();},"register for push");};const updateBadge=count=>{base.callNative(()=>_NativeBridge.default.notification.updateBadge(count));setBadges(count);};const subscribeToTopic=topic=>{base.executeOperation(()=>{_NativeBridge.default.notification.subscribeToTopic(topic);},"subscribe to topic");};const unsubscribeFromTopic=topic=>{base.executeOperation(()=>{_NativeBridge.default.notification.unsubscribeFromTopic(topic);},"unsubscribe from topic");};const getSubscribedTopics=()=>{base.executeOperation(()=>{_NativeBridge.default.notification.getSubscribedTopics();},"get subscribed topics");};return{// Standardized interface
107
107
  data:base.data,loading:base.loading,progress:base.progress,error:base.error,execute:scheduleLocal,clear:base.clear,clearError:base.clearError,// Notification-specific
108
108
  permissionStatus,pushToken,badges,lastNotification,subscribedTopics,scheduleLocal,cancelLocal,registerForPush,updateBadge,subscribeToTopic,unsubscribeFromTopic,getSubscribedTopics};};/**
109
109
  * Network status hook
110
110
  * Listens to native network changes and exposes online/offline state
111
- */exports.useNotification=useNotification;const useNetworkStatus=()=>{const initialOnline=typeof navigator!=="undefined"?navigator.onLine:true;const[status,setStatus]=(0,_react.useState)({online:initialOnline,type:null});const[error,setError]=(0,_react.useState)(null);const isNative=_NativeBridge.default.isNativeEnvironment;(0,_react.useEffect)(()=>{if(typeof window==="undefined")return;if(!window.WebBridge)return;if(!isNative)return;const handleStatus=payload=>{try{const parsed=parseNativePayload(payload)||{};setStatus({online:Boolean(parsed.online),type:parsed.type||null});setError(null);}catch(e){console.error("🌐 Error parsing network status:",e);setError(e.message);}};window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.NETWORK_STATUS_CHANGED,handleStatus);try{_NativeBridge.default.network.getStatus();}catch(e){setError(e.message||"Network status unavailable");}return()=>{window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.NETWORK_STATUS_CHANGED);};},[isNative]);return{...status,error};};/**
111
+ */exports.useNotification=useNotification;const useNetworkStatus=()=>{const initialOnline=typeof navigator!=="undefined"?navigator.onLine:true;const[status,setStatus]=(0,_react.useState)({online:initialOnline,type:null});const[error,setError]=(0,_react.useState)(null);const isNative=_NativeBridge.default.isAvailable();(0,_react.useEffect)(()=>{if(typeof window==="undefined")return;if(!window.WebBridge)return;if(!isNative)return;const handleStatus=payload=>{try{const parsed=parseNativePayload(payload)||{};setStatus({online:Boolean(parsed.online),type:parsed.type||null});setError(null);}catch(e){console.error("🌐 Error parsing network status:",e);setError(e.message);}};window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.NETWORK_STATUS_CHANGED,handleStatus);try{_NativeBridge.default.network.getStatus();}catch(e){setError(e.message||"Network status unavailable");}return()=>{window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.NETWORK_STATUS_CHANGED);};},[isNative]);return{...status,error};};/**
112
112
  * React hook for data protection primitives.
113
113
  *
114
114
  * Exposes screen-capture prevention and web data clearing as
@@ -130,8 +130,8 @@ permissionStatus,pushToken,badges,lastNotification,subscribedTopics,scheduleLoca
130
130
  * clearError: () => void,
131
131
  * }}
132
132
  */exports.useNetworkStatus=useNetworkStatus;const useDataProtection=()=>{const base=(0,_useBaseHook.useBaseHook)("useDataProtection");const[screenSecure,setScreenSecureState]=(0,_react.useState)(false);// Server-side rendering safety
133
- if(typeof window==="undefined"){return{screenSecure:false,setScreenSecure:()=>{},clearWebData:()=>{},data:null,loading:false,progress:null,error:null,isWeb:true,isNative:false,clear:()=>{},clearError:()=>{}};}if(!window.WebBridge){throw new Error("WebBridge is not initialized. Call WebBridge.init() first.");}(0,_react.useEffect)(()=>{const handleScreenSecureSet=data=>{try{const result=typeof data==="string"?JSON.parse(data):data;if(result?.error){base.handleNativeError(result.error);return;}setScreenSecureState(Boolean(result?.secure));base.setDataAndComplete(result);}catch(error){base.handleNativeError(error);}};const handleScreenSecureError=data=>{try{const result=typeof data==="string"?JSON.parse(data):data;base.handleNativeError(result?.error||"setScreenSecure failed");}catch(error){base.handleNativeError(error);}};const handleWebDataCleared=data=>{try{const result=typeof data==="string"?JSON.parse(data):data;if(result?.error){base.handleNativeError(result.error);return;}base.setDataAndComplete(result);}catch(error){base.handleNativeError(error);}};const handleWebDataClearError=data=>{try{const result=typeof data==="string"?JSON.parse(data):data;base.handleNativeError(result?.error||"clearWebData failed");}catch(error){base.handleNativeError(error);}};const handleScreenSecureStatus=data=>{try{const result=typeof data==="string"?JSON.parse(data):data;if(result?.error){base.handleNativeError(result.error);return;}setScreenSecureState(Boolean(result?.secure));}catch(error){base.handleNativeError(error);}};const cleanup=registerNativeHandlers([[_NativeInterfaces.NATIVE_CALLBACKS.ON_SCREEN_SECURE_SET,handleScreenSecureSet],[_NativeInterfaces.NATIVE_CALLBACKS.ON_SCREEN_SECURE_STATUS,handleScreenSecureStatus],[_NativeInterfaces.NATIVE_CALLBACKS.ON_SCREEN_SECURE_ERROR,handleScreenSecureError],[_NativeInterfaces.NATIVE_CALLBACKS.ON_WEB_DATA_CLEARED,handleWebDataCleared],[_NativeInterfaces.NATIVE_CALLBACKS.ON_WEB_DATA_CLEAR_ERROR,handleWebDataClearError]]);// Initialize screenSecure state from native on mount
134
- _NativeBridge.default.security.getScreenSecure();return cleanup;},[base.setDataAndComplete,base.handleNativeError]);const setScreenSecure=(0,_react.useCallback)(enable=>{base.executeOperation(()=>{_NativeBridge.default.security.setScreenSecure(enable);},"setScreenSecure");},[base.executeOperation]);const clearWebData=(0,_react.useCallback)(()=>{base.executeOperation(()=>{_NativeBridge.default.security.clearWebData();},"clearWebData");},[base.executeOperation]);return{// Standardized base interface
133
+ if(typeof window==="undefined"){return{screenSecure:false,setScreenSecure:()=>{},clearWebData:()=>{},data:null,loading:false,progress:null,error:null,isWeb:true,isNative:false,clear:()=>{},clearError:()=>{}};}if(!window.WebBridge){throw new Error("WebBridge is not initialized. Call WebBridge.init() first.");}(0,_react.useEffect)(()=>{const handleScreenSecureSet=data=>{try{const result=typeof data==="string"?JSON.parse(data):data;if(result?.error){base.handleNativeError(result.error);return;}setScreenSecureState(Boolean(result?.secure));base.setDataAndComplete(result);}catch(error){base.handleNativeError(error);}};const handleScreenSecureError=data=>{try{const result=typeof data==="string"?JSON.parse(data):data;base.handleNativeError(result?.error||"setScreenSecure failed");}catch(error){base.handleNativeError(error);}};const handleWebDataCleared=data=>{try{const result=typeof data==="string"?JSON.parse(data):data;if(result?.error){base.handleNativeError(result.error);return;}base.setDataAndComplete(result);}catch(error){base.handleNativeError(error);}};const handleWebDataClearError=data=>{try{const result=typeof data==="string"?JSON.parse(data):data;base.handleNativeError(result?.error||"clearWebData failed");}catch(error){base.handleNativeError(error);}};const handleScreenSecureStatus=data=>{try{const result=typeof data==="string"?JSON.parse(data):data;if(result?.error){base.handleNativeError(result.error);return;}setScreenSecureState(Boolean(result?.secure));}catch(error){base.handleNativeError(error);}};const cleanup=registerNativeHandlers([[_NativeInterfaces.NATIVE_CALLBACKS.ON_SCREEN_SECURE_SET,handleScreenSecureSet],[_NativeInterfaces.NATIVE_CALLBACKS.ON_SCREEN_SECURE_STATUS,handleScreenSecureStatus],[_NativeInterfaces.NATIVE_CALLBACKS.ON_SCREEN_SECURE_ERROR,handleScreenSecureError],[_NativeInterfaces.NATIVE_CALLBACKS.ON_WEB_DATA_CLEARED,handleWebDataCleared],[_NativeInterfaces.NATIVE_CALLBACKS.ON_WEB_DATA_CLEAR_ERROR,handleWebDataClearError]]);// Initialize screenSecure state from native on mount (native env only)
134
+ base.callNative(()=>_NativeBridge.default.security.getScreenSecure());return cleanup;},[base.setDataAndComplete,base.handleNativeError]);const setScreenSecure=(0,_react.useCallback)(enable=>{base.callNative(()=>_NativeBridge.default.security.setScreenSecure(enable));},[base.callNative]);const clearWebData=(0,_react.useCallback)(()=>{base.callNative(()=>_NativeBridge.default.security.clearWebData());},[base.callNative]);return{// Standardized base interface
135
135
  data:base.data,loading:base.loading,progress:base.progress,error:base.error,isWeb:base.isWeb,isNative:base.isNative,clear:base.clear,clearError:base.clearError,// Security state
136
136
  screenSecure,// Security actions
137
137
  setScreenSecure,clearWebData};};exports.useDataProtection=useDataProtection;const useSafeArea=()=>{const fromGlobal=(0,_safeArea.getSafeAreaFromGlobal)();const initialValue=fromGlobal||{..._safeArea.DEFAULT_INSETS};const[insets,setInsets]=(0,_react.useState)(()=>initialValue);(0,_react.useEffect)(()=>{if(typeof window==="undefined")return;if(!window.WebBridge)return;if(!_NativeBridge.default.isAvailable())return;const handleInsetsUpdate=data=>{try{const parsed=typeof data==="string"?JSON.parse(data):data;const normalized={top:Number(parsed.top)||0,right:Number(parsed.right)||0,bottom:Number(parsed.bottom)||0,left:Number(parsed.left)||0};setInsets(normalized);(0,_safeArea.setSafeAreaGlobal)(normalized);}catch(error){console.error("Error parsing safe area insets:",error);setInsets({..._safeArea.DEFAULT_INSETS});}};window.WebBridge.register("ON_SAFE_AREA_INSETS_UPDATED",handleInsetsUpdate);_NativeBridge.default.safeArea.get();return()=>{window.WebBridge.unregister("ON_SAFE_AREA_INSETS_UPDATED");};},[]);return insets;};exports.useSafeArea=useSafeArea;
@@ -17,9 +17,10 @@ bytesTotal:null// Total bytes
17
17
  const updateProgress=(0,_react.useCallback)(updates=>{setProgress(prev=>({...prev,...updates}));},[]);const resetProgress=(0,_react.useCallback)(()=>{setProgress({state:"idle",percentage:null,message:null,phase:null,transport:null,bytesLoaded:null,bytesTotal:null});},[]);const startProgress=(0,_react.useCallback)((phase=null,message=null)=>{updateProgress({state:"active",phase,message,percentage:null});},[updateProgress]);const completeProgress=(0,_react.useCallback)(()=>{updateProgress({state:"complete",percentage:100});},[updateProgress]);const errorProgress=(0,_react.useCallback)(()=>{updateProgress({state:"error"});},[updateProgress]);// Error handling utilities
18
18
  const handleNativeError=(0,_react.useCallback)(nativeError=>{const standardError=(0,_errors.translateError)(nativeError);setError(standardError);setLoading(false);errorProgress();// Development logging
19
19
  if((0,_errors.isDevelopment)()){console.group(`🚨 ${hookName} Error`);console.log("Standard Error:",standardError);console.log("Native Error:",nativeError);console.groupEnd();}return standardError;},[hookName,errorProgress]);const clearError=(0,_react.useCallback)(()=>{setError(null);if(progress.state==="error"){resetProgress();}},[progress.state,resetProgress]);// Data management utilities
20
- const setDataAndComplete=(0,_react.useCallback)(newData=>{setData(newData);setLoading(false);completeProgress();setError(null);if((0,_errors.isDevelopment)()){console.log(`✅ ${hookName} Success:`,newData);}},[hookName,completeProgress]);const clear=(0,_react.useCallback)(()=>{setData(null);setError(null);resetProgress();if((0,_errors.isDevelopment)()){console.log(`🗑️ ${hookName} Cleared`);}},[hookName,resetProgress]);// Operation wrapper that handles common patterns
21
- const executeOperation=(0,_react.useCallback)((operationCallback,operationName="operation")=>{try{if(isWeb()){console.warn(`${hookName} requires web fallback implementation (isWeb: true)`);return;}if(!isNative()){throw new Error("Native bridge not available");}setLoading(true);setError(null);startProgress("starting",`Starting ${operationName}...`);if((0,_errors.isDevelopment)()){console.log(`🚀 ${hookName} ${operationName} started`);}// Execute the actual operation
22
- operationCallback();}catch(err){handleNativeError(err);console.error(`❌ ${hookName} ${operationName} failed:`,err);}},[hookName,isWeb,startProgress,handleNativeError]);// Environment flags (computed values, not functions)
20
+ const setDataAndComplete=(0,_react.useCallback)(newData=>{setData(newData);setLoading(false);completeProgress();setError(null);if((0,_errors.isDevelopment)()){console.log(`✅ ${hookName} Success:`,newData);}},[hookName,completeProgress]);const clear=(0,_react.useCallback)(()=>{setData(null);setError(null);resetProgress();if((0,_errors.isDevelopment)()){console.log(`🗑️ ${hookName} Cleared`);}},[hookName,resetProgress]);// Fire-and-forget native call — no-ops silently on web, routes errors through handleNativeError on native
21
+ const callNative=(0,_react.useCallback)(fn=>{if(!isNative()){if((0,_errors.isDevelopment)()){console.warn(`${hookName} callNative skipped — not in native environment`);}return;}try{fn();}catch(err){if((0,_errors.isDevelopment)()){console.warn(`${hookName} callNative failed silently:`,err);}}},[hookName,isNative]);// Operation wrapper that handles common patterns
22
+ const executeOperation=(0,_react.useCallback)((operationCallback,operationName="operation")=>{try{if(isWeb()){console.warn(`${hookName} requires web fallback implementation (isWeb: true)`);return;}if(!isNative()){console.error(`${hookName} executeOperation: Native bridge not available`);return;}setLoading(true);setError(null);startProgress("starting",`Starting ${operationName}...`);if((0,_errors.isDevelopment)()){console.log(`🚀 ${hookName} ${operationName} started`);}// Execute the actual operation
23
+ operationCallback();}catch(err){handleNativeError(err);console.error(`❌ ${hookName} ${operationName} failed:`,err);}},[hookName,isWeb,isNative,startProgress,handleNativeError]);// Environment flags (computed values, not functions)
23
24
  const environmentFlags={isWeb:isWeb(),isNative:isNative()};// Return standardized interface
24
25
  return{// Data state
25
26
  data,// Loading states
@@ -27,7 +28,7 @@ loading,progress,// Error handling
27
28
  error,// Environment detection
28
29
  ...environmentFlags,// Actions
29
30
  clear,clearError,// Internal utilities for specific hooks
30
- setData,setLoading,setError,setProgress,updateProgress,resetProgress,startProgress,completeProgress,errorProgress,setDataAndComplete,handleNativeError,executeOperation};};/**
31
+ setData,setLoading,setError,setProgress,updateProgress,resetProgress,startProgress,completeProgress,errorProgress,setDataAndComplete,handleNativeError,callNative,executeOperation};};/**
31
32
  * Hook for development debugging and testing
32
33
  * @param {string} hookName
33
34
  * @returns {Object} Environment information and utilities
@@ -10,10 +10,10 @@
10
10
  * Log environment detection info for debugging
11
11
  */_logEnvironmentInfo(){console.log("🌉 NativeBridge Environment Detection:",{isAndroid:this.isAndroid,isIOS:this.isIOS,isNativeEnvironment:this.isNativeEnvironment,hasAndroidBridge:!!window.NativeBridge,hasIOSBridge:!!window.webkit?.messageHandlers?.NativeBridge});}/**
12
12
  * Validate command before execution
13
- */_validateCommand(command){if(!command){throw new Error("Command is required");}if(!(0,_NativeInterfaces.isValidCommand)(command)){throw new Error(`Invalid command: ${command}. Available commands: ${Object.values(_NativeInterfaces.NATIVE_COMMANDS).join(", ")}`);}if(!this.isNativeEnvironment){throw new Error("Native bridge not available. Ensure you are running in a native WebView environment.");}}/**
13
+ */_validateCommand(command){if(!command){throw new Error("Command is required");}if(!(0,_NativeInterfaces.isValidCommand)(command)){throw new Error(`Invalid command: ${command}. Available commands: ${Object.values(_NativeInterfaces.NATIVE_COMMANDS).join(", ")}`);}if(!this.isAvailable()){throw new Error("Native bridge not available. Ensure you are running in a native WebView environment.");}}/**
14
14
  * Execute command on Android platform
15
15
  */_executeAndroidCommand(command,data){try{if(typeof window.NativeBridge[command]==="function"){console.log(`🌉 Calling Android method '${command}'`,data?{data}:"(no data)");// Always pass data parameter to match Kotlin method signatures
16
- window.NativeBridge[command](data);return true;}else{throw new Error(`Android bridge method '${command}' not found`);}}catch(error){console.error(`Error executing Android command '${command}':`,error);throw error;}}/**
16
+ window.NativeBridge[command](data);return true;}else{console.error(`Android bridge method '${command}' not found`);return false;}}catch(error){console.error(`Error executing Android command '${command}':`,error);return false;}}/**
17
17
  * Execute command on iOS platform
18
18
  */_executeIOSCommand(command,data){try{// For iOS, we always send a message object
19
19
  const message={command:command,data:data// This can be null/undefined, iOS bridge should handle it
@@ -102,8 +102,8 @@ if(process.env.NODE_ENV==="development"){console.warn(`🌉 Development mode: Na
102
102
  */clearWebData:()=>this.call(_NativeInterfaces.NATIVE_COMMANDS.CLEAR_WEB_DATA)};/**
103
103
  * Get environment info
104
104
  */getEnvironmentInfo(){return{isAndroid:this.isAndroid,isIOS:this.isIOS,isNativeEnvironment:this.isNativeEnvironment,platform:this.isAndroid?"android":this.isIOS?"ios":"web"};}/**
105
- * Check if native bridge is available
106
- */isAvailable(){return this.isNativeEnvironment;}/**
105
+ * Check if native bridge is available — live check, never stale
106
+ */isAvailable(){return this._detectAndroid()||this._detectIOS();}/**
107
107
  * Debug method to test native bridge connectivity
108
108
  */testConnection(){console.group("🌉 NativeBridge Connection Test");console.log("Environment:",this.getEnvironmentInfo());console.log("Available commands:",Object.values(_NativeInterfaces.NATIVE_COMMANDS));if(this.isAndroid&&window.NativeBridge){console.log("Android bridge methods:",Object.getOwnPropertyNames(window.NativeBridge));}if(this.isIOS&&window.webkit?.messageHandlers?.NativeBridge){console.log("iOS webkit bridge available");}console.groupEnd();}}// Export singleton instance
109
109
  exports.NativeBridgeUtil=NativeBridgeUtil;const nativeBridge=new NativeBridgeUtil();var _default=exports.default=nativeBridge;// Also export the class for advanced usage
@@ -265,9 +265,20 @@ public final class CacheManager {
265
265
  }
266
266
 
267
267
  func getCacheStatistics() -> (memoryUsed: Int, diskUsed: Int) {
268
- // URLCache methods are thread-safe, no need for sync queue
269
268
  let memoryUsed = session.configuration.urlCache?.currentMemoryUsage ?? 0
270
- let diskUsed = session.configuration.urlCache?.currentDiskUsage ?? 0
269
+
270
+ // Sum size of all custom .cache files written to disk (separate from URLCache internals)
271
+ var diskUsed = 0
272
+ if let files = try? FileManager.default.contentsOfDirectory(
273
+ at: cacheDirectory,
274
+ includingPropertiesForKeys: [.fileSizeKey]
275
+ ) {
276
+ diskUsed = files.reduce(0) { total, url in
277
+ let size = (try? url.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? 0
278
+ return total + size
279
+ }
280
+ }
281
+
271
282
  return (memoryUsed, diskUsed)
272
283
  }
273
284
  }
@@ -1,6 +1,42 @@
1
1
  {
2
2
  "originHash" : "3d4b03cf830e06eaa66e0727fd91253b9df9d2675cbe24eb099df0d4bb4d1f01",
3
3
  "pins" : [
4
+ {
5
+ "identity" : "appauth-ios",
6
+ "kind" : "remoteSourceControl",
7
+ "location" : "https://github.com/openid/AppAuth-iOS.git",
8
+ "state" : {
9
+ "revision" : "2781038865a80e2c425a1da12cc1327bcd56501f",
10
+ "version" : "1.7.6"
11
+ }
12
+ },
13
+ {
14
+ "identity" : "googlesignin-ios",
15
+ "kind" : "remoteSourceControl",
16
+ "location" : "https://github.com/google/GoogleSignIn-iOS",
17
+ "state" : {
18
+ "revision" : "a7965d134c5d3567026c523e0a8a583f73b62b0d",
19
+ "version" : "7.1.0"
20
+ }
21
+ },
22
+ {
23
+ "identity" : "gtm-session-fetcher",
24
+ "kind" : "remoteSourceControl",
25
+ "location" : "https://github.com/google/gtm-session-fetcher.git",
26
+ "state" : {
27
+ "revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b",
28
+ "version" : "3.5.0"
29
+ }
30
+ },
31
+ {
32
+ "identity" : "gtmappauth",
33
+ "kind" : "remoteSourceControl",
34
+ "location" : "https://github.com/google/GTMAppAuth.git",
35
+ "state" : {
36
+ "revision" : "5d7d66f647400952b1758b230e019b07c0b4b22a",
37
+ "version" : "4.1.1"
38
+ }
39
+ },
4
40
  {
5
41
  "identity" : "jsonschema.swift",
6
42
  "kind" : "remoteSourceControl",
@@ -9,6 +9,7 @@
9
9
  }
10
10
  ],
11
11
  "defaultOptions" : {
12
+ "codeCoverage" : true,
12
13
  "commandLineArgumentEntries" : [
13
14
  {
14
15
  "argument" : "-OSLogEnable"