diff --git a/MastoSearch.xcodeproj/project.pbxproj b/MastoSearch.xcodeproj/project.pbxproj index 1e0aff8..e0ca238 100644 --- a/MastoSearch.xcodeproj/project.pbxproj +++ b/MastoSearch.xcodeproj/project.pbxproj @@ -589,6 +589,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 13.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.MastoSearch; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -616,6 +617,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 13.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.MastoSearch; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/MastoSearch/Main.storyboard b/MastoSearch/Main.storyboard index 1a9f2da..d0438d6 100644 --- a/MastoSearch/Main.storyboard +++ b/MastoSearch/Main.storyboard @@ -1,8 +1,8 @@ - + - + diff --git a/MastoSearchCore/Package.swift b/MastoSearchCore/Package.swift index 81e3c69..e0ee496 100644 --- a/MastoSearchCore/Package.swift +++ b/MastoSearchCore/Package.swift @@ -6,8 +6,8 @@ import PackageDescription let package = Package( name: "MastoSearchCore", platforms: [ - .macOS(.v12), - .iOS(.v15), + .macOS(.v13), + .iOS(.v16), ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. diff --git a/MastoSearchCore/Sources/MastoSearchCore/APIController.swift b/MastoSearchCore/Sources/MastoSearchCore/APIController.swift index a2d5df0..c0c1596 100644 --- a/MastoSearchCore/Sources/MastoSearchCore/APIController.swift +++ b/MastoSearchCore/Sources/MastoSearchCore/APIController.swift @@ -6,8 +6,9 @@ // import Foundation +import os -public struct APIController { +public final class APIController { public static let shared = APIController() @@ -38,6 +39,8 @@ public struct APIController { return decoder }() + + private var verifyCredsResponse: OSAllocatedUnfairLock = .init(initialState: nil) private init() {} @@ -61,7 +64,7 @@ public struct APIController { return } do { - let statuses = try decoder.decode(R.self, from: data) + let statuses = try self.decoder.decode(R.self, from: data) completion(.success(statuses)) } catch { completion(.failure(.decoding(error))) @@ -108,16 +111,52 @@ public struct APIController { } public func getStatuses(range: RequestRange, completion: @escaping (Result<[Status], Error>) -> Void) { + verifyCredentials { + switch $0 { + case .failure(let error): + completion(.failure(error)) + case .success(let response): + guard let account = LocalData.account else { + completion(.failure(.noAccount)) + return + } + var components = URLComponents(url: account.instanceURL, resolvingAgainstBaseURL: false)! + components.path = "/api/v1/accounts/\(response.id)/statuses" + components.queryItems = range.queryParameters + [ + URLQueryItem(name: "exclude_replies", value: "false"), + URLQueryItem(name: "limit", value: "50"), + ] + self.run(request: URLRequest(url: components.url!), completion: completion) + } + } + } + + private func verifyCredentials(completion: @escaping (Result) -> Void) { + let cached = verifyCredsResponse.withLock { + if let cached = $0 { + completion(.success(cached)) + return true + } else { + return false + } + } + guard !cached else { + return + } guard let account = LocalData.account else { + completion(.failure(.noAccount)) return } var components = URLComponents(url: account.instanceURL, resolvingAgainstBaseURL: false)! - components.path = "/api/v1/accounts/1/statuses" - components.queryItems = range.queryParameters + [ - URLQueryItem(name: "exclude_replies", value: "false"), - URLQueryItem(name: "limit", value: "50"), - ] - run(request: URLRequest(url: components.url!), completion: completion) + components.path = "/api/v1/accounts/verify_credentials" + run(request: URLRequest(url: components.url!)) { (result: Result) in + if case .success(let resp) = result { + self.verifyCredsResponse.withLock { + $0 = resp + } + } + completion(result) + } } } @@ -176,6 +215,10 @@ extension APIController { case id, url, spoiler_text, content, created_at, reblog } } + + private struct VerifyCredentialsResponse: Decodable { + let id: String + } } extension APIController { @@ -184,6 +227,7 @@ extension APIController { case error(Swift.Error) case noData case decoding(Swift.Error) + case noAccount public var localizedDescription: String { switch self { @@ -195,6 +239,8 @@ extension APIController { return "No data" case .decoding(let error): return "Decoding: \(error.localizedDescription)" + case .noAccount: + return "No account" } } }