Skip to content

Commit 2e1fc5b

Browse files
authored
URLProtocol Fix (#127)
1 parent 09aa69c commit 2e1fc5b

2 files changed

Lines changed: 75 additions & 3 deletions

File tree

Sources/OAuthKit/OAuth+URLProtocol.swift

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ extension OAuth {
1111

1212
/// A custom `URLProtocol` that can be registered with any `URLSessionConfiguration` that will automatically inject
1313
/// `Authorization: Bearer <<token>>` headers into outbound HTTP URLRequests based on ``Provider/authorizationPattern``.
14-
public class URLProtocol: Foundation.URLProtocol {
14+
public class URLProtocol: Foundation.URLProtocol, URLSessionDataDelegate, @unchecked Sendable {
15+
16+
private var session: URLSession?
17+
private var sessionDataTask: URLSessionDataTask?
1518

1619
/// The lock that provides manual synchronization around access to authorization tokens.
1720
private static let lock: NSLock = .init()
@@ -22,6 +25,18 @@ extension OAuth {
2225
set { lock.withLock { _authorizations = newValue } }
2326
}
2427

28+
/// Common Initializer.
29+
/// - Parameters:
30+
/// - request: the url requesy
31+
/// - cachedResponse: the cached response
32+
/// - client: the client
33+
override public init(request: URLRequest, cachedResponse: CachedURLResponse?, client: (any URLProtocolClient)?) {
34+
super.init(request: request, cachedResponse: cachedResponse, client: client)
35+
if session == nil {
36+
session = .init(configuration: .default, delegate: self, delegateQueue: nil)
37+
}
38+
}
39+
2540
/// Adds an authorization for the given provider that can be used inject `Authorization: Bearer <<token>>` headers into a request.
2641
/// - Parameters:
2742
/// - authorization: the authorization issued by the provider
@@ -47,7 +62,7 @@ extension OAuth {
4762

4863
/// Determines whether this protocol can handle the given request.
4964
/// - Parameter request: the request to handle
50-
/// - Returns: always true
65+
/// - Returns: true if this protocl can handle the given request.
5166
override public class func canInit(with request: URLRequest) -> Bool {
5267
// Remove any expired authorizations
5368
let expiredEntries = authorizations.filter{ $0.value.isExpired }
@@ -64,6 +79,14 @@ extension OAuth {
6479
return false
6580
}
6681

82+
/// Determines whether this protocol can handle the given task.
83+
/// - Parameter task: the task to handle
84+
/// - Returns: true if this protocl can handle the given task.
85+
override public class func canInit(with task: URLSessionTask) -> Bool {
86+
guard let request = task.originalRequest else { return false }
87+
return canInit(with: request)
88+
}
89+
6790
/// If an authorized provider matches the given request, then this method returns a canonical version
6891
/// of the given request with an additional `Authorization: Bearer <<token>>` header field.
6992
/// - Parameter request: the request
@@ -80,6 +103,55 @@ extension OAuth {
80103
}
81104
return request
82105
}
106+
107+
/// Starts the loading of the current request.
108+
override public func startLoading() {
109+
sessionDataTask = session?.dataTask(with: request)
110+
sessionDataTask?.resume()
111+
}
112+
113+
/// Stops the loading of the current request.
114+
override public func stopLoading() {
115+
sessionDataTask?.cancel()
116+
}
117+
118+
/// Called when data is available to consume.
119+
/// - Parameters:
120+
/// - session: the url session
121+
/// - dataTask: the data task
122+
/// - data: the data to consume
123+
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
124+
guard let response = dataTask.response else { return }
125+
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
126+
client?.urlProtocol(self, didLoad: data)
127+
}
128+
129+
/// Called when task is done loading data.
130+
/// - Parameters:
131+
/// - session: the url session
132+
/// - task: the session task
133+
/// - error: any error that may have occurred
134+
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: (any Error)?) {
135+
if let error {
136+
client?.urlProtocol(self, didFailWithError: error)
137+
} else {
138+
client?.urlProtocolDidFinishLoading(self)
139+
}
140+
}
141+
142+
public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
143+
client?.urlProtocol(self, wasRedirectedTo: request, redirectResponse: response)
144+
completionHandler(request)
145+
}
146+
147+
public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: (any Error)?) {
148+
guard let error = error else { return }
149+
client?.urlProtocol(self, didFailWithError: error)
150+
}
151+
152+
public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
153+
client?.urlProtocolDidFinishLoading(self)
154+
}
83155
}
84156
}
85157

Sources/OAuthKit/OAuthKit.docc/Resources/tutorial-code-files/url-protocol-step-4.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
// send a REST request to GitHub's API for a list of org repos
33
// The Authorization header will automatically get included
44
// in the request since it matches `api.github.com`
5-
let url: URL = .init(string: "https://api.github.com/orgs/{ORG}/repos")
5+
let url: URL = .init(string: "https://api.github.com/users/codefiesta/repos")
66
let request = URLRequest(url: url)
77
let (data, response) = try await urlSession.data(for: request)

0 commit comments

Comments
 (0)