diff --git a/Sources/OAuthKit/OAuth+URLProtocol.swift b/Sources/OAuthKit/OAuth+URLProtocol.swift index 7ff9f12..70ea7a6 100644 --- a/Sources/OAuthKit/OAuth+URLProtocol.swift +++ b/Sources/OAuthKit/OAuth+URLProtocol.swift @@ -11,7 +11,10 @@ extension OAuth { /// A custom `URLProtocol` that can be registered with any `URLSessionConfiguration` that will automatically inject /// `Authorization: Bearer <>` headers into outbound HTTP URLRequests based on ``Provider/authorizationPattern``. - public class URLProtocol: Foundation.URLProtocol { + public class URLProtocol: Foundation.URLProtocol, URLSessionDataDelegate, @unchecked Sendable { + + private var session: URLSession? + private var sessionDataTask: URLSessionDataTask? /// The lock that provides manual synchronization around access to authorization tokens. private static let lock: NSLock = .init() @@ -22,6 +25,18 @@ extension OAuth { set { lock.withLock { _authorizations = newValue } } } + /// Common Initializer. + /// - Parameters: + /// - request: the url requesy + /// - cachedResponse: the cached response + /// - client: the client + override public init(request: URLRequest, cachedResponse: CachedURLResponse?, client: (any URLProtocolClient)?) { + super.init(request: request, cachedResponse: cachedResponse, client: client) + if session == nil { + session = .init(configuration: .default, delegate: self, delegateQueue: nil) + } + } + /// Adds an authorization for the given provider that can be used inject `Authorization: Bearer <>` headers into a request. /// - Parameters: /// - authorization: the authorization issued by the provider @@ -47,7 +62,7 @@ extension OAuth { /// Determines whether this protocol can handle the given request. /// - Parameter request: the request to handle - /// - Returns: always true + /// - Returns: true if this protocl can handle the given request. override public class func canInit(with request: URLRequest) -> Bool { // Remove any expired authorizations let expiredEntries = authorizations.filter{ $0.value.isExpired } @@ -64,6 +79,14 @@ extension OAuth { return false } + /// Determines whether this protocol can handle the given task. + /// - Parameter task: the task to handle + /// - Returns: true if this protocl can handle the given task. + override public class func canInit(with task: URLSessionTask) -> Bool { + guard let request = task.originalRequest else { return false } + return canInit(with: request) + } + /// If an authorized provider matches the given request, then this method returns a canonical version /// of the given request with an additional `Authorization: Bearer <>` header field. /// - Parameter request: the request @@ -80,6 +103,55 @@ extension OAuth { } return request } + + /// Starts the loading of the current request. + override public func startLoading() { + sessionDataTask = session?.dataTask(with: request) + sessionDataTask?.resume() + } + + /// Stops the loading of the current request. + override public func stopLoading() { + sessionDataTask?.cancel() + } + + /// Called when data is available to consume. + /// - Parameters: + /// - session: the url session + /// - dataTask: the data task + /// - data: the data to consume + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + guard let response = dataTask.response else { return } + client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) + client?.urlProtocol(self, didLoad: data) + } + + /// Called when task is done loading data. + /// - Parameters: + /// - session: the url session + /// - task: the session task + /// - error: any error that may have occurred + public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: (any Error)?) { + if let error { + client?.urlProtocol(self, didFailWithError: error) + } else { + client?.urlProtocolDidFinishLoading(self) + } + } + + public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { + client?.urlProtocol(self, wasRedirectedTo: request, redirectResponse: response) + completionHandler(request) + } + + public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: (any Error)?) { + guard let error = error else { return } + client?.urlProtocol(self, didFailWithError: error) + } + + public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { + client?.urlProtocolDidFinishLoading(self) + } } } diff --git a/Sources/OAuthKit/OAuthKit.docc/Resources/tutorial-code-files/url-protocol-step-4.swift b/Sources/OAuthKit/OAuthKit.docc/Resources/tutorial-code-files/url-protocol-step-4.swift index 24e664a..a29f858 100644 --- a/Sources/OAuthKit/OAuthKit.docc/Resources/tutorial-code-files/url-protocol-step-4.swift +++ b/Sources/OAuthKit/OAuthKit.docc/Resources/tutorial-code-files/url-protocol-step-4.swift @@ -2,6 +2,6 @@ // send a REST request to GitHub's API for a list of org repos // The Authorization header will automatically get included // in the request since it matches `api.github.com` -let url: URL = .init(string: "https://api.github.com/orgs/{ORG}/repos") +let url: URL = .init(string: "https://api.github.com/users/codefiesta/repos") let request = URLRequest(url: url) let (data, response) = try await urlSession.data(for: request)