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,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
}