From 01c0b208ac3f322694eb8848c7419521dfad236b Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 12 Mar 2023 19:47:50 -0400 Subject: [PATCH] fix: Throw an error if the SDK isn't init in 5 seconds (#73) * fix: Throw an error if the SDK isn't init in 5 seconds * nits * nit --- CHANGELOG.md | 8 +- README.md | 35 ++--- Sources/ParseSwift/API/API+Command.swift | 133 +++++++++--------- .../API/API+NonParseBodyCommand.swift | 69 +++++---- Sources/ParseSwift/API/API.swift | 4 +- .../Documentation.docc/ParseSwift.md | 2 +- .../ParseSwift/Extensions/URLSession.swift | 25 ++-- .../ParseSwift/LiveQuery/ParseLiveQuery.swift | 2 +- .../Objects/ParseInstallation.swift | 2 +- Sources/ParseSwift/Objects/ParseUser.swift | 2 +- Sources/ParseSwift/Parse.swift | 46 +++--- Sources/ParseSwift/ParseConstants.swift | 2 +- .../ParseSwift/Protocols/ParseConfig.swift | 44 +++--- .../ParseSwift/Types/ParseConfigCodable.swift | 2 +- Tests/ParseSwiftTests/APICommandTests.swift | 26 ++-- 15 files changed, 207 insertions(+), 195 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd97794b4..e1dbdafca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,15 @@ # Parse-Swift Changelog ### main -[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.1.0...main), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/main/documentation/parseswift) +[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.1.1...main), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/main/documentation/parseswift) * _Contributing to this repo? Add info about your change here to be included in the next release_ +### 5.1.1 +[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.1.0...5.1.1), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/5.1.1/documentation/parseswift) + +__Fixes__ +* Instead of a possible inifinite loop, throw an error if the Swift SDK is used, but not initialized within 5 seconds of first use ([#73](https://github.com/netreconlab/Parse-Swift/pull/73)), thanks to [Corey Baker](https://github.com/cbaker6). + ### 5.1.0 [Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.0.1...5.1.0), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/5.1.0/documentation/parseswift) diff --git a/README.md b/README.md index 7d43e9839..688b958e9 100644 --- a/README.md +++ b/README.md @@ -24,18 +24,18 @@ --- -A pure Swift library that gives you access to the powerful Parse Server backend from your Swift applications. +A pure Swift library that gives you access to the powerful Parse Server backend. See why Parse-SwiftOG is better than all of the other Parse SDK's by looking at the [feature comparison tables](https://github.com/netreconlab/Parse-Swift/discussions/72). -For more information about the Parse Platform and its features, see the public [documentation][docs]. The ParseSwift SDK is not a port of the [Parse-SDK-iOS-OSX SDK](https://github.com/parse-community/Parse-SDK-iOS-OSX) and though some of it may feel familiar, it is not backwards compatible and is designed using [protocol oriented programming (POP) and value types](https://www.pluralsight.com/guides/protocol-oriented-programming-in-swift) instead of OOP and reference types. You can learn more about POP by watching [Protocol-Oriented Programming in Swift](https://developer.apple.com/videos/play/wwdc2015/408/) or [Protocol and Value Oriented Programming in UIKit Apps](https://developer.apple.com/videos/play/wwdc2016/419/) videos from previous WWDC's. For more details about ParseSwift, visit the [api documentation](https://netreconlab.github.io/Parse-Swift/release/documentation/parseswift/). +The ParseSwiftOG SDK is not a port of the [Parse-SDK-iOS-OSX SDK](https://github.com/parse-community/Parse-SDK-iOS-OSX) and though some of it may feel familiar, it is not backwards compatible and is designed using [protocol oriented programming (POP) and value types](https://www.pluralsight.com/guides/protocol-oriented-programming-in-swift) instead of OOP and reference types. You can learn more about POP by watching [Protocol-Oriented Programming in Swift](https://developer.apple.com/videos/play/wwdc2015/408/) or [Protocol and Value Oriented Programming in UIKit Apps](https://developer.apple.com/videos/play/wwdc2016/419/) videos from previous WWDC's. For more details about ParseSwift, visit the [api documentation](https://netreconlab.github.io/Parse-Swift/release/documentation/parseswift/). To learn how to use or experiment with ParseSwift, you can run and edit the [ParseSwift.playground](https://github.com/netreconlab/Parse-Swift/tree/main/ParseSwift.playground/Pages). You can use the parse-server in [this repo](https://github.com/netreconlab/parse-hipaa/tree/parse-swift) which has docker compose files (`docker-compose up` gives you a working server) configured to connect with the playground files, has [Parse Dashboard](https://github.com/parse-community/parse-dashboard), and can be used with MongoDB or PostgreSQL. You can also configure the Swift Playgrounds to work with your own Parse Server by editing the configuation in [Common.swift](https://github.com/netreconlab/Parse-Swift/blob/e9ba846c399257100b285d25d2bd055628b13b4b/ParseSwift.playground/Sources/Common.swift#L4-L19). To learn more, check out [CONTRIBUTING.md](https://github.com/netreconlab/Parse-Swift/blob/main/CONTRIBUTING.md#swift-playgrounds). -## Why use Parse-SwiftOG from NetReconLab? -This repo is maintained by [Corey E. Baker](https://github.com/cbaker6), [1 of 2 of the original developers of Parse-Swift](https://github.com/parse-community/Parse-Swift/graphs/contributors). Corey was responsible for the direction and development of all releases from [1.0.0](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2) to [4.14.2](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2). This repo will remain aligned with the original core principals of a swifty framework that contains zero dependencies and takes advantage of all of the features the [parse-server](https://github.com/parse-community/parse-server) has to offer. The reason for the bifurcation from the parse-community version of Parse-Swift is due to interference and a number of disagreements with the [Parse Project Management Committee (PMC)](https://github.com/parse-community/Governance/blob/main/TEAM.md#project-management-committee-pmc) about the future of ParseSwift along with the PMC member ignoring code reviews and comments, marking relevant comments as `off-topic`, merging commits directly to main branch, lack-of-understanding of Swift fundamentals, client-side development, and lack of knowledge about how the Parse-Swift framework is designed. It is important to emphasize that no member of the Parse PMC participated in the design, release, and direction of Parse-Swift from its [first commit](https://github.com/parse-community/Parse-Swift/tree/cf69b7f0638819a7070b82228bc51a97df5757e6) to version [4.14.2](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2). The Parse PMC also does not properly give attribution or show appreciation to Corey as the lead developer of Parse-Swift. In addition, no funding or support from the [parse-community funds](https://opencollective.com/parse-server) has ever been offered to Corey for any of his [contributions to Parse](https://github.com/search?q=user%3Aparse-community+author%3Acbaker6&type=issues), though a [number of funds have been offered and made to other contributors](https://opencollective.com/parse-server#category-BUDGET). If you benefit from Parse-Swift and would like to show monetary support, feel free to Buy Me A Coffee +## Why use Parse-SwiftOG from NetReconLab? +This repo is maintained by [Corey E. Baker](https://github.com/cbaker6), [1 of 2 of the original developers of Parse-Swift](https://github.com/parse-community/Parse-Swift/graphs/contributors). Corey was responsible for the direction and development of all releases from [1.0.0](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2) to [4.14.2](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2). This repo will remain aligned with the original core principals of a swifty framework that contains zero dependencies and takes advantage of all of the features the [parse-server](https://github.com/parse-community/parse-server) has to offer. You can learn more about why I left the parse-community and created my own repo [here](https://github.com/netreconlab/Parse-Swift/discussions/7). If you benefit from Parse-Swift and would like to show monetary support, feel free to Buy Me A Coffee --- -- [ Why use Parse-SwiftOG from NetReconLab?](#-why-use-parse-swift-from-netreconlab) +- [Why use Parse-SwiftOG from NetReconLab?](https://github.com/netreconlab/Parse-Swift/discussions/7) - [Installation](#installation) - [Swift Package Manager](#swift-package-manager) - [CocoaPods](#cocoapods) @@ -48,7 +48,7 @@ This repo is maintained by [Corey E. Baker](https://github.com/cbaker6), [1 of 2 - [SwiftUI View Models Using Combine](#swiftui-view-models-using-combine) - [Traditional Callbacks](#traditional-callbacks) - [Advanced Usage](#advanced-usage) -- [Migration from Parse ObjC SDK](#migration-from-parse-objc-sdk) +- [Migrating from Older Versions and SDKs](#migrating-from-older-versions-and-sdks) ## Installation @@ -57,13 +57,13 @@ This repo is maintained by [Corey E. Baker](https://github.com/cbaker6), [1 of 2 You can use The Swift Package Manager (SPM) to install ParseSwift by adding the following description to your `Package.swift` file: ```swift -// swift-tools-version:5.5 +// swift-tools-version:5.5.2 import PackageDescription let package = Package( name: "YOUR_PROJECT_NAME", dependencies: [ - .package(url: "https://github.com/netreconlab/Parse-Swift", .upToNextMajor(from: "5.1.0")), + .package(url: "https://github.com/netreconlab/Parse-Swift", .upToNextMajor(from: "5.1.1")), ] ) ``` @@ -102,9 +102,9 @@ Below is a list of apps and frameworks that use Parse-Swift to help developers t After installing ParseSwift, to use it first `import ParseSwift` in your AppDelegate.swift and then add the following code in your `application:didFinishLaunchingWithOptions:` method: ```swift -ParseSwift.initialize(applicationId: "xxxxxxxxxx", clientKey: "xxxxxxxxxx", serverURL: URL(string: "https://example.com")!) +try await ParseSwift.initialize(applicationId: "xxxxxxxxxx", clientKey: "xxxxxxxxxx", serverURL: URL(string: "https://example.com")!) ``` -Please checkout the [Swift Playground](https://github.com/netreconlab/Parse-Swift/tree/main/ParseSwift.playground) for more usage information. +Please checkout the [Swift Playground](https://github.com/netreconlab/Parse-Swift/tree/main/ParseSwift.playground/Pages) for more usage information. ## LiveQuery @@ -254,18 +254,11 @@ Handling errors is and other events is similar, take a look at the `Subscription You are not limited to a single Live Query Client - you can create multiple instances of `ParseLiveQuery`, use certificate authentication and pinning, receive metrics about each client connection, connect to individual server URLs, and more. -[docs]: https://docs.parseplatform.org [license-link]: LICENSE -## Migration from Parse ObjC SDK +## Migrating from Older Versions and SDKs -An example application that demonstrates how to convert from the Objective-C SDK to the Swift SDK can be found at [ParseMigrateKeychain](https://github.com/netreconlab/ParseMigrateKeychain). Specifically, look at [ContentViewModel.swift](https://github.com/netreconlab/ParseMigrateKeychain/blob/main/ParseMigrateKeychain/ContentViewModel.swift) for code examples. The basic steps are below: +1. See the [discussion](https://github.com/netreconlab/Parse-Swift/discussions/74) to learn how to migrate from Parse-SwiftOG 4.15.0+ to 5.1.1+ +1. See the [discussion](https://github.com/netreconlab/Parse-Swift/discussions/70) to learn how to migrate from the [Parse-Swift](https://github.com/parse-community/Parse-Swift) +1. See the [discussion](https://github.com/netreconlab/Parse-Swift/discussions/71) to learn how to migrate from the [Parse-SDK-iOS-OSX](https://github.com/parse-community/Parse-SDK-iOS-OSX) -1. Add the ParseSwift framework to your project using SPM -1. Remove the Objective-C SDK from your project -1. Add the Objective-C SDK to your project using SPM. You can use this [repo](https://github.com/mman/Parse-SDK-iOS-OSX/tree/spm) as you will only need `PFUser` and `PFInstallation` -1. Convert your app from using the Objective-C SDK to the Swift SDK -1. Create a `User` and `Installation` [model](https://github.com/netreconlab/ParseMigrateKeychain/tree/main/ParseMigrateKeychain/Models) that conform to `ParseObject` -1. Migrate `PFUser->User` using [loginusingobjckeychain](https://swiftpackageindex.com/parse-community/parse-swift/4.14.2/documentation/parseswift/parseuser/loginusingobjckeychain(options:)) -1. Migrate `PFInstallation->Installation` using [become](https://swiftpackageindex.com/parse-community/parse-swift/4.14.2/documentation/parseswift/parseinstallation/become(_:copyentireinstallation:options:)) -1. Remove the Objective-C SDK from your project after you have migrated all users diff --git a/Sources/ParseSwift/API/API+Command.swift b/Sources/ParseSwift/API/API+Command.swift index 63e2c22d8..11498fab5 100644 --- a/Sources/ParseSwift/API/API+Command.swift +++ b/Sources/ParseSwift/API/API+Command.swift @@ -256,59 +256,70 @@ internal extension API { completion: @escaping(Result) -> Void) { let params = self.params?.getURLQueryItems() Task { - var headers = await API.getHeaders(options: options) - if method == .GET || method == .DELETE { - headers.removeValue(forKey: "X-Parse-Request-Id") - } - let url = parseURL == nil ? - API.serverURL(options: options).appendingPathComponent(path.urlComponent) : parseURL! + do { + var headers = try await API.getHeaders(options: options) + if method == .GET || method == .DELETE { + headers.removeValue(forKey: "X-Parse-Request-Id") + } + let url = parseURL == nil ? + API.serverURL(options: options).appendingPathComponent(path.urlComponent) : parseURL! - guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { - let error = ParseError(code: .otherCause, - message: "Could not unrwrap url components for \(url)") - completion(.failure(error)) - return - } - components.queryItems = params + guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + let error = ParseError(code: .otherCause, + message: "Could not unrwrap url components for \(url)") + completion(.failure(error)) + return + } + components.queryItems = params - guard let urlComponents = components.url else { - let error = ParseError(code: .otherCause, - message: "Could not create url from components for \(components)") - completion(.failure(error)) - return - } + guard let urlComponents = components.url else { + let error = ParseError(code: .otherCause, + message: "Could not create url from components for \(components)") + completion(.failure(error)) + return + } - var urlRequest = URLRequest(url: urlComponents) - urlRequest.allHTTPHeaderFields = headers - if let urlBody = body { - if (urlBody as? ParseCloudTypeable) != nil { - guard let bodyData = try? ParseCoding.parseEncoder().encode(urlBody, skipKeys: .cloud) else { - let error = ParseError(code: .otherCause, - message: "Could not encode body \(urlBody)") - completion(.failure(error)) - return - } - urlRequest.httpBody = bodyData - } else { - guard let bodyData = try? ParseCoding - .parseEncoder() - .encode(urlBody, - batching: batching, - collectChildren: false, - objectsSavedBeforeThisOne: childObjects, - filesSavedBeforeThisOne: childFiles) else { - let error = ParseError(code: .otherCause, - message: "Could not encode body \(urlBody)") - completion(.failure(error)) - return + var urlRequest = URLRequest(url: urlComponents) + urlRequest.allHTTPHeaderFields = headers + if let urlBody = body { + if (urlBody as? ParseCloudTypeable) != nil { + do { + let bodyData = try ParseCoding.parseEncoder().encode(urlBody, + skipKeys: .cloud) + urlRequest.httpBody = bodyData + } catch { + let defaultError = ParseError(code: .otherCause, + message: "Could not encode body \(urlBody)", + swift: error) + let parseError = error as? ParseError ?? defaultError + completion(.failure(parseError)) + return + } + } else { + guard let bodyData = try? ParseCoding + .parseEncoder() + .encode(urlBody, + batching: batching, + collectChildren: false, + objectsSavedBeforeThisOne: childObjects, + filesSavedBeforeThisOne: childFiles) else { + let error = ParseError(code: .otherCause, + message: "Could not encode body \(urlBody)") + completion(.failure(error)) + return + } + urlRequest.httpBody = bodyData.encoded } - urlRequest.httpBody = bodyData.encoded } + urlRequest.httpMethod = method.rawValue + urlRequest.cachePolicy = requestCachePolicy(options: options) + completion(.success(urlRequest)) + return + } catch { + let parseError = error as? ParseError ?? ParseError(swift: error) + completion(.failure(parseError)) + return } - urlRequest.httpMethod = method.rawValue - urlRequest.cachePolicy = requestCachePolicy(options: options) - completion(.success(urlRequest)) - return } } @@ -527,23 +538,19 @@ internal extension API.Command where T: ParseObject { method: object.element.method) return .success(updatedObject) } catch { - guard let parseError = error as? ParseError else { - return .failure(ParseError(swift: error)) - } + let parseError = error as? ParseError ?? ParseError(swift: error) return .failure(parseError) } } else { - guard let parseError = response.error else { - return .failure(ParseError(code: .otherCause, message: "unknown error")) - } - + let parseError = response.error ?? ParseError(code: .otherCause, + message: "Unknown error") return .failure(parseError) } }) } catch { - guard let parseError = error as? ParseError else { - return [(.failure(ParseError(code: .otherCause, message: "decoding error: \(error)")))] - } + let parseError = error as? ParseError ?? ParseError(code: .otherCause, + message: "Decoding error", + swift: error) return [(.failure(parseError))] } } @@ -572,17 +579,15 @@ internal extension API.Command where T: ParseObject { if response.success != nil { return .success(()) } else { - guard let parseError = response.error else { - return .failure(ParseError(code: .otherCause, message: "unknown error")) - } - + let parseError = response.error ?? ParseError(code: .otherCause, + message: "Unknown error") return .failure(parseError) } }) } catch { - guard let parseError = error as? ParseError else { - return [(.failure(ParseError(code: .otherCause, message: "decoding error: \(error)")))] - } + let parseError = error as? ParseError ?? ParseError(code: .otherCause, + message: "Decoding error", + swift: error) return [(.failure(parseError))] } } diff --git a/Sources/ParseSwift/API/API+NonParseBodyCommand.swift b/Sources/ParseSwift/API/API+NonParseBodyCommand.swift index 04705e71a..c1a77421e 100644 --- a/Sources/ParseSwift/API/API+NonParseBodyCommand.swift +++ b/Sources/ParseSwift/API/API+NonParseBodyCommand.swift @@ -90,35 +90,43 @@ internal extension API { // MARK: URL Preperation func prepareURLRequest(options: API.Options) async -> Result { let params = self.params?.getURLQueryItems() - var headers = await API.getHeaders(options: options) - if method == .GET || method == .DELETE { - headers.removeValue(forKey: "X-Parse-Request-Id") - } - let url = API.serverURL(options: options).appendingPathComponent(path.urlComponent) - - guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { - return .failure(ParseError(code: .otherCause, - message: "Could not unrwrap url components for \(url)")) - } - components.queryItems = params + do { + var headers = try await API.getHeaders(options: options) + if method == .GET || method == .DELETE { + headers.removeValue(forKey: "X-Parse-Request-Id") + } + let url = API.serverURL(options: options).appendingPathComponent(path.urlComponent) - guard let urlComponents = components.url else { - return .failure(ParseError(code: .otherCause, - message: "Could not create url from components for \(components)")) - } + guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + return .failure(ParseError(code: .otherCause, + message: "Could not unrwrap url components for \(url)")) + } + components.queryItems = params - var urlRequest = URLRequest(url: urlComponents) - urlRequest.allHTTPHeaderFields = headers - if let urlBody = body { - guard let bodyData = try? ParseCoding.jsonEncoder().encode(urlBody) else { + guard let urlComponents = components.url else { return .failure(ParseError(code: .otherCause, - message: "Could not encode body \(urlBody)")) + message: "Could not create url from components for \(components)")) + } + + var urlRequest = URLRequest(url: urlComponents) + urlRequest.allHTTPHeaderFields = headers + if let urlBody = body { + do { + let bodyData = try ParseCoding.jsonEncoder().encode(urlBody) + urlRequest.httpBody = bodyData + } catch { + return .failure(ParseError(code: .otherCause, + message: "Could not encode body \(urlBody)", + swift: error)) + } } - urlRequest.httpBody = bodyData + urlRequest.httpMethod = method.rawValue + urlRequest.cachePolicy = requestCachePolicy(options: options) + return .success(urlRequest) + } catch { + let parseError = error as? ParseError ?? ParseError(swift: error) + return .failure(parseError) } - urlRequest.httpMethod = method.rawValue - urlRequest.cachePolicy = requestCachePolicy(options: options) - return .success(urlRequest) } enum CodingKeys: String, CodingKey { // swiftlint:disable:this nesting @@ -199,20 +207,19 @@ internal extension API.NonParseBodyCommand { let response = responses[object.offset] if let success = response.success { guard let successfulResponse = try? object.element.mapper(success) else { - return.failure(ParseError(code: .otherCause, message: "unknown error")) + return .failure(ParseError(code: .otherCause, message: "Unknown error")) } return .success(successfulResponse) } else { - guard let parseError = response.error else { - return .failure(ParseError(code: .otherCause, message: "unknown error")) - } + let parseError = response.error ?? ParseError(code: .otherCause, + message: "Unknown error") return .failure(parseError) } }) } catch { - guard let parseError = error as? ParseError else { - return [(.failure(ParseError(code: .otherCause, message: "decoding error: \(error)")))] - } + let parseError = error as? ParseError ?? ParseError(code: .otherCause, + message: "Decoding error", + swift: error) return [(.failure(parseError))] } } diff --git a/Sources/ParseSwift/API/API.swift b/Sources/ParseSwift/API/API.swift index 295413188..47451b9a6 100644 --- a/Sources/ParseSwift/API/API.swift +++ b/Sources/ParseSwift/API/API.swift @@ -203,7 +203,8 @@ public struct API { } // swiftlint:disable:next cyclomatic_complexity - internal static func getHeaders(options: API.Options) async -> [String: String] { + internal static func getHeaders(options: API.Options) async throws -> [String: String] { + try await yieldIfNotInitialized() var headers: [String: String] = ["X-Parse-Application-Id": Parse.configuration.applicationId, "Content-Type": "application/json"] if let clientKey = Parse.configuration.clientKey { @@ -253,7 +254,6 @@ public struct API { break } } - return headers } diff --git a/Sources/ParseSwift/Documentation.docc/ParseSwift.md b/Sources/ParseSwift/Documentation.docc/ParseSwift.md index a8b207ff2..d4145be3f 100644 --- a/Sources/ParseSwift/Documentation.docc/ParseSwift.md +++ b/Sources/ParseSwift/Documentation.docc/ParseSwift.md @@ -9,7 +9,7 @@ For more information about the Parse Platform and its features, see the public [ To learn how to use or experiment with ParseSwift, you can run and edit the [ParseSwift.playground](https://github.com/netreconlab/Parse-Swift/tree/main/ParseSwift.playground/Pages). You can use the parse-server in [this repo](https://github.com/netreconlab/parse-hipaa/tree/parse-swift) which has docker compose files (`docker-compose up` gives you a working server) configured to connect with the playground files, has [Parse Dashboard](https://github.com/parse-community/parse-dashboard), and can be used with MongoDB or PostgreSQL. You can also configure the Swift Playgrounds to work with your own Parse Server by editing the configuation in [Common.swift](https://github.com/netreconlab/Parse-Swift/blob/e9ba846c399257100b285d25d2bd055628b13b4b/ParseSwift.playground/Sources/Common.swift#L4-L19). To learn more, check out [CONTRIBUTING.md](https://github.com/netreconlab/Parse-Swift/blob/main/CONTRIBUTING.md#swift-playgrounds). ## Why use Parse-Swift from NetReconLab? -This repo is maintained by [Corey E. Baker](https://github.com/cbaker6), [1 of 2 of the original developers of Parse-Swift](https://github.com/parse-community/Parse-Swift/graphs/contributors). Corey was responsible for the direction and development of all releases from [1.0.0](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2) to [4.14.2](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2). This repo will remain aligned with the original core principals of a swifty framework that contains zero dependencies and takes advantage of all of the features the [parse-server](https://github.com/parse-community/parse-server) has to offer. The reason for the bifurcation from the parse-community version of Parse-Swift is due to interference and a number of disagreements with the [Parse Project Management Committee (PMC)](https://github.com/parse-community/Governance/blob/main/TEAM.md#project-management-committee-pmc) about the future of ParseSwift along with the PMC member ignoring code reviews and comments, marking relevant comments as `off-topic`, merging commits directly to main branch, lack-of-understanding of Swift fundamentals, client-side development, and lack of knowledge about how the Parse-Swift framework is designed. It is important to emphasize that no member of the Parse PMC participated in the design, release, and direction of Parse-Swift from its [first commit](https://github.com/parse-community/Parse-Swift/tree/cf69b7f0638819a7070b82228bc51a97df5757e6) to version [4.14.2](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2). The Parse PMC also does not properly give attribution or show appreciation to Corey as the lead developer of Parse-Swift. In addition, no funding or support from the [parse-community funds](https://opencollective.com/parse-server) has ever been offered to Corey for any of his [contributions to Parse](https://github.com/search?q=user%3Aparse-community+author%3Acbaker6&type=issues), though a [number of funds have been offered and made to other contributors](https://opencollective.com/parse-server#category-BUDGET). +This repo is maintained by [Corey E. Baker](https://github.com/cbaker6), [1 of 2 of the original developers of Parse-Swift](https://github.com/parse-community/Parse-Swift/graphs/contributors). Corey was responsible for the direction and development of all releases from [1.0.0](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2) to [4.14.2](https://github.com/parse-community/Parse-Swift/releases/tag/4.14.2). This repo will remain aligned with the original core principals of a swifty framework that contains zero dependencies and takes advantage of all of the features the [parse-server](https://github.com/parse-community/parse-server) has to offer. You can learn more about why I left the parse-community and created my own repo [here](https://github.com/netreconlab/Parse-Swift/discussions/7). ## Topics diff --git a/Sources/ParseSwift/Extensions/URLSession.swift b/Sources/ParseSwift/Extensions/URLSession.swift index 1dda4bba0..8a78b384e 100644 --- a/Sources/ParseSwift/Extensions/URLSession.swift +++ b/Sources/ParseSwift/Extensions/URLSession.swift @@ -42,17 +42,13 @@ internal extension URLSession { responseError: Error?, mapper: @escaping (Data) async throws -> U) async -> Result { if let responseError = responseError { - guard let parseError = responseError as? ParseError else { - return .failure(ParseError(message: "Unable to connect with parse-server", - swift: responseError)) - } + let parseError = responseError as? ParseError ?? ParseError(message: "Unable to connect with parse-server", + swift: responseError) return .failure(parseError) } guard let response = urlResponse else { - guard let parseError = responseError as? ParseError else { - return .failure(ParseError(code: .otherCause, - message: "No response from server")) - } + let parseError = responseError as? ParseError ?? ParseError(code: .otherCause, + message: "No response from server") return .failure(parseError) } if var responseData = responseData { @@ -112,17 +108,14 @@ internal extension URLSession { responseError: Error?, mapper: @escaping (Data) async throws -> U) async -> Result { guard let response = urlResponse else { - guard let parseError = responseError as? ParseError else { - return .failure(ParseError(code: .otherCause, - message: "No response from server")) - } + let parseError = responseError as? ParseError ?? ParseError(code: .otherCause, + message: "No response from server") return .failure(parseError) } if let responseError = responseError { - guard let parseError = responseError as? ParseError else { - return .failure(ParseError(message: "Unable to connect with parse-server", - swift: responseError)) - } + let defaultError = ParseError(message: "Unable to connect with parse-server", + swift: responseError) + let parseError = responseError as? ParseError ?? defaultError return .failure(parseError) } diff --git a/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift b/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift index 84c353914..2ad7cb034 100644 --- a/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift +++ b/Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift @@ -193,7 +193,7 @@ Not attempting to open ParseLiveQuery socket anymore return } isConfiguring = true - await yieldIfNotInitialized() + try await yieldIfNotInitialized() Self.defaultClient = try await Self(isDefault: true) } diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index 4bb9e2a7d..ae04e8953 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -286,7 +286,7 @@ public extension ParseInstallation { - throws: An error of `ParseError` type. */ static func current() async throws -> Self { - await yieldIfNotInitialized() + try await yieldIfNotInitialized() guard let installation = await Self.currentContainer().currentInstallation else { throw ParseError(code: .otherCause, message: "There is no current Installation") diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index f0a1c585c..b191f28f2 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -163,7 +163,7 @@ public extension ParseUser { - throws: An error of `ParseError` type. */ static func current() async throws -> Self { - await yieldIfNotInitialized() + try await yieldIfNotInitialized() guard let container = await Self.currentContainer(), let user = container.currentUser else { throw ParseError(code: .otherCause, diff --git a/Sources/ParseSwift/Parse.swift b/Sources/ParseSwift/Parse.swift index 69f1c3c66..bb43d2395 100644 --- a/Sources/ParseSwift/Parse.swift +++ b/Sources/ParseSwift/Parse.swift @@ -61,10 +61,15 @@ internal func initialize(applicationId: String, try await initialize(configuration: configuration) } -internal func yieldIfNotInitialized() async { +internal func yieldIfNotInitialized(_ iteration: Int = 0) async throws { guard ParseConfiguration.checkIfConfigured() else { - await Task.yield() - await yieldIfNotInitialized() + guard iteration < 5 else { + throw ParseError(code: .otherCause, + message: "The SDK needs to be initialized") + } + let nanoSeconds = UInt64(1 * 1_000_000_000) + try await Task.sleep(nanoseconds: nanoSeconds) + try await yieldIfNotInitialized(iteration + 1) return } } @@ -158,21 +163,7 @@ public func initialize(configuration: ParseConfiguration) async throws { // swif try await ParseVersion.setCurrent(try ParseVersion(string: ParseConstants.version)) } - // Migrate installations with installationId, but missing - // currentInstallation, ParseSwift < 1.9.10 let currentInstallationContainer = await BaseParseInstallation.currentContainer() - if let installationId = currentInstallationContainer.installationId, - currentInstallationContainer.currentInstallation == nil { - if let foundInstallation = try? await BaseParseInstallation - .query("installationId" == installationId) - .first(options: [.cachePolicy(.reloadIgnoringLocalCacheData)]) { - let newContainer = CurrentInstallationContainer(currentInstallation: foundInstallation, - installationId: installationId) - await BaseParseInstallation.setCurrentContainer(newContainer) - } - } - await BaseParseInstallation.createNewInstallationIfNeeded() - #if !os(Linux) && !os(Android) && !os(Windows) if configuration.isMigratingFromObjcSDK { await KeychainStore.createObjectiveC() @@ -189,7 +180,24 @@ public func initialize(configuration: ParseConfiguration) async throws { // swif } } #endif - Parse.configuration.isInitialized = true + + // Migrate installations with installationId, but missing + // currentInstallation, ParseSwift < 1.9.10 + if let installationId = currentInstallationContainer.installationId, + currentInstallationContainer.currentInstallation == nil { + Parse.configuration.isInitialized = true + if let foundInstallation = try? await BaseParseInstallation + .query("installationId" == installationId) + .first(options: [.cachePolicy(.reloadIgnoringLocalCacheData)]) { + let newContainer = CurrentInstallationContainer(currentInstallation: foundInstallation, + installationId: installationId) + await BaseParseInstallation.setCurrentContainer(newContainer) + } + } + await BaseParseInstallation.createNewInstallationIfNeeded() + if !Parse.configuration.isInitialized { + Parse.configuration.isInitialized = true + } } /** @@ -347,7 +355,7 @@ public func deleteObjectiveCKeychain() async throws { throw ParseError(code: .otherCause, message: "\"accessGroup\" must be set to a valid string when \"synchronizeAcrossDevices == true\"") } - await yieldIfNotInitialized() + try await yieldIfNotInitialized() guard let currentAccessGroup = try? await ParseKeychainAccessGroup.current() else { throw ParseError(code: .otherCause, message: "Problem unwrapping the current access group. Did you initialize the SDK before calling this method?") diff --git a/Sources/ParseSwift/ParseConstants.swift b/Sources/ParseSwift/ParseConstants.swift index 3b1bbcbf3..551ba079b 100644 --- a/Sources/ParseSwift/ParseConstants.swift +++ b/Sources/ParseSwift/ParseConstants.swift @@ -10,7 +10,7 @@ import Foundation enum ParseConstants { static let sdk = "swift" - static let version = "5.1.0" + static let version = "5.1.1" static let fileManagementDirectory = "parse/" static let fileManagementPrivateDocumentsDirectory = "Private Documents/" static let fileManagementLibraryDirectory = "Library/" diff --git a/Sources/ParseSwift/Protocols/ParseConfig.swift b/Sources/ParseSwift/Protocols/ParseConfig.swift index 3aaf89a46..82a39920e 100644 --- a/Sources/ParseSwift/Protocols/ParseConfig.swift +++ b/Sources/ParseSwift/Protocols/ParseConfig.swift @@ -103,10 +103,25 @@ struct CurrentConfigContainer: Codable, Equatable { var currentConfig: T? } -public extension ParseConfig { +extension ParseConfig { + + /** + Gets/Sets properties of the current config in the Keychain. + + - returns: Returns the latest `ParseConfig` on this device. If there is none, throws an error. + - throws: An error of `ParseError` type. + */ + public static func current() async throws -> Self { + try await yieldIfNotInitialized() + guard let container = await Self.currentContainer(), + let config = container.currentConfig else { + throw ParseError(code: .otherCause, + message: "There is no current Config") + } + return config + } - internal static func currentContainer() async -> CurrentConfigContainer? { - await yieldIfNotInitialized() + static func currentContainer() async -> CurrentConfigContainer? { guard let configInMemory: CurrentConfigContainer = try? await ParseStorage.shared.get(valueFor: ParseStorage.Keys.currentConfig) else { #if !os(Linux) && !os(Android) && !os(Windows) @@ -118,14 +133,14 @@ public extension ParseConfig { return configInMemory } - internal static func setCurrentContainer(_ newValue: CurrentConfigContainer?) async { + static func setCurrentContainer(_ newValue: CurrentConfigContainer?) async { try? await ParseStorage.shared.set(newValue, for: ParseStorage.Keys.currentConfig) #if !os(Linux) && !os(Android) && !os(Windows) try? await KeychainStore.shared.set(newValue, for: ParseStorage.Keys.currentConfig) #endif } - internal static func updateStorageIfNeeded(_ result: Self, deleting: Bool = false) async { + static func updateStorageIfNeeded(_ result: Self, deleting: Bool = false) async { if !deleting { await Self.setCurrent(result) } else { @@ -133,29 +148,14 @@ public extension ParseConfig { } } - internal static func deleteCurrentContainerFromStorage() async { + static func deleteCurrentContainerFromStorage() async { try? await ParseStorage.shared.delete(valueFor: ParseStorage.Keys.currentConfig) #if !os(Linux) && !os(Android) && !os(Windows) try? await KeychainStore.shared.delete(valueFor: ParseStorage.Keys.currentConfig) #endif } - /** - Gets/Sets properties of the current config in the Keychain. - - - returns: Returns the latest `ParseConfig` on this device. If there is none, throws an error. - - throws: An error of `ParseError` type. - */ - static func current() async throws -> Self { - guard let container = await Self.currentContainer(), - let config = container.currentConfig else { - throw ParseError(code: .otherCause, - message: "There is no current Config") - } - return config - } - - internal static func setCurrent(_ current: Self?) async { + static func setCurrent(_ current: Self?) async { if await Self.currentContainer() == nil { await Self.setCurrentContainer(CurrentConfigContainer()) } diff --git a/Sources/ParseSwift/Types/ParseConfigCodable.swift b/Sources/ParseSwift/Types/ParseConfigCodable.swift index b97cbca3a..b521f7a2c 100644 --- a/Sources/ParseSwift/Types/ParseConfigCodable.swift +++ b/Sources/ParseSwift/Types/ParseConfigCodable.swift @@ -110,6 +110,7 @@ extension ParseConfigCodable { - throws: An error of `ParseError` type. */ public static func current() async throws -> [String: V] { + try await yieldIfNotInitialized() guard let container = await Self.currentContainer(), let config = container.currentConfig else { throw ParseError(code: .otherCause, @@ -119,7 +120,6 @@ extension ParseConfigCodable { } static func currentContainer() async -> CurrentConfigDictionaryContainer? { - await yieldIfNotInitialized() guard let configInMemory: CurrentConfigDictionaryContainer = try? await ParseStorage.shared.get(valueFor: ParseStorage.Keys.currentConfig) else { #if !os(Linux) && !os(Android) && !os(Windows) diff --git a/Tests/ParseSwiftTests/APICommandTests.swift b/Tests/ParseSwiftTests/APICommandTests.swift index 2eaa126c0..063361211 100644 --- a/Tests/ParseSwiftTests/APICommandTests.swift +++ b/Tests/ParseSwiftTests/APICommandTests.swift @@ -336,8 +336,8 @@ class APICommandTests: XCTestCase { } } - func testApplicationIdHeader() async { - let headers = await API.getHeaders(options: []) + func testApplicationIdHeader() async throws { + let headers = try await API.getHeaders(options: []) XCTAssertEqual(headers["X-Parse-Application-Id"], ParseSwift.configuration.applicationId) let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in @@ -359,7 +359,7 @@ class APICommandTests: XCTestCase { throw ParseError(code: .otherCause, message: "Parse configuration should contain key") } - let headers = await API.getHeaders(options: []) + let headers = try await API.getHeaders(options: []) XCTAssertEqual(headers["X-Parse-Client-Key"], clientKey) let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in @@ -381,7 +381,7 @@ class APICommandTests: XCTestCase { throw ParseError(code: .otherCause, message: "Parse configuration should contain key") } - let headers = await API.getHeaders(options: []) + let headers = try await API.getHeaders(options: []) XCTAssertNil(headers["X-Parse-Master-Key"]) let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in @@ -406,7 +406,7 @@ class APICommandTests: XCTestCase { throw ParseError(code: .otherCause, message: "Parse current user should have session token") } - let headers = await API.getHeaders(options: []) + let headers = try await API.getHeaders(options: []) XCTAssertEqual(headers["X-Parse-Session-Token"], sessionToken) let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in @@ -444,7 +444,7 @@ class APICommandTests: XCTestCase { throw ParseError(code: .otherCause, message: "Parse current user should have session token") } - let headers = await API.getHeaders(options: []) + let headers = try await API.getHeaders(options: []) XCTAssertEqual(headers["X-Parse-Installation-Id"], installationId) let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in @@ -477,7 +477,7 @@ class APICommandTests: XCTestCase { } func testContentHeader() async throws { - let headers = await API.getHeaders(options: []) + let headers = try await API.getHeaders(options: []) XCTAssertEqual(headers["Content-Type"], "application/json") let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in @@ -495,7 +495,7 @@ class APICommandTests: XCTestCase { } func testReplaceContentHeader() async throws { - let headers = await API.getHeaders(options: []) + let headers = try await API.getHeaders(options: []) XCTAssertEqual(headers["Content-Type"], "application/json") let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in @@ -528,7 +528,7 @@ class APICommandTests: XCTestCase { } func testRemoveContentHeader() async throws { - let headers = await API.getHeaders(options: []) + let headers = try await API.getHeaders(options: []) XCTAssertEqual(headers["Content-Type"], "application/json") let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in @@ -565,7 +565,7 @@ class APICommandTests: XCTestCase { // swiftlint:disable:next function_body_length cyclomatic_complexity func testClientVersionHeader() async throws { - let headers = await API.getHeaders(options: []) + let headers = try await API.getHeaders(options: []) XCTAssertEqual(headers["X-Parse-Client-Version"], API.clientVersion()) let post = API.Command(method: .POST, path: .login) { _ in @@ -636,7 +636,7 @@ class APICommandTests: XCTestCase { // swiftlint:disable:next function_body_length cyclomatic_complexity func testIdempodency() async throws { - let headers = await API.getHeaders(options: []) + let headers = try await API.getHeaders(options: []) XCTAssertNotNil(headers["X-Parse-Request-Id"]) let post = API.Command(method: .POST, path: .login) { _ in @@ -707,7 +707,7 @@ class APICommandTests: XCTestCase { // swiftlint:disable:next function_body_length cyclomatic_complexity func testIdempodencyNoParseBody() async throws { - let headers = await API.getHeaders(options: []) + let headers = try await API.getHeaders(options: []) XCTAssertNotNil(headers["X-Parse-Request-Id"]) let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in @@ -805,7 +805,7 @@ class APICommandTests: XCTestCase { } func testContextHeader() async throws { - let headers = await API.getHeaders(options: []) + let headers = try await API.getHeaders(options: []) XCTAssertNil(headers["X-Parse-Cloud-Context"]) let post = API.NonParseBodyCommand(method: .POST, path: .login) { _ in