catalyst-core-internal 0.1.0 → 0.1.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.
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/BridgeMessageValidator.kt +10 -2
- package/dist/native/bridge/hooks.js +4 -4
- package/dist/native/bridge/useBaseHook.js +3 -4
- package/dist/native/bridge/utils/NativeBridge.js +4 -4
- package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +2 -13
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +0 -36
- package/dist/native/iosnativeWebView/iosnativeWebView.xctestplan +0 -1
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/FrameworkServerUtilsTests.swift +4 -14
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/WebViewTests.swift +21 -9
- package/mcp_v2/conversion-tasks.json +371 -0
- package/mcp_v2/knowledge-base.json +1450 -0
- package/mcp_v2/lib/helpers.js +145 -0
- package/mcp_v2/mcp.js +366 -0
- package/mcp_v2/package.json +13 -0
- package/mcp_v2/schema.sql +88 -0
- package/mcp_v2/setup.js +262 -0
- package/mcp_v2/tools/build.js +449 -0
- package/mcp_v2/tools/config.js +262 -0
- package/mcp_v2/tools/conversion.js +492 -0
- package/mcp_v2/tools/debug.js +62 -0
- package/mcp_v2/tools/knowledge.js +213 -0
- package/mcp_v2/tools/sync.js +21 -0
- package/mcp_v2/tools/tasks.js +844 -0
- package/package.json +1 -1
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/SecurityBridgeTest.kt +0 -199
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeCommandHandlerSecurityTests.swift +0 -212
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/ScreenSecureManagerTests.swift +0 -121
|
@@ -62,8 +62,16 @@ 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 =
|
|
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
|
+
)
|
|
67
75
|
|
|
68
76
|
// MARK: - Schema Definitions (mirror iOS schemas)
|
|
69
77
|
|
|
@@ -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=>{
|
|
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
|
|
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.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};};/**
|
|
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
|
-
|
|
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
|
|
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,8 @@ 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
|
-
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
|
|
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
|
|
23
22
|
operationCallback();}catch(err){handleNativeError(err);console.error(`❌ ${hookName} ${operationName} failed:`,err);}},[hookName,isWeb,startProgress,handleNativeError]);// Environment flags (computed values, not functions)
|
|
24
23
|
const environmentFlags={isWeb:isWeb(),isNative:isNative()};// Return standardized interface
|
|
25
24
|
return{// Data state
|
|
@@ -28,7 +27,7 @@ loading,progress,// Error handling
|
|
|
28
27
|
error,// Environment detection
|
|
29
28
|
...environmentFlags,// Actions
|
|
30
29
|
clear,clearError,// Internal utilities for specific hooks
|
|
31
|
-
setData,setLoading,setError,setProgress,updateProgress,resetProgress,startProgress,completeProgress,errorProgress,setDataAndComplete,handleNativeError,
|
|
30
|
+
setData,setLoading,setError,setProgress,updateProgress,resetProgress,startProgress,completeProgress,errorProgress,setDataAndComplete,handleNativeError,executeOperation};};/**
|
|
32
31
|
* Hook for development debugging and testing
|
|
33
32
|
* @param {string} hookName
|
|
34
33
|
* @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.isNativeEnvironment){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{throw new Error(`Android bridge method '${command}' not found`);}}catch(error){console.error(`Error executing Android command '${command}':`,error);throw error;}}/**
|
|
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
|
|
106
|
+
*/isAvailable(){return this.isNativeEnvironment;}/**
|
|
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,20 +265,9 @@ 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
|
|
268
269
|
let memoryUsed = session.configuration.urlCache?.currentMemoryUsage ?? 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
|
-
|
|
270
|
+
let diskUsed = session.configuration.urlCache?.currentDiskUsage ?? 0
|
|
282
271
|
return (memoryUsed, diskUsed)
|
|
283
272
|
}
|
|
284
273
|
}
|
|
@@ -1,42 +1,6 @@
|
|
|
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
|
-
},
|
|
40
4
|
{
|
|
41
5
|
"identity" : "jsonschema.swift",
|
|
42
6
|
"kind" : "remoteSourceControl",
|
|
@@ -47,16 +47,12 @@ final class FrameworkServerUtilsTests: XCTestCase {
|
|
|
47
47
|
// CATEGORY 1: HTTPS Server Setup (2 tests)
|
|
48
48
|
// ========================================
|
|
49
49
|
|
|
50
|
-
func testHTTPSServerSetup_ServerInitialization()
|
|
50
|
+
func testHTTPSServerSetup_ServerInitialization() {
|
|
51
51
|
// Test server initialization
|
|
52
52
|
|
|
53
53
|
// Note: Server may fail to start in test environment due to network restrictions
|
|
54
|
-
// This test verifies the API works without crashing
|
|
55
|
-
|
|
56
|
-
// (NWListener setup must not block the main thread).
|
|
57
|
-
let started = await Task.detached(priority: .userInitiated) {
|
|
58
|
-
self.frameworkServer.startServer()
|
|
59
|
-
}.value
|
|
54
|
+
// This test verifies the API works without crashing
|
|
55
|
+
let started = frameworkServer.startServer()
|
|
60
56
|
|
|
61
57
|
// If server starts successfully, verify state
|
|
62
58
|
if started {
|
|
@@ -147,14 +143,8 @@ final class FrameworkServerUtilsTests: XCTestCase {
|
|
|
147
143
|
// Start server first
|
|
148
144
|
let started = frameworkServer.startServer()
|
|
149
145
|
|
|
150
|
-
//
|
|
151
|
-
// The listener can flip isServerRunning back to false if it fails after startServer() returns.
|
|
146
|
+
// Only test file serving if server started
|
|
152
147
|
if started {
|
|
153
|
-
Thread.sleep(forTimeInterval: 0.3)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Only test file serving if server is still running after NWListener settled
|
|
157
|
-
if started && frameworkServer.isRunning() {
|
|
158
148
|
// Create a temporary test file
|
|
159
149
|
let testData = "Test file content".data(using: .utf8)!
|
|
160
150
|
let tempURL = FileManager.default.temporaryDirectory
|
|
@@ -38,7 +38,7 @@ final class WebViewTests: XCTestCase {
|
|
|
38
38
|
viewModel = WebViewModel()
|
|
39
39
|
|
|
40
40
|
// Create navigation delegate
|
|
41
|
-
navigationDelegate = WebViewNavigationDelegate(viewModel: viewModel
|
|
41
|
+
navigationDelegate = WebViewNavigationDelegate(viewModel: viewModel)
|
|
42
42
|
|
|
43
43
|
// Create a basic WKWebView for testing
|
|
44
44
|
let config = WKWebViewConfiguration()
|
|
@@ -124,7 +124,7 @@ final class WebViewTests: XCTestCase {
|
|
|
124
124
|
// ========================================
|
|
125
125
|
|
|
126
126
|
@MainActor
|
|
127
|
-
func testNavigationHandling_AllowedURLLoading()
|
|
127
|
+
func testNavigationHandling_AllowedURLLoading() {
|
|
128
128
|
// Test that allowed URLs are permitted for navigation
|
|
129
129
|
|
|
130
130
|
let allowedURL = URL(string: "https://example.com")!
|
|
@@ -138,23 +138,29 @@ final class WebViewTests: XCTestCase {
|
|
|
138
138
|
#endif
|
|
139
139
|
|
|
140
140
|
let navigationAction = createNavigationAction(url: allowedURL)
|
|
141
|
-
|
|
141
|
+
var decisionReceived = false
|
|
142
142
|
var allowedDecision = false
|
|
143
143
|
|
|
144
144
|
navigationDelegate.webView(mockWebView,
|
|
145
145
|
decidePolicyFor: navigationAction) { policy in
|
|
146
|
+
decisionReceived = true
|
|
146
147
|
allowedDecision = (policy == .allow)
|
|
147
|
-
expectation.fulfill()
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
// Wait briefly for async decision
|
|
151
|
+
let expectation = XCTestExpectation(description: "Navigation decision")
|
|
152
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
|
153
|
+
expectation.fulfill()
|
|
154
|
+
}
|
|
155
|
+
wait(for: [expectation], timeout: 1.0)
|
|
151
156
|
|
|
157
|
+
XCTAssertTrue(decisionReceived, "Navigation decision should be received")
|
|
152
158
|
XCTAssertTrue(allowedDecision || !URLWhitelistManager.shared.isAccessControlEnabled,
|
|
153
159
|
"Allowed URL should be permitted")
|
|
154
160
|
}
|
|
155
161
|
|
|
156
162
|
@MainActor
|
|
157
|
-
func testNavigationHandling_BlockedURLNavigation()
|
|
163
|
+
func testNavigationHandling_BlockedURLNavigation() {
|
|
158
164
|
// Test that blocked URLs are rejected
|
|
159
165
|
|
|
160
166
|
let blockedURL = URL(string: "https://blocked.com")!
|
|
@@ -168,17 +174,23 @@ final class WebViewTests: XCTestCase {
|
|
|
168
174
|
#endif
|
|
169
175
|
|
|
170
176
|
let navigationAction = createNavigationAction(url: blockedURL)
|
|
171
|
-
|
|
177
|
+
var decisionReceived = false
|
|
172
178
|
var blockedDecision = false
|
|
173
179
|
|
|
174
180
|
navigationDelegate.webView(mockWebView,
|
|
175
181
|
decidePolicyFor: navigationAction) { policy in
|
|
182
|
+
decisionReceived = true
|
|
176
183
|
blockedDecision = (policy == .cancel)
|
|
177
|
-
expectation.fulfill()
|
|
178
184
|
}
|
|
179
185
|
|
|
180
|
-
|
|
186
|
+
// Wait briefly for async decision
|
|
187
|
+
let expectation = XCTestExpectation(description: "Navigation decision")
|
|
188
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
|
189
|
+
expectation.fulfill()
|
|
190
|
+
}
|
|
191
|
+
wait(for: [expectation], timeout: 1.0)
|
|
181
192
|
|
|
193
|
+
XCTAssertTrue(decisionReceived, "Navigation decision should be received")
|
|
182
194
|
XCTAssertTrue(blockedDecision, "Blocked URL should be cancelled")
|
|
183
195
|
}
|
|
184
196
|
|