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,19 @@
// Copyright 2018-present 650 Industries. All rights reserved.
import ExpoModulesCore
@MainActor
@preconcurrency
@objc
public class AppDelegatesLoaderDelegate: NSObject {
/**
Gets and registers AppDelegate subscribers.
*/
@objc
public static func registerAppDelegateSubscribers(_ legacySubscriber: ExpoAppDelegateSubscriberProtocol) {
let modulesProvider = AppContext.modulesProvider(withName: "ExpoModulesProvider")
ExpoAppDelegateSubscriberRepository.registerSubscriber(legacySubscriber)
ExpoAppDelegateSubscriberRepository.registerSubscribersFrom(modulesProvider: modulesProvider)
ExpoAppDelegateSubscriberRepository.registerReactDelegateHandlersFrom(modulesProvider: modulesProvider)
}
}

View File

@@ -0,0 +1,15 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
Loads `ExpoAppDelegate` subscribers based on
the list from the generated `ExpoModulesProvider`.
*/
@interface EXAppDelegatesLoader : NSObject
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,22 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <Expo/EXLegacyAppDelegateWrapper.h>
#import <Expo/EXAppDelegatesLoader.h>
#import <Expo/Swift.h>
// Make the legacy wrapper conform to the protocol for subscribers.
@interface EXLegacyAppDelegateWrapper () <EXAppDelegateSubscriberProtocol>
@end
@implementation EXAppDelegatesLoader
// App delegate providers must be registered before any `AppDelegate` life-cycle event is called.
// Unfortunately it's not possible in Swift to run code right after the binary is loaded
// and before any code is executed, so we switch back to Objective-C just to do this one thing.
+ (void)load
{
[AppDelegatesLoaderDelegate registerAppDelegateSubscribers:[[EXLegacyAppDelegateWrapper alloc] init]];
}
@end

View File

@@ -0,0 +1,19 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
The legacy wrapper is still used to forward app delegate calls to singleton modules.
See `EXAppDelegatesLoader.m` which registers this class as a subscriber of `ExpoAppDelegate`.
*/
#if TARGET_OS_OSX
@interface EXLegacyAppDelegateWrapper : NSResponder <NSApplicationDelegate>
#else
@interface EXLegacyAppDelegateWrapper : UIResponder <UIApplicationDelegate>
#endif
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,272 @@
// Copyright © 2018 650 Industries. All rights reserved.
#import <Expo/EXLegacyAppDelegateWrapper.h>
#import <ExpoModulesCore/EXSingletonModule.h>
#import <ExpoModulesCore/Platform.h>
#import <ExpoModulesCore/EXModuleRegistryProvider.h>
#import <Foundation/FoundationErrors.h>
#if !TARGET_OS_OSX
static NSMutableArray<id<UIApplicationDelegate>> *subcontractors;
static NSMutableDictionary<NSString *,NSArray<id<UIApplicationDelegate>> *> *subcontractorsForSelector;
static dispatch_once_t onceToken;
#endif
@implementation EXLegacyAppDelegateWrapper
// The legacy app delegate wrapper is not supported on macOS, but we keep it no-op for convenience.
#if !TARGET_OS_OSX
@synthesize window = _window;
- (void)forwardInvocation:(NSInvocation *)invocation {
#if DEBUG
SEL selector = [invocation selector];
NSArray<id<UIApplicationDelegate>> *delegatesToBeCalled = [self getSubcontractorsImplementingSelector:selector];
NSString *selectorName = NSStringFromSelector(selector);
if ([delegatesToBeCalled count] > 0) {
[NSException raise:@"Method not implemented in UIApplicationDelegate" format:@"Some modules: %@ have registered for `%@` UIApplicationDelegate's callback, however, neither your AppDelegate nor %@ can handle this method. You'll need to either implement this method in your AppDelegate or submit a pull request to handle it in %@.", delegatesToBeCalled, selectorName, NSStringFromClass([self class]), NSStringFromClass([self class])];
}
#endif
[super forwardInvocation:invocation];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(nullable NSDictionary *)launchOptions
{
BOOL answer = NO;
SEL selector = @selector(application:didFinishLaunchingWithOptions:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
BOOL subcontractorAnswer = NO;
subcontractorAnswer = [subcontractor application:application didFinishLaunchingWithOptions:launchOptions];
answer |= subcontractorAnswer;
}
return answer;
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
SEL selector = @selector(applicationWillEnterForeground:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
[subcontractor applicationWillEnterForeground:application];
}
}
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
SEL selector = @selector(application:openURL:options:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
if ([subcontractor application:app openURL:url options:options]) {
return YES;
}
}
return NO;
}
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
SEL selector = @selector(application:performFetchWithCompletionHandler:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
__block NSUInteger subcontractorsLeft = [subcontractorsArray count];
__block UIBackgroundFetchResult fetchResult = UIBackgroundFetchResultNoData;
__block NSObject *lock = [NSObject new];
void (^handler)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) {
@synchronized (lock) {
if (result == UIBackgroundFetchResultFailed) {
fetchResult = UIBackgroundFetchResultFailed;
} else if (fetchResult != UIBackgroundFetchResultFailed && result == UIBackgroundFetchResultNewData) {
fetchResult = UIBackgroundFetchResultNewData;
}
subcontractorsLeft--;
if (subcontractorsLeft == 0) {
completionHandler(fetchResult);
}
}
};
if (subcontractorsLeft == 0) {
completionHandler(fetchResult);
} else {
for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
[subcontractor application:application performFetchWithCompletionHandler:handler];
}
}
}
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
SEL selector = @selector(application:continueUserActivity:restorationHandler:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
__block NSMutableArray<id<UIUserActivityRestoring>> * _Nullable mergedParams = [NSMutableArray new];
__block NSUInteger subcontractorsLeft = [subcontractorsArray count];
__block NSObject *lock = [NSObject new];
void (^handler)(NSArray<id<UIUserActivityRestoring>> * _Nullable) = ^(NSArray<id<UIUserActivityRestoring>> * _Nullable param) {
@synchronized (lock) {
[mergedParams addObjectsFromArray:param];
subcontractorsLeft--;
if (subcontractorsLeft == 0) {
restorationHandler(mergedParams);
}
}
};
BOOL result = NO;
for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
result = result || [subcontractor application:application continueUserActivity:userActivity restorationHandler:handler];
}
return result;
}
#pragma mark - BackgroundSession
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler
{
SEL selector = @selector(application:handleEventsForBackgroundURLSession:completionHandler:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
__block BOOL delegatingCompleted = NO;
__block int delegatesCompleted = 0;
__block unsigned long allDelegates = subcontractorsArray.count;
__block void (^completionHandlerCaller)(void) = ^ {
if (delegatesCompleted && delegatingCompleted == allDelegates) {
completionHandler();
}
};
for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
[subcontractor application:application handleEventsForBackgroundURLSession:identifier completionHandler:^(){
@synchronized (self) {
delegatesCompleted += 1;
completionHandlerCaller();
}
}];
}
@synchronized (self) {
delegatingCompleted = YES;
completionHandlerCaller();
}
}
#pragma mark - Notifications
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)token
{
SEL selector = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
[subcontractor application:application didRegisterForRemoteNotificationsWithDeviceToken:token];
}
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)err
{
SEL selector = @selector(application:didFailToRegisterForRemoteNotificationsWithError:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
for(id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
[subcontractor application:application didFailToRegisterForRemoteNotificationsWithError:err];
}
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
{
SEL selector = @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:);
NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
__block NSUInteger subcontractorsLeft = [subcontractorsArray count];
__block UIBackgroundFetchResult fetchResult = UIBackgroundFetchResultNoData;
__block NSObject *lock = [NSObject new];
void (^handler)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) {
@synchronized (lock) {
if (result == UIBackgroundFetchResultFailed) {
fetchResult = UIBackgroundFetchResultFailed;
} else if (fetchResult != UIBackgroundFetchResultFailed && result == UIBackgroundFetchResultNewData) {
fetchResult = UIBackgroundFetchResultNewData;
}
subcontractorsLeft--;
if (subcontractorsLeft == 0) {
completionHandler(fetchResult);
}
}
};
if (subcontractorsLeft == 0) {
completionHandler(fetchResult);
} else {
for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
[subcontractor application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:handler];
}
}
}
#pragma mark - Subcontractors
- (void)ensureSubcontractorsAreInitializedAndSorted {
dispatch_once(&onceToken, ^{
subcontractors = [[NSMutableArray alloc] init];
subcontractorsForSelector = [NSMutableDictionary new];
NSArray<EXSingletonModule*> * singletonModules = [[EXModuleRegistryProvider singletonModules] allObjects];
for (EXSingletonModule *singletonModule in singletonModules) {
if ([singletonModule conformsToProtocol:@protocol(UIApplicationDelegate)]) {
[subcontractors addObject:(id<UIApplicationDelegate>)singletonModule];
}
}
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"priority"
ascending:NO];
[subcontractors sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
});
}
- (NSArray<id<UIApplicationDelegate>> *)getSubcontractorsImplementingSelector:(SEL)selector {
[self ensureSubcontractorsAreInitializedAndSorted];
NSString *selectorKey = NSStringFromSelector(selector);
if (subcontractorsForSelector[selectorKey]) {
return subcontractorsForSelector[selectorKey];
}
NSMutableArray<id<UIApplicationDelegate>> *result = [NSMutableArray new];
for (id<UIApplicationDelegate> subcontractor in subcontractors) {
if ([subcontractor respondsToSelector:selector]) {
[result addObject:subcontractor];
}
}
subcontractorsForSelector[selectorKey] = result;
return result;
}
#endif // !TARGET_OS_OSX
@end

View File

@@ -0,0 +1,40 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#pragma once
#import <ExpoModulesCore/Platform.h>
#import <Expo/RCTAppDelegateUmbrella.h>
NS_ASSUME_NONNULL_BEGIN
@class EXReactDelegate;
NS_SWIFT_NAME(ExpoReactRootViewFactory)
@interface EXReactRootViewFactory : RCTRootViewFactory
@property (nonatomic, weak, nullable) EXReactDelegate *reactDelegate;
/**
Initializer for ExpoReactNativeFactory integration
*/
- (instancetype)initWithReactDelegate:(nullable EXReactDelegate *)reactDelegate
configuration:(RCTRootViewFactoryConfiguration *)configuration
turboModuleManagerDelegate:(nullable id)turboModuleManagerDelegate;
/**
Calls super `viewWithModuleName:initialProperties:launchOptions:` from `RCTRootViewFactory`.
*/
#if TARGET_OS_IOS || TARGET_OS_TV
- (UIView *)superViewWithModuleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties
launchOptions:(nullable NSDictionary *)launchOptions
devMenuConfiguration:(nullable RCTDevMenuConfiguration *)devMenuConfiguration;
#else
- (UIView *)superViewWithModuleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties
launchOptions:(nullable NSDictionary *)launchOptions;
#endif
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,81 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <Expo/EXReactRootViewFactory.h>
#import <Expo/RCTAppDelegateUmbrella.h>
#import <Expo/Swift.h>
#import <React/RCTDevMenu.h>
// When `use_frameworks!` is used, the generated Swift header is inside ExpoModulesCore module.
// Otherwise, it's available only locally with double-quoted imports.
#if __has_include(<ExpoModulesCore/ExpoModulesCore-Swift.h>)
#import <ExpoModulesCore/ExpoModulesCore-Swift.h>
#else
#import "ExpoModulesCore-Swift.h"
#endif
@interface RCTRootViewFactory ()
- (NSURL *)bundleURL;
@end
@implementation EXReactRootViewFactory
- (instancetype)initWithReactDelegate:(nullable EXReactDelegate *)reactDelegate
configuration:(RCTRootViewFactoryConfiguration *)configuration
turboModuleManagerDelegate:(nullable id<RCTTurboModuleManagerDelegate>)turboModuleManagerDelegate
{
if (self = [super initWithConfiguration:configuration andTurboModuleManagerDelegate:turboModuleManagerDelegate]) {
self.reactDelegate = reactDelegate;
}
return self;
}
#if TARGET_OS_IOS || TARGET_OS_TV
- (UIView *)viewWithModuleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties
launchOptions:(nullable NSDictionary *)launchOptions
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
{
if (self.reactDelegate != nil) {
return [self.reactDelegate createReactRootViewWithModuleName:moduleName initialProperties:initialProperties launchOptions:launchOptions];
}
return [super viewWithModuleName:moduleName initialProperties:initialProperties launchOptions:launchOptions devMenuConfiguration:devMenuConfiguration];
}
- (UIView *)superViewWithModuleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties
launchOptions:(nullable NSDictionary *)launchOptions
devMenuConfiguration:(nullable RCTDevMenuConfiguration *)devMenuConfiguration
{
if (devMenuConfiguration == nil) {
devMenuConfiguration = [RCTDevMenuConfiguration defaultConfiguration];
}
return [super viewWithModuleName:moduleName initialProperties:initialProperties launchOptions:launchOptions devMenuConfiguration:devMenuConfiguration];
}
#else
- (UIView *)viewWithModuleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties
launchOptions:(nullable NSDictionary *)launchOptions
{
if (self.reactDelegate != nil) {
return [self.reactDelegate createReactRootViewWithModuleName:moduleName initialProperties:initialProperties launchOptions:launchOptions];
}
return [super viewWithModuleName:moduleName initialProperties:initialProperties launchOptions:launchOptions];
}
- (UIView *)superViewWithModuleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties
launchOptions:(nullable NSDictionary *)launchOptions
{
return [super viewWithModuleName:moduleName initialProperties:initialProperties launchOptions:launchOptions];
}
#endif
- (NSURL *)bundleURL
{
return [self.reactDelegate bundleURL] ?: [super bundleURL];
}
@end

View File

@@ -0,0 +1,247 @@
import Foundation
import ExpoModulesCore
/**
Allows classes extending `ExpoAppDelegateSubscriber` to hook into project's app delegate
by forwarding `UIApplicationDelegate` events to the subscribers.
Keep functions and markers in sync with https://developer.apple.com/documentation/uikit/uiapplicationdelegate
*/
@objc(EXExpoAppDelegate)
open class ExpoAppDelegate: UIResponder, UIApplicationDelegate {
override public init() {
// The subscribers are initializing and registering before the main code starts executing.
// Here we're letting them know when the `AppDelegate` is being created,
// which happens at the beginning of the main code execution and before launching the app.
ExpoAppDelegateSubscriberRepository.subscribers.forEach {
$0.appDelegateWillBeginInitialization?()
}
super.init()
}
#if os(macOS)
required public init?(coder: NSCoder) {
super.init(coder: coder)
}
#endif
// MARK: - Initializing the App
#if os(iOS) || os(tvOS)
open func application(
_ application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
return ExpoAppDelegateSubscriberManager.application(application, willFinishLaunchingWithOptions: launchOptions)
}
open func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
return ExpoAppDelegateSubscriberManager.application(application, didFinishLaunchingWithOptions: launchOptions)
}
#elseif os(macOS)
open func applicationWillFinishLaunching(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationWillFinishLaunching(notification)
}
open func applicationDidFinishLaunching(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationDidFinishLaunching(notification)
}
// TODO: - Configuring and Discarding Scenes
#endif
// MARK: - Responding to App Life-Cycle Events
#if os(iOS) || os(tvOS)
@objc
open func applicationDidBecomeActive(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationDidBecomeActive(application)
}
@objc
open func applicationWillResignActive(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationWillResignActive(application)
}
@objc
open func applicationDidEnterBackground(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationDidEnterBackground(application)
}
open func applicationWillEnterForeground(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationWillEnterForeground(application)
}
open func applicationWillTerminate(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationWillTerminate(application)
}
#elseif os(macOS)
@objc
open func applicationDidBecomeActive(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationDidBecomeActive(notification)
}
@objc
open func applicationWillResignActive(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationWillResignActive(notification)
}
@objc
open func applicationDidHide(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationDidHide(notification)
}
open func applicationWillUnhide(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationWillUnhide(notification)
}
open func applicationWillTerminate(_ notification: Notification) {
ExpoAppDelegateSubscriberManager.applicationWillTerminate(notification)
}
#endif
// MARK: - Responding to Environment Changes
#if os(iOS) || os(tvOS)
open func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
ExpoAppDelegateSubscriberManager.applicationDidReceiveMemoryWarning(application)
}
#endif
// TODO: - Managing App State Restoration
// MARK: - Downloading Data in the Background
#if os(iOS) || os(tvOS)
open func application(
_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void
) {
ExpoAppDelegateSubscriberManager.application(application, handleEventsForBackgroundURLSession: identifier, completionHandler: completionHandler)
}
#endif
// MARK: - Handling Remote Notification Registration
open func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
ExpoAppDelegateSubscriberManager.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
}
open func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
ExpoAppDelegateSubscriberManager.application(application, didFailToRegisterForRemoteNotificationsWithError: error)
}
#if os(iOS) || os(tvOS)
open func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
ExpoAppDelegateSubscriberManager.application(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler)
}
#elseif os(macOS)
open func application(
_ application: NSApplication,
didReceiveRemoteNotification userInfo: [String: Any]
) {
ExpoAppDelegateSubscriberManager.application(application, didReceiveRemoteNotification: userInfo)
}
#endif
// MARK: - Continuing User Activity and Handling Quick Actions
open func application(_ application: UIApplication, willContinueUserActivityWithType userActivityType: String) -> Bool {
return ExpoAppDelegateSubscriberManager.application(application, willContinueUserActivityWithType: userActivityType)
}
#if os(iOS) || os(tvOS)
open func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
return ExpoAppDelegateSubscriberManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
#elseif os(macOS)
open func application(
_ application: NSApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([any NSUserActivityRestoring]) -> Void
) -> Bool {
return ExpoAppDelegateSubscriberManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
#endif
open func application(_ application: UIApplication, didUpdate userActivity: NSUserActivity) {
return ExpoAppDelegateSubscriberManager.application(application, didUpdate: userActivity)
}
open func application(_ application: UIApplication, didFailToContinueUserActivityWithType userActivityType: String, error: Error) {
return ExpoAppDelegateSubscriberManager.application(application, didFailToContinueUserActivityWithType: userActivityType, error: error)
}
#if os(iOS)
open func application(
_ application: UIApplication,
performActionFor shortcutItem: UIApplicationShortcutItem,
completionHandler: @escaping (Bool) -> Void
) {
ExpoAppDelegateSubscriberManager.application(application, performActionFor: shortcutItem, completionHandler: completionHandler)
}
#endif
// MARK: - Background Fetch
#if os(iOS) || os(tvOS)
open func application(
_ application: UIApplication,
performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
ExpoAppDelegateSubscriberManager.application(application, performFetchWithCompletionHandler: completionHandler)
}
// TODO: - Interacting With WatchKit
// TODO: - Interacting With HealthKit
#endif
// MARK: - Opening a URL-Specified Resource
#if os(iOS) || os(tvOS)
open func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
return ExpoAppDelegateSubscriberManager.application(app, open: url, options: options)
}
#elseif os(macOS)
open func application(_ app: NSApplication, open urls: [URL]) {
ExpoAppDelegateSubscriberManager.application(app, open: urls)
}
#endif
// TODO: - Disallowing Specified App Extension Types
// TODO: - Handling SiriKit Intents
// TODO: - Handling CloudKit Invitations
// MARK: - Managing Interface Geometry
#if os(iOS)
/**
* Sets allowed orientations for the application. It will use the values from `Info.plist`as the orientation mask unless a subscriber requested
* a different orientation.
*/
open func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return ExpoAppDelegateSubscriberManager.application(application, supportedInterfaceOrientationsFor: window)
}
#endif
}

View File

@@ -0,0 +1,12 @@
// Copyright 2025-present 650 Industries. All rights reserved.
#import <ExpoModulesCore/ExpoModulesCore.h>
#import <Expo/RCTAppDelegateUmbrella.h>
@protocol RCTHostDelegate;
@protocol RCTHostRuntimeDelegate;
NS_SWIFT_NAME(ExpoReactNativeFactoryObjC)
@interface EXReactNativeFactory : RCTReactNativeFactory <RCTHostDelegate, RCTHostRuntimeDelegate>
@end

View File

@@ -0,0 +1,45 @@
// Copyright 2025-present 650 Industries. All rights reserved.
#import <Expo/ExpoReactNativeFactory.h>
#import <ExpoModulesCore/ExpoModulesCore.h>
#import <ExpoModulesCore/EXRuntime.h>
#if __has_include(<ExpoModulesCore/ExpoModulesCore-Swift.h>)
#import <ExpoModulesCore/ExpoModulesCore-Swift.h>
#else
#import "ExpoModulesCore-Swift.h"
#endif
#import <ReactCommon/RCTHost.h>
@implementation EXReactNativeFactory {
EXAppContext *_appContext;
}
#pragma mark - RCTHostDelegate
// [main thread]
- (void)hostDidStart:(nonnull RCTHost *)host
{
// Setting the runtime delegate here doesn't feel right, but there is no other way
// to capture the `host:didInitializeRuntime:` method call.
// With the current API design we also depend that the runtime is initialized after the host started,
// which isn't obvious, especially they are invoked on different threads.
// Ideally if the current `RCTHostRuntimeDelegate` is part of `RCTHostDelegate`.
host.runtimeDelegate = self;
}
#pragma mark - RCTHostRuntimeDelegate
// [JS thread]
- (void)host:(nonnull RCTHost *)host didInitializeRuntime:(jsi::Runtime &)runtime
{
_appContext = [[EXAppContext alloc] init];
// Inject and decorate the `global.expo` object
_appContext._runtime = [[EXRuntime alloc] initWithRuntime:runtime];
[_appContext setHostWrapper:[[EXHostWrapper alloc] initWithHost:host]];
[_appContext registerNativeModules];
}
@end

View File

@@ -0,0 +1,149 @@
// Copyright 2015-present 650 Industries. All rights reserved.
import React
public class ExpoReactNativeFactory: ExpoReactNativeFactoryObjC, ExpoReactNativeFactoryProtocol {
private let defaultModuleName = "main"
@MainActor
private lazy var reactDelegate: ExpoReactDelegate = {
ExpoReactDelegate(
handlers: ExpoAppDelegateSubscriberRepository.reactDelegateHandlers,
reactNativeFactory: self
)
}()
@objc public override init(delegate: any RCTReactNativeFactoryDelegate) {
let releaseLevel = (Bundle.main.object(forInfoDictionaryKey: "ReactNativeReleaseLevel") as? String)
.flatMap { [
"canary": RCTReleaseLevel.Canary,
"experimental": RCTReleaseLevel.Experimental,
"stable": RCTReleaseLevel.Stable
][$0.lowercased()]
}
?? RCTReleaseLevel.Stable
super.init(delegate: delegate, releaseLevel: releaseLevel)
}
@MainActor
@objc func createRCTRootViewFactory() -> RCTRootViewFactory {
// Alan: This is temporary. We need to cast to ExpoReactNativeFactoryDelegate here because currently, if you extend RCTReactNativeFactory
// from Swift, customizeRootView will not work on the new arch because the cast to RCTRootView will never
// succeed which breaks expo-splash-screen and react-native-bootsplash.
guard let weakDelegate = self.delegate as? ExpoReactNativeFactoryDelegate else {
fatalError("ExpoReactNativeFactory: delegate is nil.")
}
let bundleUrlBlock: RCTBundleURLBlock = { [weak weakDelegate] in
return weakDelegate?.bundleURL()
}
let configuration = RCTRootViewFactoryConfiguration(
bundleURLBlock: bundleUrlBlock,
newArchEnabled: weakDelegate.newArchEnabled()
)
configuration.createRootViewWithBridge = { bridge, moduleName, initProps in
return weakDelegate.createRootView(with: bridge, moduleName: moduleName, initProps: initProps)
}
configuration.jsRuntimeConfiguratorDelegate = delegate
configuration.createBridgeWithDelegate = { delegate, launchOptions in
weakDelegate.createBridge(with: delegate, launchOptions: launchOptions)
}
configuration.customizeRootView = { rootView in
weakDelegate.customize(rootView)
}
// NOTE(kudo): `sourceURLForBridge` is not referenced intentionally because it does not support New Architecture.
configuration.sourceURLForBridge = nil
configuration.loadSourceForBridgeWithProgress = { bridge, onProgress, onComplete in
weakDelegate.loadSource(for: bridge, onProgress: onProgress, onComplete: onComplete)
}
if weakDelegate.responds(to: #selector(RCTReactNativeFactoryDelegate.extraModules(for:))) {
configuration.extraModulesForBridge = { bridge in
weakDelegate.extraModules(for: bridge)
}
}
if weakDelegate.responds(to: #selector(RCTReactNativeFactoryDelegate.extraLazyModuleClasses(for:))) {
configuration.extraLazyModuleClassesForBridge = { bridge in
weakDelegate.extraLazyModuleClasses(for: bridge)
}
}
if weakDelegate.responds(to: #selector(RCTReactNativeFactoryDelegate.bridge(_:didNotFindModule:))) {
configuration.bridgeDidNotFindModule = { bridge, moduleName in
weakDelegate.bridge(bridge, didNotFindModule: moduleName)
}
}
return ExpoReactRootViewFactory(
reactDelegate: reactDelegate,
configuration: configuration,
turboModuleManagerDelegate: self
)
}
public func recreateRootView(
withBundleURL: URL?,
moduleName: String?,
initialProps: [AnyHashable: Any]?,
launchOptions: [AnyHashable: Any]?
) -> UIView {
guard let delegate = self.delegate else {
fatalError("recreateRootView: Missing RCTReactNativeFactoryDelegate")
}
let configuration = self.rootViewFactory.value(forKey: "_configuration") as? RCTRootViewFactoryConfiguration
if let bundleURL = withBundleURL {
configuration?.bundleURLBlock = {
return bundleURL
}
}
let rootView: UIView
if let factory = self.rootViewFactory as? ExpoReactRootViewFactory {
// RCTDevMenuConfiguration is only available in react-native 0.83+
#if os(iOS) || os(tvOS)
// When calling `recreateRootViewWithBundleURL:` from `EXReactRootViewFactory`,
// we don't want to loop the ReactDelegate again. Otherwise, it will be an infinite loop.
rootView = factory.superView(
withModuleName: moduleName ?? defaultModuleName,
initialProperties: initialProps,
launchOptions: launchOptions ?? [:],
devMenuConfiguration: self.devMenuConfiguration
)
#else
rootView = factory.superView(
withModuleName: moduleName ?? defaultModuleName,
initialProperties: initialProps,
launchOptions: launchOptions ?? [:]
)
#endif
} else {
#if os(iOS) || os(tvOS)
rootView = rootViewFactory.view(
withModuleName: moduleName ?? defaultModuleName,
initialProperties: initialProps,
launchOptions: launchOptions,
devMenuConfiguration: self.devMenuConfiguration
)
#else
rootView = rootViewFactory.view(
withModuleName: moduleName ?? defaultModuleName,
initialProperties: initialProps,
launchOptions: launchOptions
)
#endif
}
return rootView
}
}

View File

@@ -0,0 +1,13 @@
import React
open class ExpoReactNativeFactoryDelegate: RCTDefaultReactNativeFactoryDelegate {
open override func customize(_ rootView: UIView) {
ExpoAppDelegateSubscriberRepository.subscribers.forEach { $0.customizeRootView?(rootView) }
}
open override func createRootViewController() -> UIViewController {
return ExpoAppDelegateSubscriberRepository.reactDelegateHandlers.lazy
.compactMap { $0.createRootViewController() }
.first(where: { _ in true }) ?? UIViewController()
}
}

View File

@@ -0,0 +1,29 @@
// Copyright 2015-present 650 Industries. All rights reserved.
/**
`React-RCTAppDelegate-umbrella.h` requires `USE_HERMES`.
This umbrella wrapper lets us import `React-RCTAppDelegate-umbrella.h` in `Expo.h` without requiring `USE_HERMES` from app project settings.
*/
#if __has_include(<reacthermes/HermesExecutorFactory.h>)
#ifndef USE_HERMES
#define USE_HERMES 1
#define INLINE_USE_HERMES 1
#endif // USE_HERMES
#endif // __has_include(<reacthermes/HermesExecutorFactory.h>)
#if __has_include(<React_RCTAppDelegate/React-RCTAppDelegate-umbrella.h>)
#import <React_RCTAppDelegate/React-RCTAppDelegate-umbrella.h>
#else
#import <React_RCTAppDelegate/React_RCTAppDelegate-umbrella.h>
#endif
#if INLINE_USE_HERMES
#undef USE_HERMES
#undef INLINE_USE_HERMES
#endif // INLINE_USE_HERMES

14
node_modules/expo/ios/EXAppDefinesLoader.h generated vendored Normal file
View File

@@ -0,0 +1,14 @@
// Copyright 2016-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
This class loads some preprocessors and pass into `EXAppDefines` of ExpoModulesCore.
*/
@interface EXAppDefinesLoader : NSObject
@end
NS_ASSUME_NONNULL_END

30
node_modules/expo/ios/EXAppDefinesLoader.m generated vendored Normal file
View File

@@ -0,0 +1,30 @@
// Copyright 2016-present 650 Industries. All rights reserved.
#import <Expo/EXAppDefinesLoader.h>
#import <ExpoModulesCore/ExpoModulesCore.h>
#import <ExpoModulesCore/EXAppDefines.h>
#import <React/RCTDefines.h>
@implementation EXAppDefinesLoader
+ (void)load
{
BOOL APP_DEBUG;
[EXAppDefines load:@{
#if DEBUG
@"APP_DEBUG": @(YES),
#else
@"APP_DEBUG": @(NO),
#endif
@"APP_RCT_DEBUG": @(RCT_DEBUG),
@"APP_RCT_DEV": @(RCT_DEV),
#if RCT_NEW_ARCH_ENABLED
@"APP_NEW_ARCH_ENABLED": @(YES),
#else
@"APP_NEW_ARCH_ENABLED": @(NO),
#endif
}];
}
@end

4
node_modules/expo/ios/Expo.h generated vendored Normal file
View File

@@ -0,0 +1,4 @@
#import <ExpoModulesCore/ExpoModulesCore.h>
#import <Expo/EXAppDefinesLoader.h>
#import <Expo/ExpoReactNativeFactory.h>
#import <Expo/RCTAppDelegateUmbrella.h>

2
node_modules/expo/ios/Expo.swift generated vendored Normal file
View File

@@ -0,0 +1,2 @@
// Export public API from the modules core, so modules and the AppDelegate can simply `import Expo`.
@_exported import ExpoModulesCore

View File

@@ -0,0 +1,12 @@
// Copyright 2015-present 650 Industries. All rights reserved.
/**
For callsites to customize network fetch functionalities like having custom `URLSessionConfiguration`.
*/
@objc(EXFetchCustomExtension)
public class ExpoFetchCustomExtension: NSObject {
@MainActor @objc
public static func setCustomURLSessionConfigurationProvider(_ provider: NSURLSessionConfigurationProvider?) {
urlSessionConfigurationProvider = provider
}
}

119
node_modules/expo/ios/Fetch/ExpoFetchModule.swift generated vendored Normal file
View File

@@ -0,0 +1,119 @@
// Copyright 2015-present 650 Industries. All rights reserved.
@preconcurrency import ExpoModulesCore
private let fetchRequestQueue = DispatchQueue(label: "expo.modules.fetch.RequestQueue")
nonisolated(unsafe) internal var urlSessionConfigurationProvider: NSURLSessionConfigurationProvider?
public final class ExpoFetchModule: Module {
private lazy var urlSession = createURLSession()
private let urlSessionDelegate: URLSessionSessionDelegateProxy
public required init(appContext: AppContext) {
urlSessionDelegate = URLSessionSessionDelegateProxy(dispatchQueue: fetchRequestQueue)
super.init(appContext: appContext)
}
public func definition() -> ModuleDefinition {
Name("ExpoFetchModule")
OnDestroy {
urlSession.invalidateAndCancel()
}
// swiftlint:disable:next closure_body_length
Class(NativeResponse.self) {
Constructor {
return NativeResponse(dispatchQueue: fetchRequestQueue)
}
AsyncFunction("startStreaming") { (response: NativeResponse) -> Data? in
return response.startStreaming()
}.runOnQueue(fetchRequestQueue)
AsyncFunction("cancelStreaming") { (response: NativeResponse, _ reason: String) in
response.cancelStreaming()
}.runOnQueue(fetchRequestQueue)
Property("bodyUsed", \.bodyUsed)
Property("_rawHeaders") { (response: NativeResponse) in
return response.responseInit?.headers ?? []
}
Property("status") { (response: NativeResponse) in
return response.responseInit?.status ?? -1
}
Property("statusText") { (response: NativeResponse) in
return response.responseInit?.statusText ?? ""
}
Property("url") { (response: NativeResponse) in
return response.responseInit?.url ?? ""
}
Property("redirected", \.redirected)
AsyncFunction("arrayBuffer") { (response: NativeResponse, promise: Promise) in
response.waitFor(states: [.bodyCompleted]) { _ in
let data = response.sink.finalize()
promise.resolve(ArrayBuffer.wrap(dataWithoutCopy: data))
}
}.runOnQueue(fetchRequestQueue)
AsyncFunction("text") { (response: NativeResponse, promise: Promise) in
response.waitFor(states: [.bodyCompleted]) { _ in
let data = response.sink.finalize()
let text = String(decoding: data, as: UTF8.self)
promise.resolve(text)
}
}.runOnQueue(fetchRequestQueue)
}
Class(NativeRequest.self) {
Constructor { (nativeResponse: NativeResponse) in
return NativeRequest(response: nativeResponse)
}
AsyncFunction("start") { (request: NativeRequest, url: URL, requestInit: NativeRequestInit, requestBody: Data?, promise: Promise) in
request.start(
urlSession: urlSession,
urlSessionDelegate: urlSessionDelegate,
url: url,
requestInit: requestInit,
requestBody: requestBody
)
request.response.waitFor(states: [.responseReceived, .errorReceived]) { state in
if state == .responseReceived {
promise.resolve()
} else if state == .errorReceived {
promise.reject(request.response.error ?? FetchUnknownException())
}
}
}.runOnQueue(fetchRequestQueue)
AsyncFunction("cancel") { (request: NativeRequest) in
request.cancel(urlSessionDelegate: self.urlSessionDelegate)
}.runOnQueue(fetchRequestQueue)
}
}
private func createURLSession() -> URLSession {
let config: URLSessionConfiguration
if let urlSessionConfigurationProvider, let concreteConfig = urlSessionConfigurationProvider() {
config = concreteConfig
} else {
config = URLSessionConfiguration.default
config.httpShouldSetCookies = true
config.httpCookieAcceptPolicy = .always
config.httpCookieStorage = HTTPCookieStorage.shared
let useWifiOnly = Bundle.main.infoDictionary?["ReactNetworkForceWifiOnly"] as? Bool ?? false
if useWifiOnly {
config.allowsCellularAccess = !useWifiOnly
}
}
return URLSession(configuration: config, delegate: urlSessionDelegate, delegateQueue: nil)
}
}

103
node_modules/expo/ios/Fetch/ExpoURLSessionTask.swift generated vendored Normal file
View File

@@ -0,0 +1,103 @@
// Copyright 2015-present 650 Industries. All rights reserved.
import ExpoModulesCore
/**
An URLSessionDataTask wrapper.
*/
internal final class ExpoURLSessionTask: NSObject, URLSessionTaskDelegate, URLSessionDataDelegate, @unchecked Sendable {
private let delegate: ExpoURLSessionTaskDelegate
private var task: URLSessionDataTask?
init(delegate: ExpoURLSessionTaskDelegate) {
self.delegate = delegate
super.init()
}
func start(
urlSession: URLSession,
urlSessionDelegate: URLSessionSessionDelegateProxy,
url: URL,
requestInit: NativeRequestInit,
requestBody: Data?
) {
let request = NSMutableURLRequest(url: url)
URLProtocol.setProperty(requestInit.redirect == .follow, forKey: "shouldFollowRedirects", in: request)
request.httpMethod = requestInit.method
request.timeoutInterval = 0
if requestInit.credentials == .include {
request.httpShouldHandleCookies = true
if let cookies = HTTPCookieStorage.shared.cookies(for: url) {
request.allHTTPHeaderFields = HTTPCookie.requestHeaderFields(with: cookies)
}
} else {
request.httpShouldHandleCookies = false
}
for tuple in requestInit.headers {
request.addValue(tuple[1], forHTTPHeaderField: tuple[0])
}
request.httpBody = requestBody
let task = urlSession.dataTask(with: request as URLRequest)
urlSessionDelegate.addDelegate(task: task, delegate: self)
self.task = task
task.resume()
self.delegate.urlSessionDidStart(self)
}
func cancel(urlSessionDelegate: URLSessionSessionDelegateProxy) {
if let task {
urlSessionDelegate.removeDelegate(task: task)
task.cancel()
}
}
// MARK: - URLSessionTaskDelegate/URLSessionDataDelegate implementations
func urlSession(
_ session: URLSession,
task: URLSessionTask,
willPerformHTTPRedirection response: HTTPURLResponse,
newRequest request: URLRequest,
completionHandler: @escaping (URLRequest?) -> Void
) {
self.delegate.urlSession(
self,
task: task,
willPerformHTTPRedirection: response,
newRequest: request,
completionHandler: completionHandler)
}
func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive response: URLResponse,
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void
) {
self.delegate.urlSession(self, didReceive: response)
completionHandler(.allow)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
self.delegate.urlSession(self, didReceive: data)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
self.delegate.urlSession(self, task: task, didCompleteWithError: error)
}
}
internal protocol ExpoURLSessionTaskDelegate: AnyObject, Sendable {
func urlSessionDidStart(_ session: ExpoURLSessionTask)
func urlSession(_ session: ExpoURLSessionTask, didReceive response: URLResponse)
func urlSession(_ session: ExpoURLSessionTask, didReceive data: Data)
func urlSession(
_ session: ExpoURLSessionTask,
task: URLSessionTask,
willPerformHTTPRedirection response: HTTPURLResponse,
newRequest request: URLRequest,
completionHandler: @escaping (URLRequest?) -> Void
)
func urlSession(_ session: ExpoURLSessionTask, task: URLSessionTask, didCompleteWithError error: Error?)
}

21
node_modules/expo/ios/Fetch/FetchExceptions.swift generated vendored Normal file
View File

@@ -0,0 +1,21 @@
// Copyright 2015-present 650 Industries. All rights reserved.
import ExpoModulesCore
internal final class FetchUnknownException: Exception {
override var reason: String {
"Unknown error"
}
}
internal final class FetchRequestCanceledException: Exception {
override var reason: String {
"Fetch request has been canceled"
}
}
internal final class FetchRedirectException: Exception {
override var reason: String {
"Redirect is not allowed when redirect mode is 'error'"
}
}

38
node_modules/expo/ios/Fetch/NativeRequest.swift generated vendored Normal file
View File

@@ -0,0 +1,38 @@
// Copyright 2015-present 650 Industries. All rights reserved.
import ExpoModulesCore
/**
A SharedObject for request.
*/
internal final class NativeRequest: SharedObject, @unchecked Sendable {
internal let response: NativeResponse
internal let task: ExpoURLSessionTask
init(response: NativeResponse) {
self.response = response
self.task = ExpoURLSessionTask(delegate: self.response)
}
func start(
urlSession: URLSession,
urlSessionDelegate: URLSessionSessionDelegateProxy,
url: URL,
requestInit: NativeRequestInit,
requestBody: Data?
) {
self.response.redirectMode = requestInit.redirect
self.task.start(
urlSession: urlSession,
urlSessionDelegate: urlSessionDelegate,
url: url,
requestInit: requestInit,
requestBody: requestBody
)
}
func cancel(urlSessionDelegate: URLSessionSessionDelegateProxy) {
self.task.cancel(urlSessionDelegate: urlSessionDelegate)
self.response.emitRequestCanceled()
}
}

View File

@@ -0,0 +1,11 @@
// Copyright 2015-present 650 Industries. All rights reserved.
import ExpoModulesCore
/**
Enum for RequestInit.credentials.
*/
internal enum NativeRequestCredentials: String, Enumerable {
case include
case omit
}

20
node_modules/expo/ios/Fetch/NativeRequestInit.swift generated vendored Normal file
View File

@@ -0,0 +1,20 @@
// Copyright 2015-present 650 Industries. All rights reserved.
import ExpoModulesCore
/**
Record for RequestInit.
*/
internal struct NativeRequestInit: Record {
@Field
var credentials: NativeRequestCredentials = .include
@Field
var headers: [[String]] = []
@Field
var method: String = "GET"
@Field
var redirect: NativeRequestRedirect = .follow
}

View File

@@ -0,0 +1,10 @@
import ExpoModulesCore
/**
Enum for RequestInit.redirect.
*/
internal enum NativeRequestRedirect: String, Enumerable {
case follow
case manual
case error
}

227
node_modules/expo/ios/Fetch/NativeResponse.swift generated vendored Normal file
View File

@@ -0,0 +1,227 @@
// Copyright 2015-present 650 Industries. All rights reserved.
import ExpoModulesCore
/**
A SharedObject for response.
*/
internal final class NativeResponse: SharedObject, ExpoURLSessionTaskDelegate, @unchecked Sendable {
internal let sink: ResponseSink
private let dispatchQueue: DispatchQueue
private(set) var state: ResponseState = .intialized {
didSet {
dispatchQueue.async { [weak self] in
guard let self else {
return
}
self.stateChangeOnceListeners.removeAll { $0(self.state) == true }
}
}
}
private typealias StateChangeListener = (ResponseState) -> Bool
private var stateChangeOnceListeners: [StateChangeListener] = []
private(set) var responseInit: NativeResponseInit?
private(set) var redirected = false
private(set) var error: Error?
var redirectMode: NativeRequestRedirect = .follow
var bodyUsed: Bool {
return self.sink.bodyUsed
}
init(dispatchQueue: DispatchQueue) {
self.sink = ResponseSink()
self.dispatchQueue = dispatchQueue
}
func startStreaming() -> Data? {
if isInvalidState(.responseReceived, .bodyCompleted) {
return nil
}
if state == .responseReceived {
state = .bodyStreamingStarted
let queuedData = self.sink.finalize()
emit(event: "didReceiveResponseData", arguments: queuedData)
} else if state == .bodyCompleted {
let queuedData = self.sink.finalize()
return queuedData
}
return nil
}
func cancelStreaming() {
if isInvalidState(.bodyStreamingStarted) {
return
}
state = .bodyStreamingCanceled
}
func emitRequestCanceled() {
let error = FetchRequestCanceledException()
self.error = error
if state == .bodyStreamingStarted {
emit(event: "didFailWithError", arguments: error.localizedDescription)
}
state = .errorReceived
emit(event: "readyForJSFinalization")
}
/**
Waits for given states and when it meets the requirement, executes the callback.
*/
func waitFor(states: [ResponseState], callback: @escaping @Sendable (ResponseState) -> Void) {
if states.contains(state) {
callback(state)
return
}
dispatchQueue.async { [weak self] () in
guard let self else {
return
}
self.stateChangeOnceListeners.append { newState in
if states.contains(newState) {
callback(newState)
return true
}
return false
}
}
}
/**
Check valid state machine
*/
private func isInvalidState(_ validStates: ResponseState...) -> Bool {
if validStates.contains(state) {
return false
}
let validStatesString = validStates.map { "\($0.rawValue)" }.joined(separator: ",")
log.error("Invalid state - currentState[\(state.rawValue)] validStates[\(validStatesString)]")
return true
}
/**
Factory of NativeResponseInit
*/
private static func createResponseInit(response: URLResponse) -> NativeResponseInit? {
guard let httpResponse = response as? HTTPURLResponse else {
return NativeResponseInit(
headers: [], status: 200, statusText: "", url: response.url?.absoluteString ?? ""
)
}
let status = httpResponse.statusCode
let statusText = HTTPURLResponse.localizedString(forStatusCode: status)
let headers = httpResponse.allHeaderFields.reduce(into: [[String]]()) { result, header in
if let key = header.key as? String, let value = header.value as? String {
result.append([key, value])
}
}
let url = httpResponse.url?.absoluteString ?? ""
return NativeResponseInit(
headers: headers, status: status, statusText: statusText, url: url
)
}
// MARK: - ExpoURLSessionTaskDelegate implementations
func urlSessionDidStart(_ session: ExpoURLSessionTask) {
if isInvalidState(.intialized) {
return
}
state = .started
}
func urlSession(_ session: ExpoURLSessionTask, didReceive response: URLResponse) {
if isInvalidState(.started) {
return
}
responseInit = Self.createResponseInit(response: response)
state = .responseReceived
}
func urlSession(_ session: ExpoURLSessionTask, didReceive data: Data) {
if isInvalidState(.responseReceived, .bodyStreamingStarted, .bodyStreamingCanceled) {
return
}
if state == .responseReceived {
self.sink.appendBufferBody(data: data)
} else if state == .bodyStreamingStarted {
emit(event: "didReceiveResponseData", arguments: data)
}
// no-op in .bodyStreamingCanceled state
}
func urlSession(
_ session: ExpoURLSessionTask,
task: URLSessionTask,
willPerformHTTPRedirection response: HTTPURLResponse,
newRequest request: URLRequest,
completionHandler: @escaping (URLRequest?) -> Void
) {
let shouldFollowRedirects = self.redirectMode == .follow
completionHandler(shouldFollowRedirects ? request : nil)
self.redirected = shouldFollowRedirects
if self.redirectMode == .error {
let error = FetchRedirectException()
self.error = error
if state == .bodyStreamingStarted {
emit(event: "didFailWithError", arguments: error.localizedDescription)
}
state = .errorReceived
emit(event: "readyForJSFinalization")
}
}
func urlSession(_ session: ExpoURLSessionTask, task: URLSessionTask, didCompleteWithError error: (any Error)?) {
if isInvalidState(.started, .responseReceived, .bodyStreamingStarted, .bodyStreamingCanceled) {
return
}
if state == .started,
let urlError = error as? URLError,
urlError.code.rawValue == CFNetworkErrors.cfurlErrorFileDoesNotExist.rawValue,
let url = task.currentRequest?.url,
url.scheme == "file" {
// When requesting a local file that does not exist,
// the `urlSession(_:didReceive:)` method will not be called.
// Instead of throwing an exception, we generate a 404 response.
responseInit = NativeResponseInit(
headers: [], status: 404, statusText: "File not found", url: url.absoluteString)
// First, set the state to .responseReceived, and then to .errorReceived in the next loop.
// This simulates the state transition similar to HTTP requests.
state = .responseReceived
dispatchQueue.async { [weak self] in
guard let self else {
return
}
self.urlSession(session, task: task, didCompleteWithError: error)
}
return
}
if state == .bodyStreamingStarted {
if let error {
emit(event: "didFailWithError", arguments: error.localizedDescription)
} else {
emit(event: "didComplete")
}
}
if let error {
self.error = error
state = .errorReceived
} else {
state = .bodyCompleted
}
emit(event: "readyForJSFinalization")
}
}

11
node_modules/expo/ios/Fetch/NativeResponseInit.swift generated vendored Normal file
View File

@@ -0,0 +1,11 @@
// Copyright 2015-present 650 Industries. All rights reserved.
/**
Native data for ResponseInit.
*/
internal struct NativeResponseInit {
let headers: [[String]]
let status: Int
let statusText: String
let url: String
}

27
node_modules/expo/ios/Fetch/ResponseSink.swift generated vendored Normal file
View File

@@ -0,0 +1,27 @@
// Copyright 2015-present 650 Industries. All rights reserved.
/**
A data structure to store response body chunks
*/
internal final class ResponseSink {
private var bodyQueue: [Data] = []
private var isFinalized = false
private(set) var bodyUsed = false
func appendBufferBody(data: Data) {
bodyUsed = true
bodyQueue.append(data)
}
func finalize() -> Data {
let size = bodyQueue.reduce(0) { $0 + $1.count }
var result = Data(capacity: size)
while !bodyQueue.isEmpty {
let data = bodyQueue.removeFirst()
result.append(data)
}
bodyUsed = true
isFinalized = true
return result
}
}

14
node_modules/expo/ios/Fetch/ResponseState.swift generated vendored Normal file
View File

@@ -0,0 +1,14 @@
// Copyright 2015-present 650 Industries. All rights reserved.
/**
States represent for native response.
*/
internal enum ResponseState: Int {
case intialized = 0
case started
case responseReceived
case bodyCompleted
case bodyStreamingStarted
case bodyStreamingCanceled
case errorReceived
}

13
node_modules/expo/ios/Swift.h generated vendored Normal file
View File

@@ -0,0 +1,13 @@
// Copyright 2018-present 650 Industries. All rights reserved.
// The generated swift header may depend on some Objective-C declarations,
// adding dependency imports here to prevent declarations not found errors.
#import <React/RCTHTTPRequestHandler.h>
// When `use_frameworks!` is used, the generated Swift header is inside ExpoModulesCore module.
// Otherwise, it's available only locally with double-quoted imports.
#if __has_include(<Expo/Expo-Swift.h>)
#import <Expo/Expo-Swift.h>
#else
#import "Expo-Swift.h"
#endif

14
node_modules/expo/ios/Tests/ExpoAppDelegateTests.swift generated vendored Normal file
View File

@@ -0,0 +1,14 @@
// Copyright 2025-present 650 Industries. All rights reserved.
import Testing
@testable import Expo
@Suite
struct ExpoAppDelegateTests {
@Test
func `extends UIResponder`() {
// Assert that `ExpoAppDelegate` extends from `UIResponder` so it doesn't regress again in the future.
// Otherwise, the `AppDelegate` would lose its responder behavior such as being able to handle touches and key presses.
#expect(ExpoAppDelegate.self is UIResponder.Type)
}
}