Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
#
Expand All @@ -54,6 +57,9 @@ vendor/
# node.js
#
node_modules/
node_modules
**/node_modules/
**/node_modules
npm-debug.log
yarn-debug.log
yarn-error.log
Expand Down
4 changes: 2 additions & 2 deletions apps/demo/ios/Demo.xcodeproj/project.pbxproj
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: should be dropped, please use bun

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fill fix it after merge

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
290 changes: 145 additions & 145 deletions apps/demo/ios/Podfile.lock
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions packages/react-native-sandbox/React-Sandbox.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,29 +33,21 @@ class SandboxReactNativeDelegate(
) {
companion object {
private const val TAG = "SandboxRNDelegate"

private val sharedHosts = mutableMapOf<String, SharedReactHost>()

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<String> = emptySet()
var allowedOrigins: Set<String> = emptySet()
set(value) {
field = value
if (origin.isNotEmpty()) {
SandboxRegistry.register(origin, this, value)
}
}

@JvmField var hasOnMessageHandler: Boolean = false

Expand All @@ -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(
Expand All @@ -79,49 +72,75 @@ class SandboxReactNativeDelegate(

val capturedBundleSource = jsBundleSource
val capturedAllowedModules = allowedTurboModules
val sandboxId = System.identityHashCode(this).toString(16)
val sandboxContext = SandboxContextWrapper(context, sandboxId)

try {
val packages: List<ReactPackage> =
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<ReactPackage> =
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

host.addReactInstanceEventListener(
object : ReactInstanceEventListener {
override fun onReactContextInitialized(reactContext: ReactContext) {
sandboxReactContext = reactContext
if (jsiStateHandle != 0L) {
reactContext.runOnJSQueueThread {
SandboxJSIInstaller.nativeInstallErrorHandler(jsiStateHandle)
}
}
}
},
)
Expand Down Expand Up @@ -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")
Expand All @@ -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) {
Expand All @@ -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()
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Loading