first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesCore/EXDefines.h>
#import <ExpoModulesCore/EXInternalModule.h>
NS_ASSUME_NONNULL_BEGIN
// Register a subclass of this class in EXModuleRegistryProvider
// to export an instance of this module to client code.
// Check documentation of the adapter appropriate to your platform
// to find out how to access constants and methods exported by the modules.
@interface EXExportedModule : NSObject <EXInternalModule, NSCopying>
- (NSDictionary *)constantsToExport;
+ (const NSString *)exportedModuleName;
- (NSDictionary<NSString *, NSString *> *)getExportedMethods;
- (void)callExportedMethod:(NSString *)methodName withArguments:(NSArray *)arguments resolver:(EXPromiseResolveBlock)resolver rejecter:(EXPromiseRejectBlock)rejecter;
- (dispatch_queue_t)methodQueue;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,171 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <objc/runtime.h>
#import <ExpoModulesCore/EXExportedModule.h>
#define QUOTE(str) #str
#define EXPAND_AND_QUOTE(str) QUOTE(str)
#define EX_IS_METHOD_EXPORTED(methodName) \
[methodName hasPrefix:@EXPAND_AND_QUOTE(EX_EXPORTED_METHODS_PREFIX)]
static const NSString *noNameExceptionName = @"No custom +(const NSString *)exportedModuleName implementation.";
static const NSString *noNameExceptionReasonFormat = @"You've subclassed an EXExportedModule in %@, but didn't override the +(const NSString *)exportedModuleName method. Override this method and return a name for your exported module.";
static const NSRegularExpression *selectorRegularExpression = nil;
static dispatch_once_t selectorRegularExpressionOnceToken = 0;
@interface EXExportedModule ()
@property (nonatomic, strong) dispatch_queue_t methodQueue;
@property (nonatomic, strong) NSDictionary<NSString *, NSString *> *exportedMethods;
@end
@implementation EXExportedModule
- (instancetype)init
{
return self = [super init];
}
- (instancetype)copyWithZone:(NSZone *)zone
{
return self;
}
+ (const NSArray<Protocol *> *)exportedInterfaces {
return nil;
}
+ (const NSString *)exportedModuleName
{
NSString *reason = [NSString stringWithFormat:(NSString *)noNameExceptionReasonFormat, [self description]];
@throw [NSException exceptionWithName:(NSString *)noNameExceptionName
reason:reason
userInfo:nil];
}
- (NSDictionary *)constantsToExport
{
return nil;
}
- (dispatch_queue_t)methodQueue
{
if (!_methodQueue) {
NSString *queueName = [NSString stringWithFormat:@"expo.modules.%@Queue", [[self class] exportedModuleName]];
_methodQueue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
}
return _methodQueue;
}
# pragma mark - Exported methods
- (NSDictionary<NSString *, NSString *> *)getExportedMethods
{
if (_exportedMethods) {
return _exportedMethods;
}
NSMutableDictionary<NSString *, NSString *> *exportedMethods = [NSMutableDictionary dictionary];
Class klass = [self class];
while (klass) {
unsigned int methodsCount;
Method *methodsDescriptions = class_copyMethodList(object_getClass(klass), &methodsCount);
@try {
for(int i = 0; i < methodsCount; i++) {
Method method = methodsDescriptions[i];
SEL methodSelector = method_getName(method);
NSString *methodName = NSStringFromSelector(methodSelector);
if (EX_IS_METHOD_EXPORTED(methodName)) {
IMP imp = method_getImplementation(method);
const EXMethodInfo *info = ((const EXMethodInfo *(*)(id, SEL))imp)(klass, methodSelector);
NSString *fullSelectorName = [NSString stringWithUTF8String:info->objcName];
// `objcName` constains a method declaration string
// (eg. `doSth:(NSString *)string options:(NSDictionary *)options`)
// We only need a selector string (eg. `doSth:options:`)
NSString *simpleSelectorName = [self selectorNameFromName:fullSelectorName];
exportedMethods[[NSString stringWithUTF8String:info->jsName]] = simpleSelectorName;
}
}
}
@finally {
free(methodsDescriptions);
}
klass = [klass superclass];
}
_exportedMethods = exportedMethods;
return _exportedMethods;
}
- (NSString *)selectorNameFromName:(NSString *)nameString
{
dispatch_once(&selectorRegularExpressionOnceToken, ^{
selectorRegularExpression = [NSRegularExpression regularExpressionWithPattern:@"\\(.+?\\).+?\\b\\s*" options:NSRegularExpressionCaseInsensitive error:nil];
});
return [selectorRegularExpression stringByReplacingMatchesInString:nameString options:0 range:NSMakeRange(0, [nameString length]) withTemplate:@""];
}
static const NSNumber *trueValue;
- (void)callExportedMethod:(NSString *)methodName withArguments:(NSArray *)arguments resolver:(EXPromiseResolveBlock)resolve rejecter:(EXPromiseRejectBlock)reject
{
trueValue = [NSNumber numberWithBool:YES];
const NSString *moduleName = [[self class] exportedModuleName];
NSString *methodDeclaration = _exportedMethods[methodName];
if (methodDeclaration == nil) {
NSString *reason = [NSString stringWithFormat:@"Module '%@' does not export method '%@'.", moduleName, methodName];
reject(@"E_NO_METHOD", reason, nil);
return;
}
SEL selector = NSSelectorFromString(methodDeclaration);
NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector];
if (methodSignature == nil) {
// This in fact should never happen -- if we have a methodDeclaration for an exported method
// it means that it has been exported with EX_IMPORT_METHOD and if we cannot find method signature
// for the cached selector either the macro or the -selectorNameFromName is faulty.
NSString *reason = [NSString stringWithFormat:@"Module '%@' does not implement method for selector '%@'.", moduleName, NSStringFromSelector(selector)];
reject(@"E_NO_METHOD", reason, nil);
return;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setTarget:self];
[invocation setSelector:selector];
[arguments enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj != [NSNull null]) {
[invocation setArgument:&obj atIndex:(2 + idx)];
}
// According to objc.h, the BOOL type can be represented by `bool` or `signed char` so
// getArgumentTypeAtIndex can return _C_BOOL (when `bool`) or _C_CHR (when `signed char`)
#if OBJC_BOOL_IS_BOOL
if ([methodSignature getArgumentTypeAtIndex:(2 + idx)][0] == _C_BOOL) {
// We need this intermediary variable, see
// https://stackoverflow.com/questions/11061166/pointer-to-bool-in-objective-c
BOOL value = [obj boolValue];
[invocation setArgument:&value atIndex:(2 + idx)];
}
#else // BOOL is represented by `signed char`
if ([methodSignature getArgumentTypeAtIndex:(2 + idx)][0] == _C_CHR){
BOOL value = [obj charValue];
[invocation setArgument:&value atIndex:(2 + idx)];
}
#endif
}];
[invocation setArgument:&resolve atIndex:(2 + [arguments count])];
[invocation setArgument:&reject atIndex:([arguments count] + 2 + 1)];
[invocation retainArguments];
[invocation invoke];
}
@end

View File

@@ -0,0 +1,15 @@
// Copyright © 2015 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface EXSingletonModule : NSObject
+ (const NSString *)name;
- (const NSInteger)priority;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,18 @@
// Copyright © 2015 650 Industries. All rights reserved.
#import <ExpoModulesCore/EXSingletonModule.h>
@implementation EXSingletonModule
+ (const NSString *)name
{
NSAssert(NO, @"[EXSingletonModule name] method not implemented, you must override it in subclasses.");
return nil;
}
- (const NSInteger)priority
{
return 0;
}
@end

View File

@@ -0,0 +1,27 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesCore/Platform.h>
#import <ExpoModulesCore/EXInternalModule.h>
#import <ExpoModulesCore/EXUtilitiesInterface.h>
#import <ExpoModulesCore/EXModuleRegistryConsumer.h>
NS_ASSUME_NONNULL_BEGIN
@interface EXUtilities : NSObject <EXInternalModule, EXUtilitiesInterface, EXModuleRegistryConsumer>
+ (void)performSynchronouslyOnMainThread:(nonnull void (^)(void))block;
+ (CGFloat)screenScale;
+ (nullable UIColor *)UIColor:(nullable id)json;
+ (nullable NSDate *)NSDate:(nullable id)json;
+ (nonnull NSString *)hexStringWithCGColor:(nonnull CGColorRef)color;
- (nullable UIViewController *)currentViewController;
- (nullable NSDictionary *)launchOptions;
+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error;
@end
NS_ASSUME_NONNULL_END

231
node_modules/expo-modules-core/ios/Legacy/EXUtilities.m generated vendored Normal file
View File

@@ -0,0 +1,231 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <ExpoModulesCore/EXDefines.h>
#import <ExpoModulesCore/EXUtilities.h>
#import <React/RCTLog.h>
@interface EXUtilities ()
@property (nonatomic, nullable, weak) EXModuleRegistry *moduleRegistry;
@end
@protocol EXUtilService
- (UIViewController *)currentViewController;
- (nullable NSDictionary *)launchOptions;
@end
@implementation EXUtilities
EX_REGISTER_MODULE();
+ (const NSArray<Protocol *> *)exportedInterfaces
{
return @[@protocol(EXUtilitiesInterface)];
}
- (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry
{
_moduleRegistry = moduleRegistry;
}
- (nullable NSDictionary *)launchOptions
{
id<EXUtilService> utilService = [_moduleRegistry getSingletonModuleForName:@"Util"];
return [utilService launchOptions];
}
- (UIViewController *)currentViewController
{
#if TARGET_OS_IOS || TARGET_OS_TV
id<EXUtilService> utilService = [_moduleRegistry getSingletonModuleForName:@"Util"];
if (utilService != nil) {
return [utilService currentViewController];
}
UIViewController *controller = [[[UIApplication sharedApplication] keyWindow] rootViewController];
UIViewController *presentedController = controller.presentedViewController;
while (presentedController && ![presentedController isBeingDismissed]) {
controller = presentedController;
presentedController = controller.presentedViewController;
}
return controller;
#elif TARGET_OS_OSX
// Even though the function's return type is `UIViewController`, react-native-macos will alias `NSViewController` to `UIViewController`.
return [[[NSApplication sharedApplication] keyWindow] contentViewController];
#endif
}
+ (void)performSynchronouslyOnMainThread:(void (^)(void))block
{
if ([NSThread isMainThread]) {
block();
} else {
dispatch_sync(dispatch_get_main_queue(), block);
}
}
// Copied from RN
+ (BOOL)isMainQueue
{
static void *mainQueueKey = &mainQueueKey;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dispatch_queue_set_specific(dispatch_get_main_queue(),
mainQueueKey, mainQueueKey, NULL);
});
return dispatch_get_specific(mainQueueKey) == mainQueueKey;
}
// Copied from RN
+ (void)unsafeExecuteOnMainQueueOnceSync:(dispatch_once_t *)onceToken block:(dispatch_block_t)block
{
// The solution was borrowed from a post by Ben Alpert:
// https://benalpert.com/2014/04/02/dispatch-once-initialization-on-the-main-thread.html
// See also: https://www.mikeash.com/pyblog/friday-qa-2014-06-06-secrets-of-dispatch_once.html
if ([self isMainQueue]) {
dispatch_once(onceToken, block);
} else {
if (DISPATCH_EXPECT(*onceToken == 0L, NO)) {
dispatch_sync(dispatch_get_main_queue(), ^{
dispatch_once(onceToken, block);
});
}
}
}
// Copied from RN
+ (CGFloat)screenScale
{
static dispatch_once_t onceToken;
static CGFloat scale;
[self unsafeExecuteOnMainQueueOnceSync:&onceToken block:^{
#if TARGET_OS_IOS || TARGET_OS_TV
scale = [UIScreen mainScreen].scale;
#elif TARGET_OS_OSX
scale = [NSScreen mainScreen].backingScaleFactor;
#endif
}];
return scale;
}
// Kind of copied from RN to make UIColor:(id)json work
+ (NSArray<NSNumber *> *)NSNumberArray:(id)json
{
return json;
}
+ (NSNumber *)NSNumber:(id)json
{
return json;
}
+ (CGFloat)CGFloat:(id)json
{
return [[self NSNumber:json] floatValue];
}
+ (NSInteger)NSInteger:(id)json
{
return [[self NSNumber:json] integerValue];
}
+ (NSUInteger)NSUInteger:(id)json
{
return [[self NSNumber:json] unsignedIntegerValue];
}
// Copied from RN
+ (UIColor *)UIColor:(id)json
{
if (!json) {
return nil;
}
if ([json isKindOfClass:[NSArray class]]) {
NSArray *components = [self NSNumberArray:json];
CGFloat alpha = components.count > 3 ? [self CGFloat:components[3]] : 1.0;
return [UIColor colorWithRed:[self CGFloat:components[0]]
green:[self CGFloat:components[1]]
blue:[self CGFloat:components[2]]
alpha:alpha];
} else if ([json isKindOfClass:[NSNumber class]]) {
NSUInteger argb = [self NSUInteger:json];
CGFloat a = ((argb >> 24) & 0xFF) / 255.0;
CGFloat r = ((argb >> 16) & 0xFF) / 255.0;
CGFloat g = ((argb >> 8) & 0xFF) / 255.0;
CGFloat b = (argb & 0xFF) / 255.0;
return [UIColor colorWithRed:r green:g blue:b alpha:a];
} else {
RCTLogInfo(@"%@ cannot be converted to a UIColor", json);
return nil;
}
}
// Copied from RN
+ (NSDate *)NSDate:(id)json
{
if ([json isKindOfClass:[NSNumber class]]) {
return [NSDate dateWithTimeIntervalSince1970:[json doubleValue] / 1000.0];
} else if ([json isKindOfClass:[NSString class]]) {
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ";
formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
formatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
});
NSDate *date = [formatter dateFromString:json];
if (!date) {
RCTLogError(@"JSON String '%@' could not be interpreted as a date. "
"Expected format: YYYY-MM-DD'T'HH:mm:ss.sssZ", json);
}
return date;
} else if (json) {
RCTLogError(json, @"a date");
}
return nil;
}
// https://stackoverflow.com/questions/14051807/how-can-i-get-a-hex-string-from-uicolor-or-from-rgb
+ (NSString *)hexStringWithCGColor:(CGColorRef)color
{
const CGFloat *components = CGColorGetComponents(color);
size_t count = CGColorGetNumberOfComponents(color);
if (count == 2) {
return [NSString stringWithFormat:@"#%02lX%02lX%02lX",
lroundf(components[0] * 255.0),
lroundf(components[0] * 255.0),
lroundf(components[0] * 255.0)];
} else {
return [NSString stringWithFormat:@"#%02lX%02lX%02lX",
lroundf(components[0] * 255.0),
lroundf(components[1] * 255.0),
lroundf(components[2] * 255.0)];
}
}
+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error
{
@try {
tryBlock();
return YES;
}
@catch (NSException *exception) {
*error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:exception.userInfo];
return NO;
}
}
@end

View File

@@ -0,0 +1,36 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesCore/EXInternalModule.h>
#import <ExpoModulesCore/EXExportedModule.h>
#import <ExpoModulesCore/EXModuleRegistryDelegate.h>
NS_ASSUME_NONNULL_BEGIN
@interface EXModuleRegistry : NSObject
- (instancetype)initWithInternalModules:(NSSet<id<EXInternalModule>> *)internalModules
exportedModules:(NSSet<EXExportedModule *> *)exportedModules
singletonModules:(NSSet *)singletonModules;
- (void)registerInternalModule:(id<EXInternalModule>)internalModule;
- (void)registerExportedModule:(EXExportedModule *)exportedModule;
- (void)setDelegate:(id<EXModuleRegistryDelegate>)delegate;
// Call this method once all the modules are set up and registered in the registry.
- (void)initialize;
- (EXExportedModule *)getExportedModuleForName:(NSString *)name;
- (EXExportedModule *)getExportedModuleOfClass:(Class)moduleClass;
- (id)getModuleImplementingProtocol:(Protocol *)protocol;
- (id)getSingletonModuleForName:(NSString *)singletonModuleName;
- (NSArray<id<EXInternalModule>> *)getAllInternalModules;
- (NSArray<EXExportedModule *> *)getAllExportedModules;
- (NSArray *)getAllSingletonModules;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,207 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <objc/runtime.h>
#import <ExpoModulesCore/EXModuleRegistry.h>
#import <ExpoModulesCore/EXModuleRegistryConsumer.h>
#import <ExpoModulesCore/Swift.h>
@interface EXModuleRegistry ()
@property (nonatomic, weak) id<EXModuleRegistryDelegate> delegate;
@property NSMutableSet<id<EXInternalModule>> *internalModulesSet;
@property NSMapTable<Protocol *, id<EXInternalModule>> *internalModules;
@property NSMapTable<Protocol *, NSMutableArray<id<EXInternalModule>> *> *internalModulesPreResolution;
@property NSMapTable<Class, EXExportedModule *> *exportedModulesByClass;
@property NSMutableDictionary<const NSString *, EXExportedModule *> *exportedModules;
@property NSMutableDictionary<const NSString *, id> *singletonModules;
@property NSPointerArray *registryConsumers;
@end
@implementation EXModuleRegistry
# pragma mark - Lifecycle
- (instancetype)init
{
if (self = [super init]) {
_internalModulesPreResolution = [NSMapTable weakToStrongObjectsMapTable];
_exportedModulesByClass = [NSMapTable weakToWeakObjectsMapTable];
_exportedModules = [NSMutableDictionary dictionary];
_singletonModules = [NSMutableDictionary dictionary];
_registryConsumers = [NSPointerArray weakObjectsPointerArray];
}
return self;
}
- (instancetype)initWithInternalModules:(NSSet<id<EXInternalModule>> *)internalModules
exportedModules:(NSSet<EXExportedModule *> *)exportedModules
singletonModules:(NSSet *)singletonModules
{
if (self = [self init]) {
for (id<EXInternalModule> internalModule in internalModules) {
[self registerInternalModule:internalModule];
}
for (EXExportedModule *exportedModule in exportedModules) {
[self registerExportedModule:exportedModule];
}
for (id singletonModule in singletonModules) {
[self registerSingletonModule:singletonModule];
}
}
return self;
}
- (void)setDelegate:(id<EXModuleRegistryDelegate>)delegate
{
_delegate = delegate;
}
// Fills in _internalModules and _internalModulesSet
- (void)resolveInternalModulesConflicts
{
if (_internalModules) {
// Conflict resolution has already been completed.
return;
}
_internalModules = [NSMapTable weakToStrongObjectsMapTable];
_internalModulesSet = [NSMutableSet new];
for (Protocol *protocol in _internalModulesPreResolution) {
NSArray<id<EXInternalModule>> *conflictingModules = [_internalModulesPreResolution objectForKey:protocol];
id<EXInternalModule> resolvedModule;
if ([conflictingModules count] > 1 && _delegate) {
resolvedModule = [_delegate pickInternalModuleImplementingInterface:protocol fromAmongModules:conflictingModules];
} else {
resolvedModule = [conflictingModules lastObject];
}
[_internalModules setObject:resolvedModule forKey:protocol];
[self maybeAddRegistryConsumer:resolvedModule];
[_internalModulesSet addObject:resolvedModule];
}
_internalModulesPreResolution = nil; // Remove references to discarded modules
}
- (void)initialize
{
[self resolveInternalModulesConflicts];
for (id<EXModuleRegistryConsumer> registryConsumer in _registryConsumers) {
[registryConsumer setModuleRegistry:self];
}
}
# pragma mark - Registering modules
- (void)registerInternalModule:(id<EXInternalModule>)internalModule
{
for (Protocol *exportedInterface in [[internalModule class] exportedInterfaces]) {
if (_internalModules) {
id<EXInternalModule> resolvedModule = internalModule;
if (_delegate && [_internalModules objectForKey:exportedInterface]) {
id<EXInternalModule> oldModule = [_internalModules objectForKey:exportedInterface];
resolvedModule = [_delegate pickInternalModuleImplementingInterface:exportedInterface fromAmongModules:@[oldModule, internalModule]];
}
[_internalModules setObject:resolvedModule forKey:exportedInterface];
[self maybeAddRegistryConsumer:resolvedModule];
[_internalModulesSet addObject:resolvedModule];
} else {
if (![_internalModulesPreResolution objectForKey:exportedInterface]) {
[_internalModulesPreResolution setObject:[NSMutableArray array] forKey:exportedInterface];
}
[[_internalModulesPreResolution objectForKey:exportedInterface] addObject:internalModule];
}
}
}
- (void)registerExportedModule:(EXExportedModule *)exportedModule
{
const NSString *exportedModuleName = [[exportedModule class] exportedModuleName];
if (_exportedModules[exportedModuleName]) {
RCTLogInfo(@"Module %@ overrides %@ as the module exported as %@.", exportedModule, _exportedModules[exportedModuleName], exportedModuleName);
}
_exportedModules[exportedModuleName] = exportedModule;
[_exportedModulesByClass setObject:exportedModule forKey:[exportedModule class]];
[self maybeAddRegistryConsumer:exportedModule];
}
- (void)registerSingletonModule:(id)singletonModule
{
if ([[singletonModule class] respondsToSelector:@selector(name)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-method-access"
[_singletonModules setObject:singletonModule forKey:[[singletonModule class] name]];
#pragma clang diagnostic pop
} else {
RCTLogWarn(@"One of the singleton modules does not respond to +(NSString *)name selector. This probably means you're either try to pass a strange object as a singleton module (it won't get registered in the module registry, sorry) or the EXSingletonModule interface and the EXModuleRegistry implementations versions are out of sync, which means things will probably not work as expected.");
}
}
- (void)maybeAddRegistryConsumer:(id)maybeConsumer
{
if ([maybeConsumer conformsToProtocol:@protocol(EXModuleRegistryConsumer)]) {
[self addRegistryConsumer:(id<EXModuleRegistryConsumer>)maybeConsumer];
}
}
- (void)addRegistryConsumer:(id<EXModuleRegistryConsumer>)registryConsumer
{
void *ptr = (__bridge void * _Nullable)registryConsumer;
for (id consumerPtr in _registryConsumers) {
if (consumerPtr == ptr) {
return;
}
}
[_registryConsumers addPointer:ptr];
}
# pragma mark - Registry API
- (id)getModuleImplementingProtocol:(Protocol *)protocol
{
[self resolveInternalModulesConflicts];
return [_internalModules objectForKey:protocol];
}
- (EXExportedModule *)getExportedModuleForName:(NSString *)name
{
return _exportedModules[name];
}
- (EXExportedModule *)getExportedModuleOfClass:(Class)moduleClass
{
return [_exportedModulesByClass objectForKey:moduleClass];
}
- (id)getSingletonModuleForName:(NSString *)singletonModuleName
{
return [_singletonModules objectForKey:singletonModuleName];
}
- (NSArray<id<EXInternalModule>> *)getAllInternalModules
{
[self resolveInternalModulesConflicts];
return [_internalModulesSet allObjects];
}
- (NSArray<EXExportedModule *> *)getAllExportedModules
{
return [_exportedModules allValues];
}
- (NSArray *)getAllSingletonModules
{
return [_singletonModules allValues];
}
@end

View File

@@ -0,0 +1,11 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesCore/EXInternalModule.h>
@protocol EXModuleRegistryDelegate <NSObject>
- (id<EXInternalModule>)pickInternalModuleImplementingInterface:(Protocol *)interface fromAmongModules:(NSArray<id<EXInternalModule>> *)internalModules;
@end

View File

@@ -0,0 +1,25 @@
// Copyright 2018-present 650 Industries. All rights reserved.
@class RCTBridge;
#import <ExpoModulesCore/EXModuleRegistryProvider.h>
// An "adapter" over module registry, for given RCTBridge and NSString
// is able to provide an array of exported RCTBridgeModules. Override
// it and use in your AppDelegate to export different bridge modules
// for different experiences.
NS_SWIFT_NAME(ModuleRegistryAdapter)
@interface EXModuleRegistryAdapter : NSObject
@property(nonnull, nonatomic, readonly) EXModuleRegistryProvider *moduleRegistryProvider;
- (nonnull instancetype)initWithModuleRegistryProvider:(nonnull EXModuleRegistryProvider *)moduleRegistryProvider
__deprecated_msg("Expo modules are now automatically registered. You can remove this method call.");
- (nonnull NSArray<id<RCTBridgeModule>> *)extraModulesForModuleRegistry:(nonnull EXModuleRegistry *)moduleRegistry;
- (nonnull NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(nonnull RCTBridge *)bridge
__deprecated_msg("Expo modules are now automatically registered. You can replace this with an empty array.");
@end

View File

@@ -0,0 +1,54 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <ExpoModulesCore/EXNativeModulesProxy.h>
#import <ExpoModulesCore/EXModuleRegistryAdapter.h>
#import <ExpoModulesCore/EXModuleRegistryProvider.h>
#import <ExpoModulesCore/EXModuleRegistryHolderReactModule.h>
@interface EXModuleRegistryAdapter ()
@property (nonatomic, strong) EXModuleRegistryProvider *moduleRegistryProvider;
@end
@implementation EXModuleRegistryAdapter
- (nonnull instancetype)initWithModuleRegistryProvider:(EXModuleRegistryProvider *)moduleRegistryProvider
{
if (self = [super init]) {
_moduleRegistryProvider = moduleRegistryProvider;
}
return self;
}
- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
{
return [self extraModulesForModuleRegistry:[_moduleRegistryProvider moduleRegistry]];
}
- (NSArray<id<RCTBridgeModule>> *)extraModulesForModuleRegistry:(EXModuleRegistry *)moduleRegistry
{
NSMutableArray<id<RCTBridgeModule>> *extraModules = [NSMutableArray array];
EXNativeModulesProxy *nativeModulesProxy = [[EXNativeModulesProxy alloc] initWithModuleRegistry:moduleRegistry];
[extraModules addObject:nativeModulesProxy];
// It is possible that among internal modules there are some RCTBridgeModules --
// let's add them to extraModules here.
for (id<EXInternalModule> module in [moduleRegistry getAllInternalModules]) {
if ([module conformsToProtocol:@protocol(RCTBridgeModule)]) {
id<RCTBridgeModule> reactBridgeModule = (id<RCTBridgeModule>)module;
[extraModules addObject:reactBridgeModule];
}
}
// Adding the way to access the module registry from RCTBridgeModules.
[extraModules addObject:[[EXModuleRegistryHolderReactModule alloc] initWithModuleRegistry:moduleRegistry]];
// One could add some modules to the Module Registry after creating it.
// Here is our last call for finalizing initialization.
[moduleRegistry initialize];
return extraModules;
}
@end

View File

@@ -0,0 +1,12 @@
// Copyright 2018-present 650 Industries. All rights reserved.
@protocol RCTBridgeModule;
#import <ExpoModulesCore/EXModuleRegistry.h>
@interface EXModuleRegistryHolderReactModule : NSObject <RCTBridgeModule>
- (nonnull instancetype)initWithModuleRegistry:(nonnull EXModuleRegistry *)moduleRegistry;
- (nullable EXModuleRegistry *)exModuleRegistry;
@end

View File

@@ -0,0 +1,30 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <ExpoModulesCore/EXModuleRegistryHolderReactModule.h>
@interface EXModuleRegistryHolderReactModule ()
@property (nonatomic, nullable, weak) EXModuleRegistry *exModuleRegistry;
@end
@implementation EXModuleRegistryHolderReactModule
- (nonnull instancetype)initWithModuleRegistry:(nonnull EXModuleRegistry *)moduleRegistry
{
if (self = [super init]) {
_exModuleRegistry = moduleRegistry;
}
return self;
}
- (nullable EXModuleRegistry *)exModuleRegistry
{
return _exModuleRegistry;
}
+ (nullable NSString *)moduleName {
return nil;
}
@end

View File

@@ -0,0 +1,25 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesCore/EXModuleRegistry.h>
NS_ASSUME_NONNULL_BEGIN
@class EXSingletonModule;
NS_SWIFT_NAME(ModuleRegistryProvider)
@interface EXModuleRegistryProvider : NSObject
@property (nonatomic, weak) id<EXModuleRegistryDelegate> moduleRegistryDelegate;
+ (NSSet<Class> *)getModulesClasses;
+ (NSSet *)singletonModules;
+ (nullable EXSingletonModule *)getSingletonModuleForClass:(Class)singletonClass;
- (instancetype)init __deprecated_msg("Expo modules are now being automatically registered. You can remove this method call.");
- (instancetype)initWithSingletonModules:(NSSet *)modules;
- (EXModuleRegistry *)moduleRegistry;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,150 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesCore/EXSingletonModule.h>
#import <ExpoModulesCore/EXModuleRegistryProvider.h>
#import <ExpoModulesCore/Swift.h>
static dispatch_once_t onceToken;
static NSMutableSet<Class> *EXModuleClasses;
static NSMutableSet<Class> *EXSingletonModuleClasses;
void (^EXinitializeGlobalModulesRegistry)(void) = ^{
EXModuleClasses = [NSMutableSet set];
EXSingletonModuleClasses = [NSMutableSet set];
// Also add temporary Swift modules from core
[EXModuleClasses addObject:[EXFileSystemLegacyUtilities class]];
};
extern void EXRegisterModule(Class);
extern void EXRegisterModule(Class moduleClass)
{
dispatch_once(&onceToken, EXinitializeGlobalModulesRegistry);
[EXModuleClasses addObject:moduleClass];
}
extern void EXRegisterSingletonModule(Class);
extern void EXRegisterSingletonModule(Class singletonModuleClass)
{
dispatch_once(&onceToken, EXinitializeGlobalModulesRegistry);
// A heuristic solution to "multiple singletons registering
// to the same name" problem. Usually it happens when we want to
// override module singleton with an ExpoKit one. This solution
// gives preference to subclasses.
// If a superclass of a registering singleton is already registered
// we want to remove it in favor of the registering singleton.
Class superClass = [singletonModuleClass superclass];
while (superClass != [NSObject class]) {
[EXSingletonModuleClasses removeObject:superClass];
superClass = [superClass superclass];
}
// If a registering singleton is a superclass of an already registered
// singleton, we don't register it.
for (Class registeredClass in EXSingletonModuleClasses) {
if ([singletonModuleClass isSubclassOfClass:registeredClass]) {
return;
}
}
[EXSingletonModuleClasses addObject:singletonModuleClass];
}
// Singleton modules classes register in EXSingletonModuleClasses
// with EXRegisterSingletonModule function. Then they should be
// initialized exactly once (onceSingletonModulesToken guards that).
static dispatch_once_t onceSingletonModulesToken;
static NSMutableSet<EXSingletonModule *> *EXSingletonModules;
void (^EXinitializeGlobalSingletonModulesSet)(void) = ^{
EXSingletonModules = [NSMutableSet set];
for (Class singletonModuleClass in EXSingletonModuleClasses) {
[EXSingletonModules addObject:[[singletonModuleClass alloc] init]];
}
};
@interface EXModuleRegistryProvider ()
@property (nonatomic, strong) NSSet *singletonModules;
@end
@implementation EXModuleRegistryProvider
- (instancetype)init
{
return [self initWithSingletonModules:[EXModuleRegistryProvider singletonModules]];
}
- (instancetype)initWithSingletonModules:(NSSet *)modules
{
if (self = [super init]) {
_singletonModules = [NSSet setWithSet:modules];
}
return self;
}
+ (NSSet<Class> *)getModulesClasses
{
return EXModuleClasses;
}
+ (NSSet<EXSingletonModule *> *)singletonModules
{
dispatch_once(&onceSingletonModulesToken, EXinitializeGlobalSingletonModulesSet);
return EXSingletonModules;
}
+ (nullable EXSingletonModule *)getSingletonModuleForClass:(Class)singletonClass
{
NSSet<EXSingletonModule *> *singletonModules = [self singletonModules];
for (EXSingletonModule *singleton in singletonModules) {
if ([singleton isKindOfClass:singletonClass]) {
return singleton;
}
}
return nil;
}
- (EXModuleRegistry *)moduleRegistry
{
NSMutableSet<id<EXInternalModule>> *internalModules = [NSMutableSet set];
NSMutableSet<EXExportedModule *> *exportedModules = [NSMutableSet set];
for (Class klass in [self.class getModulesClasses]) {
if (![klass conformsToProtocol:@protocol(EXInternalModule)]) {
RCTLogWarn(@"Registered class `%@` does not conform to the `EXInternalModule` protocol.", [klass description]);
continue;
}
id<EXInternalModule> instance = [self createModuleInstance:klass];
if ([[instance class] exportedInterfaces] != nil && [[[instance class] exportedInterfaces] count] > 0) {
[internalModules addObject:instance];
}
if ([instance isKindOfClass:[EXExportedModule class]]) {
[exportedModules addObject:(EXExportedModule *)instance];
}
}
EXModuleRegistry *moduleRegistry = [[EXModuleRegistry alloc] initWithInternalModules:internalModules
exportedModules:exportedModules
singletonModules:_singletonModules];
[moduleRegistry setDelegate:_moduleRegistryDelegate];
return moduleRegistry;
}
# pragma mark - Utilities
- (id<EXInternalModule>)createModuleInstance:(Class)moduleClass
{
return [[moduleClass alloc] init];
}
@end

View File

@@ -0,0 +1,37 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <React/RCTBridgeModule.h>
#import <ExpoModulesCore/EXInternalModule.h>
#import <ExpoModulesCore/EXModuleRegistry.h>
// A convenience class, which acts as a store for the native modules proxy config
NS_SWIFT_NAME(ModulesProxyConfig)
@interface EXModulesProxyConfig : NSObject
- (instancetype)initWithConstants:(nonnull NSDictionary *)constants
methodNames:(nonnull NSDictionary *)methodNames
viewManagers:(nonnull NSDictionary *)viewManagerMetadata;
- (void)addEntriesFromConfig:(nonnull const EXModulesProxyConfig *)config;
- (nonnull NSDictionary<NSString *, id> *)toDictionary;
@end
// RCTBridgeModule capable of receiving method calls from JS and forwarding them
// to proper exported universal modules. Also, it exports important constants to JS, like
// properties of exported methods and modules' constants.
NS_SWIFT_NAME(LegacyNativeModulesProxy)
@interface EXNativeModulesProxy : NSObject <RCTBridgeModule>
@property(nonatomic, strong, readonly) EXModulesProxyConfig *nativeModulesConfig;
- (nonnull instancetype)init;
- (nonnull instancetype)initWithModuleRegistry:(nullable EXModuleRegistry *)moduleRegistry;
- (nonnull instancetype)initWithCustomModuleRegistry:(nonnull EXModuleRegistry *)moduleRegistry;
- (void)callMethod:(NSString *)moduleName methodNameOrKey:(id)methodNameOrKey arguments:(NSArray *)arguments resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
@end

View File

@@ -0,0 +1,403 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <objc/runtime.h>
@class RCTBridge;
@class RCTModuleData;
#import <React/RCTComponentViewFactory.h> // Allows non-umbrella since it's coming from React-RCTFabric
#import <React/RCTUIManager.h>
#import <jsi/jsi.h>
#import <ExpoModulesCore/EXNativeModulesProxy.h>
#import <ExpoModulesCore/EXModuleRegistryProvider.h>
#import <ExpoModulesCore/EXJSIInstaller.h>
#import <ExpoModulesCore/Swift.h>
static const NSString *exportedMethodsNamesKeyPath = @"exportedMethods";
static const NSString *viewManagersMetadataKeyPath = @"viewManagersMetadata";
static const NSString *exportedConstantsKeyPath = @"modulesConstants";
static const NSString *methodInfoKeyKey = @"key";
static const NSString *methodInfoNameKey = @"name";
static const NSString *methodInfoArgumentsCountKey = @"argumentsCount";
@interface EXModulesProxyConfig ()
@property (readonly) NSMutableDictionary *exportedConstants;
@property (readonly) NSMutableDictionary *methodNames;
@property (readonly) NSMutableDictionary *viewManagerMetadata;
@end
@implementation EXModulesProxyConfig
- (instancetype)initWithConstants:(nonnull NSDictionary *)constants
methodNames:(nonnull NSDictionary *)methodNames
viewManagers:(nonnull NSDictionary *)viewManagerMetadata
{
if (self = [super init]) {
_exportedConstants = constants;
_methodNames = methodNames;
_viewManagerMetadata = viewManagerMetadata;
}
return self;
}
- (void)addEntriesFromConfig:(nonnull const EXModulesProxyConfig*)config
{
[_exportedConstants addEntriesFromDictionary:config.exportedConstants];
[_methodNames addEntriesFromDictionary:config.methodNames];
[_viewManagerMetadata addEntriesFromDictionary:config.viewManagerMetadata];
}
- (nonnull NSDictionary<NSString *, id> *)toDictionary
{
NSMutableDictionary <NSString *, id> *constantsAccumulator = [NSMutableDictionary dictionary];
constantsAccumulator[viewManagersMetadataKeyPath] = _viewManagerMetadata;
constantsAccumulator[exportedConstantsKeyPath] = _exportedConstants;
constantsAccumulator[exportedMethodsNamesKeyPath] = _methodNames;
return constantsAccumulator;
}
@end
@interface RCTBridge (RegisterAdditionalModuleClasses)
- (NSArray<RCTModuleData *> *)registerModulesForClasses:(NSArray<Class> *)moduleClasses;
- (void)registerAdditionalModuleClasses:(NSArray<Class> *)modules;
@end
@interface RCTBridge (JSIRuntime)
- (void *)runtime;
@end
@interface EXNativeModulesProxy ()
@property (nonatomic, strong) NSRegularExpression *regexp;
@property (nonatomic, strong) EXModuleRegistry *exModuleRegistry;
@property (nonatomic, strong) NSMutableDictionary<const NSString *, NSMutableDictionary<NSString *, NSNumber *> *> *exportedMethodsKeys;
@property (nonatomic, strong) NSMutableDictionary<const NSString *, NSMutableDictionary<NSNumber *, NSString *> *> *exportedMethodsReverseKeys;
@property (nonatomic) BOOL ownsModuleRegistry;
@end
@implementation EXNativeModulesProxy {
__weak EXAppContext * _Nullable _appContext;
}
@synthesize bridge = _bridge;
@synthesize nativeModulesConfig = _nativeModulesConfig;
RCT_EXPORT_MODULE(NativeUnimoduleProxy)
/**
The designated initializer. It's used in the old setup where the native modules proxy
is registered in `extraModulesForBridge:` by the bridge delegate.
*/
- (instancetype)initWithModuleRegistry:(nullable EXModuleRegistry *)moduleRegistry
{
if (self = [super init]) {
_exModuleRegistry = moduleRegistry != nil ? moduleRegistry : [[EXModuleRegistryProvider new] moduleRegistry];
_exportedMethodsKeys = [NSMutableDictionary dictionary];
_exportedMethodsReverseKeys = [NSMutableDictionary dictionary];
_ownsModuleRegistry = moduleRegistry == nil;
}
return self;
}
/**
The initializer for Expo Go to pass a custom `EXModuleRegistry`
other than the default one from `EXModuleRegistryProvider`.
The `EXModuleRegistry` is still owned by this class.
*/
- (instancetype)initWithCustomModuleRegistry:(nonnull EXModuleRegistry *)moduleRegistry
{
self = [self initWithModuleRegistry:moduleRegistry];
self.ownsModuleRegistry = YES;
return self;
}
/**
Convenience initializer used by React Native in the new setup, where the modules are registered automatically.
*/
- (instancetype)init
{
return [self initWithModuleRegistry:nil];
}
# pragma mark - React API
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
- (nonnull EXModulesProxyConfig *)nativeModulesConfig
{
if (_nativeModulesConfig) {
return _nativeModulesConfig;
}
NSMutableDictionary <NSString *, id> *exportedModulesConstants = [NSMutableDictionary dictionary];
// Grab all the constants exported by modules
for (EXExportedModule *exportedModule in [_exModuleRegistry getAllExportedModules]) {
@try {
exportedModulesConstants[[[exportedModule class] exportedModuleName]] = [exportedModule constantsToExport] ?: [NSNull null];
} @catch (NSException *exception) {
continue;
}
}
// Also add `exportedMethodsNames`
NSMutableDictionary<const NSString *, NSMutableArray<NSMutableDictionary<const NSString *, id> *> *> *exportedMethodsNamesAccumulator = [NSMutableDictionary dictionary];
for (EXExportedModule *exportedModule in [_exModuleRegistry getAllExportedModules]) {
const NSString *exportedModuleName = [[exportedModule class] exportedModuleName];
exportedMethodsNamesAccumulator[exportedModuleName] = [NSMutableArray array];
[[exportedModule getExportedMethods] enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull exportedName, NSString * _Nonnull selectorName, BOOL * _Nonnull stop) {
NSMutableDictionary<const NSString *, id> *methodInfo = [NSMutableDictionary dictionaryWithDictionary:@{
methodInfoNameKey: exportedName,
// - 3 is for resolver and rejecter of the promise and the last, empty component
methodInfoArgumentsCountKey: @([[selectorName componentsSeparatedByString:@":"] count] - 3)
}];
[exportedMethodsNamesAccumulator[exportedModuleName] addObject:methodInfo];
}];
[self assignExportedMethodsKeys:exportedMethodsNamesAccumulator[exportedModuleName] forModuleName:exportedModuleName];
}
EXModulesProxyConfig *config = [[EXModulesProxyConfig alloc] initWithConstants:exportedModulesConstants
methodNames:exportedMethodsNamesAccumulator
viewManagers:[NSMutableDictionary new]];
// decorate legacy config with sweet expo-modules config
[config addEntriesFromConfig:[_appContext expoModulesConfig]];
_nativeModulesConfig = config;
return config;
}
- (nonnull NSDictionary *)constantsToExport
{
return [self.nativeModulesConfig toDictionary];
}
- (void)setBridge:(RCTBridge *)bridge
{
ExpoBridgeModule *expoBridgeModule = [bridge moduleForClass:ExpoBridgeModule.class];
[expoBridgeModule legacyProxyDidSetBridge:self legacyModuleRegistry:_exModuleRegistry];
_appContext = [expoBridgeModule appContext];
if (!_bridge) {
// The `setBridge` can be called during module setup or after. Registering more modules
// during setup causes a crash due to mutating `_moduleDataByID` while it's being enumerated.
// In that case we register them asynchronously.
if ([[bridge valueForKey:@"_moduleSetupComplete"] boolValue]) {
[self registerExpoModulesInBridge:bridge];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self registerExpoModulesInBridge:bridge];
});
}
}
_bridge = bridge;
}
RCT_EXPORT_METHOD(callMethod:(NSString *)moduleName methodNameOrKey:(id)methodNameOrKey arguments:(NSArray *)arguments resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
// Backwards compatibility for the new architecture
if ([_appContext hasModule:moduleName]) {
[_appContext callFunction:methodNameOrKey onModule:moduleName withArgs:arguments resolve:resolve reject:reject];
return;
}
EXExportedModule *module = [_exModuleRegistry getExportedModuleForName:moduleName];
if (module == nil) {
NSString *reason = [NSString stringWithFormat:@"No exported module was found for name '%@'. Are you sure all the packages are linked correctly?", moduleName];
reject(@"E_NO_MODULE", reason, nil);
return;
}
if (!methodNameOrKey) {
reject(@"E_NO_METHOD", @"No method key or name provided", nil);
return;
}
NSString *methodName;
if ([methodNameOrKey isKindOfClass:[NSString class]]) {
methodName = (NSString *)methodNameOrKey;
} else if ([methodNameOrKey isKindOfClass:[NSNumber class]]) {
methodName = _exportedMethodsReverseKeys[moduleName][(NSNumber *)methodNameOrKey];
} else {
reject(@"E_INV_MKEY", @"Method key is neither a String nor an Integer -- don't know how to map it to method name.", nil);
return;
}
dispatch_async([module methodQueue], ^{
@try {
[module callExportedMethod:methodName withArguments:arguments resolver:resolve rejecter:reject];
} @catch (NSException *e) {
NSString *message = [NSString stringWithFormat:@"An exception was thrown while calling `%@.%@` with arguments `%@`: %@", moduleName, methodName, arguments, e];
reject(@"E_EXC", message, nil);
}
});
}
#pragma mark - Privates
- (void)registerExpoModulesInBridge:(RCTBridge *)bridge
{
// Registering expo modules (excluding Swifty view managers!) in bridge is needed only when the proxy module owns
// the registry (was autoinitialized by React Native). Otherwise they're registered by the registry adapter.
BOOL ownsModuleRegistry = _ownsModuleRegistry;
// An array of `RCTBridgeModule` classes to register.
NSMutableArray<Class<RCTBridgeModule>> *additionalModuleClasses = [NSMutableArray new];
NSMutableSet *visitedSweetModules = [NSMutableSet new];
// Add dynamic wrappers for view modules written in Sweet API.
for (EXViewModuleWrapper *swiftViewModule in [_appContext getViewManagers]) {
Class wrappedViewModuleClass = [self registerComponentData:swiftViewModule
inBridge:bridge
forAppId:_appContext.appIdentifier];
[additionalModuleClasses addObject:wrappedViewModuleClass];
[visitedSweetModules addObject:swiftViewModule.name];
}
[additionalModuleClasses addObject:[EXViewModuleWrapper class]];
[self registerLegacyComponentData:[EXViewModuleWrapper class] inBridge:bridge];
// Add modules from legacy module registry only when the NativeModulesProxy owns the registry.
if (ownsModuleRegistry) {
// Some modules might need access to the bridge.
for (id module in [_exModuleRegistry getAllInternalModules]) {
if ([module conformsToProtocol:@protocol(RCTBridgeModule)]) {
[module setValue:bridge forKey:@"bridge"];
}
}
}
// `registerAdditionalModuleClasses:` call below is not thread-safe if RCTUIManager is not initialized.
// The case happens especially with reanimated which accesses `bridge.uiManager` and initialize bridge in js thread.
// Accessing uiManager here, we try to make sure RCTUIManager is initialized.
[bridge uiManager];
// Register the view managers as additional modules.
[self registerAdditionalModuleClasses:additionalModuleClasses inBridge:bridge];
// As the last step, when the registry is owned,
// register the event emitter and initialize the registry.
if (ownsModuleRegistry) {
// Let the modules consume the registry :)
// It calls `setModuleRegistry:` on all `EXModuleRegistryConsumer`s.
[_exModuleRegistry initialize];
}
}
- (void)registerAdditionalModuleClasses:(NSArray<Class> *)moduleClasses inBridge:(RCTBridge *)bridge
{
// In remote debugging mode, i.e. executorClass is `RCTWebSocketExecutor`,
// there is a deadlock issue in `registerAdditionalModuleClasses:` and causes app freezed.
// - The JS thread acquired the `RCTCxxBridge._moduleRegistryLock` lock in `RCTCxxBridge._initializeBridgeLocked`
// = it further goes into RCTObjcExecutor and tries to get module config from main thread
// - The main thread is pending in `RCTCxxBridge.registerAdditionalModuleClasses` where trying to acquire the same lock.
// To workaround the deadlock, we tend to use the non-locked registration and mutate the bridge internal module data.
// Since JS thread in this situation is waiting for main thread, it's safe to mutate module data without lock.
// The only risk should be the internal `_moduleRegistryCreated` flag without lock protection.
// As we just workaround in `RCTWebSocketExecutor` case, the risk of `_moduleRegistryCreated` race condition should be lower.
//
// Learn more about the non-locked initialization:
// https://github.com/facebook/react-native/blob/757bb75fbf837714725d7b2af62149e8e2a7ee51/React/CxxBridge/RCTCxxBridge.mm#L922-L935
// See the `_moduleRegistryCreated` false case
if ([NSStringFromClass([bridge executorClass]) isEqualToString:@"RCTWebSocketExecutor"]) {
NSNumber *moduleRegistryCreated = [bridge valueForKey:@"_moduleRegistryCreated"];
if (![moduleRegistryCreated boolValue]) {
[bridge registerModulesForClasses:moduleClasses];
return;
}
}
if (bridge.isLoading) {
[bridge registerModulesForClasses:moduleClasses];
} else {
[bridge registerAdditionalModuleClasses:moduleClasses];
}
}
- (Class)registerComponentData:(EXViewModuleWrapper *)viewModule inBridge:(RCTBridge *)bridge forAppId:(NSString *)appId
{
// Hacky way to get a dictionary with `RCTComponentData` from UIManager.
NSMutableDictionary<NSString *, RCTComponentData *> *componentDataByName = [[bridge uiManager] valueForKey:@"_componentDataByName"];
Class wrappedViewModuleClass = [EXViewModuleWrapper createViewModuleWrapperClassWithModule:viewModule appId:appId];
NSString *className = NSStringFromClass(wrappedViewModuleClass);
if (componentDataByName[className]) {
// Just in case the component was already registered, let's leave a log that we're overriding it.
NSLog(@"Overriding ComponentData for view %@", className);
}
EXComponentData *componentData = [[EXComponentData alloc] initWithViewModule:viewModule
managerClass:wrappedViewModuleClass
bridge:bridge];
componentDataByName[className] = componentData;
#ifdef RCT_NEW_ARCH_ENABLED
Class viewClass = [ExpoFabricView makeViewClassForAppContext:_appContext moduleName:[viewModule moduleName] viewName: [viewModule viewName] className:className];
[[RCTComponentViewFactory currentComponentViewFactory] registerComponentViewClass:viewClass];
#endif
return wrappedViewModuleClass;
}
/**
Bridge's `registerAdditionalModuleClasses:` method doesn't register
components in UIManager — we need to register them on our own.
*/
- (void)registerLegacyComponentData:(Class)moduleClass inBridge:(RCTBridge *)bridge
{
// Hacky way to get a dictionary with `RCTComponentData` from UIManager.
NSMutableDictionary<NSString *, RCTComponentData *> *componentDataByName = [bridge.uiManager valueForKey:@"_componentDataByName"];
NSString *className = [moduleClass moduleName] ?: NSStringFromClass(moduleClass);
if ([moduleClass isSubclassOfClass:[RCTViewManager class]] && !componentDataByName[className]) {
RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:moduleClass bridge:bridge eventDispatcher:bridge.eventDispatcher];
componentDataByName[className] = componentData;
}
}
- (void)assignExportedMethodsKeys:(NSMutableArray<NSMutableDictionary<const NSString *, id> *> *)exportedMethods forModuleName:(const NSString *)moduleName
{
if (!_exportedMethodsKeys[moduleName]) {
_exportedMethodsKeys[moduleName] = [NSMutableDictionary dictionary];
}
if (!_exportedMethodsReverseKeys[moduleName]) {
_exportedMethodsReverseKeys[moduleName] = [NSMutableDictionary dictionary];
}
for (int i = 0; i < [exportedMethods count]; i++) {
NSMutableDictionary<const NSString *, id> *methodInfo = exportedMethods[i];
if (!methodInfo[(NSString *)methodInfoNameKey] || ![methodInfo[methodInfoNameKey] isKindOfClass:[NSString class]]) {
NSString *reason = [NSString stringWithFormat:@"Method info of a method of module %@ has no method name.", moduleName];
@throw [NSException exceptionWithName:@"Empty method name in method info" reason:reason userInfo:nil];
}
NSString *methodName = methodInfo[(NSString *)methodInfoNameKey];
NSNumber *previousMethodKey = _exportedMethodsKeys[moduleName][methodName];
if (previousMethodKey) {
methodInfo[methodInfoKeyKey] = previousMethodKey;
} else {
NSNumber *newKey = @([[_exportedMethodsKeys[moduleName] allValues] count]);
methodInfo[methodInfoKeyKey] = newKey;
_exportedMethodsKeys[moduleName][methodName] = newKey;
_exportedMethodsReverseKeys[moduleName][newKey] = methodName;
}
}
}
@end

View File

@@ -0,0 +1,37 @@
import Foundation
public class NativeModulesProxyModule: Module {
public static let moduleName = "NativeModulesProxy"
public func definition() -> ModuleDefinition {
Name(Self.moduleName)
Constants { () -> [String: Any?] in
guard let config = self.appContext?.legacyModulesProxy?.nativeModulesConfig else {
// TODO: Throw, but what?
return [:]
}
return config.toDictionary()
}
AsyncFunction("callMethod") { (moduleName: String, methodName: String, arguments: [Any], promise: Promise) in
guard let appContext = self.appContext else {
return promise.reject(Exceptions.AppContextLost())
}
// Call a method on the new module if exists
if appContext.hasModule(moduleName) {
appContext.callFunction(methodName, onModule: moduleName, withArgs: arguments, resolve: promise.resolver, reject: promise.legacyRejecter)
return
}
// Call a method on the legacy module
guard let legacyModule = appContext.legacyModuleRegistry?.getExportedModule(forName: moduleName) else {
return promise.reject(ModuleHolder.ModuleNotFoundException(moduleName))
}
legacyModule.methodQueue().async {
legacyModule.callExportedMethod(methodName, withArguments: arguments, resolver: promise.resolver, rejecter: promise.legacyRejecter)
}
}
}
}

View File

@@ -0,0 +1,17 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
// Utility protocol helping modules to register with specific platform adapter
// for application lifecycle events.
@protocol EXAppLifecycleListener <NSObject>
- (void)onAppBackgrounded;
- (void)onAppForegrounded;
@optional
- (void)onAppContentDidAppear;
- (void)onAppContentWillReload;
@end

View File

@@ -0,0 +1,14 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesCore/EXAppLifecycleListener.h>
@protocol EXAppLifecycleService <NSObject>
- (void)registerAppLifecycleListener:(id<EXAppLifecycleListener>)listener;
- (void)unregisterAppLifecycleListener:(id<EXAppLifecycleListener>)listener;
- (void)setAppStateToBackground;
- (void)setAppStateToForeground;
@end

View File

@@ -0,0 +1,15 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
// Register a class implementing this protocol in EXModuleClasses
// of EXModuleRegistryProvider (macros defined in EXDefines.h should help you)
// to make the module available under any of `exportedInterfaces`
// via EXModuleRegistry.
@protocol EXInternalModule <NSObject>
- (instancetype)init;
+ (const NSArray<Protocol *> *)exportedInterfaces;
@end

View File

@@ -0,0 +1,11 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
@protocol EXJavaScriptContextProvider <NSObject>
- (JSGlobalContextRef)javaScriptContextRef;
- (void *)javaScriptRuntimePointer;
@end

View File

@@ -0,0 +1,15 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesCore/EXModuleRegistry.h>
// Implement this protocol in any module registered
// in EXModuleRegistry to receive an instance of the module registry
// when it's initialized (ready to provide references to other modules).
@protocol EXModuleRegistryConsumer <NSObject>
- (void)setModuleRegistry:(nonnull EXModuleRegistry *)moduleRegistry;
@end

View File

@@ -0,0 +1,17 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesCore/Platform.h>
@protocol EXUIManager <NSObject>
- (void)addUIBlock:(void (^)(NSDictionary<id, UIView *> *))block;
- (void)addUIBlock:(void (^)(id))block forView:(id)viewId ofClass:(Class)klass;
- (void)addUIBlock:(void (^)(id))block forView:(id)viewId implementingProtocol:(Protocol *)protocol;
- (void)executeUIBlock:(void (^)(NSDictionary<id, UIView *> *))block;
- (void)executeUIBlock:(void (^)(id))block forView:(id)viewId ofClass:(Class)klass;
- (void)executeUIBlock:(void (^)(id))block forView:(id)viewId implementingProtocol:(Protocol *)protocol;
- (void)dispatchOnClientThread:(dispatch_block_t)block;
@end

View File

@@ -0,0 +1,11 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <ExpoModulesCore/Platform.h>
@protocol EXUtilitiesInterface
- (nullable NSDictionary *)launchOptions;
- (nullable UIViewController *)currentViewController;
@end

View File

@@ -0,0 +1,14 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <ExpoModulesCore/EXUIManager.h>
#import <ExpoModulesCore/EXInternalModule.h>
#import <ExpoModulesCore/EXAppLifecycleService.h>
#import <ExpoModulesCore/EXAppLifecycleListener.h>
#import <ExpoModulesCore/EXModuleRegistryConsumer.h>
#import <ExpoModulesCore/EXJavaScriptContextProvider.h>
@interface EXReactNativeAdapter : NSObject <EXInternalModule, EXAppLifecycleService, EXUIManager, EXJavaScriptContextProvider, EXModuleRegistryConsumer>
- (void)setBridge:(RCTBridge *)bridge;
@end

View File

@@ -0,0 +1,322 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <JavaScriptCore/JavaScriptCore.h>
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerUtils.h>
#import <ExpoModulesCore/EXReactNativeAdapter.h>
#import <ExpoModulesCore/ExpoFabricViewObjC.h>
@interface EXReactNativeAdapter ()
@property (nonatomic, weak) RCTBridge *bridge;
@property (nonatomic, assign) BOOL isForegrounded;
@property (nonatomic, strong) NSPointerArray *lifecycleListeners;
@end
@interface RCTBridge ()
- (JSGlobalContextRef)jsContextRef;
- (void *)runtime;
- (void)dispatchBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue;
@end
@implementation EXReactNativeAdapter
EX_REGISTER_MODULE();
+ (NSString *)moduleName
{
return @"EXReactNativeAdapter";
}
+ (const NSArray<Protocol *> *)exportedInterfaces
{
return @[@protocol(EXAppLifecycleService), @protocol(EXUIManager), @protocol(EXJavaScriptContextProvider)];
}
# pragma mark - Lifecycle methods
- (instancetype)init
{
if (self = [super init]) {
_isForegrounded = false;
_lifecycleListeners = [NSPointerArray weakObjectsPointerArray];
}
return self;
}
- (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry
{
if (moduleRegistry) {
[self startObserving];
}
}
- (void)dealloc
{
[self stopObserving];
}
# pragma mark - Public API
- (void)addUIBlock:(void (^)(NSDictionary<id, UIView *> *))block
{
#if RCT_NEW_ARCH_ENABLED
[NSException raise:NSGenericException format:@"This version of `addUIBlock` is not supported in the New Architecture"];
#else
__weak EXReactNativeAdapter *weakSelf = self;
dispatch_async(_bridge.uiManager.methodQueue, ^{
__strong EXReactNativeAdapter *strongSelf = weakSelf;
if (strongSelf) {
[strongSelf.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
block(viewRegistry);
}];
}
});
#endif
}
- (void)addUIBlock:(void (^)(id))block forView:(id)viewId ofClass:(Class)klass
{
[self addUIBlock:^(UIView *view) {
if (![view isKindOfClass:klass]) {
block(nil);
} else {
block(view);
}
} forView:viewId];
}
- (void)addUIBlock:(void (^)(id))block forView:(id)viewId implementingProtocol:(Protocol *)protocol
{
[self addUIBlock:^(UIView *view) {
if (![view.class conformsToProtocol:protocol]) {
block(nil);
} else {
block(view);
}
} forView:viewId];
}
- (void)dispatchOnClientThread:(dispatch_block_t)block
{
[self.bridge dispatchBlock:block queue:RCTJSThread];
}
- (void)executeUIBlock:(void (^)(NSDictionary<id,UIView *> *))block {
#if RCT_NEW_ARCH_ENABLED
[NSException raise:NSGenericException format:@"This version of `executeUIBlock` is not supported in the New Architecture"];
#else
__weak EXReactNativeAdapter *weakSelf = self;
dispatch_async(_bridge.uiManager.methodQueue, ^{
__strong EXReactNativeAdapter *strongSelf = weakSelf;
if (strongSelf) {
[strongSelf.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
block(viewRegistry);
}];
[strongSelf.bridge.uiManager setNeedsLayout];
}
});
#endif
}
- (void)executeUIBlock:(void (^)(id))block forView:(id)viewId implementingProtocol:(Protocol *)protocol {
[self executeUIBlock:^(UIView *view) {
if (![view.class conformsToProtocol:protocol]) {
block(nil);
} else {
block(view);
}
} forView:viewId];
}
- (void)executeUIBlock:(void (^)(id))block forView:(id)viewId ofClass:(Class)klass {
[self executeUIBlock:^(UIView *view) {
if (![view isKindOfClass:klass]) {
block(nil);
} else {
block(view);
}
} forView:viewId];
}
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
}
- (void)registerAppLifecycleListener:(id<EXAppLifecycleListener>)listener
{
[_lifecycleListeners addPointer:(__bridge void * _Nullable)(listener)];
}
- (void)unregisterAppLifecycleListener:(id<EXAppLifecycleListener>)listener
{
for (int i = 0; i < _lifecycleListeners.count; i++) {
void * _Nullable pointer = [_lifecycleListeners pointerAtIndex:i];
if (pointer == (__bridge void * _Nullable)(listener) || !pointer) {
[_lifecycleListeners removePointerAtIndex:i];
i--;
}
}
// -(void)compact doesn't work, that's why we have this `|| !pointer` above
// http://www.openradar.me/15396578
[_lifecycleListeners compact];
}
# pragma mark - EXJavaScriptContextProvider
- (JSGlobalContextRef)javaScriptContextRef
{
if ([_bridge respondsToSelector:@selector(jsContextRef)]) {
return _bridge.jsContextRef;
} else if (_bridge.runtime) {
// In react-native 0.59 vm is abstracted by JSI and all JSC specific references are removed
// To access jsc context we are extracting specific offset in jsi::Runtime, JSGlobalContextRef
// is first field inside Runtime class and in memory it's preceded only by pointer to virtual method table.
// WARNING: This is temporary solution that may break with new react-native releases.
return *(((JSGlobalContextRef *)(_bridge.runtime)) + 1);
}
return nil;
}
- (void *)javaScriptRuntimePointer
{
return _bridge.runtime;
}
# pragma mark - App state observing
- (void)startObserving
{
for (NSString *name in @[UIApplicationDidBecomeActiveNotification,
UIApplicationDidEnterBackgroundNotification,
UIApplicationDidFinishLaunchingNotification,
UIApplicationWillResignActiveNotification,
UIApplicationWillEnterForegroundNotification,
RCTContentDidAppearNotification,
RCTBridgeWillReloadNotification]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleAppStateDidChange:)
name:name
object:nil];
}
}
- (void)handleAppStateDidChange:(NSNotification *)notification
{
if ([notification.name isEqualToString:RCTContentDidAppearNotification]) {
[self notifyAboutContentDidAppear];
} else if ([notification.name isEqualToString:RCTBridgeWillReloadNotification]) {
[self notifyAboutContentWillReload];
} else if (
_isForegrounded && (
[notification.name isEqualToString:UIApplicationWillResignActiveNotification] ||
[notification.name isEqualToString:UIApplicationDidEnterBackgroundNotification] ||
[self isApplicationStateBackground]
)
) {
[self setAppStateToBackground];
} else if (!_isForegrounded && [self isApplicationStateActive]) {
[self setAppStateToForeground];
}
}
- (void)setAppStateToBackground
{
if (_isForegrounded) {
[[_lifecycleListeners allObjects] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj onAppBackgrounded];
}];
_isForegrounded = false;
}
}
- (void)setAppStateToForeground
{
if (!_isForegrounded) {
[[_lifecycleListeners allObjects] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj onAppForegrounded];
}];
_isForegrounded = true;
}
}
- (void)notifyAboutContentDidAppear
{
[[_lifecycleListeners allObjects] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj respondsToSelector:@selector(onAppContentDidAppear)]) {
[obj performSelector:@selector(onAppContentDidAppear)];
}
}];
}
- (void)notifyAboutContentWillReload
{
[[_lifecycleListeners allObjects] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj respondsToSelector:@selector(onAppContentWillReload)]) {
[obj performSelector:@selector(onAppContentWillReload)];
}
}];
}
- (void)stopObserving
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
# pragma mark - Internal methods
- (void)addUIBlock:(void (^)(UIView *view))block forView:(id)viewId
{
__weak RCTUIManager *uiManager = [_bridge uiManager];
dispatch_async(RCTGetUIManagerQueue(), ^{
[uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
UIView *view = [uiManager viewForReactTag:(NSNumber *)viewId];
block(view);
}];
});
}
- (void)executeUIBlock:(void (^)(UIView *view))block forView:(id)viewId
{
__weak RCTUIManager *uiManager = [_bridge uiManager];
dispatch_async(RCTGetUIManagerQueue(), ^{
[uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
UIView *view = [uiManager viewForReactTag:(NSNumber *)viewId];
block(view);
}];
[uiManager setNeedsLayout];
});
}
- (BOOL)isApplicationStateBackground
{
#if TARGET_OS_IOS || TARGET_OS_TV
return RCTSharedApplication().applicationState == UIApplicationStateBackground;
#elif TARGET_OS_OSX
return RCTSharedApplication().isHidden;
#endif
}
- (BOOL)isApplicationStateActive
{
#if TARGET_OS_IOS || TARGET_OS_TV
return RCTSharedApplication().applicationState == UIApplicationStateActive;
#elif TARGET_OS_OSX
return RCTSharedApplication().isActive;
#endif
}
@end

View File

@@ -0,0 +1,18 @@
// Copyright 2016-present 650 Industries. All rights reserved.
#import <ExpoModulesCore/EXExportedModule.h>
#import <ExpoModulesCore/EXPermissionsInterface.h>
@interface EXPermissionsService : NSObject <EXPermissionsInterface>
+ (EXPermissionStatus)statusForPermission:(NSDictionary *)permission;
+ (NSString *)permissionStringForStatus:(EXPermissionStatus)status;
- (void)askForGlobalPermissionUsingRequesterClass:(Class)requesterClass
withResolver:(EXPromiseResolveBlock)resolver
withRejecter:(EXPromiseRejectBlock)reject;
- (NSDictionary *)getPermissionUsingRequesterClass:(Class)requesterClass;
@end

View File

@@ -0,0 +1,173 @@
// Copyright 2016-present 650 Industries. All rights reserved.
#import <ExpoModulesCore/EXUtilitiesInterface.h>
#import <ExpoModulesCore/EXUtilities.h>
#import <ExpoModulesCore/EXPermissionsService.h>
#import <React/RCTLog.h>
NSString * const EXStatusKey = @"status";
NSString * const EXExpiresKey = @"expires";
NSString * const EXGrantedKey = @"granted";
NSString * const EXCanAskAgain = @"canAskAgain";
NSString * const EXPermissionExpiresNever = @"never";
@interface EXPermissionsService ()
@property (nonatomic, strong) NSMutableDictionary<NSString *, id<EXPermissionsRequester>> *requesters;
@property (nonatomic, strong) NSMapTable<Class, id<EXPermissionsRequester>> *requestersByClass;
@end
@implementation EXPermissionsService
- (instancetype)init
{
if (self = [super init]) {
_requesters = [NSMutableDictionary<NSString *, id<EXPermissionsRequester>> new];
_requestersByClass = [NSMapTable<Class, id<EXPermissionsRequester>> new];
}
return self;
}
- (void)registerRequesters:(NSArray<id<EXPermissionsRequester>> *)newRequesters {
for (id<EXPermissionsRequester> requester in newRequesters) {
[_requesters setObject:requester forKey:[[requester class] permissionType]];
[_requestersByClass setObject:requester forKey:[requester class]];
}
}
# pragma mark - permission requsters / getters
- (void)getPermissionUsingRequesterClass:(Class)requesterClass
resolve:(EXPromiseResolveBlock)resolve
reject:(EXPromiseRejectBlock)reject
{
NSDictionary *permission = [self getPermissionUsingRequesterClass:requesterClass];
if (permission == nil) {
return reject(@"E_PERMISSIONS_UNKNOWN", [NSString stringWithFormat:@"Unrecognized requester: %@", NSStringFromClass(requesterClass)], nil);
}
return resolve(permission);
}
- (NSDictionary *)getPermissionUsingRequesterClass:(Class)requesterClass
{
return [self getPermissionUsingRequester:[self getPermissionRequesterForClass:requesterClass]];
}
- (NSDictionary *)getPermissionsForResource:(NSString *)type
{
return [self getPermissionUsingRequester:[self getPermissionRequesterForType:type]];
}
- (NSDictionary *)getPermissionUsingRequester:(id<EXPermissionsRequester>)requester
{
if (requester) {
return [EXPermissionsService parsePermissionFromRequester:[requester getPermissions]];
}
return nil;
}
// shorthand method that checks both global and per-experience permission
- (BOOL)hasGrantedPermissionUsingRequesterClass:(Class)requesterClass
{
NSDictionary *permissions = [self getPermissionUsingRequesterClass:requesterClass];
if (!permissions) {
RCTLogWarn(@"Permission requester '%@' not found.", NSStringFromClass(requesterClass));
return false;
}
return [permissions[EXStatusKey] isEqualToString:@"granted"];
}
- (void)askForPermissionUsingRequesterClass:(Class)requesterClass
resolve:(EXPromiseResolveBlock)onResult
reject:(EXPromiseRejectBlock)reject
{
NSMutableDictionary *permission = [[self getPermissionUsingRequesterClass:requesterClass] mutableCopy];
// permission type not found - reject immediately
if (permission == nil) {
return reject(@"E_PERMISSIONS_UNKNOWN", [NSString stringWithFormat:@"Unrecognized requester: %@", NSStringFromClass(requesterClass)], nil);
}
BOOL isGranted = [EXPermissionsService statusForPermission:permission] == EXPermissionStatusGranted;
permission[@"granted"] = @(isGranted);
if (isGranted) {
return onResult(permission);
}
[self askForGlobalPermissionUsingRequesterClass:requesterClass withResolver:onResult withRejecter:reject];
}
- (void)askForGlobalPermissionUsingRequesterClass:(Class)requesterClass
withResolver:(EXPromiseResolveBlock)resolver
withRejecter:(EXPromiseRejectBlock)reject
{
id<EXPermissionsRequester> requester = [self getPermissionRequesterForClass:requesterClass];
if (requester == nil) {
return reject(@"E_PERMISSIONS_UNSUPPORTED", @"Cannot find requester", nil);
}
void (^permissionParser)(NSDictionary *) = ^(NSDictionary * permission){
resolver([EXPermissionsService parsePermissionFromRequester:permission]);
};
[requester requestPermissionsWithResolver:permissionParser rejecter:reject];
}
# pragma mark - helpers
+ (NSDictionary *)parsePermissionFromRequester:(NSDictionary *)permission
{
NSMutableDictionary *parsedPermission = [permission mutableCopy];
EXPermissionStatus status = (EXPermissionStatus)[permission[EXStatusKey] intValue];
BOOL isGranted = status == EXPermissionStatusGranted;
BOOL canAskAgain = status != EXPermissionStatusDenied;
[parsedPermission setValue:[[self class] permissionStringForStatus:status] forKey:EXStatusKey];
[parsedPermission setValue:EXPermissionExpiresNever forKey:EXExpiresKey];
[parsedPermission setValue:@(isGranted) forKey:EXGrantedKey];
[parsedPermission setValue:@(canAskAgain) forKey:EXCanAskAgain];
return parsedPermission;
}
+ (NSString *)permissionStringForStatus:(EXPermissionStatus)status
{
switch (status) {
case EXPermissionStatusGranted:
return @"granted";
case EXPermissionStatusDenied:
return @"denied";
default:
return @"undetermined";
}
}
+ (EXPermissionStatus)statusForPermission:(NSDictionary *)permission
{
NSString *status = permission[EXStatusKey];
if ([status isEqualToString:@"granted"]) {
return EXPermissionStatusGranted;
} else if ([status isEqualToString:@"denied"]) {
return EXPermissionStatusDenied;
} else {
return EXPermissionStatusUndetermined;
}
}
- (id<EXPermissionsRequester>)getPermissionRequesterForType:(NSString *)type
{
return _requesters[type];
}
- (id<EXPermissionsRequester>)getPermissionRequesterForClass:(Class)requesterClass
{
return [_requestersByClass objectForKey:requesterClass];
}
@end

View File

@@ -0,0 +1,11 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <UserNotifications/UserNotifications.h>
#import <ExpoModulesCore/EXInternalModule.h>
#import <ExpoModulesCore/EXUserNotificationCenterProxyInterface.h>
@interface EXReactNativeUserNotificationCenterProxy : NSObject <EXInternalModule, EXUserNotificationCenterProxyInterface>
@end

View File

@@ -0,0 +1,25 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <ExpoModulesCore/EXUtilities.h>
#import <ExpoModulesCore/EXReactNativeUserNotificationCenterProxy.h>
@implementation EXReactNativeUserNotificationCenterProxy
EX_REGISTER_MODULE();
+ (const NSArray<Protocol *> *)exportedInterfaces
{
return @[@protocol(EXUserNotificationCenterProxyInterface)];
}
- (void)getNotificationSettingsWithCompletionHandler:(void(^)(UNNotificationSettings *settings))completionHandler
{
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:completionHandler];
}
- (void)requestAuthorizationWithOptions:(UNAuthorizationOptions)options completionHandler:(void (^)(BOOL granted, NSError *__nullable error))completionHandler
{
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:options completionHandler:completionHandler];
}
@end