feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,216 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTDefines.h>
/*
* Defined in RCTUtils.m
*/
RCT_EXTERN BOOL RCTIsMainQueue(void);
/**
* This is the main assert macro that you should use. Asserts should be compiled out
* in production builds. You can customize the assert behaviour by setting a custom
* assert handler through `RCTSetAssertFunction`.
*/
#ifndef NS_BLOCK_ASSERTIONS
#define RCTAssert(condition, ...) \
do { \
if ((condition) == 0) { \
_RCTAssertFormat(#condition, __FILE__, __LINE__, __func__, __VA_ARGS__); \
if (RCT_NSASSERT) { \
[[NSAssertionHandler currentHandler] handleFailureInFunction:(NSString *_Nonnull)@(__func__) \
file:(NSString *_Nonnull)@(__FILE__) \
lineNumber:__LINE__ \
description:__VA_ARGS__]; \
} \
} \
} while (false)
#else
#define RCTAssert(condition, ...) \
do { \
} while (false)
#endif
RCT_EXTERN void _RCTAssertFormat(const char *, const char *, int, const char *, NSString *, ...)
NS_FORMAT_FUNCTION(5, 6);
/**
* Report a fatal condition when executing. These calls will _NOT_ be compiled out
* in production, and crash the app by default. You can customize the fatal behaviour
* by setting a custom fatal handler through `RCTSetFatalHandler` and
* `RCTSetFatalExceptionHandler`.
*/
RCT_EXTERN void RCTFatal(NSError *error);
RCT_EXTERN void RCTFatalException(NSException *exception);
/**
* The default error domain to be used for React errors.
*/
RCT_EXTERN NSString *const RCTErrorDomain;
/**
* JS Stack trace provided as part of an NSError's userInfo
*/
RCT_EXTERN NSString *const RCTJSStackTraceKey;
/**
* Raw JS Stack trace string provided as part of an NSError's userInfo
*/
RCT_EXTERN NSString *const RCTJSRawStackTraceKey;
/**
* Objective-C stack trace string provided as part of an NSError's userInfo
*/
RCT_EXTERN NSString *const RCTObjCStackTraceKey;
/**
* Name of fatal exceptions generated by RCTFatal
*/
RCT_EXTERN NSString *const RCTFatalExceptionName;
/**
* Stringified JSON object containing extra data to attach to the error from JavaScript.
*/
RCT_EXTERN NSString *const RCTJSExtraDataKey;
/**
* A block signature to be used for custom assertion handling.
*/
typedef void (^RCTAssertFunction)(
NSString *condition,
NSString *fileName,
NSNumber *lineNumber,
NSString *function,
NSString *message);
typedef void (^RCTFatalHandler)(NSError *error);
typedef void (^RCTFatalExceptionHandler)(NSException *exception);
/**
* Convenience macro for asserting that a parameter is non-nil/non-zero.
*/
#define RCTAssertParam(name) RCTAssert(name, @"'%s' is a required parameter", #name)
/**
* Convenience macro for asserting that we're running on main queue.
*/
#define RCTAssertMainQueue() RCTAssert(RCTIsMainQueue(), @"This function must be called on the main queue")
/**
* Convenience macro for asserting that we're running off the main queue.
*/
#define RCTAssertNotMainQueue() RCTAssert(!RCTIsMainQueue(), @"This function must not be called on the main queue")
/**
* These methods get and set the current assert function called by the RCTAssert
* macros. You can use these to replace the standard behavior with custom assert
* functionality.
*/
RCT_EXTERN void RCTSetAssertFunction(RCTAssertFunction assertFunction);
RCT_EXTERN RCTAssertFunction RCTGetAssertFunction(void);
/**
* This appends additional code to the existing assert function, without
* replacing the existing functionality. Useful if you just want to forward
* assert info to an extra service without changing the default behavior.
*/
RCT_EXTERN void RCTAddAssertFunction(RCTAssertFunction assertFunction);
/**
* This method temporarily overrides the assert function while performing the
* specified block. This is useful for testing purposes (to detect if a given
* function asserts something) or to suppress or override assertions temporarily.
*/
RCT_EXTERN void RCTPerformBlockWithAssertFunction(void (^block)(void), RCTAssertFunction assertFunction);
/**
* These methods get and set the current fatal handler called by the `RCTFatal`
* and `RCTFatalException` methods.
*/
RCT_EXTERN void RCTSetFatalHandler(RCTFatalHandler fatalHandler);
RCT_EXTERN RCTFatalHandler RCTGetFatalHandler(void);
RCT_EXTERN void RCTSetFatalExceptionHandler(RCTFatalExceptionHandler fatalExceptionHandler);
RCT_EXTERN RCTFatalExceptionHandler RCTGetFatalExceptionHandler(void);
/**
* Get the current thread's name (or the current queue, if in debug mode)
*/
RCT_EXTERN NSString *RCTCurrentThreadName(void);
/**
* Helper to get generate exception message from NSError
*/
RCT_EXTERN NSString *
RCTFormatError(NSString *message, NSArray<NSDictionary<NSString *, id> *> *stacktrace, NSUInteger maxMessageLength);
/**
* Formats a JS stack trace for logging.
*/
RCT_EXTERN NSString *RCTFormatStackTrace(NSArray<NSDictionary<NSString *, id> *> *stackTrace);
/**
* Convenience macro to assert which thread is currently running (DEBUG mode only)
*/
#if DEBUG
#define RCTAssertThread(thread, ...) \
_Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") RCTAssert( \
[(id)thread isKindOfClass:[NSString class]] ? [RCTCurrentThreadName() isEqualToString:(NSString *)thread] \
: [(id)thread isKindOfClass:[NSThread class]] ? [NSThread currentThread] == (NSThread *)thread \
: dispatch_get_current_queue() == (dispatch_queue_t)thread, \
__VA_ARGS__); \
_Pragma("clang diagnostic pop")
#else
#define RCTAssertThread(thread, ...) \
do { \
} while (0)
#endif
// MARK: - New Architecture Validation
typedef enum {
RCTNotAllowedInBridgeless = 1,
RCTNotAllowedInFabricWithoutLegacy = 2,
RCTNotAllowedValidationDisabled = 3,
} RCTNotAllowedValidation;
/**
* // TODO: (T125626909) Only validate legacy architecture usages in Bridgeless mode, not Bridged Fabric mode
*
* Ensure runtime assumptions holds for the new architecture by reporting when assumptions are violated.
* Note: this is work in progress.
*
* When level is RCTNotAllowedInFabricWithoutLegacy, validate Fabric assumptions.
* i.e. Report legacy pre-Fabric call sites that should not be used while Fabric is enabled on all surfaces.
*
* When level is RCTNotAllowedInBridgeless, validate Fabric or Bridgeless assumptions.
* i.e. Report Bridge call sites that should not be used while Bridgeless mode is enabled.
*
* Note: enabling this at runtime is not early enough to report issues within ObjC class +load execution.
*/
__attribute__((used)) RCT_EXTERN void RCTNewArchitectureSetMinValidationLevel(RCTNotAllowedValidation level);
// When new architecture validation reporting is enabled, trigger an assertion and crash.
__attribute__((used)) RCT_EXTERN void
RCTEnforceNewArchitectureValidation(RCTNotAllowedValidation type, id context, NSString *extra);
// When new architecture validation reporting is enabled, trigger an error but do not crash.
// When ready, switch to stricter variant above.
__attribute__((used)) RCT_EXTERN void
RCTErrorNewArchitectureValidation(RCTNotAllowedValidation type, id context, NSString *extra);
// When new architecture validation reporting is enabled, log an message.
// When ready, switch to stricter variant above.
__attribute__((used)) RCT_EXTERN void
RCTLogNewArchitectureValidation(RCTNotAllowedValidation type, id context, NSString *extra);
// A placeholder for callsites that frequently fail validation.
// When ready, switch to stricter variant above.
__attribute__((used)) RCT_EXTERN void
RCTNewArchitectureValidationPlaceholder(RCTNotAllowedValidation type, id context, NSString *extra);

View File

@@ -0,0 +1,345 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTAssert.h"
#import "RCTLog.h"
NSString *const RCTErrorDomain = @"RCTErrorDomain";
NSString *const RCTJSStackTraceKey = @"RCTJSStackTraceKey";
NSString *const RCTJSRawStackTraceKey = @"RCTJSRawStackTraceKey";
NSString *const RCTObjCStackTraceKey = @"RCTObjCStackTraceKey";
NSString *const RCTFatalExceptionName = @"RCTFatalException";
NSString *const RCTUntruncatedMessageKey = @"RCTUntruncatedMessageKey";
NSString *const RCTJSExtraDataKey = @"RCTJSExtraDataKey";
static NSString *const RCTAssertFunctionStack = @"RCTAssertFunctionStack";
RCTAssertFunction RCTCurrentAssertFunction = nil;
RCTFatalHandler RCTCurrentFatalHandler = nil;
RCTFatalExceptionHandler RCTCurrentFatalExceptionHandler = nil;
NSException *_RCTNotImplementedException(SEL, Class);
NSException *_RCTNotImplementedException(SEL cmd, Class cls)
{
NSString *msg = [NSString stringWithFormat:
@"%s is not implemented "
"for the class %@",
sel_getName(cmd),
cls];
return [NSException exceptionWithName:@"RCTNotDesignatedInitializerException" reason:msg userInfo:nil];
}
void RCTSetAssertFunction(RCTAssertFunction assertFunction)
{
RCTCurrentAssertFunction = assertFunction;
}
RCTAssertFunction RCTGetAssertFunction(void)
{
return RCTCurrentAssertFunction;
}
void RCTAddAssertFunction(RCTAssertFunction assertFunction)
{
RCTAssertFunction existing = RCTCurrentAssertFunction;
if (existing) {
RCTCurrentAssertFunction =
^(NSString *condition, NSString *fileName, NSNumber *lineNumber, NSString *function, NSString *message) {
existing(condition, fileName, lineNumber, function, message);
assertFunction(condition, fileName, lineNumber, function, message);
};
} else {
RCTCurrentAssertFunction = assertFunction;
}
}
/**
* returns the topmost stacked assert function for the current thread, which
* may not be the same as the current value of RCTCurrentAssertFunction.
*/
static RCTAssertFunction RCTGetLocalAssertFunction(void)
{
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
NSArray<RCTAssertFunction> *functionStack = threadDictionary[RCTAssertFunctionStack];
RCTAssertFunction assertFunction = functionStack.lastObject;
if (assertFunction) {
return assertFunction;
}
return RCTCurrentAssertFunction;
}
void RCTPerformBlockWithAssertFunction(void (^block)(void), RCTAssertFunction assertFunction)
{
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
NSMutableArray<RCTAssertFunction> *functionStack = threadDictionary[RCTAssertFunctionStack];
if (!functionStack) {
functionStack = [NSMutableArray new];
threadDictionary[RCTAssertFunctionStack] = functionStack;
}
[functionStack addObject:assertFunction];
block();
[functionStack removeLastObject];
}
NSString *RCTCurrentThreadName(void)
{
NSThread *thread = [NSThread currentThread];
NSString *threadName = RCTIsMainQueue() || thread.isMainThread ? @"main" : thread.name;
if (threadName.length == 0) {
const char *label = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
if (label && strlen(label) > 0) {
threadName = @(label);
} else {
threadName = [NSString stringWithFormat:@"%p", thread];
}
}
return threadName;
}
void _RCTAssertFormat(
const char *condition,
const char *fileName,
int lineNumber,
const char *function,
NSString *format,
...)
{
RCTAssertFunction assertFunction = RCTGetLocalAssertFunction();
if (assertFunction) {
va_list args;
va_start(args, format);
NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
assertFunction(@(condition), @(fileName), @(lineNumber), @(function), message);
}
}
void RCTFatal(NSError *error)
{
_RCTLogNativeInternal(RCTLogLevelFatal, NULL, 0, @"%@", error.localizedDescription);
RCTFatalHandler fatalHandler = RCTGetFatalHandler();
if (fatalHandler) {
fatalHandler(error);
} else {
#if DEBUG
@try {
#endif
NSString *name = [NSString stringWithFormat:@"%@: %@", RCTFatalExceptionName, error.localizedDescription];
// Truncate the localized description to 175 characters to avoid wild screen overflows
NSString *message = RCTFormatError(error.localizedDescription, error.userInfo[RCTJSStackTraceKey], 175);
// Attach an untruncated copy of the description to the userInfo, in case it is needed
NSMutableDictionary *userInfo = [error.userInfo mutableCopy];
[userInfo setObject:RCTFormatError(error.localizedDescription, error.userInfo[RCTJSStackTraceKey], -1)
forKey:RCTUntruncatedMessageKey];
// Expected resulting exception information:
// name: RCTFatalException: <underlying error description>
// reason: <underlying error description plus JS stack trace, truncated to 175 characters>
// userInfo: <underlying error userinfo, plus untruncated description plus JS stack trace>
@throw [[NSException alloc] initWithName:name reason:message userInfo:userInfo];
#if DEBUG
} @catch (NSException *e) {
}
#endif
}
}
void RCTSetFatalHandler(RCTFatalHandler fatalHandler)
{
RCTCurrentFatalHandler = fatalHandler;
}
RCTFatalHandler RCTGetFatalHandler(void)
{
return RCTCurrentFatalHandler;
}
NSString *
RCTFormatError(NSString *message, NSArray<NSDictionary<NSString *, id> *> *stackTrace, NSUInteger maxMessageLength)
{
if (maxMessageLength > 0 && message.length > maxMessageLength) {
message = [[message substringToIndex:maxMessageLength] stringByAppendingString:@"..."];
}
NSString *prettyStack = RCTFormatStackTrace(stackTrace);
return [NSString
stringWithFormat:@"%@%@%@", message, prettyStack ? @", stack:\n" : @"", prettyStack ? prettyStack : @""];
}
NSString *RCTFormatStackTrace(NSArray<NSDictionary<NSString *, id> *> *stackTrace)
{
if (stackTrace) {
NSMutableString *prettyStack = [NSMutableString string];
NSRegularExpression *regex =
[NSRegularExpression regularExpressionWithPattern:@"\\b((?:seg-\\d+(?:_\\d+)?|\\d+)\\.js)"
options:NSRegularExpressionCaseInsensitive
error:NULL];
for (NSDictionary<NSString *, id> *frame in stackTrace) {
NSString *fileName = [frame[@"file"] lastPathComponent];
NSTextCheckingResult *match =
fileName != nil ? [regex firstMatchInString:fileName options:0 range:NSMakeRange(0, fileName.length)] : nil;
if (match) {
fileName = [NSString stringWithFormat:@"%@:", [fileName substringWithRange:match.range]];
} else {
fileName = @"";
}
[prettyStack
appendFormat:@"%@@%@%@:%@\n", frame[@"methodName"], fileName, frame[@"lineNumber"], frame[@"column"]];
}
return prettyStack;
}
return nil;
}
void RCTFatalException(NSException *exception)
{
_RCTLogNativeInternal(RCTLogLevelFatal, NULL, 0, @"%@: %@", exception.name, exception.reason);
RCTFatalExceptionHandler fatalExceptionHandler = RCTGetFatalExceptionHandler();
if (fatalExceptionHandler) {
fatalExceptionHandler(exception);
} else {
#if DEBUG
@try {
#endif
@throw exception;
#if DEBUG
} @catch (NSException *e) {
}
#endif
}
}
void RCTSetFatalExceptionHandler(RCTFatalExceptionHandler fatalExceptionHandler)
{
RCTCurrentFatalExceptionHandler = fatalExceptionHandler;
}
RCTFatalExceptionHandler RCTGetFatalExceptionHandler(void)
{
return RCTCurrentFatalExceptionHandler;
}
// MARK: - New Architecture Validation - Enable Reporting
#if RCT_ONLY_NEW_ARCHITECTURE_EXPERIMENTAL_DO_NOT_USE
static RCTNotAllowedValidation minValidationLevel = RCTNotAllowedInBridgeless;
#else
static RCTNotAllowedValidation minValidationLevel = RCTNotAllowedValidationDisabled;
#endif
__attribute__((used)) RCT_EXTERN void RCTNewArchitectureSetMinValidationLevel(RCTNotAllowedValidation level)
{
#if RCT_ONLY_NEW_ARCHITECTURE_EXPERIMENTAL_DO_NOT_USE
// Cannot disable the reporting in this mode.
#else
minValidationLevel = level;
#endif
}
// MARK: - New Architecture Validation - Private
static BOOL shouldEnforceValidation(RCTNotAllowedValidation type)
{
return type >= minValidationLevel;
}
static NSString *stringDescribingContext(id context)
{
if ([context isKindOfClass:NSString.class]) {
return context;
} else if (context) {
Class klass = [context class];
if (klass) {
return NSStringFromClass(klass);
}
}
return @"uncategorized";
}
static NSString *validationMessage(RCTNotAllowedValidation type, id context, NSString *extra)
{
NSString *notAllowedType;
switch (type) {
case RCTNotAllowedValidationDisabled:
RCTAssert(0, @"RCTNotAllowedValidationDisabled not a validation type.");
return nil;
case RCTNotAllowedInFabricWithoutLegacy:
notAllowedType = @"Fabric";
break;
case RCTNotAllowedInBridgeless:
notAllowedType = @"Bridgeless";
break;
}
return
[NSString stringWithFormat:@"[ReactNative Architecture][NotAllowedIn%@] Unexpectedly reached code path in %@. %@",
notAllowedType,
stringDescribingContext(context),
extra ?: @""];
}
static void
newArchitectureValidationInternal(RCTLogLevel level, RCTNotAllowedValidation type, id context, NSString *extra)
{
if (!shouldEnforceValidation(type)) {
return;
}
NSString *msg = validationMessage(type, context, extra);
if (msg) {
switch (level) {
case RCTLogLevelInfo:
RCTLogInfo(@"%@", msg);
break;
case RCTLogLevelError:
RCTLogError(@"%@", msg);
break;
case RCTLogLevelFatal:
RCTAssert(0, @"%@", msg);
break;
default:
RCTAssert(0, @"New architecture validation is only for info, error, and fatal levels.");
}
}
}
// MARK: - New Architecture Validation - Public
void RCTEnforceNewArchitectureValidation(RCTNotAllowedValidation type, id context, NSString *extra)
{
newArchitectureValidationInternal(RCTLogLevelFatal, type, context, extra);
}
void RCTErrorNewArchitectureValidation(RCTNotAllowedValidation type, id context, NSString *extra)
{
#if RCT_ONLY_NEW_ARCHITECTURE_EXPERIMENTAL_DO_NOT_USE
newArchitectureValidationInternal(RCTLogLevelFatal, type, context, extra);
#else
newArchitectureValidationInternal(RCTLogLevelError, type, context, extra);
#endif
}
void RCTLogNewArchitectureValidation(RCTNotAllowedValidation type, id context, NSString *extra)
{
newArchitectureValidationInternal(RCTLogLevelInfo, type, context, extra);
}
void RCTNewArchitectureValidationPlaceholder(RCTNotAllowedValidation type, id context, NSString *extra)
{
#if RCT_ONLY_NEW_ARCHITECTURE_EXPERIMENTAL_DO_NOT_USE
newArchitectureValidationInternal(RCTLogLevelInfo, type, context, extra);
#endif
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTBridge.h>
#ifdef __cplusplus
#import <jsinspector-modern/ReactCdp.h>
#endif
@interface RCTBridge (Inspector)
/**
* The HostTarget for this bridge, if one has been created. Exposed for RCTCxxBridge only.
*/
@property (nonatomic, assign, readonly)
#ifdef __cplusplus
facebook::react::jsinspector_modern::PageTarget *
#else
// The inspector infrastructure cannot be used in C or Swift code.
void *
#endif
inspectorTarget;
@property (nonatomic, readonly, getter=isInspectable) BOOL inspectable;
@end

View File

@@ -0,0 +1,151 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTBridge.h>
@class RCTModuleRegistry;
@class RCTModuleData;
@protocol RCTJavaScriptExecutor;
RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
RCT_EXTERN void RCTRegisterModule(Class);
@interface RCTBridge ()
// Private designated initializer
- (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate
bundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleListProvider)block
launchOptions:(NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER;
// Used for the profiler flow events between JS and native
@property (nonatomic, assign) int64_t flowID;
@property (nonatomic, assign) CFMutableDictionaryRef flowIDMap;
@property (nonatomic, strong) NSLock *flowIDMapLock;
// Used by RCTDevMenu
@property (nonatomic, copy) NSString *bridgeDescription;
+ (instancetype)currentBridge;
+ (void)setCurrentBridge:(RCTBridge *)bridge;
/**
* Bridge setup code - creates an instance of RCTBachedBridge. Exposed for
* test only
*/
- (void)setUp;
/**
* This method is used to invoke a callback that was registered in the
* JavaScript application context. Safe to call from any thread.
*/
- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args;
/**
* This property is mostly used on the main thread, but may be touched from
* a background thread if the RCTBridge happens to deallocate on a background
* thread. Therefore, we want all writes to it to be seen atomically.
*/
@property (atomic, strong) RCTBridge *batchedBridge;
/**
* The block that creates the modules' instances to be added to the bridge.
* Exposed for RCTCxxBridge
*/
@property (nonatomic, copy, readonly) RCTBridgeModuleListProvider moduleProvider;
/**
* Used by RCTDevMenu to override the `hot` param of the current bundleURL.
*/
@property (nonatomic, strong, readwrite) NSURL *bundleURL;
/**
* An object that allows one to require NativeModules/TurboModules.
* RCTModuleRegistry is implemented in bridgeless mode and bridge mode.
* Used by RCTRootView.
*/
@property (nonatomic, strong, readonly) RCTModuleRegistry *moduleRegistry;
@end
@interface RCTBridge (RCTCxxBridge)
/**
* Used by RCTModuleData
*/
@property (nonatomic, weak, readonly) RCTBridge *parentBridge;
/**
* Used by RCTModuleData
*/
@property (nonatomic, assign, readonly) BOOL moduleSetupComplete;
/**
* Called on the child bridge to run the executor and start loading.
*/
- (void)start;
/**
* Used by RCTModuleData to register the module for frame updates after it is
* lazily initialized.
*/
- (void)registerModuleForFrameUpdates:(id<RCTBridgeModule>)module withModuleData:(RCTModuleData *)moduleData;
/**
* Dispatch work to a module's queue - this is also supports the fake RCTJSThread
* queue. Exposed for the RCTProfiler
*/
- (void)dispatchBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue;
/**
* Get the module data for a given module name. Used by UIManager to implement
* the `dispatchViewManagerCommand` method.
*/
- (RCTModuleData *)moduleDataForName:(NSString *)moduleName;
/**
* Registers additional classes with the ModuleRegistry.
*/
- (void)registerAdditionalModuleClasses:(NSArray<Class> *)newModules;
/**
* Updates the ModuleRegistry with a pre-initialized instance.
*/
- (void)updateModuleWithInstance:(id<RCTBridgeModule>)instance;
/**
* Systrace profiler toggling methods exposed for the RCTDevMenu
*/
- (void)startProfiling;
- (void)stopProfiling:(void (^)(NSData *))callback;
/**
* Synchronously call a specific native module's method and return the result
*/
- (id)callNativeModule:(NSUInteger)moduleID method:(NSUInteger)methodID params:(NSArray *)params;
/**
* Hook exposed for RCTLog to send logs to JavaScript when not running in JSC
*/
- (void)logMessage:(NSString *)message level:(NSString *)level;
/**
* Allow super fast, one time, timers to skip the queue and be directly executed
*/
- (void)_immediatelyCallTimer:(NSNumber *)timer;
@end
@interface RCTCxxBridge : RCTBridge
// TODO(cjhopman): this seems unsafe unless we require that it is only called on the main js queue.
@property (nonatomic, readonly) void *runtime;
- (instancetype)initWithParentBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
@end

View File

@@ -0,0 +1,249 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <React/RCTBridgeDelegate.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTBridgeModuleDecorator.h>
#import <React/RCTDefines.h>
#import <React/RCTFrameUpdate.h>
#import <React/RCTInvalidating.h>
#import "RCTBridgeConstants.h"
#import "RCTConstants.h"
@class JSValue;
@class RCTBridge;
@class RCTPerformanceLogger;
/**
* This block can be used to instantiate modules that require additional
* init parameters, or additional configuration prior to being used.
* The bridge will call this block to instantiate the modules, and will
* be responsible for invalidating/releasing them when the bridge is destroyed.
* For this reason, the block should always return new module instances, and
* module instances should not be shared between bridges.
*/
typedef NSArray<id<RCTBridgeModule>> * (^RCTBridgeModuleListProvider)(void);
RCT_EXTERN_C_BEGIN
/**
* This function returns the module name for a given class.
*/
NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
/**
* Experimental.
* Check/set if JSI-bound NativeModule is enabled. By default it's off.
*/
BOOL RCTTurboModuleEnabled(void);
void RCTEnableTurboModule(BOOL enabled);
// Turn on TurboModule interop
BOOL RCTTurboModuleInteropEnabled(void);
void RCTEnableTurboModuleInterop(BOOL enabled);
// Turn on TurboModule interop's Bridge proxy
BOOL RCTTurboModuleInteropBridgeProxyEnabled(void);
void RCTEnableTurboModuleInteropBridgeProxy(BOOL enabled);
// Turn on the fabric interop layer
BOOL RCTFabricInteropLayerEnabled(void);
void RCTEnableFabricInteropLayer(BOOL enabled);
// Turn on TurboModule sync execution of void methods
BOOL RCTTurboModuleSyncVoidMethodsEnabled(void);
void RCTEnableTurboModuleSyncVoidMethods(BOOL enabled);
// Use a shared queue for executing module methods
BOOL RCTTurboModuleSharedQueueEnabled(void);
void RCTEnableTurboModuleSharedQueue(BOOL enabled);
BOOL RCTUIManagerDispatchAccessibilityManagerInitOntoMain(void);
void RCTUIManagerSetDispatchAccessibilityManagerInitOntoMain(BOOL enabled);
typedef enum {
kRCTBridgeProxyLoggingLevelNone,
kRCTBridgeProxyLoggingLevelWarning,
kRCTBridgeProxyLoggingLevelError,
} RCTBridgeProxyLoggingLevel;
RCTBridgeProxyLoggingLevel RCTTurboModuleInteropBridgeProxyLogLevel(void);
void RCTSetTurboModuleInteropBridgeProxyLogLevel(RCTBridgeProxyLoggingLevel logLevel);
// Route all TurboModules through TurboModule interop
BOOL RCTTurboModuleInteropForAllTurboModulesEnabled(void);
void RCTEnableTurboModuleInteropForAllTurboModules(BOOL enabled);
typedef enum {
kRCTGlobalScope,
kRCTGlobalScopeUsingRetainJSCallback,
kRCTTurboModuleManagerScope,
} RCTTurboModuleCleanupMode;
RCTTurboModuleCleanupMode RCTGetTurboModuleCleanupMode(void);
void RCTSetTurboModuleCleanupMode(RCTTurboModuleCleanupMode mode);
RCT_EXTERN_C_END
/**
* Async batched bridge used to communicate with the JavaScript application.
*/
@interface RCTBridge : NSObject <RCTInvalidating>
/**
* Creates a new bridge with a custom RCTBridgeDelegate.
*
* All the interaction with the JavaScript context should be done using the bridge
* instance of the RCTBridgeModules. Modules will be automatically instantiated
* using the default constructor, but you can optionally pass in an array of
* pre-initialized module instances if they require additional init parameters
* or configuration.
*/
- (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate launchOptions:(NSDictionary *)launchOptions;
/**
* DEPRECATED: Use initWithDelegate:launchOptions: instead
*
* The designated initializer. This creates a new bridge on top of the specified
* executor. The bridge should then be used for all subsequent communication
* with the JavaScript code running in the executor. Modules will be automatically
* instantiated using the default constructor, but you can optionally pass in an
* array of pre-initialized module instances if they require additional init
* parameters or configuration.
*/
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleListProvider)block
launchOptions:(NSDictionary *)launchOptions;
/**
* This method is used to call functions in the JavaScript application context.
* It is primarily intended for use by modules that require two-way communication
* with the JavaScript code. Safe to call from any thread.
*/
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args;
- (void)enqueueJSCall:(NSString *)module
method:(NSString *)method
args:(NSArray *)args
completion:(dispatch_block_t)completion;
/**
* This method registers the file path of an additional JS segment by its ID.
*
* @experimental
*/
- (void)registerSegmentWithId:(NSUInteger)segmentId path:(NSString *)path;
/**
* Retrieve a bridge module instance by name or class. Note that modules are
* lazily instantiated, so calling these methods for the first time with a given
* module name/class may cause the class to be synchronously instantiated,
* potentially blocking both the calling thread and main thread for a short time.
*
* Note: This method does NOT lazily load the particular module if it's not yet loaded.
*/
- (id)moduleForName:(NSString *)moduleName;
- (id)moduleForName:(NSString *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyLoad;
// Note: This method lazily load the module as necessary.
- (id)moduleForClass:(Class)moduleClass;
/**
* When a NativeModule performs a lookup for a TurboModule, we need to query
* the TurboModuleRegistry.
*/
- (void)setRCTTurboModuleRegistry:(id<RCTTurboModuleRegistry>)turboModuleRegistry;
/**
* This hook is called by the TurboModule infra with every ObjC module that's created.
* It allows the bridge to attach properties to ObjC modules that give those modules
* access to Bridge APIs.
*/
- (RCTBridgeModuleDecorator *)bridgeModuleDecorator;
/**
* Convenience method for retrieving all modules conforming to a given protocol.
* Modules will be synchronously instantiated if they haven't already been,
* potentially blocking both the calling thread and main thread for a short time.
*/
- (NSArray *)modulesConformingToProtocol:(Protocol *)protocol;
/**
* Test if a module has been initialized. Use this prior to calling
* `moduleForClass:` or `moduleForName:` if you do not want to cause the module
* to be instantiated if it hasn't been already.
*/
- (BOOL)moduleIsInitialized:(Class)moduleClass;
/**
* All registered bridge module classes.
*/
@property (nonatomic, copy, readonly) NSArray<Class> *moduleClasses;
/**
* URL of the script that was loaded into the bridge.
*/
@property (nonatomic, strong, readonly) NSURL *bundleURL;
/**
* The class of the executor currently being used. Changes to this value will
* take effect after the bridge is reloaded.
*/
@property (nonatomic, strong) Class executorClass;
/**
* The delegate provided during the bridge initialization
*/
@property (nonatomic, weak, readonly) id<RCTBridgeDelegate> delegate;
/**
* The launch options that were used to initialize the bridge.
*/
@property (nonatomic, copy, readonly) NSDictionary *launchOptions;
/**
* Use this to check if the bridge is currently loading.
*/
@property (nonatomic, readonly, getter=isLoading) BOOL loading;
/**
* Use this to check if the bridge has been invalidated.
*/
@property (nonatomic, readonly, getter=isValid) BOOL valid;
/**
* Link to the Performance Logger that logs React Native perf events.
*/
@property (nonatomic, readonly, strong) RCTPerformanceLogger *performanceLogger;
/**
* Reload the bundle and reset executor & modules. Safe to call from any thread.
*/
- (void)reload __deprecated_msg("Use RCTReloadCommand instead");
/**
* Reload the bundle and reset executor & modules. Safe to call from any thread.
*/
- (void)reloadWithReason:(NSString *)reason __deprecated_msg("Use RCTReloadCommand instead");
/**
* Handle notifications for a fast refresh. Safe to call from any thread.
*/
- (void)onFastRefresh;
/**
* Inform the bridge, and anything subscribing to it, that it should reload.
*/
- (void)requestReload __deprecated_msg("Use RCTReloadCommand instead");
/**
* Says whether bridge has started receiving calls from JavaScript.
*/
- (BOOL)isBatchActive;
@end

View File

@@ -0,0 +1,534 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTBridge.h"
#import "RCTBridge+Inspector.h"
#import "RCTBridge+Private.h"
#import <objc/runtime.h>
#import "RCTConvert.h"
#if RCT_ENABLE_INSPECTOR
#import "RCTInspectorDevServerHelper.h"
#endif
#import <jsinspector-modern/InspectorFlags.h>
#import <jsinspector-modern/InspectorInterfaces.h>
#import <jsinspector-modern/ReactCdp.h>
#import <optional>
#import "RCTDevLoadingViewProtocol.h"
#import "RCTJSThread.h"
#import "RCTLog.h"
#import "RCTModuleData.h"
#import "RCTPerformanceLogger.h"
#import "RCTProfile.h"
#import "RCTReloadCommand.h"
#import "RCTUtils.h"
static NSMutableArray<Class> *RCTModuleClasses;
static dispatch_queue_t RCTModuleClassesSyncQueue;
NSArray<Class> *RCTGetModuleClasses(void)
{
__block NSArray<Class> *result;
dispatch_sync(RCTModuleClassesSyncQueue, ^{
result = [RCTModuleClasses copy];
});
return result;
}
/**
* Register the given class as a bridge module. All modules must be registered
* prior to the first bridge initialization.
* TODO: (T115656171) Refactor RCTRegisterModule out of Bridge.m since it doesn't use the Bridge.
*/
void RCTRegisterModule(Class);
void RCTRegisterModule(Class moduleClass)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTModuleClasses = [NSMutableArray new];
RCTModuleClassesSyncQueue =
dispatch_queue_create("com.facebook.react.ModuleClassesSyncQueue", DISPATCH_QUEUE_CONCURRENT);
});
RCTAssert(
[moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
@"%@ does not conform to the RCTBridgeModule protocol",
moduleClass);
// Register module
dispatch_barrier_async(RCTModuleClassesSyncQueue, ^{
[RCTModuleClasses addObject:moduleClass];
});
}
/**
* This function returns the module name for a given class.
*/
NSString *RCTBridgeModuleNameForClass(Class cls)
{
#if RCT_DEBUG
RCTAssert(
[cls conformsToProtocol:@protocol(RCTBridgeModule)],
@"Bridge module `%@` does not conform to RCTBridgeModule",
cls);
#endif
NSString *name = [cls moduleName];
if (name.length == 0) {
name = NSStringFromClass(cls);
}
return RCTDropReactPrefixes(name);
}
static BOOL turboModuleEnabled = NO;
BOOL RCTTurboModuleEnabled(void)
{
#if RCT_DEBUG
// TODO(T53341772): Allow TurboModule for test environment. Right now this breaks RNTester tests if enabled.
if (RCTRunningInTestEnvironment()) {
return NO;
}
#endif
return turboModuleEnabled;
}
void RCTEnableTurboModule(BOOL enabled)
{
turboModuleEnabled = enabled;
}
static BOOL turboModuleInteropEnabled = NO;
BOOL RCTTurboModuleInteropEnabled(void)
{
return turboModuleInteropEnabled;
}
void RCTEnableTurboModuleInterop(BOOL enabled)
{
turboModuleInteropEnabled = enabled;
}
static BOOL turboModuleInteropBridgeProxyEnabled = NO;
BOOL RCTTurboModuleInteropBridgeProxyEnabled(void)
{
return turboModuleInteropBridgeProxyEnabled;
}
void RCTEnableTurboModuleInteropBridgeProxy(BOOL enabled)
{
turboModuleInteropBridgeProxyEnabled = enabled;
}
static BOOL fabricInteropLayerEnabled = YES;
BOOL RCTFabricInteropLayerEnabled()
{
return fabricInteropLayerEnabled;
}
void RCTEnableFabricInteropLayer(BOOL enabled)
{
fabricInteropLayerEnabled = enabled;
}
static RCTBridgeProxyLoggingLevel bridgeProxyLoggingLevel = kRCTBridgeProxyLoggingLevelNone;
RCTBridgeProxyLoggingLevel RCTTurboModuleInteropBridgeProxyLogLevel(void)
{
return bridgeProxyLoggingLevel;
}
void RCTSetTurboModuleInteropBridgeProxyLogLevel(RCTBridgeProxyLoggingLevel logLevel)
{
bridgeProxyLoggingLevel = logLevel;
}
static BOOL useTurboModuleInteropForAllTurboModules = NO;
BOOL RCTTurboModuleInteropForAllTurboModulesEnabled(void)
{
return useTurboModuleInteropForAllTurboModules;
}
void RCTEnableTurboModuleInteropForAllTurboModules(BOOL enabled)
{
useTurboModuleInteropForAllTurboModules = enabled;
}
// Turn on TurboModule sync execution of void methods
static BOOL gTurboModuleEnableSyncVoidMethods = NO;
BOOL RCTTurboModuleSyncVoidMethodsEnabled(void)
{
return gTurboModuleEnableSyncVoidMethods;
}
void RCTEnableTurboModuleSyncVoidMethods(BOOL enabled)
{
gTurboModuleEnableSyncVoidMethods = enabled;
}
// Use a shared queue for executing module methods
static BOOL gTurboModuleEnableSharedQueue = NO;
BOOL RCTTurboModuleSharedQueueEnabled(void)
{
return gTurboModuleEnableSharedQueue;
}
void RCTEnableTurboModuleSharedQueue(BOOL enabled)
{
gTurboModuleEnableSharedQueue = enabled;
}
BOOL kDispatchAccessibilityManagerInitOntoMain = NO;
BOOL RCTUIManagerDispatchAccessibilityManagerInitOntoMain(void)
{
return kDispatchAccessibilityManagerInitOntoMain;
}
void RCTUIManagerSetDispatchAccessibilityManagerInitOntoMain(BOOL enabled)
{
kDispatchAccessibilityManagerInitOntoMain = enabled;
}
class RCTBridgePageTargetDelegate : public facebook::react::jsinspector_modern::PageTargetDelegate {
public:
RCTBridgePageTargetDelegate(RCTBridge *bridge) : bridge_(bridge) {}
void onReload(const PageReloadRequest &request) override
{
RCTAssertMainQueue();
[bridge_ reload];
}
private:
__weak RCTBridge *bridge_;
};
@interface RCTBridge () <RCTReloadListener>
@end
@implementation RCTBridge {
NSURL *_delegateBundleURL;
std::unique_ptr<RCTBridgePageTargetDelegate> _inspectorPageDelegate;
std::shared_ptr<facebook::react::jsinspector_modern::PageTarget> _inspectorTarget;
std::optional<int> _inspectorPageId;
}
+ (void)initialize
{
_RCTInitializeJSThreadConstantInternal();
}
static RCTBridge *RCTCurrentBridgeInstance = nil;
/**
* The last current active bridge instance. This is set automatically whenever
* the bridge is accessed. It can be useful for static functions or singletons
* that need to access the bridge for purposes such as logging, but should not
* be relied upon to return any particular instance, due to race conditions.
*/
+ (instancetype)currentBridge
{
return RCTCurrentBridgeInstance;
}
+ (void)setCurrentBridge:(RCTBridge *)currentBridge
{
RCTCurrentBridgeInstance = currentBridge;
}
- (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate launchOptions:(NSDictionary *)launchOptions
{
return [self initWithDelegate:delegate bundleURL:nil moduleProvider:nil launchOptions:launchOptions];
}
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleListProvider)block
launchOptions:(NSDictionary *)launchOptions
{
return [self initWithDelegate:nil bundleURL:bundleURL moduleProvider:block launchOptions:launchOptions];
}
- (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate
bundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleListProvider)block
launchOptions:(NSDictionary *)launchOptions
{
if (self = [super init]) {
RCTEnforceNewArchitectureValidation(RCTNotAllowedInBridgeless, self, nil);
_delegate = delegate;
_bundleURL = bundleURL;
_moduleProvider = block;
_launchOptions = [launchOptions copy];
_inspectorPageDelegate = std::make_unique<RCTBridgePageTargetDelegate>(self);
[self setUp];
}
return self;
}
RCT_NOT_IMPLEMENTED(-(instancetype)init)
- (void)dealloc
{
/**
* This runs only on the main thread, but crashes the subclass
* RCTAssertMainQueue();
*/
// NOTE: RCTCxxBridge will use _inspectorTarget during [self invalidate], so we must
// keep it alive until after the call returns.
[self invalidate];
if (_inspectorPageId.has_value()) {
facebook::react::jsinspector_modern::getInspectorInstance().removePage(*_inspectorPageId);
_inspectorPageId.reset();
_inspectorTarget.reset();
}
}
- (void)setRCTTurboModuleRegistry:(id<RCTTurboModuleRegistry>)turboModuleRegistry
{
[self.batchedBridge setRCTTurboModuleRegistry:turboModuleRegistry];
}
- (RCTBridgeModuleDecorator *)bridgeModuleDecorator
{
return [self.batchedBridge bridgeModuleDecorator];
}
- (void)didReceiveReloadCommand
{
#if RCT_ENABLE_INSPECTOR
auto &inspectorFlags = facebook::react::jsinspector_modern::InspectorFlags::getInstance();
if (!inspectorFlags.getEnableModernCDPRegistry()) {
// Disable debugger to resume the JsVM & avoid thread locks while reloading
[RCTInspectorDevServerHelper disableDebugger];
}
#endif
[[NSNotificationCenter defaultCenter] postNotificationName:RCTBridgeWillReloadNotification object:self userInfo:nil];
/**
* Any thread
*/
dispatch_async(dispatch_get_main_queue(), ^{
// WARNING: Invalidation is async, so it may not finish before re-setting up the bridge,
// causing some issues. TODO: revisit this post-Fabric/TurboModule.
[self invalidate];
// Reload is a special case, do not preserve launchOptions and treat reload as a fresh start
self->_launchOptions = nil;
[self setUp];
});
}
- (RCTModuleRegistry *)moduleRegistry
{
return self.batchedBridge.moduleRegistry;
}
- (NSArray<Class> *)moduleClasses
{
return self.batchedBridge.moduleClasses;
}
- (id)moduleForName:(NSString *)moduleName
{
return [self.batchedBridge moduleForName:moduleName];
}
- (id)moduleForName:(NSString *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyLoad
{
return [self.batchedBridge moduleForName:moduleName lazilyLoadIfNecessary:lazilyLoad];
}
- (id)moduleForClass:(Class)moduleClass
{
id module = [self.batchedBridge moduleForClass:moduleClass];
if (!module) {
module = [self moduleForName:RCTBridgeModuleNameForClass(moduleClass)];
}
return module;
}
- (NSArray *)modulesConformingToProtocol:(Protocol *)protocol
{
NSMutableArray *modules = [NSMutableArray new];
for (Class moduleClass in [self.moduleClasses copy]) {
if ([moduleClass conformsToProtocol:protocol]) {
id module = [self moduleForClass:moduleClass];
if (module) {
[modules addObject:module];
}
}
}
return [modules copy];
}
- (BOOL)moduleIsInitialized:(Class)moduleClass
{
return [self.batchedBridge moduleIsInitialized:moduleClass];
}
/**
* DEPRECATED - please use RCTReloadCommand.
*/
- (void)reload
{
RCTTriggerReloadCommandListeners(@"Unknown from bridge");
}
/**
* DEPRECATED - please use RCTReloadCommand.
*/
- (void)reloadWithReason:(NSString *)reason
{
RCTTriggerReloadCommandListeners(reason);
}
- (void)onFastRefresh
{
[[NSNotificationCenter defaultCenter] postNotificationName:RCTBridgeFastRefreshNotification object:self];
}
/**
* DEPRECATED - please use RCTReloadCommand.
*/
- (void)requestReload
{
[self reloadWithReason:@"Requested from bridge"];
}
- (Class)bridgeClass
{
return [RCTCxxBridge class];
}
- (void)setUp
{
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBridge setUp]", nil);
_performanceLogger = [RCTPerformanceLogger new];
[_performanceLogger markStartForTag:RCTPLInitReactRuntime];
[_performanceLogger markStartForTag:RCTPLBridgeStartup];
[_performanceLogger markStartForTag:RCTPLTTI];
auto &inspectorFlags = facebook::react::jsinspector_modern::InspectorFlags::getInstance();
if (inspectorFlags.getEnableModernCDPRegistry() && !_inspectorPageId.has_value()) {
_inspectorTarget =
facebook::react::jsinspector_modern::PageTarget::create(*_inspectorPageDelegate, [](auto callback) {
RCTExecuteOnMainQueue(^{
callback();
});
});
__weak RCTBridge *weakSelf = self;
_inspectorPageId = facebook::react::jsinspector_modern::getInspectorInstance().addPage(
"React Native Bridge (Experimental)",
/* vm */ "",
[weakSelf](std::unique_ptr<facebook::react::jsinspector_modern::IRemoteConnection> remote)
-> std::unique_ptr<facebook::react::jsinspector_modern::ILocalConnection> {
RCTBridge *strongSelf = weakSelf;
if (!strongSelf) {
// This can happen if we're about to be dealloc'd. Reject the connection.
return nullptr;
}
return strongSelf->_inspectorTarget->connect(
std::move(remote),
{
.integrationName = "iOS Bridge (RCTBridge)",
});
},
{.nativePageReloads = true});
}
Class bridgeClass = self.bridgeClass;
// Only update bundleURL from delegate if delegate bundleURL has changed
NSURL *previousDelegateURL = _delegateBundleURL;
_delegateBundleURL = [self.delegate sourceURLForBridge:self];
if (_delegateBundleURL && ![_delegateBundleURL isEqual:previousDelegateURL]) {
_bundleURL = _delegateBundleURL;
}
// Sanitize the bundle URL
_bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString];
RCTExecuteOnMainQueue(^{
RCTRegisterReloadCommandListener(self);
RCTReloadCommandSetBundleURL(self->_bundleURL);
});
self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
[self.batchedBridge start];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
- (BOOL)isLoading
{
return self.batchedBridge.loading;
}
- (BOOL)isValid
{
return self.batchedBridge.valid;
}
- (BOOL)isBatchActive
{
return [_batchedBridge isBatchActive];
}
- (void)invalidate
{
[[NSNotificationCenter defaultCenter] postNotificationName:RCTBridgeWillBeInvalidatedNotification object:self];
RCTBridge *batchedBridge = self.batchedBridge;
self.batchedBridge = nil;
if (batchedBridge) {
RCTExecuteOnMainQueue(^{
[batchedBridge invalidate];
});
}
}
- (void)updateModuleWithInstance:(id<RCTBridgeModule>)instance
{
[self.batchedBridge updateModuleWithInstance:instance];
}
- (void)registerAdditionalModuleClasses:(NSArray<Class> *)modules
{
[self.batchedBridge registerAdditionalModuleClasses:modules];
}
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
{
NSArray<NSString *> *ids = [moduleDotMethod componentsSeparatedByString:@"."];
NSString *module = ids[0];
NSString *method = ids[1];
[self enqueueJSCall:module method:method args:args completion:NULL];
}
- (void)enqueueJSCall:(NSString *)module
method:(NSString *)method
args:(NSArray *)args
completion:(dispatch_block_t)completion
{
[self.batchedBridge enqueueJSCall:module method:method args:args completion:completion];
}
- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args
{
[self.batchedBridge enqueueCallback:cbID args:args];
}
- (void)registerSegmentWithId:(NSUInteger)segmentId path:(NSString *)path
{
[self.batchedBridge registerSegmentWithId:segmentId path:path];
}
- (facebook::react::jsinspector_modern::PageTarget *)inspectorTarget
{
return _inspectorTarget.get();
}
@end

View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTDefines.h>
/**
* RCTBridgeConstants are constants that are only used in the legacy architecture.
* Please place constants used in the new architecture into RCTConstants.
*/
/**
* DEPRECATED - Use RCTReloadCommand instead. This notification fires just before the bridge starts
* processing a request to reload.
*/
RCT_EXTERN NSString *const RCTBridgeWillReloadNotification;
/**
* This notification fires whenever a fast refresh happens.
*/
RCT_EXTERN NSString *const RCTBridgeFastRefreshNotification;
/**
* This notification fires just before the bridge begins downloading a script
* from the packager.
*/
RCT_EXTERN NSString *const RCTBridgeWillDownloadScriptNotification;
/**
* This notification fires just after the bridge finishes downloading a script
* from the packager.
*/
RCT_EXTERN NSString *const RCTBridgeDidDownloadScriptNotification;
/**
* This notification fires right after the bridge is about to invalidate NativeModule
* instances during teardown. Handle this notification to perform additional invalidation.
*/
RCT_EXTERN NSString *const RCTBridgeWillInvalidateModulesNotification;
/**
* This notification fires right after the bridge finishes invalidating NativeModule
* instances during teardown. Handle this notification to perform additional invalidation.
*/
RCT_EXTERN NSString *const RCTBridgeDidInvalidateModulesNotification;
/**
* This notification fires right before the bridge starting invalidation process.
* Handle this notification to perform additional invalidation.
* The notification can be issued on any thread.
*/
RCT_EXTERN NSString *const RCTBridgeWillBeInvalidatedNotification;
/**
* Key for the RCTSource object in the RCTBridgeDidDownloadScriptNotification
* userInfo dictionary.
*/
RCT_EXTERN NSString *const RCTBridgeDidDownloadScriptNotificationSourceKey;
/**
* Key for the reload reason in the RCTBridgeWillReloadNotification userInfo dictionary.
*/
RCT_EXTERN NSString *const RCTBridgeDidDownloadScriptNotificationReasonKey;
/**
* Key for the bridge description (NSString_ in the
* RCTBridgeDidDownloadScriptNotification userInfo dictionary.
*/
RCT_EXTERN NSString *const RCTBridgeDidDownloadScriptNotificationBridgeDescriptionKey;

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTBridgeConstants.h"
NSString *const RCTBridgeWillReloadNotification = @"RCTBridgeWillReloadNotification";
NSString *const RCTBridgeFastRefreshNotification = @"RCTBridgeFastRefreshNotification";
NSString *const RCTBridgeWillDownloadScriptNotification = @"RCTBridgeWillDownloadScriptNotification";
NSString *const RCTBridgeDidDownloadScriptNotification = @"RCTBridgeDidDownloadScriptNotification";
NSString *const RCTBridgeWillInvalidateModulesNotification = @"RCTBridgeWillInvalidateModulesNotification";
NSString *const RCTBridgeDidInvalidateModulesNotification = @"RCTBridgeDidInvalidateModulesNotification";
NSString *const RCTBridgeWillBeInvalidatedNotification = @"RCTBridgeWillBeInvalidatedNotification";
NSString *const RCTBridgeDidDownloadScriptNotificationSourceKey = @"source";
NSString *const RCTBridgeDidDownloadScriptNotificationBridgeDescriptionKey = @"bridgeDescription";

View File

@@ -0,0 +1,83 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTJavaScriptLoader.h>
@class RCTBridge;
@protocol RCTBridgeModule;
NS_ASSUME_NONNULL_BEGIN
@protocol RCTBridgeDelegate <NSObject>
/**
* The location of the JavaScript source file. When running from the packager
* this should be an absolute URL, e.g. `http://localhost:8081/index.ios.bundle`.
* When running from a locally bundled JS file, this should be a `file://` url
* pointing to a path inside the app resources, e.g. `file://.../main.jsbundle`.
*/
- (NSURL *__nullable)sourceURLForBridge:(RCTBridge *)bridge;
@optional
/**
* The bridge initializes any registered RCTBridgeModules automatically, however
* if you wish to instantiate your own module instances, you can return them
* from this method.
*
* Note: You should always return a new instance for each call, rather than
* returning the same instance each time the bridge is reloaded. Module instances
* should not be shared between bridges, and this may cause unexpected behavior.
*
* It is also possible to override standard modules with your own implementations
* by returning a class with the same `moduleName` from this method, but this is
* not recommended in most cases - if the module methods and behavior do not
* match exactly, it may lead to bugs or crashes.
*/
- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge;
/**
* Configure whether the JSCExecutor created should use the system JSC API or
* alternative hooks provided. When returning YES from this method, you must have
* previously called facebook::react::setCustomJSCWrapper.
*
* @experimental
*/
- (BOOL)shouldBridgeUseCustomJSC:(RCTBridge *)bridge;
/**
* The bridge will call this method when a module been called from JS
* cannot be found among registered modules.
* It should return YES if the module with name 'moduleName' was registered
* in the implementation, and the system must attempt to look for it again among registered.
* If the module was not registered, return NO to prevent further searches.
*/
- (BOOL)bridge:(RCTBridge *)bridge didNotFindModule:(NSString *)moduleName;
/**
* The bridge will automatically attempt to load the JS source code from the
* location specified by the `sourceURLForBridge:` method, however, if you want
* to handle loading the JS yourself, you can do so by implementing this method.
*/
- (void)loadSourceForBridge:(RCTBridge *)bridge
onProgress:(RCTSourceLoadProgressBlock)onProgress
onComplete:(RCTSourceLoadBlock)loadCallback;
/**
* Similar to loadSourceForBridge:onProgress:onComplete: but without progress
* reporting.
*/
- (void)loadSourceForBridge:(RCTBridge *)bridge withBlock:(RCTSourceLoadBlock)loadCallback;
/**
* Retrieve the list of lazy-native-modules names for the given bridge.
*/
- (NSDictionary<NSString *, Class> *)extraLazyModuleClassesForBridge:(RCTBridge *)bridge;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@class RCTBridge;
typedef NS_ENUM(NSInteger, RCTFunctionType) {
RCTFunctionTypeNormal,
RCTFunctionTypePromise,
RCTFunctionTypeSync,
};
static inline const char *RCTFunctionDescriptorFromType(RCTFunctionType type)
{
switch (type) {
case RCTFunctionTypeNormal:
return "async";
case RCTFunctionTypePromise:
return "promise";
case RCTFunctionTypeSync:
return "sync";
}
};
@protocol RCTBridgeMethod <NSObject>
@property (nonatomic, readonly) const char *JSMethodName;
@property (nonatomic, readonly) RCTFunctionType functionType;
- (id)invokeWithBridge:(RCTBridge *)bridge module:(id)module arguments:(NSArray *)arguments;
@end

View File

@@ -0,0 +1,409 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <React/RCTDefines.h>
#import <React/RCTJSThread.h>
#import <RCTDeprecation/RCTDeprecation.h>
#import "RCTBundleManager.h"
@class RCTBridge;
@protocol RCTBridgeMethod;
@protocol RCTTurboModule;
@protocol RCTTurboModuleRegistry;
@class RCTModuleRegistry;
@class RCTViewRegistry;
@class RCTCallableJSModules;
/**
* The type of a block that is capable of sending a response to a bridged
* operation. Use this for returning callback methods to JS.
*/
typedef void (^RCTResponseSenderBlock)(NSArray *response);
/**
* The type of a block that is capable of sending an error response to a
* bridged operation. Use this for returning error information to JS.
*/
typedef void (^RCTResponseErrorBlock)(NSError *error);
/**
* Block that bridge modules use to resolve the JS promise waiting for a result.
* Nil results are supported and are converted to JS's undefined value.
*/
typedef void (^RCTPromiseResolveBlock)(id result);
/**
* Block that bridge modules use to reject the JS promise waiting for a result.
* The error may be nil but it is preferable to pass an NSError object for more
* precise error messages.
*/
typedef void (^RCTPromiseRejectBlock)(NSString *code, NSString *message, NSError *error);
RCT_EXTERN_C_BEGIN
typedef struct RCTMethodInfo {
const char *const jsName;
const char *const objcName;
const BOOL isSync;
} RCTMethodInfo;
RCT_EXTERN_C_END
/**
* Provides the interface needed to register a bridge module.
*/
@protocol RCTBridgeModule <NSObject>
/**
* Place this macro in your class implementation to automatically register
* your module with the bridge when it loads. The optional js_name argument
* will be used as the JS module name. If omitted, the JS module name will
* match the Objective-C class name.
*/
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+(NSString *)moduleName \
{ \
return @ #js_name; \
} \
+(void)load \
{ \
RCTRegisterModule(self); \
}
/**
* Same as RCT_EXPORT_MODULE, but uses __attribute__((constructor)) for module
* registration. Useful for registering swift classes that forbids use of load
* Used in RCT_EXTERN_REMAP_MODULE
*/
#define RCT_EXPORT_MODULE_NO_LOAD(js_name, objc_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+(NSString *)moduleName \
{ \
return @ #js_name; \
} \
__attribute__((constructor)) static void RCT_CONCAT(initialize_, objc_name)(void) \
{ \
RCTRegisterModule([objc_name class]); \
}
// Implemented by RCT_EXPORT_MODULE
+ (NSString *)moduleName;
@optional
/**
* A reference to the RCTModuleRegistry. Useful for modules that require access
* to other NativeModules. To implement this in your module, just add `@synthesize
* moduleRegistry = _moduleRegistry;`. If using Swift, add
* `@objc var moduleRegistry: RCTModuleRegistry!` to your module.
*/
@property (nonatomic, weak, readwrite) RCTModuleRegistry *moduleRegistry;
/**
* A reference to the RCTViewRegistry. Useful for modules that query UIViews,
* given a react tag. This API is deprecated, and only exists to help migrate
* NativeModules to Venice.
*
* To implement this in your module, just add `@synthesize
* viewRegistry_DEPRECATED = _viewRegistry_DEPRECATED;`. If using Swift, add
* `@objc var viewRegistry_DEPRECATED: RCTViewRegistry!` to your module.
*/
@property (nonatomic, weak, readwrite) RCTViewRegistry *viewRegistry_DEPRECATED;
/**
* A reference to the RCTBundleManager. Useful for modules that need to read
* or write to the app's bundle URL.
*
* To implement this in your module, just add `@synthesize bundleManager =
* _bundleManager;`. If using Swift, add `@objc var bundleManager:
* RCTBundleManager!` to your module.
*/
@property (nonatomic, weak, readwrite) RCTBundleManager *bundleManager;
/**
* A reference to an RCTCallableJSModules. Useful for modules that need to
* call into methods on JavaScript modules registered as callable with
* React Native.
*
* To implement this in your module, just add `@synthesize callableJSModules =
* _callableJSModules;`. If using Swift, add `@objc var callableJSModules:
* RCTCallableJSModules!` to your module.
*/
@property (nonatomic, weak, readwrite) RCTCallableJSModules *callableJSModules;
/**
* A reference to the RCTBridge. Useful for modules that require access
* to bridge features, such as sending events or making JS calls. This
* will be set automatically by the bridge when it initializes the module.
* To implement this in your module, just add `@synthesize bridge = _bridge;`
* If using Swift, add `@objc var bridge: RCTBridge!` to your module.
*/
@property (nonatomic, weak, readonly) RCTBridge *bridge RCT_DEPRECATED;
/**
* This property is deprecated. This selector used to support two functionalities.
*
* 1) Providing a queue to do additional async work.
* Instead of synthesizing this selector, retrieve a queue from GCD to do any asynchronous work.
* Example: _myQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
*
* 2) Overriding this in order to run all the module's methods on a specific queue, usually main.
* Instead of overriding this, directly dispatch the code onto main queue when necessary.
* Example: dispatch_async(dispatch_get_main_queue, ^{ ... });
*/
@property (nonatomic, strong, readonly) dispatch_queue_t methodQueue RCT_DEPRECATED;
/**
* Wrap the parameter line of your method implementation with this macro to
* expose it to JS. By default the exposed method will match the first part of
* the Objective-C method selector name (up to the first colon). Use
* RCT_REMAP_METHOD to specify the JS name of the method.
*
* For example, in ModuleName.m:
*
* - (void)doSomething:(NSString *)aString withA:(NSInteger)a andB:(NSInteger)b
* { ... }
*
* becomes
*
* RCT_EXPORT_METHOD(doSomething:(NSString *)aString
* withA:(NSInteger)a
* andB:(NSInteger)b)
* { ... }
*
* and is exposed to JavaScript as `NativeModules.ModuleName.doSomething`.
*
* ## Promises
*
* Bridge modules can also define methods that are exported to JavaScript as
* methods that return a Promise, and are compatible with JS async functions.
*
* Declare the last two parameters of your native method to be a resolver block
* and a rejecter block. The resolver block must precede the rejecter block.
*
* For example:
*
* RCT_EXPORT_METHOD(doSomethingAsync:(NSString *)aString
* resolver:(RCTPromiseResolveBlock)resolve
* rejecter:(RCTPromiseRejectBlock)reject
* { ... }
*
* Calling `NativeModules.ModuleName.doSomethingAsync(aString)` from
* JavaScript will return a promise that is resolved or rejected when your
* native method implementation calls the respective block.
*
*/
#define RCT_EXPORT_METHOD(method) RCT_REMAP_METHOD(, method)
/**
* Same as RCT_EXPORT_METHOD but the method is called from JS
* synchronously **on the JS thread**, possibly returning a result.
*
* WARNING: in the vast majority of cases, you should use RCT_EXPORT_METHOD which
* allows your native module methods to be called asynchronously: calling
* methods synchronously can have strong performance penalties and introduce
* threading-related bugs to your native modules.
*
* The return type must be of object type (id) and should be serializable
* to JSON. This means that the hook can only return nil or JSON values
* (e.g. NSNumber, NSString, NSArray, NSDictionary).
*
* Calling these methods when running under the websocket executor
* is currently not supported.
*/
#define RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(method) RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(id, method)
#define RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(returnType, method) \
RCT_REMAP_BLOCKING_SYNCHRONOUS_METHOD(, returnType, method)
/**
* Similar to RCT_EXPORT_METHOD but lets you set the JS name of the exported
* method. Example usage:
*
* RCT_REMAP_METHOD(executeQueryWithParameters,
* executeQuery:(NSString *)query parameters:(NSDictionary *)parameters)
* { ... }
*/
#define RCT_REMAP_METHOD(js_name, method) \
_RCT_EXTERN_REMAP_METHOD(js_name, method, NO) \
-(void)method RCT_DYNAMIC
/**
* Similar to RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD but lets you set
* the JS name of the exported method. Example usage:
*
* RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(executeQueryWithParameters,
* executeQuery:(NSString *)query parameters:(NSDictionary *)parameters)
* { ... }
*/
#define RCT_REMAP_BLOCKING_SYNCHRONOUS_METHOD(js_name, returnType, method) \
_RCT_EXTERN_REMAP_METHOD(js_name, method, YES) \
-(returnType)method RCT_DYNAMIC
/**
* Use this macro in a private Objective-C implementation file to automatically
* register an external module with the bridge when it loads. This allows you to
* register Swift or private Objective-C classes with the bridge.
*
* For example if one wanted to export a Swift class to the bridge:
*
* MyModule.swift:
*
* @objc(MyModule) class MyModule: NSObject {
*
* @objc func doSomething(string: String! withFoo a: Int, bar b: Int) { ... }
*
* }
*
* MyModuleExport.m:
*
* #import <React/RCTBridgeModule.h>
*
* @interface RCT_EXTERN_MODULE(MyModule, NSObject)
*
* RCT_EXTERN_METHOD(doSomething:(NSString *)string withFoo:(NSInteger)a bar:(NSInteger)b)
*
* @end
*
* This will now expose MyModule and the method to JavaScript via
* `NativeModules.MyModule.doSomething`
*/
#define RCT_EXTERN_MODULE(objc_name, objc_supername) RCT_EXTERN_REMAP_MODULE(, objc_name, objc_supername)
/**
* Like RCT_EXTERN_MODULE, but allows setting a custom JavaScript name.
*/
#define RCT_EXTERN_REMAP_MODULE(js_name, objc_name, objc_supername) \
objc_name: \
objc_supername @ \
end @interface objc_name(RCTExternModule)<RCTBridgeModule> \
@end \
@implementation objc_name (RCTExternModule) \
RCT_EXPORT_MODULE_NO_LOAD(js_name, objc_name)
/**
* Use this macro in accordance with RCT_EXTERN_MODULE to export methods
* of an external module.
*/
#define RCT_EXTERN_METHOD(method) _RCT_EXTERN_REMAP_METHOD(, method, NO)
/**
* Use this macro in accordance with RCT_EXTERN_MODULE to export methods
* of an external module that should be invoked synchronously.
*/
#define RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(method) _RCT_EXTERN_REMAP_METHOD(, method, YES)
/**
* Like RCT_EXTERN_REMAP_METHOD, but allows setting a custom JavaScript name
* and also whether this method is synchronous.
*/
#define _RCT_EXTERN_REMAP_METHOD(js_name, method, is_blocking_synchronous_method) \
+(const RCTMethodInfo *)RCT_CONCAT(__rct_export__, RCT_CONCAT(js_name, RCT_CONCAT(__LINE__, __COUNTER__))) \
{ \
static RCTMethodInfo config = {#js_name, #method, is_blocking_synchronous_method}; \
return &config; \
}
/**
* Most modules can be used from any thread. All of the modules exported non-sync method will be called on its
* methodQueue, and the module will be constructed lazily when its first invoked. Some modules have main need to access
* information that's main queue only (e.g. most UIKit classes). Since we don't want to dispatch synchronously to the
* main thread to this safely, we construct these modules and export their constants ahead-of-time.
*
* Note that when set to false, the module constructor will be called from any thread.
*
* This requirement is currently inferred by checking if the module has a custom initializer or if there's exported
* constants. In the future, we'll stop automatically inferring this and instead only rely on this method.
*/
+ (BOOL)requiresMainQueueSetup;
/**
* Injects methods into JS. Entries in this array are used in addition to any
* methods defined using the macros above. This method is called only once,
* before registration.
*/
- (NSArray<id<RCTBridgeMethod>> *)methodsToExport;
/**
* Injects constants into JS. These constants are made accessible via NativeModules.ModuleName.X. It is only called once
* for the lifetime of the bridge, so it is not suitable for returning dynamic values, but may be used for long-lived
* values such as session keys, that are regenerated only as part of a reload of the entire React application.
*
* If you implement this method and do not implement `requiresMainQueueSetup`, you will trigger deprecated logic
* that eagerly initializes your module on bridge startup. In the future, this behaviour will be changed to default
* to initializing lazily, and even modules with constants will be initialized lazily.
*/
- (NSDictionary *)constantsToExport;
/**
* Notifies the module that a batch of JS method invocations has just completed.
*/
- (void)batchDidComplete;
/**
* Notifies the module that the active batch of JS method invocations has been
* partially flushed.
*
* This occurs before -batchDidComplete, and more frequently.
*/
- (void)partialBatchDidFlush;
@end
/**
* A class that allows NativeModules and TurboModules to look up one another.
*/
@interface RCTModuleRegistry : NSObject
- (void)setBridge:(RCTBridge *)bridge;
- (void)setTurboModuleRegistry:(id<RCTTurboModuleRegistry>)turboModuleRegistry;
- (id)moduleForName:(const char *)moduleName;
- (id)moduleForName:(const char *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyLoad;
- (BOOL)moduleIsInitialized:(Class)moduleClass;
@end
typedef UIView * (^RCTBridgelessComponentViewProvider)(NSNumber *);
typedef void (^RCTViewRegistryUIBlock)(RCTViewRegistry *viewRegistry);
/**
* A class that allows NativeModules to query for views, given React Tags.
*/
@interface RCTViewRegistry : NSObject
- (void)setBridge:(RCTBridge *)bridge;
- (void)setBridgelessComponentViewProvider:(RCTBridgelessComponentViewProvider)bridgelessComponentViewProvider;
- (UIView *)viewForReactTag:(NSNumber *)reactTag;
- (void)addUIBlock:(RCTViewRegistryUIBlock)block;
@end
typedef void (^RCTBridgelessJSModuleMethodInvoker)(
NSString *moduleName,
NSString *methodName,
NSArray *args,
dispatch_block_t onComplete);
/**
* A class that allows NativeModules to call methods on JavaScript modules registered
* as callable with React Native.
*/
@interface RCTCallableJSModules : NSObject
- (void)setBridge:(RCTBridge *)bridge;
- (void)setBridgelessJSModuleMethodInvoker:(RCTBridgelessJSModuleMethodInvoker)bridgelessJSModuleMethodInvoker;
- (void)invokeModule:(NSString *)moduleName method:(NSString *)methodName withArgs:(NSArray *)args;
- (void)invokeModule:(NSString *)moduleName
method:(NSString *)methodName
withArgs:(NSArray *)args
onComplete:(dispatch_block_t)onComplete;
@end

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTBridgeModule.h"
@class RCTBundleManager;
@class RCTCallableJSModules;
@class RCTModuleRegistry;
@class RCTViewRegistry;
/**
RCTBridgeModuleDecorator contains instances that can be initialized with @synthesize
in RCTBridgeModules. For the Fabric interop layer.
In Bridgeless, @synthesize ivars are passed from RCTBridgeModuleDecorator.
In Bridge, @synthesize ivars are passed from RCTModuleData.
*/
@interface RCTBridgeModuleDecorator : NSObject
@property (nonatomic, strong, readonly) RCTViewRegistry *viewRegistry_DEPRECATED;
@property (nonatomic, strong, readonly) RCTModuleRegistry *moduleRegistry;
@property (nonatomic, strong, readonly) RCTBundleManager *bundleManager;
@property (nonatomic, strong, readonly) RCTCallableJSModules *callableJSModules;
- (instancetype)initWithViewRegistry:(RCTViewRegistry *)viewRegistry
moduleRegistry:(RCTModuleRegistry *)moduleRegistry
bundleManager:(RCTBundleManager *)bundleManager
callableJSModules:(RCTCallableJSModules *)callableJSModules;
- (void)attachInteropAPIsToModule:(id<RCTBridgeModule>)bridgeModule;
@end

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "RCTBridgeModuleDecorator.h"
@implementation RCTBridgeModuleDecorator
- (instancetype)initWithViewRegistry:(RCTViewRegistry *)viewRegistry
moduleRegistry:(RCTModuleRegistry *)moduleRegistry
bundleManager:(RCTBundleManager *)bundleManager
callableJSModules:(RCTCallableJSModules *)callableJSModules
{
if (self = [super init]) {
_viewRegistry_DEPRECATED = viewRegistry;
_moduleRegistry = moduleRegistry;
_bundleManager = bundleManager;
_callableJSModules = callableJSModules;
}
return self;
}
- (void)attachInteropAPIsToModule:(id<RCTBridgeModule>)bridgeModule
{
/**
* Attach the RCTViewRegistry to this TurboModule, which allows this TurboModule
* To query a React component's UIView, given its reactTag.
*
* Usage: In the TurboModule @implementation, include:
* `@synthesize viewRegistry_DEPRECATED = _viewRegistry_DEPRECATED`
*/
if ([bridgeModule respondsToSelector:@selector(setViewRegistry_DEPRECATED:)]) {
bridgeModule.viewRegistry_DEPRECATED = _viewRegistry_DEPRECATED;
}
/**
* Attach the RCTBundleManager to this TurboModule, which allows this TurboModule to
* read from/write to the app's bundle URL.
*
* Usage: In the TurboModule @implementation, include:
* `@synthesize bundleManager = _bundleManager`
*/
if ([bridgeModule respondsToSelector:@selector(setBundleManager:)]) {
bridgeModule.bundleManager = _bundleManager;
}
/**
* Attach the RCTCallableJSModules to this TurboModule, which allows this TurboModule
* to call JS Module methods.
*
* Usage: In the TurboModule @implementation, include:
* `@synthesize callableJSModules = _callableJSModules`
*/
if ([bridgeModule respondsToSelector:@selector(setCallableJSModules:)]) {
bridgeModule.callableJSModules = _callableJSModules;
}
/**
* Attach the RCTModuleRegistry to this TurboModule, which allows this TurboModule
* to require other TurboModules/NativeModules.
*
* Usage: In the TurboModule @implementation, include:
* `@synthesize moduleRegistry = _moduleRegistry`
*/
if ([bridgeModule respondsToSelector:@selector(setModuleRegistry:)]) {
bridgeModule.moduleRegistry = _moduleRegistry;
}
}
@end

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#ifdef __cplusplus
#import <ReactCommon/CallInvoker.h>
#endif
#import "RCTBridgeProxy.h"
@interface RCTBridgeProxy (Cxx)
#ifdef __cplusplus
@property (nonatomic, readwrite) std::shared_ptr<facebook::react::CallInvoker> jsCallInvoker;
#endif
@end

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import "RCTBridgeModule.h"
NS_ASSUME_NONNULL_BEGIN
@class RCTBundleManager;
@class RCTCallableJSModules;
@class RCTModuleRegistry;
@class RCTViewRegistry;
@interface RCTBridgeProxy : NSProxy
- (instancetype)initWithViewRegistry:(RCTViewRegistry *)viewRegistry
moduleRegistry:(RCTModuleRegistry *)moduleRegistry
bundleManager:(RCTBundleManager *)bundleManager
callableJSModules:(RCTCallableJSModules *)callableJSModules
dispatchToJSThread:(void (^)(dispatch_block_t))dispatchToJSThread
registerSegmentWithId:(void (^)(NSNumber *, NSString *))registerSegmentWithId
runtime:(void *)runtime
launchOptions:(nullable NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
- (void)forwardInvocation:(NSInvocation *)invocation;
- (void)logWarning:(NSString *)message cmd:(SEL)cmd;
- (void)logError:(NSString *)message cmd:(SEL)cmd;
/**
* Methods required for RCTBridge class extensions
*/
- (id)moduleForClass:(Class)moduleClass;
- (id)moduleForName:(NSString *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyLoad;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,492 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTBridgeProxy.h"
#import "RCTBridgeProxy+Cxx.h"
#import <React/RCTBridge+Private.h>
#import <React/RCTBridge.h>
#import <React/RCTLog.h>
#import <React/RCTUIManager.h>
#import <ReactCommon/CallInvoker.h>
#import <jsi/jsi.h>
using namespace facebook;
@interface RCTUIManagerProxy : NSProxy
- (instancetype)initWithViewRegistry:(RCTViewRegistry *)viewRegistry NS_DESIGNATED_INITIALIZER;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
- (void)forwardInvocation:(NSInvocation *)invocation;
@end
@interface RCTBridgeProxy ()
@property (nonatomic, readwrite) std::shared_ptr<facebook::react::CallInvoker> jsCallInvoker;
@end
@implementation RCTBridgeProxy {
RCTUIManagerProxy *_uiManagerProxy;
RCTModuleRegistry *_moduleRegistry;
RCTBundleManager *_bundleManager;
RCTCallableJSModules *_callableJSModules;
NSDictionary *_launchOptions;
void (^_dispatchToJSThread)(dispatch_block_t);
void (^_registerSegmentWithId)(NSNumber *, NSString *);
void *_runtime;
}
- (instancetype)initWithViewRegistry:(RCTViewRegistry *)viewRegistry
moduleRegistry:(RCTModuleRegistry *)moduleRegistry
bundleManager:(RCTBundleManager *)bundleManager
callableJSModules:(RCTCallableJSModules *)callableJSModules
dispatchToJSThread:(void (^)(dispatch_block_t))dispatchToJSThread
registerSegmentWithId:(void (^)(NSNumber *, NSString *))registerSegmentWithId
runtime:(void *)runtime
launchOptions:(nullable NSDictionary *)launchOptions
{
self = [super self];
if (self) {
_uiManagerProxy = [[RCTUIManagerProxy alloc] initWithViewRegistry:viewRegistry];
_moduleRegistry = moduleRegistry;
_bundleManager = bundleManager;
_callableJSModules = callableJSModules;
_dispatchToJSThread = dispatchToJSThread;
_registerSegmentWithId = registerSegmentWithId;
_runtime = runtime;
_launchOptions = [launchOptions copy];
}
return self;
}
- (void)dispatchBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue
{
[self logWarning:@"Please migrate to dispatchToJSThread: @synthesize dispatchToJSThread = _dispatchToJSThread"
cmd:_cmd];
if (queue == RCTJSThread) {
_dispatchToJSThread(block);
} else if (queue) {
dispatch_async(queue, block);
}
}
/**
* Used By:
* - RCTDevSettings
*/
- (Class)executorClass
{
[self logWarning:@"This method is unsupported. Returning nil." cmd:_cmd];
return nil;
}
/**
* Used By:
* - RCTBlobCollector
*/
- (void *)runtime
{
[self logWarning:@"Please migrate to C++ TurboModule or RuntimeExecutor." cmd:_cmd];
return _runtime;
}
- (std::shared_ptr<facebook::react::CallInvoker>)jsCallInvoker
{
[self logWarning:@"Please migrate to RuntimeExecutor" cmd:_cmd];
return _jsCallInvoker;
}
/**
* RCTModuleRegistry
*/
- (id)moduleForName:(NSString *)moduleName
{
[self logWarning:@"Please migrate to RCTModuleRegistry: @synthesize moduleRegistry = _moduleRegistry." cmd:_cmd];
return [_moduleRegistry moduleForName:[moduleName UTF8String]];
}
- (id)moduleForName:(NSString *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyLoad
{
[self logWarning:@"Please migrate to RCTModuleRegistry: @synthesize moduleRegistry = _moduleRegistry." cmd:_cmd];
return [_moduleRegistry moduleForName:[moduleName UTF8String] lazilyLoadIfNecessary:lazilyLoad];
}
- (id)moduleForClass:(Class)moduleClass
{
[self logWarning:@"Please migrate to RCTModuleRegistry: @synthesize moduleRegistry = _moduleRegistry." cmd:_cmd];
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
return [_moduleRegistry moduleForName:[moduleName UTF8String] lazilyLoadIfNecessary:YES];
}
- (NSArray *)modulesConformingToProtocol:(Protocol *)protocol
{
[self logError:@"The TurboModule system cannot load modules by protocol. Returning an empty NSArray*." cmd:_cmd];
return @[];
}
- (BOOL)moduleIsInitialized:(Class)moduleClass
{
[self logWarning:@"Please migrate to RCTModuleRegistry: @synthesize moduleRegistry = _moduleRegistry." cmd:_cmd];
return [_moduleRegistry moduleIsInitialized:moduleClass];
}
- (NSArray<Class> *)moduleClasses
{
[self logError:@"The TurboModuleManager does not implement this method. Returning an empty NSArray*." cmd:_cmd];
return @[];
}
/**
* RCTBundleManager
*/
- (void)setBundleURL:(NSURL *)bundleURL
{
[self logWarning:@"Please migrate to RCTBundleManager: @synthesize bundleManager = _bundleManager." cmd:_cmd];
[_bundleManager setBundleURL:bundleURL];
}
- (NSURL *)bundleURL
{
[self logWarning:@"Please migrate to RCTBundleManager: @synthesize bundleManager = _bundleManager." cmd:_cmd];
return [_bundleManager bundleURL];
}
/**
* RCTCallableJSModules
*/
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
{
[self logWarning:@"Please migrate to RCTCallableJSModules: @synthesize callableJSModules = _callableJSModules."
cmd:_cmd];
NSArray<NSString *> *ids = [moduleDotMethod componentsSeparatedByString:@"."];
NSString *module = ids[0];
NSString *method = ids[1];
[_callableJSModules invokeModule:module method:method withArgs:args];
}
- (void)enqueueJSCall:(NSString *)module
method:(NSString *)method
args:(NSArray *)args
completion:(dispatch_block_t)completion
{
[self logWarning:@"Please migrate to RCTCallableJSModules: @synthesize callableJSModules = _callableJSModules."
cmd:_cmd];
[_callableJSModules invokeModule:module method:method withArgs:args onComplete:completion];
}
- (void)registerSegmentWithId:(NSUInteger)segmentId path:(NSString *)path
{
_registerSegmentWithId(@(segmentId), path);
}
- (id<RCTBridgeDelegate>)delegate
{
[self logError:@"This method is unsupported. Returning nil." cmd:_cmd];
return nil;
}
- (NSDictionary *)launchOptions
{
return _launchOptions;
}
- (BOOL)loading
{
[self logWarning:@"This method is not implemented. Returning NO." cmd:_cmd];
return NO;
}
- (BOOL)valid
{
[self logWarning:@"This method is not implemented. Returning NO." cmd:_cmd];
return NO;
}
- (RCTPerformanceLogger *)performanceLogger
{
[self logWarning:@"This method is not supported. Returning nil." cmd:_cmd];
return nil;
}
- (void)reload
{
[self logError:@"Please use RCTReloadCommand instead. Nooping." cmd:_cmd];
}
- (void)reloadWithReason:(NSString *)reason
{
[self logError:@"Please use RCTReloadCommand instead. Nooping." cmd:_cmd];
}
- (void)onFastRefresh
{
[[NSNotificationCenter defaultCenter] postNotificationName:RCTBridgeFastRefreshNotification object:self];
}
- (void)requestReload __deprecated_msg("Use RCTReloadCommand instead")
{
[self logError:@"Please use RCTReloadCommand instead. Nooping." cmd:_cmd];
}
- (BOOL)isBatchActive
{
[self logWarning:@"This method is not supported. Returning NO." cmd:_cmd];
return NO;
}
/**
* RCTBridge ()
*/
- (NSString *)bridgeDescription
{
[self logWarning:@"This method is not supported. Returning \"BridgeProxy\"." cmd:_cmd];
return @"BridgeProxy";
}
- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args
{
[self logError:@"This method is not supported. No-oping." cmd:_cmd];
}
- (RCTBridge *)batchedBridge
{
[self logWarning:@"This method is not supported. Returning bridge proxy." cmd:_cmd];
return (RCTBridge *)self;
}
- (void)setBatchedBridge
{
[self logError:@"This method is not supported. No-oping." cmd:_cmd];
}
- (RCTBridgeModuleListProvider)moduleProvider
{
[self logWarning:@"This method is not supported. Returning empty block" cmd:_cmd];
return ^{
return @[];
};
}
- (RCTModuleRegistry *)moduleRegistry
{
return _moduleRegistry;
}
/**
* RCTBridge (RCTCxxBridge)
*/
- (RCTBridge *)parentBridge
{
[self logWarning:@"This method is not supported. Returning bridge proxy." cmd:_cmd];
return (RCTBridge *)self;
}
- (BOOL)moduleSetupComplete
{
[self logWarning:@"This method is not supported. Returning YES." cmd:_cmd];
return YES;
}
- (void)start
{
[self
logError:
@"Starting the bridge proxy does nothing. If you want to start React Native, please use RCTHost start. Nooping"
cmd:_cmd];
}
- (void)registerModuleForFrameUpdates:(id<RCTBridgeModule>)module withModuleData:(RCTModuleData *)moduleData
{
[self logError:@"This method is not supported. Nooping" cmd:_cmd];
}
- (RCTModuleData *)moduleDataForName:(NSString *)moduleName
{
[self logError:@"This method is not supported. Returning nil." cmd:_cmd];
return nil;
}
- (void)registerAdditionalModuleClasses:(NSArray<Class> *)newModules
{
[self
logError:
@"This API is unsupported. Please return all module classes from your app's RCTTurboModuleManagerDelegate getModuleClassFromName:. Nooping."
cmd:_cmd];
}
- (void)updateModuleWithInstance:(id<RCTBridgeModule>)instance
{
[self logError:@"This method is not supported. Nooping." cmd:_cmd];
}
- (void)startProfiling
{
[self logWarning:@"This method is not supported. Nooping." cmd:_cmd];
}
- (void)stopProfiling:(void (^)(NSData *))callback
{
[self logWarning:@"This method is not supported. Nooping." cmd:_cmd];
}
- (id)callNativeModule:(NSUInteger)moduleID method:(NSUInteger)methodID params:(NSArray *)params
{
[self logError:@"This method is not supported. Nooping and returning nil." cmd:_cmd];
return nil;
}
- (void)logMessage:(NSString *)message level:(NSString *)level
{
[self logWarning:@"This method is not supported. Nooping." cmd:_cmd];
}
- (void)_immediatelyCallTimer:(NSNumber *)timer
{
[self logWarning:@"This method is not supported. Nooping." cmd:_cmd];
}
/**
* RCTBridge (Inspector)
*/
- (BOOL)inspectable
{
[self logWarning:@"This method is not supported. Returning NO." cmd:_cmd];
return NO;
}
/**
* RCTBridge (RCTUIManager)
*/
- (RCTUIManager *)uiManager
{
return (RCTUIManager *)_uiManagerProxy;
}
- (RCTBridgeProxy *)object
{
return self;
}
/**
* NSProxy setup
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
{
return [RCTCxxBridge instanceMethodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[self logError:@"This method is unsupported." cmd:invocation.selector];
}
/**
* Logging
* TODO(155977839): Add a means to configure/disable these logs, so people do not ignore all LogBoxes
*/
- (void)logWarning:(NSString *)message cmd:(SEL)cmd
{
if (RCTTurboModuleInteropBridgeProxyLogLevel() == kRCTBridgeProxyLoggingLevelWarning) {
RCTLogWarn(@"RCTBridgeProxy: Calling [bridge %@]. %@", NSStringFromSelector(cmd), message);
}
}
- (void)logError:(NSString *)message cmd:(SEL)cmd
{
if (RCTTurboModuleInteropBridgeProxyLogLevel() == kRCTBridgeProxyLoggingLevelWarning ||
RCTTurboModuleInteropBridgeProxyLogLevel() == kRCTBridgeProxyLoggingLevelError) {
RCTLogError(@"RCTBridgeProxy: Calling [bridge %@]. %@", NSStringFromSelector(cmd), message);
}
}
@end
@implementation RCTUIManagerProxy {
RCTViewRegistry *_viewRegistry;
NSMutableDictionary<NSNumber *, UIView *> *_legacyViewRegistry;
}
- (instancetype)initWithViewRegistry:(RCTViewRegistry *)viewRegistry
{
self = [super self];
if (self) {
_viewRegistry = viewRegistry;
_legacyViewRegistry = [NSMutableDictionary new];
}
return self;
}
/**
* RCTViewRegistry
*/
- (UIView *)viewForReactTag:(NSNumber *)reactTag
{
[self logWarning:@"Please migrate to RCTViewRegistry: @synthesize viewRegistry_DEPRECATED = _viewRegistry_DEPRECATED."
cmd:_cmd];
UIView *view = [_viewRegistry viewForReactTag:reactTag] ? [_viewRegistry viewForReactTag:reactTag]
: [_legacyViewRegistry objectForKey:reactTag];
return [RCTUIManager paperViewOrCurrentView:view];
}
- (void)addUIBlock:(RCTViewManagerUIBlock)block
{
[self
logWarning:
@"This method isn't implemented faithfully. Please migrate to RCTViewRegistry if possible: @synthesize viewRegistry_DEPRECATED = _viewRegistry_DEPRECATED."
cmd:_cmd];
__weak __typeof(self) weakSelf = self;
RCTExecuteOnMainQueue(^{
__typeof(self) strongSelf = weakSelf;
if (strongSelf) {
RCTUIManager *proxiedManager = (RCTUIManager *)strongSelf;
RCTComposedViewRegistry *composedViewRegistry =
[[RCTComposedViewRegistry alloc] initWithUIManager:proxiedManager
andRegistry:strongSelf->_legacyViewRegistry];
block(proxiedManager, composedViewRegistry);
}
});
}
/**
* NSProxy setup
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [RCTUIManager instanceMethodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[self logError:@"This methid is unsupported." cmd:invocation.selector];
}
/**
* Logging
* TODO(155977839): Add a means to configure/disable these logs, so people do not ignore all LogBoxes
*/
- (void)logWarning:(NSString *)message cmd:(SEL)cmd
{
if (RCTTurboModuleInteropBridgeProxyLogLevel() == kRCTBridgeProxyLoggingLevelWarning) {
RCTLogWarn(
@"RCTBridgeProxy (RCTUIManagerProxy): Calling [bridge.uiManager %@]. %@", NSStringFromSelector(cmd), message);
}
}
- (void)logError:(NSString *)message cmd:(SEL)cmd
{
if (RCTTurboModuleInteropBridgeProxyLogLevel() == kRCTBridgeProxyLoggingLevelWarning ||
RCTTurboModuleInteropBridgeProxyLogLevel() == kRCTBridgeProxyLoggingLevelError) {
RCTLogError(
@"RCTBridgeProxy (RCTUIManagerProxy): Calling [bridge.uiManager %@]. %@", NSStringFromSelector(cmd), message);
}
}
@end

View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
@class RCTBridge;
typedef NSURL * (^RCTBridgelessBundleURLGetter)(void);
typedef void (^RCTBridgelessBundleURLSetter)(NSURL *bundleURL);
/**
* A class that allows NativeModules/TurboModules to read/write the bundleURL, with or without the bridge.
*/
@interface RCTBundleManager : NSObject
- (void)setBridge:(RCTBridge *)bridge;
- (void)setBridgelessBundleURLGetter:(RCTBridgelessBundleURLGetter)getter
andSetter:(RCTBridgelessBundleURLSetter)setter
andDefaultGetter:(RCTBridgelessBundleURLGetter)defaultGetter;
- (void)resetBundleURL;
@property NSURL *bundleURL;
@end

View File

@@ -0,0 +1,78 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTBundleManager.h"
#import "RCTAssert.h"
#import "RCTBridge+Private.h"
#import "RCTBridge.h"
@implementation RCTBundleManager {
__weak RCTBridge *_bridge;
RCTBridgelessBundleURLGetter _bridgelessBundleURLGetter;
RCTBridgelessBundleURLSetter _bridgelessBundleURLSetter;
RCTBridgelessBundleURLGetter _bridgelessBundleURLDefaultGetter;
}
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
}
- (void)setBridgelessBundleURLGetter:(RCTBridgelessBundleURLGetter)getter
andSetter:(RCTBridgelessBundleURLSetter)setter
andDefaultGetter:(RCTBridgelessBundleURLGetter)defaultGetter
{
_bridgelessBundleURLGetter = getter;
_bridgelessBundleURLSetter = setter;
_bridgelessBundleURLDefaultGetter = defaultGetter;
}
- (void)setBundleURL:(NSURL *)bundleURL
{
if (_bridge) {
_bridge.bundleURL = bundleURL;
return;
}
RCTAssert(
_bridgelessBundleURLSetter != nil,
@"RCTBundleManager: In bridgeless mode, RCTBridgelessBundleURLSetter must not be nil.");
_bridgelessBundleURLSetter(bundleURL);
}
- (NSURL *)bundleURL
{
if (_bridge) {
return _bridge.bundleURL;
}
RCTAssert(
_bridgelessBundleURLGetter != nil,
@"RCTBundleManager: In bridgeless mode, RCTBridgelessBundleURLGetter must not be nil.");
return _bridgelessBundleURLGetter();
}
- (void)resetBundleURL
{
RCTBridge *strongBridge = _bridge;
if (strongBridge) {
strongBridge.bundleURL = [strongBridge.delegate sourceURLForBridge:strongBridge];
return;
}
RCTAssert(
_bridgelessBundleURLDefaultGetter != nil,
@"RCTBundleManager: In bridgeless mode, default RCTBridgelessBundleURLGetter must not be nil.");
RCTAssert(
_bridgelessBundleURLSetter != nil,
@"RCTBundleManager: In bridgeless mode, RCTBridgelessBundleURLSetter must not be nil.");
_bridgelessBundleURLSetter(_bridgelessBundleURLDefaultGetter());
}
@end

View File

@@ -0,0 +1,167 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import "RCTDefines.h"
RCT_EXTERN NSString *_Nonnull const RCTBundleURLProviderUpdatedNotification;
RCT_EXTERN const NSUInteger kRCTBundleURLProviderDefaultPort;
#if RCT_DEV_MENU | RCT_PACKAGER_LOADING_FUNCTIONALITY
/**
* Allow/disallow accessing the packager server for various runtime scenario.
* For instance, if a test run should never access the packager, disable it
* by calling this function before initializing React Native (RCTBridge etc).
* By default the access is enabled.
*/
RCT_EXTERN void RCTBundleURLProviderAllowPackagerServerAccess(BOOL allowed);
#endif
NS_ASSUME_NONNULL_BEGIN
@interface RCTBundleURLProvider : NSObject
/**
* Reset every settings to default.
*/
- (void)resetToDefaults;
/**
* Return the server host. If its a development build and there's no jsLocation defined,
* it will return the server host IP address
*/
- (NSString *)packagerServerHost;
/**
* Return the server host with optional port. If its a development build and there's no jsLocation defined,
* it will return the server host IP address
*/
- (NSString *)packagerServerHostPort;
/**
* Returns if there's a packager running at the given host port.
* The port is optional, if not specified, kRCTBundleURLProviderDefaultPort will be used
*/
+ (BOOL)isPackagerRunning:(NSString *)hostPort;
/**
* Returns if there's a packager running at the given scheme://host:port.
* The port is optional, if not specified, kRCTBundleURLProviderDefaultPort will be used
* The scheme is also optional, if not specified, a default http protocol will be used
*/
+ (BOOL)isPackagerRunning:(NSString *)hostPort scheme:(NSString *__nullable)scheme;
/**
* Returns the jsBundleURL for a given bundle entrypoint and
* the fallback offline JS bundle if the packager is not running.
*/
- (NSURL *__nullable)jsBundleURLForBundleRoot:(NSString *)bundleRoot
fallbackURLProvider:(NSURL *__nullable (^)(void))fallbackURLProvider;
/**
* Returns the jsBundleURL for a given split bundle entrypoint in development
*/
- (NSURL *__nullable)jsBundleURLForSplitBundleRoot:(NSString *)bundleRoot;
/**
* Returns the jsBundleURL for a given bundle entrypoint and
* the fallback offline JS bundle if the packager is not running.
* if extension is nil, "jsbundle" will be used.
*/
- (NSURL *__nullable)jsBundleURLForBundleRoot:(NSString *)bundleRoot fallbackExtension:(NSString *__nullable)extension;
/**
* Returns the jsBundleURL for a given bundle entrypoint and
* the fallback offline JS bundle if the packager is not running.
*/
- (NSURL *__nullable)jsBundleURLForBundleRoot:(NSString *)bundleRoot;
/**
* Returns the jsBundleURL for a given bundle entrypoint and
* the fallback offline JS bundle. If extension is nil,
* "jsbundle" will be used.
*/
- (NSURL *__nullable)jsBundleURLForFallbackExtension:(NSString *__nullable)extension;
/**
* Returns the resourceURL for a given bundle entrypoint and
* the fallback offline resource file if the packager is not running.
*/
- (NSURL *__nullable)resourceURLForResourceRoot:(NSString *)root
resourceName:(NSString *)name
resourceExtension:(NSString *)extension
offlineBundle:(NSBundle *)offlineBundle;
/**
* The IP address or hostname of the packager.
*/
@property (nonatomic, copy, nullable) NSString *jsLocation;
@property (nonatomic, assign) BOOL enableMinification;
@property (nonatomic, assign) BOOL enableDev;
@property (nonatomic, assign) BOOL inlineSourceMap;
/**
* The scheme/protocol used of the packager, the default is the http protocol
*/
@property (nonatomic, copy) NSString *packagerScheme;
+ (instancetype)sharedSettings;
/**
* Given a hostname for the packager and a bundle root, returns the URL to the js bundle. Generally you should use the
* instance method -jsBundleURLForBundleRoot:fallbackExtension: which includes logic to guess if the packager is running
* and fall back to a pre-packaged bundle if it is not.
*
* The options here mirror some of Metro's Bundling Options:
* - enableDev: Whether to keep or remove `__DEV__` blocks from the bundle.
* - enableMinification: Enables or disables minification. Usually production bundles are minified and development
* bundles are not.
* - inlineSourceMap: When true, the bundler will inline the source map in the bundle
* - modulesOnly: When true, will only send module definitions without polyfills and without the require-runtime.
* - runModule: When true, will run the main module after defining all modules. This is used in the main bundle but not
* in split bundles.
*/
+ (NSURL *__nullable)jsBundleURLForBundleRoot:(NSString *)bundleRoot
packagerHost:(NSString *)packagerHost
enableDev:(BOOL)enableDev
enableMinification:(BOOL)enableMinification
inlineSourceMap:(BOOL)inlineSourceMap;
+ (NSURL *__nullable)jsBundleURLForBundleRoot:(NSString *)bundleRoot
packagerHost:(NSString *)packagerHost
packagerScheme:(NSString *__nullable)scheme
enableDev:(BOOL)enableDev
enableMinification:(BOOL)enableMinification
inlineSourceMap:(BOOL)inlineSourceMap
modulesOnly:(BOOL)modulesOnly
runModule:(BOOL)runModule;
/**
* Given a hostname for the packager and a resource path (including "/"), return the URL to the resource.
* In general, please use the instance method to decide if the packager is running and fallback to the pre-packaged
* resource if it is not: -resourceURLForResourceRoot:resourceName:resourceExtension:offlineBundle:
*/
+ (NSURL *)resourceURLForResourcePath:(NSString *)path
packagerHost:(NSString *)packagerHost
scheme:(NSString *__nullable)scheme
query:(NSString *__nullable)query
__deprecated_msg("Use version with queryItems parameter instead");
/**
* Given a hostname for the packager and a resource path (including "/"), return the URL to the resource.
* In general, please use the instance method to decide if the packager is running and fallback to the pre-packaged
* resource if it is not: -resourceURLForResourceRoot:resourceName:resourceExtension:offlineBundle:
*/
+ (NSURL *)resourceURLForResourcePath:(NSString *)path
packagerHost:(NSString *)packagerHost
scheme:(NSString *)scheme
queryItems:(NSArray<NSURLQueryItem *> *__nullable)queryItems;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,398 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTBundleURLProvider.h"
#import "RCTConstants.h"
#import "RCTConvert.h"
#import "RCTDefines.h"
#import "RCTLog.h"
NSString *const RCTBundleURLProviderUpdatedNotification = @"RCTBundleURLProviderUpdatedNotification";
const NSUInteger kRCTBundleURLProviderDefaultPort = RCT_METRO_PORT;
#if RCT_DEV_MENU | RCT_PACKAGER_LOADING_FUNCTIONALITY
static BOOL kRCTAllowPackagerAccess = YES;
void RCTBundleURLProviderAllowPackagerServerAccess(BOOL allowed)
{
kRCTAllowPackagerAccess = allowed;
}
#endif
static NSString *const kRCTPackagerSchemeKey = @"RCT_packager_scheme";
static NSString *const kRCTJsLocationKey = @"RCT_jsLocation";
static NSString *const kRCTEnableDevKey = @"RCT_enableDev";
static NSString *const kRCTEnableMinificationKey = @"RCT_enableMinification";
static NSString *const kRCTInlineSourceMapKey = @"RCT_inlineSourceMap";
@implementation RCTBundleURLProvider
- (instancetype)init
{
self = [super init];
if (self) {
[self _setDefaults];
}
return self;
}
- (NSDictionary *)defaults
{
return @{
kRCTEnableDevKey : @YES,
kRCTEnableMinificationKey : @NO,
};
}
- (void)settingsUpdated
{
[[NSNotificationCenter defaultCenter] postNotificationName:RCTBundleURLProviderUpdatedNotification object:self];
}
- (void)resetToDefaults
{
for (NSString *key in [[self defaults] allKeys]) {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
}
[self _setDefaults];
[self settingsUpdated];
}
static NSURL *serverRootWithHostPort(NSString *hostPort, NSString *scheme)
{
if (![scheme length]) {
scheme = @"http";
}
if ([hostPort rangeOfString:@":"].location != NSNotFound) {
return [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@/", scheme, hostPort]];
}
return [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@:%lu/",
scheme,
hostPort,
(unsigned long)kRCTBundleURLProviderDefaultPort]];
}
#if RCT_DEV_MENU | RCT_PACKAGER_LOADING_FUNCTIONALITY
+ (BOOL)isPackagerRunning:(NSString *)hostPort
{
return [RCTBundleURLProvider isPackagerRunning:hostPort scheme:nil];
}
+ (BOOL)isPackagerRunning:(NSString *)hostPort scheme:(NSString *)scheme
{
if (!kRCTAllowPackagerAccess) {
return NO;
}
NSURL *url = [serverRootWithHostPort(hostPort, scheme) URLByAppendingPathComponent:@"status"];
NSURLSession *session = [NSURLSession sharedSession];
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10];
__block NSURLResponse *response;
__block NSData *data;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[[session dataTaskWithRequest:request
completionHandler:^(NSData *d, NSURLResponse *res, __unused NSError *err) {
data = d;
response = res;
dispatch_semaphore_signal(semaphore);
}] resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSString *status = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return [status isEqualToString:@"packager-status:running"];
}
- (NSString *)guessPackagerHost
{
static NSString *ipGuess;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *ipPath = [[NSBundle mainBundle] pathForResource:@"ip" ofType:@"txt"];
ipGuess =
[[NSString stringWithContentsOfFile:ipPath encoding:NSUTF8StringEncoding
error:nil] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
});
NSString *host = ipGuess ?: @"localhost";
if ([RCTBundleURLProvider isPackagerRunning:host]) {
return host;
}
return nil;
}
#else
+ (BOOL)isPackagerRunning:(NSString *)hostPort
{
return false;
}
+ (BOOL)isPackagerRunning:(NSString *)hostPort scheme:(NSString *)scheme
{
return false;
}
#endif
- (NSString *)packagerServerHost
{
NSString *location = [self packagerServerHostPort];
if (location) {
NSInteger index = [location rangeOfString:@":"].location;
if (index != NSNotFound) {
location = [location substringToIndex:index];
}
}
return location;
}
- (NSString *)packagerServerHostPort
{
#if RCT_DEV_MENU | RCT_PACKAGER_LOADING_FUNCTIONALITY
if (!kRCTAllowPackagerAccess) {
RCTLogInfo(@"Packager server access is disabled in this environment");
return nil;
}
#endif
NSString *location = [self jsLocation];
#if RCT_DEV_MENU
NSString *scheme = [self packagerScheme];
if ([location length] && ![RCTBundleURLProvider isPackagerRunning:location scheme:scheme]) {
location = nil;
}
#endif
if (location != nil) {
return location;
}
#if RCT_DEV
NSString *host = [self guessPackagerHost];
if (host) {
return host;
}
#endif
return nil;
}
- (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot fallbackURLProvider:(NSURL * (^)(void))fallbackURLProvider
{
NSString *packagerServerHostPort = [self packagerServerHostPort];
if (!packagerServerHostPort) {
return fallbackURLProvider();
} else {
return [RCTBundleURLProvider jsBundleURLForBundleRoot:bundleRoot
packagerHost:packagerServerHostPort
packagerScheme:[self packagerScheme]
enableDev:[self enableDev]
enableMinification:[self enableMinification]
inlineSourceMap:[self inlineSourceMap]
modulesOnly:NO
runModule:YES];
}
}
- (NSURL *)jsBundleURLForSplitBundleRoot:(NSString *)bundleRoot
{
return [RCTBundleURLProvider jsBundleURLForBundleRoot:bundleRoot
packagerHost:[self packagerServerHostPort]
packagerScheme:[self packagerScheme]
enableDev:[self enableDev]
enableMinification:[self enableMinification]
inlineSourceMap:[self inlineSourceMap]
modulesOnly:YES
runModule:NO];
}
- (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot fallbackExtension:(NSString *)extension
{
return [self jsBundleURLForBundleRoot:bundleRoot
fallbackURLProvider:^NSURL * {
return [self jsBundleURLForFallbackExtension:extension];
}];
}
- (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot
{
return [self jsBundleURLForBundleRoot:bundleRoot fallbackExtension:nil];
}
- (NSURL *)jsBundleURLForFallbackExtension:(NSString *)extension
{
extension = extension ?: @"jsbundle";
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:extension];
}
- (NSURL *)resourceURLForResourceRoot:(NSString *)root
resourceName:(NSString *)name
resourceExtension:(NSString *)extension
offlineBundle:(NSBundle *)offlineBundle
{
NSString *packagerServerHostPort = [self packagerServerHostPort];
NSString *packagerServerScheme = [self packagerScheme];
if (!packagerServerHostPort) {
// Serve offline bundle (local file)
NSBundle *bundle = offlineBundle ?: [NSBundle mainBundle];
return [bundle URLForResource:name withExtension:extension];
}
NSString *path = [NSString stringWithFormat:@"/%@/%@.%@", root, name, extension];
return [[self class] resourceURLForResourcePath:path
packagerHost:packagerServerHostPort
scheme:packagerServerScheme
queryItems:nil];
}
+ (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot
packagerHost:(NSString *)packagerHost
enableDev:(BOOL)enableDev
enableMinification:(BOOL)enableMinification
inlineSourceMap:(BOOL)inlineSourceMap
{
return [self jsBundleURLForBundleRoot:bundleRoot
packagerHost:packagerHost
packagerScheme:nil
enableDev:enableDev
enableMinification:enableMinification
inlineSourceMap:inlineSourceMap
modulesOnly:NO
runModule:YES];
}
+ (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot
packagerHost:(NSString *)packagerHost
packagerScheme:(NSString *)scheme
enableDev:(BOOL)enableDev
enableMinification:(BOOL)enableMinification
inlineSourceMap:(BOOL)inlineSourceMap
modulesOnly:(BOOL)modulesOnly
runModule:(BOOL)runModule
{
NSString *path = [NSString stringWithFormat:@"/%@.bundle", bundleRoot];
BOOL lazy = enableDev;
NSArray<NSURLQueryItem *> *queryItems = @[
[[NSURLQueryItem alloc] initWithName:@"platform" value:RCTPlatformName],
[[NSURLQueryItem alloc] initWithName:@"dev" value:enableDev ? @"true" : @"false"],
[[NSURLQueryItem alloc] initWithName:@"lazy" value:lazy ? @"true" : @"false"],
[[NSURLQueryItem alloc] initWithName:@"minify" value:enableMinification ? @"true" : @"false"],
[[NSURLQueryItem alloc] initWithName:@"inlineSourceMap" value:inlineSourceMap ? @"true" : @"false"],
[[NSURLQueryItem alloc] initWithName:@"modulesOnly" value:modulesOnly ? @"true" : @"false"],
[[NSURLQueryItem alloc] initWithName:@"runModule" value:runModule ? @"true" : @"false"],
];
NSString *bundleID = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleIdentifierKey];
if (bundleID) {
queryItems = [queryItems arrayByAddingObject:[[NSURLQueryItem alloc] initWithName:@"app" value:bundleID]];
}
return [[self class] resourceURLForResourcePath:path packagerHost:packagerHost scheme:scheme queryItems:queryItems];
}
+ (NSURL *)resourceURLForResourcePath:(NSString *)path
packagerHost:(NSString *)packagerHost
scheme:(NSString *)scheme
query:(NSString *)query
{
NSURLComponents *components = [NSURLComponents componentsWithURL:serverRootWithHostPort(packagerHost, scheme)
resolvingAgainstBaseURL:NO];
components.path = path;
if (query != nil) {
components.query = query;
}
return components.URL;
}
+ (NSURL *)resourceURLForResourcePath:(NSString *)path
packagerHost:(NSString *)packagerHost
scheme:(NSString *)scheme
queryItems:(NSArray<NSURLQueryItem *> *)queryItems
{
NSURLComponents *components = [NSURLComponents componentsWithURL:serverRootWithHostPort(packagerHost, scheme)
resolvingAgainstBaseURL:NO];
components.path = path;
if (queryItems != nil) {
components.queryItems = queryItems;
}
return components.URL;
}
- (void)updateValue:(id)object forKey:(NSString *)key
{
[[NSUserDefaults standardUserDefaults] setObject:object forKey:key];
[[NSUserDefaults standardUserDefaults] synchronize];
[self settingsUpdated];
}
- (BOOL)enableDev
{
return [[NSUserDefaults standardUserDefaults] boolForKey:kRCTEnableDevKey];
}
- (BOOL)enableMinification
{
return [[NSUserDefaults standardUserDefaults] boolForKey:kRCTEnableMinificationKey];
}
- (BOOL)inlineSourceMap
{
return [[NSUserDefaults standardUserDefaults] boolForKey:kRCTInlineSourceMapKey];
}
- (NSString *)jsLocation
{
return [[NSUserDefaults standardUserDefaults] stringForKey:kRCTJsLocationKey];
}
- (NSString *)packagerScheme
{
NSString *packagerScheme = [[NSUserDefaults standardUserDefaults] stringForKey:kRCTPackagerSchemeKey];
if (![packagerScheme length]) {
return @"http";
}
return packagerScheme;
}
- (void)setEnableDev:(BOOL)enableDev
{
[self updateValue:@(enableDev) forKey:kRCTEnableDevKey];
}
- (void)setJsLocation:(NSString *)jsLocation
{
[self updateValue:jsLocation forKey:kRCTJsLocationKey];
}
- (void)setEnableMinification:(BOOL)enableMinification
{
[self updateValue:@(enableMinification) forKey:kRCTEnableMinificationKey];
}
- (void)setInlineSourceMap:(BOOL)inlineSourceMap
{
[self updateValue:@(inlineSourceMap) forKey:kRCTInlineSourceMapKey];
}
- (void)setPackagerScheme:(NSString *)packagerScheme
{
[self updateValue:packagerScheme forKey:kRCTPackagerSchemeKey];
}
+ (instancetype)sharedSettings
{
static RCTBundleURLProvider *sharedInstance;
static dispatch_once_t once_token;
dispatch_once(&once_token, ^{
sharedInstance = [RCTBundleURLProvider new];
});
return sharedInstance;
}
#pragma mark - Private helpers
- (void)_setDefaults
{
[[NSUserDefaults standardUserDefaults] registerDefaults:[self defaults]];
}
@end

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTBridge.h"
#import "RCTBridgeModule.h"
@implementation RCTCallableJSModules {
RCTBridgelessJSModuleMethodInvoker _bridgelessJSModuleMethodInvoker;
__weak RCTBridge *_bridge;
}
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
}
- (void)setBridgelessJSModuleMethodInvoker:(RCTBridgelessJSModuleMethodInvoker)bridgelessJSModuleMethodInvoker
{
_bridgelessJSModuleMethodInvoker = bridgelessJSModuleMethodInvoker;
}
- (void)invokeModule:(NSString *)moduleName method:(NSString *)methodName withArgs:(NSArray *)args
{
[self invokeModule:moduleName method:methodName withArgs:args onComplete:NULL];
}
- (void)invokeModule:(NSString *)moduleName
method:(NSString *)methodName
withArgs:(NSArray *)args
onComplete:(dispatch_block_t)onComplete
{
RCTBridge *bridge = _bridge;
if (bridge) {
[bridge enqueueJSCall:moduleName method:methodName args:args completion:onComplete];
return;
}
if (_bridgelessJSModuleMethodInvoker) {
_bridgelessJSModuleMethodInvoker(moduleName, methodName, args, onComplete);
}
}
@end

View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTEventDispatcherProtocol.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Generic untyped event for Components. Used internally by RCTDirectEventBlock and
* RCTBubblingEventBlock, for other use cases prefer using a class that implements
* RCTEvent to have a type safe way to initialize it.
*/
@interface RCTComponentEvent : NSObject <RCTEvent>
- (instancetype)initWithName:(NSString *)name viewTag:(NSNumber *)viewTag body:(NSDictionary *)body;
NS_ASSUME_NONNULL_END
@end

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTComponentEvent.h"
#import "RCTAssert.h"
@implementation RCTComponentEvent {
NSArray *_arguments;
}
@synthesize eventName = _eventName;
@synthesize viewTag = _viewTag;
- (instancetype)initWithName:(NSString *)name viewTag:(NSNumber *)viewTag body:(NSDictionary *)body
{
if (self = [super init]) {
NSMutableDictionary *mutableBody = [NSMutableDictionary dictionaryWithDictionary:body];
mutableBody[@"target"] = viewTag;
_eventName = RCTNormalizeInputEventName(name);
_viewTag = viewTag;
_arguments = @[ _viewTag, _eventName, mutableBody ];
}
return self;
}
RCT_NOT_IMPLEMENTED(-(instancetype)init)
- (NSArray *)arguments
{
return _arguments;
}
- (BOOL)canCoalesce
{
return NO;
}
+ (NSString *)moduleDotMethod
{
return @"RCTEventEmitter.receiveEvent";
}
@end

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTDefines.h>
RCT_EXTERN NSString *const RCTPlatformName;
RCT_EXTERN NSString *const RCTUserInterfaceStyleDidChangeNotification;
RCT_EXTERN NSString *const RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey;
RCT_EXTERN NSString *const RCTWindowFrameDidChangeNotification;
/**
* This notification fires when the bridge initializes.
*/
RCT_EXTERN NSString *const RCTJavaScriptWillStartLoadingNotification;
/**
* This notification fires when the bridge starts executing the JS bundle.
*/
RCT_EXTERN NSString *const RCTJavaScriptWillStartExecutingNotification;
/**
* This notification fires when the bridge has finished loading the JS bundle.
*/
RCT_EXTERN NSString *const RCTJavaScriptDidLoadNotification;
/**
* This notification fires when the bridge failed to load the JS bundle. The
* `error` key can be used to determine the error that occurred.
*/
RCT_EXTERN NSString *const RCTJavaScriptDidFailToLoadNotification;
/**
* This notification fires each time a native module is instantiated. The
* `module` key will contain a reference to the newly-created module instance.
* Note that this notification may be fired before the module is available via
* the `[bridge moduleForClass:]` method.
*/
RCT_EXTERN NSString *const RCTDidInitializeModuleNotification;
/*
* W3C Pointer Events
*/
RCT_EXTERN BOOL RCTGetDispatchW3CPointerEvents(void);
RCT_EXTERN void RCTSetDispatchW3CPointerEvents(BOOL value);
/*
* Memory Pressure Unloading Level
*/
RCT_EXTERN int RCTGetMemoryPressureUnloadLevel(void);
RCT_EXTERN void RCTSetMemoryPressureUnloadLevel(int value);
/*
* Use native view configs in bridgeless mode
*/
RCT_EXTERN BOOL RCTGetUseNativeViewConfigsInBridgelessMode(void);
RCT_EXTERN void RCTSetUseNativeViewConfigsInBridgelessMode(BOOL value);

View File

@@ -0,0 +1,68 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTConstants.h"
NSString *const RCTPlatformName = @"ios";
NSString *const RCTUserInterfaceStyleDidChangeNotification = @"RCTUserInterfaceStyleDidChangeNotification";
NSString *const RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey = @"traitCollection";
NSString *const RCTWindowFrameDidChangeNotification = @"RCTWindowFrameDidChangeNotification";
NSString *const RCTJavaScriptDidFailToLoadNotification = @"RCTJavaScriptDidFailToLoadNotification";
NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
NSString *const RCTJavaScriptWillStartExecutingNotification = @"RCTJavaScriptWillStartExecutingNotification";
NSString *const RCTJavaScriptWillStartLoadingNotification = @"RCTJavaScriptWillStartLoadingNotification";
NSString *const RCTDidInitializeModuleNotification = @"RCTDidInitializeModuleNotification";
/*
* W3C Pointer Events
*/
static BOOL RCTDispatchW3CPointerEvents = NO;
BOOL RCTGetDispatchW3CPointerEvents(void)
{
return RCTDispatchW3CPointerEvents;
}
void RCTSetDispatchW3CPointerEvents(BOOL value)
{
RCTDispatchW3CPointerEvents = value;
}
/*
* Memory Pressure Unloading Level for experimentation only.
* Default is 15, which is TRIM_MEMORY_RUNNING_CRITICAL.
*/
static int RCTMemoryPressureUnloadLevel = 15;
int RCTGetMemoryPressureUnloadLevel(void)
{
return RCTMemoryPressureUnloadLevel;
}
void RCTSetMemoryPressureUnloadLevel(int value)
{
RCTMemoryPressureUnloadLevel = value;
}
/*
* Use native view configs in bridgeless mode
*/
static BOOL RCTUseNativeViewConfigsInBridgelessMode = NO;
BOOL RCTGetUseNativeViewConfigsInBridgelessMode(void)
{
return RCTUseNativeViewConfigsInBridgelessMode;
}
void RCTSetUseNativeViewConfigsInBridgelessMode(BOOL value)
{
RCTUseNativeViewConfigsInBridgelessMode = value;
}

View File

@@ -0,0 +1,265 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
#import <React/RCTAnimationType.h>
#import <React/RCTBorderCurve.h>
#import <React/RCTBorderStyle.h>
#import <React/RCTCursor.h>
#import <React/RCTDefines.h>
#import <React/RCTLog.h>
#import <React/RCTPointerEvents.h>
#import <React/RCTTextDecorationLineType.h>
#import <yoga/Yoga.h>
/**
* This class provides a collection of conversion functions for mapping
* JSON objects to native types and classes. These are useful when writing
* custom RCTViewManager setter methods.
*/
@interface RCTConvert : NSObject
+ (id)id:(id)json;
+ (BOOL)BOOL:(id)json;
+ (double)double:(id)json;
+ (float)float:(id)json;
+ (int)int:(id)json;
+ (int64_t)int64_t:(id)json;
+ (uint64_t)uint64_t:(id)json;
+ (NSInteger)NSInteger:(id)json;
+ (NSUInteger)NSUInteger:(id)json;
+ (NSArray *)NSArray:(id)json;
+ (NSDictionary *)NSDictionary:(id)json;
+ (NSString *)NSString:(id)json;
+ (NSNumber *)NSNumber:(id)json;
+ (NSSet *)NSSet:(id)json;
+ (NSData *)NSData:(id)json;
+ (NSIndexSet *)NSIndexSet:(id)json;
+ (NSURLRequestCachePolicy)NSURLRequestCachePolicy:(id)json;
+ (NSURL *)NSURL:(id)json;
+ (NSURLRequest *)NSURLRequest:(id)json;
typedef NSURL RCTFileURL;
+ (RCTFileURL *)RCTFileURL:(id)json;
+ (NSDate *)NSDate:(id)json;
+ (NSLocale *)NSLocale:(id)json;
+ (NSTimeZone *)NSTimeZone:(id)json;
+ (NSTimeInterval)NSTimeInterval:(id)json;
+ (NSLineBreakMode)NSLineBreakMode:(id)json;
+ (NSTextAlignment)NSTextAlignment:(id)json;
+ (NSUnderlineStyle)NSUnderlineStyle:(id)json;
+ (NSWritingDirection)NSWritingDirection:(id)json;
+ (NSLineBreakStrategy)NSLineBreakStrategy:(id)json;
+ (UITextAutocapitalizationType)UITextAutocapitalizationType:(id)json;
+ (UITextFieldViewMode)UITextFieldViewMode:(id)json;
+ (UIKeyboardType)UIKeyboardType:(id)json;
+ (UIKeyboardAppearance)UIKeyboardAppearance:(id)json;
+ (UIReturnKeyType)UIReturnKeyType:(id)json;
+ (UIUserInterfaceStyle)UIUserInterfaceStyle:(id)json API_AVAILABLE(ios(12));
+ (UIInterfaceOrientationMask)UIInterfaceOrientationMask:(NSString *)orientation;
#if !TARGET_OS_TV
+ (UIDataDetectorTypes)UIDataDetectorTypes:(id)json;
#endif
+ (UIViewContentMode)UIViewContentMode:(id)json;
#if !TARGET_OS_TV
+ (UIBarStyle)UIBarStyle:(id)json __deprecated;
#endif
+ (RCTCursor)RCTCursor:(id)json;
+ (CGFloat)CGFloat:(id)json;
+ (CGPoint)CGPoint:(id)json;
+ (CGSize)CGSize:(id)json;
+ (CGRect)CGRect:(id)json;
+ (UIEdgeInsets)UIEdgeInsets:(id)json;
+ (CGLineCap)CGLineCap:(id)json;
+ (CGLineJoin)CGLineJoin:(id)json;
+ (CGAffineTransform)CGAffineTransform:(id)json;
+ (UIColor *)UIColor:(id)json;
+ (CGColorRef)CGColor:(id)json CF_RETURNS_NOT_RETAINED;
+ (YGValue)YGValue:(id)json;
+ (NSArray<NSArray *> *)NSArrayArray:(id)json;
+ (NSArray<NSString *> *)NSStringArray:(id)json;
+ (NSArray<NSArray<NSString *> *> *)NSStringArrayArray:(id)json;
+ (NSArray<NSDictionary *> *)NSDictionaryArray:(id)json;
+ (NSArray<NSURL *> *)NSURLArray:(id)json;
+ (NSArray<RCTFileURL *> *)RCTFileURLArray:(id)json;
+ (NSArray<NSNumber *> *)NSNumberArray:(id)json;
+ (NSArray<UIColor *> *)UIColorArray:(id)json;
typedef NSArray CGColorArray;
+ (CGColorArray *)CGColorArray:(id)json;
/**
* Convert a JSON object to a Plist-safe equivalent by stripping null values.
*/
typedef id NSPropertyList;
+ (NSPropertyList)NSPropertyList:(id)json;
typedef BOOL css_backface_visibility_t;
+ (YGOverflow)YGOverflow:(id)json;
+ (YGDisplay)YGDisplay:(id)json;
+ (css_backface_visibility_t)css_backface_visibility_t:(id)json;
+ (YGFlexDirection)YGFlexDirection:(id)json;
+ (YGJustify)YGJustify:(id)json;
+ (YGAlign)YGAlign:(id)json;
+ (YGPositionType)YGPositionType:(id)json;
+ (YGWrap)YGWrap:(id)json;
+ (YGDirection)YGDirection:(id)json;
+ (RCTPointerEvents)RCTPointerEvents:(id)json;
+ (RCTAnimationType)RCTAnimationType:(id)json;
+ (RCTBorderStyle)RCTBorderStyle:(id)json;
+ (RCTBorderCurve)RCTBorderCurve:(id)json;
+ (RCTTextDecorationLineType)RCTTextDecorationLineType:(id)json;
@end
@interface RCTConvert (Deprecated)
/**
* Use lightweight generics syntax instead, e.g. NSArray<NSString *>
*/
typedef NSArray NSArrayArray __deprecated_msg("Use NSArray<NSArray *>");
typedef NSArray NSStringArray __deprecated_msg("Use NSArray<NSString *>");
typedef NSArray NSStringArrayArray __deprecated_msg("Use NSArray<NSArray<NSString *> *>");
typedef NSArray NSDictionaryArray __deprecated_msg("Use NSArray<NSDictionary *>");
typedef NSArray NSURLArray __deprecated_msg("Use NSArray<NSURL *>");
typedef NSArray RCTFileURLArray __deprecated_msg("Use NSArray<RCTFileURL *>");
typedef NSArray NSNumberArray __deprecated_msg("Use NSArray<NSNumber *>");
typedef NSArray UIColorArray __deprecated_msg("Use NSArray<UIColor *>");
/**
* Synchronous image loading is generally a bad idea for performance reasons.
* If you need to pass image references, try to use `RCTImageSource` and then
* `RCTImageLoader` instead of converting directly to a UIImage.
*/
+ (UIImage *)UIImage:(id)json;
+ (CGImageRef)CGImage:(id)json CF_RETURNS_NOT_RETAINED;
@end
/**
* Underlying implementations of RCT_XXX_CONVERTER macros. Ignore these.
*/
RCT_EXTERN NSNumber *RCTConvertEnumValue(const char *, NSDictionary *, NSNumber *, id);
RCT_EXTERN NSNumber *RCTConvertMultiEnumValue(const char *, NSDictionary *, NSNumber *, id);
RCT_EXTERN NSArray *RCTConvertArrayValue(SEL, id);
/**
* This macro is used for logging conversion errors. This is just used to
* avoid repeating the same boilerplate for every error message.
*/
#define RCTLogConvertError(json, typeName) \
RCTLogInfo(@"JSON value '%@' of type %@ cannot be converted to %@", json, [json classForCoder], typeName)
/**
* This macro is used for creating simple converter functions that just call
* the specified getter method on the json value.
*/
#define RCT_CONVERTER(type, name, getter) RCT_CUSTOM_CONVERTER(type, name, [json getter])
/**
* This macro is used for creating converter functions with arbitrary logic.
*/
#define RCT_CUSTOM_CONVERTER(type, name, code) \
+(type)name : (id)json RCT_DYNAMIC \
{ \
if (!RCT_DEBUG) { \
return code; \
} else { \
@try { \
return code; \
} @catch (__unused NSException * e) { \
RCTLogConvertError(json, @ #type); \
json = nil; \
return code; \
} \
} \
}
/**
* This macro is similar to RCT_CONVERTER, but specifically geared towards
* numeric types. It will handle string input correctly, and provides more
* detailed error reporting if an invalid value is passed in.
*/
#define RCT_NUMBER_CONVERTER(type, getter) \
RCT_CUSTOM_CONVERTER(type, type, [RCT_DEBUG ? [self NSNumber:json] : json getter])
/**
* When using RCT_ENUM_CONVERTER in ObjC, the compiler is OK with us returning
* the underlying NSInteger/NSUInteger. In ObjC++, this is a type mismatch and
* we need to explicitly cast the return value to expected enum return type.
*/
#ifdef __cplusplus
#define _RCT_CAST(type, expr) static_cast<type>(expr)
#else
#define _RCT_CAST(type, expr) expr
#endif
/**
* This macro is used for creating converters for enum types.
*/
#define RCT_ENUM_CONVERTER(type, values, default, getter) \
+(type)type : (id)json RCT_DYNAMIC \
{ \
static NSDictionary *mapping; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
mapping = values; \
}); \
return _RCT_CAST(type, [RCTConvertEnumValue(#type, mapping, @(default), json) getter]); \
}
/**
* This macro is used for creating converters for enum types for
* multiple enum values combined with | operator
*/
#define RCT_MULTI_ENUM_CONVERTER(type, values, default, getter) \
+(type)type : (id)json RCT_DYNAMIC \
{ \
static NSDictionary *mapping; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
mapping = values; \
}); \
return _RCT_CAST(type, [RCTConvertMultiEnumValue(#type, mapping, @(default), json) getter]); \
}
/**
* This macro is used for creating explicitly-named converter functions
* for typed arrays.
*/
#define RCT_ARRAY_CONVERTER_NAMED(type, name) \
+(NSArray<type *> *)name##Array : (id)json RCT_DYNAMIC \
{ \
return RCTConvertArrayValue(@selector(name:), json); \
}
/**
* This macro is used for creating converter functions for typed arrays.
* RCT_ARRAY_CONVERTER_NAMED may be used when type contains characters
* which are disallowed in selector names.
*/
#define RCT_ARRAY_CONVERTER(type) RCT_ARRAY_CONVERTER_NAMED(type, type)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
/**
* This class provides a collection of conversion functions for mapping
* JSON objects to cxx types. Extensible via categories.
* Convert methods are expected to return cxx objects wrapped in RCTManagedPointer.
*/
@interface RCTCxxConvert : NSObject
@end

View File

@@ -0,0 +1,12 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTCxxConvert.h"
@implementation RCTCxxConvert
@end

View File

@@ -0,0 +1,192 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if __OBJC__
#import <Foundation/Foundation.h>
#endif
/**
* Make global functions usable in C++
*/
#if defined(__cplusplus)
#define RCT_EXTERN extern "C" __attribute__((visibility("default")))
#define RCT_EXTERN_C_BEGIN extern "C" {
#define RCT_EXTERN_C_END }
#else
#define RCT_EXTERN extern __attribute__((visibility("default")))
#define RCT_EXTERN_C_BEGIN
#define RCT_EXTERN_C_END
#endif
/**
* The RCT_DEBUG macro can be used to exclude error checking and logging code
* from release builds to improve performance and reduce binary size.
*/
#ifndef RCT_DEBUG
#if DEBUG
#define RCT_DEBUG 1
#else
#define RCT_DEBUG 0
#endif
#endif
/**
* The RCT_DEV macro can be used to enable or disable development tools
* such as the debug executors, dev menu, red box, etc.
*/
#ifndef RCT_DEV
#if DEBUG
#define RCT_DEV 1
#else
#define RCT_DEV 0
#endif
#endif
/**
* RCT_REMOTE_PROFILE: RCT_PROFILE + RCT_ENABLE_INSPECTOR + enable the
* connectivity functionality to control the profiler remotely, such as via Chrome DevTools or
* Flipper.
*/
#ifndef RCT_REMOTE_PROFILE
#define RCT_REMOTE_PROFILE RCT_DEV
#endif
/**
* Enable the code to support making calls to the underlying sampling profiler mechanism.
*/
#ifndef RCT_PROFILE
#define RCT_PROFILE RCT_REMOTE_PROFILE
#endif
#ifndef RCT_ENABLE_INSPECTOR
#if (RCT_DEV || RCT_REMOTE_PROFILE) && __has_include(<React/RCTInspectorDevServerHelper.h>)
#define RCT_ENABLE_INSPECTOR 1
#else
#define RCT_ENABLE_INSPECTOR 0
#endif
#endif
/**
* Sanity check that these compile-time flags are compatible. RCT_REMOTE_PROFILE requires RCT_PROFILE and
* RCT_ENABLE_INSPECTOR
*/
#if RCT_REMOTE_PROFILE
#if !RCT_PROFILE
#error "RCT_PROFILE needs to be set to fulfill RCT_REMOTE_PROFILE"
#endif // RCT_PROFILE
#if !RCT_ENABLE_INSPECTOR
#error "RCT_ENABLE_INSPECTOR needs to be set to fulfill RCT_REMOTE_PROFILE"
#endif // RCT_ENABLE_INSPECTOR
#endif // RCT_REMOTE_PROFILE
/**
* RCT_DEV_MENU can be used to toggle the dev menu separately from RCT_DEV.
* By default though, it will inherit from RCT_DEV.
*/
#ifndef RCT_DEV_MENU
#define RCT_DEV_MENU RCT_DEV
#endif
/**
* Controls for the core packgaer loading functionality
* By default, this inherits from RCT_DEV_MENU but it also gives the capability to
* enable the packager functionality without the rest of the dev tools from RCT_DEV_MENU
*/
#ifndef RCT_ENABLE_LOADING_FROM_PACKAGER
#define RCT_ENABLE_LOADING_FROM_PACKAGER RCT_DEV_MENU
#endif
#ifndef RCT_DEV_SETTINGS_ENABLE_PACKAGER_CONNECTION
#if RCT_DEV && (__has_include("RCTPackagerConnection.h") || __has_include(<React/RCTPackagerConnection.h>))
#define RCT_DEV_SETTINGS_ENABLE_PACKAGER_CONNECTION 1
#else
#define RCT_DEV_SETTINGS_ENABLE_PACKAGER_CONNECTION 0
#endif
#endif
#if RCT_DEV
#define RCT_IF_DEV(...) __VA_ARGS__
#else
#define RCT_IF_DEV(...)
#endif
#ifndef RCT_PROFILE
#define RCT_PROFILE RCT_DEV
#endif
/**
* Add the default Metro packager port number
*/
#ifndef RCT_METRO_PORT
#define RCT_METRO_PORT 8081
#else
// test if RCT_METRO_PORT is empty
#define RCT_METRO_PORT_DO_EXPAND(VAL) VAL##1
#define RCT_METRO_PORT_EXPAND(VAL) RCT_METRO_PORT_DO_EXPAND(VAL)
#if !defined(RCT_METRO_PORT) || (RCT_METRO_PORT_EXPAND(RCT_METRO_PORT) == 1)
// Only here if RCT_METRO_PORT is not defined
// OR RCT_METRO_PORT is the empty string
#undef RCT_METRO_PORT
#define RCT_METRO_PORT 8081
#endif
#endif
/**
* Add the default packager name
*/
#ifndef RCT_PACKAGER_NAME
#define RCT_PACKAGER_NAME @"Metro"
#endif
/**
* By default, only raise an NSAssertion in debug mode
* (custom assert functions will still be called).
*/
#ifndef RCT_NSASSERT
#define RCT_NSASSERT RCT_DEBUG
#endif
/**
* Concat two literals. Supports macro expansions,
* e.g. RCT_CONCAT(foo, __FILE__).
*/
#define RCT_CONCAT2(A, B) A##B
#define RCT_CONCAT(A, B) RCT_CONCAT2(A, B)
/**
* This attribute is used for static analysis.
*/
#if !defined RCT_DYNAMIC
#if __has_attribute(objc_dynamic)
#define RCT_DYNAMIC __attribute__((objc_dynamic))
#else
#define RCT_DYNAMIC
#endif
#endif
/**
* Throw an assertion for unimplemented methods.
*/
#define RCT_NOT_IMPLEMENTED(method) \
_Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wmissing-method-return-type\"") \
_Pragma("clang diagnostic ignored \"-Wunused-parameter\"") \
RCT_EXTERN NSException *_RCTNotImplementedException(SEL, Class); \
method NS_UNAVAILABLE \
{ \
@throw _RCTNotImplementedException(_cmd, [self class]); \
} \
_Pragma("clang diagnostic pop")
/**
* Controls for activating the new architecture without the legacy system.
* Note: this is work in progress.
*/
#ifdef REACT_NATIVE_FORCE_NEW_ARCHITECTURE_EXPERIMENTAL_DO_NOT_USE
#define RCT_ONLY_NEW_ARCHITECTURE_EXPERIMENTAL_DO_NOT_USE 1
#else
#define RCT_ONLY_NEW_ARCHITECTURE_EXPERIMENTAL_DO_NOT_USE 0
#endif

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@protocol RCTBridgeModule;
@class RCTModuleData;
@interface RCTDisplayLink : NSObject
- (instancetype)init;
- (void)invalidate;
- (void)registerModuleForFrameUpdates:(id<RCTBridgeModule>)module withModuleData:(RCTModuleData *)moduleData;
- (void)addToRunLoop:(NSRunLoop *)runLoop;
@end

View File

@@ -0,0 +1,164 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTDisplayLink.h"
#import <Foundation/Foundation.h>
#import <QuartzCore/CADisplayLink.h>
#import "RCTAssert.h"
#import "RCTBridgeModule.h"
#import "RCTFrameUpdate.h"
#import "RCTModuleData.h"
#import "RCTProfile.h"
#define RCTAssertRunLoop() \
RCTAssert(_runLoop == [NSRunLoop currentRunLoop], @"This method must be called on the CADisplayLink run loop")
@implementation RCTDisplayLink {
CADisplayLink *_jsDisplayLink;
NSMutableSet<RCTModuleData *> *_frameUpdateObservers;
NSRunLoop *_runLoop;
}
- (instancetype)init
{
if ((self = [super init])) {
_frameUpdateObservers = [NSMutableSet new];
_jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)];
}
return self;
}
- (void)registerModuleForFrameUpdates:(id<RCTBridgeModule>)module withModuleData:(RCTModuleData *)moduleData
{
if (![moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)] ||
[_frameUpdateObservers containsObject:moduleData]) {
return;
}
[_frameUpdateObservers addObject:moduleData];
// Don't access the module instance via moduleData, as this will cause deadlock
id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)module;
__weak typeof(self) weakSelf = self;
observer.pauseCallback = ^{
typeof(self) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
CFRunLoopRef cfRunLoop = [strongSelf->_runLoop getCFRunLoop];
if (!cfRunLoop) {
return;
}
if ([NSRunLoop currentRunLoop] == strongSelf->_runLoop) {
[weakSelf updateJSDisplayLinkState];
} else {
CFRunLoopPerformBlock(cfRunLoop, kCFRunLoopDefaultMode, ^{
@autoreleasepool {
[weakSelf updateJSDisplayLinkState];
}
});
CFRunLoopWakeUp(cfRunLoop);
}
};
// Assuming we're paused right now, we only need to update the display link's state
// when the new observer is not paused. If it not paused, the observer will immediately
// start receiving updates anyway.
if (![observer isPaused] && _runLoop) {
CFRunLoopPerformBlock([_runLoop getCFRunLoop], kCFRunLoopDefaultMode, ^{
@autoreleasepool {
[self updateJSDisplayLinkState];
}
});
}
}
- (void)addToRunLoop:(NSRunLoop *)runLoop
{
_runLoop = runLoop;
[_jsDisplayLink addToRunLoop:runLoop forMode:NSRunLoopCommonModes];
}
- (void)dealloc
{
[self invalidate];
}
- (void)invalidate
{
// ensure observer callbacks do not hold a reference to weak self via pauseCallback
for (RCTModuleData *moduleData in _frameUpdateObservers) {
id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)moduleData.instance;
[observer setPauseCallback:nil];
}
[_frameUpdateObservers removeAllObjects]; // just to be explicit
[_jsDisplayLink invalidate];
}
- (void)dispatchBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue
{
if (queue == RCTJSThread) {
block();
} else if (queue) {
dispatch_async(queue, block);
}
}
- (void)_jsThreadUpdate:(CADisplayLink *)displayLink
{
RCTAssertRunLoop();
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTDisplayLink _jsThreadUpdate:]", nil);
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
for (RCTModuleData *moduleData in _frameUpdateObservers) {
id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)moduleData.instance;
if (!observer.paused) {
if (moduleData.methodQueue) {
RCTProfileBeginFlowEvent();
[self
dispatchBlock:^{
RCTProfileEndFlowEvent();
[observer didUpdateFrame:frameUpdate];
}
queue:moduleData.methodQueue];
} else {
[observer didUpdateFrame:frameUpdate];
}
}
}
[self updateJSDisplayLinkState];
RCTProfileImmediateEvent(RCTProfileTagAlways, @"JS Thread Tick", displayLink.timestamp, 'g');
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"objc_call");
}
- (void)updateJSDisplayLinkState
{
RCTAssertRunLoop();
BOOL pauseDisplayLink = YES;
for (RCTModuleData *moduleData in _frameUpdateObservers) {
id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)moduleData.instance;
if (!observer.paused) {
pauseDisplayLink = NO;
break;
}
}
_jsDisplayLink.paused = pauseDisplayLink;
}
@end

View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@class RCTErrorInfo;
/**
* Provides an interface to customize React Native error messages and stack
* traces from exceptions.
*/
@protocol RCTErrorCustomizer <NSObject>
/**
* Customizes the given error, returning the passed info argument if no
* customization is required.
*/
- (nonnull RCTErrorInfo *)customizeErrorInfo:(nonnull RCTErrorInfo *)info;
@end

View File

@@ -0,0 +1,21 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@class RCTJSStackFrame;
/**
* An ObjC wrapper for React Native errors.
*/
@interface RCTErrorInfo : NSObject
@property (nonatomic, copy, readonly) NSString *errorMessage;
@property (nonatomic, copy, readonly) NSArray<RCTJSStackFrame *> *stack;
- (instancetype)initWithErrorMessage:(NSString *)errorMessage stack:(NSArray<RCTJSStackFrame *> *)stack;
@end

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTErrorInfo.h"
#import "RCTJSStackFrame.h"
@implementation RCTErrorInfo
- (instancetype)initWithErrorMessage:(NSString *)errorMessage stack:(NSArray<RCTJSStackFrame *> *)stack
{
self = [super init];
if (self) {
_errorMessage = [errorMessage copy];
_stack = [stack copy];
}
return self;
}
@end

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTEventDispatcherProtocol.h"
const NSInteger RCTTextUpdateLagWarningThreshold = 3;
NSString *RCTNormalizeInputEventName(NSString *eventName)
{
if ([eventName hasPrefix:@"on"]) {
eventName = [eventName stringByReplacingCharactersInRange:(NSRange){0, 2} withString:@"top"];
} else if (![eventName hasPrefix:@"top"]) {
eventName = [[@"top" stringByAppendingString:[eventName substringToIndex:1].uppercaseString]
stringByAppendingString:[eventName substringFromIndex:1]];
}
return eventName;
}
@implementation RCTBridge (RCTEventDispatcher)
- (id<RCTEventDispatcherProtocol>)eventDispatcher
{
return [self moduleForName:@"EventDispatcher" lazilyLoadIfNecessary:YES];
}
@end
@implementation RCTBridgeProxy (RCTEventDispatcher)
- (id<RCTEventDispatcherProtocol>)eventDispatcher
{
return [self moduleForName:@"EventDispatcher" lazilyLoadIfNecessary:YES];
}
@end

View File

@@ -0,0 +1,141 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <React/RCTBridge.h>
#import <React/RCTBridgeProxy.h>
/**
* The threshold at which text inputs will start warning that the JS thread
* has fallen behind (resulting in poor input performance, missed keys, etc.)
*/
RCT_EXTERN const NSInteger RCTTextUpdateLagWarningThreshold;
/**
* Takes an input event name and normalizes it to the form that is required
* by the events system (currently that means starting with the "top" prefix,
* but that's an implementation detail that may change in future).
*/
RCT_EXTERN NSString *RCTNormalizeInputEventName(NSString *eventName);
typedef NS_ENUM(NSInteger, RCTTextEventType) {
RCTTextEventTypeFocus,
RCTTextEventTypeBlur,
RCTTextEventTypeChange,
RCTTextEventTypeSubmit,
RCTTextEventTypeEnd,
RCTTextEventTypeKeyPress
};
@protocol RCTEvent <NSObject>
@required
@property (nonatomic, strong, readonly) NSNumber *viewTag;
@property (nonatomic, copy, readonly) NSString *eventName;
- (BOOL)canCoalesce;
/** used directly for doing a JS call */
+ (NSString *)moduleDotMethod;
/** must contain only JSON compatible values */
- (NSArray *)arguments;
@optional
/**
* Coalescing related methods must only be implemented if canCoalesce
* returns YES.
*/
@property (nonatomic, assign, readonly) uint16_t coalescingKey;
- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent;
@end
/**
* This protocol allows observing events dispatched by RCTEventDispatcher.
*/
@protocol RCTEventDispatcherObserver <NSObject>
/**
* Called before dispatching an event, on the same thread the event was
* dispatched from.
*/
- (void)eventDispatcherWillDispatchEvent:(id<RCTEvent>)event;
@end
@protocol RCTJSDispatcherModule
@property (nonatomic, copy) void (^dispatchToJSThread)(dispatch_block_t block);
@end
/**
* This class wraps the -[RCTBridge enqueueJSCall:args:] method, and
* provides some convenience methods for generating event calls.
*/
@protocol RCTEventDispatcherProtocol <RCTBridgeModule, RCTJSDispatcherModule>
- (void)sendViewEventWithName:(NSString *)name reactTag:(NSNumber *)reactTag;
/**
* Deprecated, do not use.
*/
- (void)sendAppEventWithName:(NSString *)name body:(id)body __deprecated_msg("Subclass RCTEventEmitter instead");
/**
* Deprecated, do not use.
*/
- (void)sendDeviceEventWithName:(NSString *)name body:(id)body __deprecated_msg("Subclass RCTEventEmitter instead");
/**
* Send a text input/focus event. For internal use only.
*/
- (void)sendTextEventWithType:(RCTTextEventType)type
reactTag:(NSNumber *)reactTag
text:(NSString *)text
key:(NSString *)key
eventCount:(NSInteger)eventCount;
/**
* Notify Observers of event
*/
- (void)notifyObserversOfEvent:(id<RCTEvent>)event;
/**
* Send a pre-prepared event object.
*
* Events are sent to JS as soon as the thread is free to process them.
* If an event can be coalesced and there is another compatible event waiting, the coalescing will happen immediately.
*/
- (void)sendEvent:(id<RCTEvent>)event;
/**
* Add an event dispatcher observer.
*/
- (void)addDispatchObserver:(id<RCTEventDispatcherObserver>)observer;
/**
* Remove an event dispatcher observer.
*/
- (void)removeDispatchObserver:(id<RCTEventDispatcherObserver>)observer;
@end
@interface RCTBridge (RCTEventDispatcher)
- (id<RCTEventDispatcherProtocol>)eventDispatcher;
@end
@interface RCTBridgeProxy (RCTEventDispatcher)
- (id<RCTEventDispatcherProtocol>)eventDispatcher;
@end

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@class CADisplayLink;
/**
* Interface containing the information about the last screen refresh.
*/
@interface RCTFrameUpdate : NSObject
/**
* Timestamp for the actual screen refresh
*/
@property (nonatomic, readonly) NSTimeInterval timestamp;
/**
* Time since the last frame update ( >= 16.6ms )
*/
@property (nonatomic, readonly) NSTimeInterval deltaTime;
- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink NS_DESIGNATED_INITIALIZER;
@end
/**
* Protocol that must be implemented for subscribing to display refreshes (DisplayLink updates)
*/
@protocol RCTFrameUpdateObserver <NSObject>
/**
* Method called on every screen refresh (if paused != YES)
*/
- (void)didUpdateFrame:(RCTFrameUpdate *)update;
/**
* Synthesize and set to true to pause the calls to -[didUpdateFrame:]
*/
@property (nonatomic, readonly, getter=isPaused) BOOL paused;
/**
* Callback for pause/resume observer.
* Observer should call it when paused property is changed.
*/
@property (nonatomic, copy) dispatch_block_t pauseCallback;
@end

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <QuartzCore/CADisplayLink.h>
#import "RCTFrameUpdate.h"
#import "RCTUtils.h"
@implementation RCTFrameUpdate
RCT_NOT_IMPLEMENTED(-(instancetype)init)
- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink
{
if ((self = [super init])) {
_timestamp = displayLink.timestamp;
_deltaTime = displayLink.duration;
}
return self;
}
@end

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTConvert.h>
/**
* Object containing an image URL and associated metadata.
*/
@interface RCTImageSource : NSObject
@property (nonatomic, copy, readonly) NSURLRequest *request;
@property (nonatomic, assign, readonly) CGSize size;
@property (nonatomic, assign, readonly) CGFloat scale;
/**
* Create a new image source object.
* Pass a size of CGSizeZero if you do not know or wish to specify the image
* size. Pass a scale of zero if you do not know or wish to specify the scale.
*/
- (instancetype)initWithURLRequest:(NSURLRequest *)request size:(CGSize)size scale:(CGFloat)scale;
/**
* Create a copy of the image source with the specified size and scale.
*/
- (instancetype)imageSourceWithSize:(CGSize)size scale:(CGFloat)scale;
@end
@interface RCTConvert (ImageSource)
+ (RCTImageSource *)RCTImageSource:(id)json;
+ (NSArray<RCTImageSource *> *)RCTImageSourceArray:(id)json;
@end

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTImageSource.h"
#import "RCTUtils.h"
@interface RCTImageSource ()
@property (nonatomic, assign) BOOL packagerAsset;
@end
@implementation RCTImageSource
- (instancetype)initWithURLRequest:(NSURLRequest *)request size:(CGSize)size scale:(CGFloat)scale
{
if ((self = [super init])) {
_request = [request copy];
_size = size;
_scale = scale;
}
return self;
}
- (instancetype)imageSourceWithSize:(CGSize)size scale:(CGFloat)scale
{
RCTImageSource *imageSource = [[RCTImageSource alloc] initWithURLRequest:_request size:size scale:scale];
imageSource.packagerAsset = _packagerAsset;
return imageSource;
}
- (BOOL)isEqual:(RCTImageSource *)object
{
if (![object isKindOfClass:[RCTImageSource class]]) {
return NO;
}
return [_request isEqual:object.request] && _scale == object.scale &&
(CGSizeEqualToSize(_size, object.size) || CGSizeEqualToSize(object.size, CGSizeZero));
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<RCTImageSource: %p URL=%@, size=%@, scale=%0.f>",
self,
_request.URL,
NSStringFromCGSize(_size),
_scale];
}
@end
@implementation RCTConvert (ImageSource)
+ (RCTImageSource *)RCTImageSource:(id)json
{
if (!json) {
return nil;
}
NSURLRequest *request;
CGSize size = CGSizeZero;
CGFloat scale = 1.0;
BOOL packagerAsset = NO;
if ([json isKindOfClass:[NSDictionary class]]) {
if (!(request = [self NSURLRequest:json])) {
return nil;
}
size = [self CGSize:json];
scale = [self CGFloat:json[@"scale"]] ?: [self BOOL:json[@"deprecated"]] ? 0.0 : 1.0;
packagerAsset = [self BOOL:json[@"__packager_asset"]];
} else if ([json isKindOfClass:[NSString class]]) {
request = [self NSURLRequest:json];
} else {
RCTLogConvertError(json, @"an image. Did you forget to call resolveAssetSource() on the JS side?");
return nil;
}
RCTImageSource *imageSource = [[RCTImageSource alloc] initWithURLRequest:request size:size scale:scale];
imageSource.packagerAsset = packagerAsset;
return imageSource;
}
RCT_ARRAY_CONVERTER(RCTImageSource)
@end

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@protocol RCTInitializing <NSObject>
- (void)initialize;
@end

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@protocol RCTInvalidating <NSObject>
- (void)invalidate;
@end

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@interface RCTJSStackFrame : NSObject
@property (nonatomic, copy, readonly) NSString *methodName;
@property (nonatomic, copy, readonly) NSString *file;
@property (nonatomic, readonly) NSInteger lineNumber;
@property (nonatomic, readonly) NSInteger column;
@property (nonatomic, readonly) BOOL collapse;
- (instancetype)initWithMethodName:(NSString *)methodName
file:(NSString *)file
lineNumber:(NSInteger)lineNumber
column:(NSInteger)column
collapse:(BOOL)collapse;
- (NSDictionary *)toDictionary;
+ (instancetype)stackFrameWithLine:(NSString *)line;
+ (instancetype)stackFrameWithDictionary:(NSDictionary *)dict;
+ (NSArray<RCTJSStackFrame *> *)stackFramesWithLines:(NSString *)lines;
+ (NSArray<RCTJSStackFrame *> *)stackFramesWithDictionaries:(NSArray<NSDictionary *> *)dicts;
@end

View File

@@ -0,0 +1,150 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTJSStackFrame.h"
#import "RCTLog.h"
#import "RCTUtils.h"
/**
* The RegEx used to parse Error.stack.
*
* JavaScriptCore has the following format:
*
* Exception: Error: argh
* func1@/path/to/file.js:2:18
* func2@/path/to/file.js:6:8
* eval@[native code]
* global code@/path/to/file.js:13:5
*
* Another supported format:
*
* Error: argh
* at func1 (/path/to/file.js:2:18)
* at func2 (/path/to/file.js:6:8)
* at eval (native)
* at global (/path/to/file.js:13:5)
*/
static NSRegularExpression *RCTJSStackFrameRegex(void)
{
static dispatch_once_t onceToken;
static NSRegularExpression *_regex;
dispatch_once(&onceToken, ^{
NSString *pattern =
@"\\s*(?:at)?\\s*" // Skip leading "at" and whitespace, noncapturing
@"(.+?)" // Capture the function name (group 1)
@"\\s*[@(]" // Skip whitespace, then @ or (
@"(.*):" // Capture the file name (group 2), then colon
@"(\\d+):(\\d+)" // Line and column number (groups 3 and 4)
@"\\)?$" // Optional closing paren and EOL
;
NSError *regexError;
_regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&regexError];
if (regexError) {
RCTLogError(@"Failed to build regex: %@", [regexError localizedDescription]);
}
});
return _regex;
}
@implementation RCTJSStackFrame
- (instancetype)initWithMethodName:(NSString *)methodName
file:(NSString *)file
lineNumber:(NSInteger)lineNumber
column:(NSInteger)column
collapse:(BOOL)collapse
{
if (self = [super init]) {
_methodName = methodName;
_file = file;
_lineNumber = lineNumber;
_column = column;
_collapse = collapse;
}
return self;
}
- (NSDictionary *)toDictionary
{
return @{
@"methodName" : RCTNullIfNil(self.methodName),
@"file" : RCTNullIfNil(self.file),
@"lineNumber" : @(self.lineNumber),
@"column" : @(self.column),
@"collapse" : @(self.collapse)
};
}
+ (instancetype)stackFrameWithLine:(NSString *)line
{
NSTextCheckingResult *match = [RCTJSStackFrameRegex() firstMatchInString:line
options:0
range:NSMakeRange(0, line.length)];
if (!match) {
return nil;
}
// methodName may not be present for e.g. anonymous functions
const NSRange methodNameRange = [match rangeAtIndex:1];
NSString *methodName = methodNameRange.location == NSNotFound ? nil : [line substringWithRange:methodNameRange];
NSString *file = [line substringWithRange:[match rangeAtIndex:2]];
NSString *lineNumber = [line substringWithRange:[match rangeAtIndex:3]];
NSString *column = [line substringWithRange:[match rangeAtIndex:4]];
return [[self alloc] initWithMethodName:methodName
file:file
lineNumber:[lineNumber integerValue]
column:[column integerValue]
collapse:NO];
}
+ (instancetype)stackFrameWithDictionary:(NSDictionary *)dict
{
return [[self alloc] initWithMethodName:RCTNilIfNull(dict[@"methodName"])
file:dict[@"file"]
lineNumber:[RCTNilIfNull(dict[@"lineNumber"]) integerValue]
column:[RCTNilIfNull(dict[@"column"]) integerValue]
collapse:[RCTNilIfNull(dict[@"collapse"]) boolValue]];
}
+ (NSArray<RCTJSStackFrame *> *)stackFramesWithLines:(NSString *)lines
{
NSMutableArray *stack = [NSMutableArray new];
for (NSString *line in [lines componentsSeparatedByString:@"\n"]) {
RCTJSStackFrame *frame = [self stackFrameWithLine:line];
if (frame) {
[stack addObject:frame];
}
}
return stack;
}
+ (NSArray<RCTJSStackFrame *> *)stackFramesWithDictionaries:(NSArray<NSDictionary *> *)dicts
{
NSMutableArray *stack = [NSMutableArray new];
for (NSDictionary *dict in dicts) {
RCTJSStackFrame *frame = [self stackFrameWithDictionary:dict];
if (frame) {
[stack addObject:frame];
}
}
return stack;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p method name: %@; file name: %@; line: %ld; column: %ld>",
self.class,
self,
self.methodName,
self.file,
(long)self.lineNumber,
(long)self.column];
}
@end

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <React/RCTDefines.h>
/**
* This constant can be returned from +methodQueue to force module
* methods to be called on the JavaScript thread. This can have serious
* implications for performance, so only use this if you're sure it's what
* you need.
*
* NOTE: RCTJSThread is not a real libdispatch queue
*/
RCT_EXTERN dispatch_queue_t RCTJSThread;
/**
* Initializes the RCTJSThread constant.
* Exported because the bridgeless initialization layer needs to initialize
* RCTJSThread. In bridgeless mode, RCTBridge isn't accessed, and RCTJSThread
* therefore isn't initialized.
*/
RCT_EXTERN void _RCTInitializeJSThreadConstantInternal(void);

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTJSThread.h"
dispatch_queue_t RCTJSThread;
void _RCTInitializeJSThreadConstantInternal(void)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Set up JS thread
RCTJSThread = (id)kCFNull;
});
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <objc/runtime.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTInvalidating.h>
typedef void (^RCTJavaScriptCompleteBlock)(NSError *error);
typedef void (^RCTJavaScriptCallback)(id result, NSError *error);
/**
* Abstracts away a JavaScript execution context - we may be running code in a
* web view (for debugging purposes), or may be running code in a `JSContext`.
*/
@protocol RCTJavaScriptExecutor <RCTInvalidating, RCTBridgeModule>
/**
* Used to set up the executor after the bridge has been fully initialized.
* Do any expensive setup in this method instead of `-init`.
*/
- (void)setUp;
/**
* Whether the executor has been invalidated
*/
@property (nonatomic, readonly, getter=isValid) BOOL valid;
/**
* Executes BatchedBridge.flushedQueue on JS thread and calls the given callback
* with JSValue, containing the next queue, and JSContext.
*/
- (void)flushedQueue:(RCTJavaScriptCallback)onComplete;
/**
* Executes BatchedBridge.callFunctionReturnFlushedQueue with the module name,
* method name and optional additional arguments on the JS thread and calls the
* given callback with JSValue, containing the next queue, and JSContext.
*/
- (void)callFunctionOnModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args
callback:(RCTJavaScriptCallback)onComplete;
/**
* Executes BatchedBridge.invokeCallbackAndReturnFlushedQueue with the cbID,
* and optional additional arguments on the JS thread and calls the
* given callback with JSValue, containing the next queue, and JSContext.
*/
- (void)invokeCallbackID:(NSNumber *)cbID arguments:(NSArray *)args callback:(RCTJavaScriptCallback)onComplete;
/**
* Runs an application script, and notifies of the script load being complete via `onComplete`.
*/
- (void)executeApplicationScript:(NSData *)script
sourceURL:(NSURL *)sourceURL
onComplete:(RCTJavaScriptCompleteBlock)onComplete;
- (void)injectJSONText:(NSString *)script
asGlobalObjectNamed:(NSString *)objectName
callback:(RCTJavaScriptCompleteBlock)onComplete;
/**
* Enqueue a block to run in the executors JS thread. Fallback to `dispatch_async`
* on the main queue if the executor doesn't own a thread.
*/
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block;
/**
* Special case for Timers + ContextExecutor - instead of the default
* if jsthread then call else dispatch call on jsthread
* ensure the call is made async on the jsthread
*/
- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block;
@end

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <React/RCTDefines.h>
extern NSString *const RCTJavaScriptLoaderErrorDomain;
NS_ENUM(NSInteger){
RCTJavaScriptLoaderErrorNoScriptURL = 1,
RCTJavaScriptLoaderErrorFailedOpeningFile = 2,
RCTJavaScriptLoaderErrorFailedReadingFile = 3,
RCTJavaScriptLoaderErrorFailedStatingFile = 3,
RCTJavaScriptLoaderErrorURLLoadFailed = 3,
RCTJavaScriptLoaderErrorBCVersion = 4,
RCTJavaScriptLoaderErrorBCNotSupported = 4,
RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously = 1000,
};
NS_ENUM(NSInteger){
RCTSourceFilesChangedCountNotBuiltByBundler = -2,
RCTSourceFilesChangedCountRebuiltFromScratch = -1,
};
@interface RCTLoadingProgress : NSObject
@property (nonatomic, copy) NSString *status;
@property (strong, nonatomic) NSNumber *done;
@property (strong, nonatomic) NSNumber *total;
@end
@interface RCTSource : NSObject
/**
* URL of the source object.
*/
@property (strong, nonatomic, readonly) NSURL *url;
/**
* JS source (or simply the binary header in the case of a RAM bundle).
*/
@property (strong, nonatomic, readonly) NSData *data;
/**
* Length of the entire JS bundle. Note that self.length != self.data.length in the case of certain bundle formats. For
* instance, when using RAM bundles:
*
* - self.data will point to the bundle header
* - self.data.length is the length of the bundle header, i.e. sizeof(facebook::react::BundleHeader)
* - self.length is the length of the entire bundle file (header + contents)
*/
@property (nonatomic, readonly) NSUInteger length;
/**
* Returns number of files changed when building this bundle:
*
* - RCTSourceFilesChangedCountNotBuiltByBundler if the source wasn't built by the bundler (e.g. read from disk)
* - RCTSourceFilesChangedCountRebuiltFromScratch if the source was rebuilt from scratch by the bundler
* - Otherwise, the number of files changed when incrementally rebuilding the source
*/
@property (nonatomic, readonly) NSInteger filesChangedCount;
@end
typedef void (^RCTSourceLoadProgressBlock)(RCTLoadingProgress *progressData);
typedef void (^RCTSourceLoadBlock)(NSError *error, RCTSource *source);
@interface RCTJavaScriptLoader : NSObject
+ (void)loadBundleAtURL:(NSURL *)scriptURL
onProgress:(RCTSourceLoadProgressBlock)onProgress
onComplete:(RCTSourceLoadBlock)onComplete;
/**
* @experimental
* Attempts to synchronously load the script at the given URL. The following two conditions must be met:
* 1. It must be a file URL.
* 2. It must not point to a text/javascript file.
* If the URL does not meet those conditions, this method will return nil and supply an error with the domain
* RCTJavaScriptLoaderErrorDomain and the code RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously.
*/
+ (NSData *)attemptSynchronousLoadOfBundleAtURL:(NSURL *)scriptURL
sourceLength:(int64_t *)sourceLength
error:(NSError **)error;
@end

View File

@@ -0,0 +1,384 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTJavaScriptLoader.h"
#import <sys/stat.h>
#import <cxxreact/JSBundleType.h>
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTMultipartDataTask.h"
#import "RCTPerformanceLogger.h"
#import "RCTUtils.h"
NSString *const RCTJavaScriptLoaderErrorDomain = @"RCTJavaScriptLoaderErrorDomain";
@interface RCTSource () {
@public
NSURL *_url;
NSData *_data;
NSUInteger _length;
NSInteger _filesChangedCount;
}
@end
@implementation RCTSource
static RCTSource *RCTSourceCreate(NSURL *url, NSData *data, int64_t length) NS_RETURNS_RETAINED
{
using facebook::react::ScriptTag;
facebook::react::BundleHeader header;
[data getBytes:&header length:sizeof(header)];
RCTSource *source = [RCTSource new];
source->_url = url;
source->_data = data;
source->_length = length;
source->_filesChangedCount = RCTSourceFilesChangedCountNotBuiltByBundler;
return source;
}
@end
@implementation RCTLoadingProgress
- (NSString *)description
{
NSMutableString *desc = [NSMutableString new];
[desc appendString:_status ?: @"Bundling"];
if ([_total integerValue] > 0 && [_done integerValue] > [_total integerValue]) {
[desc appendFormat:@" %ld%%", (long)100];
} else if ([_total integerValue] > 0) {
[desc appendFormat:@" %ld%%", (long)(100 * [_done integerValue] / [_total integerValue])];
} else {
[desc appendFormat:@" %ld%%", (long)0];
}
[desc appendString:@"\u2026"];
return desc;
}
@end
@implementation RCTJavaScriptLoader
RCT_NOT_IMPLEMENTED(-(instancetype)init)
+ (void)loadBundleAtURL:(NSURL *)scriptURL
onProgress:(RCTSourceLoadProgressBlock)onProgress
onComplete:(RCTSourceLoadBlock)onComplete
{
int64_t sourceLength;
NSError *error;
NSData *data = [self attemptSynchronousLoadOfBundleAtURL:scriptURL sourceLength:&sourceLength error:&error];
if (data) {
onComplete(nil, RCTSourceCreate(scriptURL, data, sourceLength));
return;
}
const BOOL isCannotLoadSyncError = [error.domain isEqualToString:RCTJavaScriptLoaderErrorDomain] &&
error.code == RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously;
if (isCannotLoadSyncError) {
attemptAsynchronousLoadOfBundleAtURL(scriptURL, onProgress, onComplete);
} else {
onComplete(error, nil);
}
}
+ (NSData *)attemptSynchronousLoadOfBundleAtURL:(NSURL *)scriptURL
sourceLength:(int64_t *)sourceLength
error:(NSError **)error
{
NSString *unsanitizedScriptURLString = scriptURL.absoluteString;
// Sanitize the script URL
scriptURL = sanitizeURL(scriptURL);
if (!scriptURL) {
if (error) {
*error = [NSError
errorWithDomain:RCTJavaScriptLoaderErrorDomain
code:RCTJavaScriptLoaderErrorNoScriptURL
userInfo:@{
NSLocalizedDescriptionKey : [NSString
stringWithFormat:@"No script URL provided. Make sure the packager is "
@"running or you have embedded a JS bundle in your application bundle.\n\n"
@"unsanitizedScriptURLString = %@",
unsanitizedScriptURLString]
}];
}
return nil;
}
// Load local script file
if (!scriptURL.fileURL) {
if (error) {
*error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain
code:RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously
userInfo:@{
NSLocalizedDescriptionKey :
[NSString stringWithFormat:@"Cannot load %@ URLs synchronously", scriptURL.scheme]
}];
}
return nil;
}
// Load the first 4 bytes to check if the bundle is regular or RAM ("Random Access Modules" bundle).
// The RAM bundle has a magic number in the 4 first bytes `(0xFB0BD1E5)`.
// The benefit of RAM bundle over a regular bundle is that we can lazily inject
// modules into JSC as they're required.
FILE *bundle = fopen(scriptURL.path.UTF8String, "r");
if (!bundle) {
if (error) {
*error = [NSError
errorWithDomain:RCTJavaScriptLoaderErrorDomain
code:RCTJavaScriptLoaderErrorFailedOpeningFile
userInfo:@{
NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Error opening bundle %@", scriptURL.path]
}];
}
return nil;
}
facebook::react::BundleHeader header;
size_t readResult = fread(&header, sizeof(header), 1, bundle);
fclose(bundle);
if (readResult != 1) {
if (error) {
*error = [NSError
errorWithDomain:RCTJavaScriptLoaderErrorDomain
code:RCTJavaScriptLoaderErrorFailedReadingFile
userInfo:@{
NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Error reading bundle %@", scriptURL.path]
}];
}
return nil;
}
facebook::react::ScriptTag tag = facebook::react::parseTypeFromHeader(header);
switch (tag) {
case facebook::react::ScriptTag::RAMBundle:
break;
case facebook::react::ScriptTag::String: {
#if RCT_ENABLE_INSPECTOR
NSData *source = [NSData dataWithContentsOfFile:scriptURL.path options:NSDataReadingMappedIfSafe error:error];
if (sourceLength && source != nil) {
*sourceLength = source.length;
}
return source;
#else
if (error) {
*error =
[NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain
code:RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously
userInfo:@{NSLocalizedDescriptionKey : @"Cannot load text/javascript files synchronously"}];
}
return nil;
#endif
}
}
struct stat statInfo;
if (stat(scriptURL.path.UTF8String, &statInfo) != 0) {
if (error) {
*error = [NSError
errorWithDomain:RCTJavaScriptLoaderErrorDomain
code:RCTJavaScriptLoaderErrorFailedStatingFile
userInfo:@{
NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Error stating bundle %@", scriptURL.path]
}];
}
return nil;
}
if (sourceLength) {
*sourceLength = statInfo.st_size;
}
return [NSData dataWithBytes:&header length:sizeof(header)];
}
static void parseHeaders(NSDictionary *headers, RCTSource *source)
{
source->_filesChangedCount = [headers[@"X-Metro-Files-Changed-Count"] integerValue];
}
static void attemptAsynchronousLoadOfBundleAtURL(
NSURL *scriptURL,
RCTSourceLoadProgressBlock onProgress,
RCTSourceLoadBlock onComplete)
{
scriptURL = sanitizeURL(scriptURL);
if (scriptURL.fileURL) {
// Reading in a large bundle can be slow. Dispatch to the background queue to do it.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error = nil;
NSData *source = [NSData dataWithContentsOfFile:scriptURL.path options:NSDataReadingMappedIfSafe error:&error];
onComplete(error, RCTSourceCreate(scriptURL, source, source.length));
});
return;
}
RCTMultipartDataTask *task = [[RCTMultipartDataTask alloc] initWithURL:scriptURL
partHandler:^(NSInteger statusCode, NSDictionary *headers, NSData *data, NSError *error, BOOL done) {
if (!done) {
if (onProgress) {
onProgress(progressEventFromData(data));
}
return;
}
// Handle general request errors
if (error) {
if ([error.domain isEqualToString:NSURLErrorDomain]) {
error = [NSError
errorWithDomain:RCTJavaScriptLoaderErrorDomain
code:RCTJavaScriptLoaderErrorURLLoadFailed
userInfo:@{
NSLocalizedDescriptionKey :
[@"Could not connect to development server.\n\n"
"Ensure the following:\n"
"- Node server is running and available on the same network - run 'npm start' from react-native root\n"
"- Node server URL is correctly set in AppDelegate\n"
"- WiFi is enabled and connected to the same network as the Node Server\n\n"
"URL: " stringByAppendingString:scriptURL.absoluteString],
NSLocalizedFailureReasonErrorKey : error.localizedDescription,
NSUnderlyingErrorKey : error,
}];
}
onComplete(error, nil);
return;
}
// For multipart responses packager sets X-Http-Status header in case HTTP status code
// is different from 200 OK
NSString *statusCodeHeader = headers[@"X-Http-Status"];
if (statusCodeHeader) {
statusCode = [statusCodeHeader integerValue];
}
if (statusCode != 200) {
error =
[NSError errorWithDomain:@"JSServer"
code:statusCode
userInfo:userInfoForRawResponse([[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding])];
onComplete(error, nil);
return;
}
// Validate that the packager actually returned javascript.
NSString *contentType = headers[@"Content-Type"];
NSString *mimeType = [[contentType componentsSeparatedByString:@";"] firstObject];
if (![mimeType isEqualToString:@"application/javascript"] && ![mimeType isEqualToString:@"text/javascript"]) {
NSString *description;
if ([mimeType isEqualToString:@"application/json"]) {
NSError *parseError;
NSDictionary *jsonError = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
if (!parseError && [jsonError isKindOfClass:[NSDictionary class]] &&
[[jsonError objectForKey:@"message"] isKindOfClass:[NSString class]] &&
[[jsonError objectForKey:@"message"] length]) {
description = [jsonError objectForKey:@"message"];
} else {
description = [NSString stringWithFormat:@"Unknown error fetching '%@'.", scriptURL.absoluteString];
}
} else {
description = [NSString
stringWithFormat:
@"Expected MIME-Type to be 'application/javascript' or 'text/javascript', but got '%@'.", mimeType];
}
error = [NSError
errorWithDomain:@"JSServer"
code:NSURLErrorCannotParseResponse
userInfo:@{NSLocalizedDescriptionKey : description, @"headers" : headers, @"data" : data}];
onComplete(error, nil);
return;
}
// Prefer `Content-Location` as the canonical source URL, if given, or fall back to scriptURL.
NSURL *sourceURL = scriptURL;
NSString *contentLocationHeader = headers[@"Content-Location"];
if (contentLocationHeader) {
NSURL *contentLocationURL = [NSURL URLWithString:contentLocationHeader relativeToURL:scriptURL];
if (contentLocationURL) {
sourceURL = contentLocationURL;
}
}
RCTSource *source = RCTSourceCreate(sourceURL, data, data.length);
parseHeaders(headers, source);
onComplete(nil, source);
}
progressHandler:^(NSDictionary *headers, NSNumber *loaded, NSNumber *total) {
// Only care about download progress events for the javascript bundle part.
if ([headers[@"Content-Type"] isEqualToString:@"application/javascript"] ||
[headers[@"Content-Type"] isEqualToString:@"application/x-metro-bytecode-bundle"]) {
onProgress(progressEventFromDownloadProgress(loaded, total));
}
}];
[task startTask];
}
static NSURL *sanitizeURL(NSURL *url)
{
// Why we do this is lost to time. We probably shouldn't; passing a valid URL is the caller's responsibility not ours.
return [RCTConvert NSURL:url.absoluteString];
}
static RCTLoadingProgress *progressEventFromData(NSData *rawData)
{
NSString *text = [[NSString alloc] initWithData:rawData encoding:NSUTF8StringEncoding];
id info = RCTJSONParse(text, nil);
if (!info || ![info isKindOfClass:[NSDictionary class]]) {
return nil;
}
RCTLoadingProgress *progress = [RCTLoadingProgress new];
progress.status = info[@"status"];
progress.done = info[@"done"];
progress.total = info[@"total"];
return progress;
}
static RCTLoadingProgress *progressEventFromDownloadProgress(NSNumber *total, NSNumber *done)
{
RCTLoadingProgress *progress = [RCTLoadingProgress new];
progress.status = @"Downloading";
// Progress values are in bytes transform them to kilobytes for smaller numbers.
progress.done = done != nil ? @([done integerValue] / 1024) : nil;
progress.total = total != nil ? @([total integerValue] / 1024) : nil;
return progress;
}
static NSDictionary *userInfoForRawResponse(NSString *rawText)
{
NSDictionary *parsedResponse = RCTJSONParse(rawText, nil);
if (![parsedResponse isKindOfClass:[NSDictionary class]]) {
return @{NSLocalizedDescriptionKey : rawText};
}
NSArray *errors = parsedResponse[@"errors"];
if (![errors isKindOfClass:[NSArray class]]) {
return @{NSLocalizedDescriptionKey : rawText};
}
NSMutableArray<NSDictionary *> *fakeStack = [NSMutableArray new];
for (NSDictionary *err in errors) {
[fakeStack addObject:@{
@"methodName" : err[@"description"] ?: @"",
@"file" : err[@"filename"] ?: @"",
@"lineNumber" : err[@"lineNumber"] ?: @0
}];
}
return
@{NSLocalizedDescriptionKey : parsedResponse[@"message"] ?: @"No message provided", @"stack" : [fakeStack copy]};
}
@end

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
@interface RCTKeyCommands : NSObject
+ (instancetype)sharedInstance;
/**
* Register a keyboard command.
*/
- (void)registerKeyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags
action:(void (^)(UIKeyCommand *command))block;
/**
* Unregister a keyboard command.
*/
- (void)unregisterKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags;
/**
* Check if a command is registered.
*/
- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags;
@end

View File

@@ -0,0 +1,278 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTKeyCommands.h"
#import <UIKit/UIKit.h>
#import <objc/message.h>
#import <objc/runtime.h>
#import "RCTDefines.h"
#import "RCTUtils.h"
#if RCT_DEV
@interface UIEvent (UIPhysicalKeyboardEvent)
@property (nonatomic) NSString *_modifiedInput;
@property (nonatomic) NSString *_unmodifiedInput;
@property (nonatomic) UIKeyModifierFlags _modifierFlags;
@property (nonatomic) BOOL _isKeyDown;
@property (nonatomic) long _keyCode;
@end
@interface RCTKeyCommand : NSObject <NSCopying>
@property (nonatomic, copy, readonly) NSString *key;
@property (nonatomic, readonly) UIKeyModifierFlags flags;
@property (nonatomic, copy) void (^block)(UIKeyCommand *);
@end
@implementation RCTKeyCommand
- (instancetype)init:(NSString *)key flags:(UIKeyModifierFlags)flags block:(void (^)(UIKeyCommand *))block
{
if ((self = [super init])) {
_key = key;
_flags = flags;
_block = block;
}
return self;
}
RCT_NOT_IMPLEMENTED(-(instancetype)init)
- (id)copyWithZone:(__unused NSZone *)zone
{
return self;
}
- (NSUInteger)hash
{
return _key.hash ^ _flags;
}
- (BOOL)isEqual:(RCTKeyCommand *)object
{
if (![object isKindOfClass:[RCTKeyCommand class]]) {
return NO;
}
return [self matchesInput:object.key flags:object.flags];
}
- (BOOL)matchesInput:(NSString *)input flags:(UIKeyModifierFlags)flags
{
// We consider the key command a match if the modifier flags match
// exactly or is there are no modifier flags. This means that for
// `cmd + r`, we will match both `cmd + r` and `r` but not `opt + r`.
return [_key isEqual:input] && (_flags == flags || flags == 0);
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@:%p input=\"%@\" flags=%lld hasBlock=%@>",
[self class],
self,
_key,
(long long)_flags,
_block ? @"YES" : @"NO"];
}
@end
@interface RCTKeyCommands ()
@property (nonatomic, strong) NSMutableSet<RCTKeyCommand *> *commands;
@end
@implementation RCTKeyCommands
+ (void)initialize
{
SEL originalKeyEventSelector = NSSelectorFromString(@"handleKeyUIEvent:");
SEL swizzledKeyEventSelector = NSSelectorFromString(
[NSString stringWithFormat:@"_rct_swizzle_%x_%@", arc4random(), NSStringFromSelector(originalKeyEventSelector)]);
void (^handleKeyUIEventSwizzleBlock)(UIApplication *, UIEvent *) = ^(UIApplication *slf, UIEvent *event) {
[[[self class] sharedInstance] handleKeyUIEventSwizzle:event];
((void (*)(id, SEL, id))objc_msgSend)(slf, swizzledKeyEventSelector, event);
};
RCTSwapInstanceMethodWithBlock(
[UIApplication class], originalKeyEventSelector, handleKeyUIEventSwizzleBlock, swizzledKeyEventSelector);
}
- (void)handleKeyUIEventSwizzle:(UIEvent *)event
{
NSString *modifiedInput = nil;
UIKeyModifierFlags modifierFlags = 0;
BOOL isKeyDown = NO;
if ([event respondsToSelector:@selector(_modifiedInput)]) {
modifiedInput = [event _modifiedInput];
}
if ([event respondsToSelector:@selector(_modifierFlags)]) {
modifierFlags = [event _modifierFlags];
}
if ([event respondsToSelector:@selector(_isKeyDown)]) {
isKeyDown = [event _isKeyDown];
}
BOOL interactionEnabled = !RCTSharedApplication().isIgnoringInteractionEvents;
BOOL hasFirstResponder = NO;
if (isKeyDown && modifiedInput.length > 0 && interactionEnabled) {
UIResponder *firstResponder = nil;
for (UIWindow *window in [self allWindows]) {
firstResponder = [window valueForKey:@"firstResponder"];
if (firstResponder) {
hasFirstResponder = YES;
break;
}
}
// Ignore key commands (except escape) when there's an active responder
if (!firstResponder) {
[self RCT_handleKeyCommand:modifiedInput flags:modifierFlags];
}
}
};
- (NSArray<UIWindow *> *)allWindows
{
BOOL includeInternalWindows = YES;
BOOL onlyVisibleWindows = NO;
// Obfuscating selector allWindowsIncludingInternalWindows:onlyVisibleWindows:
NSArray<NSString *> *allWindowsComponents =
@[ @"al", @"lWindo", @"wsIncl", @"udingInt", @"ernalWin", @"dows:o", @"nlyVisi", @"bleWin", @"dows:" ];
SEL allWindowsSelector = NSSelectorFromString([allWindowsComponents componentsJoinedByString:@""]);
NSMethodSignature *methodSignature = [[UIWindow class] methodSignatureForSelector:allWindowsSelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.target = [UIWindow class];
invocation.selector = allWindowsSelector;
[invocation setArgument:&includeInternalWindows atIndex:2];
[invocation setArgument:&onlyVisibleWindows atIndex:3];
[invocation invoke];
__unsafe_unretained NSArray<UIWindow *> *windows = nil;
[invocation getReturnValue:&windows];
return windows;
}
- (void)RCT_handleKeyCommand:(NSString *)input flags:(UIKeyModifierFlags)modifierFlags
{
// In Bridgeless mode we might incur in some concurrency issues
// where the React Native instance is invalidated while iterating on the
// list of available commands.
// That will cleanup the set while iterating, which is a not allowed mutation.
// To work around that, we store the commands that we need to execute in a separate
// array, local to this function call, so we don't incur in concurrency issues
NSMutableArray<RCTKeyCommand *> *commandsToExecute = [NSMutableArray new];
for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) {
if ([command matchesInput:input flags:modifierFlags]) {
if (command.block) {
[commandsToExecute addObject:command];
}
}
}
for (RCTKeyCommand *command in commandsToExecute) {
command.block(nil);
}
}
+ (instancetype)sharedInstance
{
static RCTKeyCommands *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [self new];
});
return sharedInstance;
}
- (instancetype)init
{
if ((self = [super init])) {
_commands = [NSMutableSet new];
}
return self;
}
- (void)registerKeyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags
action:(void (^)(UIKeyCommand *))block
{
RCTAssertMainQueue();
RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc] init:input flags:flags block:block];
[_commands removeObject:keyCommand];
[_commands addObject:keyCommand];
}
- (void)unregisterKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
{
RCTAssertMainQueue();
for (RCTKeyCommand *command in _commands.allObjects) {
if ([command matchesInput:input flags:flags]) {
[_commands removeObject:command];
break;
}
}
}
- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
{
RCTAssertMainQueue();
for (RCTKeyCommand *command in _commands) {
if ([command matchesInput:input flags:flags]) {
return YES;
}
}
return NO;
}
@end
#else
@implementation RCTKeyCommands
+ (instancetype)sharedInstance
{
return nil;
}
- (void)registerKeyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags
action:(void (^)(UIKeyCommand *))block
{
}
- (void)unregisterKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
{
}
- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
{
return NO;
}
@end
#endif

View File

@@ -0,0 +1,153 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTAssert.h>
#import <React/RCTDefines.h>
#import <React/RCTUtils.h>
@class RCTModuleRegistry;
@class RCTCallableJSModules;
#ifndef RCTLOG_ENABLED
#define RCTLOG_ENABLED 1
#endif
/**
* Thresholds for logs to display a redbox. You can override these values when debugging
* in order to tweak the default logging behavior.
*/
#ifndef RCTLOG_REDBOX_LEVEL
#define RCTLOG_REDBOX_LEVEL RCTLogLevelError
#endif
/**
* Logging macros. Use these to log information, warnings and errors in your
* own code.
*/
#define RCTLog(...) _RCTLog(RCTLogLevelInfo, __VA_ARGS__)
#define RCTLogTrace(...) _RCTLog(RCTLogLevelTrace, __VA_ARGS__)
#define RCTLogInfo(...) _RCTLog(RCTLogLevelInfo, __VA_ARGS__)
#define RCTLogAdvice(string, ...) RCTLogWarn([@"(ADVICE) " stringByAppendingString:(NSString *)string], __VA_ARGS__)
#define RCTLogWarn(...) _RCTLog(RCTLogLevelWarning, __VA_ARGS__)
#define RCTLogError(...) _RCTLog(RCTLogLevelError, __VA_ARGS__)
/**
* An enum representing the severity of the log message.
*/
typedef NS_ENUM(NSInteger, RCTLogLevel) {
RCTLogLevelTrace = 0,
RCTLogLevelInfo = 1,
RCTLogLevelWarning = 2,
RCTLogLevelError = 3,
RCTLogLevelFatal = 4
};
/**
* An enum representing the source of a log message.
*/
typedef NS_ENUM(NSInteger, RCTLogSource) { RCTLogSourceNative = 1, RCTLogSourceJavaScript = 2 };
/**
* A block signature to be used for custom logging functions. In most cases you
* will want to pass these arguments to the RCTFormatLog function in order to
* generate a string.
*/
typedef void (^RCTLogFunction)(
RCTLogLevel level,
RCTLogSource source,
NSString *fileName,
NSNumber *lineNumber,
NSString *message);
/**
* A method to generate a string from a collection of log data. To omit any
* particular data from the log, just pass nil or zero for the argument.
*/
RCT_EXTERN NSString *
RCTFormatLog(NSDate *timestamp, RCTLogLevel level, NSString *fileName, NSNumber *lineNumber, NSString *message);
/**
* A method to generate a string RCTLogLevel
*/
RCT_EXTERN NSString *RCTFormatLogLevel(RCTLogLevel);
/**
* A method to generate a string from a RCTLogSource
*/
RCT_EXTERN NSString *RCTFormatLogSource(RCTLogSource);
/**
* The default logging function used by RCTLogXX.
*/
extern RCTLogFunction RCTDefaultLogFunction;
/**
* These methods get and set the global logging threshold. This is the level
* below which logs will be ignored. Default is RCTLogLevelInfo for debug and
* RCTLogLevelError for production.
*/
RCT_EXTERN void RCTSetLogThreshold(RCTLogLevel threshold);
RCT_EXTERN RCTLogLevel RCTGetLogThreshold(void);
/**
* These methods get and set the global logging function called by the RCTLogXX
* macros. You can use these to replace the standard behavior with custom log
* functionality.
*/
RCT_EXTERN void RCTSetLogFunction(RCTLogFunction logFunction);
RCT_EXTERN RCTLogFunction RCTGetLogFunction(void);
/**
* This appends additional code to the existing log function, without replacing
* the existing functionality. Useful if you just want to forward logs to an
* extra service without changing the default behavior.
*/
RCT_EXTERN void RCTAddLogFunction(RCTLogFunction logFunction);
/**
* This method temporarily overrides the log function while performing the
* specified block. This is useful for testing purposes (to detect if a given
* function logs something) or to suppress or override logging temporarily.
*/
RCT_EXTERN void RCTPerformBlockWithLogFunction(void (^block)(void), RCTLogFunction logFunction);
/**
* This method adds a conditional prefix to any messages logged within the scope
* of the passed block. This is useful for adding additional context to log
* messages. The block will be performed synchronously on the current thread.
*/
RCT_EXTERN void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix);
/**
* These methods allows static methods in RCTLog to call NativeModules and TurboModules.
* TODO(T112035275) After Bridgeless mixed mode is removed, we can merge these methods
*/
RCT_EXTERN void RCTLogSetBridgeModuleRegistry(RCTModuleRegistry *moduleRegistry);
RCT_EXTERN void RCTLogSetBridgelessModuleRegistry(RCTModuleRegistry *moduleRegistry);
/**
* This methods allows static methods in RCTLog to call JS methods.
* TODO(T112035275) After Bridgeless mixed mode is removed, we can merge these methods
*/
RCT_EXTERN void RCTLogSetBridgeCallableJSModules(RCTCallableJSModules *callableJSModules);
RCT_EXTERN void RCTLogSetBridgelessCallableJSModules(RCTCallableJSModules *callableJSModules);
/**
* Private logging function - ignore this.
*/
#if RCTLOG_ENABLED
#define _RCTLog(lvl, ...) _RCTLogNativeInternal(lvl, __FILE__, __LINE__, __VA_ARGS__)
#else
#define _RCTLog(lvl, ...) \
do { \
} while (0)
#endif
RCT_EXTERN void _RCTLogNativeInternal(RCTLogLevel, const char *, int, NSString *, ...) NS_FORMAT_FUNCTION(4, 5);
RCT_EXTERN void _RCTLogJavaScriptInternal(RCTLogLevel, NSString *);

View File

@@ -0,0 +1,337 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTLog.h"
#include <cxxabi.h>
#import <objc/message.h>
#import <os/log.h>
#import "RCTAssert.h"
#import "RCTBridge+Private.h"
#import "RCTBridge.h"
#import "RCTDefines.h"
#import "RCTRedBoxSetEnabled.h"
#import "RCTUtils.h"
static NSString *const RCTLogFunctionStack = @"RCTLogFunctionStack";
const char *RCTLogLevels[] = {
"trace",
"info",
"warn",
"error",
"fatal",
};
/* os log will discard debug and info messages if they are not needed */
static const RCTLogLevel RCTDefaultLogThreshold = (RCTLogLevel)(RCTLogLevelInfo - 1);
static RCTLogFunction RCTCurrentLogFunction;
static RCTLogLevel RCTCurrentLogThreshold = RCTDefaultLogThreshold;
static __weak RCTModuleRegistry *RCTLogBridgeModuleRegistry;
static __weak RCTModuleRegistry *RCTLogBridgelessModuleRegistry;
static __weak RCTCallableJSModules *RCTLogBridgeCallableJSModules;
static __weak RCTCallableJSModules *RCTLogBridgelessCallableJSModules;
RCTLogLevel RCTGetLogThreshold()
{
return RCTCurrentLogThreshold;
}
void RCTSetLogThreshold(RCTLogLevel threshold)
{
RCTCurrentLogThreshold = threshold;
}
void RCTLogSetBridgeModuleRegistry(RCTModuleRegistry *moduleRegistry)
{
RCTLogBridgeModuleRegistry = moduleRegistry;
}
void RCTLogSetBridgelessModuleRegistry(RCTModuleRegistry *moduleRegistry)
{
RCTLogBridgelessModuleRegistry = moduleRegistry;
}
void RCTLogSetBridgeCallableJSModules(RCTCallableJSModules *callableJSModules)
{
RCTLogBridgeCallableJSModules = callableJSModules;
}
void RCTLogSetBridgelessCallableJSModules(RCTCallableJSModules *callableJSModules)
{
RCTLogBridgelessCallableJSModules = callableJSModules;
}
static os_log_type_t RCTLogTypeForLogLevel(RCTLogLevel logLevel)
{
if (logLevel < RCTLogLevelInfo) {
return OS_LOG_TYPE_DEBUG;
} else if (logLevel <= RCTLogLevelWarning) {
return OS_LOG_TYPE_INFO;
} else {
return OS_LOG_TYPE_ERROR;
}
}
static os_log_t RCTLogForLogSource(RCTLogSource source)
{
switch (source) {
case RCTLogSourceNative: {
static os_log_t nativeLog;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
nativeLog = os_log_create("com.facebook.react.log", "native");
});
return nativeLog;
}
case RCTLogSourceJavaScript: {
static os_log_t javaScriptLog;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
javaScriptLog = os_log_create("com.facebook.react.log", "javascript");
});
return javaScriptLog;
}
}
}
RCTLogFunction RCTDefaultLogFunction =
^(RCTLogLevel level,
RCTLogSource source,
__unused NSString *fileName,
__unused NSNumber *lineNumber,
NSString *message) {
os_log_with_type(RCTLogForLogSource(source), RCTLogTypeForLogLevel(level), "%{public}s", message.UTF8String);
};
void RCTSetLogFunction(RCTLogFunction logFunction)
{
RCTCurrentLogFunction = logFunction;
}
RCTLogFunction RCTGetLogFunction()
{
if (!RCTCurrentLogFunction) {
RCTCurrentLogFunction = RCTDefaultLogFunction;
}
return RCTCurrentLogFunction;
}
void RCTAddLogFunction(RCTLogFunction logFunction)
{
RCTLogFunction existing = RCTGetLogFunction();
if (existing) {
RCTSetLogFunction(
^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
existing(level, source, fileName, lineNumber, message);
logFunction(level, source, fileName, lineNumber, message);
});
} else {
RCTSetLogFunction(logFunction);
}
}
/**
* returns the topmost stacked log function for the current thread, which
* may not be the same as the current value of RCTCurrentLogFunction.
*/
static RCTLogFunction RCTGetLocalLogFunction()
{
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
NSArray<RCTLogFunction> *functionStack = threadDictionary[RCTLogFunctionStack];
RCTLogFunction logFunction = functionStack.lastObject;
if (logFunction) {
return logFunction;
}
return RCTGetLogFunction();
}
void RCTPerformBlockWithLogFunction(void (^block)(void), RCTLogFunction logFunction)
{
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
NSMutableArray<RCTLogFunction> *functionStack = threadDictionary[RCTLogFunctionStack];
if (!functionStack) {
functionStack = [NSMutableArray new];
threadDictionary[RCTLogFunctionStack] = functionStack;
}
[functionStack addObject:logFunction];
block();
[functionStack removeLastObject];
}
void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix)
{
RCTLogFunction logFunction = RCTGetLocalLogFunction();
if (logFunction) {
RCTPerformBlockWithLogFunction(
block, ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
logFunction(level, source, fileName, lineNumber, [prefix stringByAppendingString:message]);
});
}
}
NSString *
RCTFormatLog(NSDate *timestamp, RCTLogLevel level, NSString *fileName, NSNumber *lineNumber, NSString *message)
{
NSMutableString *log = [NSMutableString new];
if (timestamp) {
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
formatter.dateFormat = formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS ";
});
[log appendString:[formatter stringFromDate:timestamp]];
}
if (level) {
[log appendFormat:@"[%s]", RCTLogLevels[level]];
}
[log appendFormat:@"[tid:%@]", RCTCurrentThreadName()];
if (fileName) {
fileName = fileName.lastPathComponent;
if (lineNumber) {
[log appendFormat:@"[%@:%@]", fileName, lineNumber];
} else {
[log appendFormat:@"[%@]", fileName];
}
}
if (message) {
[log appendString:@" "];
[log appendString:message];
}
return log;
}
NSString *RCTFormatLogLevel(RCTLogLevel level)
{
NSDictionary *levelsToString = @{
@(RCTLogLevelTrace) : @"trace",
@(RCTLogLevelInfo) : @"info",
@(RCTLogLevelWarning) : @"warning",
@(RCTLogLevelFatal) : @"fatal",
@(RCTLogLevelError) : @"error"
};
return levelsToString[@(level)];
}
NSString *RCTFormatLogSource(RCTLogSource source)
{
NSDictionary *sourcesToString = @{@(RCTLogSourceNative) : @"native", @(RCTLogSourceJavaScript) : @"js"};
return sourcesToString[@(source)];
}
static NSRegularExpression *nativeStackFrameRegex()
{
static dispatch_once_t onceToken;
static NSRegularExpression *_regex;
dispatch_once(&onceToken, ^{
NSError *regexError;
_regex = [NSRegularExpression regularExpressionWithPattern:@"0x[0-9a-f]+ (.*) \\+ (\\d+)$"
options:0
error:&regexError];
if (regexError) {
RCTLogError(@"Failed to build regex: %@", [regexError localizedDescription]);
}
});
return _regex;
}
void _RCTLogNativeInternal(RCTLogLevel level, const char *fileName, int lineNumber, NSString *format, ...)
{
RCTLogFunction logFunction = RCTGetLocalLogFunction();
BOOL log = RCT_DEBUG || (logFunction != nil);
if (log && level >= RCTGetLogThreshold()) {
// Get message
va_list args;
va_start(args, format);
NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
// Call log function
if (logFunction) {
logFunction(
level, RCTLogSourceNative, fileName ? @(fileName) : nil, lineNumber > 0 ? @(lineNumber) : nil, message);
}
// Log to red box if one is configured.
if (RCTSharedApplication() && RCTRedBoxGetEnabled() && level >= RCTLOG_REDBOX_LEVEL) {
NSArray<NSString *> *stackSymbols = [NSThread callStackSymbols];
NSMutableArray<NSDictionary *> *stack = [NSMutableArray arrayWithCapacity:(stackSymbols.count - 1)];
[stackSymbols enumerateObjectsUsingBlock:^(NSString *frameSymbols, NSUInteger idx, __unused BOOL *stop) {
if (idx == 0) {
// don't include the current frame
return;
}
NSRange range = NSMakeRange(0, frameSymbols.length);
NSTextCheckingResult *match = [nativeStackFrameRegex() firstMatchInString:frameSymbols options:0 range:range];
if (!match) {
return;
}
NSString *methodName = [frameSymbols substringWithRange:[match rangeAtIndex:1]];
char *demangledName = abi::__cxa_demangle([methodName UTF8String], NULL, NULL, NULL);
if (demangledName) {
methodName = @(demangledName);
free(demangledName);
}
if (idx == 1 && fileName) {
NSString *file = [@(fileName) componentsSeparatedByString:@"/"].lastObject;
[stack addObject:@{@"methodName" : methodName, @"file" : file, @"lineNumber" : @(lineNumber)}];
} else {
[stack addObject:@{@"methodName" : methodName}];
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
// red box is thread safe, but by deferring to main queue we avoid a startup
// race condition that causes the module to be accessed before it has loaded
RCTModuleRegistry *moduleRegistry = RCTLogBridgeModuleRegistry ?: RCTLogBridgelessModuleRegistry;
id redbox = [moduleRegistry moduleForName:"RedBox" lazilyLoadIfNecessary:YES];
if (redbox) {
void (*showErrorMessage)(id, SEL, NSString *, NSMutableArray<NSDictionary *> *) =
(__typeof__(showErrorMessage))objc_msgSend;
SEL showErrorMessageSEL = NSSelectorFromString(@"showErrorMessage:withStack:");
if ([redbox respondsToSelector:showErrorMessageSEL]) {
showErrorMessage(redbox, showErrorMessageSEL, message, stack);
}
}
});
}
#if RCT_DEBUG
if (!RCTRunningInTestEnvironment()) {
// Log to JS executor
NSString *levelString = level ? @(RCTLogLevels[level]) : @"info";
RCTCallableJSModules *callableModule = RCTLogBridgeCallableJSModules ?: RCTLogBridgelessCallableJSModules;
[callableModule invokeModule:@"RCTLog" method:@"logIfNoNativeHook" withArgs:@[ levelString, message ]];
}
#endif
}
}
void _RCTLogJavaScriptInternal(RCTLogLevel level, NSString *message)
{
RCTLogFunction logFunction = RCTGetLocalLogFunction();
BOOL log = RCT_DEBUG || (logFunction != nil);
if (log && level >= RCTGetLogThreshold()) {
if (logFunction) {
logFunction(level, RCTLogSourceJavaScript, nil, nil, message);
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#ifdef __cplusplus
#include <memory>
#import <Foundation/Foundation.h>
/**
* Type erased wrapper over any cxx value that can be passed as an argument
* to native method.
*/
@interface RCTManagedPointer : NSObject
@property (nonatomic, readonly) void *voidPointer;
- (instancetype)initWithPointer:(std::shared_ptr<void>)pointer;
@end
namespace facebook::react {
template <typename T, typename P>
RCTManagedPointer *managedPointer(P initializer)
{
auto ptr = std::shared_ptr<void>(new T(initializer));
return [[RCTManagedPointer alloc] initWithPointer:std::move(ptr)];
}
} // namespace facebook::react
#endif

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTManagedPointer.h"
@implementation RCTManagedPointer {
std::shared_ptr<void> _pointer;
}
- (instancetype)initWithPointer:(std::shared_ptr<void>)pointer
{
if (self = [super init]) {
_pointer = std::move(pointer);
}
return self;
}
- (void *)voidPointer
{
return _pointer.get();
}
@end

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTDefines.h>
/* These macros are used to stub C functions. Here's an example:
*
* Helpers.h
* ------
* boolean ReturnsTrueOrFalse(void);
*
* FileToBeTested.h
* ------
* RCT_MOCK_DEF(Testing, ReturnsTrueOrFalse);
* #define ReturnsTrueOrFalse RCT_MOCK_USE(Testing, ReturnsTrueOrFalse)
*
* int FunctionToBeTested(int input) {
* return ReturnsTrueOrFalse() ? input + 1 : input - 1;
* }
*
* Test.h
* -----
* RCT_MOCK_GET(Testing, ReturnsTrueOrFalse);
*
* boolean _ReturnsTrue(void) { return true; }
* boolean _ReturnsFalse(void) { return false; }
*
* void TestFunctionTrue(void) {
* RCT_MOCK_SET(Testing, ReturnsTrueOrFalse, _ReturnsTrue);
* assert(FunctionToBeTested(5) == 6);
* RCT_MOCK_RESET(Testing, ReturnsTrueOrFalse);
* }
*
* void TestFunctionFalse(void) {
* RCT_MOCK_SET(Testing, ReturnsTrueOrFalse, _ReturnsFalse);
* assert(FunctionToBeTested(5) == 4);
* RCT_MOCK_RESET(Testing, ReturnsTrueOrFalse);
* }
*
*/
#ifdef RCT_DEV
#define RCT_MOCK_DEF(context, api) \
__typeof(__typeof(api)*) mockptr_##context##_##api = &api;
#define RCT_MOCK_REF(context, api) \
extern __typeof(__typeof(api)*) mockptr_##context##_##api;
#define RCT_MOCK_SET(context, api, mockapi) \
(mockptr_##context##_##api = &mockapi)
#define RCT_MOCK_RESET(context, api) (mockptr_##context##_##api = &api)
#define RCT_MOCK_USE(context, api) (*mockptr_##context##_##api)
#else
#define RCT_MOCK_DEF(context, api)
#define RCT_MOCK_REF(context, api)
#define RCT_MOCK_SET(context, api, mockapi)
#define RCT_MOCK_RESET(context, api)
#define RCT_MOCK_USE(context, api) api
#endif

View File

@@ -0,0 +1,113 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTInvalidating.h>
@protocol RCTBridgeMethod;
@protocol RCTBridgeModule;
@class RCTBridge;
@class RCTModuleRegistry;
@class RCTViewRegistry;
@class RCTBundleManager;
@class RCTCallableJSModules;
typedef id<RCTBridgeModule> (^RCTBridgeModuleProvider)(void);
@interface RCTModuleData : NSObject <RCTInvalidating>
- (instancetype)initWithModuleClass:(Class)moduleClass
bridge:(RCTBridge *)bridge
moduleRegistry:(RCTModuleRegistry *)moduleRegistry
viewRegistry_DEPRECATED:(RCTViewRegistry *)viewRegistry_DEPRECATED
bundleManager:(RCTBundleManager *)bundleManager
callableJSModules:(RCTCallableJSModules *)callableJSModules;
- (instancetype)initWithModuleClass:(Class)moduleClass
moduleProvider:(RCTBridgeModuleProvider)moduleProvider
bridge:(RCTBridge *)bridge
moduleRegistry:(RCTModuleRegistry *)moduleRegistry
viewRegistry_DEPRECATED:(RCTViewRegistry *)viewRegistry_DEPRECATED
bundleManager:(RCTBundleManager *)bundleManager
callableJSModules:(RCTCallableJSModules *)callableJSModules NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithModuleInstance:(id<RCTBridgeModule>)instance
bridge:(RCTBridge *)bridge
moduleRegistry:(RCTModuleRegistry *)moduleRegistry
viewRegistry_DEPRECATED:(RCTViewRegistry *)viewRegistry_DEPRECATED
bundleManager:(RCTBundleManager *)bundleManager
callableJSModules:(RCTCallableJSModules *)callableJSModules NS_DESIGNATED_INITIALIZER;
/**
* Calls `constantsToExport` on the module and stores the result. Note that
* this will init the module if it has not already been created. This method
* can be called on any thread, but may block the main thread briefly if the
* module implements `constantsToExport`.
*/
- (void)gatherConstants;
@property (nonatomic, strong, readonly) Class moduleClass;
@property (nonatomic, copy, readonly) NSString *name;
/**
* Returns the module methods. Note that this will gather the methods the first
* time it is called and then memoize the results.
*/
@property (nonatomic, copy, readonly) NSArray<id<RCTBridgeMethod>> *methods;
/**
* Returns a map of the module methods. Note that this will gather the methods the first
* time it is called and then memoize the results.
*/
@property (nonatomic, copy, readonly) NSDictionary<NSString *, id<RCTBridgeMethod>> *methodsByName;
/**
* Returns the module's constants, if it exports any
*/
@property (nonatomic, copy, readonly) NSDictionary<NSString *, id> *exportedConstants;
/**
* Returns YES if module instance has already been initialized; NO otherwise.
*/
@property (nonatomic, assign, readonly) BOOL hasInstance;
/**
* Returns YES if module instance must be created on the main thread.
*/
@property (nonatomic, assign) BOOL requiresMainQueueSetup;
/**
* Returns YES if module has constants to export.
*/
@property (nonatomic, assign, readonly) BOOL hasConstantsToExport;
/**
* Returns the current module instance. Note that this will init the instance
* if it has not already been created. To check if the module instance exists
* without causing it to be created, use `hasInstance` instead.
*/
@property (nonatomic, strong, readwrite) id<RCTBridgeModule> instance;
/**
* Returns the module method dispatch queue. Note that this will init both the
* queue and the module itself if they have not already been created.
*/
@property (nonatomic, strong, readonly) dispatch_queue_t methodQueue;
/**
* Whether the receiver has a valid `instance` which implements -batchDidComplete.
*/
@property (nonatomic, assign, readonly) BOOL implementsBatchDidComplete;
/**
* Whether the receiver has a valid `instance` which implements
* -partialBatchDidFlush.
*/
@property (nonatomic, assign, readonly) BOOL implementsPartialBatchDidFlush;
@end

View File

@@ -0,0 +1,488 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTModuleData.h"
#import <objc/runtime.h>
#import <atomic>
#import <mutex>
#import <reactperflogger/BridgeNativeModulePerfLogger.h>
#import "RCTBridge+Private.h"
#import "RCTBridge.h"
#import "RCTBridgeModuleDecorator.h"
#import "RCTConstants.h"
#import "RCTInitializing.h"
#import "RCTLog.h"
#import "RCTModuleMethod.h"
#import "RCTProfile.h"
#import "RCTUtils.h"
using namespace facebook::react;
namespace {
int32_t getUniqueId()
{
static std::atomic<int32_t> counter{0};
return counter++;
}
}
@implementation RCTModuleData {
NSDictionary<NSString *, id> *_constantsToExport;
NSString *_queueName;
__weak RCTBridge *_bridge;
RCTBridgeModuleProvider _moduleProvider;
std::mutex _instanceLock;
BOOL _setupComplete;
RCTModuleRegistry *_moduleRegistry;
RCTViewRegistry *_viewRegistry_DEPRECATED;
RCTBundleManager *_bundleManager;
RCTCallableJSModules *_callableJSModules;
BOOL _isInitialized;
}
@synthesize methods = _methods;
@synthesize methodsByName = _methodsByName;
@synthesize instance = _instance;
@synthesize methodQueue = _methodQueue;
- (void)setUp
{
_implementsBatchDidComplete = [_moduleClass instancesRespondToSelector:@selector(batchDidComplete)];
_implementsPartialBatchDidFlush = [_moduleClass instancesRespondToSelector:@selector(partialBatchDidFlush)];
// If a module overrides `constantsToExport` and doesn't implement `requiresMainQueueSetup`, then we must assume
// that it must be called on the main thread, because it may need to access UIKit.
_hasConstantsToExport = [_moduleClass instancesRespondToSelector:@selector(constantsToExport)];
const BOOL implementsRequireMainQueueSetup = [_moduleClass respondsToSelector:@selector(requiresMainQueueSetup)];
if (implementsRequireMainQueueSetup) {
_requiresMainQueueSetup = [_moduleClass requiresMainQueueSetup];
} else {
static IMP objectInitMethod;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
objectInitMethod = [NSObject instanceMethodForSelector:@selector(init)];
});
// If a module overrides `init` then we must assume that it expects to be
// initialized on the main thread, because it may need to access UIKit.
const BOOL hasCustomInit =
!_instance && [_moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod;
_requiresMainQueueSetup = _hasConstantsToExport || hasCustomInit;
}
}
- (instancetype)initWithModuleClass:(Class)moduleClass
bridge:(RCTBridge *)bridge
moduleRegistry:(RCTModuleRegistry *)moduleRegistry
viewRegistry_DEPRECATED:(RCTViewRegistry *)viewRegistry_DEPRECATED
bundleManager:(RCTBundleManager *)bundleManager
callableJSModules:(RCTCallableJSModules *)callableJSModules
{
return [self initWithModuleClass:moduleClass
moduleProvider:^id<RCTBridgeModule> {
return [moduleClass new];
}
bridge:bridge
moduleRegistry:moduleRegistry
viewRegistry_DEPRECATED:viewRegistry_DEPRECATED
bundleManager:bundleManager
callableJSModules:callableJSModules];
}
- (instancetype)initWithModuleClass:(Class)moduleClass
moduleProvider:(RCTBridgeModuleProvider)moduleProvider
bridge:(RCTBridge *)bridge
moduleRegistry:(RCTModuleRegistry *)moduleRegistry
viewRegistry_DEPRECATED:(RCTViewRegistry *)viewRegistry_DEPRECATED
bundleManager:(RCTBundleManager *)bundleManager
callableJSModules:(RCTCallableJSModules *)callableJSModules
{
if (self = [super init]) {
_bridge = bridge;
_moduleClass = moduleClass;
_moduleProvider = [moduleProvider copy];
_moduleRegistry = moduleRegistry;
_viewRegistry_DEPRECATED = viewRegistry_DEPRECATED;
_bundleManager = bundleManager;
_callableJSModules = callableJSModules;
[self setUp];
}
return self;
}
- (instancetype)initWithModuleInstance:(id<RCTBridgeModule>)instance
bridge:(RCTBridge *)bridge
moduleRegistry:(RCTModuleRegistry *)moduleRegistry
viewRegistry_DEPRECATED:(RCTViewRegistry *)viewRegistry_DEPRECATED
bundleManager:(RCTBundleManager *)bundleManager
callableJSModules:(RCTCallableJSModules *)callableJSModules
{
if (self = [super init]) {
_bridge = bridge;
_instance = instance;
_moduleClass = [instance class];
_moduleRegistry = moduleRegistry;
_viewRegistry_DEPRECATED = viewRegistry_DEPRECATED;
_bundleManager = bundleManager;
_callableJSModules = callableJSModules;
[self setUp];
}
return self;
}
RCT_NOT_IMPLEMENTED(-(instancetype)init);
#pragma mark - private setup methods
- (void)setUpInstanceAndBridge:(int32_t)requestId
{
NSString *moduleName = [self name];
RCT_PROFILE_BEGIN_EVENT(
RCTProfileTagAlways,
@"[RCTModuleData setUpInstanceAndBridge]",
@{@"moduleClass" : NSStringFromClass(_moduleClass)});
{
std::unique_lock<std::mutex> lock(_instanceLock);
BOOL shouldSetup = !_setupComplete && _bridge.valid;
if (shouldSetup) {
if (!_instance) {
if (RCT_DEBUG && _requiresMainQueueSetup) {
RCTAssertMainQueue();
}
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTModuleData setUpInstanceAndBridge] Create module", nil);
BridgeNativeModulePerfLogger::moduleCreateConstructStart([moduleName UTF8String], requestId);
_instance = _moduleProvider ? _moduleProvider() : nil;
BridgeNativeModulePerfLogger::moduleCreateConstructEnd([moduleName UTF8String], requestId);
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
if (!_instance) {
// Module init returned nil, probably because automatic instantiation
// of the module is not supported, and it is supposed to be passed in to
// the bridge constructor. Mark setup complete to avoid doing more work.
_setupComplete = YES;
RCTLogWarn(
@"The module %@ is returning nil from its constructor. You "
"may need to instantiate it yourself and pass it into the "
"bridge.",
_moduleClass);
}
}
if (_instance && RCTProfileIsProfiling()) {
RCTProfileHookInstance(_instance);
}
}
if (_instance) {
BridgeNativeModulePerfLogger::moduleCreateSetUpStart([moduleName UTF8String], requestId);
}
if (shouldSetup) {
// Bridge must be set before methodQueue is set up, as methodQueue
// initialization requires it (View Managers get their queue by calling
// self.bridge.uiManager.methodQueue)
[self setBridgeForInstance];
RCTBridgeModuleDecorator *moduleDecorator =
[[RCTBridgeModuleDecorator alloc] initWithViewRegistry:_viewRegistry_DEPRECATED
moduleRegistry:_moduleRegistry
bundleManager:_bundleManager
callableJSModules:_callableJSModules];
[moduleDecorator attachInteropAPIsToModule:_instance];
}
[self setUpMethodQueue];
if (shouldSetup) {
[self _initializeModule];
}
}
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
// This is called outside of the lock in order to prevent deadlock issues
// because the logic in `finishSetupForInstance` can cause
// `moduleData.instance` to be accessed re-entrantly.
if (_bridge.moduleSetupComplete) {
[self finishSetupForInstance];
} else {
// If we're here, then the module is completely initialized,
// except for what finishSetupForInstance does. When the instance
// method is called after moduleSetupComplete,
// finishSetupForInstance will run. If _requiresMainQueueSetup
// is true, getting the instance will block waiting for the main
// thread, which could take a while if the main thread is busy
// (I've seen 50ms in testing). So we clear that flag, since
// nothing in finishSetupForInstance needs to be run on the main
// thread.
_requiresMainQueueSetup = NO;
}
if (_instance) {
BridgeNativeModulePerfLogger::moduleCreateSetUpEnd([moduleName UTF8String], requestId);
}
}
- (void)setBridgeForInstance
{
if ([_instance respondsToSelector:@selector(bridge)] && _instance.bridge != _bridge) {
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTModuleData setBridgeForInstance]", nil);
@try {
[(id)_instance setValue:_bridge forKey:@"bridge"];
} @catch (NSException *exception) {
RCTLogError(
@"%@ has no setter or ivar for its bridge, which is not "
"permitted. You must either @synthesize the bridge property, "
"or provide your own setter method.",
self.name);
}
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
}
- (void)_initializeModule
{
if (!_isInitialized && [_instance respondsToSelector:@selector(initialize)]) {
_isInitialized = YES;
[(id<RCTInitializing>)_instance initialize];
}
}
- (void)finishSetupForInstance
{
if (!_setupComplete && _instance) {
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTModuleData finishSetupForInstance]", nil);
_setupComplete = YES;
[_bridge registerModuleForFrameUpdates:_instance withModuleData:self];
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTDidInitializeModuleNotification
object:_bridge
userInfo:@{@"module" : _instance, @"bridge" : RCTNullIfNil(_bridge.parentBridge)}];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
}
- (void)setUpMethodQueue
{
if (_instance && !_methodQueue && _bridge.valid) {
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTModuleData setUpMethodQueue]", nil);
BOOL implementsMethodQueue = [_instance respondsToSelector:@selector(methodQueue)];
if (implementsMethodQueue && _bridge.valid) {
_methodQueue = _instance.methodQueue;
}
if (!_methodQueue && _bridge.valid) {
// Create new queue (store queueName, as it isn't retained by dispatch_queue)
_queueName = [NSString stringWithFormat:@"com.facebook.react.%@Queue", self.name];
_methodQueue = dispatch_queue_create(_queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
// assign it to the module
if (implementsMethodQueue) {
@try {
[(id)_instance setValue:_methodQueue forKey:@"methodQueue"];
} @catch (NSException *exception) {
RCTLogError(
@"%@ is returning nil for its methodQueue, which is not "
"permitted. You must either return a pre-initialized "
"queue, or @synthesize the methodQueue to let the bridge "
"create a queue for you.",
self.name);
}
}
}
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
}
- (void)calculateMethods
{
if (_methods && _methodsByName) {
return;
}
NSMutableArray<id<RCTBridgeMethod>> *moduleMethods = [NSMutableArray new];
NSMutableDictionary<NSString *, id<RCTBridgeMethod>> *moduleMethodsByName = [NSMutableDictionary new];
if ([_moduleClass instancesRespondToSelector:@selector(methodsToExport)]) {
[moduleMethods addObjectsFromArray:[self.instance methodsToExport]];
}
unsigned int methodCount;
Class cls = _moduleClass;
while (cls && cls != [NSObject class] && cls != [NSProxy class]) {
Method *methods = class_copyMethodList(object_getClass(cls), &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
Method method = methods[i];
SEL selector = method_getName(method);
if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
IMP imp = method_getImplementation(method);
auto exportedMethod = ((const RCTMethodInfo *(*)(id, SEL))imp)(_moduleClass, selector);
id<RCTBridgeMethod> moduleMethod = [[RCTModuleMethod alloc] initWithExportedMethod:exportedMethod
moduleClass:_moduleClass];
NSString *str = [NSString stringWithUTF8String:moduleMethod.JSMethodName];
[moduleMethodsByName setValue:moduleMethod forKey:str];
[moduleMethods addObject:moduleMethod];
}
}
free(methods);
cls = class_getSuperclass(cls);
}
_methods = [moduleMethods copy];
_methodsByName = [moduleMethodsByName copy];
}
#pragma mark - public getters
- (BOOL)hasInstance
{
std::unique_lock<std::mutex> lock(_instanceLock);
return _instance != nil;
}
- (id<RCTBridgeModule>)instance
{
NSString *moduleName = [self name];
int32_t requestId = getUniqueId();
BridgeNativeModulePerfLogger::moduleCreateStart([moduleName UTF8String], requestId);
if (!_setupComplete) {
RCT_PROFILE_BEGIN_EVENT(
RCTProfileTagAlways, ([NSString stringWithFormat:@"[RCTModuleData instanceForClass:%@]", _moduleClass]), nil);
if (_requiresMainQueueSetup) {
// The chances of deadlock here are low, because module init very rarely
// calls out to other threads, however we can't control when a module might
// get accessed by client code during bridge setup, and a very low risk of
// deadlock is better than a fairly high risk of an assertion being thrown.
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTModuleData instance] main thread setup", nil);
if (!RCTIsMainQueue()) {
RCTLogWarn(@"RCTBridge required dispatch_sync to load %@. This may lead to deadlocks", _moduleClass);
}
RCTUnsafeExecuteOnMainQueueSync(^{
[self setUpInstanceAndBridge:requestId];
});
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
} else {
[self setUpInstanceAndBridge:requestId];
}
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
} else {
BridgeNativeModulePerfLogger::moduleCreateCacheHit([moduleName UTF8String], requestId);
}
if (_instance) {
BridgeNativeModulePerfLogger::moduleCreateEnd([moduleName UTF8String], requestId);
} else {
BridgeNativeModulePerfLogger::moduleCreateFail([moduleName UTF8String], requestId);
}
return _instance;
}
- (NSString *)name
{
return RCTBridgeModuleNameForClass(_moduleClass);
}
- (NSArray<id<RCTBridgeMethod>> *)methods
{
[self calculateMethods];
return _methods;
}
- (NSDictionary<NSString *, id<RCTBridgeMethod>> *)methodsByName
{
[self calculateMethods];
return _methodsByName;
}
- (void)gatherConstants
{
return [self gatherConstantsAndSignalJSRequireEnding:NO];
}
- (void)gatherConstantsAndSignalJSRequireEnding:(BOOL)startMarkers
{
NSString *moduleName = [self name];
if (_hasConstantsToExport && !_constantsToExport) {
RCT_PROFILE_BEGIN_EVENT(
RCTProfileTagAlways, ([NSString stringWithFormat:@"[RCTModuleData gatherConstants] %@", _moduleClass]), nil);
(void)[self instance];
if (startMarkers) {
/**
* Why do we instrument moduleJSRequireEndingStart here?
* - NativeModule requires from JS go through ModuleRegistry::getConfig().
* - ModuleRegistry::getConfig() calls NativeModule::getConstants() first.
* - This delegates to RCTNativeModule::getConstants(), which calls RCTModuleData gatherConstants().
* - Therefore, this is the first statement that executes after the NativeModule is created/initialized in a JS
* require.
*/
BridgeNativeModulePerfLogger::moduleJSRequireEndingStart([moduleName UTF8String]);
}
if (_requiresMainQueueSetup) {
if (!RCTIsMainQueue()) {
RCTLogWarn(@"Required dispatch_sync to load constants for %@. This may lead to deadlocks", _moduleClass);
}
RCTUnsafeExecuteOnMainQueueSync(^{
self->_constantsToExport = [self->_instance constantsToExport] ?: @{};
});
} else {
_constantsToExport = [_instance constantsToExport] ?: @{};
}
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
} else if (startMarkers) {
/**
* If a NativeModule doesn't have constants, it isn't eagerly loaded until its methods are first invoked.
* Therefore, we should immediately start JSRequireEnding
*/
BridgeNativeModulePerfLogger::moduleJSRequireEndingStart([moduleName UTF8String]);
}
}
- (NSDictionary<NSString *, id> *)exportedConstants
{
[self gatherConstantsAndSignalJSRequireEnding:YES];
NSDictionary<NSString *, id> *constants = _constantsToExport;
_constantsToExport = nil; // Not needed anymore
return constants;
}
- (dispatch_queue_t)methodQueue
{
if (_bridge.valid) {
id instance = self.instance;
RCTAssert(_methodQueue != nullptr, @"Module %@ has no methodQueue (instance: %@)", self, instance);
}
return _methodQueue;
}
- (void)invalidate
{
_methodQueue = nil;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; name=\"%@\">", [self class], self, self.name];
}
@end

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTBridgeMethod.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTNullability.h>
@class RCTBridge;
@interface RCTMethodArgument : NSObject
@property (nonatomic, copy, readonly) NSString *type;
@property (nonatomic, readonly) RCTNullability nullability;
@property (nonatomic, readonly) BOOL unused;
@end
@interface RCTModuleMethod : NSObject <RCTBridgeMethod>
@property (nonatomic, readonly) Class moduleClass;
@property (nonatomic, readonly) SEL selector;
- (instancetype)initWithExportedMethod:(const RCTMethodInfo *)exportMethod
moduleClass:(Class)moduleClass NS_DESIGNATED_INITIALIZER;
@end
RCT_EXTERN NSString *RCTParseMethodSignature(const char *input, NSArray<RCTMethodArgument *> **arguments);

View File

@@ -0,0 +1,615 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTModuleMethod.h"
#import <objc/message.h>
#import "RCTAssert.h"
#import "RCTBridge+Private.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTCxxConvert.h"
#import "RCTLog.h"
#import "RCTManagedPointer.h"
#import "RCTParserUtils.h"
#import "RCTProfile.h"
#import "RCTUtils.h"
typedef BOOL (^RCTArgumentBlock)(RCTBridge *, NSUInteger, id);
/**
* Get the converter function for the specified type
*/
static SEL selectorForType(NSString *type)
{
const char *input = type.UTF8String;
return NSSelectorFromString([RCTParseType(&input) stringByAppendingString:@":"]);
}
@implementation RCTMethodArgument
- (instancetype)initWithType:(NSString *)type nullability:(RCTNullability)nullability unused:(BOOL)unused
{
if (self = [super init]) {
_type = [type copy];
_nullability = nullability;
_unused = unused;
}
return self;
}
@end
@implementation RCTModuleMethod {
Class _moduleClass;
const RCTMethodInfo *_methodInfo;
NSString *_JSMethodName;
SEL _selector;
NSInvocation *_invocation;
NSArray<RCTArgumentBlock> *_argumentBlocks;
NSMutableArray *_retainedObjects;
}
static void RCTLogArgumentError(RCTModuleMethod *method, NSUInteger index, id valueOrType, const char *issue)
{
RCTLogError(
@"Argument %tu (%@) of %@.%s %s",
index,
valueOrType,
RCTBridgeModuleNameForClass(method->_moduleClass),
method.JSMethodName,
issue);
}
RCT_NOT_IMPLEMENTED(-(instancetype)init)
RCT_EXTERN_C_BEGIN
// returns YES if the selector ends in a colon (indicating that there is at
// least one argument, and maybe more selector parts) or NO if it doesn't.
static BOOL RCTParseSelectorPart(const char **input, NSMutableString *selector)
{
NSString *selectorPart;
if (RCTParseSelectorIdentifier(input, &selectorPart)) {
[selector appendString:selectorPart];
}
RCTSkipWhitespace(input);
if (RCTReadChar(input, ':')) {
[selector appendString:@":"];
RCTSkipWhitespace(input);
return YES;
}
return NO;
}
static BOOL RCTParseUnused(const char **input)
{
return RCTReadString(input, "__attribute__((unused))") || RCTReadString(input, "__attribute__((__unused__))") ||
RCTReadString(input, "__unused") || RCTReadString(input, "[[maybe_unused]]");
}
static RCTNullability RCTParseNullability(const char **input)
{
if (RCTReadString(input, "nullable")) {
return RCTNullable;
} else if (RCTReadString(input, "nonnull")) {
return RCTNonnullable;
}
return RCTNullabilityUnspecified;
}
static RCTNullability RCTParseNullabilityPostfix(const char **input)
{
if (RCTReadString(input, "_Nullable") || RCTReadString(input, "__nullable")) {
return RCTNullable;
} else if (RCTReadString(input, "_Nonnull") || RCTReadString(input, "__nonnull")) {
return RCTNonnullable;
}
return RCTNullabilityUnspecified;
}
// returns YES if execution is safe to proceed (enqueue callback invocation), NO if callback has already been invoked
#if RCT_DEBUG
static BOOL checkCallbackMultipleInvocations(BOOL *didInvoke)
{
if (*didInvoke) {
RCTFatal(RCTErrorWithMessage(
@"Illegal callback invocation from native module. This callback type only permits a single invocation from native code."));
return NO;
} else {
*didInvoke = YES;
return YES;
}
}
#endif
NSString *RCTParseMethodSignature(const char *input, NSArray<RCTMethodArgument *> **arguments)
{
RCTSkipWhitespace(&input);
NSMutableArray *args;
NSMutableString *selector = [NSMutableString new];
while (RCTParseSelectorPart(&input, selector)) {
if (!args) {
args = [NSMutableArray new];
}
// Parse type
if (RCTReadChar(&input, '(')) {
RCTSkipWhitespace(&input);
// 5 cases that both nullable and __unused exist
// 1: foo:(nullable __unused id)foo 2: foo:(nullable id __unused)foo
// 3: foo:(__unused id _Nullable)foo 4: foo:(id __unused _Nullable)foo
// 5: foo:(id _Nullable __unused)foo
RCTNullability nullability = RCTParseNullability(&input);
RCTSkipWhitespace(&input);
BOOL unused = RCTParseUnused(&input);
RCTSkipWhitespace(&input);
NSString *type = RCTParseType(&input);
RCTSkipWhitespace(&input);
if (nullability == RCTNullabilityUnspecified) {
nullability = RCTParseNullabilityPostfix(&input);
RCTSkipWhitespace(&input);
if (!unused) {
unused = RCTParseUnused(&input);
RCTSkipWhitespace(&input);
if (unused && nullability == RCTNullabilityUnspecified) {
nullability = RCTParseNullabilityPostfix(&input);
RCTSkipWhitespace(&input);
}
}
} else if (!unused) {
unused = RCTParseUnused(&input);
RCTSkipWhitespace(&input);
}
[args addObject:[[RCTMethodArgument alloc] initWithType:type nullability:nullability unused:unused]];
RCTSkipWhitespace(&input);
RCTReadChar(&input, ')');
RCTSkipWhitespace(&input);
} else {
// Type defaults to id if unspecified
[args addObject:[[RCTMethodArgument alloc] initWithType:@"id" nullability:RCTNullable unused:NO]];
}
// Argument name
RCTParseArgumentIdentifier(&input, NULL);
RCTSkipWhitespace(&input);
}
*arguments = [args copy];
return selector;
}
RCT_EXTERN_C_END
- (instancetype)initWithExportedMethod:(const RCTMethodInfo *)exportedMethod moduleClass:(Class)moduleClass
{
if (self = [super init]) {
_moduleClass = moduleClass;
_methodInfo = exportedMethod;
}
return self;
}
- (void)processMethodSignature
{
NSArray<RCTMethodArgument *> *arguments;
_selector = NSSelectorFromString(RCTParseMethodSignature(_methodInfo->objcName, &arguments));
RCTAssert(_selector, @"%s is not a valid selector", _methodInfo->objcName);
// Create method invocation
NSMethodSignature *methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector];
RCTAssert(methodSignature, @"%s is not a recognized Objective-C method.", sel_getName(_selector));
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.selector = _selector;
_invocation = invocation;
NSMutableArray *retainedObjects = [NSMutableArray array];
_retainedObjects = retainedObjects;
// Process arguments
NSUInteger numberOfArguments = methodSignature.numberOfArguments;
NSMutableArray<RCTArgumentBlock> *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
#if RCT_DEBUG
__weak RCTModuleMethod *weakSelf = self;
#endif
#define RCT_RETAINED_ARG_BLOCK(_logic) \
[argumentBlocks addObject:^(__unused __weak RCTBridge * bridge, NSUInteger index, id json) { \
_logic [invocation setArgument:&value atIndex:(index) + 2]; \
if (value) { \
[retainedObjects addObject:value]; \
} \
return YES; \
}]
#define __PRIMITIVE_CASE(_type, _nullable) \
{ \
isNullableType = _nullable; \
_type (*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend; \
[argumentBlocks addObject:^(__unused RCTBridge * bridge, NSUInteger index, id json) { \
_type value = convert([RCTConvert class], selector, json); \
[invocation setArgument:&value atIndex:(index) + 2]; \
return YES; \
}]; \
break; \
}
#define PRIMITIVE_CASE(_type) __PRIMITIVE_CASE(_type, NO)
#define NULLABLE_PRIMITIVE_CASE(_type) __PRIMITIVE_CASE(_type, YES)
// Explicitly copy the block
#define __COPY_BLOCK(block...) \
id value = [block copy]; \
if (value) { \
[retainedObjects addObject:value]; \
}
#if RCT_DEBUG
#define BLOCK_CASE(_block_args, _block) \
RCT_RETAINED_ARG_BLOCK(if (json && ![json isKindOfClass:[NSNumber class]]) { \
RCTLogArgumentError(weakSelf, index, json, "should be a function"); \
return NO; \
} __block BOOL didInvoke = NO; \
__COPY_BLOCK(^_block_args { \
if (checkCallbackMultipleInvocations(&didInvoke)) \
_block \
});)
#else
#define BLOCK_CASE(_block_args, _block) \
RCT_RETAINED_ARG_BLOCK(__COPY_BLOCK(^_block_args{ \
_block});)
#endif
for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *objcType = [methodSignature getArgumentTypeAtIndex:i];
BOOL isNullableType = NO;
RCTMethodArgument *argument = arguments[i - 2];
NSString *typeName = argument.type;
SEL selector = selectorForType(typeName);
if ([RCTConvert respondsToSelector:selector]) {
switch (objcType[0]) {
// Primitives
case _C_CHR:
PRIMITIVE_CASE(char)
case _C_UCHR:
PRIMITIVE_CASE(unsigned char)
case _C_SHT:
PRIMITIVE_CASE(short)
case _C_USHT:
PRIMITIVE_CASE(unsigned short)
case _C_INT:
PRIMITIVE_CASE(int)
case _C_UINT:
PRIMITIVE_CASE(unsigned int)
case _C_LNG:
PRIMITIVE_CASE(long)
case _C_ULNG:
PRIMITIVE_CASE(unsigned long)
case _C_LNG_LNG:
PRIMITIVE_CASE(long long)
case _C_ULNG_LNG:
PRIMITIVE_CASE(unsigned long long)
case _C_FLT:
PRIMITIVE_CASE(float)
case _C_DBL:
PRIMITIVE_CASE(double)
case _C_BOOL:
PRIMITIVE_CASE(BOOL)
case _C_SEL:
NULLABLE_PRIMITIVE_CASE(SEL)
case _C_CHARPTR:
NULLABLE_PRIMITIVE_CASE(const char *)
case _C_PTR:
NULLABLE_PRIMITIVE_CASE(void *)
case _C_ID: {
isNullableType = YES;
id (*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
RCT_RETAINED_ARG_BLOCK(id value = convert([RCTConvert class], selector, json););
break;
}
case _C_STRUCT_B: {
NSMethodSignature *typeSignature = [RCTConvert methodSignatureForSelector:selector];
NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
typeInvocation.selector = selector;
typeInvocation.target = [RCTConvert class];
[argumentBlocks addObject:^(__unused RCTBridge *bridge, NSUInteger index, id json) {
void *returnValue = malloc(typeSignature.methodReturnLength);
if (!returnValue) {
// CWE - 391 : Unchecked error condition
// https://www.cvedetails.com/cwe-details/391/Unchecked-Error-Condition.html
// https://eli.thegreenplace.net/2009/10/30/handling-out-of-memory-conditions-in-c
abort();
}
[typeInvocation setArgument:&json atIndex:2];
[typeInvocation invoke];
[typeInvocation getReturnValue:returnValue];
[invocation setArgument:returnValue atIndex:index + 2];
free(returnValue);
return YES;
}];
break;
}
default: {
static const char *blockType = @encode(__typeof__(^{
}));
if (!strcmp(objcType, blockType)) {
BLOCK_CASE((NSArray * args), { [bridge enqueueCallback:json args:args]; });
} else {
RCTLogError(@"Unsupported argument type '%@' in method %@.", typeName, [self methodName]);
}
}
}
} else if ([typeName isEqualToString:@"RCTResponseSenderBlock"]) {
BLOCK_CASE((NSArray * args), { [bridge enqueueCallback:json args:args]; });
} else if ([typeName isEqualToString:@"RCTResponseErrorBlock"]) {
BLOCK_CASE((NSError * error), { [bridge enqueueCallback:json args:@[ RCTJSErrorFromNSError(error) ]]; });
} else if ([typeName isEqualToString:@"RCTPromiseResolveBlock"]) {
RCTAssert(
i == numberOfArguments - 2,
@"The RCTPromiseResolveBlock must be the second to last parameter in %@",
[self methodName]);
BLOCK_CASE((id result), { [bridge enqueueCallback:json args:result ? @[ result ] : @[]]; });
} else if ([typeName isEqualToString:@"RCTPromiseRejectBlock"]) {
RCTAssert(
i == numberOfArguments - 1, @"The RCTPromiseRejectBlock must be the last parameter in %@", [self methodName]);
BLOCK_CASE((NSString * code, NSString * message, NSError * error), {
NSDictionary *errorJSON = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
[bridge enqueueCallback:json args:@[ errorJSON ]];
});
} else if ([typeName hasPrefix:@"JS::"]) {
NSString *selectorNameForCxxType =
[[typeName stringByReplacingOccurrencesOfString:@"::" withString:@"_"] stringByAppendingString:@":"];
selector = NSSelectorFromString(selectorNameForCxxType);
[argumentBlocks addObject:^(__unused RCTBridge *bridge, NSUInteger index, id json) {
RCTManagedPointer *(*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
RCTManagedPointer *box = convert([RCTCxxConvert class], selector, json);
void *pointer = box.voidPointer;
[invocation setArgument:&pointer atIndex:index + 2];
[retainedObjects addObject:box];
return YES;
}];
} else {
// Unknown argument type
RCTLogError(
@"Unknown argument type '%@' in method %@. Extend RCTConvert to support this type.",
typeName,
[self methodName]);
}
#if RCT_DEBUG
RCTNullability nullability = argument.nullability;
if (!isNullableType) {
if (nullability == RCTNullable) {
RCTLogArgumentError(
weakSelf,
i - 2,
typeName,
"is marked as "
"nullable, but is not a nullable type.");
}
nullability = RCTNonnullable;
}
/**
* Special case - Numbers are not nullable in Android, so we
* don't support this for now. In future we may allow it.
*/
if ([typeName isEqualToString:@"NSNumber"]) {
BOOL unspecified = (nullability == RCTNullabilityUnspecified);
if (!argument.unused && (nullability == RCTNullable || unspecified)) {
RCTLogArgumentError(
weakSelf,
i - 2,
typeName,
[unspecified ? @"has unspecified nullability" : @"is marked as nullable"
stringByAppendingString:@" but React requires that all NSNumber "
"arguments are explicitly marked as `nonnull` to ensure "
"compatibility with Android."]
.UTF8String);
}
nullability = RCTNonnullable;
}
if (nullability == RCTNonnullable) {
RCTArgumentBlock oldBlock = argumentBlocks[i - 2];
argumentBlocks[i - 2] = ^(RCTBridge *bridge, NSUInteger index, id json) {
if (json != nil) {
if (!oldBlock(bridge, index, json)) {
return NO;
}
if (isNullableType) {
// Check converted value wasn't null either, as method probably
// won't gracefully handle a nil value for a nonull argument
void *value;
[invocation getArgument:&value atIndex:index + 2];
if (value == NULL) {
return NO;
}
}
return YES;
}
RCTLogArgumentError(weakSelf, index, typeName, "must not be null");
return NO;
};
}
#endif
}
#if RCT_DEBUG
const char *objcType = _invocation.methodSignature.methodReturnType;
if (_methodInfo->isSync && objcType[0] != _C_ID) {
RCTLogError(
@"Return type of %@.%s should be (id) as the method is \"sync\"",
RCTBridgeModuleNameForClass(_moduleClass),
self.JSMethodName);
}
#endif
_argumentBlocks = argumentBlocks;
}
- (SEL)selector
{
if (_selector == NULL) {
RCT_PROFILE_BEGIN_EVENT(
RCTProfileTagAlways,
@"",
(@{@"module" : NSStringFromClass(_moduleClass), @"method" : @(_methodInfo->objcName)}));
[self processMethodSignature];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
return _selector;
}
- (const char *)JSMethodName
{
NSString *methodName = _JSMethodName;
if (!methodName) {
const char *jsName = _methodInfo->jsName;
if (jsName && strlen(jsName) > 0) {
methodName = @(jsName);
} else {
methodName = @(_methodInfo->objcName);
NSRange colonRange = [methodName rangeOfString:@":"];
if (colonRange.location != NSNotFound) {
methodName = [methodName substringToIndex:colonRange.location];
}
methodName = [methodName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
RCTAssert(
methodName.length,
@"%s is not a valid JS function name, please"
" supply an alternative using RCT_REMAP_METHOD()",
_methodInfo->objcName);
}
_JSMethodName = methodName;
}
return methodName.UTF8String;
}
- (RCTFunctionType)functionType
{
if (strstr(_methodInfo->objcName, "RCTPromise") != NULL) {
RCTAssert(!_methodInfo->isSync, @"Promises cannot be used in sync functions");
return RCTFunctionTypePromise;
} else if (_methodInfo->isSync) {
return RCTFunctionTypeSync;
} else {
return RCTFunctionTypeNormal;
}
}
- (id)invokeWithBridge:(RCTBridge *)bridge module:(id)module arguments:(NSArray *)arguments
{
if (_argumentBlocks == nil) {
[self processMethodSignature];
}
#if RCT_DEBUG
// Sanity check
RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \
%@ on a module of class %@", [self methodName], [module class]);
// Safety check
if (arguments.count != _argumentBlocks.count) {
NSInteger actualCount = arguments.count;
NSInteger expectedCount = _argumentBlocks.count;
// Subtract the implicit Promise resolver and rejecter functions for implementations of async functions
if (self.functionType == RCTFunctionTypePromise) {
actualCount -= 2;
expectedCount -= 2;
}
RCTLogError(
@"%@.%s was called with %lld arguments but expects %lld arguments. "
@"If you haven\'t changed this method yourself, this usually means that "
@"your versions of the native code and JavaScript code are out of sync. "
@"Updating both should make this error go away.",
RCTBridgeModuleNameForClass(_moduleClass),
self.JSMethodName,
(long long)actualCount,
(long long)expectedCount);
return nil;
}
#endif
// Set arguments
NSUInteger index = 0;
for (id json in arguments) {
RCTArgumentBlock block = _argumentBlocks[index];
if (!block(bridge, index, RCTNilIfNull(json))) {
// Invalid argument, abort
RCTLogArgumentError(self, index, json, "could not be processed. Aborting method call.");
return nil;
}
index++;
}
// Invoke method
#ifdef RCT_MAIN_THREAD_WATCH_DOG_THRESHOLD
if (RCTIsMainQueue()) {
CFTimeInterval start = CACurrentMediaTime();
[_invocation invokeWithTarget:module];
CFTimeInterval duration = CACurrentMediaTime() - start;
if (duration > RCT_MAIN_THREAD_WATCH_DOG_THRESHOLD) {
RCTLogWarn(
@"Main Thread Watchdog: Invocation of %@ blocked the main thread for %dms. "
"Consider using background-threaded modules and asynchronous calls "
"to spend less time on the main thread and keep the app's UI responsive.",
[self methodName],
(int)(duration * 1000));
}
} else {
[_invocation invokeWithTarget:module];
}
#else
[_invocation invokeWithTarget:module];
#endif
[_retainedObjects removeAllObjects];
if (_methodInfo->isSync) {
void *returnValue;
[_invocation getReturnValue:&returnValue];
return (__bridge id)returnValue;
}
return nil;
}
- (NSString *)methodName
{
if (!_selector) {
[self processMethodSignature];
}
return [NSString stringWithFormat:@"-[%@ %s]", _moduleClass, sel_getName(_selector)];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; exports %@ as %s(); type: %s>",
[self class],
self,
[self methodName],
self.JSMethodName,
RCTFunctionDescriptorFromType(self.functionType)];
}
@end

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTBridge.h"
#import "RCTTurboModuleRegistry.h"
@class RCTBridgeModule;
@implementation RCTModuleRegistry {
__weak id<RCTTurboModuleRegistry> _turboModuleRegistry;
__weak RCTBridge *_bridge;
}
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
}
- (void)setTurboModuleRegistry:(id<RCTTurboModuleRegistry>)turboModuleRegistry
{
_turboModuleRegistry = turboModuleRegistry;
}
- (id)moduleForName:(const char *)moduleName
{
return [self moduleForName:moduleName lazilyLoadIfNecessary:YES];
}
- (id)moduleForName:(const char *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyLoad
{
id<RCTBridgeModule> module = nil;
RCTBridge *bridge = _bridge;
if (bridge) {
module = [bridge moduleForName:[NSString stringWithUTF8String:moduleName] lazilyLoadIfNecessary:lazilyLoad];
}
id<RCTTurboModuleRegistry> turboModuleRegistry = _turboModuleRegistry;
if (module == nil && turboModuleRegistry && (lazilyLoad || [turboModuleRegistry moduleIsInitialized:moduleName])) {
module = [turboModuleRegistry moduleForName:moduleName];
}
return module;
}
- (BOOL)moduleIsInitialized:(Class)moduleClass
{
RCTBridge *bridge = _bridge;
if (bridge) {
return [bridge moduleIsInitialized:moduleClass];
}
id<RCTTurboModuleRegistry> turboModuleRegistry = _turboModuleRegistry;
if (turboModuleRegistry) {
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
return [turboModuleRegistry moduleIsInitialized:[moduleName UTF8String]];
}
return NO;
}
@end

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTMultipartStreamReader.h>
typedef void (^RCTMultipartDataTaskCallback)(
NSInteger statusCode,
NSDictionary *headers,
NSData *content,
NSError *error,
BOOL done);
@interface RCTMultipartDataTask : NSObject
- (instancetype)initWithURL:(NSURL *)url
partHandler:(RCTMultipartDataTaskCallback)partHandler
progressHandler:(RCTMultipartProgressCallback)progressHandler;
- (void)startTask;
@end

View File

@@ -0,0 +1,130 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTMultipartDataTask.h"
@interface RCTMultipartDataTask () <NSURLSessionDataDelegate, NSURLSessionDataDelegate>
@end
@implementation RCTMultipartDataTask {
NSURL *_url;
RCTMultipartDataTaskCallback _partHandler;
RCTMultipartProgressCallback _progressHandler;
NSInteger _statusCode;
NSDictionary *_headers;
NSString *_boundary;
NSMutableData *_data;
}
- (instancetype)initWithURL:(NSURL *)url
partHandler:(RCTMultipartDataTaskCallback)partHandler
progressHandler:(RCTMultipartProgressCallback)progressHandler
{
if (self = [super init]) {
_url = url;
_partHandler = [partHandler copy];
_progressHandler = [progressHandler copy];
}
return self;
}
- (void)startTask
{
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
delegate:self
delegateQueue:nil];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:_url];
[request addValue:@"multipart/mixed" forHTTPHeaderField:@"Accept"];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
[dataTask resume];
[session finishTasksAndInvalidate];
}
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
_headers = [httpResponse allHeaderFields];
_statusCode = [httpResponse statusCode];
NSString *contentType = @"";
for (NSString *key in [_headers keyEnumerator]) {
if ([[key lowercaseString] isEqualToString:@"content-type"]) {
contentType = [_headers valueForKey:key];
break;
}
}
NSRegularExpression *regex =
[NSRegularExpression regularExpressionWithPattern:@"multipart/mixed;.*boundary=\"([^\"]+)\""
options:0
error:nil];
NSTextCheckingResult *match = [regex firstMatchInString:contentType
options:0
range:NSMakeRange(0, contentType.length)];
if (match) {
_boundary = [contentType substringWithRange:[match rangeAtIndex:1]];
completionHandler(NSURLSessionResponseBecomeStream);
return;
}
}
// In case the server doesn't support multipart/mixed responses, fallback to normal download
_data = [[NSMutableData alloc] initWithCapacity:1024 * 1024];
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(__unused NSURLSession *)session
task:(__unused NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
if (_partHandler) {
_partHandler(_statusCode, _headers, _data, error, YES);
}
}
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
[_data appendData:data];
}
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask
{
[streamTask captureStreams];
}
- (void)URLSession:(__unused NSURLSession *)session
streamTask:(__unused NSURLSessionStreamTask *)streamTask
didBecomeInputStream:(NSInputStream *)inputStream
outputStream:(__unused NSOutputStream *)outputStream
{
RCTMultipartStreamReader *reader = [[RCTMultipartStreamReader alloc] initWithInputStream:inputStream
boundary:_boundary];
RCTMultipartDataTaskCallback partHandler = _partHandler;
_partHandler = nil;
NSInteger statusCode = _statusCode;
BOOL completed = [reader
readAllPartsWithCompletionCallback:^(NSDictionary *headers, NSData *content, BOOL done) {
partHandler(statusCode, headers, content, nil, done);
}
progressCallback:_progressHandler];
if (!completed) {
partHandler(
statusCode, nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:nil], YES);
}
}
@end

View File

@@ -0,0 +1,21 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
typedef void (^RCTMultipartCallback)(NSDictionary *headers, NSData *content, BOOL done);
typedef void (^RCTMultipartProgressCallback)(NSDictionary *headers, NSNumber *loaded, NSNumber *total);
// RCTMultipartStreamReader can be used to parse responses with Content-Type: multipart/mixed
// See https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
@interface RCTMultipartStreamReader : NSObject
- (instancetype)initWithInputStream:(NSInputStream *)stream boundary:(NSString *)boundary;
- (BOOL)readAllPartsWithCompletionCallback:(RCTMultipartCallback)callback
progressCallback:(RCTMultipartProgressCallback)progressCallback;
@end

View File

@@ -0,0 +1,167 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTMultipartStreamReader.h"
#import <QuartzCore/QuartzCore.h>
#define CRLF @"\r\n"
@implementation RCTMultipartStreamReader {
__strong NSInputStream *_stream;
__strong NSString *_boundary;
CFTimeInterval _lastDownloadProgress;
}
- (instancetype)initWithInputStream:(NSInputStream *)stream boundary:(NSString *)boundary
{
if (self = [super init]) {
_stream = stream;
_boundary = boundary;
_lastDownloadProgress = CACurrentMediaTime();
}
return self;
}
- (NSDictionary *)parseHeaders:(NSData *)data
{
NSMutableDictionary *headers = [NSMutableDictionary new];
NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSArray<NSString *> *lines = [text componentsSeparatedByString:CRLF];
for (NSString *line in lines) {
NSUInteger location = [line rangeOfString:@":"].location;
if (location == NSNotFound) {
continue;
}
NSString *key = [line substringToIndex:location];
NSString *value = [[line substringFromIndex:location + 1]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[headers setValue:value forKey:key];
}
return headers;
}
- (void)emitChunk:(NSData *)data headers:(NSDictionary *)headers callback:(RCTMultipartCallback)callback done:(BOOL)done
{
NSData *marker = [CRLF CRLF dataUsingEncoding:NSUTF8StringEncoding];
NSRange range = [data rangeOfData:marker options:0 range:NSMakeRange(0, data.length)];
if (range.location == NSNotFound) {
callback(nil, data, done);
} else if (headers != nil) {
// If headers were parsed already just use that to avoid doing it twice.
NSInteger bodyStart = range.location + marker.length;
NSData *bodyData = [data subdataWithRange:NSMakeRange(bodyStart, data.length - bodyStart)];
callback(headers, bodyData, done);
} else {
NSData *headersData = [data subdataWithRange:NSMakeRange(0, range.location)];
NSInteger bodyStart = range.location + marker.length;
NSData *bodyData = [data subdataWithRange:NSMakeRange(bodyStart, data.length - bodyStart)];
callback([self parseHeaders:headersData], bodyData, done);
}
}
- (void)emitProgress:(NSDictionary *)headers
contentLength:(NSUInteger)contentLength
final:(BOOL)final
callback:(RCTMultipartProgressCallback)callback
{
if (headers == nil) {
return;
}
// Throttle progress events so we don't send more that around 60 per second.
CFTimeInterval currentTime = CACurrentMediaTime();
NSInteger headersContentLength = headers[@"Content-Length"] != nil ? [headers[@"Content-Length"] integerValue] : 0;
if (callback && (currentTime - _lastDownloadProgress > 0.016 || final)) {
_lastDownloadProgress = currentTime;
callback(headers, @(headersContentLength), @(contentLength));
}
}
- (BOOL)readAllPartsWithCompletionCallback:(RCTMultipartCallback)callback
progressCallback:(RCTMultipartProgressCallback)progressCallback
{
NSInteger chunkStart = 0;
NSInteger bytesSeen = 0;
NSData *delimiter =
[[NSString stringWithFormat:@"%@--%@%@", CRLF, _boundary, CRLF] dataUsingEncoding:NSUTF8StringEncoding];
NSData *closeDelimiter =
[[NSString stringWithFormat:@"%@--%@--%@", CRLF, _boundary, CRLF] dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData *content = [[NSMutableData alloc] initWithCapacity:1];
NSDictionary *currentHeaders = nil;
NSUInteger currentHeadersLength = 0;
const NSUInteger bufferLen = 4 * 1024;
uint8_t buffer[bufferLen];
[_stream open];
while (true) {
BOOL isCloseDelimiter = NO;
// Search only a subset of chunk that we haven't seen before + few bytes
// to allow for the edge case when the delimiter is cut by read call
NSInteger searchStart = MAX(bytesSeen - (NSInteger)closeDelimiter.length, chunkStart);
NSRange remainingBufferRange = NSMakeRange(searchStart, content.length - searchStart);
// Check for delimiters.
NSRange range = [content rangeOfData:delimiter options:0 range:remainingBufferRange];
if (range.location == NSNotFound) {
isCloseDelimiter = YES;
range = [content rangeOfData:closeDelimiter options:0 range:remainingBufferRange];
}
if (range.location == NSNotFound) {
if (currentHeaders == nil) {
// Check for the headers delimiter.
NSData *headersMarker = [CRLF CRLF dataUsingEncoding:NSUTF8StringEncoding];
NSRange headersRange = [content rangeOfData:headersMarker options:0 range:remainingBufferRange];
if (headersRange.location != NSNotFound) {
NSData *headersData = [content subdataWithRange:NSMakeRange(chunkStart, headersRange.location - chunkStart)];
currentHeadersLength = headersData.length;
currentHeaders = [self parseHeaders:headersData];
}
} else {
// When headers are loaded start sending progress callbacks.
[self emitProgress:currentHeaders
contentLength:content.length - currentHeadersLength
final:NO
callback:progressCallback];
}
bytesSeen = content.length;
NSInteger bytesRead = [_stream read:buffer maxLength:bufferLen];
if (bytesRead <= 0 || _stream.streamError) {
return NO;
}
[content appendBytes:buffer length:bytesRead];
continue;
}
NSInteger chunkEnd = range.location;
NSInteger length = chunkEnd - chunkStart;
bytesSeen = chunkEnd;
// Ignore preamble
if (chunkStart > 0) {
NSData *chunk = [content subdataWithRange:NSMakeRange(chunkStart, length)];
[self emitProgress:currentHeaders
contentLength:chunk.length - currentHeadersLength
final:YES
callback:progressCallback];
[self emitChunk:chunk headers:currentHeaders callback:callback done:isCloseDelimiter];
currentHeaders = nil;
currentHeadersLength = 0;
}
if (isCloseDelimiter) {
return YES;
}
chunkStart = chunkEnd + delimiter.length;
}
}
@end

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, RCTNullability) {
RCTNullabilityUnspecified,
RCTNullable,
RCTNonnullable,
};

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
typedef NS_ENUM(NSInteger, RCTPLTag) {
RCTPLScriptDownload = 0,
RCTPLScriptExecution,
RCTPLRAMBundleLoad,
RCTPLRAMStartupCodeSize,
RCTPLRAMStartupNativeRequires,
RCTPLRAMStartupNativeRequiresCount,
RCTPLRAMNativeRequires,
RCTPLRAMNativeRequiresCount,
RCTPLNativeModuleInit,
RCTPLNativeModuleMainThread,
RCTPLNativeModulePrepareConfig,
RCTPLNativeModuleMainThreadUsesCount,
RCTPLNativeModuleSetup,
RCTPLTurboModuleSetup,
RCTPLJSCWrapperOpenLibrary,
RCTPLBridgeStartup,
RCTPLTTI,
RCTPLBundleSize,
RCTPLReactInstanceInit,
RCTPLAppStartup,
RCTPLInitReactRuntime,
RCTPLSize // This is used to count the size
};

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTDefines.h>
@interface RCTParserUtils : NSObject
/**
* Generic utility functions for parsing Objective-C source code.
*/
RCT_EXTERN BOOL RCTReadChar(const char **input, char c);
RCT_EXTERN BOOL RCTReadString(const char **input, const char *string);
RCT_EXTERN void RCTSkipWhitespace(const char **input);
RCT_EXTERN BOOL RCTParseSelectorIdentifier(const char **input, NSString **string);
RCT_EXTERN BOOL RCTParseArgumentIdentifier(const char **input, NSString **string);
/**
* Parse an Objective-C type into a form that can be used by RCTConvert.
* This doesn't really belong here, but it's used by both RCTConvert and
* RCTModuleMethod, which makes it difficult to find a better home for it.
*/
RCT_EXTERN NSString *RCTParseType(const char **input);
@end

View File

@@ -0,0 +1,137 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTParserUtils.h"
#import "RCTLog.h"
@implementation RCTParserUtils
BOOL RCTReadChar(const char **input, char c)
{
if (**input == c) {
(*input)++;
return YES;
}
return NO;
}
BOOL RCTReadString(const char **input, const char *string)
{
int i;
for (i = 0; string[i] != 0; i++) {
if (string[i] != (*input)[i]) {
return NO;
}
}
*input += i;
return YES;
}
void RCTSkipWhitespace(const char **input)
{
while (isspace(**input)) {
(*input)++;
}
}
static BOOL RCTIsIdentifierHead(const char c)
{
return isalpha(c) || c == '_';
}
static BOOL RCTIsIdentifierTail(const char c)
{
return isalnum(c) || c == '_';
}
BOOL RCTParseArgumentIdentifier(const char **input, NSString **string)
{
const char *start = *input;
do {
if (!RCTIsIdentifierHead(**input)) {
return NO;
}
(*input)++;
while (RCTIsIdentifierTail(**input)) {
(*input)++;
}
// allow namespace resolution operator
} while (RCTReadString(input, "::"));
if (string) {
*string = [[NSString alloc] initWithBytes:start length:(NSInteger)(*input - start) encoding:NSASCIIStringEncoding];
}
return YES;
}
BOOL RCTParseSelectorIdentifier(const char **input, NSString **string)
{
const char *start = *input;
if (!RCTIsIdentifierHead(**input)) {
return NO;
}
(*input)++;
while (RCTIsIdentifierTail(**input)) {
(*input)++;
}
if (string) {
*string = [[NSString alloc] initWithBytes:start length:(NSInteger)(*input - start) encoding:NSASCIIStringEncoding];
}
return YES;
}
static BOOL RCTIsCollectionType(NSString *type)
{
static NSSet *collectionTypes;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
collectionTypes = [[NSSet alloc] initWithObjects:@"NSArray", @"NSSet", @"NSDictionary", nil];
});
return [collectionTypes containsObject:type];
}
NSString *RCTParseType(const char **input)
{
NSString *type;
RCTParseArgumentIdentifier(input, &type);
RCTSkipWhitespace(input);
if (RCTReadChar(input, '<')) {
RCTSkipWhitespace(input);
NSString *subtype = RCTParseType(input);
if (RCTIsCollectionType(type)) {
if ([type isEqualToString:@"NSDictionary"]) {
// Dictionaries have both a key *and* value type, but the key type has
// to be a string for JSON, so we only care about the value type
if (RCT_DEBUG && ![subtype isEqualToString:@"NSString"]) {
RCTLogError(@"%@ is not a valid key type for a JSON dictionary", subtype);
}
RCTSkipWhitespace(input);
RCTReadChar(input, ',');
RCTSkipWhitespace(input);
subtype = RCTParseType(input);
}
if (![subtype isEqualToString:@"id"]) {
type = [type stringByReplacingCharactersInRange:(NSRange){0, 2 /* "NS" */} withString:subtype];
}
} else {
// It's a protocol rather than a generic collection - ignore it
}
RCTSkipWhitespace(input);
RCTReadChar(input, '>');
}
RCTSkipWhitespace(input);
if (!RCTReadChar(input, '*')) {
RCTReadChar(input, '&');
}
return type;
}
@end

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import "RCTPLTag.h"
@interface RCTPerformanceLogger : NSObject
/**
* Starts measuring a metric with the given tag.
* Overrides previous value if the measurement has been already started.
* If RCTProfile is enabled it also begins appropriate async event.
* All work is scheduled on the background queue so this doesn't block current thread.
*/
- (void)markStartForTag:(RCTPLTag)tag;
/**
* Stops measuring a metric with given tag.
* Checks if RCTPerformanceLoggerStart() has been called before
* and doesn't do anything and log a message if it hasn't.
* If RCTProfile is enabled it also ends appropriate async event.
* All work is scheduled on the background queue so this doesn't block current thread.
*/
- (void)markStopForTag:(RCTPLTag)tag;
/**
* Sets given value for a metric with given tag.
* All work is scheduled on the background queue so this doesn't block current thread.
*/
- (void)setValue:(int64_t)value forTag:(RCTPLTag)tag;
/**
* Adds given value to the current value for a metric with given tag.
* All work is scheduled on the background queue so this doesn't block current thread.
*/
- (void)addValue:(int64_t)value forTag:(RCTPLTag)tag;
/**
* Starts an additional measurement for a metric with given tag.
* It doesn't override previous measurement, instead it'll append a new value
* to the old one.
* All work is scheduled on the background queue so this doesn't block current thread.
*/
- (void)appendStartForTag:(RCTPLTag)tag;
/**
* Stops measurement and appends the result to the metric with given tag.
* Checks if RCTPerformanceLoggerAppendStart() has been called before
* and doesn't do anything and log a message if it hasn't.
* All work is scheduled on the background queue so this doesn't block current thread.
*/
- (void)appendStopForTag:(RCTPLTag)tag;
/**
* Returns an array with values for all tags.
* Use RCTPLTag to go over the array, there's a pair of values
* for each tag: start and stop (with indexes 2 * tag and 2 * tag + 1).
*/
- (NSArray<NSNumber *> *)valuesForTags;
/**
* Returns a duration in ms (stop_time - start_time) for given RCTPLTag.
*/
- (int64_t)durationForTag:(RCTPLTag)tag;
/**
* Returns a value for given RCTPLTag.
*/
- (int64_t)valueForTag:(RCTPLTag)tag;
@end

View File

@@ -0,0 +1,135 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <QuartzCore/QuartzCore.h>
#import <cxxreact/ReactMarker.h>
#include <unordered_map>
#import "RCTLog.h"
#import "RCTPerformanceLogger.h"
#import "RCTPerformanceLoggerLabels.h"
#import "RCTProfile.h"
#import "RCTRootView.h"
using namespace facebook::react;
@interface RCTPerformanceLogger () {
int64_t _data[RCTPLSize][2];
NSInteger _cookies[RCTPLSize];
}
@end
@implementation RCTPerformanceLogger
static const std::unordered_map<RCTPLTag, ReactMarker::ReactMarkerId> &getStartTagToReactMarkerIdMap()
{
static std::unordered_map<RCTPLTag, ReactMarker::ReactMarkerId> StartTagToReactMarkerIdMap = {
{RCTPLAppStartup, ReactMarker::APP_STARTUP_START},
{RCTPLInitReactRuntime, ReactMarker::INIT_REACT_RUNTIME_START},
{RCTPLScriptExecution, ReactMarker::RUN_JS_BUNDLE_START}};
return StartTagToReactMarkerIdMap;
}
static const std::unordered_map<RCTPLTag, ReactMarker::ReactMarkerId> &getStopTagToReactMarkerIdMap()
{
static std::unordered_map<RCTPLTag, ReactMarker::ReactMarkerId> StopTagToReactMarkerIdMap = {
{RCTPLAppStartup, ReactMarker::APP_STARTUP_STOP},
{RCTPLInitReactRuntime, ReactMarker::INIT_REACT_RUNTIME_STOP},
{RCTPLScriptExecution, ReactMarker::RUN_JS_BUNDLE_STOP}};
return StopTagToReactMarkerIdMap;
}
- (void)markStartForTag:(RCTPLTag)tag
{
#if RCT_PROFILE
if (RCTProfileIsProfiling()) {
NSString *label = RCTPLLabelForTag(tag);
_cookies[tag] = RCTProfileBeginAsyncEvent(RCTProfileTagAlways, label, nil);
}
#endif
const NSTimeInterval currentTime = CACurrentMediaTime() * 1000;
_data[tag][0] = currentTime;
_data[tag][1] = 0;
// Notify RN ReactMarker when hosting platform log for markers
const auto &startTagToReactMarkerIdMap = getStartTagToReactMarkerIdMap();
if (startTagToReactMarkerIdMap.find(tag) != startTagToReactMarkerIdMap.end()) {
ReactMarker::logMarkerDone(startTagToReactMarkerIdMap.at(tag), currentTime);
}
}
- (void)markStopForTag:(RCTPLTag)tag
{
#if RCT_PROFILE
if (RCTProfileIsProfiling()) {
NSString *label = RCTPLLabelForTag(tag);
RCTProfileEndAsyncEvent(RCTProfileTagAlways, @"native", _cookies[tag], label, @"RCTPerformanceLogger");
}
#endif
const NSTimeInterval currentTime = CACurrentMediaTime() * 1000;
if (_data[tag][0] != 0 && _data[tag][1] == 0) {
_data[tag][1] = currentTime;
} else {
RCTLogInfo(@"Unbalanced calls start/end for tag %li", (unsigned long)tag);
}
// Notify RN ReactMarker when hosting platform log for markers
const auto &stopTagToReactMarkerIdMap = getStopTagToReactMarkerIdMap();
if (stopTagToReactMarkerIdMap.find(tag) != stopTagToReactMarkerIdMap.end()) {
ReactMarker::logMarkerDone(stopTagToReactMarkerIdMap.at(tag), currentTime);
}
}
- (void)setValue:(int64_t)value forTag:(RCTPLTag)tag
{
_data[tag][0] = 0;
_data[tag][1] = value;
}
- (void)addValue:(int64_t)value forTag:(RCTPLTag)tag
{
_data[tag][0] = 0;
_data[tag][1] += value;
}
- (void)appendStartForTag:(RCTPLTag)tag
{
_data[tag][0] = CACurrentMediaTime() * 1000;
}
- (void)appendStopForTag:(RCTPLTag)tag
{
if (_data[tag][0] != 0) {
_data[tag][1] += CACurrentMediaTime() * 1000 - _data[tag][0];
_data[tag][0] = 0;
} else {
RCTLogInfo(@"Unbalanced calls start/end for tag %li", (unsigned long)tag);
}
}
- (NSArray<NSNumber *> *)valuesForTags
{
NSMutableArray *result = [NSMutableArray array];
for (NSUInteger index = 0; index < RCTPLSize; index++) {
[result addObject:@(_data[index][0])];
[result addObject:@(_data[index][1])];
}
return result;
}
- (int64_t)durationForTag:(RCTPLTag)tag
{
return _data[tag][1] - _data[tag][0];
}
- (int64_t)valueForTag:(RCTPLTag)tag
{
return _data[tag][1];
}
@end

View File

@@ -0,0 +1,12 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTDefines.h>
#import "RCTPLTag.h"
// Return the string label for the enum RCTPLTag for performance logging
__attribute__((used)) RCT_EXTERN NSString *RCTPLLabelForTag(RCTPLTag tag);

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTPerformanceLoggerLabels.h"
#import <React/RCTAssert.h>
NSString *RCTPLLabelForTag(RCTPLTag tag)
{
switch (tag) {
case RCTPLScriptDownload:
return @"ScriptDownload";
case RCTPLScriptExecution:
return @"ScriptExecution";
case RCTPLRAMBundleLoad:
return @"RAMBundleLoad";
case RCTPLRAMStartupCodeSize:
return @"RAMStartupCodeSize";
case RCTPLRAMStartupNativeRequires:
return @"RAMStartupNativeRequires";
case RCTPLRAMStartupNativeRequiresCount:
return @"RAMStartupNativeRequiresCount";
case RCTPLRAMNativeRequires:
return @"RAMNativeRequires";
case RCTPLRAMNativeRequiresCount:
return @"RAMNativeRequiresCount";
case RCTPLNativeModuleInit:
return @"NativeModuleInit";
case RCTPLNativeModuleMainThread:
return @"NativeModuleMainThread";
case RCTPLNativeModulePrepareConfig:
return @"NativeModulePrepareConfig";
case RCTPLNativeModuleMainThreadUsesCount:
return @"NativeModuleMainThreadUsesCount";
case RCTPLNativeModuleSetup:
return @"NativeModuleSetup";
case RCTPLTurboModuleSetup:
return @"TurboModuleSetup";
case RCTPLJSCWrapperOpenLibrary:
return @"JSCWrapperOpenLibrary";
case RCTPLBridgeStartup:
return @"BridgeStartup";
case RCTPLTTI:
return @"RootViewTTI";
case RCTPLBundleSize:
return @"BundleSize";
case RCTPLReactInstanceInit:
return @"ReactInstanceInit";
case RCTPLAppStartup:
return @"AppStartup";
case RCTPLInitReactRuntime:
return @"InitReactRuntime";
case RCTPLSize: // Only used to count enum size
RCTAssert(NO, @"RCTPLSize should not be used to track performance timestamps.");
return nil;
}
}

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTDefines.h>
// In debug builds, the red box is enabled by default but it is further
// customizable using this method. However, this method only has an effect in
// builds where RCTRedBox is actually compiled in.
RCT_EXTERN void RCTRedBoxSetEnabled(BOOL enabled);
RCT_EXTERN BOOL RCTRedBoxGetEnabled(void);

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTRedBoxSetEnabled.h"
#if RCT_DEV
static BOOL redBoxEnabled = YES;
#else
static BOOL redBoxEnabled = NO;
#endif
void RCTRedBoxSetEnabled(BOOL enabled)
{
redBoxEnabled = enabled;
}
BOOL RCTRedBoxGetEnabled(void)
{
return redBoxEnabled;
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTDefines.h>
/**
* A protocol which should be conformed to in order to be notified of RN reload events. These events can be
* created by CMD+R or dev menu during development, or anywhere the trigger is exposed to JS.
* The listener must also register itself using the method below.
*/
@protocol RCTReloadListener
- (void)didReceiveReloadCommand;
@end
/**
* Registers a weakly-held observer of RN reload events.
*/
RCT_EXTERN void RCTRegisterReloadCommandListener(id<RCTReloadListener> listener);
/**
* Triggers a reload for all current listeners. Replaces [_bridge reload].
*/
RCT_EXTERN void RCTTriggerReloadCommandListeners(NSString *reason);
/**
* This notification fires anytime RCTTriggerReloadCommandListeners() is called.
*/
RCT_EXTERN NSString *const RCTTriggerReloadCommandNotification;
RCT_EXTERN NSString *const RCTTriggerReloadCommandReasonKey;
RCT_EXTERN NSString *const RCTTriggerReloadCommandBundleURLKey;
RCT_EXTERN void RCTReloadCommandSetBundleURL(NSURL *URL);

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTReloadCommand.h"
#import "RCTAssert.h"
#import "RCTKeyCommands.h"
#import "RCTUtils.h"
static NSHashTable<id<RCTReloadListener>> *listeners;
static NSLock *listenersLock;
static NSURL *bundleURL;
NSString *const RCTTriggerReloadCommandNotification = @"RCTTriggerReloadCommandNotification";
NSString *const RCTTriggerReloadCommandReasonKey = @"reason";
NSString *const RCTTriggerReloadCommandBundleURLKey = @"bundleURL";
void RCTRegisterReloadCommandListener(id<RCTReloadListener> listener)
{
if (!listenersLock) {
listenersLock = [NSLock new];
}
[listenersLock lock];
if (!listeners) {
listeners = [NSHashTable weakObjectsHashTable];
}
#if RCT_DEV
RCTAssertMainQueue(); // because registerKeyCommandWithInput: must be called on the main thread
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:^(__unused UIKeyCommand *command) {
RCTTriggerReloadCommandListeners(@"Command + R");
}];
});
#endif
[listeners addObject:listener];
[listenersLock unlock];
}
void RCTTriggerReloadCommandListeners(NSString *reason)
{
[listenersLock lock];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTTriggerReloadCommandNotification
object:nil
userInfo:@{
RCTTriggerReloadCommandReasonKey : RCTNullIfNil(reason),
RCTTriggerReloadCommandBundleURLKey : RCTNullIfNil(bundleURL)
}];
for (id<RCTReloadListener> l in [listeners allObjects]) {
[l didReceiveReloadCommand];
}
[listenersLock unlock];
}
void RCTReloadCommandSetBundleURL(NSURL *URL)
{
bundleURL = URL;
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <React/RCTInvalidating.h>
#import <React/RCTRootView.h>
#import <React/RCTView.h>
@class RCTBridge;
@class RCTTouchHandler;
@interface RCTRootContentView : RCTView <RCTInvalidating>
@property (nonatomic, readonly, weak) RCTBridge *bridge;
@property (nonatomic, readonly, assign) BOOL contentHasAppeared;
@property (nonatomic, readonly, strong) RCTTouchHandler *touchHandler;
@property (nonatomic, readonly, assign) CGSize availableSize;
@property (nonatomic, assign) BOOL passThroughTouches;
@property (nonatomic, assign) RCTRootViewSizeFlexibility sizeFlexibility;
- (instancetype)initWithFrame:(CGRect)frame
bridge:(RCTBridge *)bridge
reactTag:(NSNumber *)reactTag
sizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility NS_DESIGNATED_INITIALIZER;
@end

View File

@@ -0,0 +1,107 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTRootContentView.h"
#import "RCTBridge.h"
#import "RCTPerformanceLogger.h"
#import "RCTRootView.h"
#import "RCTRootViewInternal.h"
#import "RCTTouchHandler.h"
#import "RCTUIManager.h"
#import "UIView+React.h"
@implementation RCTRootContentView
- (instancetype)initWithFrame:(CGRect)frame
bridge:(RCTBridge *)bridge
reactTag:(NSNumber *)reactTag
sizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility
{
if ((self = [super initWithFrame:frame])) {
_bridge = bridge;
self.reactTag = reactTag;
_sizeFlexibility = sizeFlexibility;
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
[_touchHandler attachToView:self];
[_bridge.uiManager registerRootView:self];
}
return self;
}
RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame)
RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (nonnull NSCoder *)aDecoder)
- (void)layoutSubviews
{
[super layoutSubviews];
[self updateAvailableSize];
}
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
{
[super insertReactSubview:subview atIndex:atIndex];
[_bridge.performanceLogger markStopForTag:RCTPLTTI];
dispatch_async(dispatch_get_main_queue(), ^{
if (!self->_contentHasAppeared) {
self->_contentHasAppeared = YES;
[[NSNotificationCenter defaultCenter] postNotificationName:RCTContentDidAppearNotification object:self.superview];
}
});
}
- (void)setSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility
{
if (_sizeFlexibility == sizeFlexibility) {
return;
}
_sizeFlexibility = sizeFlexibility;
[self setNeedsLayout];
}
- (CGSize)availableSize
{
CGSize size = self.bounds.size;
return CGSizeMake(
_sizeFlexibility & RCTRootViewSizeFlexibilityWidth ? INFINITY : size.width,
_sizeFlexibility & RCTRootViewSizeFlexibilityHeight ? INFINITY : size.height);
}
- (void)updateAvailableSize
{
if (!self.reactTag || !_bridge.isValid) {
return;
}
[_bridge.uiManager setAvailableSize:self.availableSize forRootView:self];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// The root content view itself should never receive touches
UIView *hitView = [super hitTest:point withEvent:event];
if (_passThroughTouches && hitView == self) {
return nil;
}
return hitView;
}
- (void)invalidate
{
if (self.userInteractionEnabled) {
self.userInteractionEnabled = NO;
[(RCTRootView *)self.superview contentViewInvalidated];
[_bridge enqueueJSCall:@"AppRegistry"
method:@"unmountApplicationComponentAtRootTag"
args:@[ self.reactTag ]
completion:NULL];
}
}
@end

View File

@@ -0,0 +1,181 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventDispatcherProtocol.h>
@protocol RCTRootViewDelegate;
/**
* This enum is used to define size flexibility type of the root view.
* If a dimension is flexible, the view will recalculate that dimension
* so the content fits. Recalculations are performed when the root's frame,
* size flexibility mode or content size changes. After a recalculation,
* rootViewDidChangeIntrinsicSize method of the RCTRootViewDelegate will be called.
*/
typedef NS_ENUM(NSInteger, RCTRootViewSizeFlexibility) {
RCTRootViewSizeFlexibilityNone = 0,
RCTRootViewSizeFlexibilityWidth = 1 << 0,
RCTRootViewSizeFlexibilityHeight = 1 << 1,
RCTRootViewSizeFlexibilityWidthAndHeight = RCTRootViewSizeFlexibilityWidth | RCTRootViewSizeFlexibilityHeight,
};
/**
* This notification is sent when the first subviews are added to the root view
* after the application has loaded. This is used to hide the `loadingView`, and
* is a good indicator that the application is ready to use.
*/
#if defined(__cplusplus)
extern "C"
#else
extern
#endif
NS_ASSUME_NONNULL_BEGIN
NSString *const RCTContentDidAppearNotification;
/**
* Native view used to host React-managed views within the app. Can be used just
* like any ordinary UIView. You can have multiple RCTRootViews on screen at
* once, all controlled by the same JavaScript application.
*/
@interface RCTRootView : UIView
/**
* - Designated initializer -
*/
- (instancetype)initWithFrame:(CGRect)frame
bridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER;
/**
* - Convenience initializer -
* The frame will default to CGRectZero.
*/
- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties;
/**
* - Convenience initializer -
* A bridge will be created internally.
* This initializer is intended to be used when the app has a single RCTRootView,
* otherwise create an `RCTBridge` and pass it in via `initWithBridge:moduleName:`
* to all the instances.
*/
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties
launchOptions:(nullable NSDictionary *)launchOptions;
/**
* The name of the JavaScript module to execute within the
* specified scriptURL (required). Setting this will not have
* any immediate effect, but it must be done prior to loading
* the script.
*/
@property (nonatomic, copy, readonly) NSString *moduleName;
/**
* The bridge used by the root view. Bridges can be shared between multiple
* root views, so you can use this property to initialize another RCTRootView.
*/
@property (nonatomic, strong, readonly) RCTBridge *bridge;
/**
* The properties to apply to the view. Use this property to update
* application properties and rerender the view. Initialized with
* initialProperties argument of the initializer.
*
* Set this property only on the main thread.
*/
@property (nonatomic, copy, readwrite, nullable) NSDictionary *appProperties;
/**
* The size flexibility mode of the root view.
*/
@property (nonatomic, assign) RCTRootViewSizeFlexibility sizeFlexibility;
/*
* The minimum size of the root view, defaults to CGSizeZero.
*/
@property (nonatomic, assign) CGSize minimumSize;
/**
* The delegate that handles intrinsic size updates.
*/
@property (nonatomic, weak, nullable) id<RCTRootViewDelegate> delegate;
/**
* The backing view controller of the root view.
*/
@property (nonatomic, weak, nullable) UIViewController *reactViewController;
/**
* The root view casted as UIView. Used by splash screen libraries.
*/
@property (nonatomic, strong, readonly) UIView *view;
/**
* The React-managed contents view of the root view.
*/
@property (nonatomic, strong, readonly) UIView *contentView;
/**
* A view to display while the JavaScript is loading, so users aren't presented
* with a blank screen. By default this is nil, but you can override it with
* (for example) a UIActivityIndicatorView or a placeholder image.
*/
@property (nonatomic, strong, nullable) UIView *loadingView;
/**
* When set, any touches on the RCTRootView that are not matched up to any of the child
* views will be passed to siblings of the RCTRootView. See -[UIView hitTest:withEvent:]
* for details on iOS hit testing.
*
* Enable this to support a semi-transparent RN view that occupies the whole screen but
* has visible content below it that the user can interact with.
*
* The default value is NO.
*/
@property (nonatomic, assign) BOOL passThroughTouches;
/**
* Timings for hiding the loading view after the content has loaded. Both of
* these values default to 0.25 seconds.
*/
@property (nonatomic, assign) NSTimeInterval loadingViewFadeDelay;
@property (nonatomic, assign) NSTimeInterval loadingViewFadeDuration;
@end
@interface RCTRootView (Deprecated)
/**
* The intrinsic size of the root view's content. This is set right before the
* `rootViewDidChangeIntrinsicSize` method of `RCTRootViewDelegate` is called.
* This property is deprecated and will be removed in next releases.
* Use UIKit `intrinsicContentSize` property instead.
*/
@property (readonly, nonatomic, assign) CGSize intrinsicSize __deprecated_msg("Use `intrinsicContentSize` instead.");
/**
* This methods is deprecated and will be removed soon.
* To interrupt a React Native gesture recognizer, use the standard
* `UIGestureRecognizer` negotiation process.
* See `UIGestureRecognizerDelegate` for more details.
*/
- (void)cancelTouches;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,404 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTRootView.h"
#import "RCTRootViewDelegate.h"
#import "RCTRootViewInternal.h"
#import <objc/runtime.h>
#import "RCTAssert.h"
#import "RCTBridge+Private.h"
#import "RCTBridge.h"
#import "RCTConstants.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTPerformanceLogger.h"
#import "RCTProfile.h"
#import "RCTRootContentView.h"
#import "RCTRootShadowView.h"
#import "RCTTouchHandler.h"
#import "RCTUIManager.h"
#import "RCTUIManagerUtils.h"
#import "RCTUtils.h"
#import "RCTView.h"
#import "UIView+React.h"
NSString *const RCTContentDidAppearNotification = @"RCTContentDidAppearNotification";
@interface RCTUIManager (RCTRootView)
- (NSNumber *)allocateRootTag;
@end
@implementation RCTRootView {
RCTBridge *_bridge;
NSString *_moduleName;
RCTRootContentView *_contentView;
BOOL _passThroughTouches;
CGSize _intrinsicContentSize;
}
- (instancetype)initWithFrame:(CGRect)frame
bridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties
{
RCTAssertMainQueue();
RCTAssert(bridge, @"A bridge instance is required to create an RCTRootView");
RCTAssert(moduleName, @"A moduleName is required to create an RCTRootView");
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTRootView init]", nil);
if (!bridge.isLoading) {
[bridge.performanceLogger markStartForTag:RCTPLTTI];
}
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor whiteColor];
_bridge = bridge;
_moduleName = moduleName;
_appProperties = [initialProperties copy];
_loadingViewFadeDelay = 0.25;
_loadingViewFadeDuration = 0.25;
_sizeFlexibility = RCTRootViewSizeFlexibilityNone;
_minimumSize = CGSizeZero;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(bridgeDidReload)
name:RCTJavaScriptWillStartLoadingNotification
object:_bridge];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(javaScriptDidLoad:)
name:RCTJavaScriptDidLoadNotification
object:_bridge];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(hideLoadingView)
name:RCTContentDidAppearNotification
object:self];
[self showLoadingView];
// Immediately schedule the application to be started.
// (Sometimes actual `_bridge` is already batched bridge here.)
[self bundleFinishedLoading:([_bridge batchedBridge] ?: _bridge)];
}
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
return self;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties
{
return [self initWithFrame:CGRectZero bridge:bridge moduleName:moduleName initialProperties:initialProperties];
}
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties
launchOptions:(NSDictionary *)launchOptions
{
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL moduleProvider:nil launchOptions:launchOptions];
return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
}
RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame)
RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder)
- (UIView *)view
{
return self;
}
#pragma mark - passThroughTouches
- (BOOL)passThroughTouches
{
return _contentView.passThroughTouches;
}
- (void)setPassThroughTouches:(BOOL)passThroughTouches
{
_passThroughTouches = passThroughTouches;
_contentView.passThroughTouches = passThroughTouches;
}
#pragma mark - Layout
- (CGSize)sizeThatFits:(CGSize)size
{
CGSize fitSize = _intrinsicContentSize;
CGSize currentSize = self.bounds.size;
// Following the current `size` and current `sizeFlexibility` policy.
fitSize = CGSizeMake(
_sizeFlexibility & RCTRootViewSizeFlexibilityWidth ? fitSize.width : currentSize.width,
_sizeFlexibility & RCTRootViewSizeFlexibilityHeight ? fitSize.height : currentSize.height);
// Following the given size constraints.
fitSize = CGSizeMake(MIN(size.width, fitSize.width), MIN(size.height, fitSize.height));
return fitSize;
}
- (void)layoutSubviews
{
[super layoutSubviews];
_contentView.frame = self.bounds;
_loadingView.center = (CGPoint){CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)};
}
- (void)setMinimumSize:(CGSize)minimumSize
{
if (CGSizeEqualToSize(_minimumSize, minimumSize)) {
return;
}
_minimumSize = minimumSize;
__block NSNumber *tag = self.reactTag;
__weak typeof(self) weakSelf = self;
RCTExecuteOnUIManagerQueue(^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf && strongSelf->_bridge.isValid) {
RCTRootShadowView *shadowView = (RCTRootShadowView *)[strongSelf->_bridge.uiManager shadowViewForReactTag:tag];
shadowView.minimumSize = minimumSize;
}
});
}
- (UIViewController *)reactViewController
{
return _reactViewController ?: [super reactViewController];
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (void)setLoadingView:(UIView *)loadingView
{
_loadingView = loadingView;
if (!_contentView.contentHasAppeared) {
[self showLoadingView];
}
}
- (void)showLoadingView
{
if (_loadingView && !_contentView.contentHasAppeared) {
_loadingView.hidden = NO;
[self addSubview:_loadingView];
}
}
- (void)hideLoadingView
{
if (_loadingView.superview == self && _contentView.contentHasAppeared) {
if (_loadingViewFadeDuration > 0) {
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_loadingViewFadeDelay * NSEC_PER_SEC)),
dispatch_get_main_queue(),
^{
[UIView transitionWithView:self
duration:self->_loadingViewFadeDuration
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
self->_loadingView.hidden = YES;
}
completion:^(__unused BOOL finished) {
[self->_loadingView removeFromSuperview];
}];
});
} else {
_loadingView.hidden = YES;
[_loadingView removeFromSuperview];
}
}
}
- (NSNumber *)reactTag
{
RCTAssertMainQueue();
if (!super.reactTag) {
/**
* Every root view that is created must have a unique react tag.
* Numbering of these tags goes from 1, 11, 21, 31, etc
*
* NOTE: Since the bridge persists, the RootViews might be reused, so the
* react tag must be re-assigned every time a new UIManager is created.
*/
self.reactTag = RCTAllocateRootViewTag();
}
return super.reactTag;
}
- (void)bridgeDidReload
{
RCTAssertMainQueue();
// Clear the reactTag so it can be re-assigned
self.reactTag = nil;
}
- (void)javaScriptDidLoad:(NSNotification *)notification
{
RCTAssertMainQueue();
// Use the (batched) bridge that's sent in the notification payload, so the
// RCTRootContentView is scoped to the right bridge
RCTBridge *bridge = notification.userInfo[@"bridge"];
if (bridge != _contentView.bridge) {
[self bundleFinishedLoading:bridge];
}
}
- (void)bundleFinishedLoading:(RCTBridge *)bridge
{
RCTAssert(bridge != nil, @"Bridge cannot be nil");
if (!bridge.valid) {
return;
}
[_contentView removeFromSuperview];
_contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
bridge:bridge
reactTag:self.reactTag
sizeFlexibility:_sizeFlexibility];
[self runApplication:bridge];
_contentView.passThroughTouches = _passThroughTouches;
[self insertSubview:_contentView atIndex:0];
if (_sizeFlexibility == RCTRootViewSizeFlexibilityNone) {
self.intrinsicContentSize = self.bounds.size;
}
}
- (void)runApplication:(RCTBridge *)bridge
{
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@"rootTag" : _contentView.reactTag,
@"initialProps" : _appProperties ?: @{},
};
RCTLogInfo(@"Running application %@ (%@)", moduleName, appParameters);
[bridge enqueueJSCall:@"AppRegistry" method:@"runApplication" args:@[ moduleName, appParameters ] completion:NULL];
}
- (void)setSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility
{
if (_sizeFlexibility == sizeFlexibility) {
return;
}
_sizeFlexibility = sizeFlexibility;
[self setNeedsLayout];
_contentView.sizeFlexibility = _sizeFlexibility;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// The root view itself should never receive touches
UIView *hitView = [super hitTest:point withEvent:event];
if (self.passThroughTouches && hitView == self) {
return nil;
}
return hitView;
}
- (void)setAppProperties:(NSDictionary *)appProperties
{
RCTAssertMainQueue();
if ([_appProperties isEqualToDictionary:appProperties]) {
return;
}
_appProperties = [appProperties copy];
if (_contentView && _bridge.valid && !_bridge.loading) {
[self runApplication:_bridge];
}
}
- (void)setIntrinsicContentSize:(CGSize)intrinsicContentSize
{
BOOL oldSizeHasAZeroDimension = _intrinsicContentSize.height == 0 || _intrinsicContentSize.width == 0;
BOOL newSizeHasAZeroDimension = intrinsicContentSize.height == 0 || intrinsicContentSize.width == 0;
BOOL bothSizesHaveAZeroDimension = oldSizeHasAZeroDimension && newSizeHasAZeroDimension;
BOOL sizesAreEqual = CGSizeEqualToSize(_intrinsicContentSize, intrinsicContentSize);
_intrinsicContentSize = intrinsicContentSize;
// Don't notify the delegate if the content remains invisible or its size has not changed
if (bothSizesHaveAZeroDimension || sizesAreEqual) {
return;
}
[self invalidateIntrinsicContentSize];
[self.superview setNeedsLayout];
[_delegate rootViewDidChangeIntrinsicSize:self];
}
- (CGSize)intrinsicContentSize
{
return _intrinsicContentSize;
}
- (void)contentViewInvalidated
{
[_contentView removeFromSuperview];
_contentView = nil;
[self showLoadingView];
}
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
[super traitCollectionDidChange:previousTraitCollection];
if (RCTSharedApplication().applicationState == UIApplicationStateBackground) {
return;
}
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTUserInterfaceStyleDidChangeNotification
object:self
userInfo:@{
RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey : self.traitCollection,
}];
}
- (void)dealloc
{
[_contentView invalidate];
}
@end
@implementation RCTRootView (Deprecated)
- (CGSize)intrinsicSize
{
RCTLogWarn(@"Calling deprecated `[-RCTRootView intrinsicSize]`.");
return self.intrinsicContentSize;
}
- (void)cancelTouches
{
RCTLogWarn(@"`-[RCTRootView cancelTouches]` is deprecated and will be deleted soon.");
[[_contentView touchHandler] cancel];
}
@end

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@class RCTRootView;
@protocol RCTRootViewDelegate <NSObject>
/**
* Called after the root view's intrinsic content size is changed.
*
* The method is not called when both old size and new size have
* a dimension that equals to zero.
*
* The delegate can use this callback to appropriately resize
* the root view's frame to fit the new intrinsic content view size,
* but usually it is not necessary because the root view will also call
* `setNeedsLayout` for its superview which in its turn will trigger relayout.
*
* The new intrinsic content size is available via the `intrinsicContentSize`
* property of the root view. The view will not resize itself.
*/
- (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView;
@end

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTRootView.h>
/**
* The interface provides a set of functions that allow other internal framework
* classes to change the RCTRootViews's internal state.
*/
@interface RCTRootView ()
/**
* This setter should be used only by RCTUIManager on react root view
* intrinsic content size update.
*/
@property (readwrite, nonatomic, assign) CGSize intrinsicContentSize;
- (void)contentViewInvalidated;
@end

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
@class RCTRuntimeExecutor;
/**
* Have your module conform to this protocol to access the RuntimeExecutor.
* Only available in the bridgeless runtime.
*/
@protocol RCTRuntimeExecutorModule <NSObject>
@property (nonatomic, nullable, readwrite) RCTRuntimeExecutor *runtimeExecutor;
@end

View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTEventDispatcherProtocol.h>
/**
* Represents a touch event, which may be composed of several touches (one for every finger).
* For more information on contents of passed data structures see RCTTouchHandler.
*/
@interface RCTTouchEvent : NSObject <RCTEvent>
- (instancetype)initWithEventName:(NSString *)eventName
reactTag:(NSNumber *)reactTag
reactTouches:(NSArray<NSDictionary *> *)reactTouches
changedIndexes:(NSArray<NSNumber *> *)changedIndexes
coalescingKey:(uint16_t)coalescingKey NS_DESIGNATED_INITIALIZER;
@end

View File

@@ -0,0 +1,107 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTTouchEvent.h"
#import "RCTAssert.h"
@implementation RCTTouchEvent {
NSArray<NSDictionary *> *_reactTouches;
NSArray<NSNumber *> *_changedIndexes;
uint16_t _coalescingKey;
}
@synthesize eventName = _eventName;
@synthesize viewTag = _viewTag;
- (instancetype)initWithEventName:(NSString *)eventName
reactTag:(NSNumber *)reactTag
reactTouches:(NSArray<NSDictionary *> *)reactTouches
changedIndexes:(NSArray<NSNumber *> *)changedIndexes
coalescingKey:(uint16_t)coalescingKey
{
if (self = [super init]) {
_viewTag = reactTag;
_eventName = eventName;
_reactTouches = reactTouches;
_changedIndexes = changedIndexes;
_coalescingKey = coalescingKey;
}
return self;
}
RCT_NOT_IMPLEMENTED(-(instancetype)init)
#pragma mark - RCTEvent
- (BOOL)canCoalesce
{
return [_eventName isEqual:@"touchMove"];
}
// We coalesce only move events, while holding some assumptions that seem reasonable but there are no explicit
// guarantees about them.
- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent
{
RCTAssert(
[newEvent isKindOfClass:[RCTTouchEvent class]],
@"Touch event cannot be coalesced with any other type of event, such as provided %@",
newEvent);
RCTTouchEvent *newTouchEvent = (RCTTouchEvent *)newEvent;
RCTAssert(
[_reactTouches count] == [newTouchEvent->_reactTouches count],
@"Touch events have different number of touches. %@ %@",
self,
newEvent);
BOOL newEventIsMoreRecent = NO;
BOOL oldEventIsMoreRecent = NO;
NSInteger count = _reactTouches.count;
for (int i = 0; i < count; i++) {
NSDictionary *touch = _reactTouches[i];
NSDictionary *newTouch = newTouchEvent->_reactTouches[i];
RCTAssert(
[touch[@"identifier"] isEqual:newTouch[@"identifier"]],
@"Touch events doesn't have touches in the same order. %@ %@",
touch,
newTouch);
if ([touch[@"timestamp"] doubleValue] > [newTouch[@"timestamp"] doubleValue]) {
oldEventIsMoreRecent = YES;
} else {
newEventIsMoreRecent = YES;
}
}
RCTAssert(
!(oldEventIsMoreRecent && newEventIsMoreRecent),
@"Neither touch event is exclusively more recent than the other one. %@ %@",
_reactTouches,
newTouchEvent->_reactTouches);
return newEventIsMoreRecent ? newEvent : self;
}
+ (NSString *)moduleDotMethod
{
return @"RCTEventEmitter.receiveTouches";
}
- (NSArray *)arguments
{
return @[ RCTNormalizeInputEventName(_eventName), _reactTouches, _changedIndexes ];
}
- (uint16_t)coalescingKey
{
return _coalescingKey;
}
- (NSString *)description
{
return [NSString
stringWithFormat:@"<%@: %p; name = %@; coalescing key = %hu>", [self class], self, _eventName, _coalescingKey];
}
@end

View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <React/RCTFrameUpdate.h>
@class RCTBridge;
@interface RCTTouchHandler : UIGestureRecognizer
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
- (void)attachToView:(UIView *)view;
- (void)detachFromView:(UIView *)view;
- (void)cancel;
@end

View File

@@ -0,0 +1,377 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTTouchHandler.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTEventDispatcherProtocol.h"
#import "RCTLog.h"
#import "RCTSurfaceView.h"
#import "RCTTouchEvent.h"
#import "RCTUIManager.h"
#import "RCTUtils.h"
#import "UIView+React.h"
@interface RCTTouchHandler () <UIGestureRecognizerDelegate>
@end
// TODO: this class behaves a lot like a module, and could be implemented as a
// module if we were to assume that modules and RootViews had a 1:1 relationship
@implementation RCTTouchHandler {
__weak id<RCTEventDispatcherProtocol> _eventDispatcher;
/**
* Arrays managed in parallel tracking native touch object along with the
* native view that was touched, and the React touch data dictionary.
* These must be kept track of because `UIKit` destroys the touch targets
* if touches are canceled, and we have no other way to recover this info.
*/
NSMutableOrderedSet<UITouch *> *_nativeTouches;
NSMutableArray<NSMutableDictionary *> *_reactTouches;
NSMutableArray<UIView *> *_touchViews;
__weak UIView *_cachedRootView;
uint16_t _coalescingKey;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
RCTAssertParam(bridge);
if ((self = [super initWithTarget:nil action:NULL])) {
_eventDispatcher = bridge.eventDispatcher;
_nativeTouches = [NSMutableOrderedSet new];
_reactTouches = [NSMutableArray new];
_touchViews = [NSMutableArray new];
// `cancelsTouchesInView` and `delaysTouches*` are needed in order to be used as a top level
// event delegated recognizer. Otherwise, lower-level components not built
// using RCT, will fail to recognize gestures.
self.cancelsTouchesInView = NO;
self.delaysTouchesBegan = NO; // This is default value.
self.delaysTouchesEnded = NO;
self.delegate = self;
}
return self;
}
RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)action)
- (void)attachToView:(UIView *)view
{
RCTAssert(self.view == nil, @"RCTTouchHandler already has attached view.");
[view addGestureRecognizer:self];
}
- (void)detachFromView:(UIView *)view
{
RCTAssertParam(view);
RCTAssert(self.view == view, @"RCTTouchHandler attached to another view.");
[view removeGestureRecognizer:self];
}
#pragma mark - Bookkeeping for touch indices
- (void)_recordNewTouches:(NSSet<UITouch *> *)touches
{
for (UITouch *touch in touches) {
RCTAssert(![_nativeTouches containsObject:touch], @"Touch is already recorded. This is a critical bug.");
// Find closest React-managed touchable view
UIView *targetView = touch.view;
while (targetView) {
if (targetView.reactTag && targetView.userInteractionEnabled) {
break;
}
targetView = targetView.superview;
}
NSNumber *reactTag = [targetView reactTagAtPoint:[touch locationInView:targetView]];
if (!reactTag || !targetView.userInteractionEnabled) {
continue;
}
// Get new, unique touch identifier for the react touch
const NSUInteger RCTMaxTouches = 11; // This is the maximum supported by iDevices
NSInteger touchID = ([_reactTouches.lastObject[@"identifier"] integerValue] + 1) % RCTMaxTouches;
for (NSDictionary *reactTouch in _reactTouches) {
NSInteger usedID = [reactTouch[@"identifier"] integerValue];
if (usedID == touchID) {
// ID has already been used, try next value
touchID++;
} else if (usedID > touchID) {
// If usedID > touchID, touchID must be unique, so we can stop looking
break;
}
}
// Create touch
NSMutableDictionary *reactTouch = [[NSMutableDictionary alloc] initWithCapacity:RCTMaxTouches];
reactTouch[@"target"] = reactTag;
reactTouch[@"identifier"] = @(touchID);
// Add to arrays
[_touchViews addObject:targetView];
[_nativeTouches addObject:touch];
[_reactTouches addObject:reactTouch];
}
}
- (void)_recordRemovedTouches:(NSSet<UITouch *> *)touches
{
for (UITouch *touch in touches) {
NSUInteger index = [_nativeTouches indexOfObject:touch];
if (index == NSNotFound) {
continue;
}
[_touchViews removeObjectAtIndex:index];
[_nativeTouches removeObjectAtIndex:index];
[_reactTouches removeObjectAtIndex:index];
}
}
- (void)_updateReactTouchAtIndex:(NSInteger)touchIndex
{
UITouch *nativeTouch = _nativeTouches[touchIndex];
CGPoint windowLocation = [nativeTouch locationInView:nativeTouch.window];
RCTAssert(_cachedRootView, @"We were unable to find a root view for the touch");
CGPoint rootViewLocation = [nativeTouch.window convertPoint:windowLocation toView:_cachedRootView];
UIView *touchView = _touchViews[touchIndex];
CGPoint touchViewLocation = [nativeTouch.window convertPoint:windowLocation toView:touchView];
NSMutableDictionary *reactTouch = _reactTouches[touchIndex];
reactTouch[@"pageX"] = @(RCTSanitizeNaNValue(rootViewLocation.x, @"touchEvent.pageX"));
reactTouch[@"pageY"] = @(RCTSanitizeNaNValue(rootViewLocation.y, @"touchEvent.pageY"));
reactTouch[@"locationX"] = @(RCTSanitizeNaNValue(touchViewLocation.x, @"touchEvent.locationX"));
reactTouch[@"locationY"] = @(RCTSanitizeNaNValue(touchViewLocation.y, @"touchEvent.locationY"));
reactTouch[@"timestamp"] = @(nativeTouch.timestamp * 1000); // in ms, for JS
// TODO: force for a 'normal' touch is usually 1.0;
// should we expose a `normalTouchForce` constant somewhere (which would
// have a value of `1.0 / nativeTouch.maximumPossibleForce`)?
if (RCTForceTouchAvailable()) {
reactTouch[@"force"] = @(RCTZeroIfNaN(nativeTouch.force / nativeTouch.maximumPossibleForce));
} else if (nativeTouch.type == UITouchTypePencil) {
reactTouch[@"force"] = @(RCTZeroIfNaN(nativeTouch.force / nativeTouch.maximumPossibleForce));
reactTouch[@"altitudeAngle"] = @(RCTZeroIfNaN(nativeTouch.altitudeAngle));
}
}
/**
* Constructs information about touch events to send across the serialized
* boundary. This data should be compliant with W3C `Touch` objects. This data
* alone isn't sufficient to construct W3C `Event` objects. To construct that,
* there must be a simple receiver on the other side of the bridge that
* organizes the touch objects into `Event`s.
*
* We send the data as an array of `Touch`es, the type of action
* (start/end/move/cancel) and the indices that represent "changed" `Touch`es
* from that array.
*/
- (void)_updateAndDispatchTouches:(NSSet<UITouch *> *)touches eventName:(NSString *)eventName
{
// Update touches
NSMutableArray<NSNumber *> *changedIndexes = [NSMutableArray new];
for (UITouch *touch in touches) {
NSInteger index = [_nativeTouches indexOfObject:touch];
if (index == NSNotFound) {
continue;
}
[self _updateReactTouchAtIndex:index];
[changedIndexes addObject:@(index)];
}
if (changedIndexes.count == 0) {
return;
}
// Deep copy the touches because they will be accessed from another thread
// TODO: would it be safer to do this in the bridge or executor, rather than trusting caller?
NSMutableArray<NSDictionary *> *reactTouches = [[NSMutableArray alloc] initWithCapacity:_reactTouches.count];
for (NSDictionary *touch in _reactTouches) {
[reactTouches addObject:[touch copy]];
}
BOOL canBeCoalesced = [eventName isEqualToString:@"touchMove"];
// We increment `_coalescingKey` twice here just for sure that
// this `_coalescingKey` will not be reused by another (preceding or following) event
// (yes, even if coalescing only happens (and makes sense) on events of the same type).
if (!canBeCoalesced) {
_coalescingKey++;
}
RCTTouchEvent *event = [[RCTTouchEvent alloc] initWithEventName:eventName
reactTag:self.view.reactTag
reactTouches:reactTouches
changedIndexes:changedIndexes
coalescingKey:_coalescingKey];
if (!canBeCoalesced) {
_coalescingKey++;
}
[_eventDispatcher sendEvent:event];
}
/***
* To ensure compatibility when using UIManager.measure and RCTTouchHandler, we have to adopt
* UIManager.measure's behavior in finding a "root view".
* Usually RCTTouchHandler is already attached to a root view but in some cases (e.g. Modal),
* we are instead attached to some RCTView subtree. This is also the case when embedding some RN
* views inside a separate ViewController not controlled by RN.
* This logic will either find the nearest rootView, or go all the way to the UIWindow.
* While this is not optimal, it is exactly what UIManager.measure does, and what Touchable.js
* relies on.
* We cache it here so that we don't have to repeat it for every touch in the gesture.
*/
- (void)_cacheRootView
{
UIView *rootView = self.view;
while (rootView.superview && ![rootView isReactRootView] && ![rootView isKindOfClass:[RCTSurfaceView class]]) {
rootView = rootView.superview;
}
_cachedRootView = rootView;
}
#pragma mark - Gesture Recognizer Delegate Callbacks
static BOOL RCTAllTouchesAreCancelledOrEnded(NSSet<UITouch *> *touches)
{
for (UITouch *touch in touches) {
if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved || touch.phase == UITouchPhaseStationary) {
return NO;
}
}
return YES;
}
static BOOL RCTAnyTouchesChanged(NSSet<UITouch *> *touches)
{
for (UITouch *touch in touches) {
if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
return YES;
}
}
return NO;
}
#pragma mark - `UIResponder`-ish touch-delivery methods
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
[self _cacheRootView];
// "start" has to record new touches *before* extracting the event.
// "end"/"cancel" needs to remove the touch *after* extracting the event.
[self _recordNewTouches:touches];
[self _updateAndDispatchTouches:touches eventName:@"touchStart"];
if (self.state == UIGestureRecognizerStatePossible) {
self.state = UIGestureRecognizerStateBegan;
} else if (self.state == UIGestureRecognizerStateBegan) {
self.state = UIGestureRecognizerStateChanged;
}
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
[self _updateAndDispatchTouches:touches eventName:@"touchMove"];
self.state = UIGestureRecognizerStateChanged;
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self _updateAndDispatchTouches:touches eventName:@"touchEnd"];
if (RCTAllTouchesAreCancelledOrEnded(event.allTouches)) {
self.state = UIGestureRecognizerStateEnded;
} else if (RCTAnyTouchesChanged(event.allTouches)) {
self.state = UIGestureRecognizerStateChanged;
}
[self _recordRemovedTouches:touches];
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
[self _updateAndDispatchTouches:touches eventName:@"touchCancel"];
if (RCTAllTouchesAreCancelledOrEnded(event.allTouches)) {
self.state = UIGestureRecognizerStateCancelled;
} else if (RCTAnyTouchesChanged(event.allTouches)) {
self.state = UIGestureRecognizerStateChanged;
}
[self _recordRemovedTouches:touches];
}
- (BOOL)canPreventGestureRecognizer:(__unused UIGestureRecognizer *)preventedGestureRecognizer
{
return NO;
}
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer
{
// We fail in favour of other external gesture recognizers.
// iOS will ask `delegate`'s opinion about this gesture recognizer little bit later.
return ![preventingGestureRecognizer.view isDescendantOfView:self.view];
}
- (void)reset
{
if (_nativeTouches.count != 0) {
[self _updateAndDispatchTouches:_nativeTouches.set eventName:@"touchCancel"];
[_nativeTouches removeAllObjects];
[_reactTouches removeAllObjects];
[_touchViews removeAllObjects];
_cachedRootView = nil;
}
}
#pragma mark - Other
- (void)cancel
{
self.enabled = NO;
self.enabled = YES;
}
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(__unused UIGestureRecognizer *)gestureRecognizer
shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
// Same condition for `failure of` as for `be prevented by`.
return [self canBePreventedByGestureRecognizer:otherGestureRecognizer];
}
@end

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* A protocol that allows TurboModules to do lookup on other TurboModules.
* Calling these methods may cause a module to be synchronously instantiated.
*/
@protocol RCTTurboModuleRegistry <NSObject>
- (id)moduleForName:(const char *)moduleName;
/**
* Rationale:
* When TurboModules lookup other modules by name, we first check the TurboModule
* registry to see if a TurboModule exists with the respective name. In this case,
* we don't want a RedBox to be raised if the TurboModule isn't found.
*
* This method is deprecated and will be deleted after the migration from
* TurboModules to TurboModules is complete.
*/
- (id)moduleForName:(const char *)moduleName warnOnLookupFailure:(BOOL)warnOnLookupFailure;
- (BOOL)moduleIsInitialized:(const char *)moduleName;
@end

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
/**
* An abstract interface used by request handler modules to send
* data back over the bridge back to JS.
*/
@protocol RCTURLRequestDelegate <NSObject>
/**
* Call this when you send request data to the server. This is used to track
* upload progress, so should be called multiple times for large request bodies.
*/
- (void)URLRequest:(id)requestToken didSendDataWithProgress:(int64_t)bytesSent;
/**
* Call this when you first receives a response from the server. This should
* include response headers, etc.
*/
- (void)URLRequest:(id)requestToken didReceiveResponse:(NSURLResponse *)response;
/**
* Call this when you receive data from the server. This can be called multiple
* times with partial data chunks, or just once with the full data packet.
*/
- (void)URLRequest:(id)requestToken didReceiveData:(NSData *)data;
/**
* Call this when the request is complete and/or if an error is encountered.
* For a successful request, the error parameter should be nil.
*/
- (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error;
@end

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTBridgeModule.h>
#import <React/RCTURLRequestDelegate.h>
/**
* Provides the interface needed to register a request handler. Request handlers
* are also bridge modules, so should be registered using RCT_EXPORT_MODULE().
*/
@protocol RCTURLRequestHandler <RCTBridgeModule>
/**
* Indicates whether this handler is capable of processing the specified
* request. Typically the handler would examine the scheme/protocol of the
* request URL (and possibly the HTTP method and/or headers) to determine this.
*/
- (BOOL)canHandleRequest:(NSURLRequest *)request;
/**
* Send a network request and call the delegate with the response data. The
* method should return a token, which can be anything, including the request
* itself. This will be used later to refer to the request in callbacks. The
* `sendRequest:withDelegate:` method *must* return before calling any of the
* delegate methods, or the delegate won't recognize the token.
* Following common Objective-C pattern, `delegate` will not be retained.
*/
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate;
@optional
/**
* Not all request types can be cancelled, but this method can be implemented
* for ones that can. It should be used to free up any resources on ongoing
* processes associated with the request.
*/
- (void)cancelRequest:(id)requestToken;
/**
* If more than one RCTURLRequestHandler responds YES to `canHandleRequest:`
* then `handlerPriority` is used to determine which one to use. The handler
* with the highest priority will be selected. Default priority is zero. If
* two or more valid handlers have the same priority, the selection order is
* undefined.
*/
- (float)handlerPriority;
@end

View File

@@ -0,0 +1,198 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <tgmath.h>
#import <CoreGraphics/CoreGraphics.h>
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <React/RCTAssert.h>
#import <React/RCTDefines.h>
NS_ASSUME_NONNULL_BEGIN
// Whether the New Architecture is enabled or not
RCT_EXTERN BOOL RCTIsNewArchEnabled(void);
RCT_EXTERN void RCTSetNewArchEnabled(BOOL enabled);
// JSON serialization/deserialization
RCT_EXTERN NSString *__nullable RCTJSONStringify(id __nullable jsonObject, NSError **error);
RCT_EXTERN id __nullable RCTJSONParse(NSString *__nullable jsonString, NSError **error);
RCT_EXTERN id __nullable RCTJSONParseMutable(NSString *__nullable jsonString, NSError **error);
// Sanitize a JSON object by stripping invalid types and/or NaN values
RCT_EXTERN id RCTJSONClean(id object);
// Get MD5 hash of a string
RCT_EXTERN NSString *RCTMD5Hash(NSString *string);
// Check if we are currently on the main queue (not to be confused with
// the main thread, which is not necessarily the same thing)
// https://twitter.com/olebegemann/status/738656134731599872
RCT_EXTERN BOOL RCTIsMainQueue(void);
// Execute the specified block on the main queue. Unlike dispatch_async()
// this will execute immediately if we're already on the main queue.
RCT_EXTERN void RCTExecuteOnMainQueue(dispatch_block_t block);
// Legacy function to execute the specified block on the main queue synchronously.
// Please do not use this unless you know what you're doing.
RCT_EXTERN void RCTUnsafeExecuteOnMainQueueSync(dispatch_block_t block);
// Get screen scale, can be only used on main
RCT_EXTERN void RCTComputeScreenScale(void);
// Get screen metrics in a thread-safe way
RCT_EXTERN CGFloat RCTScreenScale(void);
RCT_EXTERN CGFloat RCTFontSizeMultiplier(void);
RCT_EXTERN CGSize RCTScreenSize(void);
RCT_EXTERN CGSize RCTViewportSize(void);
// Round float coordinates to nearest whole screen pixel (not point)
RCT_EXTERN CGFloat RCTRoundPixelValue(CGFloat value);
RCT_EXTERN CGFloat RCTCeilPixelValue(CGFloat value);
RCT_EXTERN CGFloat RCTFloorPixelValue(CGFloat value);
// Convert a size in points to pixels, rounded up to the nearest integral size
RCT_EXTERN CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale);
// Method swizzling
RCT_EXTERN void RCTSwapClassMethods(Class cls, SEL original, SEL replacement);
RCT_EXTERN void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement);
RCT_EXTERN void RCTSwapInstanceMethodWithBlock(Class cls, SEL original, id replacementBlock, SEL replacementSelector);
// Module subclass support
RCT_EXTERN BOOL RCTClassOverridesClassMethod(Class cls, SEL selector);
RCT_EXTERN BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector);
// Creates a standardized error object to return in callbacks
RCT_EXTERN NSDictionary<NSString *, id>
*RCTMakeError(NSString *message, id __nullable toStringify, NSDictionary<NSString *, id> *__nullable extraData);
RCT_EXTERN NSDictionary<NSString *, id> *
RCTMakeAndLogError(NSString *message, id __nullable toStringify, NSDictionary<NSString *, id> *__nullable extraData);
RCT_EXTERN NSDictionary<NSString *, id> *RCTJSErrorFromNSError(NSError *error);
RCT_EXTERN NSDictionary<NSString *, id>
*RCTJSErrorFromCodeMessageAndNSError(NSString *code, NSString *message, NSError *__nullable error);
// The default error code to use as the `code` property for callback error objects
RCT_EXTERN NSString *const RCTErrorUnspecified;
// Returns YES if React is running in a test environment
RCT_EXTERN BOOL RCTRunningInTestEnvironment(void);
// Returns YES if React is running in an iOS App Extension
RCT_EXTERN BOOL RCTRunningInAppExtension(void);
// Returns the shared UIApplication instance, or nil if running in an App Extension
RCT_EXTERN UIApplication *__nullable RCTSharedApplication(void);
// Returns the current main window, useful if you need to access the root view
// or view controller
RCT_EXTERN UIWindow *__nullable RCTKeyWindow(void);
// Returns the presented view controller, useful if you need
// e.g. to present a modal view controller or alert over it
RCT_EXTERN UIViewController *__nullable RCTPresentedViewController(void);
// Retrieve current window UIStatusBarManager
RCT_EXTERN UIStatusBarManager *__nullable RCTUIStatusBarManager(void) API_AVAILABLE(ios(13));
// Does this device support force touch (aka 3D Touch)?
RCT_EXTERN BOOL RCTForceTouchAvailable(void);
// Create an NSError in the RCTErrorDomain
RCT_EXTERN NSError *RCTErrorWithMessage(NSString *message);
// Creates an NSError from given an NSException
RCT_EXTERN NSError *RCTErrorWithNSException(NSException *exception);
// Convert nil values to NSNull, and vice-versa
#define RCTNullIfNil(value) ((value) ?: (id)kCFNull)
#define RCTNilIfNull(value) \
({ \
__typeof__(value) t = (value); \
(id) t == (id)kCFNull ? (__typeof(value))nil : t; \
})
// Convert NaN or infinite values to zero, as these aren't JSON-safe
RCT_EXTERN double RCTZeroIfNaN(double value);
// Returns `0` and log special warning if value is NaN or INF.
RCT_EXTERN double RCTSanitizeNaNValue(double value, NSString *property);
// Convert data to a Base64-encoded data URL
RCT_EXTERN NSURL *RCTDataURL(NSString *mimeType, NSData *data);
// Gzip functionality - compression level in range 0 - 1 (-1 for default)
RCT_EXTERN NSData *__nullable RCTGzipData(NSData *__nullable data, float level);
// Returns the relative path within the main bundle for an absolute URL
// (or nil, if the URL does not specify a path within the main bundle)
RCT_EXTERN NSString *__nullable RCTBundlePathForURL(NSURL *__nullable URL);
// Returns the Path of Library directory
RCT_EXTERN NSString *__nullable RCTLibraryPath(void);
// Returns the relative path within the library for an absolute URL
// (or nil, if the URL does not specify a path within the Library directory)
RCT_EXTERN NSString *__nullable RCTLibraryPathForURL(NSURL *__nullable URL);
// Determines if a given image URL refers to a image in bundle
RCT_EXTERN BOOL RCTIsBundleAssetURL(NSURL *__nullable imageURL);
// Determines if a given image URL refers to a image in library
RCT_EXTERN BOOL RCTIsLibraryAssetURL(NSURL *__nullable imageURL);
// Determines if a given image URL refers to a local image
RCT_EXTERN BOOL RCTIsLocalAssetURL(NSURL *__nullable imageURL);
// Returns an UIImage for a local image asset. Returns nil if the URL
// does not correspond to a local asset.
RCT_EXTERN UIImage *__nullable RCTImageFromLocalAssetURL(NSURL *imageURL);
// Only used in case when RCTImageFromLocalAssetURL fails to get an image
// This method basically checks for the image in the bundle location, instead
// of the CodePush location
RCT_EXTERN UIImage *__nullable RCTImageFromLocalBundleAssetURL(NSURL *imageURL);
// Creates a new, unique temporary file path with the specified extension
RCT_EXTERN NSString *__nullable RCTTempFilePath(NSString *__nullable extension, NSError **error);
// Get RGBA components of CGColor
RCT_EXTERN void RCTGetRGBAColorComponents(CGColorRef color, CGFloat rgba[_Nonnull 4]);
// Converts a CGColor to a hex string
RCT_EXTERN NSString *RCTColorToHexString(CGColorRef color);
// Get standard localized string (if it exists)
RCT_EXTERN NSString *RCTUIKitLocalizedString(NSString *string);
// Get a human readable type string from an NSObject. For example NSString becomes string
RCT_EXTERN NSString *RCTHumanReadableType(NSObject *obj);
// URL manipulation
RCT_EXTERN NSString *__nullable RCTGetURLQueryParam(NSURL *__nullable URL, NSString *param);
RCT_EXTERN NSURL *__nullable
RCTURLByReplacingQueryParam(NSURL *__nullable URL, NSString *param, NSString *__nullable value);
// Given a string, drop common RN prefixes (RCT, RK, etc.)
RCT_EXTERN NSString *RCTDropReactPrefixes(NSString *s);
RCT_EXTERN BOOL RCTUIManagerTypeForTagIsFabric(NSNumber *reactTag);
RCT_EXTERN BOOL RCTValidateTypeOfViewCommandArgument(
NSObject *obj,
id expectedClass,
const NSString *expectedType,
const NSString *componentName,
const NSString *commandName,
const NSString *argPos);
RCT_EXTERN BOOL RCTIsAppActive(void);
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface RCTUtilsUIOverride : NSObject
/**
Set the global presented view controller instance override.
*/
+ (void)setPresentedViewController:(UIViewController *)presentedViewController;
+ (UIViewController *)presentedViewController;
+ (BOOL)hasPresentedViewController;
@end

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTUtilsUIOverride.h"
@implementation RCTUtilsUIOverride
static UIViewController *_presentedViewController = nil;
+ (void)setPresentedViewController:(UIViewController *)presentedViewController
{
_presentedViewController = presentedViewController;
}
+ (UIViewController *)presentedViewController
{
return _presentedViewController;
}
+ (BOOL)hasPresentedViewController
{
return _presentedViewController != nil;
}
@end

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTDefines.h>
RCT_EXTERN NSString *const RCTVersionMajor;
RCT_EXTERN NSString *const RCTVersionMinor;
RCT_EXTERN NSString *const RCTVersionPatch;
RCT_EXTERN NSString *const RCTVersionPrerelease;
RCT_EXTERN NSDictionary *RCTGetReactNativeVersion(void);

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated by scripts/releases/set-rn-version.js
*/
#import "RCTVersion.h"
NSString* const RCTVersionMajor = @"major";
NSString* const RCTVersionMinor = @"minor";
NSString* const RCTVersionPatch = @"patch";
NSString* const RCTVersionPrerelease = @"prerelease";
NSDictionary* RCTGetReactNativeVersion(void)
{
static NSDictionary* __rnVersion;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^(void){
__rnVersion = @{
RCTVersionMajor: @(0),
RCTVersionMinor: @(74),
RCTVersionPatch: @(0),
RCTVersionPrerelease: [NSNull null],
};
});
return __rnVersion;
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTSurfacePresenterStub.h>
#import <React/RCTUIManager.h>
#import "RCTBridge.h"
#import "RCTBridgeModule.h"
@implementation RCTViewRegistry {
RCTBridgelessComponentViewProvider _bridgelessComponentViewProvider;
__weak RCTBridge *_bridge;
}
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
}
- (void)setBridgelessComponentViewProvider:(RCTBridgelessComponentViewProvider)bridgelessComponentViewProvider
{
_bridgelessComponentViewProvider = bridgelessComponentViewProvider;
}
- (UIView *)viewForReactTag:(NSNumber *)reactTag
{
UIView *view = nil;
RCTBridge *bridge = _bridge;
if (bridge) {
view = [bridge.uiManager viewForReactTag:reactTag];
}
if (view == nil && _bridgelessComponentViewProvider) {
view = _bridgelessComponentViewProvider(reactTag);
}
return view;
}
- (void)addUIBlock:(RCTViewRegistryUIBlock)block
{
if (!block) {
return;
}
__weak __typeof(self) weakSelf = self;
if (_bridge) {
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
__typeof(self) strongSelf = weakSelf;
if (strongSelf) {
block(strongSelf);
}
}];
} else {
RCTExecuteOnMainQueue(^{
__typeof(self) strongSelf = weakSelf;
if (strongSelf) {
block(strongSelf);
}
});
}
}
@end

View File

@@ -0,0 +1,90 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTSurfaceProtocol.h>
#import <React/RCTSurfaceStage.h>
NS_ASSUME_NONNULL_BEGIN
@class RCTBridge;
@class RCTSurfaceView;
@protocol RCTSurfaceDelegate;
/**
* RCTSurface instance represents React Native-powered piece of a user interface
* which can be a full-screen app, separate modal view controller,
* or even small widget.
* It is called "Surface".
*
* The RCTSurface instance is completely thread-safe by design;
* it can be created on any thread, and any its method can be called from
* any thread (if the opposite is not mentioned explicitly).
*
* The primary goals of the RCTSurface are:
* * ability to measure and layout the surface in a thread-safe
* and synchronous manner;
* * ability to create a UIView instance on demand (later);
* * ability to communicate the current stage of the surface granularly.
*/
@interface RCTSurface : NSObject <RCTSurfaceProtocol>
- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties;
#pragma mark - Layout: Setting the size constrains
/**
* Previously set `minimumSize` layout constraint.
* Defaults to `{0, 0}`.
*/
@property (atomic, assign, readonly) CGSize minimumSize;
/**
* Previously set `maximumSize` layout constraint.
* Defaults to `{CGFLOAT_MAX, CGFLOAT_MAX}`.
*/
@property (atomic, assign, readonly) CGSize maximumSize;
/**
* Simple shortcut to `-[RCTSurface setMinimumSize:size maximumSize:size]`.
*/
- (void)setSize:(CGSize)size;
#pragma mark - Synchronous waiting
/**
* Synchronously blocks the current thread up to given `timeout` until
* the Surface reaches `stage`.
* Limitations:
* - Do nothing, if called on `UIManager` queue.
* - Calling on the main queue with `RCTSurfaceStageSurfaceDidInitialMounting`
* stage temporary is not supported; in this case the stage will be
* downgraded to `RCTSurfaceStageSurfaceDidInitialLayout`.
*/
- (BOOL)synchronouslyWaitForStage:(RCTSurfaceStage)stage timeout:(NSTimeInterval)timeout;
#pragma mark - Mounting/Unmounting of React components
/**
* Mount the React component specified by the given moduleName. This is typically
* calling runApplication.js from the native side.
*/
- (void)mountReactComponentWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
params:(NSDictionary *)params;
/**
* Unmount the React component specified by the given rootViewTag, called from native.
*/
- (void)unmountReactComponentWithBridge:(RCTBridge *)bridge rootViewTag:(NSNumber *)rootViewTag;
@end
NS_ASSUME_NONNULL_END

Some files were not shown because too many files have changed in this diff Show More