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.
- package/README.md +4 -4
- package/changelog.md +26 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/BridgeMessageValidator.kt +2 -10
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/SecurityBridgeTest.kt +199 -0
- package/dist/native/bridge/hooks.js +4 -4
- package/dist/native/bridge/useBaseHook.js +5 -4
- package/dist/native/bridge/utils/NativeBridge.js +4 -4
- package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +13 -2
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +36 -0
- package/dist/native/iosnativeWebView/iosnativeWebView.xctestplan +1 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeCommandHandlerSecurityTests.swift +212 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/FrameworkServerUtilsTests.swift +14 -4
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/ScreenSecureManagerTests.swift +121 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/WebViewTests.swift +9 -21
- package/mcp_v2/conversion-tasks.json +324 -369
- package/mcp_v2/knowledge-base.json +1067 -1449
- package/mcp_v2/lib/helpers.js +125 -105
- package/mcp_v2/mcp.js +487 -290
- package/mcp_v2/package.json +11 -11
- package/mcp_v2/setup.js +207 -191
- package/mcp_v2/tools/build.js +629 -392
- package/mcp_v2/tools/config.js +417 -226
- package/mcp_v2/tools/conversion.js +749 -442
- package/mcp_v2/tools/debug.js +95 -44
- package/mcp_v2/tools/knowledge.js +171 -165
- package/mcp_v2/tools/sync.js +15 -13
- package/mcp_v2/tools/tasks.js +754 -653
- package/package.json +6 -13
- package/license +0 -10
package/README.md
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
## Table of Contents
|
|
9
9
|
|
|
10
|
-
-
|
|
11
|
-
-
|
|
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
|
-
-
|
|
24
|
-
-
|
|
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
|
|
66
|
-
private val validCommands =
|
|
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.
|
|
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.
|
|
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]);//
|
|
21
|
-
const
|
|
22
|
-
operationCallback();}
|
|
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.
|
|
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{
|
|
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.
|
|
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
|
-
|
|
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",
|