Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
DominikPalo committed Nov 28, 2024
1 parent cbbf5c1 commit 8be3281
Show file tree
Hide file tree
Showing 11 changed files with 311 additions and 428 deletions.
14 changes: 3 additions & 11 deletions Sources/Base/OAuth2Base.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,8 @@ open class OAuth2Base: OAuth2Securable {
set { clientConfig.customUserAgent = newValue }
}


/// This closure is internally used with `authorize(params:callback:)` and only exposed for subclassing reason, do not mess with it!
public final var didAuthorizeOrFail: ((_ parameters: OAuth2JSON?, _ error: OAuth2Error?) -> Void)?

/// Returns true if the receiver is currently authorizing.
public final var isAuthorizing: Bool {
return nil != didAuthorizeOrFail
}
public final var isAuthorizing: Bool = false

/// Returns true if the receiver is currently exchanging the refresh token.
public final var isExchangingRefreshToken: Bool = false
Expand Down Expand Up @@ -277,8 +271,7 @@ open class OAuth2Base: OAuth2Securable {
storeTokensToKeychain()
}
callOnMainThread() {
self.didAuthorizeOrFail?(parameters, nil)
self.didAuthorizeOrFail = nil
self.isAuthorizing = false
self.internalAfterAuthorizeOrFail?(false, nil)
self.afterAuthorizeOrFail?(parameters, nil)
}
Expand All @@ -301,8 +294,7 @@ open class OAuth2Base: OAuth2Securable {
finalError = OAuth2Error.requestCancelled
}
callOnMainThread() {
self.didAuthorizeOrFail?(nil, finalError)
self.didAuthorizeOrFail = nil
self.isAuthorizing = false
self.internalAfterAuthorizeOrFail?(true, finalError)
self.afterAuthorizeOrFail?(nil, finalError)
}
Expand Down
22 changes: 7 additions & 15 deletions Sources/Base/OAuth2RequestPerformer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,12 @@ The class `OAuth2DataTaskRequestPerformer` implements this protocol and is by de
public protocol OAuth2RequestPerformer {

/**
This method should start executing the given request, returning a URLSessionTask if it chooses to do so. **You do not neet to call
`resume()` on this task**, it's supposed to already have started. It is being returned so you may be able to do additional stuff.
This method should execute the given request asynchronously.

- parameter request: An URLRequest object that provides the URL, cache policy, request type, body data or body stream, and so on.
- parameter completionHandler: The completion handler to call when the load request is complete.
- returns: An already running session task
- returns: Data and response.
*/
func perform(request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionTask?
func perform(request: URLRequest) async throws -> (Data, URLResponse)

Check failure on line 25 in Sources/Base/OAuth2RequestPerformer.swift

View workflow job for this annotation

GitHub Actions / results-xcode-tests-tvOS

error

Concurrency is only available in application extensions for tvOS 13.0.0 or newer

Check failure on line 25 in Sources/Base/OAuth2RequestPerformer.swift

View workflow job for this annotation

GitHub Actions / results-xcode-tests-tvOS

error

Concurrency is only available in application extensions for tvOS 13.0.0 or newer

Check failure on line 25 in Sources/Base/OAuth2RequestPerformer.swift

View workflow job for this annotation

GitHub Actions / results-xcode-tests-iOS

error

Concurrency is only available in iOS 13.0.0 or newer
}


Expand All @@ -36,7 +34,6 @@ open class OAuth2DataTaskRequestPerformer: OAuth2RequestPerformer {
/// The URLSession that should be used.
public var session: URLSession


/**
Designated initializer.
*/
Expand All @@ -45,18 +42,13 @@ open class OAuth2DataTaskRequestPerformer: OAuth2RequestPerformer {
}

/**
This method should start executing the given request, returning a URLSessionTask if it chooses to do so. **You do not neet to call
`resume()` on this task**, it's supposed to already have started. It is being returned so you may be able to do additional stuff.
This method should execute the given request asynchronously.

- parameter request: An URLRequest object that provides the URL, cache policy, request type, body data or body stream, and so on.
- parameter completionHandler: The completion handler to call when the load request is complete.
- returns: An already running session data task
- returns: Data and response.
*/
@discardableResult
open func perform(request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionTask? {
let task = session.dataTask(with: request, completionHandler: completionHandler)
task.resume()
return task
open func perform(request: URLRequest) async throws -> (Data, URLResponse) {

Check failure on line 50 in Sources/Base/OAuth2RequestPerformer.swift

View workflow job for this annotation

GitHub Actions / results-xcode-tests-tvOS

error

Concurrency is only available in application extensions for tvOS 13.0.0 or newer

Check failure on line 50 in Sources/Base/OAuth2RequestPerformer.swift

View workflow job for this annotation

GitHub Actions / results-xcode-tests-tvOS

error

Concurrency is only available in application extensions for tvOS 13.0.0 or newer

Check failure on line 50 in Sources/Base/OAuth2RequestPerformer.swift

View workflow job for this annotation

GitHub Actions / results-xcode-tests-iOS

error

Concurrency is only available in iOS 13.0.0 or newer
try await session.data(for: request)

Check failure on line 51 in Sources/Base/OAuth2RequestPerformer.swift

View workflow job for this annotation

GitHub Actions / results-xcode-tests-tvOS

error

'data(for:delegate:)' is only available in application extensions for tvOS 15.0 or newer
}
}

64 changes: 38 additions & 26 deletions Sources/Base/OAuth2Requestable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,41 +105,40 @@ open class OAuth2Requestable {
open var requestPerformer: OAuth2RequestPerformer?

/**
Perform the supplied request and call the callback with the response JSON dict or an error. This method is intended for authorization
Perform the supplied request and return the response JSON dict or throw an error. This method is intended for authorization
calls, not for data calls outside of the OAuth2 dance.

This implementation uses the shared `NSURLSession` and executes a data task. If the server responds with an error, this will be
converted into an error according to information supplied in the response JSON (if availale).

The callback returns a response object that is easy to use, like so:

perform(request: req) { response in
do {
let data = try response.responseData()
// do what you must with `data` as Data and `response.response` as HTTPURLResponse
}
catch let error {
// the request failed because of `error`
}
}

Easy, right?
This implementation uses the shared `NSURLSession`. If the server responds with an error, this will be
converted into an error according to information supplied in the response JSON (if available).

- parameter request: The request to execute
- parameter callback: The callback to call when the request completes/fails. Looks terrifying, see above on how to use it
- returns : OAuth2 response
*/
open func perform(request: URLRequest, callback: @escaping ((OAuth2Response) -> Void)) {
open func perform(request: URLRequest) async -> OAuth2Response {

Check failure on line 117 in Sources/Base/OAuth2Requestable.swift

View workflow job for this annotation

GitHub Actions / results-xcode-tests-tvOS

error

Concurrency is only available in application extensions for tvOS 13.0.0 or newer

Check failure on line 117 in Sources/Base/OAuth2Requestable.swift

View workflow job for this annotation

GitHub Actions / results-xcode-tests-tvOS

error

Concurrency is only available in application extensions for tvOS 13.0.0 or newer

Check failure on line 117 in Sources/Base/OAuth2Requestable.swift

View workflow job for this annotation

GitHub Actions / results-xcode-tests-iOS

error

Concurrency is only available in iOS 13.0.0 or newer

Check failure on line 117 in Sources/Base/OAuth2Requestable.swift

View workflow job for this annotation

GitHub Actions / results-xcode-tests-iOS

error

Concurrency is only available in iOS 13.0.0 or newer
self.logger?.trace("OAuth2", msg: "REQUEST\n\(request.debugDescription)\n---")
let performer = requestPerformer ?? OAuth2DataTaskRequestPerformer(session: session)
requestPerformer = performer
let task = performer.perform(request: request) { sessData, sessResponse, error in
self.abortableTask = nil
self.logger?.trace("OAuth2", msg: "RESPONSE\n\(sessResponse?.debugDescription ?? "no response")\n\n\(String(data: sessData ?? Data(), encoding: String.Encoding.utf8) ?? "no data")\n---")
let http = (sessResponse as? HTTPURLResponse) ?? HTTPURLResponse(url: request.url!, statusCode: 499, httpVersion: nil, headerFields: nil)!
let response = OAuth2Response(data: sessData, request: request, response: http, error: error)
callback(response)

do {
// TODO: add support for aborting the request, see https://www.hackingwithswift.com/quick-start/concurrency/how-to-cancel-a-task
let (sessData, sessResponse) = try await performer.perform(request: request)
self.logger?.trace("OAuth2", msg: "RESPONSE\n\(sessResponse.debugDescription)\n\n\(String(data: sessData, encoding: String.Encoding.utf8) ?? "no data")\n---")

guard let response = sessResponse as? HTTPURLResponse else {
throw CommonError.castError(
from: String(describing: sessResponse.self),
to: String(describing: HTTPURLResponse.self)
)
}

return OAuth2Response(data: sessData, request: request, response: response, error: nil)

} catch {
self.logger?.trace("OAuth2", msg: "RESPONSE\nno response\n\nno data\n---")

let http = HTTPURLResponse(url: request.url!, statusCode: 499, httpVersion: nil, headerFields: nil)!
return OAuth2Response(data: nil, request: request, response: http, error: error)
}
abortableTask = task
}

/// Currently running abortable session task.
Expand Down Expand Up @@ -222,3 +221,16 @@ public func callOnMainThread(_ callback: (() -> Void)) {
}
}

// TODO: move to a separate file
enum CommonError: Error {
case castError(from: String, to: String)
}

extension CommonError: CustomStringConvertible {
public var description: String {
switch self {
case .castError(from: let from, to: let to):
return "Could not cast \(from) to \(to)"
}
}
}
17 changes: 8 additions & 9 deletions Sources/Base/OAuth2Response.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,14 @@ Encapsulates a URLResponse to a URLRequest.

Instances of this class are returned from `OAuth2Requestable` calls, they can be used like so:

perform(request: req) { response in
do {
let data = try response.responseData()
// do what you must with `data` as Data and `response.response` as HTTPURLResponse
}
catch let error {
// the request failed because of `error`
}
}
await perform(request: req)
do {
let data = try response.responseData()
// do what you must with `data` as Data and `response.response` as HTTPURLResponse
}
catch let error {
// the request failed because of `error`
}
*/
open class OAuth2Response {

Expand Down
40 changes: 23 additions & 17 deletions Sources/DataLoader/OAuth2DataLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ open class OAuth2DataLoader: OAuth2Requestable {
- parameter request: The request to execute
- parameter callback: The callback to call when the request completes/fails. Looks terrifying, see above on how to use it
*/
override open func perform(request: URLRequest, callback: @escaping ((OAuth2Response) -> Void)) {
open func perform(request: URLRequest, callback: @escaping ((OAuth2Response) -> Void)) {
perform(request: request, retry: true, callback: callback)
}

Expand Down Expand Up @@ -112,7 +112,9 @@ open class OAuth2DataLoader: OAuth2Requestable {
return
}

super.perform(request: request) { response in
Task {

Check failure on line 115 in Sources/DataLoader/OAuth2DataLoader.swift

View workflow job for this annotation

GitHub Actions / results-xcode-tests-tvOS

error

'Task' is only available in application extensions for tvOS 13.0 or newer

Check failure on line 115 in Sources/DataLoader/OAuth2DataLoader.swift

View workflow job for this annotation

GitHub Actions / results-xcode-tests-tvOS

error

'init(priority:operation:)' is only available in application extensions for tvOS 13.0 or newer
let response = await super.perform(request: request)

do {
if self.alsoIntercept403, 403 == response.response.statusCode {
throw OAuth2Error.unauthorizedClient(nil)
Expand All @@ -126,16 +128,19 @@ open class OAuth2DataLoader: OAuth2Requestable {
if retry {
self.enqueue(request: request, callback: callback)
self.oauth2.clientConfig.accessToken = nil
self.attemptToAuthorize() { json, error in

// dequeue all if we're authorized, throw all away if something went wrong
if nil != json {
self.retryAll()
}
else {
self.throwAllAway(with: error ?? OAuth2Error.requestCancelled)


do {
let json = try await self.attemptToAuthorize()
guard json != nil else {
throw OAuth2Error.requestCancelled
}

self.retryAll()
} catch {
self.throwAllAway(with: error.asOAuth2Error)
}

}
else {
callback(response)
Expand All @@ -157,14 +162,15 @@ open class OAuth2DataLoader: OAuth2Requestable {
- parameter callback: The callback passed on from `authorize(callback:)`. Authorization finishes successfully (auth parameters will be
non-nil but may be an empty dict), fails (error will be non-nil) or is canceled (both params and error are nil)
*/
open func attemptToAuthorize(callback: @escaping ((OAuth2JSON?, OAuth2Error?) -> Void)) {
if !isAuthorizing {
isAuthorizing = true
oauth2.authorize() { authParams, error in
self.isAuthorizing = false
callback(authParams, error)
}
open func attemptToAuthorize() async throws -> OAuth2JSON? {

Check failure on line 165 in Sources/DataLoader/OAuth2DataLoader.swift

View workflow job for this annotation

GitHub Actions / results-xcode-tests-tvOS

error

Concurrency is only available in application extensions for tvOS 13.0.0 or newer

Check failure on line 165 in Sources/DataLoader/OAuth2DataLoader.swift

View workflow job for this annotation

GitHub Actions / results-xcode-tests-tvOS

error

Concurrency is only available in application extensions for tvOS 13.0.0 or newer

Check failure on line 165 in Sources/DataLoader/OAuth2DataLoader.swift

View workflow job for this annotation

GitHub Actions / results-xcode-tests-iOS

error

Concurrency is only available in iOS 13.0.0 or newer
guard !self.isAuthorizing else {
return nil
}

self.isAuthorizing = true
let authParams = try await oauth2.authorize()
self.isAuthorizing = false
return authParams
}


Expand Down
Loading

0 comments on commit 8be3281

Please sign in to comment.