diff --git a/.gitignore b/.gitignore index 16df570..3375525 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,9 @@ project.xcworkspace .settings local.properties android.iml +apps/*/android/app/src/main/assets/ +apps/*/android/app/src/main/res/drawable-*/ +apps/*/android/app/src/main/res/raw/ # Cocoapods # @@ -54,6 +57,9 @@ vendor/ # node.js # node_modules/ +node_modules +**/node_modules/ +**/node_modules npm-debug.log yarn-debug.log yarn-error.log diff --git a/apps/demo/ios/Demo.xcodeproj/project.pbxproj b/apps/demo/ios/Demo.xcodeproj/project.pbxproj index 3e0c15f..5c41998 100644 --- a/apps/demo/ios/Demo.xcodeproj/project.pbxproj +++ b/apps/demo/ios/Demo.xcodeproj/project.pbxproj @@ -385,7 +385,7 @@ "$(inherited)", " ", ); - REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; USE_HERMES = true; @@ -462,7 +462,7 @@ "$(inherited)", " ", ); - REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; VALIDATE_PRODUCT = YES; diff --git a/apps/demo/ios/Podfile.lock b/apps/demo/ios/Podfile.lock index 764b717..a5bb8b9 100644 --- a/apps/demo/ios/Podfile.lock +++ b/apps/demo/ios/Podfile.lock @@ -2155,81 +2155,81 @@ PODS: - Yoga (0.0.0) DEPENDENCIES: - - boost (from `../../../node_modules/react-native/third-party-podspecs/boost.podspec`) - - DoubleConversion (from `../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - - fast_float (from `../../../node_modules/react-native/third-party-podspecs/fast_float.podspec`) - - FBLazyVector (from `../../../node_modules/react-native/Libraries/FBLazyVector`) - - fmt (from `../../../node_modules/react-native/third-party-podspecs/fmt.podspec`) - - glog (from `../../../node_modules/react-native/third-party-podspecs/glog.podspec`) - - hermes-engine (from `../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - - RCT-Folly (from `../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - - RCTDeprecation (from `../../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) - - RCTRequired (from `../../../node_modules/react-native/Libraries/Required`) - - RCTTypeSafety (from `../../../node_modules/react-native/Libraries/TypeSafety`) - - React (from `../../../node_modules/react-native/`) - - React-callinvoker (from `../../../node_modules/react-native/ReactCommon/callinvoker`) - - React-Core (from `../../../node_modules/react-native/`) - - React-Core/RCTWebSocket (from `../../../node_modules/react-native/`) - - React-CoreModules (from `../../../node_modules/react-native/React/CoreModules`) - - React-cxxreact (from `../../../node_modules/react-native/ReactCommon/cxxreact`) - - React-debug (from `../../../node_modules/react-native/ReactCommon/react/debug`) - - React-defaultsnativemodule (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/defaults`) - - React-domnativemodule (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/dom`) - - React-Fabric (from `../../../node_modules/react-native/ReactCommon`) - - React-FabricComponents (from `../../../node_modules/react-native/ReactCommon`) - - React-FabricImage (from `../../../node_modules/react-native/ReactCommon`) - - React-featureflags (from `../../../node_modules/react-native/ReactCommon/react/featureflags`) - - React-featureflagsnativemodule (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/featureflags`) - - React-graphics (from `../../../node_modules/react-native/ReactCommon/react/renderer/graphics`) - - React-hermes (from `../../../node_modules/react-native/ReactCommon/hermes`) - - React-idlecallbacksnativemodule (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`) - - React-ImageManager (from `../../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) - - React-jserrorhandler (from `../../../node_modules/react-native/ReactCommon/jserrorhandler`) - - React-jsi (from `../../../node_modules/react-native/ReactCommon/jsi`) - - React-jsiexecutor (from `../../../node_modules/react-native/ReactCommon/jsiexecutor`) - - React-jsinspector (from `../../../node_modules/react-native/ReactCommon/jsinspector-modern`) - - React-jsinspectorcdp (from `../../../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`) - - React-jsinspectornetwork (from `../../../node_modules/react-native/ReactCommon/jsinspector-modern/network`) - - React-jsinspectortracing (from `../../../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`) - - React-jsitooling (from `../../../node_modules/react-native/ReactCommon/jsitooling`) - - React-jsitracing (from `../../../node_modules/react-native/ReactCommon/hermes/executor/`) - - React-logger (from `../../../node_modules/react-native/ReactCommon/logger`) - - React-Mapbuffer (from `../../../node_modules/react-native/ReactCommon`) - - React-microtasksnativemodule (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) - - React-NativeModulesApple (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - - React-oscompat (from `../../../node_modules/react-native/ReactCommon/oscompat`) - - React-perflogger (from `../../../node_modules/react-native/ReactCommon/reactperflogger`) - - React-performancetimeline (from `../../../node_modules/react-native/ReactCommon/react/performance/timeline`) - - React-RCTActionSheet (from `../../../node_modules/react-native/Libraries/ActionSheetIOS`) - - React-RCTAnimation (from `../../../node_modules/react-native/Libraries/NativeAnimation`) - - React-RCTAppDelegate (from `../../../node_modules/react-native/Libraries/AppDelegate`) - - React-RCTBlob (from `../../../node_modules/react-native/Libraries/Blob`) - - React-RCTFabric (from `../../../node_modules/react-native/React`) - - React-RCTFBReactNativeSpec (from `../../../node_modules/react-native/React`) - - React-RCTImage (from `../../../node_modules/react-native/Libraries/Image`) - - React-RCTLinking (from `../../../node_modules/react-native/Libraries/LinkingIOS`) - - React-RCTNetwork (from `../../../node_modules/react-native/Libraries/Network`) - - React-RCTRuntime (from `../../../node_modules/react-native/React/Runtime`) - - React-RCTSettings (from `../../../node_modules/react-native/Libraries/Settings`) - - React-RCTText (from `../../../node_modules/react-native/Libraries/Text`) - - React-RCTVibration (from `../../../node_modules/react-native/Libraries/Vibration`) - - React-rendererconsistency (from `../../../node_modules/react-native/ReactCommon/react/renderer/consistency`) - - React-renderercss (from `../../../node_modules/react-native/ReactCommon/react/renderer/css`) - - React-rendererdebug (from `../../../node_modules/react-native/ReactCommon/react/renderer/debug`) - - React-rncore (from `../../../node_modules/react-native/ReactCommon`) - - React-RuntimeApple (from `../../../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) - - React-RuntimeCore (from `../../../node_modules/react-native/ReactCommon/react/runtime`) - - React-runtimeexecutor (from `../../../node_modules/react-native/ReactCommon/runtimeexecutor`) - - React-RuntimeHermes (from `../../../node_modules/react-native/ReactCommon/react/runtime`) - - React-runtimescheduler (from `../../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) - - "React-Sandbox (from `../../../node_modules/@callstack/react-native-sandbox`)" - - React-timing (from `../../../node_modules/react-native/ReactCommon/react/timing`) - - React-utils (from `../../../node_modules/react-native/ReactCommon/react/utils`) + - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) + - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) + - fast_float (from `../node_modules/react-native/third-party-podspecs/fast_float.podspec`) + - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) + - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) + - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) + - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) + - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) + - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) + - RCTRequired (from `../node_modules/react-native/Libraries/Required`) + - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) + - React (from `../node_modules/react-native/`) + - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) + - React-Core (from `../node_modules/react-native/`) + - React-Core/RCTWebSocket (from `../node_modules/react-native/`) + - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) + - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) + - React-debug (from `../node_modules/react-native/ReactCommon/react/debug`) + - React-defaultsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/defaults`) + - React-domnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/dom`) + - React-Fabric (from `../node_modules/react-native/ReactCommon`) + - React-FabricComponents (from `../node_modules/react-native/ReactCommon`) + - React-FabricImage (from `../node_modules/react-native/ReactCommon`) + - React-featureflags (from `../node_modules/react-native/ReactCommon/react/featureflags`) + - React-featureflagsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/featureflags`) + - React-graphics (from `../node_modules/react-native/ReactCommon/react/renderer/graphics`) + - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) + - React-idlecallbacksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`) + - React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) + - React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`) + - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) + - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) + - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`) + - React-jsinspectorcdp (from `../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`) + - React-jsinspectornetwork (from `../node_modules/react-native/ReactCommon/jsinspector-modern/network`) + - React-jsinspectortracing (from `../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`) + - React-jsitooling (from `../node_modules/react-native/ReactCommon/jsitooling`) + - React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`) + - React-logger (from `../node_modules/react-native/ReactCommon/logger`) + - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) + - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) + - React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`) + - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) + - React-performancetimeline (from `../node_modules/react-native/ReactCommon/react/performance/timeline`) + - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) + - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) + - React-RCTAppDelegate (from `../node_modules/react-native/Libraries/AppDelegate`) + - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) + - React-RCTFabric (from `../node_modules/react-native/React`) + - React-RCTFBReactNativeSpec (from `../node_modules/react-native/React`) + - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) + - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) + - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) + - React-RCTRuntime (from `../node_modules/react-native/React/Runtime`) + - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) + - React-RCTText (from `../node_modules/react-native/Libraries/Text`) + - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) + - React-rendererconsistency (from `../node_modules/react-native/ReactCommon/react/renderer/consistency`) + - React-renderercss (from `../node_modules/react-native/ReactCommon/react/renderer/css`) + - React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`) + - React-rncore (from `../node_modules/react-native/ReactCommon`) + - React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) + - React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) + - React-RuntimeHermes (from `../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) + - "React-Sandbox (from `../node_modules/@callstack/react-native-sandbox`)" + - React-timing (from `../node_modules/react-native/ReactCommon/react/timing`) + - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - ReactAppDependencyProvider (from `build/generated/ios`) - ReactCodegen (from `build/generated/ios`) - - ReactCommon/turbomodule/core (from `../../../node_modules/react-native/ReactCommon`) + - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - SocketRocket (~> 0.7.1) - - Yoga (from `../../../node_modules/react-native/ReactCommon/yoga`) + - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: trunk: @@ -2237,152 +2237,152 @@ SPEC REPOS: EXTERNAL SOURCES: boost: - :podspec: "../../../node_modules/react-native/third-party-podspecs/boost.podspec" + :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" DoubleConversion: - :podspec: "../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" fast_float: - :podspec: "../../../node_modules/react-native/third-party-podspecs/fast_float.podspec" + :podspec: "../node_modules/react-native/third-party-podspecs/fast_float.podspec" FBLazyVector: - :path: "../../../node_modules/react-native/Libraries/FBLazyVector" + :path: "../node_modules/react-native/Libraries/FBLazyVector" fmt: - :podspec: "../../../node_modules/react-native/third-party-podspecs/fmt.podspec" + :podspec: "../node_modules/react-native/third-party-podspecs/fmt.podspec" glog: - :podspec: "../../../node_modules/react-native/third-party-podspecs/glog.podspec" + :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" hermes-engine: - :podspec: "../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" + :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" :tag: hermes-2025-05-06-RNv0.80.0-4eb6132a5bf0450bf4c6c91987675381d7ac8bca RCT-Folly: - :podspec: "../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" + :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" RCTDeprecation: - :path: "../../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" + :path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" RCTRequired: - :path: "../../../node_modules/react-native/Libraries/Required" + :path: "../node_modules/react-native/Libraries/Required" RCTTypeSafety: - :path: "../../../node_modules/react-native/Libraries/TypeSafety" + :path: "../node_modules/react-native/Libraries/TypeSafety" React: - :path: "../../../node_modules/react-native/" + :path: "../node_modules/react-native/" React-callinvoker: - :path: "../../../node_modules/react-native/ReactCommon/callinvoker" + :path: "../node_modules/react-native/ReactCommon/callinvoker" React-Core: - :path: "../../../node_modules/react-native/" + :path: "../node_modules/react-native/" React-CoreModules: - :path: "../../../node_modules/react-native/React/CoreModules" + :path: "../node_modules/react-native/React/CoreModules" React-cxxreact: - :path: "../../../node_modules/react-native/ReactCommon/cxxreact" + :path: "../node_modules/react-native/ReactCommon/cxxreact" React-debug: - :path: "../../../node_modules/react-native/ReactCommon/react/debug" + :path: "../node_modules/react-native/ReactCommon/react/debug" React-defaultsnativemodule: - :path: "../../../node_modules/react-native/ReactCommon/react/nativemodule/defaults" + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/defaults" React-domnativemodule: - :path: "../../../node_modules/react-native/ReactCommon/react/nativemodule/dom" + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/dom" React-Fabric: - :path: "../../../node_modules/react-native/ReactCommon" + :path: "../node_modules/react-native/ReactCommon" React-FabricComponents: - :path: "../../../node_modules/react-native/ReactCommon" + :path: "../node_modules/react-native/ReactCommon" React-FabricImage: - :path: "../../../node_modules/react-native/ReactCommon" + :path: "../node_modules/react-native/ReactCommon" React-featureflags: - :path: "../../../node_modules/react-native/ReactCommon/react/featureflags" + :path: "../node_modules/react-native/ReactCommon/react/featureflags" React-featureflagsnativemodule: - :path: "../../../node_modules/react-native/ReactCommon/react/nativemodule/featureflags" + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/featureflags" React-graphics: - :path: "../../../node_modules/react-native/ReactCommon/react/renderer/graphics" + :path: "../node_modules/react-native/ReactCommon/react/renderer/graphics" React-hermes: - :path: "../../../node_modules/react-native/ReactCommon/hermes" + :path: "../node_modules/react-native/ReactCommon/hermes" React-idlecallbacksnativemodule: - :path: "../../../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" React-ImageManager: - :path: "../../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" + :path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" React-jserrorhandler: - :path: "../../../node_modules/react-native/ReactCommon/jserrorhandler" + :path: "../node_modules/react-native/ReactCommon/jserrorhandler" React-jsi: - :path: "../../../node_modules/react-native/ReactCommon/jsi" + :path: "../node_modules/react-native/ReactCommon/jsi" React-jsiexecutor: - :path: "../../../node_modules/react-native/ReactCommon/jsiexecutor" + :path: "../node_modules/react-native/ReactCommon/jsiexecutor" React-jsinspector: - :path: "../../../node_modules/react-native/ReactCommon/jsinspector-modern" + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern" React-jsinspectorcdp: - :path: "../../../node_modules/react-native/ReactCommon/jsinspector-modern/cdp" + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/cdp" React-jsinspectornetwork: - :path: "../../../node_modules/react-native/ReactCommon/jsinspector-modern/network" + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/network" React-jsinspectortracing: - :path: "../../../node_modules/react-native/ReactCommon/jsinspector-modern/tracing" + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/tracing" React-jsitooling: - :path: "../../../node_modules/react-native/ReactCommon/jsitooling" + :path: "../node_modules/react-native/ReactCommon/jsitooling" React-jsitracing: - :path: "../../../node_modules/react-native/ReactCommon/hermes/executor/" + :path: "../node_modules/react-native/ReactCommon/hermes/executor/" React-logger: - :path: "../../../node_modules/react-native/ReactCommon/logger" + :path: "../node_modules/react-native/ReactCommon/logger" React-Mapbuffer: - :path: "../../../node_modules/react-native/ReactCommon" + :path: "../node_modules/react-native/ReactCommon" React-microtasksnativemodule: - :path: "../../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" React-NativeModulesApple: - :path: "../../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" React-oscompat: - :path: "../../../node_modules/react-native/ReactCommon/oscompat" + :path: "../node_modules/react-native/ReactCommon/oscompat" React-perflogger: - :path: "../../../node_modules/react-native/ReactCommon/reactperflogger" + :path: "../node_modules/react-native/ReactCommon/reactperflogger" React-performancetimeline: - :path: "../../../node_modules/react-native/ReactCommon/react/performance/timeline" + :path: "../node_modules/react-native/ReactCommon/react/performance/timeline" React-RCTActionSheet: - :path: "../../../node_modules/react-native/Libraries/ActionSheetIOS" + :path: "../node_modules/react-native/Libraries/ActionSheetIOS" React-RCTAnimation: - :path: "../../../node_modules/react-native/Libraries/NativeAnimation" + :path: "../node_modules/react-native/Libraries/NativeAnimation" React-RCTAppDelegate: - :path: "../../../node_modules/react-native/Libraries/AppDelegate" + :path: "../node_modules/react-native/Libraries/AppDelegate" React-RCTBlob: - :path: "../../../node_modules/react-native/Libraries/Blob" + :path: "../node_modules/react-native/Libraries/Blob" React-RCTFabric: - :path: "../../../node_modules/react-native/React" + :path: "../node_modules/react-native/React" React-RCTFBReactNativeSpec: - :path: "../../../node_modules/react-native/React" + :path: "../node_modules/react-native/React" React-RCTImage: - :path: "../../../node_modules/react-native/Libraries/Image" + :path: "../node_modules/react-native/Libraries/Image" React-RCTLinking: - :path: "../../../node_modules/react-native/Libraries/LinkingIOS" + :path: "../node_modules/react-native/Libraries/LinkingIOS" React-RCTNetwork: - :path: "../../../node_modules/react-native/Libraries/Network" + :path: "../node_modules/react-native/Libraries/Network" React-RCTRuntime: - :path: "../../../node_modules/react-native/React/Runtime" + :path: "../node_modules/react-native/React/Runtime" React-RCTSettings: - :path: "../../../node_modules/react-native/Libraries/Settings" + :path: "../node_modules/react-native/Libraries/Settings" React-RCTText: - :path: "../../../node_modules/react-native/Libraries/Text" + :path: "../node_modules/react-native/Libraries/Text" React-RCTVibration: - :path: "../../../node_modules/react-native/Libraries/Vibration" + :path: "../node_modules/react-native/Libraries/Vibration" React-rendererconsistency: - :path: "../../../node_modules/react-native/ReactCommon/react/renderer/consistency" + :path: "../node_modules/react-native/ReactCommon/react/renderer/consistency" React-renderercss: - :path: "../../../node_modules/react-native/ReactCommon/react/renderer/css" + :path: "../node_modules/react-native/ReactCommon/react/renderer/css" React-rendererdebug: - :path: "../../../node_modules/react-native/ReactCommon/react/renderer/debug" + :path: "../node_modules/react-native/ReactCommon/react/renderer/debug" React-rncore: - :path: "../../../node_modules/react-native/ReactCommon" + :path: "../node_modules/react-native/ReactCommon" React-RuntimeApple: - :path: "../../../node_modules/react-native/ReactCommon/react/runtime/platform/ios" + :path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios" React-RuntimeCore: - :path: "../../../node_modules/react-native/ReactCommon/react/runtime" + :path: "../node_modules/react-native/ReactCommon/react/runtime" React-runtimeexecutor: - :path: "../../../node_modules/react-native/ReactCommon/runtimeexecutor" + :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" React-RuntimeHermes: - :path: "../../../node_modules/react-native/ReactCommon/react/runtime" + :path: "../node_modules/react-native/ReactCommon/react/runtime" React-runtimescheduler: - :path: "../../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" + :path: "../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" React-Sandbox: - :path: "../../../node_modules/@callstack/react-native-sandbox" + :path: "../node_modules/@callstack/react-native-sandbox" React-timing: - :path: "../../../node_modules/react-native/ReactCommon/react/timing" + :path: "../node_modules/react-native/ReactCommon/react/timing" React-utils: - :path: "../../../node_modules/react-native/ReactCommon/react/utils" + :path: "../node_modules/react-native/ReactCommon/react/utils" ReactAppDependencyProvider: :path: build/generated/ios ReactCodegen: :path: build/generated/ios ReactCommon: - :path: "../../../node_modules/react-native/ReactCommon" + :path: "../node_modules/react-native/ReactCommon" Yoga: - :path: "../../../node_modules/react-native/ReactCommon/yoga" + :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 @@ -2451,14 +2451,14 @@ SPEC CHECKSUMS: React-runtimeexecutor: 17c70842d5e611130cb66f91e247bc4a609c3508 React-RuntimeHermes: 3c88e6e1ea7ea0899dcffc77c10d61ea46688cfd React-runtimescheduler: 024500621c7c93d65371498abb4ee26d34f5d47d - React-Sandbox: e3cf3c955559ed9f0bf014b29dce1e94600cd790 + React-Sandbox: 012f04281351487aa1618716fbe98da54be21b6e React-timing: c3c923df2b86194e1682e01167717481232f1dc7 React-utils: 9154a037543147e1c24098f1a48fc8472602c092 ReactAppDependencyProvider: afd905e84ee36e1678016ae04d7370c75ed539be ReactCodegen: 06bf9ae2e01a2416250cf5e44e4a06b1c9ea201b ReactCommon: 17fd88849a174bf9ce45461912291aca711410fc SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: daa1e4de4b971b977b23bc842aaa3e135324f1f3 + Yoga: 8afa6701277b3739469211d8e5f6496b35e35596 PODFILE CHECKSUM: 5a84e4b97971d8ea6afcd805e0f67ace94e714f1 diff --git a/packages/react-native-sandbox/React-Sandbox.podspec b/packages/react-native-sandbox/React-Sandbox.podspec index b033a8c..0e26e1b 100644 --- a/packages/react-native-sandbox/React-Sandbox.podspec +++ b/packages/react-native-sandbox/React-Sandbox.podspec @@ -18,11 +18,11 @@ Pod::Spec.new do |s| s.authors = { "Alex Babrykovich" => "aliaksandr.babrykovich@callstack.com" } s.platforms = { :ios => "12.4" } s.source = { :git => "https://github.com/callstackincubator/react-native-sandbox.git", :tag => "#{s.version}" } - s.source_files = "ios/**/*.{h,m,mm,cpp,swift}" + s.source_files = ["ios/**/*.{h,m,mm,cpp,swift}", "cxx/**/*.{h,cpp}"] install_modules_dependencies(s) s.dependency "fmt" s.pod_target_xcconfig = { - "HEADER_SEARCH_PATHS" => header_search_paths, + "HEADER_SEARCH_PATHS" => header_search_paths + ["\"$(PODS_TARGET_SRCROOT)/cxx\""], # "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" } diff --git a/packages/react-native-sandbox/android/src/main/java/io/callstack/rnsandbox/SandboxJSIInstaller.kt b/packages/react-native-sandbox/android/src/main/java/io/callstack/rnsandbox/SandboxJSIInstaller.kt index b892ad2..4cbc318 100644 --- a/packages/react-native-sandbox/android/src/main/java/io/callstack/rnsandbox/SandboxJSIInstaller.kt +++ b/packages/react-native-sandbox/android/src/main/java/io/callstack/rnsandbox/SandboxJSIInstaller.kt @@ -44,4 +44,13 @@ object SandboxJSIInstaller { */ @JvmStatic external fun nativeDestroy(stateHandle: Long) + + /** + * Installs the error handler into the JS runtime. Must be called on the + * JS thread after the bundle has loaded (when ErrorUtils is available). + * + * @param stateHandle Handle returned by nativeInstall + */ + @JvmStatic + external fun nativeInstallErrorHandler(stateHandle: Long) } diff --git a/packages/react-native-sandbox/android/src/main/java/io/callstack/rnsandbox/SandboxReactNativeDelegate.kt b/packages/react-native-sandbox/android/src/main/java/io/callstack/rnsandbox/SandboxReactNativeDelegate.kt index 97f27ce..1ac5ed4 100644 --- a/packages/react-native-sandbox/android/src/main/java/io/callstack/rnsandbox/SandboxReactNativeDelegate.kt +++ b/packages/react-native-sandbox/android/src/main/java/io/callstack/rnsandbox/SandboxReactNativeDelegate.kt @@ -33,29 +33,21 @@ class SandboxReactNativeDelegate( ) { companion object { private const val TAG = "SandboxRNDelegate" + + private val sharedHosts = mutableMapOf() + + private data class SharedReactHost( + val reactHost: ReactHostImpl, + val sandboxContext: Context, + var refCount: Int, + ) } - var origin: String = "" - set(value) { - if (field == value) return - if (field.isNotEmpty()) { - SandboxRegistry.unregister(field) - } - field = value - if (value.isNotEmpty()) { - SandboxRegistry.register(value, this, allowedOrigins) - } - } + @JvmField var origin: String = "" var jsBundleSource: String = "" var allowedTurboModules: Set = emptySet() var allowedOrigins: Set = emptySet() - set(value) { - field = value - if (origin.isNotEmpty()) { - SandboxRegistry.register(origin, this, value) - } - } @JvmField var hasOnMessageHandler: Boolean = false @@ -66,6 +58,7 @@ class SandboxReactNativeDelegate( private var reactSurface: ReactSurface? = null private var jsiStateHandle: Long = 0 private var sandboxReactContext: ReactContext? = null + private var ownsReactHost = false @OptIn(UnstableReactNativeAPI::class) fun loadReactNativeView( @@ -79,42 +72,63 @@ class SandboxReactNativeDelegate( val capturedBundleSource = jsBundleSource val capturedAllowedModules = allowedTurboModules - val sandboxId = System.identityHashCode(this).toString(16) - val sandboxContext = SandboxContextWrapper(context, sandboxId) try { - val packages: List = - listOf( - FilteredReactPackage(MainReactPackage(), capturedAllowedModules), - ) - - val bundleLoader = createBundleLoader(capturedBundleSource) ?: return null - - val tmmDelegateBuilder = DefaultTurboModuleManagerDelegate.Builder() - - val bindingsInstaller = SandboxBindingsInstaller.create(this) - - val hostDelegate = - DefaultReactHostDelegate( - jsMainModulePath = capturedBundleSource, - jsBundleLoader = bundleLoader, - reactPackages = packages, - jsRuntimeFactory = HermesInstance(), - turboModuleManagerDelegateBuilder = tmmDelegateBuilder, - bindingsInstaller = bindingsInstaller, - ) - - val componentFactory = ComponentFactory() - DefaultComponentsRegistry.register(componentFactory) - - val host = - ReactHostImpl( - sandboxContext, - hostDelegate, - componentFactory, - true, - true, - ) + val shared = if (origin.isNotEmpty()) sharedHosts[origin] else null + + val host: ReactHostImpl + val sandboxContext: Context + + if (shared != null) { + host = shared.reactHost + sandboxContext = shared.sandboxContext + shared.refCount++ + ownsReactHost = false + Log.d(TAG, "Reusing shared ReactHost for origin '$origin' (refCount=${shared.refCount})") + } else { + val sandboxId = System.identityHashCode(this).toString(16) + sandboxContext = SandboxContextWrapper(context, sandboxId) + + val packages: List = + listOf( + FilteredReactPackage(MainReactPackage(), capturedAllowedModules), + ) + + val bundleLoader = createBundleLoader(capturedBundleSource) ?: return null + + val tmmDelegateBuilder = DefaultTurboModuleManagerDelegate.Builder() + + val bindingsInstaller = SandboxBindingsInstaller.create(this) + + val hostDelegate = + DefaultReactHostDelegate( + jsMainModulePath = capturedBundleSource, + jsBundleLoader = bundleLoader, + reactPackages = packages, + jsRuntimeFactory = HermesInstance(), + turboModuleManagerDelegateBuilder = tmmDelegateBuilder, + bindingsInstaller = bindingsInstaller, + ) + + val componentFactory = ComponentFactory() + DefaultComponentsRegistry.register(componentFactory) + + host = + ReactHostImpl( + sandboxContext, + hostDelegate, + componentFactory, + true, + true, + ) + + ownsReactHost = true + + if (origin.isNotEmpty()) { + sharedHosts[origin] = SharedReactHost(host, sandboxContext, refCount = 1) + Log.d(TAG, "Created shared ReactHost for origin '$origin'") + } + } reactHost = host @@ -122,6 +136,11 @@ class SandboxReactNativeDelegate( object : ReactInstanceEventListener { override fun onReactContextInitialized(reactContext: ReactContext) { sandboxReactContext = reactContext + if (jsiStateHandle != 0L) { + reactContext.runOnJSQueueThread { + SandboxJSIInstaller.nativeInstallErrorHandler(jsiStateHandle) + } + } } }, ) @@ -240,7 +259,8 @@ class SandboxReactNativeDelegate( return false } - return routeMessage(messageJson, targetOrigin) + // Routing handled entirely in C++ SandboxRegistry (see SandboxJSIInstaller.cpp) + return false } @Suppress("unused") @@ -261,28 +281,6 @@ class SandboxReactNativeDelegate( } } - fun routeMessage( - message: String, - targetId: String, - ): Boolean { - val target = SandboxRegistry.find(targetId) - Log.d(TAG, "routeMessage from '$origin' to '$targetId': target found=${target != null}") - if (target == null) return false - - if (!SandboxRegistry.isPermittedFrom(origin, targetId)) { - Log.w(TAG, "routeMessage DENIED: '$origin' -> '$targetId'") - sandboxView?.emitOnError( - "AccessDeniedError", - "Access denied: Sandbox '$origin' is not permitted to send messages to '$targetId'", - ) - return false - } - - Log.d(TAG, "routeMessage PERMITTED: '$origin' -> '$targetId', delivering...") - target.postMessage(message) - return true - } - private fun getActivity(): Activity? { var ctx = context while (ctx is android.content.ContextWrapper) { @@ -305,17 +303,28 @@ class SandboxReactNativeDelegate( } reactSurface = null - reactHost?.let { - it.onHostDestroy() - it.destroy("sandbox cleanup", null) + val host = reactHost + if (host != null) { + if (origin.isNotEmpty()) { + val shared = sharedHosts[origin] + if (shared != null && shared.reactHost === host) { + shared.refCount-- + if (shared.refCount <= 0) { + sharedHosts.remove(origin) + host.onHostDestroy() + host.destroy("sandbox cleanup", null) + } + } + } else if (ownsReactHost) { + host.onHostDestroy() + host.destroy("sandbox cleanup", null) + } } reactHost = null + ownsReactHost = false } fun destroy() { - if (origin.isNotEmpty()) { - SandboxRegistry.unregister(origin) - } cleanup() } diff --git a/packages/react-native-sandbox/android/src/main/java/io/callstack/rnsandbox/SandboxRegistry.kt b/packages/react-native-sandbox/android/src/main/java/io/callstack/rnsandbox/SandboxRegistry.kt deleted file mode 100644 index 588a009..0000000 --- a/packages/react-native-sandbox/android/src/main/java/io/callstack/rnsandbox/SandboxRegistry.kt +++ /dev/null @@ -1,72 +0,0 @@ -package io.callstack.rnsandbox - -import android.util.Log - -/** - * Thread-safe singleton registry for managing sandbox delegates. - * Mirrors the C++ SandboxRegistry for cross-sandbox communication. - */ -object SandboxRegistry { - private const val TAG = "SandboxRegistry" - private val lock = Any() - private val sandboxRegistry = mutableMapOf() - private val allowedOriginsMap = mutableMapOf>() - - fun register( - origin: String, - delegate: SandboxReactNativeDelegate, - allowedOrigins: Set, - ) { - if (origin.isEmpty()) return - - synchronized(lock) { - if (sandboxRegistry.containsKey(origin)) { - Log.w(TAG, "Overwriting existing sandbox with origin: $origin, allowedOrigins=$allowedOrigins") - } else { - Log.d(TAG, "Registering sandbox origin: $origin, allowedOrigins=$allowedOrigins") - } - sandboxRegistry[origin] = delegate - allowedOriginsMap[origin] = allowedOrigins - } - } - - fun unregister(origin: String) { - if (origin.isEmpty()) return - - synchronized(lock) { - sandboxRegistry.remove(origin) - allowedOriginsMap.remove(origin) - } - } - - fun find(origin: String): SandboxReactNativeDelegate? { - if (origin.isEmpty()) return null - - synchronized(lock) { - return sandboxRegistry[origin] - } - } - - fun isPermittedFrom( - sourceOrigin: String, - targetOrigin: String, - ): Boolean { - if (sourceOrigin.isEmpty() || targetOrigin.isEmpty()) return false - - synchronized(lock) { - val origins = allowedOriginsMap[sourceOrigin] ?: return false - val permitted = origins.contains(targetOrigin) - if (!permitted) { - Log.w(TAG, "isPermittedFrom($sourceOrigin -> $targetOrigin): denied. allowedOrigins[$sourceOrigin]=$origins") - } - return permitted - } - } - - fun reset() { - synchronized(lock) { - sandboxRegistry.clear() - allowedOriginsMap.clear() - } - } -} diff --git a/packages/react-native-sandbox/android/src/main/jni/CMakeLists.txt b/packages/react-native-sandbox/android/src/main/jni/CMakeLists.txt index 9864da9..5a7a4ac 100644 --- a/packages/react-native-sandbox/android/src/main/jni/CMakeLists.txt +++ b/packages/react-native-sandbox/android/src/main/jni/CMakeLists.txt @@ -4,17 +4,22 @@ project(rnsandbox) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +# Shared C++ sources +set(CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../cxx") + find_package(fbjni REQUIRED CONFIG) find_package(ReactAndroid REQUIRED CONFIG) add_library(${PROJECT_NAME} SHARED SandboxJSIInstaller.cpp SandboxBindingsInstaller.cpp + ${CPP_DIR}/SandboxRegistry.cpp ) target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}" + "${CPP_DIR}" ) target_link_libraries(${PROJECT_NAME} diff --git a/packages/react-native-sandbox/android/src/main/jni/SandboxJSIInstaller.cpp b/packages/react-native-sandbox/android/src/main/jni/SandboxJSIInstaller.cpp index cfd5202..acc16b2 100644 --- a/packages/react-native-sandbox/android/src/main/jni/SandboxJSIInstaller.cpp +++ b/packages/react-native-sandbox/android/src/main/jni/SandboxJSIInstaller.cpp @@ -1,4 +1,6 @@ +#include "ISandboxDelegate.h" #include "SandboxBindingsInstaller.h" +#include "SandboxRegistry.h" #include #include @@ -23,11 +25,68 @@ struct SandboxJSIState { std::shared_ptr onMessageCallback; std::vector pendingMessages; std::mutex mutex; + jobject delegateRef = nullptr; + std::string origin; + std::shared_ptr registryDelegate; }; static std::mutex gRegistryMutex; static std::unordered_map> gStates; +/** + * ISandboxDelegate that dispatches postMessage through the Kotlin delegate + * via JNI. The Kotlin postMessage uses runOnJSQueueThread to safely access + * the JSI runtime on the correct thread. + */ +class JNISandboxDelegate : public rnsandbox::ISandboxDelegate { + public: + JNISandboxDelegate(jobject globalDelegateRef) + : globalDelegateRef_(globalDelegateRef) {} + + void postMessage(const std::string& message) override { + JNIEnv* env = getJNIEnv(); + if (!env || !globalDelegateRef_) + return; + jclass cls = env->GetObjectClass(globalDelegateRef_); + jmethodID mid = + env->GetMethodID(cls, "postMessage", "(Ljava/lang/String;)V"); + jstring jMsg = env->NewStringUTF(message.c_str()); + env->CallVoidMethod(globalDelegateRef_, mid, jMsg); + env->DeleteLocalRef(jMsg); + env->DeleteLocalRef(cls); + } + + bool routeMessage(const std::string& message, const std::string& targetId) + override { + auto& registry = rnsandbox::SandboxRegistry::getInstance(); + auto targets = registry.findAll(targetId); + if (targets.empty()) + return false; + for (auto& target : targets) { + target->postMessage(message); + } + return true; + } + + void setOrigin(const std::string&) override {} + void setAllowedOrigins(const std::set&) override {} + void setAllowedTurboModules(const std::set&) override {} + + private: + jobject globalDelegateRef_; + + static JNIEnv* getJNIEnv() { + JNIEnv* env = nullptr; + if (gJavaVM) { + gJavaVM->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + if (!env) { + gJavaVM->AttachCurrentThread(&env, nullptr); + } + } + return env; + } +}; + static JNIEnv* getJNIEnv() { JNIEnv* env = nullptr; if (gJavaVM) { @@ -158,6 +217,7 @@ jlong installSandboxJSIBindings( } jobject globalDelegateRef = env->NewGlobalRef(delegateRef); + state->delegateRef = globalDelegateRef; auto postMessageFn = jsi::Function::createFromHostFunction( runtime, @@ -194,17 +254,17 @@ jlong installSandboxJSIBindings( rt, "postMessage: targetOrigin must be a string"); } std::string targetOrigin = args[1].getString(rt).utf8(rt); - jclass cls = jniEnv->GetObjectClass(globalDelegateRef); - jmethodID mid = jniEnv->GetMethodID( - cls, - "routeMessageFromJS", - "(Ljava/lang/String;Ljava/lang/String;)Z"); - jstring jMsg = jniEnv->NewStringUTF(messageJson.c_str()); - jstring jTarget = jniEnv->NewStringUTF(targetOrigin.c_str()); - jniEnv->CallBooleanMethod(globalDelegateRef, mid, jMsg, jTarget); - jniEnv->DeleteLocalRef(jMsg); - jniEnv->DeleteLocalRef(jTarget); - jniEnv->DeleteLocalRef(cls); + + // Fan out to all delegates registered for the target origin + auto& registry = rnsandbox::SandboxRegistry::getInstance(); + auto targets = registry.findAll(targetOrigin); + if (!targets.empty()) { + for (auto& target : targets) { + target->postMessage(messageJson); + } + } else { + LOGW("postMessage: target '%s' not found", targetOrigin.c_str()); + } } else { jclass cls = jniEnv->GetObjectClass(globalDelegateRef); jmethodID mid = jniEnv->GetMethodID( @@ -301,6 +361,33 @@ jlong installSandboxJSIBindings( LOGW("Failed to setup error handler: %s", e.what()); } + // Register in C++ SandboxRegistry if origin is set + { + JNIEnv* jniEnv = getJNIEnv(); + if (jniEnv) { + jclass cls = jniEnv->GetObjectClass(globalDelegateRef); + jfieldID originField = + jniEnv->GetFieldID(cls, "origin", "Ljava/lang/String;"); + auto jOrigin = + (jstring)jniEnv->GetObjectField(globalDelegateRef, originField); + jniEnv->DeleteLocalRef(cls); + if (jOrigin) { + const char* originChars = jniEnv->GetStringUTFChars(jOrigin, nullptr); + std::string origin(originChars); + jniEnv->ReleaseStringUTFChars(jOrigin, originChars); + jniEnv->DeleteLocalRef(jOrigin); + if (!origin.empty()) { + state->origin = origin; + auto delegate = + std::make_shared(globalDelegateRef); + state->registryDelegate = delegate; + rnsandbox::SandboxRegistry::getInstance().registerSandbox( + origin, delegate, std::set()); + } + } + } + } + return stateHandle; } @@ -375,6 +462,31 @@ Java_io_callstack_rnsandbox_SandboxJSIInstaller_nativePostMessage( } } +JNIEXPORT void JNICALL +Java_io_callstack_rnsandbox_SandboxJSIInstaller_nativeInstallErrorHandler( + JNIEnv*, + jclass, + jlong stateHandle) { + std::shared_ptr state; + { + std::lock_guard lock(gRegistryMutex); + auto it = gStates.find(stateHandle); + if (it == gStates.end()) + return; + state = it->second; + } + + std::lock_guard lock(state->mutex); + if (!state->runtime || !state->delegateRef) + return; + + try { + setupErrorHandler(*state->runtime, state->delegateRef); + } catch (const std::exception& e) { + LOGW("Failed to setup error handler post-bundle: %s", e.what()); + } +} + JNIEXPORT void JNICALL Java_io_callstack_rnsandbox_SandboxJSIInstaller_nativeDestroy( JNIEnv*, @@ -383,11 +495,20 @@ Java_io_callstack_rnsandbox_SandboxJSIInstaller_nativeDestroy( std::lock_guard lock(gRegistryMutex); auto it = gStates.find(stateHandle); if (it != gStates.end()) { + std::string origin; + std::shared_ptr delegate; { std::lock_guard stateLock(it->second->mutex); + origin = it->second->origin; + delegate = it->second->registryDelegate; it->second->onMessageCallback.reset(); it->second->pendingMessages.clear(); it->second->runtime = nullptr; + it->second->registryDelegate.reset(); + } + if (!origin.empty() && delegate) { + rnsandbox::SandboxRegistry::getInstance().unregisterDelegate( + origin, delegate); } gStates.erase(it); } diff --git a/packages/react-native-sandbox/ios/ISandboxDelegate.h b/packages/react-native-sandbox/cxx/ISandboxDelegate.h similarity index 95% rename from packages/react-native-sandbox/ios/ISandboxDelegate.h rename to packages/react-native-sandbox/cxx/ISandboxDelegate.h index 247b6c3..19234a4 100644 --- a/packages/react-native-sandbox/ios/ISandboxDelegate.h +++ b/packages/react-native-sandbox/cxx/ISandboxDelegate.h @@ -4,8 +4,7 @@ #include #include -namespace facebook { -namespace react { +namespace rnsandbox { /** * Interface for sandbox delegates that provides a clean abstraction @@ -57,5 +56,4 @@ class ISandboxDelegate { virtual void setAllowedTurboModules(const std::set& modules) = 0; }; -} // namespace react -} // namespace facebook \ No newline at end of file +} // namespace rnsandbox \ No newline at end of file diff --git a/packages/react-native-sandbox/ios/SandboxDelegateWrapper.h b/packages/react-native-sandbox/cxx/SandboxDelegateWrapper.h similarity index 94% rename from packages/react-native-sandbox/ios/SandboxDelegateWrapper.h rename to packages/react-native-sandbox/cxx/SandboxDelegateWrapper.h index dd54ae7..952eff2 100644 --- a/packages/react-native-sandbox/ios/SandboxDelegateWrapper.h +++ b/packages/react-native-sandbox/cxx/SandboxDelegateWrapper.h @@ -12,8 +12,7 @@ typedef struct objc_object SandboxReactNativeDelegate; #endif -namespace facebook { -namespace react { +namespace rnsandbox { /** * C++ wrapper for SandboxReactNativeDelegate that implements ISandboxDelegate. @@ -46,5 +45,4 @@ class SandboxDelegateWrapper : public ISandboxDelegate { SandboxReactNativeDelegate* delegate_; }; -} // namespace react -} // namespace facebook \ No newline at end of file +} // namespace rnsandbox \ No newline at end of file diff --git a/packages/react-native-sandbox/cxx/SandboxLog.h b/packages/react-native-sandbox/cxx/SandboxLog.h new file mode 100644 index 0000000..83b9d71 --- /dev/null +++ b/packages/react-native-sandbox/cxx/SandboxLog.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#ifdef __APPLE__ +#ifdef __OBJC__ +#import +#define SANDBOX_LOG_WARN(fmt, ...) RCTLogWarn(@fmt, ##__VA_ARGS__) +#else +// C++ context on Apple — use stderr as fallback +#include +#define SANDBOX_LOG_WARN(fmt, ...) \ + fprintf(stderr, "[SandboxWarn] " fmt "\n", ##__VA_ARGS__) +#endif +#elif defined(__ANDROID__) +#include +#define SANDBOX_LOG_WARN(fmt, ...) \ + __android_log_print( \ + ANDROID_LOG_WARN, "ReactNativeSandbox", fmt, ##__VA_ARGS__) +#else +#include +#define SANDBOX_LOG_WARN(fmt, ...) \ + fprintf(stderr, "[SandboxWarn] " fmt "\n", ##__VA_ARGS__) +#endif diff --git a/packages/react-native-sandbox/ios/SandboxRegistry.cpp b/packages/react-native-sandbox/cxx/SandboxRegistry.cpp similarity index 55% rename from packages/react-native-sandbox/ios/SandboxRegistry.cpp rename to packages/react-native-sandbox/cxx/SandboxRegistry.cpp index de9ca4f..e12a7c3 100644 --- a/packages/react-native-sandbox/ios/SandboxRegistry.cpp +++ b/packages/react-native-sandbox/cxx/SandboxRegistry.cpp @@ -1,23 +1,8 @@ #include "SandboxRegistry.h" -#include +#include #include -namespace facebook { -namespace react { - -/** - * Implementation of the thread-safe C++ SandboxRegistry. - * - * This class provides a singleton registry for managing sandbox delegates - * across multiple React Native instances. It uses std::recursive_mutex for - * thread safety and std::map for efficient lookups. - * - * Key features: - * - Thread-safe operations with recursive mutex - * - Efficient O(log n) lookups using std::map - * - Automatic cleanup of unregistered sandboxes - * - Support for permission-based communication - */ +namespace rnsandbox { SandboxRegistry& SandboxRegistry::getInstance() { static SandboxRegistry instance; @@ -34,36 +19,52 @@ void SandboxRegistry::registerSandbox( std::lock_guard lock(registryMutex_); - if (sandboxRegistry_.find(origin) != sandboxRegistry_.end()) { - std::cerr - << fmt::format( - "[SandboxRegistry] Warning: Overwriting existing sandbox with origin: {}", - origin) - << std::endl; + auto& delegates = sandboxRegistry_[origin]; + // Avoid duplicate registration of the same delegate + for (const auto& d : delegates) { + if (d == delegate) { + return; + } } - - sandboxRegistry_[origin] = delegate; + delegates.push_back(delegate); allowedOrigins_[origin] = allowedOrigins; } -void SandboxRegistry::unregister(const std::string& origin) { - if (origin.empty()) { +void SandboxRegistry::unregisterDelegate( + const std::string& origin, + const std::shared_ptr& delegate) { + if (origin.empty() || !delegate) { return; } std::lock_guard lock(registryMutex_); - auto registryIt = sandboxRegistry_.find(origin); - if (registryIt != sandboxRegistry_.end()) { - sandboxRegistry_.erase(registryIt); + auto it = sandboxRegistry_.find(origin); + if (it == sandboxRegistry_.end()) { + return; } - auto originsIt = allowedOrigins_.find(origin); - if (originsIt != allowedOrigins_.end()) { - allowedOrigins_.erase(originsIt); + auto& delegates = it->second; + delegates.erase( + std::remove(delegates.begin(), delegates.end(), delegate), + delegates.end()); + + if (delegates.empty()) { + sandboxRegistry_.erase(it); + allowedOrigins_.erase(origin); } } +void SandboxRegistry::unregister(const std::string& origin) { + if (origin.empty()) { + return; + } + + std::lock_guard lock(registryMutex_); + sandboxRegistry_.erase(origin); + allowedOrigins_.erase(origin); +} + std::shared_ptr SandboxRegistry::find( const std::string& origin) { if (origin.empty()) { @@ -72,12 +73,28 @@ std::shared_ptr SandboxRegistry::find( std::lock_guard lock(registryMutex_); + auto it = sandboxRegistry_.find(origin); + if (it != sandboxRegistry_.end() && !it->second.empty()) { + return it->second.front(); + } + + return nullptr; +} + +std::vector> SandboxRegistry::findAll( + const std::string& origin) { + if (origin.empty()) { + return {}; + } + + std::lock_guard lock(registryMutex_); + auto it = sandboxRegistry_.find(origin); if (it != sandboxRegistry_.end()) { return it->second; } - return nullptr; + return {}; } bool SandboxRegistry::isPermittedFrom( @@ -103,5 +120,4 @@ void SandboxRegistry::reset() { allowedOrigins_.clear(); } -} // namespace react -} // namespace facebook \ No newline at end of file +} // namespace rnsandbox diff --git a/packages/react-native-sandbox/cxx/SandboxRegistry.h b/packages/react-native-sandbox/cxx/SandboxRegistry.h new file mode 100644 index 0000000..487c91f --- /dev/null +++ b/packages/react-native-sandbox/cxx/SandboxRegistry.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "ISandboxDelegate.h" + +namespace rnsandbox { + +class SandboxRegistry { + public: + static SandboxRegistry& getInstance(); + + void registerSandbox( + const std::string& origin, + std::shared_ptr delegate, + const std::set& allowedOrigins); + + void unregisterDelegate( + const std::string& origin, + const std::shared_ptr& delegate); + + void unregister(const std::string& origin); + + std::shared_ptr find(const std::string& origin); + + std::vector> findAll( + const std::string& origin); + + bool isPermittedFrom( + const std::string& sourceOrigin, + const std::string& targetOrigin); + + void reset(); + + private: + SandboxRegistry() = default; + SandboxRegistry(const SandboxRegistry&) = delete; + SandboxRegistry& operator=(const SandboxRegistry&) = delete; + + std::map>> + sandboxRegistry_; + std::map> allowedOrigins_; + mutable std::recursive_mutex registryMutex_; +}; + +} // namespace rnsandbox diff --git a/packages/react-native-sandbox/cxx/StubTurboModuleCxx.cpp b/packages/react-native-sandbox/cxx/StubTurboModuleCxx.cpp new file mode 100644 index 0000000..cc23ec8 --- /dev/null +++ b/packages/react-native-sandbox/cxx/StubTurboModuleCxx.cpp @@ -0,0 +1,50 @@ +#include "StubTurboModuleCxx.h" +#include "SandboxLog.h" + +namespace rnsandbox { + +StubTurboModuleCxx::StubTurboModuleCxx( + const std::string& moduleName, + std::shared_ptr jsInvoker) + : facebook::react::TurboModule("StubTurboModuleCxx", jsInvoker), + moduleName_(moduleName) { + logBlockedAccess("constructor"); +} + +facebook::jsi::Value StubTurboModuleCxx::get( + facebook::jsi::Runtime& runtime, + const facebook::jsi::PropNameID& propName) { + std::string methodName = propName.utf8(runtime); + logBlockedAccess(methodName); + return createStubFunction(runtime, methodName); +} + +void StubTurboModuleCxx::logBlockedAccess(const std::string& methodName) const { + SANDBOX_LOG_WARN( + "[StubTurboModuleCxx] Blocked access to method '%s' on disallowed " + "module '%s'.", + methodName.c_str(), + moduleName_.c_str()); +} + +facebook::jsi::Function StubTurboModuleCxx::createStubFunction( + facebook::jsi::Runtime& runtime, + const std::string& methodName) const { + return facebook::jsi::Function::createFromHostFunction( + runtime, + facebook::jsi::PropNameID::forAscii(runtime, methodName.c_str()), + 0, + [this, methodName]( + facebook::jsi::Runtime& rt, + const facebook::jsi::Value& thisVal, + const facebook::jsi::Value* args, + size_t count) -> facebook::jsi::Value { + SANDBOX_LOG_WARN( + "[StubTurboModuleCxx] Method call '%s' blocked on module '%s'.", + methodName.c_str(), + this->moduleName_.c_str()); + return facebook::jsi::Value::undefined(); + }); +} + +} // namespace rnsandbox diff --git a/packages/react-native-sandbox/cxx/StubTurboModuleCxx.h b/packages/react-native-sandbox/cxx/StubTurboModuleCxx.h new file mode 100644 index 0000000..62487a1 --- /dev/null +++ b/packages/react-native-sandbox/cxx/StubTurboModuleCxx.h @@ -0,0 +1,34 @@ +#pragma once + +#ifdef __cplusplus + +#include +#include +#include +#include + +namespace rnsandbox { + +class StubTurboModuleCxx : public facebook::react::TurboModule { + public: + StubTurboModuleCxx( + const std::string& moduleName, + std::shared_ptr jsInvoker); + + facebook::jsi::Value get( + facebook::jsi::Runtime& runtime, + const facebook::jsi::PropNameID& propName) override; + + private: + std::string moduleName_; + + void logBlockedAccess(const std::string& methodName) const; + + facebook::jsi::Function createStubFunction( + facebook::jsi::Runtime& runtime, + const std::string& methodName) const; +}; + +} // namespace rnsandbox + +#endif // __cplusplus diff --git a/packages/react-native-sandbox/ios/SandboxDelegateWrapper.mm b/packages/react-native-sandbox/ios/SandboxDelegateWrapper.mm index 81d8dd4..36bcea8 100644 --- a/packages/react-native-sandbox/ios/SandboxDelegateWrapper.mm +++ b/packages/react-native-sandbox/ios/SandboxDelegateWrapper.mm @@ -1,8 +1,7 @@ #include "SandboxDelegateWrapper.h" #import "SandboxReactNativeDelegate.h" -namespace facebook { -namespace react { +namespace rnsandbox { SandboxDelegateWrapper::SandboxDelegateWrapper(SandboxReactNativeDelegate *delegate) : delegate_(delegate) { @@ -47,5 +46,4 @@ [delegate_ setAllowedTurboModules:modules]; } -} // namespace react -} // namespace facebook \ No newline at end of file +} // namespace rnsandbox \ No newline at end of file diff --git a/packages/react-native-sandbox/ios/SandboxReactNativeDelegate.mm b/packages/react-native-sandbox/ios/SandboxReactNativeDelegate.mm index 6a628d4..05474a3 100644 --- a/packages/react-native-sandbox/ios/SandboxReactNativeDelegate.mm +++ b/packages/react-native-sandbox/ios/SandboxReactNativeDelegate.mm @@ -123,7 +123,7 @@ - (void)setOrigin:(std::string)origin // Unregister old origin if it exists if (!_origin.empty()) { - auto ®istry = SandboxRegistry::getInstance(); + auto ®istry = rnsandbox::SandboxRegistry::getInstance(); registry.unregister(_origin); } @@ -132,8 +132,8 @@ - (void)setOrigin:(std::string)origin // Register new origin if it's not empty if (!_origin.empty()) { - auto ®istry = SandboxRegistry::getInstance(); - auto wrapper = std::make_shared(self); + auto ®istry = rnsandbox::SandboxRegistry::getInstance(); + auto wrapper = std::make_shared(self); registry.registerSandbox(_origin, wrapper, _allowedOrigins); } } @@ -149,8 +149,8 @@ - (void)setAllowedOrigins:(std::set)allowedOrigins // Re-register with new allowedOrigins if origin is set if (!_origin.empty()) { - auto ®istry = SandboxRegistry::getInstance(); - auto wrapper = std::make_shared(self); + auto ®istry = rnsandbox::SandboxRegistry::getInstance(); + auto wrapper = std::make_shared(self); registry.registerSandbox(_origin, wrapper, _allowedOrigins); } } @@ -163,7 +163,7 @@ - (void)setAllowedTurboModules:(std::set)allowedTurboModules - (void)dealloc { if (!_origin.empty()) { - auto ®istry = SandboxRegistry::getInstance(); + auto ®istry = rnsandbox::SandboxRegistry::getInstance(); registry.unregister(_origin); } else { [self cleanupResources]; @@ -238,7 +238,7 @@ - (void)postMessage:(const std::string &)message - (bool)routeMessage:(const std::string &)message toSandbox:(const std::string &)targetId { - auto ®istry = SandboxRegistry::getInstance(); + auto ®istry = rnsandbox::SandboxRegistry::getInstance(); auto target = registry.find(targetId); if (!target) { return false; @@ -302,7 +302,7 @@ - (void)hostDidStart:(RCTHost *)host return [super getTurboModule:name jsInvoker:jsInvoker]; } else { // Return C++ stub instead of nullptr - return std::make_shared(name, jsInvoker); + return std::make_shared(name, jsInvoker); } } diff --git a/packages/react-native-sandbox/ios/SandboxRegistry.h b/packages/react-native-sandbox/ios/SandboxRegistry.h deleted file mode 100644 index 116878a..0000000 --- a/packages/react-native-sandbox/ios/SandboxRegistry.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include "ISandboxDelegate.h" - -namespace facebook { -namespace react { - -/** - * A thread-safe singleton registry for managing sandbox delegates across - * multiple instances. This C++ implementation provides better performance and - * testability compared to the Objective-C++ version by eliminating string - * conversions and React Native dependencies. - * - * The registry is type-agnostic and works with any delegate type that - * implements the ISandboxDelegate interface. - */ -class SandboxRegistry { - public: - /** - * Returns the shared singleton instance of the registry. - * @return Reference to the shared registry instance - */ - static SandboxRegistry& getInstance(); - - /** - * Registers a delegate with the specified origin and allowed origins. - * @param origin Unique identifier for the sandbox - * @param delegate The delegate instance to register (must implement - * ISandboxDelegate) - * @param allowedOrigins Vector of sandbox origins that are allowed to send - * messages to this sandbox. If empty, no other sandboxes will be allowed to - * send messages (except 'host'). Re-registering with new allowedOrigins will - * override previous settings. - */ - void registerSandbox( - const std::string& origin, - std::shared_ptr delegate, - const std::set& allowedOrigins); - - /** - * Unregisters a delegate by origin. - * @param origin The origin of the sandbox to unregister - */ - void unregister(const std::string& origin); - - /** - * Finds a delegate by origin. - * @param origin The origin of the sandbox to find - * @return The registered delegate, or nullptr if not found - */ - std::shared_ptr find(const std::string& origin); - - /** - * Checks if communication is permitted from one sandbox to another. - * @param sourceOrigin The origin of the sandbox attempting to send a message - * @param targetOrigin The origin of the sandbox that would receive the - * message - * @return true if the source sandbox is permitted to send messages to the - * target, false otherwise - */ - bool isPermittedFrom( - const std::string& sourceOrigin, - const std::string& targetOrigin); - - /** - * Resets the registry (clears all sandboxes and permissions). - * This is primarily used for testing. - */ - void reset(); - - private: - // Private constructor for singleton pattern - SandboxRegistry() = default; - - // Disable copy constructor and assignment operator - SandboxRegistry(const SandboxRegistry&) = delete; - SandboxRegistry& operator=(const SandboxRegistry&) = delete; - - // Registry storage - std::map> sandboxRegistry_; - std::map> allowedOrigins_; - - // Thread safety - mutable std::recursive_mutex registryMutex_; -}; - -} // namespace react -} // namespace facebook \ No newline at end of file diff --git a/packages/react-native-sandbox/ios/StubTurboModuleCxx.h b/packages/react-native-sandbox/ios/StubTurboModuleCxx.h deleted file mode 100644 index 0530a79..0000000 --- a/packages/react-native-sandbox/ios/StubTurboModuleCxx.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#ifdef __cplusplus - -#include -#include -#include -#include - -namespace facebook { -namespace react { - -/** - * A C++ TurboModule stub implementation that intercepts all method calls for - * blocked modules. This prevents crashes when sandbox code tries to access - * disallowed modules while logging warnings. - */ -class StubTurboModuleCxx : public TurboModule { - public: - /** - * Creates a stub for the specified module name - * @param moduleName The name of the blocked module - * @param jsInvoker The JavaScript invoker for communication - */ - StubTurboModuleCxx( - const std::string& moduleName, - std::shared_ptr jsInvoker); - - /** - * Intercepts all method calls and returns safe default values - * This is the main method that gets called for any TurboModule method - * invocation - */ - jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& propName) - override; - - private: - std::string moduleName_; - - /** - * Logs a warning message about the blocked module access - */ - void logBlockedAccess(const std::string& methodName) const; - - /** - * Creates a stub function that logs warnings and returns safe values - */ - jsi::Function createStubFunction( - jsi::Runtime& runtime, - const std::string& methodName) const; -}; - -} // namespace react -} // namespace facebook - -#endif // __cplusplus \ No newline at end of file diff --git a/packages/react-native-sandbox/ios/StubTurboModuleCxx.mm b/packages/react-native-sandbox/ios/StubTurboModuleCxx.mm deleted file mode 100644 index bed5266..0000000 --- a/packages/react-native-sandbox/ios/StubTurboModuleCxx.mm +++ /dev/null @@ -1,53 +0,0 @@ -#include "StubTurboModuleCxx.h" -#import - -namespace facebook { -namespace react { - -StubTurboModuleCxx::StubTurboModuleCxx(const std::string &moduleName, std::shared_ptr jsInvoker) - : TurboModule("StubTurboModuleCxx", jsInvoker), moduleName_(moduleName) -{ - logBlockedAccess("constructor"); -} - -jsi::Value StubTurboModuleCxx::get(jsi::Runtime &runtime, const jsi::PropNameID &propName) -{ - // Get the property name as a string - std::string methodName = propName.utf8(runtime); - - // Log the blocked access attempt - logBlockedAccess(methodName); - - // Return a stub function that will handle any method calls - return createStubFunction(runtime, methodName); -} - -void StubTurboModuleCxx::logBlockedAccess(const std::string &methodName) const -{ - RCTLogWarn( - @"[StubTurboModuleCxx] Blocked access to method '%s' on disallowed module '%s'. This module is blocked as unsafe, please add it to allowedTurboModules in SandboxReactNativeView.", - methodName.c_str(), - moduleName_.c_str()); -} - -jsi::Function StubTurboModuleCxx::createStubFunction(jsi::Runtime &runtime, const std::string &methodName) const -{ - return jsi::Function::createFromHostFunction( - runtime, - jsi::PropNameID::forAscii(runtime, methodName.c_str()), - 0, // number of parameters - we accept any number - [this, methodName]( - jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value { - // Log the method call attempt using React Native API - RCTLogWarn( - @"[StubTurboModuleCxx] Method call '%s' blocked on module '%s'. This module is blocked as unsafe, please add it to allowedTurboModules in SandboxReactNativeView.", - methodName.c_str(), - this->moduleName_.c_str()); - - // Fail fast - just return undefined for all cases - return jsi::Value::undefined(); - }); -} - -} // namespace react -} // namespace facebook \ No newline at end of file diff --git a/packages/react-native-sandbox/package.json b/packages/react-native-sandbox/package.json index 790896b..b0f8a29 100644 --- a/packages/react-native-sandbox/package.json +++ b/packages/react-native-sandbox/package.json @@ -23,16 +23,17 @@ "files": [ "src", "ios", + "cxx", "android", "specs", "React-Sandbox.podspec" ], "scripts": { "lint": "npm run lint:clang && npm run lint:kotlin", - "lint:clang": "clang-format --dry-run --Werror ios/*.{h,mm,cpp} android/src/main/jni/*.{h,cpp} tests/*.{h,cpp}", + "lint:clang": "clang-format --dry-run --Werror ios/*.{h,mm} cxx/*.{h,cpp} android/src/main/jni/*.{h,cpp} tests/*.{h,cpp}", "lint:kotlin": "ktlint 'android/src/main/java/**/*.kt'", "format": "npm run format:clang && npm run format:kotlin", - "format:clang": "clang-format -i ios/*.{h,mm,cpp} android/src/main/jni/*.{h,cpp} tests/*.{h,cpp}", + "format:clang": "clang-format -i ios/*.{h,mm} cxx/*.{h,cpp} android/src/main/jni/*.{h,cpp} tests/*.{h,cpp}", "format:kotlin": "ktlint -F 'android/src/main/java/**/*.kt'", "typecheck": "tsc --noEmit", "prepare": "bob build", diff --git a/packages/react-native-sandbox/tests/CMakeLists.txt b/packages/react-native-sandbox/tests/CMakeLists.txt index bf04aeb..bef9aeb 100644 --- a/packages/react-native-sandbox/tests/CMakeLists.txt +++ b/packages/react-native-sandbox/tests/CMakeLists.txt @@ -10,11 +10,11 @@ endif() set(CPP_TEST_SOURCES SandboxRegistryTest.cpp - ../ios/SandboxRegistry.cpp + ../cxx/SandboxRegistry.cpp ) set(INCLUDE_DIRS - ${CMAKE_CURRENT_SOURCE_DIR}/../ios + ${CMAKE_CURRENT_SOURCE_DIR}/../cxx ${CMAKE_CURRENT_SOURCE_DIR} ) @@ -28,13 +28,6 @@ FetchContent_Declare( set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) -FetchContent_Declare( - fmt - GIT_REPOSITORY https://github.com/fmtlib/fmt.git - GIT_TAG 10.1.1 -) -FetchContent_MakeAvailable(fmt) - set(TEST_EXECUTABLE_NAME SandboxRegistryCppTests) add_executable(${TEST_EXECUTABLE_NAME} ${CPP_TEST_SOURCES}) @@ -45,7 +38,6 @@ target_link_libraries(${TEST_EXECUTABLE_NAME} gtest_main gmock gmock_main - fmt::fmt ) target_compile_options(${TEST_EXECUTABLE_NAME} PRIVATE @@ -55,4 +47,4 @@ target_compile_options(${TEST_EXECUTABLE_NAME} PRIVATE ) enable_testing() -add_test(NAME ${TEST_EXECUTABLE_NAME} COMMAND ${TEST_EXECUTABLE_NAME}) \ No newline at end of file +add_test(NAME ${TEST_EXECUTABLE_NAME} COMMAND ${TEST_EXECUTABLE_NAME}) diff --git a/packages/react-native-sandbox/tests/MockSandboxDelegate.h b/packages/react-native-sandbox/tests/MockSandboxDelegate.h index df4dbbf..b68401f 100644 --- a/packages/react-native-sandbox/tests/MockSandboxDelegate.h +++ b/packages/react-native-sandbox/tests/MockSandboxDelegate.h @@ -6,8 +6,7 @@ #include -namespace facebook { -namespace react { +namespace rnsandbox { class MockSandboxDelegate : public ISandboxDelegate { public: @@ -30,5 +29,4 @@ class MockSandboxDelegate : public ISandboxDelegate { (override)); }; -} // namespace react -} // namespace facebook \ No newline at end of file +} // namespace rnsandbox \ No newline at end of file diff --git a/packages/react-native-sandbox/tests/SandboxRegistryTest.cpp b/packages/react-native-sandbox/tests/SandboxRegistryTest.cpp index 2f3099b..bb35a8f 100644 --- a/packages/react-native-sandbox/tests/SandboxRegistryTest.cpp +++ b/packages/react-native-sandbox/tests/SandboxRegistryTest.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -10,7 +9,7 @@ #include "MockSandboxDelegate.h" -using namespace facebook::react; +using namespace rnsandbox; using ::testing::_; using ::testing::Return; using ::testing::StrictMock; @@ -82,23 +81,66 @@ TEST_F(SandboxRegistryTest, EmptyAllowedOrigins) { EXPECT_FALSE(registry.isPermittedFrom("test-origin", "any-origin")); } -TEST_F(SandboxRegistryTest, OverwriteExistingSandbox) { +TEST_F(SandboxRegistryTest, MultipleDelegatesSameOrigin) { auto& registry = SandboxRegistry::getInstance(); - auto mockDelegate1 = std::make_shared>(); - auto mockDelegate2 = std::make_shared>(); + auto delegate1 = std::make_shared>(); + auto delegate2 = std::make_shared>(); - std::set allowedOrigins1 = {"allowed1"}; - std::set allowedOrigins2 = {"allowed2"}; + std::set allowedOrigins = {"other"}; + registry.registerSandbox("shared", delegate1, allowedOrigins); + registry.registerSandbox("shared", delegate2, allowedOrigins); - registry.registerSandbox("test-origin", mockDelegate1, allowedOrigins1); + // find returns the first + EXPECT_EQ(registry.find("shared"), delegate1); - registry.registerSandbox("test-origin", mockDelegate2, allowedOrigins2); + // findAll returns both + auto all = registry.findAll("shared"); + EXPECT_EQ(all.size(), 2u); + EXPECT_EQ(all[0], delegate1); + EXPECT_EQ(all[1], delegate2); +} - auto found = registry.find("test-origin"); - EXPECT_EQ(found, mockDelegate2); +TEST_F(SandboxRegistryTest, UnregisterDelegateRemovesOnlyThatDelegate) { + auto& registry = SandboxRegistry::getInstance(); + auto delegate1 = std::make_shared>(); + auto delegate2 = std::make_shared>(); - EXPECT_FALSE(registry.isPermittedFrom("allowed2", "test-origin")); - EXPECT_FALSE(registry.isPermittedFrom("allowed1", "test-origin")); + std::set allowedOrigins = {"other"}; + registry.registerSandbox("shared", delegate1, allowedOrigins); + registry.registerSandbox("shared", delegate2, allowedOrigins); + + registry.unregisterDelegate("shared", delegate1); + + auto all = registry.findAll("shared"); + EXPECT_EQ(all.size(), 1u); + EXPECT_EQ(all[0], delegate2); + EXPECT_EQ(registry.find("shared"), delegate2); +} + +TEST_F(SandboxRegistryTest, UnregisterDelegateLastCleansUpOrigin) { + auto& registry = SandboxRegistry::getInstance(); + auto delegate = std::make_shared>(); + + std::set allowedOrigins = {"other"}; + registry.registerSandbox("solo", delegate, allowedOrigins); + + registry.unregisterDelegate("solo", delegate); + + EXPECT_EQ(registry.find("solo"), nullptr); + EXPECT_TRUE(registry.findAll("solo").empty()); + EXPECT_FALSE(registry.isPermittedFrom("solo", "other")); +} + +TEST_F(SandboxRegistryTest, DuplicateRegistrationIgnored) { + auto& registry = SandboxRegistry::getInstance(); + auto delegate = std::make_shared>(); + + std::set allowedOrigins; + registry.registerSandbox("dup", delegate, allowedOrigins); + registry.registerSandbox("dup", delegate, allowedOrigins); + + auto all = registry.findAll("dup"); + EXPECT_EQ(all.size(), 1u); } TEST_F(SandboxRegistryTest, EmptyOriginHandling) { @@ -167,7 +209,7 @@ TEST_F(SandboxRegistryTest, ThreadSafety) { EXPECT_TRUE(registry.isPermittedFrom(origin, "other_sandbox")); EXPECT_FALSE(registry.isPermittedFrom(origin, "blocked_sandbox")); - registry.unregister(origin); + registry.unregisterDelegate(origin, mockDelegate); auto notFound = registry.find(origin); EXPECT_EQ(notFound, nullptr); @@ -180,4 +222,4 @@ TEST_F(SandboxRegistryTest, ThreadSafety) { EXPECT_EQ(status, std::future_status::ready) << "Thread safety test timed out after 5 seconds"; } -} \ No newline at end of file +}