Convert methods to async await.
This commit is contained in:
parent
4041f024e6
commit
a56ffa9405
@ -12,7 +12,7 @@ import Secrets
|
||||
|
||||
/// Models the access token response from Feedly.
|
||||
/// https://developer.feedly.com/v3/auth/#exchanging-an-auth-code-for-a-refresh-token-and-an-access-token
|
||||
public struct FeedlyOAuthAccessTokenResponse: Decodable, OAuthAccessTokenResponse {
|
||||
public struct FeedlyOAuthAccessTokenResponse: Decodable, OAuthAccessTokenResponse, Sendable {
|
||||
/// The ID of the Feedly user.
|
||||
public var id: String
|
||||
|
||||
@ -45,13 +45,9 @@ extension FeedlyAccountDelegate: OAuthAuthorizationGranting {
|
||||
scope: oauthAuthorizationGrantScope,
|
||||
client: client)
|
||||
let caller = FeedlyAPICaller(transport: transport, api: environment, secretsProvider: secretsProvider)
|
||||
let response = try await caller.requestAccessToken(request)
|
||||
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
caller.requestAccessToken(request) { result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
let accessToken = Credentials(type: .oauthAccessToken, username: response.id, secret: response.accessToken)
|
||||
|
||||
let refreshToken: Credentials? = {
|
||||
guard let token = response.refreshToken else {
|
||||
return nil
|
||||
@ -61,25 +57,17 @@ extension FeedlyAccountDelegate: OAuthAuthorizationGranting {
|
||||
|
||||
let grant = OAuthAuthorizationGrant(accessToken: accessToken, refreshToken: refreshToken)
|
||||
|
||||
continuation.resume(returning: grant)
|
||||
|
||||
case .failure(let error):
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
return grant
|
||||
}
|
||||
}
|
||||
|
||||
extension FeedlyAccountDelegate: OAuthAccessTokenRefreshing {
|
||||
func refreshAccessToken(with refreshToken: String, client: OAuthAuthorizationClient, completion: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ()) {
|
||||
func refreshAccessToken(with refreshToken: String, client: OAuthAuthorizationClient) async throws -> OAuthAuthorizationGrant {
|
||||
|
||||
let request = OAuthRefreshAccessTokenRequest(refreshToken: refreshToken, scope: nil, client: client)
|
||||
let response = try await caller.refreshAccessToken(request)
|
||||
|
||||
caller.refreshAccessToken(request) { result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
let accessToken = Credentials(type: .oauthAccessToken, username: response.id, secret: response.accessToken)
|
||||
|
||||
let refreshToken: Credentials? = {
|
||||
guard let token = response.refreshToken else {
|
||||
return nil
|
||||
@ -89,11 +77,6 @@ extension FeedlyAccountDelegate: OAuthAccessTokenRefreshing {
|
||||
|
||||
let grant = OAuthAuthorizationGrant(accessToken: accessToken, refreshToken: refreshToken)
|
||||
|
||||
completion(.success(grant))
|
||||
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
return grant
|
||||
}
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ protocol FeedlyAPICallerDelegate: AnyObject {
|
||||
|
||||
guard !isSuspended else { throw TransportError.suspended }
|
||||
|
||||
var request = try urlRequest(path: "/v3/opml", method: HTTPMethod.post, includeJSONHeaders: false, includeOauthToken: true)
|
||||
var request = try urlRequest(path: "/v3/opml", method: HTTPMethod.post, includeJSONHeaders: false, includeOAuthToken: true)
|
||||
request.addValue("text/xml", forHTTPHeaderField: HTTPRequestHeader.contentType)
|
||||
request.addValue("application/json", forHTTPHeaderField: HTTPRequestHeader.acceptType)
|
||||
request.httpBody = opmlData
|
||||
@ -170,14 +170,12 @@ protocol FeedlyAPICallerDelegate: AnyObject {
|
||||
|
||||
guard !isSuspended else { throw TransportError.suspended }
|
||||
|
||||
var request = try urlRequest(path: "/v3/collections", method: HTTPMethod.post, includeJSONHeaders: true, includeOauthToken: true)
|
||||
var request = try urlRequest(path: "/v3/collections", method: HTTPMethod.post, includeJSONHeaders: true, includeOAuthToken: true)
|
||||
|
||||
struct CreateCollectionBody: Encodable {
|
||||
var label: String
|
||||
}
|
||||
let encoder = JSONEncoder()
|
||||
let data = try encoder.encode(CreateCollectionBody(label: label))
|
||||
request.httpBody = data
|
||||
try addObject(CreateCollectionBody(label: label), to: &request)
|
||||
|
||||
let (httpResponse, collections) = try await send(request: request, resultType: [FeedlyCollection].self)
|
||||
|
||||
@ -191,15 +189,13 @@ protocol FeedlyAPICallerDelegate: AnyObject {
|
||||
|
||||
guard !isSuspended else { throw TransportError.suspended }
|
||||
|
||||
var request = try urlRequest(path: "/v3/collections", method: HTTPMethod.post, includeJSONHeaders: true, includeOauthToken: true)
|
||||
var request = try urlRequest(path: "/v3/collections", method: HTTPMethod.post, includeJSONHeaders: true, includeOAuthToken: true)
|
||||
|
||||
struct RenameCollectionBody: Encodable {
|
||||
var id: String
|
||||
var label: String
|
||||
}
|
||||
let encoder = JSONEncoder()
|
||||
let data = try encoder.encode(RenameCollectionBody(id: id, label: name))
|
||||
request.httpBody = data
|
||||
try addObject(RenameCollectionBody(id: id, label: name), to: &request)
|
||||
|
||||
let (httpResponse, collections) = try await send(request: request, resultType: [FeedlyCollection].self)
|
||||
|
||||
@ -220,7 +216,7 @@ protocol FeedlyAPICallerDelegate: AnyObject {
|
||||
guard let encodedID = encodeForURLPath(id) else {
|
||||
throw FeedlyAccountDelegateError.unexpectedResourceID(id)
|
||||
}
|
||||
let request = try urlRequest(path: "/v3/collections/\(encodedID)", method: HTTPMethod.delete, includeJSONHeaders: true, includeOauthToken: true)
|
||||
let request = try urlRequest(path: "/v3/collections/\(encodedID)", method: HTTPMethod.delete, includeJSONHeaders: true, includeOAuthToken: true)
|
||||
|
||||
let (httpResponse, _) = try await send(request: request, resultType: Optional<FeedlyCollection>.self)
|
||||
|
||||
@ -229,7 +225,7 @@ protocol FeedlyAPICallerDelegate: AnyObject {
|
||||
}
|
||||
}
|
||||
|
||||
func removeFeed(_ feedId: String, fromCollectionWith collectionID: String) async throws {
|
||||
func removeFeed(_ feedID: String, fromCollectionWith collectionID: String) async throws {
|
||||
|
||||
guard !isSuspended else { throw TransportError.suspended }
|
||||
|
||||
@ -246,14 +242,12 @@ protocol FeedlyAPICallerDelegate: AnyObject {
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = HTTPMethod.delete
|
||||
addJSONHeaders(&request)
|
||||
try addOauthAccessToken(&request)
|
||||
try addOAuthAccessToken(&request)
|
||||
|
||||
struct RemovableFeed: Encodable {
|
||||
let id: String
|
||||
}
|
||||
let encoder = JSONEncoder()
|
||||
let data = try encoder.encode([RemovableFeed(id: feedId)])
|
||||
request.httpBody = data
|
||||
try addObject([RemovableFeed(id: feedID)], to: &request)
|
||||
|
||||
// `resultType` is optional because the Feedly API has gone from returning an array of removed feeds to returning `null`.
|
||||
// https://developer.feedly.com/v3/collections/#remove-multiple-feeds-from-a-personal-collection
|
||||
@ -283,15 +277,13 @@ extension FeedlyAPICaller: FeedlyAddFeedToCollectionService {
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = HTTPMethod.put
|
||||
addJSONHeaders(&request)
|
||||
try addOauthAccessToken(&request)
|
||||
try addOAuthAccessToken(&request)
|
||||
|
||||
struct AddFeedBody: Encodable {
|
||||
var id: String
|
||||
var title: String?
|
||||
}
|
||||
let encoder = JSONEncoder()
|
||||
let data = try encoder.encode(AddFeedBody(id: feedID.id, title: title))
|
||||
request.httpBody = data
|
||||
try addObject(AddFeedBody(id: feedID.id, title: title), to: &request)
|
||||
|
||||
let (_, collectionFeeds) = try await send(request: request, resultType: [FeedlyFeed].self)
|
||||
guard let collectionFeeds else {
|
||||
@ -305,6 +297,7 @@ extension FeedlyAPICaller: FeedlyAddFeedToCollectionService {
|
||||
extension FeedlyAPICaller: OAuthAuthorizationCodeGrantRequesting {
|
||||
|
||||
static func authorizationCodeURLRequest(for request: OAuthAuthorizationRequest, baseUrlComponents: URLComponents) -> URLRequest {
|
||||
|
||||
var components = baseUrlComponents
|
||||
components.path = "/v3/auth/auth"
|
||||
components.queryItems = request.queryItems
|
||||
@ -323,153 +316,62 @@ extension FeedlyAPICaller: OAuthAuthorizationCodeGrantRequesting {
|
||||
|
||||
typealias AccessTokenResponse = FeedlyOAuthAccessTokenResponse
|
||||
|
||||
func requestAccessToken(_ authorizationRequest: OAuthAccessTokenRequest, completion: @escaping (Result<FeedlyOAuthAccessTokenResponse, Error>) -> ()) {
|
||||
guard !isSuspended else {
|
||||
return DispatchQueue.main.async {
|
||||
completion(.failure(TransportError.suspended))
|
||||
}
|
||||
func requestAccessToken(_ authorizationRequest: OAuthAccessTokenRequest) async throws -> FeedlyOAuthAccessTokenResponse {
|
||||
|
||||
guard !isSuspended else { throw TransportError.suspended }
|
||||
|
||||
var request = try urlRequest(path: "/v3/auth/token", method: HTTPMethod.post, includeJSONHeaders: true, includeOAuthToken: false)
|
||||
try addObject(authorizationRequest, keyEncodingStrategy: .convertToSnakeCase, to: &request)
|
||||
|
||||
let (_, tokenResponse) = try await send(request: request, resultType: AccessTokenResponse.self)
|
||||
guard let tokenResponse else {
|
||||
throw URLError(.cannotDecodeContentData)
|
||||
}
|
||||
|
||||
var components = baseURLComponents
|
||||
components.path = "/v3/auth/token"
|
||||
|
||||
guard let url = components.url else {
|
||||
fatalError("\(components) does not produce a valid URL.")
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
request.addValue("application/json", forHTTPHeaderField: "Accept-Type")
|
||||
|
||||
do {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.keyEncodingStrategy = .convertToSnakeCase
|
||||
request.httpBody = try encoder.encode(authorizationRequest)
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(error))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
send(request: request, resultType: AccessTokenResponse.self, keyDecoding: .convertFromSnakeCase) { result in
|
||||
switch result {
|
||||
case .success(let (_, tokenResponse)):
|
||||
if let response = tokenResponse {
|
||||
completion(.success(response))
|
||||
} else {
|
||||
completion(.failure(URLError(.cannotDecodeContentData)))
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
return tokenResponse
|
||||
}
|
||||
}
|
||||
|
||||
extension FeedlyAPICaller: OAuthAcessTokenRefreshRequesting {
|
||||
|
||||
func refreshAccessToken(_ refreshRequest: OAuthRefreshAccessTokenRequest, completion: @escaping (Result<FeedlyOAuthAccessTokenResponse, Error>) -> ()) {
|
||||
guard !isSuspended else {
|
||||
return DispatchQueue.main.async {
|
||||
completion(.failure(TransportError.suspended))
|
||||
}
|
||||
func refreshAccessToken(_ refreshRequest: OAuthRefreshAccessTokenRequest) async throws -> FeedlyOAuthAccessTokenResponse {
|
||||
|
||||
guard !isSuspended else { throw TransportError.suspended }
|
||||
|
||||
var request = try urlRequest(path: "/v3/auth/token", method: HTTPMethod.post, includeJSONHeaders: true, includeOAuthToken: false)
|
||||
try addObject(refreshRequest, keyEncodingStrategy: .convertToSnakeCase, to: &request)
|
||||
|
||||
let (_, tokenResponse) = try await send(request: request, resultType: AccessTokenResponse.self)
|
||||
guard let tokenResponse else {
|
||||
throw URLError(.cannotDecodeContentData)
|
||||
}
|
||||
|
||||
var components = baseURLComponents
|
||||
components.path = "/v3/auth/token"
|
||||
|
||||
guard let url = components.url else {
|
||||
fatalError("\(components) does not produce a valid URL.")
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
request.addValue("application/json", forHTTPHeaderField: "Accept-Type")
|
||||
|
||||
do {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.keyEncodingStrategy = .convertToSnakeCase
|
||||
request.httpBody = try encoder.encode(refreshRequest)
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(error))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
send(request: request, resultType: AccessTokenResponse.self, keyDecoding: .convertFromSnakeCase) { result in
|
||||
switch result {
|
||||
case .success(let (_, tokenResponse)):
|
||||
if let response = tokenResponse {
|
||||
completion(.success(response))
|
||||
} else {
|
||||
completion(.failure(URLError(.cannotDecodeContentData)))
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
return tokenResponse
|
||||
}
|
||||
}
|
||||
|
||||
extension FeedlyAPICaller: FeedlyGetCollectionsService {
|
||||
|
||||
func getCollections(completion: @escaping @Sendable (Result<[FeedlyCollection], Error>) -> ()) {
|
||||
guard !isSuspended else {
|
||||
return DispatchQueue.main.async {
|
||||
completion(.failure(TransportError.suspended))
|
||||
}
|
||||
func getCollections() async throws -> [FeedlyCollection] {
|
||||
|
||||
guard !isSuspended else { throw TransportError.suspended }
|
||||
|
||||
let request = try urlRequest(path: "/v3/collections", method: HTTPMethod.get, includeJSONHeaders: true, includeOAuthToken: true)
|
||||
|
||||
let (_, collections) = try await send(request: request, resultType: [FeedlyCollection].self)
|
||||
guard let collections else {
|
||||
throw URLError(.cannotDecodeContentData)
|
||||
}
|
||||
|
||||
guard let accessToken = credentials?.secret else {
|
||||
return DispatchQueue.main.async {
|
||||
completion(.failure(CredentialsError.incompleteCredentials))
|
||||
}
|
||||
}
|
||||
var components = baseURLComponents
|
||||
components.path = "/v3/collections"
|
||||
|
||||
guard let url = components.url else {
|
||||
fatalError("\(components) does not produce a valid URL.")
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.addValue("application/json", forHTTPHeaderField: HTTPRequestHeader.contentType)
|
||||
request.addValue("application/json", forHTTPHeaderField: "Accept-Type")
|
||||
request.addValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization)
|
||||
|
||||
send(request: request, resultType: [FeedlyCollection].self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in
|
||||
switch result {
|
||||
case .success(let (_, collections)):
|
||||
if let response = collections {
|
||||
completion(.success(response))
|
||||
} else {
|
||||
completion(.failure(URLError(.cannotDecodeContentData)))
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
return collections
|
||||
}
|
||||
}
|
||||
|
||||
extension FeedlyAPICaller: FeedlyGetStreamContentsService {
|
||||
|
||||
@MainActor func getStreamContents(for resource: FeedlyResourceID, continuation: String? = nil, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStream, Error>) -> ()) {
|
||||
guard !isSuspended else {
|
||||
return DispatchQueue.main.async {
|
||||
completion(.failure(TransportError.suspended))
|
||||
}
|
||||
}
|
||||
@MainActor func getStreamContents(for resource: FeedlyResourceID, continuation: String?, newerThan: Date?, unreadOnly: Bool?) async throws -> FeedlyStream {
|
||||
|
||||
guard let accessToken = credentials?.secret else {
|
||||
return DispatchQueue.main.async {
|
||||
completion(.failure(CredentialsError.incompleteCredentials))
|
||||
}
|
||||
}
|
||||
guard !isSuspended else { throw TransportError.suspended }
|
||||
|
||||
var components = baseURLComponents
|
||||
components.path = "/v3/streams/contents"
|
||||
@ -505,22 +407,16 @@ extension FeedlyAPICaller: FeedlyGetStreamContentsService {
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.addValue("application/json", forHTTPHeaderField: HTTPRequestHeader.contentType)
|
||||
request.addValue("application/json", forHTTPHeaderField: "Accept-Type")
|
||||
request.addValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization)
|
||||
addJSONHeaders(&request)
|
||||
try addOAuthAccessToken(&request)
|
||||
|
||||
send(request: request, resultType: FeedlyStream.self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in
|
||||
switch result {
|
||||
case .success(let (_, collections)):
|
||||
if let response = collections {
|
||||
completion(.success(response))
|
||||
} else {
|
||||
completion(.failure(URLError(.cannotDecodeContentData)))
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
let (_, collections) = try await send(request: request, resultType: FeedlyStream.self)
|
||||
|
||||
guard let collections else {
|
||||
throw URLError(.cannotDecodeContentData)
|
||||
}
|
||||
|
||||
return collections
|
||||
}
|
||||
}
|
||||
|
||||
@ -809,7 +705,7 @@ extension FeedlyAPICaller: FeedlyLogoutService {
|
||||
|
||||
private extension FeedlyAPICaller {
|
||||
|
||||
func urlRequest(path: String, method: String, includeJSONHeaders: Bool, includeOauthToken: Bool) throws -> URLRequest {
|
||||
func urlRequest(path: String, method: String, includeJSONHeaders: Bool, includeOAuthToken: Bool) throws -> URLRequest {
|
||||
|
||||
let url = apiURL(path)
|
||||
var request = URLRequest(url: url)
|
||||
@ -819,8 +715,8 @@ private extension FeedlyAPICaller {
|
||||
if includeJSONHeaders {
|
||||
addJSONHeaders(&request)
|
||||
}
|
||||
if includeOauthToken {
|
||||
try addOauthAccessToken(&request)
|
||||
if includeOAuthToken {
|
||||
try addOAuthAccessToken(&request)
|
||||
}
|
||||
|
||||
return request
|
||||
@ -832,7 +728,7 @@ private extension FeedlyAPICaller {
|
||||
request.addValue("application/json", forHTTPHeaderField: "Accept-Type")
|
||||
}
|
||||
|
||||
func addOauthAccessToken(_ request: inout URLRequest) throws {
|
||||
func addOAuthAccessToken(_ request: inout URLRequest) throws {
|
||||
|
||||
guard let accessToken = credentials?.secret else {
|
||||
throw CredentialsError.incompleteCredentials
|
||||
@ -852,4 +748,10 @@ private extension FeedlyAPICaller {
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func addObject<T: Encodable & Sendable>(_ object: T, keyEncodingStrategy: JSONEncoder.KeyEncodingStrategy = .useDefaultKeys, to request: inout URLRequest) throws {
|
||||
|
||||
let data = try JSONEncoder().encode(object)
|
||||
request.httpBody = data
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import Feedly
|
||||
|
||||
/// Models section 6 of the OAuth 2.0 Authorization Framework
|
||||
/// https://tools.ietf.org/html/rfc6749#section-6
|
||||
public struct OAuthRefreshAccessTokenRequest: Encodable {
|
||||
public struct OAuthRefreshAccessTokenRequest: Encodable, Sendable {
|
||||
public let grantType = "refresh_token"
|
||||
public var refreshToken: String
|
||||
public var scope: String?
|
||||
@ -37,11 +37,11 @@ public protocol OAuthAcessTokenRefreshRequesting {
|
||||
/// Access tokens expire. Perform a request for a fresh access token given the long life refresh token received when authorization was granted.
|
||||
/// - Parameter refreshRequest: The refresh token and other information the authorization server requires to grant the client fresh access tokens on the user's behalf.
|
||||
/// - Parameter completion: On success, the access token response appropriate for concrete type's service. Both the access and refresh token should be stored, preferably on the Keychain. On failure, possibly a `URLError` or `OAuthAuthorizationErrorResponse` value.
|
||||
func refreshAccessToken(_ refreshRequest: OAuthRefreshAccessTokenRequest, completion: @escaping (Result<AccessTokenResponse, Error>) -> ())
|
||||
func refreshAccessToken(_ refreshRequest: OAuthRefreshAccessTokenRequest) async throws -> AccessTokenResponse
|
||||
}
|
||||
|
||||
/// Implemented by concrete types to perform the actual request.
|
||||
protocol OAuthAccessTokenRefreshing: AnyObject {
|
||||
|
||||
@MainActor func refreshAccessToken(with refreshToken: String, client: OAuthAuthorizationClient, completion: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ())
|
||||
@MainActor func refreshAccessToken(with refreshToken: String, client: OAuthAuthorizationClient) async throws -> OAuthAuthorizationGrant
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ public enum OAuthAuthorizationError: String, Sendable {
|
||||
|
||||
/// Models section 4.1.3 of the OAuth 2.0 Authorization Framework
|
||||
/// https://tools.ietf.org/html/rfc6749#section-4.1.3
|
||||
public struct OAuthAccessTokenRequest: Encodable {
|
||||
public struct OAuthAccessTokenRequest: Encodable, Sendable {
|
||||
public let grantType = "authorization_code"
|
||||
public var code: String
|
||||
public var redirectUri: String
|
||||
@ -157,13 +157,13 @@ public protocol OAuthAuthorizationCodeGrantRequesting {
|
||||
/// Provides the URL request that allows users to consent to the client having access to their information. Typically loaded by a web view.
|
||||
/// - Parameter request: The information about the client requesting authorization to be granted access tokens.
|
||||
/// - Parameter baseUrlComponents: The scheme and host of the url except for the path.
|
||||
static func authorizationCodeURLRequest(for request: OAuthAuthorizationRequest, baseUrlComponents: URLComponents) -> URLRequest
|
||||
@MainActor static func authorizationCodeURLRequest(for request: OAuthAuthorizationRequest, baseUrlComponents: URLComponents) -> URLRequest
|
||||
|
||||
|
||||
/// Performs the request for the access token given an authorization code.
|
||||
/// - Parameter authorizationRequest: The authorization code and other information the authorization server requires to grant the client access tokens on the user's behalf.
|
||||
/// - Parameter completion: On success, the access token response appropriate for concrete type's service. On failure, possibly a `URLError` or `OAuthAuthorizationErrorResponse` value.
|
||||
func requestAccessToken(_ authorizationRequest: OAuthAccessTokenRequest, completion: @escaping (Result<AccessTokenResponse, Error>) -> ())
|
||||
/// - Returns: On success, the access token response appropriate for concrete type's service. On failure, throws possibly a `URLError` or `OAuthAuthorizationErrorResponse` value.
|
||||
func requestAccessToken(_ authorizationRequest: OAuthAccessTokenRequest) async throws -> FeedlyOAuthAccessTokenResponse
|
||||
}
|
||||
|
||||
protocol OAuthAuthorizationGranting: AccountDelegate {
|
||||
|
@ -27,7 +27,8 @@ final class FeedlyRefreshAccessTokenOperation: FeedlyOperation {
|
||||
}
|
||||
|
||||
override func run() {
|
||||
let refreshToken: Credentials
|
||||
|
||||
Task { @MainActor in
|
||||
|
||||
do {
|
||||
guard let credentials = try account.retrieveCredentials(type: .oauthRefreshToken) else {
|
||||
@ -35,44 +36,25 @@ final class FeedlyRefreshAccessTokenOperation: FeedlyOperation {
|
||||
throw TransportError.httpError(status: 403)
|
||||
}
|
||||
|
||||
refreshToken = credentials
|
||||
|
||||
} catch {
|
||||
didFinish(with: error)
|
||||
return
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Refreshing access token.")
|
||||
|
||||
// Ignore cancellation after the request is resumed otherwise we may continue storing a potentially invalid token!
|
||||
service.refreshAccessToken(with: refreshToken.secret, client: oauthClient) { result in
|
||||
self.didRefreshAccessToken(result)
|
||||
}
|
||||
}
|
||||
os_log(.debug, log: log, "Refreshing access token.")
|
||||
let grant = try await service.refreshAccessToken(with: credentials.secret, client: oauthClient)
|
||||
|
||||
private func didRefreshAccessToken(_ result: Result<OAuthAuthorizationGrant, Error>) {
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
switch result {
|
||||
case .success(let grant):
|
||||
do {
|
||||
os_log(.debug, log: log, "Storing refresh token.")
|
||||
// Store the refresh token first because it sends this token to the account delegate.
|
||||
if let token = grant.refreshToken {
|
||||
try account.storeCredentials(token)
|
||||
os_log(.debug, log: log, "Storing refresh token.")
|
||||
if let refreshToken = grant.refreshToken {
|
||||
try account.storeCredentials(refreshToken)
|
||||
}
|
||||
|
||||
os_log(.debug, log: log, "Storing access token.")
|
||||
// Now store the access token because we want the account delegate to use it.
|
||||
os_log(.debug, log: log, "Storing access token.")
|
||||
try account.storeCredentials(grant.accessToken)
|
||||
|
||||
didFinish()
|
||||
|
||||
} catch {
|
||||
didFinish(with: error)
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
didFinish(with: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,22 +28,20 @@ public final class FeedlyGetCollectionsOperation: FeedlyOperation, FeedlyCollect
|
||||
}
|
||||
|
||||
public override func run() {
|
||||
|
||||
Task { @MainActor in
|
||||
os_log(.debug, log: log, "Requesting collections.")
|
||||
|
||||
service.getCollections { result in
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
switch result {
|
||||
case .success(let collections):
|
||||
do {
|
||||
let collections = try await service.getCollections()
|
||||
os_log(.debug, log: self.log, "Received collections: %{public}@", collections.map { $0.id })
|
||||
self.collections = collections
|
||||
self.didFinish()
|
||||
|
||||
case .failure(let error):
|
||||
} catch {
|
||||
os_log(.debug, log: self.log, "Unable to request collections: %{public}@.", error as NSError)
|
||||
self.didFinish(with: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,16 +98,17 @@ public final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntr
|
||||
}
|
||||
|
||||
public override func run() {
|
||||
service.getStreamContents(for: resourceProvider.resource, continuation: continuation, newerThan: newerThan, unreadOnly: unreadOnly) { result in
|
||||
switch result {
|
||||
case .success(let stream):
|
||||
|
||||
Task { @MainActor in
|
||||
|
||||
do {
|
||||
let stream = try await service.getStreamContents(for: resourceProvider.resource, continuation: continuation, newerThan: newerThan, unreadOnly: unreadOnly)
|
||||
|
||||
self.stream = stream
|
||||
|
||||
self.streamDelegate?.feedlyGetStreamContentsOperation(self, didGetContentsOf: stream)
|
||||
|
||||
self.didFinish()
|
||||
|
||||
case .failure(let error):
|
||||
} catch {
|
||||
os_log(.debug, log: self.log, "Unable to get stream contents: %{public}@.", error as NSError)
|
||||
self.didFinish(with: error)
|
||||
}
|
||||
|
@ -9,5 +9,6 @@
|
||||
import Foundation
|
||||
|
||||
public protocol FeedlyGetCollectionsService: AnyObject {
|
||||
func getCollections(completion: @escaping @Sendable (Result<[FeedlyCollection], Error>) -> ())
|
||||
|
||||
@MainActor func getCollections() async throws -> [FeedlyCollection]
|
||||
}
|
||||
|
@ -9,5 +9,6 @@
|
||||
import Foundation
|
||||
|
||||
public protocol FeedlyGetStreamContentsService: AnyObject {
|
||||
func getStreamContents(for resource: FeedlyResourceID, continuation: String?, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStream, Error>) -> ())
|
||||
|
||||
@MainActor func getStreamContents(for resource: FeedlyResourceID, continuation: String?, newerThan: Date?, unreadOnly: Bool?) async throws -> FeedlyStream
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user