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,176 @@
// Copyright 2015-present 650 Industries. All rights reserved.
import Foundation
struct CdpNetwork {
// MARK: Types
typealias Headers = [String: String]?
typealias MonotonicTime = TimeInterval
typealias RequestId = String
typealias TimeSinceEpoch = TimeInterval
enum ResourceType: String, Encodable {
case image = "Image"
case media = "Media"
case font = "Font"
case script = "Script"
case fetch = "Fetch"
case other = "Other"
static func fromMimeType(_ mimeType: String) -> ResourceType {
if mimeType.starts(with: "image/") {
return image
}
if mimeType.starts(with: "audio/") || mimeType.starts(with: "video/") {
return media
}
if mimeType.starts(with: "font/") {
return font
}
return other
}
}
struct ConnectTiming: Encodable {
let requestTime: MonotonicTime
}
struct Request: Encodable {
let url: String
let method: String
let headers: Headers
let postData: String?
init(_ request: URLRequest) {
self.url = request.url?.absoluteString ?? ""
self.method = request.httpMethod ?? "GET"
self.headers = request.allHTTPHeaderFields ?? [:]
if let httpBody = request.httpBodyData() {
self.postData = String(data: httpBody, encoding: .utf8)
} else {
self.postData = nil
}
}
}
struct Response: Encodable {
let url: String
let status: Int
let statusText: String
let headers: Headers
let mimeType: String
let encodedDataLength: Int64
init(_ response: HTTPURLResponse, encodedDataLength: Int64) {
self.url = response.url?.absoluteString ?? ""
self.status = response.statusCode
self.statusText = ""
let headers = response.allHeaderFields.reduce(into: [String: String]()) { result, header in
if let key = header.key as? String, let value = header.value as? String {
result[key] = value
}
}
self.headers = headers
self.mimeType = response.value(forHTTPHeaderField: "Content-Type") ?? ""
self.encodedDataLength = encodedDataLength
}
}
// MARK: Events
struct RequestWillBeSentParams: EventParams {
let requestId: RequestId
var loaderId = ""
var documentURL = "mobile"
let request: Request
let timestamp: MonotonicTime
let wallTime: TimeSinceEpoch
var initiator = ["type": "script"]
var redirectHasExtraInfo: Bool {
return self.redirectResponse != nil
}
let redirectResponse: Response?
var referrerPolicy = "no-referrer"
let type: ResourceType
init(now: TimeInterval, requestId: RequestId, request: URLRequest, encodedDataLength: Int64, redirectResponse: HTTPURLResponse?) {
self.requestId = requestId
self.request = Request(request)
self.timestamp = now
self.wallTime = now
if let redirectResponse = redirectResponse {
self.redirectResponse = Response(redirectResponse, encodedDataLength: encodedDataLength)
} else {
self.redirectResponse = nil
}
self.type = ResourceType.other
}
}
struct RequestWillBeSentExtraInfoParams: EventParams {
let requestId: RequestId
var associatedCookies = [String: String]()
let headers: Headers
let connectTiming: ConnectTiming
init(now: TimeInterval, requestId: RequestId, request: URLRequest) {
self.requestId = requestId
self.headers = request.allHTTPHeaderFields ?? [:]
self.connectTiming = ConnectTiming(requestTime: now)
}
}
struct ResponseReceivedParams: EventParams {
let requestId: RequestId
var loaderId = ""
let timestamp: MonotonicTime
let type: ResourceType
let response: Response
var hasExtraInfo = false
init(now: TimeInterval, requestId: RequestId, request: URLRequest, response: HTTPURLResponse, encodedDataLength: Int64) {
self.requestId = requestId
self.timestamp = now
self.response = Response(response, encodedDataLength: encodedDataLength)
self.type = ResourceType.fromMimeType(self.response.mimeType)
}
}
struct LoadingFinishedParams: EventParams {
let requestId: RequestId
let timestamp: MonotonicTime
let encodedDataLength: Int64
init(now: TimeInterval, requestId: RequestId, encodedDataLength: Int64) {
self.requestId = requestId
self.timestamp = now
self.encodedDataLength = encodedDataLength
}
}
struct ExpoReceivedResponseBodyParams: EventParams {
let requestId: RequestId
let body: String
let base64Encoded: Bool
init(now: TimeInterval, requestId: RequestId, responseBody: Data, isText: Bool) {
self.requestId = requestId
let bodyString = isText ? String(data: responseBody, encoding: .utf8) : responseBody.base64EncodedString()
if let bodyString = bodyString {
self.body = bodyString
self.base64Encoded = !isText
} else {
self.body = ""
self.base64Encoded = false
}
}
}
typealias EventParams = Encodable & Sendable
struct Event<T: EventParams>: Encodable, Sendable {
let method: String
let params: T
}
}

View File

@@ -0,0 +1,83 @@
// Copyright 2015-present 650 Industries. All rights reserved.
import Foundation
/**
The `ExpoRequestInterceptorProtocolDelegate` implementation to
dispatch CDP (Chrome DevTools Protocol: https://chromedevtools.github.io/devtools-protocol/) events.
*/
@objc(EXRequestCdpInterceptor)
public final class ExpoRequestCdpInterceptor: NSObject, ExpoRequestInterceptorProtocolDelegate, @unchecked Sendable {
private weak var delegate: ExpoRequestCdpInterceptorDelegate?
public var dispatchQueue = DispatchQueue(label: "expo.requestCdpInterceptor")
override private init() {}
@objc
public static let shared = ExpoRequestCdpInterceptor()
@objc
public func setDelegate(_ newValue: ExpoRequestCdpInterceptorDelegate?) {
dispatchQueue.async {
self.delegate = newValue
}
}
private func dispatchEvent<T: CdpNetwork.EventParams>(_ event: CdpNetwork.Event<T>) {
dispatchQueue.async {
let encoder = JSONEncoder()
if let jsonData = try? encoder.encode(event), let payload = String(data: jsonData, encoding: .utf8) {
self.delegate?.dispatch(payload)
}
}
}
// MARK: ExpoRequestInterceptorProtocolDelegate implementations
func willSendRequest(requestId: String, task: URLSessionTask, request: URLRequest, redirectResponse: HTTPURLResponse?) {
let now = Date().timeIntervalSince1970
let params = CdpNetwork.RequestWillBeSentParams(
now: now,
requestId: requestId,
request: request,
encodedDataLength: task.countOfBytesReceived,
redirectResponse: redirectResponse)
dispatchEvent(CdpNetwork.Event(method: "Network.requestWillBeSent", params: params))
let params2 = CdpNetwork.RequestWillBeSentExtraInfoParams(now: now, requestId: requestId, request: request)
dispatchEvent(CdpNetwork.Event(method: "Network.requestWillBeSentExtraInfo", params: params2))
}
func didReceiveResponse(requestId: String, task: URLSessionTask, responseBody: Data, isText: Bool, responseBodyExceedsLimit: Bool) {
guard let request = task.currentRequest, let response = task.response as? HTTPURLResponse else {
return
}
let now = Date().timeIntervalSince1970
let params = CdpNetwork.ResponseReceivedParams(
now: now,
requestId: requestId,
request: request,
response: response,
encodedDataLength: task.countOfBytesReceived)
dispatchEvent(CdpNetwork.Event(method: "Network.responseReceived", params: params))
if !responseBodyExceedsLimit {
let params2 = CdpNetwork.ExpoReceivedResponseBodyParams(now: now, requestId: requestId, responseBody: responseBody, isText: isText)
dispatchEvent(CdpNetwork.Event(method: "Expo(Network.receivedResponseBody)", params: params2))
}
let params3 = CdpNetwork.LoadingFinishedParams(now: now, requestId: requestId, encodedDataLength: task.countOfBytesReceived)
dispatchEvent(CdpNetwork.Event(method: "Network.loadingFinished", params: params3))
}
}
/**
The delegate to dispatch CDP events for ExpoRequestCdpInterceptor
*/
@objc(EXRequestCdpInterceptorDelegate)
public protocol ExpoRequestCdpInterceptorDelegate: Sendable {
@objc
func dispatch(_ event: String)
}

View File

@@ -0,0 +1,243 @@
// Copyright 2015-present 650 Industries. All rights reserved.
import Foundation
/**
A `URLSession` interceptor which passes network events to its delegate
*/
@objc(EXRequestInterceptorProtocol)
public final class ExpoRequestInterceptorProtocol: URLProtocol, URLSessionDataDelegate, @unchecked Sendable {
nonisolated(unsafe) private static var requestIdProvider = RequestIdProvider()
private static let sessionDelegate
= URLSessionSessionDelegateProxy(dispatchQueue: ExpoRequestCdpInterceptor.shared.dispatchQueue)
private static let urlSession = URLSession(
configuration: URLSessionConfiguration.default,
delegate: sessionDelegate,
delegateQueue: nil
)
private var requestId: String?
private var dataTask_: URLSessionDataTask?
private let responseBody = NSMutableData()
private var responseBodyExceedsLimit = false
static let MAX_BODY_SIZE = 1_048_576
// Currently keeps the delegate fixed for ExpoRequestCdpInterceptor and be thread-safe
static let delegate: ExpoRequestInterceptorProtocolDelegate = ExpoRequestCdpInterceptor.shared
// MARK: URLProtocol implementations
public override class func canInit(with request: URLRequest) -> Bool {
guard let scheme = request.url?.scheme else {
return false
}
if !["http", "https"].contains(scheme) {
return false
}
return URLProtocol.property(
forKey: REQUEST_ID,
in: request
) == nil
}
override init(
request: URLRequest,
cachedResponse: CachedURLResponse?,
client: URLProtocolClient?
) {
super.init(request: request, cachedResponse: cachedResponse, client: client)
// swiftlint:disable force_cast
let mutableRequest = request as! NSMutableURLRequest
// swiftlint:enable force_cast
self.requestId = Self.requestIdProvider.create()
guard let requestId else {
fatalError("requestId should not be nil.")
}
URLProtocol.setProperty(
requestId,
forKey: REQUEST_ID,
in: mutableRequest
)
let dataTask = Self.urlSession.dataTask(with: mutableRequest as URLRequest)
Self.sessionDelegate.addDelegate(task: dataTask, delegate: self)
Self.delegate.willSendRequest(
requestId: requestId,
task: dataTask,
request: mutableRequest as URLRequest,
redirectResponse: nil
)
dataTask_ = dataTask
}
public override class func canonicalRequest(for request: URLRequest) -> URLRequest {
request
}
public override func startLoading() {
dataTask_?.resume()
}
public override func stopLoading() {
if let task = dataTask_ {
task.cancel()
Self.sessionDelegate.removeDelegate(task: task)
}
}
// MARK: URLSessionDataDelegate implementations
public func urlSession(_: URLSession, dataTask _: URLSessionDataTask, didReceive data: Data) {
client?.urlProtocol(self, didLoad: data)
if responseBody.length + data.count <= Self.MAX_BODY_SIZE {
responseBody.append(data)
} else {
responseBodyExceedsLimit = true
}
}
public func urlSession(_: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
client?.urlProtocol(self, didFailWithError: error)
} else {
if let response = task.response as? HTTPURLResponse,
let requestId {
let contentType = response.value(forHTTPHeaderField: "Content-Type")
let isText = (contentType?.starts(with: "text/") ?? false) || contentType == "application/json"
Self.delegate.didReceiveResponse(
requestId: requestId, task: task, responseBody: responseBody as Data, isText: isText, responseBodyExceedsLimit: responseBodyExceedsLimit)
}
client?.urlProtocolDidFinishLoading(self)
}
}
public func urlSession(
_: URLSession,
dataTask: URLSessionDataTask,
didReceive response: URLResponse,
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void
) {
completionHandler(.allow)
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .allowed)
}
public func urlSession(
_: URLSession,
task: URLSessionTask,
willPerformHTTPRedirection response: HTTPURLResponse,
newRequest request: URLRequest,
completionHandler: @escaping (URLRequest?) -> Void
) {
if let requestId {
Self.delegate.willSendRequest(
requestId: requestId,
task: task,
request: request,
redirectResponse: response
)
}
// The `shouldFollowRedirects` property is set by `expo/fetch`.
// It tells `ExpoRequestInterceptorProtocol` whether to follow HTTP redirects.
let shouldFollowRedirects = URLProtocol.property(forKey: "shouldFollowRedirects", in: request) as? Bool ?? true
if shouldFollowRedirects {
completionHandler(request)
} else {
completionHandler(nil)
// NOTE (kudo): The exact usage of
// `urlProtocol(_:wasRedirectedTo:redirectResponse:)` isnt fully clear.
// My understanding is that this delegate method informs the client
// about a redirect when youre handling it yourself.
// Since were not following the redirect and are stopping at the
// current request/response, we call it here with those values.
client?.urlProtocol(self, wasRedirectedTo: request, redirectResponse: response)
}
}
public func urlSession(
_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
let sender = URLAuthenticationChallengeForwardSender(completionHandler: completionHandler)
let challengeWithSender = URLAuthenticationChallenge(authenticationChallenge: challenge, sender: sender)
client?.urlProtocol(self, didReceive: challengeWithSender)
}
public func urlSession(
_ session: URLSession,
task: URLSessionTask,
didSendBodyData bytesSent: Int64,
totalBytesSent: Int64,
totalBytesExpectedToSend: Int64
) {
// swiftlint:disable line_length
// Apple does not support sending upload progress from URLProtocol back to URLProtocolClient.
// > Similarly, there is no way for your NSURLProtocol subclass to call the NSURLConnection delegate's -connection:needNewBodyStream: or -connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite: methods (<rdar://problem/9226155> and <rdar://problem/9226157>). The latter is not a serious concern--it just means that your clients don't get upload progress--but the former is a real issue. If you're in a situation where you might need a second copy of a request body, you will need your own logic to make that copy, including the case where the body is a stream.
// See: https://developer.apple.com/library/archive/samplecode/CustomHTTPProtocol/Listings/Read_Me_About_CustomHTTPProtocol_txt.html
//
// Workaround to get the original task's URLSessionDelegate through the internal property and send upload process
// Fixes https://github.com/expo/expo/issues/28269
// swiftlint:enable line_length
guard let dataTask = dataTask_ else {
return
}
// Prevent recursive delegate calls
if task === dataTask {
return
}
if #available(iOS 15.0, tvOS 15.0, macOS 12.0, *), let delegate = dataTask.delegate {
// For the case if the task has a dedicated delegate than the default delegate from its URLSession
delegate.urlSession?(
session, task: dataTask, didSendBodyData: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend
)
return
}
guard let session = task.value(forKey: "session") as? URLSession,
let delegate = session.delegate as? URLSessionTaskDelegate else {
return
}
delegate.urlSession?(
session, task: dataTask, didSendBodyData: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend
)
}
/**
Data structure to save the response for redirection
*/
private struct RedirectResponse {
let requestId: String
let redirectResponse: HTTPURLResponse
}
}
/**
The delegate to dispatch network request events
*/
@objc(EXRequestInterceptorProtocolDelegate)
protocol ExpoRequestInterceptorProtocolDelegate: Sendable {
@objc
func willSendRequest(requestId: String, task: URLSessionTask, request: URLRequest, redirectResponse: HTTPURLResponse?)
@objc
func didReceiveResponse(requestId: String, task: URLSessionTask, responseBody: Data, isText: Bool, responseBodyExceedsLimit: Bool)
}
/**
A helper class to create a unique request ID
*/
private struct RequestIdProvider {
private var value: UInt64 = 0
mutating func create() -> String {
// We can ensure it is thread-safe to increment this value,
// because we always access this function from the same thread (com.apple.CFNetwork.CustomProtocols).
value += 1
return String(value)
}
}
private let REQUEST_ID = "ExpoRequestInterceptorProtocol.requestId"

View File

@@ -0,0 +1,179 @@
class ModuleDefinitionEncoder: Encodable {
private let definition: ModuleDefinition
init(_ definition: ModuleDefinition) {
self.definition = definition
}
enum CodingKeys: String, CodingKey {
case name
case functions
case properties
case constants
case views
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(definition.name, forKey: .name)
var allConstants: [[ConstantEncoder]] = []
allConstants.append(contentsOf: definition.legacyConstants.map({ LegacyConstantsDefinitionEncoder($0).getEncoders() }))
if !definition.constants.isEmpty {
allConstants.append(definition.constants.values.map({ ConstantEncoder($0.name, value: $0.getRawValue()) }))
}
try container.encode(allConstants, forKey: .constants)
try container.encode(definition.properties.values.map({ PropertyDefinitionEncoder($0) }), forKey: .properties)
try container.encode(definition.functions.values.map({ FunctionDefinitionEncoder($0) }), forKey: .functions)
try container.encode(definition.views.values.map({ ViewDefinitionEncoder($0) }), forKey: .views)
}
}
public class ModuleRegistryEncoder: Encodable {
private let registry: ModuleRegistry
public init(_ registry: ModuleRegistry) {
self.registry = registry
}
public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
registry.getModuleNames().forEach {
guard let definition = registry.get(moduleWithName: $0)?.definition() else {
return
}
try? container.encode(ModuleDefinitionEncoder(definition))
}
}
}
class FunctionDefinitionEncoder: Encodable {
private let definition: any AnyFunctionDefinition
init(_ definition: any AnyFunctionDefinition) {
self.definition = definition
}
enum CodingKeys: String, CodingKey {
case name
case argumentsCount
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(definition.name, forKey: .name)
try container.encode(definition.argumentsCount, forKey: .argumentsCount)
}
}
class ViewDefinitionEncoder: Encodable {
private let definition: any AnyViewDefinition
init(_ definition: any AnyViewDefinition) {
self.definition = definition
}
enum CodingKeys: String, CodingKey {
case name
case props
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(definition.name, forKey: .name)
try container.encode(definition.props.map({ ViewPropEncoder($0) }), forKey: .props)
}
}
class ViewPropEncoder: Encodable {
private let definition: AnyViewProp
init(_ definition: AnyViewProp) {
self.definition = definition
}
enum CodingKeys: String, CodingKey {
case name
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(definition.name, forKey: .name)
}
}
class ConstantEncoder: Encodable {
private let key: String
private let value: Any?
init(_ key: String, value: Any?) {
self.key = key
self.value = value
}
enum CodingKeys: String, CodingKey {
case name
case value
case type
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(key, forKey: .name)
switch value {
case let value as String:
try container.encode(value, forKey: .value)
try container.encode("string", forKey: .type)
case let value as Bool:
try container.encode(value, forKey: .value)
try container.encode("boolean", forKey: .type)
case let value as Int:
try container.encode(value, forKey: .value)
try container.encode("number", forKey: .type)
case let value as Double:
try container.encode(value, forKey: .value)
try container.encode("number", forKey: .type)
case nil:
try container.encodeNil(forKey: .value)
try container.encode("null", forKey: .type)
case _ as [String: Any]:
try container.encodeNil(forKey: .value)
try container.encode("object", forKey: .type)
case _ as [Any]:
try container.encodeNil(forKey: .value)
try container.encode("array", forKey: .type)
default:
try container.encodeNil(forKey: .value)
try container.encode("unknown", forKey: .type)
}
}
}
class LegacyConstantsDefinitionEncoder {
private let definition: ConstantsDefinition
init(_ definition: ConstantsDefinition) {
self.definition = definition
}
func getEncoders() -> [ConstantEncoder] {
let constants = definition.body()
return constants.map { (key, value) in ConstantEncoder(key, value: value) }
}
}
class PropertyDefinitionEncoder: Encodable {
private let definition: any AnyPropertyDefinition
init(_ definition: any AnyPropertyDefinition) {
self.definition = definition
}
enum CodingKeys: String, CodingKey {
case name
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(definition.name, forKey: .name)
}
}

View File

@@ -0,0 +1,33 @@
// Copyright 2015-present 650 Industries. All rights reserved.
/**
A helper class to forward URLAuthenticationChallenge completion handler to URLAuthenticationChallengeSender
*/
internal final class URLAuthenticationChallengeForwardSender: NSObject, URLAuthenticationChallengeSender {
let completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
init(completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
self.completionHandler = completionHandler
super.init()
}
func use(_ credential: URLCredential, for challenge: URLAuthenticationChallenge) {
completionHandler(.useCredential, credential)
}
func continueWithoutCredential(for challenge: URLAuthenticationChallenge) {
completionHandler(.useCredential, nil)
}
func cancel(_ challenge: URLAuthenticationChallenge) {
completionHandler(.cancelAuthenticationChallenge, nil)
}
func performDefaultHandling(for challenge: URLAuthenticationChallenge) {
completionHandler(.performDefaultHandling, nil)
}
func rejectProtectionSpaceAndContinue(with challenge: URLAuthenticationChallenge) {
completionHandler(.rejectProtectionSpace, nil)
}
}

View File

@@ -0,0 +1,43 @@
// Copyright 2015-present 650 Industries. All rights reserved.
import Foundation
/**
`URLRequest.httpBodyData()` extension to read the underlying `httpBodyStream` as Data.
*/
extension URLRequest {
func httpBodyData(limit: Int = ExpoRequestInterceptorProtocol.MAX_BODY_SIZE) -> Data? {
if let httpBody = self.httpBody {
return httpBody
}
if let contentLength = self.allHTTPHeaderFields?["Content-Length"],
let contentLengthInt = Int(contentLength),
contentLengthInt > limit {
return nil
}
guard let stream = self.httpBodyStream else {
return nil
}
let bufferSize: Int = 8192
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
stream.open()
defer {
buffer.deallocate()
stream.close()
}
var data = Data()
while stream.hasBytesAvailable {
let chunkSize = stream.read(buffer, maxLength: bufferSize)
if data.count + chunkSize > limit {
return nil
}
data.append(buffer, count: chunkSize)
}
return data
}
}

View File

@@ -0,0 +1,120 @@
// Copyright 2015-present 650 Industries. All rights reserved.
/**
Shared URLSessionDelegate instance and delete calls back to ExpoRequestInterceptorProtocol instances.
*/
public final class URLSessionSessionDelegateProxy: NSObject, URLSessionDataDelegate {
private let dispatchQueue: DispatchQueue
private var delegateMap: [AnyHashable: URLSessionDataDelegate] = [:]
public init(dispatchQueue: DispatchQueue) {
self.dispatchQueue = dispatchQueue
super.init()
}
public func addDelegate(task: URLSessionTask, delegate: URLSessionDataDelegate) {
self.dispatchQueue.async {
self.delegateMap[task] = delegate
}
}
public func removeDelegate(task: URLSessionTask) {
self.dispatchQueue.async {
self.delegateMap.removeValue(forKey: task)
}
}
public func getDelegate(task: URLSessionTask) -> URLSessionDataDelegate? {
return self.dispatchQueue.sync {
return self.delegateMap[task]
}
}
// MARK: - URLSessionDataDelegate implementations
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive: Data) {
if let delegate = getDelegate(task: dataTask) {
delegate.urlSession?(
session,
dataTask: dataTask,
didReceive: didReceive)
}
}
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError: Error?) {
if let delegate = getDelegate(task: task) {
delegate.urlSession?(
session,
task: task,
didCompleteWithError: didCompleteWithError)
}
self.removeDelegate(task: task)
}
public func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive: URLResponse,
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void
) {
if let delegate = getDelegate(task: dataTask) {
delegate.urlSession?(
session,
dataTask: dataTask,
didReceive: didReceive,
completionHandler: completionHandler)
}
}
public func urlSession(
_ session: URLSession,
task: URLSessionTask,
willPerformHTTPRedirection: HTTPURLResponse,
newRequest: URLRequest,
completionHandler: @escaping (URLRequest?) -> Void
) {
if let delegate = getDelegate(task: task) {
delegate.urlSession?(
session,
task: task,
willPerformHTTPRedirection: willPerformHTTPRedirection,
newRequest: newRequest,
completionHandler: completionHandler)
}
}
public func urlSession(
_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
if let delegate = getDelegate(task: task),
delegate.responds(to: #selector(URLSessionTaskDelegate.urlSession(_:task:didReceive:completionHandler:))) {
delegate.urlSession?(
session,
task: task,
didReceive: challenge,
completionHandler: completionHandler)
} else {
completionHandler(.performDefaultHandling, nil)
}
}
public func urlSession(
_ session: URLSession,
task: URLSessionTask,
didSendBodyData bytesSent: Int64,
totalBytesSent: Int64,
totalBytesExpectedToSend: Int64
) {
if let delegate = getDelegate(task: task) {
delegate.urlSession?(
session,
task: task,
didSendBodyData: bytesSent,
totalBytesSent: totalBytesSent,
totalBytesExpectedToSend: totalBytesExpectedToSend)
}
}
}