Skip to content

Commit 7ab3610

Browse files
feat: replace RCTStubTurboModule with C++ StubTurboModuleCxx implementation
- Remove getModuleClassFromName: and getModuleInstanceFromClass: methods - Implement pure C++ TurboModule stub that works via getTurboModule:jsInvoker: - Use RCTLogWarn for proper React Native logging - Add C++ compilation guards for umbrella header compatibility - Update podspec to include .cpp/.mm files and C++17 standard
1 parent b5d7dab commit 7ab3610

File tree

6 files changed

+154
-6
lines changed

6 files changed

+154
-6
lines changed

packages/react-native-multinstance/React-MultInstance.podspec

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ Pod::Spec.new do |s|
1111
s.authors = { "Alex Babrykovich" => "aliaksandr.babrykovich@callstack.com" } # Please update this
1212
s.platforms = { :ios => "12.4" }
1313
s.source = { :git => "https://github.com/callstackincubator/multi-instance-poc.git", :tag => "#{s.version}" } # Please update this
14-
s.source_files = "ios/**/*.{h,m,mm,swift}"
14+
s.source_files = "ios/**/*.{h,m,mm,cpp,swift}"
1515
s.dependency "React-Core"
1616
s.dependency "React-RCTAppDelegate"
17+
s.dependency "ReactAppDependencyProvider"
1718
s.dependency "ReactCommon"
18-
# s.dependency "RCT-Folly"
1919
s.pod_target_xcconfig = {
2020
"DEFINES_MODULE" => "YES",
2121
"GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) FOLLY_NO_CONFIG FOLLY_CFG_NO_COROUTINES",
2222
"OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
23+
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
2324
}
2425
end

packages/react-native-multinstance/ios/SandboxReactNativeDelegate.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,33 @@
1212

1313
NS_ASSUME_NONNULL_BEGIN
1414

15+
/**
16+
* A React Native delegate that provides sandboxed environments with filtered module access.
17+
* This delegate uses RCTFilteredAppDependencyProvider to restrict which native modules
18+
* are available to the JavaScript runtime, enhancing security in multi-instance scenarios.
19+
*/
1520
@interface SandboxReactNativeDelegate : RCTDefaultReactNativeFactoryDelegate
1621

1722
@property (nonatomic, copy, nullable) RCTDirectEventBlock onMessageHost;
1823
@property (nonatomic, copy, nullable) RCTDirectEventBlock onErrorHost;
24+
25+
/**
26+
* Sets the list of allowed TurboModules for this sandbox instance.
27+
* Only modules in this list will be accessible to the JavaScript runtime.
28+
*/
1929
@property (nonatomic, copy) NSArray<NSString *> *allowedTurboModules;
2030

31+
/**
32+
* Initializes the delegate with a specific JS bundle source.
33+
* @param jsBundleSource The source for the JavaScript bundle (file path or URL)
34+
* @return Initialized delegate instance with filtered module access
35+
*/
2136
- (instancetype)initWithJSBundleSource:(NSString *)jsBundleSource;
37+
38+
/**
39+
* Posts a message to the JavaScript runtime.
40+
* @param message Dictionary containing the message data
41+
*/
2242
- (void)postMessage:(NSDictionary *)message;
2343

2444
@end

packages/react-native-multinstance/ios/SandboxReactNativeDelegate.mm

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
#import "SandboxReactNativeDelegate.h"
9+
#import "StubTurboModuleCxx.h"
910

1011
#include <memory>
1112
#include <jsi/decorator.h>
@@ -207,9 +208,14 @@ - (void)hostDidStart:(RCTHost *)host {
207208
}
208209

209210
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
210-
jsInvoker:
211-
(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker {
212-
return _allowedModules.contains(name) ? [super getTurboModule:name jsInvoker:jsInvoker] : nullptr;
211+
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker {
212+
if (_allowedModules.contains(name)) {
213+
return [super getTurboModule:name jsInvoker:jsInvoker];
214+
} else {
215+
NSLog(@"SandboxReactNativeDelegate.getTurboModule: blocking access to C++ TurboModule '%s', returning C++ stub", name.c_str());
216+
// Return C++ stub instead of nullptr
217+
return std::make_shared<facebook::react::StubTurboModuleCxx>(name, jsInvoker);
218+
}
213219
}
214220

215221
@end
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#pragma once
2+
3+
#ifdef __cplusplus
4+
5+
#include <ReactCommon/TurboModule.h>
6+
#include <jsi/jsi.h>
7+
#include <memory>
8+
#include <string>
9+
10+
namespace facebook {
11+
namespace react {
12+
13+
/**
14+
* A C++ TurboModule stub implementation that intercepts all method calls for blocked modules.
15+
* This prevents crashes when sandbox code tries to access disallowed modules while logging warnings.
16+
*/
17+
class StubTurboModuleCxx : public TurboModule {
18+
public:
19+
/**
20+
* Creates a stub for the specified module name
21+
* @param moduleName The name of the blocked module
22+
* @param jsInvoker The JavaScript invoker for communication
23+
*/
24+
StubTurboModuleCxx(const std::string& moduleName, std::shared_ptr<CallInvoker> jsInvoker);
25+
26+
/**
27+
* Intercepts all method calls and returns safe default values
28+
* This is the main method that gets called for any TurboModule method invocation
29+
*/
30+
jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& propName) override;
31+
32+
private:
33+
std::string moduleName_;
34+
35+
/**
36+
* Logs a warning message about the blocked module access
37+
*/
38+
void logBlockedAccess(const std::string& methodName) const;
39+
40+
/**
41+
* Creates a stub function that logs warnings and returns safe values
42+
*/
43+
jsi::Function createStubFunction(jsi::Runtime& runtime, const std::string& methodName) const;
44+
};
45+
46+
} // namespace react
47+
} // namespace facebook
48+
49+
#endif // __cplusplus
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include "StubTurboModuleCxx.h"
2+
#import <React/RCTLog.h>
3+
4+
namespace facebook {
5+
namespace react {
6+
7+
StubTurboModuleCxx::StubTurboModuleCxx(const std::string& moduleName, std::shared_ptr<CallInvoker> jsInvoker)
8+
: TurboModule("StubTurboModuleCxx", jsInvoker), moduleName_(moduleName) {
9+
logBlockedAccess("constructor");
10+
}
11+
12+
jsi::Value StubTurboModuleCxx::get(jsi::Runtime& runtime, const jsi::PropNameID& propName) {
13+
// Get the property name as a string
14+
std::string methodName = propName.utf8(runtime);
15+
16+
// Log the blocked access attempt
17+
logBlockedAccess(methodName);
18+
19+
// Return a stub function that will handle any method calls
20+
return createStubFunction(runtime, methodName);
21+
}
22+
23+
void StubTurboModuleCxx::logBlockedAccess(const std::string& methodName) const {
24+
RCTLogWarn(@"[StubTurboModuleCxx] Blocked access to method '%s' on disallowed module '%s'. This module is blocked as unsafe, please add it to allowedTurboModules in SandboxReactNativeView.",
25+
methodName.c_str(), moduleName_.c_str());
26+
}
27+
28+
jsi::Function StubTurboModuleCxx::createStubFunction(jsi::Runtime& runtime, const std::string& methodName) const {
29+
return jsi::Function::createFromHostFunction(
30+
runtime,
31+
jsi::PropNameID::forAscii(runtime, methodName.c_str()),
32+
0, // number of parameters - we accept any number
33+
[this, methodName](jsi::Runtime& rt, const jsi::Value& thisVal, const jsi::Value* args, size_t count) -> jsi::Value {
34+
// Log the method call attempt using React Native API
35+
RCTLogWarn(@"[StubTurboModuleCxx] Method call '%s' blocked on module '%s'. This module is blocked as unsafe, please add it to allowedTurboModules in SandboxReactNativeView.",
36+
methodName.c_str(), this->moduleName_.c_str());
37+
38+
// Fail fast - just return undefined for all cases
39+
return jsi::Value::undefined();
40+
}
41+
);
42+
}
43+
44+
} // namespace react
45+
} // namespace facebook

packages/react-native-multinstance/src/index.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,34 @@ import type {
44
DirectEventHandler,
55
} from 'react-native/Libraries/Types/CodegenTypes';
66

7-
const SANDBOX_TURBOMODULES_WHITELIST = ['NativeMicrotasksCxx'];
7+
const SANDBOX_TURBOMODULES_WHITELIST = [
8+
'NativeMicrotasksCxx',
9+
'RedBox',
10+
'DevMenu',
11+
'DevLoadingView',
12+
'EventDispatcher',
13+
'ImageLoader',
14+
'ExceptionsManager',
15+
'PlatformConstants',
16+
'DevSettings',
17+
'SettingsManager',
18+
'AppState',
19+
'SourceCode',
20+
'WebSocketModule',
21+
'Networking',
22+
'DeviceInfo',
23+
'AccessibilityManager',
24+
'LinkingManager',
25+
'BlobModule',
26+
'NativeEventEmitter',
27+
'LogBox',
28+
'Appearance',
29+
'ReactDevToolsRuntimeSettingsModule',
30+
'NativeReactNativeFeatureFlagsCxx',
31+
//'NativeAnimatedTurboModule',
32+
'KeyboardObserver',
33+
//'FrameRateLogger',
34+
];
835

936
type GenericProps = {
1037
[key: string]: any;

0 commit comments

Comments
 (0)