From 0f9005eca00295be5908a692b3ef4d8ecf483267 Mon Sep 17 00:00:00 2001 From: Victor Barros Date: Tue, 27 Jun 2017 10:16:45 -0700 Subject: [PATCH 01/11] MLIBZ-1936: removing support for tvOS for now --- Kinvey.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Kinvey.podspec b/Kinvey.podspec index e39f79062..d7bfaa58e 100644 --- a/Kinvey.podspec +++ b/Kinvey.podspec @@ -70,7 +70,7 @@ Pod::Spec.new do |s| s.ios.deployment_target = "9.0" s.osx.deployment_target = "10.12" s.watchos.deployment_target = "2.2" - s.tvos.deployment_target = "9.2" + # s.tvos.deployment_target = "9.2" # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # From c0ba5e8517d787bd0b5a371bb955b28fb557ca66 Mon Sep 17 00:00:00 2001 From: Victor Barros Date: Wed, 5 Jul 2017 16:17:43 -0700 Subject: [PATCH 02/11] MLIBZ-1731: delta set count and completion handler --- Kinvey/Kinvey/DataStore.swift | 4 ++-- Kinvey/Kinvey/FindOperation.swift | 11 ++++++++--- Kinvey/Kinvey/PullOperation.swift | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Kinvey/Kinvey/DataStore.swift b/Kinvey/Kinvey/DataStore.swift index d345d9af2..0e9fd2584 100644 --- a/Kinvey/Kinvey/DataStore.swift +++ b/Kinvey/Kinvey/DataStore.swift @@ -744,7 +744,7 @@ open class DataStore where T: NSObject { /// Gets the records from the backend that matches with the query passed by parameter and saves locally in the local cache. @discardableResult - open func pull(_ query: Query = Query(), deltaSet: Bool? = nil, completionHandler: ((Result<[T], Swift.Error>) -> Void)? = nil) -> Request { + open func pull(_ query: Query = Query(), deltaSet: Bool? = nil, deltaSetCompletionHandler: (([T]) -> Void)? = nil, completionHandler: ((Result<[T], Swift.Error>) -> Void)? = nil) -> Request { var request: Request! Promise<[T]> { fulfill, reject in if type == .network { @@ -755,7 +755,7 @@ open class DataStore where T: NSObject { reject(Error.invalidOperation(description: "You must push all pending sync items before new data is pulled. Call push() on the data store instance to push pending items, or purge() to remove them.")) } else { let deltaSet = deltaSet ?? self.deltaSet - let operation = PullOperation(query: Query(query: query, persistableType: T.self), deltaSet: deltaSet, readPolicy: .forceNetwork, cache: cache, client: client) + let operation = PullOperation(query: Query(query: query, persistableType: T.self), deltaSet: deltaSet, deltaSetCompletionHandler: deltaSetCompletionHandler, readPolicy: .forceNetwork, cache: cache, client: client) request = operation.execute { result in switch result { case .success(let array): diff --git a/Kinvey/Kinvey/FindOperation.swift b/Kinvey/Kinvey/FindOperation.swift index 4ad3b75a1..bc9ee0b19 100644 --- a/Kinvey/Kinvey/FindOperation.swift +++ b/Kinvey/Kinvey/FindOperation.swift @@ -15,6 +15,7 @@ internal class FindOperation: ReadOperation let query: Query let deltaSet: Bool + let deltaSetCompletionHandler: (([T]) -> Void)? lazy var isEmptyQuery: Bool = { return (self.query.predicate == nil || self.query.predicate == NSPredicate()) && self.query.skip == nil && self.query.limit == nil @@ -27,9 +28,10 @@ internal class FindOperation: ReadOperation typealias ResultsHandler = ([JsonDictionary]) -> Void let resultsHandler: ResultsHandler? - init(query: Query, deltaSet: Bool, readPolicy: ReadPolicy, cache: AnyCache?, client: Client, resultsHandler: ResultsHandler? = nil) { + init(query: Query, deltaSet: Bool, deltaSetCompletionHandler: (([T]) -> Void)? = nil, readPolicy: ReadPolicy, cache: AnyCache?, client: Client, resultsHandler: ResultsHandler? = nil) { self.query = query self.deltaSet = deltaSet + self.deltaSetCompletionHandler = deltaSetCompletionHandler self.resultsHandler = resultsHandler super.init(readPolicy: readPolicy, cache: cache, client: client) } @@ -69,12 +71,12 @@ internal class FindOperation: ReadOperation allIds.formUnion(deltaSet.deleted) if allIds.count > MaxIdsPerQuery { let allIds = Array(allIds) - var promises = [Promise<[AnyObject]>]() + var promises = [Promise<[T]>]() var newRefObjs = [String : String]() for offset in stride(from: 0, to: allIds.count, by: MaxIdsPerQuery) { let limit = min(offset + MaxIdsPerQuery, allIds.count - 1) let allIds = Set(allIds[offset...limit]) - let promise = Promise<[AnyObject]> { fulfill, reject in + let promise = Promise<[T]> { fulfill, reject in let query = Query(format: "\(Entity.Key.entityId) IN %@", allIds) let operation = FindOperation(query: query, deltaSet: false, readPolicy: .forceNetwork, cache: cache, client: self.client) { jsonArray in for (key, value) in self.reduceToIdsLmts(jsonArray) { @@ -96,6 +98,9 @@ internal class FindOperation: ReadOperation if self.mustRemoveCachedRecords { self.removeCachedRecords(cache, keys: refObjs.keys, deleted: deltaSet.deleted) } + if let deltaSetCompletionHandler = self.deltaSetCompletionHandler { + deltaSetCompletionHandler(results.flatMap { $0 }) + } self.executeLocal(completionHandler) }.catch { error in completionHandler?(.failure(error)) diff --git a/Kinvey/Kinvey/PullOperation.swift b/Kinvey/Kinvey/PullOperation.swift index b384c3a49..5a1640880 100644 --- a/Kinvey/Kinvey/PullOperation.swift +++ b/Kinvey/Kinvey/PullOperation.swift @@ -10,8 +10,8 @@ import Foundation internal class PullOperation: FindOperation where T: NSObject { - override init(query: Query, deltaSet: Bool, readPolicy: ReadPolicy, cache: AnyCache?, client: Client, resultsHandler: ResultsHandler? = nil) { - super.init(query: query, deltaSet: deltaSet, readPolicy: readPolicy, cache: cache, client: client, resultsHandler: resultsHandler) + override init(query: Query, deltaSet: Bool, deltaSetCompletionHandler: (([T]) -> Void)?, readPolicy: ReadPolicy, cache: AnyCache?, client: Client, resultsHandler: ResultsHandler? = nil) { + super.init(query: query, deltaSet: deltaSet, deltaSetCompletionHandler: deltaSetCompletionHandler, readPolicy: readPolicy, cache: cache, client: client, resultsHandler: resultsHandler) } override var mustRemoveCachedRecords: Bool { From 2d9adbcfd9cc2183e055dca54d16e40c7d7c2cc3 Mon Sep 17 00:00:00 2001 From: Victor Barros Date: Fri, 7 Jul 2017 11:32:33 -0700 Subject: [PATCH 03/11] MLIBZ-1951: crashing with empty body error --- Kinvey/Kinvey/Error.swift | 42 +++++++++++++++--- Kinvey/Kinvey/Kinvey.swift | 71 ++++++++++++++++++++++++++----- Kinvey/Kinvey/PushOperation.swift | 11 ++++- 3 files changed, 105 insertions(+), 19 deletions(-) diff --git a/Kinvey/Kinvey/Error.swift b/Kinvey/Kinvey/Error.swift index dcbbba8c3..5b26a7186 100644 --- a/Kinvey/Kinvey/Error.swift +++ b/Kinvey/Kinvey/Error.swift @@ -179,16 +179,46 @@ public enum Error: Swift.Error, LocalizedError, CustomStringConvertible, CustomD return unknownJsonError(httpResponse: httpResponse, data: data, json: json) } - static func buildDataLinkEntityNotFound(httpResponse: HTTPURLResponse?, data: Data?, json: [String : String]) -> Error { - return dataLinkEntityNotFound(httpResponse: httpResponse, data: data, debug: json["debug"]!, description: json["description"]!) + static func buildDataLinkEntityNotFound( + httpResponse: HTTPURLResponse?, + data: Data?, + debug: String, + description: String + ) -> Error { + return dataLinkEntityNotFound( + httpResponse: httpResponse, + data: data, + debug: debug, + description: description + ) } - static func buildMethodNotAllowed(httpResponse: HTTPURLResponse?, data: Data?, json: [String : String]) -> Error { - return methodNotAllowed(httpResponse: httpResponse, data: data, debug: json["debug"]!, description: json["description"]!) + static func buildMethodNotAllowed( + httpResponse: HTTPURLResponse?, + data: Data?, + debug: String, + description: String + ) -> Error { + return methodNotAllowed( + httpResponse: httpResponse, + data: data, + debug: debug, + description: description + ) } - static func buildUnauthorized(httpResponse: HTTPURLResponse?, data: Data?, json: [String : String]) -> Error { - return unauthorized(httpResponse: httpResponse, data: data, error: json["error"]!, description: json["description"]!) + static func buildUnauthorized( + httpResponse: HTTPURLResponse?, + data: Data?, + error: String, + description: String + ) -> Error { + return unauthorized( + httpResponse: httpResponse, + data: data, + error: error, + description: description + ) } } diff --git a/Kinvey/Kinvey/Kinvey.swift b/Kinvey/Kinvey/Kinvey.swift index 2d17b6edb..efc0e3187 100644 --- a/Kinvey/Kinvey/Kinvey.swift +++ b/Kinvey/Kinvey/Kinvey.swift @@ -93,17 +93,52 @@ func buildError(client: Client) -> Swift.Error { return buildError(data: nil, response: nil, error: nil, client: client) } -func buildError(data: Data?, response: Response?, error: Swift.Error?, client: Client) -> Swift.Error { +func buildError( + data: Data?, + response: Response?, + error: Swift.Error?, + client: Client +) -> Swift.Error { if let error = error { return error } else if let response = response, response.isUnauthorized, - let json = client.responseParser.parse(data) as? [String : String] + let json = client.responseParser.parse(data) as? [String : String], + let error = json["error"], + let description = json["description"] + { + return Error.buildUnauthorized( + httpResponse: response.httpResponse, + data: data, + error: error, + description: description + ) + } else if let response = response, + response.isMethodNotAllowed, + let json = client.responseParser.parse(data) as? [String : String], + let error = json["error"], + error == "MethodNotAllowed", + let debug = json["debug"], + let description = json["description"] { - return Error.buildUnauthorized(httpResponse: response.httpResponse, data: data, json: json) - } else if let response = response, response.isMethodNotAllowed, let json = client.responseParser.parse(data) as? [String : String], json["error"] == "MethodNotAllowed" { - return Error.buildMethodNotAllowed(httpResponse: response.httpResponse, data: data, json: json) - } else if let response = response, response.isNotFound, let json = client.responseParser.parse(data) as? [String : String], json["error"] == "DataLinkEntityNotFound" { - return Error.buildDataLinkEntityNotFound(httpResponse: response.httpResponse, data: data, json: json) + return Error.buildMethodNotAllowed( + httpResponse: response.httpResponse, + data: data, + debug: debug, + description: description + ) + } else if let response = response, + response.isNotFound, + let json = client.responseParser.parse(data) as? [String : String], + json["error"] == "DataLinkEntityNotFound", + let debug = json["debug"], + let description = json["description"] + { + return Error.buildDataLinkEntityNotFound( + httpResponse: response.httpResponse, + data: data, + debug: debug, + description: description + ) } else if let response = response, response.isForbidden, let json = client.responseParser.parse(data) as? [String : String], @@ -112,7 +147,12 @@ func buildError(data: Data?, response: Response?, error: Swift.Error?, client: C let debug = json["debug"], let description = json["description"] { - return Error.missingConfiguration(httpResponse: response.httpResponse, data: data, debug: debug, description: description) + return Error.missingConfiguration( + httpResponse: response.httpResponse, + data: data, + debug: debug, + description: description + ) } else if let response = response, response.isNotFound, let json = client.responseParser.parse(data) as? [String : String], @@ -120,8 +160,17 @@ func buildError(data: Data?, response: Response?, error: Swift.Error?, client: C let description = json["description"] { return Error.appNotFound(description: description) - } else if let response = response, let json = client.responseParser.parse(data) { - return Error.buildUnknownJsonError(httpResponse: response.httpResponse, data: data, json: json) + } else if let response = response, + let json = client.responseParser.parse(data) + { + return Error.buildUnknownJsonError( + httpResponse: response.httpResponse, + data: data, + json: json + ) } - return Error.invalidResponse(httpResponse: response?.httpResponse, data: data) + return Error.invalidResponse( + httpResponse: response?.httpResponse, + data: data + ) } diff --git a/Kinvey/Kinvey/PushOperation.swift b/Kinvey/Kinvey/PushOperation.swift index 0ecad70bf..f1cdb4554 100644 --- a/Kinvey/Kinvey/PushOperation.swift +++ b/Kinvey/Kinvey/PushOperation.swift @@ -114,9 +114,16 @@ internal class PushOperation: SyncOperation Date: Fri, 7 Jul 2017 17:02:23 -0700 Subject: [PATCH 04/11] MLIBZ-1933: Increase documentation coverage --- Kinvey/Kinvey/Acl.swift | 1 + Kinvey/Kinvey/AggregateOperation.swift | 12 + Kinvey/Kinvey/Client.swift | 16 + Kinvey/Kinvey/CustomEndpoint.swift | 4 + Kinvey/Kinvey/DataStore.swift | 369 +++++++++++++++++++++--- Kinvey/Kinvey/Entity.swift | 31 ++ Kinvey/Kinvey/File.swift | 6 +- Kinvey/Kinvey/FileStore.swift | 8 + Kinvey/Kinvey/Geolocation.swift | 35 ++- Kinvey/Kinvey/Kinvey.swift | 35 ++- Kinvey/Kinvey/KinveyDateTransform.swift | 5 +- Kinvey/Kinvey/MIC.swift | 5 + Kinvey/Kinvey/Metadata.swift | 26 ++ Kinvey/Kinvey/Persistable.swift | 1 + Kinvey/Kinvey/Realtime.swift | 42 ++- Kinvey/Kinvey/Result.swift | 4 + Kinvey/Kinvey/String.swift | 1 + Kinvey/Kinvey/User.swift | 3 + 18 files changed, 551 insertions(+), 53 deletions(-) diff --git a/Kinvey/Kinvey/Acl.swift b/Kinvey/Kinvey/Acl.swift index 9d87e4b77..ccd0a09b0 100644 --- a/Kinvey/Kinvey/Acl.swift +++ b/Kinvey/Kinvey/Acl.swift @@ -144,6 +144,7 @@ extension Acl: Mappable { extension Acl { + /// Property names for Acl public struct Key { static let creator = "creator" diff --git a/Kinvey/Kinvey/AggregateOperation.swift b/Kinvey/Kinvey/AggregateOperation.swift index 0934acb1e..941047f7a 100644 --- a/Kinvey/Kinvey/AggregateOperation.swift +++ b/Kinvey/Kinvey/AggregateOperation.swift @@ -125,6 +125,10 @@ enum Aggregation { public typealias AggregationCustomResult = (value: T, custom: JsonDictionary) +/** + Protocol that marks all types that are compatible as a Count type such as Int, + Int8, Int16, Int32 and Int64 + */ public protocol CountType {} extension Int: CountType {} extension Int8: CountType {} @@ -134,6 +138,10 @@ extension Int64: CountType {} public typealias AggregationCountResult = (value: T, count: Count) +/** + Protocol that marks all types that are compatible as a Add type such as + NSNumber, Double, Float, Int, Int8, Int16, Int32 and Int64 + */ public protocol AddableType {} extension NSNumber: AddableType {} extension Double: AddableType {} @@ -147,6 +155,10 @@ extension Int64: AddableType {} public typealias AggregationSumResult = (value: T, sum: Sum) public typealias AggregationAvgResult = (value: T, avg: Avg) +/** + Protocol that marks all types that are compatible as a Min type such as + NSNumber, Double, Float, Int, Int8, Int16, Int32, Int64, Date and NSDate + */ public protocol MinMaxType {} extension NSNumber: MinMaxType {} extension Double: MinMaxType {} diff --git a/Kinvey/Kinvey/Client.swift b/Kinvey/Kinvey/Client.swift index 4b974c37e..97b88531b 100644 --- a/Kinvey/Kinvey/Client.swift +++ b/Kinvey/Kinvey/Client.swift @@ -327,6 +327,10 @@ open class Client: Credential { return Client.fileURL(appKey: self.appKey!, tag: tag) } + /** + Check if the `appKey` and `appSecret` properties are correct doing a ping + call to the server. + */ @discardableResult public func ping(completionHandler: @escaping (EnvironmentInfo?, Swift.Error?) -> Void) -> Request { return ping() { (result: Result) in @@ -339,6 +343,10 @@ open class Client: Credential { } } + /** + Check if the `appKey` and `appSecret` properties are correct doing a ping + call to the server. + */ @discardableResult public func ping(completionHandler: @escaping (Result) -> Void) -> Request { guard let _ = appKey, let _ = appSecret else { @@ -371,11 +379,19 @@ open class Client: Credential { } } +/// Environment Information for a specific `appKey` and `appSecret` public struct EnvironmentInfo: StaticMappable { + /// Version of the backend public let version: String + + /// Hello message from Kinvey public let kinvey: String + + /// Application Name public let appName: String + + /// Environment Name public let environmentName: String public static func objectForMapping(map: Map) -> BaseMappable? { diff --git a/Kinvey/Kinvey/CustomEndpoint.swift b/Kinvey/Kinvey/CustomEndpoint.swift index 0a602d867..55f464dc8 100644 --- a/Kinvey/Kinvey/CustomEndpoint.swift +++ b/Kinvey/Kinvey/CustomEndpoint.swift @@ -20,18 +20,22 @@ open class CustomEndpoint { } + /// Parameter Wrapper open class Params { internal let value: ParamsEnum + /// Constructor that takes a JSON Dictionary public init(_ json: JsonDictionary) { value = ParamsEnum.json(json) } + /// Constructor that takes any type that implements Mappable public init(_ object: Mappable) { value = ParamsEnum.object(object) } + /// Constructor that takes any type that implements StaticMappable public init(_ object: StaticMappable) { value = ParamsEnum.object(object) } diff --git a/Kinvey/Kinvey/DataStore.swift b/Kinvey/Kinvey/DataStore.swift index 0e9fd2584..d09ac4d0f 100644 --- a/Kinvey/Kinvey/DataStore.swift +++ b/Kinvey/Kinvey/DataStore.swift @@ -90,6 +90,15 @@ open class DataStore where T: NSObject { /** Deprecated method. Please use `collection()` instead. + - Parameters: + - type: (Optional) Type for the new DataStore instance. Default value: + `.cache` + - deltaSet: (Optional) Enables delta set cache. Default value: `nil` + - client: (Optional) `Client` instance to be used for all requests. + Default value: `sharedClient` + - tag: (Optional) Tag the store and separate stores in different tags. + Default value: `defaultTag` + - Returns: An instance of `DataStore` which can be a new instance or a cached instance if you are passing a `tag` parameter. */ @available(*, deprecated: 3.0.22, message: "Please use `collection()` instead") open class func getInstance(_ type: StoreType = .cache, deltaSet: Bool? = nil, client: Client = sharedClient, tag: String = defaultTag) -> DataStore { @@ -118,8 +127,22 @@ open class DataStore where T: NSObject { return dataStore! } - open func collection(newType: NewType.Type) -> DataStore where NewType: NSObject { - return DataStore(type: type, deltaSet: deltaSet, client: client, fileURL: fileURL, encryptionKey: client.encryptionKey) + /** + Factory method that returns a new instance of a DataStore copying all the + current configuration but for a new type. + - parameter newType: Type for the new DataStore instance + - returns: A new DataStore instance for the type specified + */ + open func collection( + newType: NewType.Type + ) -> DataStore where NewType: NSObject { + return DataStore( + type: type, + deltaSet: deltaSet, + client: client, + fileURL: fileURL, + encryptionKey: client.encryptionKey + ) } fileprivate init(type: StoreType, deltaSet: Bool, client: Client, fileURL: URL?, encryptionKey: Data?) { @@ -143,7 +166,7 @@ open class DataStore where T: NSObject { Gets a single record using the `_id` of the record. - parameter id: The `_id` value of the entity to be find - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` - - parameter completionHandler: Completion handler to be called once the respose returns + - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ @discardableResult @@ -162,7 +185,7 @@ open class DataStore where T: NSObject { Gets a single record using the `_id` of the record. - parameter id: The `_id` value of the entity to be find - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` - - parameter completionHandler: Completion handler to be called once the respose returns + - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ @discardableResult @@ -182,7 +205,7 @@ open class DataStore where T: NSObject { PS: This method is just a shortcut for `findById()` - parameter id: The `_id` value of the entity to be find - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` - - parameter completionHandler: Completion handler to be called once the respose returns + - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ @discardableResult @@ -203,7 +226,7 @@ open class DataStore where T: NSObject { PS: This method is just a shortcut for `findById()` - parameter id: The `_id` value of the entity to be find - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` - - parameter completionHandler: Completion handler to be called once the respose returns + - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ @discardableResult @@ -225,7 +248,7 @@ open class DataStore where T: NSObject { - parameter query: The query used to filter the results - parameter deltaSet: Enforces delta set cache otherwise use the client's `deltaSet` value. Default value: `false` - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` - - parameter completionHandler: Completion handler to be called once the respose returns + - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ @discardableResult @@ -245,7 +268,7 @@ open class DataStore where T: NSObject { - parameter query: The query used to filter the results - parameter deltaSet: Enforces delta set cache otherwise use the client's `deltaSet` value. Default value: `false` - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` - - parameter completionHandler: Completion handler to be called once the respose returns + - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ @discardableResult @@ -265,7 +288,7 @@ open class DataStore where T: NSObject { Gets a count of how many records that matches with the (optional) query passed by parameter. - parameter query: The query used to filter the results - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` - - parameter completionHandler: Completion handler to be called once the respose returns + - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ @discardableResult @@ -281,10 +304,14 @@ open class DataStore where T: NSObject { } /** - Gets a count of how many records that matches with the (optional) query passed by parameter. - - parameter query: The query used to filter the results - - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` - - parameter completionHandler: Completion handler to be called once the respose returns + Gets a count of how many records that matches with the (optional) query + passed by parameter. + - parameter query: (Optional) The query used to filter the results. Default + value: `nil` + - parameter readPolicy: (Optional) Enforces a different `ReadPolicy` + otherwise use the client's `ReadPolicy`. Default value: `nil` + - parameter completionHandler: Completion handler to be called once the + response returns - returns: A `Request` instance which will allow cancel the request later */ @discardableResult @@ -299,9 +326,38 @@ open class DataStore where T: NSObject { return request } + /** + Custom aggregation function performed in the backend. + - parameter keys: (Optional) Property names that should be grouped. Default + value: `nil` + - parameter initialObject: Sets an initial object that contain initial + values needed for the reduce function + - parameter reduceJSFunction: JavaScript reduce function that performs the + aggregation + - parameter condition: (Optional) Predicate that filter the records to be + considered during the reduce function. Default value: `nil` + - parameter readPolicy: (Optional) Enforces a different `ReadPolicy` + otherwise use the client's `ReadPolicy`. Default value: `nil` + - parameter completionHandler: Completion handler to be called once the + response returns + - returns: A `Request` instance which will allow cancel the request later + */ @discardableResult - open func group(keys: [String]? = nil, initialObject: JsonDictionary, reduceJSFunction: String, condition: NSPredicate? = nil, readPolicy: ReadPolicy? = nil, completionHandler: @escaping ([AggregationCustomResult]?, Swift.Error?) -> Void) -> Request { - return group(keys: keys, initialObject: initialObject, reduceJSFunction: reduceJSFunction, condition: condition, readPolicy: readPolicy) { (result: Result<[AggregationCustomResult], Swift.Error>) in + open func group( + keys: [String]? = nil, + initialObject: JsonDictionary, + reduceJSFunction: String, + condition: NSPredicate? = nil, + readPolicy: ReadPolicy? = nil, + completionHandler: @escaping ([AggregationCustomResult]?, Swift.Error?) -> Void + ) -> Request { + return group( + keys: keys, + initialObject: initialObject, + reduceJSFunction: reduceJSFunction, + condition: condition, + readPolicy: readPolicy + ) { (result: Result<[AggregationCustomResult], Swift.Error>) in switch result { case .success(let results): completionHandler(results, nil) @@ -311,16 +367,51 @@ open class DataStore where T: NSObject { } } + /** + Custom aggregation function performed in the backend. + - parameter keys: (Optional) Property names that should be grouped. Default + value: `nil` + - parameter initialObject: Sets an initial object that contain initial + values needed for the reduce function + - parameter reduceJSFunction: JavaScript reduce function that performs the + aggregation + - parameter condition: (Optional) Predicate that filter the records to be + considered during the reduce function. Default value: `nil` + - parameter readPolicy: (Optional) Enforces a different `ReadPolicy` + otherwise use the client's `ReadPolicy`. Default value: `nil` + - parameter completionHandler: Completion handler to be called once the + response returns + - returns: A `Request` instance which will allow cancel the request later + */ @discardableResult - open func group(keys: [String]? = nil, initialObject: JsonDictionary, reduceJSFunction: String, condition: NSPredicate? = nil, readPolicy: ReadPolicy? = nil, completionHandler: @escaping (Result<[AggregationCustomResult], Swift.Error>) -> Void) -> Request { + open func group( + keys: [String]? = nil, + initialObject: JsonDictionary, + reduceJSFunction: String, + condition: NSPredicate? = nil, + readPolicy: ReadPolicy? = nil, + completionHandler: @escaping (Result<[AggregationCustomResult], Swift.Error>) -> Void + ) -> Request { let readPolicy = readPolicy ?? self.readPolicy let keys = keys ?? [] - let aggregation: Aggregation = .custom(keys: keys, initialObject: initialObject, reduceJSFunction: reduceJSFunction) - let operation = AggregateOperation(aggregation: aggregation, condition: condition, readPolicy: readPolicy, cache: cache, client: client) + let aggregation: Aggregation = .custom( + keys: keys, + initialObject: initialObject, + reduceJSFunction: reduceJSFunction + ) + let operation = AggregateOperation( + aggregation: aggregation, + condition: condition, + readPolicy: readPolicy, + cache: cache, + client: client + ) let request = operation.execute { result in switch result { case .success(let results): - let array = results.map { AggregationCustomResult(value: T(JSON: $0)!, custom: $0) } + let array = results.map { + AggregationCustomResult(value: T(JSON: $0)!, custom: $0) + } DispatchQueue.main.async { completionHandler(.success(array)) } @@ -333,10 +424,23 @@ open class DataStore where T: NSObject { return request } + /** + Count aggregation function performed in the backend. + */ @discardableResult - open func group(count keys: [String], countType: Count.Type? = nil, condition: NSPredicate? = nil, readPolicy: ReadPolicy? = nil, completionHandler: @escaping - ([AggregationCountResult]?, Swift.Error?) -> Void) -> Request { - return group(count: keys, countType: countType, condition: condition, readPolicy: readPolicy) { (result: Result<[AggregationCountResult], Swift.Error>) in + open func group( + count keys: [String], + countType: Count.Type? = nil, + condition: NSPredicate? = nil, + readPolicy: ReadPolicy? = nil, + completionHandler: @escaping ([AggregationCountResult]?, Swift.Error?) -> Void + ) -> Request { + return group( + count: keys, + countType: countType, + condition: condition, + readPolicy: readPolicy + ) { (result: Result<[AggregationCountResult], Swift.Error>) in switch result { case .success(let results): completionHandler(results, nil) @@ -346,16 +450,35 @@ open class DataStore where T: NSObject { } } + /** + Count aggregation function performed in the backend. + */ @discardableResult - open func group(count keys: [String], countType: Count.Type? = nil, condition: NSPredicate? = nil, readPolicy: ReadPolicy? = nil, completionHandler: @escaping - (Result<[AggregationCountResult], Swift.Error>) -> Void) -> Request { + open func group( + count keys: [String], + countType: Count.Type? = nil, + condition: NSPredicate? = nil, + readPolicy: ReadPolicy? = nil, + completionHandler: @escaping (Result<[AggregationCountResult], Swift.Error>) -> Void + ) -> Request { let readPolicy = readPolicy ?? self.readPolicy let aggregation: Aggregation = .count(keys: keys) - let operation = AggregateOperation(aggregation: aggregation, condition: condition, readPolicy: readPolicy, cache: cache, client: client) + let operation = AggregateOperation( + aggregation: aggregation, + condition: condition, + readPolicy: readPolicy, + cache: cache, + client: client + ) let request = operation.execute { result in switch result { case .success(let results): - let array = results.map { AggregationCountResult(value: T(JSON: $0)!, count: $0[aggregation.resultKey] as! Count) } + let array = results.map { + AggregationCountResult( + value: T(JSON: $0)!, + count: $0[aggregation.resultKey] as! Count + ) + } DispatchQueue.main.async { completionHandler(.success(array)) } @@ -368,9 +491,25 @@ open class DataStore where T: NSObject { return request } + /** + Sum aggregation function performed in the backend. + */ @discardableResult - open func group(keys: [String], sum: String, sumType: Sum.Type? = nil, condition: NSPredicate? = nil, readPolicy: ReadPolicy? = nil, completionHandler: @escaping ([AggregationSumResult]?, Swift.Error?) -> Void) -> Request { - return group(keys: keys, sum: sum, sumType: sumType, condition: condition, readPolicy: readPolicy) { (result: Result<[AggregationSumResult], Swift.Error>) in + open func group( + keys: [String], + sum: String, + sumType: Sum.Type? = nil, + condition: NSPredicate? = nil, + readPolicy: ReadPolicy? = nil, + completionHandler: @escaping ([AggregationSumResult]?, Swift.Error?) -> Void + ) -> Request { + return group( + keys: keys, + sum: sum, + sumType: sumType, + condition: condition, + readPolicy: readPolicy + ) { (result: Result<[AggregationSumResult], Swift.Error>) in switch result { case .success(let results): completionHandler(results, nil) @@ -380,15 +519,36 @@ open class DataStore where T: NSObject { } } + /** + Sum aggregation function performed in the backend. + */ @discardableResult - open func group(keys: [String], sum: String, sumType: Sum.Type? = nil, condition: NSPredicate? = nil, readPolicy: ReadPolicy? = nil, completionHandler: @escaping (Result<[AggregationSumResult], Swift.Error>) -> Void) -> Request { + open func group( + keys: [String], + sum: String, + sumType: Sum.Type? = nil, + condition: NSPredicate? = nil, + readPolicy: ReadPolicy? = nil, + completionHandler: @escaping (Result<[AggregationSumResult], Swift.Error>) -> Void + ) -> Request { let readPolicy = readPolicy ?? self.readPolicy let aggregation: Aggregation = .sum(keys: keys, sum: sum) - let operation = AggregateOperation(aggregation: aggregation, condition: condition, readPolicy: readPolicy, cache: cache, client: client) + let operation = AggregateOperation( + aggregation: aggregation, + condition: condition, + readPolicy: readPolicy, + cache: cache, + client: client + ) let request = operation.execute { result in switch result { case .success(let results): - let array = results.map { AggregationSumResult(value: T(JSON: $0)!, sum: $0[aggregation.resultKey] as! Sum) } + let array = results.map { + AggregationSumResult( + value: T(JSON: $0)!, + sum: $0[aggregation.resultKey] as! Sum + ) + } DispatchQueue.main.async { completionHandler(.success(array)) } @@ -401,9 +561,25 @@ open class DataStore where T: NSObject { return request } + /** + Average aggregation function performed in the backend. + */ @discardableResult - open func group(keys: [String], avg: String, avgType: Avg.Type? = nil, condition: NSPredicate? = nil, readPolicy: ReadPolicy? = nil, completionHandler: @escaping ([AggregationAvgResult]?, Swift.Error?) -> Void) -> Request { - return group(keys: keys, avg: avg, avgType: avgType, condition: condition, readPolicy: readPolicy) { (result: Result<[AggregationAvgResult], Swift.Error>) in + open func group( + keys: [String], + avg: String, + avgType: Avg.Type? = nil, + condition: NSPredicate? = nil, + readPolicy: ReadPolicy? = nil, + completionHandler: @escaping ([AggregationAvgResult]?, Swift.Error?) -> Void + ) -> Request { + return group( + keys: keys, + avg: avg, + avgType: avgType, + condition: condition, + readPolicy: readPolicy + ) { (result: Result<[AggregationAvgResult], Swift.Error>) in switch result { case .success(let results): completionHandler(results, nil) @@ -413,15 +589,36 @@ open class DataStore where T: NSObject { } } + /** + Average aggregation function performed in the backend. + */ @discardableResult - open func group(keys: [String], avg: String, avgType: Avg.Type? = nil, condition: NSPredicate? = nil, readPolicy: ReadPolicy? = nil, completionHandler: @escaping (Result<[AggregationAvgResult], Swift.Error>) -> Void) -> Request { + open func group( + keys: [String], + avg: String, + avgType: Avg.Type? = nil, + condition: NSPredicate? = nil, + readPolicy: ReadPolicy? = nil, + completionHandler: @escaping (Result<[AggregationAvgResult], Swift.Error>) -> Void + ) -> Request { let readPolicy = readPolicy ?? self.readPolicy let aggregation: Aggregation = .avg(keys: keys, avg: avg) - let operation = AggregateOperation(aggregation: aggregation, condition: condition, readPolicy: readPolicy, cache: cache, client: client) + let operation = AggregateOperation( + aggregation: aggregation, + condition: condition, + readPolicy: readPolicy, + cache: cache, + client: client + ) let request = operation.execute { result in switch result { case .success(let results): - let array = results.map { AggregationAvgResult(value: T(JSON: $0)!, avg: $0[aggregation.resultKey] as! Avg) } + let array = results.map { + AggregationAvgResult( + value: T(JSON: $0)!, + avg: $0[aggregation.resultKey] as! Avg + ) + } DispatchQueue.main.async { completionHandler(.success(array)) } @@ -434,9 +631,25 @@ open class DataStore where T: NSObject { return request } + /** + Minimum aggregation function performed in the backend. + */ @discardableResult - open func group(keys: [String], min: String, minType: Min.Type? = nil, condition: NSPredicate? = nil, readPolicy: ReadPolicy? = nil, completionHandler: @escaping ([AggregationMinResult]?, Swift.Error?) -> Void) -> Request { - return group(keys: keys, min: min, minType: minType, condition: condition, readPolicy: readPolicy) { (result: Result<[AggregationMinResult], Swift.Error>) in + open func group( + keys: [String], + min: String, + minType: Min.Type? = nil, + condition: NSPredicate? = nil, + readPolicy: ReadPolicy? = nil, + completionHandler: @escaping ([AggregationMinResult]?, Swift.Error?) -> Void + ) -> Request { + return group( + keys: keys, + min: min, + minType: minType, + condition: condition, + readPolicy: readPolicy + ) { (result: Result<[AggregationMinResult], Swift.Error>) in switch result { case .success(let results): completionHandler(results, nil) @@ -446,15 +659,36 @@ open class DataStore where T: NSObject { } } + /** + Minimum aggregation function performed in the backend. + */ @discardableResult - open func group(keys: [String], min: String, minType: Min.Type? = nil, condition: NSPredicate? = nil, readPolicy: ReadPolicy? = nil, completionHandler: @escaping (Result<[AggregationMinResult], Swift.Error>) -> Void) -> Request { + open func group( + keys: [String], + min: String, + minType: Min.Type? = nil, + condition: NSPredicate? = nil, + readPolicy: ReadPolicy? = nil, + completionHandler: @escaping (Result<[AggregationMinResult], Swift.Error>) -> Void + ) -> Request { let readPolicy = readPolicy ?? self.readPolicy let aggregation: Aggregation = .min(keys: keys, min: min) - let operation = AggregateOperation(aggregation: aggregation, condition: condition, readPolicy: readPolicy, cache: cache, client: client) + let operation = AggregateOperation( + aggregation: aggregation, + condition: condition, + readPolicy: readPolicy, + cache: cache, + client: client + ) let request = operation.execute { result in switch result { case .success(let results): - let array = results.map { AggregationMinResult(value: T(JSON: $0)!, min: $0[aggregation.resultKey] as! Min) } + let array = results.map { + AggregationMinResult( + value: T(JSON: $0)!, + min: $0[aggregation.resultKey] as! Min + ) + } DispatchQueue.main.async { completionHandler(.success(array)) } @@ -467,9 +701,25 @@ open class DataStore where T: NSObject { return request } + /** + Maximum aggregation function performed in the backend. + */ @discardableResult - open func group(keys: [String], max: String, maxType: Max.Type? = nil, condition: NSPredicate? = nil, readPolicy: ReadPolicy? = nil, completionHandler: @escaping ([AggregationMaxResult]?, Swift.Error?) -> Void) -> Request { - return group(keys: keys, max: max, maxType: maxType, condition: condition, readPolicy: readPolicy) { (result: Result<[AggregationMaxResult], Swift.Error>) in + open func group( + keys: [String], + max: String, + maxType: Max.Type? = nil, + condition: NSPredicate? = nil, + readPolicy: ReadPolicy? = nil, + completionHandler: @escaping ([AggregationMaxResult]?, Swift.Error?) -> Void + ) -> Request { + return group( + keys: keys, + max: max, + maxType: maxType, + condition: condition, + readPolicy: readPolicy + ) { (result: Result<[AggregationMaxResult], Swift.Error>) in switch result { case .success(let results): completionHandler(results, nil) @@ -479,15 +729,36 @@ open class DataStore where T: NSObject { } } + /** + Maximum aggregation function performed in the backend. + */ @discardableResult - open func group(keys: [String], max: String, maxType: Max.Type? = nil, condition: NSPredicate? = nil, readPolicy: ReadPolicy? = nil, completionHandler: @escaping (Result<[AggregationMaxResult], Swift.Error>) -> Void) -> Request { + open func group( + keys: [String], + max: String, + maxType: Max.Type? = nil, + condition: NSPredicate? = nil, + readPolicy: ReadPolicy? = nil, + completionHandler: @escaping (Result<[AggregationMaxResult], Swift.Error>) -> Void + ) -> Request { let readPolicy = readPolicy ?? self.readPolicy let aggregation: Aggregation = .max(keys: keys, max: max) - let operation = AggregateOperation(aggregation: aggregation, condition: condition, readPolicy: readPolicy, cache: cache, client: client) + let operation = AggregateOperation( + aggregation: aggregation, + condition: condition, + readPolicy: readPolicy, + cache: cache, + client: client + ) let request = operation.execute { result in switch result { case .success(let results): - let array = results.map { AggregationMaxResult(value: T(JSON: $0)!, max: $0[aggregation.resultKey] as! Max) } + let array = results.map { + AggregationMaxResult( + value: T(JSON: $0)!, + max: $0[aggregation.resultKey] as! Max + ) + } DispatchQueue.main.async { completionHandler(.success(array)) } @@ -911,6 +1182,9 @@ open class DataStore where T: NSObject { return realtimeRouter } + /** + Subscribe and start listening changes in the collection + */ @discardableResult open func subscribe( subscription: @escaping () -> Void, @@ -953,6 +1227,9 @@ open class DataStore where T: NSObject { return request } + /** + Unsubscribe and stop listening changes in the collection + */ @discardableResult open func unsubscribe(completionHandler: @escaping (Result) -> Void) -> Request { let request = client.networkRequestFactory.buildAppDataUnSubscribe(collectionName: collectionName, deviceId: deviceId) diff --git a/Kinvey/Kinvey/Entity.swift b/Kinvey/Kinvey/Entity.swift index 8d946a3e6..9a44b62d6 100644 --- a/Kinvey/Kinvey/Entity.swift +++ b/Kinvey/Kinvey/Entity.swift @@ -42,6 +42,7 @@ internal func StringFromClass(cls: AnyClass) -> String { /// Base class for entity classes that are mapped to a collection in Kinvey. open class Entity: Object, Persistable { + /// Property names for the `Entity` class public struct Key { /// Key to map the `_id` column in your Persistable implementation class. @@ -159,25 +160,31 @@ open class Entity: Object, Persistable { } +/// Wrapper type for string values that needs to be stored locally in the device open class StringValue: Object, ExpressibleByStringLiteral { + /// String value for the wrapper public dynamic var value = "" + /// Constructor for the `ExpressibleByUnicodeScalarLiteral` protocol public convenience required init(unicodeScalarLiteral value: String) { self.init() self.value = value } + /// Constructor for the `ExpressibleByExtendedGraphemeClusterLiteral` protocol public convenience required init(extendedGraphemeClusterLiteral value: String) { self.init() self.value = value } + /// Constructor for the `ExpressibleByStringLiteral` protocol public convenience required init(stringLiteral value: String) { self.init() self.value = value } + /// Constructor that takes a string value to wrap public convenience init(_ value: String) { self.init() self.value = value @@ -185,15 +192,21 @@ open class StringValue: Object, ExpressibleByStringLiteral { } +/** + Wrapper type for integer values that needs to be stored locally in the device + */ open class IntValue: Object, ExpressibleByIntegerLiteral { + /// Integer value for the wrapper public dynamic var value = 0 + /// Constructor for the `ExpressibleByIntegerLiteral` protocol public convenience required init(integerLiteral value: Int) { self.init() self.value = value } + /// Constructor that takes an integer value to wrap public convenience init(_ value: Int) { self.init() self.value = value @@ -201,15 +214,21 @@ open class IntValue: Object, ExpressibleByIntegerLiteral { } +/** + Wrapper type for float values that needs to be stored locally in the device + */ open class FloatValue: Object, ExpressibleByFloatLiteral { + /// Float value for the wrapper public dynamic var value = Float(0) + /// Constructor for the `ExpressibleByFloatLiteral` protocol public convenience required init(floatLiteral value: Float) { self.init() self.value = value } + /// Constructor that takes a float value to wrap public convenience init(_ value: Float) { self.init() self.value = value @@ -217,15 +236,21 @@ open class FloatValue: Object, ExpressibleByFloatLiteral { } +/** + Wrapper type for double values that needs to be stored locally in the device + */ open class DoubleValue: Object, ExpressibleByFloatLiteral { + /// Double value for the wrapper public dynamic var value = 0.0 + /// Constructor for the `ExpressibleByFloatLiteral` protocol public convenience required init(floatLiteral value: Double) { self.init() self.value = value } + /// Constructor that takes a double value to wrap public convenience init(_ value: Double) { self.init() self.value = value @@ -233,15 +258,21 @@ open class DoubleValue: Object, ExpressibleByFloatLiteral { } +/** + Wrapper type for boolean values that needs to be stored locally in the device + */ open class BoolValue: Object, ExpressibleByBooleanLiteral { + /// Boolean value for the wrapper public dynamic var value = false + /// Constructor for the `ExpressibleByBooleanLiteral` protocol public convenience required init(booleanLiteral value: Bool) { self.init() self.value = value } + /// Constructor that takes a boolean value to wrap public convenience init(_ value: Bool) { self.init() self.value = value diff --git a/Kinvey/Kinvey/File.swift b/Kinvey/Kinvey/File.swift index 5eb4f97bf..b1bafd22b 100644 --- a/Kinvey/Kinvey/File.swift +++ b/Kinvey/Kinvey/File.swift @@ -45,7 +45,8 @@ open class File: Object, Mappable { } } - open dynamic var upload:String? + /// Temporary upload URL String of the file. + open dynamic var upload: String? /// Temporary upload URL of the file. open dynamic var uploadURL: URL? { @@ -65,8 +66,10 @@ open class File: Object, Mappable { /// Expiration data of the `downloadURL`. open dynamic var expiresAt: Date? + /// ETag header used for validate the local cache open internal(set) dynamic var etag: String? + /// Local path URL String for the cached file open internal(set) dynamic var path: String? { didSet { if let path = path, @@ -81,6 +84,7 @@ open class File: Object, Mappable { } } + /// Local path URL for the cached file open internal(set) dynamic var pathURL: URL? { get { if let path = path { diff --git a/Kinvey/Kinvey/FileStore.swift b/Kinvey/Kinvey/FileStore.swift index 4f75b96ea..857dfcf1d 100644 --- a/Kinvey/Kinvey/FileStore.swift +++ b/Kinvey/Kinvey/FileStore.swift @@ -17,9 +17,13 @@ import ObjectMapper import UIKit #endif +/// Enumeration to describe which format an image should be represented public enum ImageRepresentation { + /// PNG Format case png + + /// JPEG Format with a compression quality value case jpeg(compressionQuality: Float) #if os(macOS) @@ -89,6 +93,10 @@ open class FileStore { return FileStore(client: client) } + /** + Constructor that takes a specific `Client` instance or use the + `sharedClient` instance + */ public init(client: Client = sharedClient) { self.client = client self.cache = client.cacheManager.fileCache(fileURL: client.fileURL()) diff --git a/Kinvey/Kinvey/Geolocation.swift b/Kinvey/Kinvey/Geolocation.swift index 8f325c56a..13ca84329 100644 --- a/Kinvey/Kinvey/Geolocation.swift +++ b/Kinvey/Kinvey/Geolocation.swift @@ -12,27 +12,50 @@ import ObjectMapper import CoreLocation import MapKit +/// Class that represents a 2D geolocation with latitude and longitude public final class GeoPoint: Object { + /// Specifies the north–south position of a point open dynamic var latitude: CLLocationDegrees = 0.0 + + /// Specifies the east-west position of a point open dynamic var longitude: CLLocationDegrees = 0.0 - public convenience init(latitude: CLLocationDegrees, longitude: CLLocationDegrees) { + /** + Constructor that takes `CLLocationDegrees` (`Double`) values for latitude + and longitude + */ + public convenience init( + latitude: CLLocationDegrees, + longitude: CLLocationDegrees + ) { self.init() self.latitude = latitude self.longitude = longitude } + /// Constructor that takes a `CLLocationCoordinate2D` public convenience init(coordinate: CLLocationCoordinate2D) { - self.init(latitude: coordinate.latitude, longitude: coordinate.longitude) + self.init( + latitude: coordinate.latitude, + longitude: coordinate.longitude + ) } + /** + Constructor that takes an array of `CLLocationDegrees` (`Double`) values + for longitude and latitude (in this order) + */ convenience init(_ array: [CLLocationDegrees]) { self.init(latitude: array[1], longitude: array[0]) } } +/** + Make the GeoPoint implement Mappable, so we can serialize and deserialize + GeoPoint + */ extension GeoPoint: Mappable { public convenience init?(map: Map) { @@ -94,6 +117,10 @@ func ==(lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool { extension CLLocation { + /** + Constructor that takes a `GeoPoint` to make it easy to convert to a + `CLLocation` instance + */ public convenience init(geoPoint: GeoPoint) { self.init(latitude: geoPoint.latitude, longitude: geoPoint.longitude) } @@ -102,6 +129,10 @@ extension CLLocation { extension CLLocationCoordinate2D { + /** + Constructor that takes a `GeoPoint` to make it easy to convert to a + `CLLocationCoordinate2D` instance + */ public init(geoPoint: GeoPoint) { self.init(latitude: geoPoint.latitude, longitude: geoPoint.longitude) } diff --git a/Kinvey/Kinvey/Kinvey.swift b/Kinvey/Kinvey/Kinvey.swift index 2d17b6edb..2e14bc3b8 100644 --- a/Kinvey/Kinvey/Kinvey.swift +++ b/Kinvey/Kinvey/Kinvey.swift @@ -11,9 +11,13 @@ import XCGLogger let ObjectIdTmpPrefix = "tmp_" -/// Shared client instance for simplicity. Use this instance if *you don't need* to handle with multiple Kinvey environments. +/** + Shared client instance for simplicity. Use this instance if *you don't need* to + handle with multiple Kinvey environments. + */ public let sharedClient = Client.sharedClient +/// A once-per-installation value generated to give an ID for the running device public let deviceId = Keychain().deviceId fileprivate extension Keychain { @@ -34,9 +38,36 @@ fileprivate extension Keychain { } +/** + Define how detailed operations should be logged. Here's the asceding order + (from the less detailed to the most detailed level): none, severe, error, + warning, info, debug, verbose + */ public enum LogLevel { - case verbose, debug, info, warning, error, severe, none + /** + Log operations that are useful if you are debugging giving aditional + information. Most detailed level + */ + case verbose + + /// Log operations that are useful if you are debugging + case debug + + /// Log operations giving aditional information for basic operations + case info + + /// Only log warning messages when needed + case warning + + /// Only log error messages when needed + case error + + /// Only log severe error messages when needed + case severe + + /// Log is turned off + case none internal var outputLevel: XCGLogger.Level { switch self { diff --git a/Kinvey/Kinvey/KinveyDateTransform.swift b/Kinvey/Kinvey/KinveyDateTransform.swift index 5cf21df0f..7b5583b9b 100644 --- a/Kinvey/Kinvey/KinveyDateTransform.swift +++ b/Kinvey/Kinvey/KinveyDateTransform.swift @@ -9,12 +9,13 @@ import Foundation import ObjectMapper - +/// Default TransformType for `Date` types open class KinveyDateTransform : TransformType { public typealias Object = Date public typealias JSON = String + /// Default Constructor public init() {} //read formatter that accounts for the timezone @@ -42,6 +43,7 @@ open class KinveyDateTransform : TransformType { return wFormatter }() + /// Converts any value to `Date`, if possible open func transformFromJSON(_ value: Any?) -> Date? { if let dateString = value as? String { @@ -62,6 +64,7 @@ open class KinveyDateTransform : TransformType { return nil } + /// Converts any `Date` to `String`, if possible open func transformToJSON(_ value: Date?) -> String? { if let date = value { return dateWriteFormatter.string(from: date) diff --git a/Kinvey/Kinvey/MIC.swift b/Kinvey/Kinvey/MIC.swift index f7c47ac29..b7e3755fe 100644 --- a/Kinvey/Kinvey/MIC.swift +++ b/Kinvey/Kinvey/MIC.swift @@ -17,11 +17,15 @@ class URLSessionDelegateAdapter : NSObject, URLSessionTaskDelegate { } +/// Class that handles Mobile Identity Connect (MIC) calls open class MIC { private init() { } + /** + Validate if a URL matches for a redirect URI and also contains a code value + */ open class func isValid(redirectURI: URL, url: URL) -> Bool { return parseCode(redirectURI: redirectURI, url: url) != nil } @@ -44,6 +48,7 @@ open class MIC { return code } + /// Returns a URL that must be used for login with MIC open class func urlForLogin( redirectURI: URL, loginPage: Bool = true, diff --git a/Kinvey/Kinvey/Metadata.swift b/Kinvey/Kinvey/Metadata.swift index 877a30cd3..e20b498da 100644 --- a/Kinvey/Kinvey/Metadata.swift +++ b/Kinvey/Kinvey/Metadata.swift @@ -26,6 +26,7 @@ public class Metadata: Object, Mappable { @available(*, deprecated: 3.5.2, message: "Please use Metadata.Key.authToken instead") open static let AuthTokenKey = "authtoken" + /// Property names for `Metadata` public struct Key { /// Last Modification Time Key. @@ -123,10 +124,16 @@ public class Metadata: Object, Mappable { } +/// Metadata information for each `User` public final class UserMetadata: Metadata { + /// Status of the email verification process open internal(set) var emailVerification: EmailVerification? + + /// Status of the password reset process open internal(set) var passwordReset: PasswordReset? + + /// Status of the activation process open internal(set) var userStatus: UserStatus? public override func mapping(map: Map) { @@ -139,15 +146,24 @@ public final class UserMetadata: Metadata { } +/// Status of the email verification process for each `User` public final class EmailVerification: Object { + /// Current Status open internal(set) var status: String? + + /// Date of the last Status change open internal(set) var lastStateChangeAt: Date? + + /// Date of the last email confirmation open internal(set) var lastConfirmedAt: Date? + + /// Email Address open internal(set) var emailAddress: String? } +/// Allows serialization and deserialization of EmailVerification extension EmailVerification: Mappable { /// Constructor that validates if the map can be build a new instance of Metadata. @@ -165,13 +181,18 @@ extension EmailVerification: Mappable { } +/// Status of the password reset process for each `User` public final class PasswordReset: Object { + /// Current Status open internal(set) var status: String? + + /// Date of the last Status change open internal(set) var lastStateChangeAt: Date? } +/// Allows serialization and deserialization of PasswordReset extension PasswordReset: Mappable { /// Constructor that validates if the map can be build a new instance of Metadata. @@ -187,13 +208,18 @@ extension PasswordReset: Mappable { } +/// Status of activation process for each `User` public final class UserStatus: Object { + /// Current Status open internal(set) var value: String? + + /// Date of the last Status change open internal(set) var lastChange: Date? } +/// Allows serialization and deserialization of UserStatus extension UserStatus: Mappable { /// Constructor that validates if the map can be build a new instance of Metadata. diff --git a/Kinvey/Kinvey/Persistable.swift b/Kinvey/Kinvey/Persistable.swift index 1cc73be68..e78d8e424 100644 --- a/Kinvey/Kinvey/Persistable.swift +++ b/Kinvey/Kinvey/Persistable.swift @@ -189,6 +189,7 @@ class ListValueTransform: TransformOf, [JsonDictio } +/// Overload operator for `List` values public func <-(lhs: List, rhs: (String, Map)) { let (_, map) = rhs var list = lhs diff --git a/Kinvey/Kinvey/Realtime.swift b/Kinvey/Kinvey/Realtime.swift index c353beb7d..656a26e53 100644 --- a/Kinvey/Kinvey/Realtime.swift +++ b/Kinvey/Kinvey/Realtime.swift @@ -10,9 +10,20 @@ import Foundation import ObjectMapper import PromiseKit +/// Tells the current status for the realtime connection public enum RealtimeStatus { - case connected, disconnected, reconnected, unexpectedDisconnect + /// Connection is stablished + case connected + + /// Connection used to be on, but is now off + case disconnected + + /// Connection used to be `disconnected`, but is now on again + case reconnected + + /// Connection used to be on, but is now off for an unexpected reason + case unexpectedDisconnect } @@ -36,6 +47,10 @@ protocol RealtimeRouter { } +/** + Class that creates a live stream connection to be used in a peer-to-peer + communication + */ public class LiveStream { private let name: String @@ -45,6 +60,10 @@ public class LiveStream { fileprivate let uuid = UUID() + /** + Constructor that takes the name of the stream and an (optional) `Client` + instance + */ public init(name: String, client: Client = sharedClient) { self.name = name self.client = client @@ -72,6 +91,7 @@ public class LiveStream { } } + /// Grant access to a user for the `LiveStream` @discardableResult public func grantStreamAccess(userId: String, acl: LiveStreamAcl, completionHandler: ((Result) -> Void)? = nil) -> Request { let request = client.networkRequestFactory.buildLiveStreamGrantAccess(streamName: name, userId: userId, acl: acl) @@ -91,6 +111,7 @@ public class LiveStream { return request } + /// Sends a message to an specific user public func send(userId: String, message: Type, retry: Bool = true, completionHandler: ((Result) -> Void)? = nil) { realtimeRouterPromise.then { activeUser, realtimeRouter in return Promise<(RealtimeRouter, String)> { fulfill, reject in @@ -149,6 +170,7 @@ public class LiveStream { } } + /// Start listening messages sent to the current active user public func listen( listening: @escaping () -> Void, onNext: @escaping (Type) -> Void, @@ -162,6 +184,7 @@ public class LiveStream { } } + /// /// Stop listening messages sent to the current active user public func stopListening(completionHandler: ((Result) -> Void)? = nil) { realtimeRouterPromise.then { activeUser, _ in self.unfollow(userId: activeUser.userId, completionHandler: completionHandler) @@ -170,6 +193,7 @@ public class LiveStream { } } + /// Sends a message to the current active user public func post(message: Type, completionHandler: ((Result) -> Void)? = nil) { realtimeRouterPromise.then { activeUser, _ in self.send(userId: activeUser.userId, message: message, completionHandler: completionHandler) @@ -178,6 +202,7 @@ public class LiveStream { } } + /// Start listening messages sent to an specific user public func follow( userId: String, following: @escaping () -> Void, @@ -226,6 +251,7 @@ public class LiveStream { } } + /// Stop listening messages sent to an specific user public func unfollow(userId: String, completionHandler: ((Result) -> Void)? = nil) { realtimeRouterPromise.then { activeUser, realtimeRouter in return Promise { fulfill, reject in @@ -251,10 +277,16 @@ public class LiveStream { } +/// Access Control Level (Acl) for `LiveStream` objects public struct LiveStreamAcl: StaticMappable { + /// List of `userId`s that are allowed to subscribe public var subscribers = [String]() + + /// List of `userId`s that are allowed to publish public var publishers = [String]() + + /// Group Acl public var groups = LiveStreamAclGroups() public static func objectForMapping(map: Map) -> BaseMappable? { @@ -269,9 +301,13 @@ public struct LiveStreamAcl: StaticMappable { } +/// Group Access Control Level (Group Acl) for `LiveStream` objects public struct LiveStreamAclGroups: StaticMappable { + /// List of groups that are allowed to publish public var publishers = [String]() + + /// List of groups that are allowed to subscribe public var subscribers = [String]() public static func objectForMapping(map: Map) -> BaseMappable? { @@ -285,6 +321,10 @@ public struct LiveStreamAclGroups: StaticMappable { } +/** + Makes the `LiveStream` to conforms to `Hashable`, so they can be stored in + Dictionaries for example + */ extension LiveStream: Hashable { public var hashValue: Int { diff --git a/Kinvey/Kinvey/Result.swift b/Kinvey/Kinvey/Result.swift index 8270ba06f..0d56448e2 100644 --- a/Kinvey/Kinvey/Result.swift +++ b/Kinvey/Kinvey/Result.swift @@ -8,9 +8,13 @@ import Foundation +/// Enumeration that represents a result expected usually after an async call public enum Result { + /// Case when the result is a success result holding the succeed type value case success(SuccessType) + + /// Case when the result is a failure result holding the failure type value case failure(FailureType) } diff --git a/Kinvey/Kinvey/String.swift b/Kinvey/Kinvey/String.swift index 0bf0c917f..cd3cac91e 100644 --- a/Kinvey/Kinvey/String.swift +++ b/Kinvey/Kinvey/String.swift @@ -32,6 +32,7 @@ fileprivate let dateFormatters = DateFormatters() extension String { + /// Converts a `String` to `Date`, if possible public func toDate() -> Date? { switch self.characters.count { case 20: diff --git a/Kinvey/Kinvey/User.swift b/Kinvey/Kinvey/User.swift index 24b3cda99..feca6db64 100644 --- a/Kinvey/Kinvey/User.swift +++ b/Kinvey/Kinvey/User.swift @@ -648,6 +648,7 @@ open class User: NSObject, Credential, Mappable { return request } + /// Register the user to start performing realtime / live calls @discardableResult open func registerForRealtime(completionHandler: ((Result) -> Void)? = nil) -> Request { let request = client.networkRequestFactory.buildUserRegisterRealtime(user: self, deviceId: deviceId) @@ -675,6 +676,7 @@ open class User: NSObject, Credential, Mappable { return request } + /// Unregister the user to stop performing realtime / live calls @discardableResult open func unregisterForRealtime(completionHandler: ((Result) -> Void)? = nil) -> Request { let request = client.networkRequestFactory.buildUserUnregisterRealtime(user: self, deviceId: deviceId) @@ -939,6 +941,7 @@ open class User: NSObject, Credential, Mappable { } +/// Holds the Social Identities attached to a specific User public struct UserSocialIdentity : StaticMappable { /// Facebook social identity From 9f56784783c2e95678e2f82e5f7aa3382b60739d Mon Sep 17 00:00:00 2001 From: Victor Barros Date: Thu, 13 Jul 2017 11:29:06 -0700 Subject: [PATCH 05/11] multiple changes requested during code review --- Kinvey/Kinvey/Client.swift | 5 +- Kinvey/Kinvey/CustomEndpoint.swift | 15 ++- Kinvey/Kinvey/DataStore.swift | 204 ++++++++++++++++++++++++----- Kinvey/Kinvey/Kinvey.swift | 8 +- Kinvey/Kinvey/Realtime.swift | 2 +- Kinvey/Kinvey/Result.swift | 13 +- 6 files changed, 207 insertions(+), 40 deletions(-) diff --git a/Kinvey/Kinvey/Client.swift b/Kinvey/Kinvey/Client.swift index 97b88531b..483374173 100644 --- a/Kinvey/Kinvey/Client.swift +++ b/Kinvey/Kinvey/Client.swift @@ -344,8 +344,9 @@ open class Client: Credential { } /** - Check if the `appKey` and `appSecret` properties are correct doing a ping - call to the server. + Checks connectivity to your backend. A successful response returns a + summary of your backend environment and confirms that the app can talk to + the backend. */ @discardableResult public func ping(completionHandler: @escaping (Result) -> Void) -> Request { diff --git a/Kinvey/Kinvey/CustomEndpoint.swift b/Kinvey/Kinvey/CustomEndpoint.swift index 55f464dc8..bfd2cc35b 100644 --- a/Kinvey/Kinvey/CustomEndpoint.swift +++ b/Kinvey/Kinvey/CustomEndpoint.swift @@ -25,17 +25,26 @@ open class CustomEndpoint { internal let value: ParamsEnum - /// Constructor that takes a JSON Dictionary + /** + Sets the `value` enumeration to a JSON dictionary. + - parameter json: JSON dictionary to be used as a parameter value + */ public init(_ json: JsonDictionary) { value = ParamsEnum.json(json) } - /// Constructor that takes any type that implements Mappable + /** + Sets the `value` enumeration to any Mappable object. + - parameter object: Mappable object to be used as a parameter value + */ public init(_ object: Mappable) { value = ParamsEnum.object(object) } - /// Constructor that takes any type that implements StaticMappable + /** + Sets the `value` enumeration to any StaticMappable struct. + - parameter object: StaticMappable struct to be used as a parameter value + */ public init(_ object: StaticMappable) { value = ParamsEnum.object(object) } diff --git a/Kinvey/Kinvey/DataStore.swift b/Kinvey/Kinvey/DataStore.swift index d09ac4d0f..f0f580e13 100644 --- a/Kinvey/Kinvey/DataStore.swift +++ b/Kinvey/Kinvey/DataStore.swift @@ -165,7 +165,9 @@ open class DataStore where T: NSObject { /** Gets a single record using the `_id` of the record. - parameter id: The `_id` value of the entity to be find - - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` + - parameter readPolicy: (Optional) Enforces a different `ReadPolicy` + otherwise use the `ReadPolicy` inferred from the store's type. Default + value: `ReadPolicy` inferred from the store's type - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ @@ -184,7 +186,9 @@ open class DataStore where T: NSObject { /** Gets a single record using the `_id` of the record. - parameter id: The `_id` value of the entity to be find - - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` + - parameter readPolicy: (Optional) Enforces a different `ReadPolicy` + otherwise use the `ReadPolicy` inferred from the store's type. Default + value: `ReadPolicy` inferred from the store's type - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ @@ -204,7 +208,9 @@ open class DataStore where T: NSObject { PS: This method is just a shortcut for `findById()` - parameter id: The `_id` value of the entity to be find - - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` + - parameter readPolicy: (Optional) Enforces a different `ReadPolicy` + otherwise use the `ReadPolicy` inferred from the store's type. Default + value: `ReadPolicy` inferred from the store's type - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ @@ -225,7 +231,9 @@ open class DataStore where T: NSObject { PS: This method is just a shortcut for `findById()` - parameter id: The `_id` value of the entity to be find - - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` + - parameter readPolicy: (Optional) Enforces a different `ReadPolicy` + otherwise use the `ReadPolicy` inferred from the store's type. Default + value: `ReadPolicy` inferred from the store's type - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ @@ -247,7 +255,9 @@ open class DataStore where T: NSObject { Gets a list of records that matches with the query passed by parameter. - parameter query: The query used to filter the results - parameter deltaSet: Enforces delta set cache otherwise use the client's `deltaSet` value. Default value: `false` - - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` + - parameter readPolicy: (Optional) Enforces a different `ReadPolicy` + otherwise use the `ReadPolicy` inferred from the store's type. Default + value: `ReadPolicy` inferred from the store's type - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ @@ -267,7 +277,7 @@ open class DataStore where T: NSObject { Gets a list of records that matches with the query passed by parameter. - parameter query: The query used to filter the results - parameter deltaSet: Enforces delta set cache otherwise use the client's `deltaSet` value. Default value: `false` - - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` + - parameter readPolicy: (Optional) Enforces a different `ReadPolicy` otherwise use the `ReadPolicy` inferred from the store's type. Default value: `ReadPolicy` inferred from the store's type - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ @@ -285,10 +295,12 @@ open class DataStore where T: NSObject { } /** - Gets a count of how many records that matches with the (optional) query passed by parameter. - - parameter query: The query used to filter the results - - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` - - parameter completionHandler: Completion handler to be called once the response returns + Count of records that matches with the (optional) query parameter. + - parameter query: (Optional) The query used to filter the results. When + query is nil, gets the total count of the collection. Default value: `nil` + - parameter readPolicy: (Optional) Enforces a different `ReadPolicy` otherwise use the `ReadPolicy` inferred from the store's type. Default value: `ReadPolicy` inferred from the store's type + - parameter completionHandler: Completion handler to be called once the + response returns - returns: A `Request` instance which will allow cancel the request later */ @discardableResult @@ -304,12 +316,12 @@ open class DataStore where T: NSObject { } /** - Gets a count of how many records that matches with the (optional) query - passed by parameter. - - parameter query: (Optional) The query used to filter the results. Default - value: `nil` + Count of records that matches with the (optional) query parameter. + - parameter query: (Optional) The query used to filter the results. When + query is nil, gets the total count of the collection. Default value: `nil` - parameter readPolicy: (Optional) Enforces a different `ReadPolicy` - otherwise use the client's `ReadPolicy`. Default value: `nil` + otherwise use the `ReadPolicy` inferred from the store's type. Default + value: `ReadPolicy` inferred from the store's type - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later @@ -327,10 +339,12 @@ open class DataStore where T: NSObject { } /** - Custom aggregation function performed in the backend. + Custom aggregation function. + Note: this function does not work on local data. It must be run against the + backend. - parameter keys: (Optional) Property names that should be grouped. Default value: `nil` - - parameter initialObject: Sets an initial object that contain initial + - parameter initialObject: Sets an initial object that contains initial values needed for the reduce function - parameter reduceJSFunction: JavaScript reduce function that performs the aggregation @@ -368,10 +382,12 @@ open class DataStore where T: NSObject { } /** - Custom aggregation function performed in the backend. + Custom aggregation function. + Note: this function does not work on local data. It must be run against the + backend. - parameter keys: (Optional) Property names that should be grouped. Default value: `nil` - - parameter initialObject: Sets an initial object that contain initial + - parameter initialObject: Sets an initial object that contains initial values needed for the reduce function - parameter reduceJSFunction: JavaScript reduce function that performs the aggregation @@ -425,7 +441,19 @@ open class DataStore where T: NSObject { } /** - Count aggregation function performed in the backend. + Count aggregation function. + Note: this function does not work on local data. It must be run against the + backend. + - Parameters: + - keys: Property names that should be grouped + - countType: Integer type to be return as a result count + - condition: (Optional) Predicate that filter the records to be + considered during the reduce function. Default value: `nil` + - readPolicy: (Optional) Enforces a different `ReadPolicy` otherwise use + the client's `ReadPolicy`. Default value: `nil` + - completionHandler: Completion handler to be called once the + response returns + - returns: A `Request` instance which will allow cancel the request later */ @discardableResult open func group( @@ -451,7 +479,19 @@ open class DataStore where T: NSObject { } /** - Count aggregation function performed in the backend. + Count aggregation function. + Note: this function does not work on local data. It must be run against the + backend. + - Parameters: + - keys: Property names that should be grouped + - countType: Integer type to be return as a result count + - condition: (Optional) Predicate that filter the records to be + considered during the reduce function. Default value: `nil` + - readPolicy: (Optional) Enforces a different `ReadPolicy` otherwise use + the client's `ReadPolicy`. Default value: `nil` + - completionHandler: Completion handler to be called once the + response returns + - returns: A `Request` instance which will allow cancel the request later */ @discardableResult open func group( @@ -492,7 +532,20 @@ open class DataStore where T: NSObject { } /** - Sum aggregation function performed in the backend. + Sum aggregation function. + Note: this function does not work on local data. It must be run against the + backend. + - Parameters: + - keys: Property names that should be grouped + - sum: Property name used in the sum operation + - sumType: Integer type to be return as a result sum + - condition: (Optional) Predicate that filter the records to be + considered during the reduce function. Default value: `nil` + - readPolicy: (Optional) Enforces a different `ReadPolicy` otherwise use + the client's `ReadPolicy`. Default value: `nil` + - completionHandler: Completion handler to be called once the + response returns + - returns: A `Request` instance which will allow cancel the request later */ @discardableResult open func group( @@ -520,7 +573,20 @@ open class DataStore where T: NSObject { } /** - Sum aggregation function performed in the backend. + Sum aggregation function. + Note: this function does not work on local data. It must be run against the + backend. + - Parameters: + - keys: Property names that should be grouped + - sum: Property name used in the sum operation + - sumType: Integer type to be return as a result sum + - condition: (Optional) Predicate that filter the records to be + considered during the reduce function. Default value: `nil` + - readPolicy: (Optional) Enforces a different `ReadPolicy` otherwise use + the client's `ReadPolicy`. Default value: `nil` + - completionHandler: Completion handler to be called once the + response returns + - returns: A `Request` instance which will allow cancel the request later */ @discardableResult open func group( @@ -562,7 +628,20 @@ open class DataStore where T: NSObject { } /** - Average aggregation function performed in the backend. + Average aggregation function. + Note: this function does not work on local data. It must be run against the + backend. + - Parameters: + - keys: Property names that should be grouped + - avg: Property name used in the average operation + - avgType: Integer type to be return as a result average + - condition: (Optional) Predicate that filter the records to be + considered during the reduce function. Default value: `nil` + - readPolicy: (Optional) Enforces a different `ReadPolicy` otherwise use + the client's `ReadPolicy`. Default value: `nil` + - completionHandler: Completion handler to be called once the + response returns + - returns: A `Request` instance which will allow cancel the request later */ @discardableResult open func group( @@ -590,7 +669,20 @@ open class DataStore where T: NSObject { } /** - Average aggregation function performed in the backend. + Average aggregation function. + Note: this function does not work on local data. It must be run against the + backend. + - Parameters: + - keys: Property names that should be grouped + - avg: Property name used in the average operation + - avgType: Integer type to be return as a result average + - condition: (Optional) Predicate that filter the records to be + considered during the reduce function. Default value: `nil` + - readPolicy: (Optional) Enforces a different `ReadPolicy` otherwise use + the client's `ReadPolicy`. Default value: `nil` + - completionHandler: Completion handler to be called once the + response returns + - returns: A `Request` instance which will allow cancel the request later */ @discardableResult open func group( @@ -632,7 +724,20 @@ open class DataStore where T: NSObject { } /** - Minimum aggregation function performed in the backend. + Minimum aggregation function. + Note: this function does not work on local data. It must be run against the + backend. + - Parameters: + - keys: Property names that should be grouped + - min: Property name used in the minimum operation + - minType: Integer type to be return as a result minimum + - condition: (Optional) Predicate that filter the records to be + considered during the reduce function. Default value: `nil` + - readPolicy: (Optional) Enforces a different `ReadPolicy` otherwise use + the client's `ReadPolicy`. Default value: `nil` + - completionHandler: Completion handler to be called once the + response returns + - returns: A `Request` instance which will allow cancel the request later */ @discardableResult open func group( @@ -660,7 +765,20 @@ open class DataStore where T: NSObject { } /** - Minimum aggregation function performed in the backend. + Minimum aggregation function. + Note: this function does not work on local data. It must be run against the + backend. + - Parameters: + - keys: Property names that should be grouped + - min: Property name used in the minimum operation + - minType: Integer type to be return as a result minimum + - condition: (Optional) Predicate that filter the records to be + considered during the reduce function. Default value: `nil` + - readPolicy: (Optional) Enforces a different `ReadPolicy` otherwise use + the client's `ReadPolicy`. Default value: `nil` + - completionHandler: Completion handler to be called once the + response returns + - returns: A `Request` instance which will allow cancel the request later */ @discardableResult open func group( @@ -702,7 +820,20 @@ open class DataStore where T: NSObject { } /** - Maximum aggregation function performed in the backend. + Maximum aggregation function. + Note: this function does not work on local data. It must be run against the + backend. + - Parameters: + - keys: Property names that should be grouped + - max: Property name used in the maximum operation + - maxType: Integer type to be return as a result maximum + - condition: (Optional) Predicate that filter the records to be + considered during the reduce function. Default value: `nil` + - readPolicy: (Optional) Enforces a different `ReadPolicy` otherwise use + the client's `ReadPolicy`. Default value: `nil` + - completionHandler: Completion handler to be called once the + response returns + - returns: A `Request` instance which will allow cancel the request later */ @discardableResult open func group( @@ -730,7 +861,20 @@ open class DataStore where T: NSObject { } /** - Maximum aggregation function performed in the backend. + Maximum aggregation function. + Note: this function does not work on local data. It must be run against the + backend. + - Parameters: + - keys: Property names that should be grouped + - max: Property name used in the maximum operation + - maxType: Integer type to be return as a result maximum + - condition: (Optional) Predicate that filter the records to be + considered during the reduce function. Default value: `nil` + - readPolicy: (Optional) Enforces a different `ReadPolicy` otherwise use + the client's `ReadPolicy`. Default value: `nil` + - completionHandler: Completion handler to be called once the + response returns + - returns: A `Request` instance which will allow cancel the request later */ @discardableResult open func group( @@ -1183,7 +1327,7 @@ open class DataStore where T: NSObject { } /** - Subscribe and start listening changes in the collection + Subscribe and start listening to changes in the collection */ @discardableResult open func subscribe( diff --git a/Kinvey/Kinvey/Kinvey.swift b/Kinvey/Kinvey/Kinvey.swift index 2e14bc3b8..2361ddcf1 100644 --- a/Kinvey/Kinvey/Kinvey.swift +++ b/Kinvey/Kinvey/Kinvey.swift @@ -12,8 +12,10 @@ import XCGLogger let ObjectIdTmpPrefix = "tmp_" /** - Shared client instance for simplicity. Use this instance if *you don't need* to - handle with multiple Kinvey environments. + Shared client instance for simplicity. All methods that use a client will + default to this instance. If you intend to use multiple backend apps or + environments, you should override this default by providing a separate Client + instance. */ public let sharedClient = Client.sharedClient @@ -39,7 +41,7 @@ fileprivate extension Keychain { } /** - Define how detailed operations should be logged. Here's the asceding order + Define how detailed operations should be logged. Here's the ascending order (from the less detailed to the most detailed level): none, severe, error, warning, info, debug, verbose */ diff --git a/Kinvey/Kinvey/Realtime.swift b/Kinvey/Kinvey/Realtime.swift index 656a26e53..d0dc0af0a 100644 --- a/Kinvey/Kinvey/Realtime.swift +++ b/Kinvey/Kinvey/Realtime.swift @@ -13,7 +13,7 @@ import PromiseKit /// Tells the current status for the realtime connection public enum RealtimeStatus { - /// Connection is stablished + /// Connection is established case connected /// Connection used to be on, but is now off diff --git a/Kinvey/Kinvey/Result.swift b/Kinvey/Kinvey/Result.swift index 0d56448e2..1d1265572 100644 --- a/Kinvey/Kinvey/Result.swift +++ b/Kinvey/Kinvey/Result.swift @@ -8,7 +8,18 @@ import Foundation -/// Enumeration that represents a result expected usually after an async call +/** + Enumeration that represents the result of an operation. + Here's a sample code how to handle a `Result` + ``` +switch result { +case .success(let successObject): + print("here you should handle the success case") +case .failure(let failureObject): + print("here you should handle the failure case") +} + ``` + */ public enum Result { /// Case when the result is a success result holding the succeed type value From b64052413a90b43893f3367254145528bc8caf90 Mon Sep 17 00:00:00 2001 From: Victor Barros Date: Thu, 13 Jul 2017 11:42:35 -0700 Subject: [PATCH 06/11] adding a unit test to test empty body errors --- Kinvey/KinveyTests/SyncStoreTests.swift | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Kinvey/KinveyTests/SyncStoreTests.swift b/Kinvey/KinveyTests/SyncStoreTests.swift index 42e52eeaa..ff93d941f 100644 --- a/Kinvey/KinveyTests/SyncStoreTests.swift +++ b/Kinvey/KinveyTests/SyncStoreTests.swift @@ -712,6 +712,41 @@ class SyncStoreTests: StoreTestCase { XCTAssertEqual(store.syncCount(), 0) } + func testPushError401EmptyBody() { + save() + + defer { + store.clearCache() + + XCTAssertEqual(store.syncCount(), 0) + } + + XCTAssertEqual(store.syncCount(), 1) + + if useMockData { + mockResponse(statusCode: 401, json: [:]) + } + defer { + if useMockData { setURLProtocol(nil) } + } + + weak var expectationPush = expectation(description: "Push") + + store.push() { count, error in + self.assertThread() + XCTAssertNil(count) + XCTAssertNotNil(error) + + expectationPush?.fulfill() + } + + waitForExpectations(timeout: defaultTimeout) { error in + expectationPush = nil + } + + XCTAssertEqual(store.syncCount(), 1) + } + func testPushInvalidDataStoreType() { save() From 80c3c526bb22ac3570e4c3feec832bc1dab81972 Mon Sep 17 00:00:00 2001 From: Victor Barros Date: Fri, 14 Jul 2017 11:05:29 -0700 Subject: [PATCH 07/11] MLIBZ-1970: bug fix for count not translating queries --- Kinvey/Kinvey/DataStore.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Kinvey/Kinvey/DataStore.swift b/Kinvey/Kinvey/DataStore.swift index f0f580e13..f85b9bbc9 100644 --- a/Kinvey/Kinvey/DataStore.swift +++ b/Kinvey/Kinvey/DataStore.swift @@ -329,7 +329,7 @@ open class DataStore where T: NSObject { @discardableResult open func count(_ query: Query? = nil, readPolicy: ReadPolicy? = nil, completionHandler: ((Result) -> Void)?) -> Request { let readPolicy = readPolicy ?? self.readPolicy - let operation = CountOperation(query: query, readPolicy: readPolicy, cache: cache, client: client) + let operation = CountOperation(query: Query(query: query ?? Query(), persistableType: T.self), readPolicy: readPolicy, cache: cache, client: client) let request = operation.execute { result in DispatchQueue.main.async { completionHandler?(result) From 614e6e7c61f658fbbbd34525c09c7e7d94700ac0 Mon Sep 17 00:00:00 2001 From: Victor Barros Date: Fri, 14 Jul 2017 12:18:31 -0700 Subject: [PATCH 08/11] unit test --- Kinvey/KinveyTests/NetworkStoreTests.swift | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Kinvey/KinveyTests/NetworkStoreTests.swift b/Kinvey/KinveyTests/NetworkStoreTests.swift index 81fb05840..1962d9f4a 100644 --- a/Kinvey/KinveyTests/NetworkStoreTests.swift +++ b/Kinvey/KinveyTests/NetworkStoreTests.swift @@ -389,6 +389,43 @@ class NetworkStoreTests: StoreTestCase { } } + func testCountTranslateQuery() { + let store = DataStore.collection(.network) + + if useMockData { + mockResponse { (request) -> HttpResponse in + let urlComponents = URLComponents(url: request.url!, resolvingAgainstBaseURL: false)! + let queryString = urlComponents.queryItems!.filter { $0.name == "query" }.first!.value! + let query = try! JSONSerialization.jsonObject(with: queryString.data(using: .utf8)!) as! JsonDictionary + XCTAssertNil(query["publishDate"]) + XCTAssertNotNil(query["date"]) + return HttpResponse(json: ["count" : 85]) + } + } + defer { + if useMockData { + setURLProtocol(nil) + } + } + + weak var expectationCount = expectation(description: "Count") + + let query = Query(format: "publishDate >= %@", Date()) + + store.count(query) { (count, error) in + XCTAssertNotNil(count) + XCTAssertNil(error) + + XCTAssertEqual(count, 85) + + expectationCount?.fulfill() + } + + waitForExpectations(timeout: defaultTimeout) { error in + expectationCount = nil + } + } + func testCountTimeoutError() { let store = DataStore.collection(.network) From 10eb17af5d2501864e4bb2626af127b6577ce67b Mon Sep 17 00:00:00 2001 From: Victor Barros Date: Tue, 20 Jun 2017 15:40:35 -0700 Subject: [PATCH 09/11] MLIBZ-1908: x-kinvey-custom-request-properties header --- Kinvey/Kinvey/AggregateOperation.swift | 23 +- Kinvey/Kinvey/Client.swift | 8 +- Kinvey/Kinvey/CountOperation.swift | 19 +- Kinvey/Kinvey/CustomEndpoint.swift | 219 ++++- Kinvey/Kinvey/DataStore.swift | 771 ++++++++++++++++-- Kinvey/Kinvey/FileStore.swift | 464 ++++++++++- Kinvey/Kinvey/FindOperation.swift | 65 +- Kinvey/Kinvey/GetOperation.swift | 19 +- Kinvey/Kinvey/HttpRequest.swift | 29 +- Kinvey/Kinvey/HttpRequestFactory.swift | 483 ++++++++--- Kinvey/Kinvey/MIC.swift | 89 +- Kinvey/Kinvey/Operation.swift | 9 +- Kinvey/Kinvey/PullOperation.swift | 20 +- Kinvey/Kinvey/PurgeOperation.swift | 18 +- Kinvey/Kinvey/Push.swift | 118 ++- Kinvey/Kinvey/PushOperation.swift | 19 +- Kinvey/Kinvey/ReadOperation.swift | 11 +- Kinvey/Kinvey/Realtime.swift | 90 +- Kinvey/Kinvey/RemoveByIdOperation.swift | 24 +- Kinvey/Kinvey/RemoveByQueryOperation.swift | 24 +- Kinvey/Kinvey/RemoveOperation.swift | 16 +- Kinvey/Kinvey/RequestFactory.swift | 143 ++-- Kinvey/Kinvey/SaveOperation.swift | 53 +- Kinvey/Kinvey/SyncOperation.swift | 11 +- Kinvey/Kinvey/User.swift | 718 ++++++++++++++-- Kinvey/Kinvey/WriteOperation.swift | 12 +- Kinvey/KinveyApp/AppDelegate.swift | 6 +- .../KinveyTests/DeltaSetCacheTestCase.swift | 47 +- Kinvey/KinveyTests/NetworkStoreTests.swift | 103 ++- Kinvey/KinveyTests/RealtimeTestCase.swift | 6 +- Kinvey/KinveyTests/UserTests.swift | 93 ++- Kinvey/RealtimeSender/ViewController.swift | 6 +- 32 files changed, 3176 insertions(+), 560 deletions(-) diff --git a/Kinvey/Kinvey/AggregateOperation.swift b/Kinvey/Kinvey/AggregateOperation.swift index 941047f7a..32b138cd1 100644 --- a/Kinvey/Kinvey/AggregateOperation.swift +++ b/Kinvey/Kinvey/AggregateOperation.swift @@ -13,10 +13,20 @@ class AggregateOperation: ReadOperation?, client: Client) { + init( + aggregation: Aggregation, + condition predicate: NSPredicate? = nil, + readPolicy: ReadPolicy, + cache: AnyCache?, + options: Options? + ) { self.aggregation = aggregation self.predicate = predicate - super.init(readPolicy: readPolicy, cache: cache, client: client) + super.init( + readPolicy: readPolicy, + cache: cache, + options: options + ) } func executeLocal(_ completionHandler: CompletionHandler? = nil) -> Request { @@ -32,7 +42,14 @@ class AggregateOperation: ReadOperation Request { - let request = client.networkRequestFactory.buildAppDataGroup(collectionName: T.collectionName(), keys: aggregation.keys, initialObject: aggregation.initialObject, reduceJSFunction: aggregation.reduceJSFunction, condition: predicate) + let request = client.networkRequestFactory.buildAppDataGroup( + collectionName: T.collectionName(), + keys: aggregation.keys, + initialObject: aggregation.initialObject, + reduceJSFunction: aggregation.reduceJSFunction, + condition: predicate, + options: options + ) request.execute() { data, response, error in if let response = response, response.isOK, let data = data, diff --git a/Kinvey/Kinvey/Client.swift b/Kinvey/Kinvey/Client.swift index 483374173..4e1ca6a13 100644 --- a/Kinvey/Kinvey/Client.swift +++ b/Kinvey/Kinvey/Client.swift @@ -92,11 +92,7 @@ open class Client: Credential { /// Timeout interval for this client instance. open var timeoutInterval: TimeInterval = 60 - /// App version for this client instance. - open var clientAppVersion: String? - - /// Custom request properties for this client instance. - open var customRequestProperties: [String : String] = [:] + open var options: Options? /// The default value for `apiHostName` variable. open static let defaultApiHostName = URL(string: "https://baas.kinvey.com/")! @@ -356,7 +352,7 @@ open class Client: Credential { } return LocalRequest() } - let request = networkRequestFactory.buildAppDataPing() + let request = networkRequestFactory.buildAppDataPing(options: options) Promise { fulfill, reject in request.execute() { data, response, error in if let response = response, diff --git a/Kinvey/Kinvey/CountOperation.swift b/Kinvey/Kinvey/CountOperation.swift index b76ba7518..6425b98d6 100644 --- a/Kinvey/Kinvey/CountOperation.swift +++ b/Kinvey/Kinvey/CountOperation.swift @@ -12,9 +12,18 @@ class CountOperation: ReadOperation, ReadOp let query: Query? - init(query: Query? = nil, readPolicy: ReadPolicy, cache: AnyCache?, client: Client) { + init( + query: Query? = nil, + readPolicy: ReadPolicy, + cache: AnyCache?, + options: Options? + ) { self.query = query - super.init(readPolicy: readPolicy, cache: cache, client: client) + super.init( + readPolicy: readPolicy, + cache: cache, + options: options + ) } func executeLocal(_ completionHandler: CompletionHandler? = nil) -> Request { @@ -31,7 +40,11 @@ class CountOperation: ReadOperation, ReadOp } func executeNetwork(_ completionHandler: CompletionHandler? = nil) -> Request { - let request = client.networkRequestFactory.buildAppDataCountByQuery(collectionName: T.collectionName(), query: query) + let request = client.networkRequestFactory.buildAppDataCountByQuery( + collectionName: T.collectionName(), + query: query, + options: options + ) request.execute() { data, response, error in if let response = response, response.isOK, let data = data, diff --git a/Kinvey/Kinvey/CustomEndpoint.swift b/Kinvey/Kinvey/CustomEndpoint.swift index bfd2cc35b..9d95c1324 100644 --- a/Kinvey/Kinvey/CustomEndpoint.swift +++ b/Kinvey/Kinvey/CustomEndpoint.swift @@ -54,8 +54,17 @@ open class CustomEndpoint { /// Completion handler block for execute custom endpoints. public typealias CompletionHandler = (T?, Swift.Error?) -> Void - private static func callEndpoint(_ name: String, params: Params? = nil, client: Client, completionHandler: DataResponseCompletionHandler? = nil) -> Request { - let request = client.networkRequestFactory.buildCustomEndpoint(name) + private static func callEndpoint( + _ name: String, + params: Params? = nil, + options: Options?, + completionHandler: DataResponseCompletionHandler? = nil + ) -> Request { + let client = options?.client ?? sharedClient + let request = client.networkRequestFactory.buildCustomEndpoint( + name, + options: options + ) if let params = params { request.request.setValue("application/json", forHTTPHeaderField: "Content-Type") switch params.value { @@ -73,11 +82,22 @@ open class CustomEndpoint { /// Executes a custom endpoint by name and passing the expected parameters. @discardableResult @available(*, deprecated, message: "Please use the generic version of execute(params: CustomEndpoint.Params?) method") - open static func execute(_ name: String, params: JsonDictionary? = nil, client: Client = sharedClient, completionHandler: CompletionHandler? = nil) -> Request { + open static func execute( + _ name: String, + params: JsonDictionary? = nil, + client: Client = sharedClient, + completionHandler: CompletionHandler? = nil + ) -> Request { let params = params != nil ? Params(params!) : nil var request: Request! Promise { fulfill, reject in - request = callEndpoint(name, params: params, client: client) { data, response, error in + request = callEndpoint( + name, + params: params, + options: Options( + client: client + ) + ) { data, response, error in if let response = response, response.isOK, let json: JsonDictionary = client.responseParser.parse(data) { fulfill(json) } else { @@ -95,11 +115,22 @@ open class CustomEndpoint { /// Executes a custom endpoint by name and passing the expected parameters. @discardableResult @available(*, deprecated, message: "Please use the generic version of execute(params: CustomEndpoint.Params?) method") - open static func execute(_ name: String, params: JsonDictionary? = nil, client: Client = sharedClient, completionHandler: CompletionHandler<[JsonDictionary]>? = nil) -> Request { + open static func execute( + _ name: String, + params: JsonDictionary? = nil, + client: Client = sharedClient, + completionHandler: CompletionHandler<[JsonDictionary]>? = nil + ) -> Request { let params = params != nil ? Params(params!) : nil var request: Request! Promise<[JsonDictionary]> { fulfill, reject in - request = callEndpoint(name, params: params, client: client) { data, response, error in + request = callEndpoint( + name, + params: params, + options: Options( + client: client + ) + ) { data, response, error in if let response = response, response.isOK, let json = client.responseParser.parseArray(data) { fulfill(json) } else { @@ -116,7 +147,12 @@ open class CustomEndpoint { /// Executes a custom endpoint by name and passing the expected parameters. @discardableResult - open static func execute(_ name: String, params: Params? = nil, client: Client = sharedClient, completionHandler: CompletionHandler? = nil) -> Request { + open static func execute( + _ name: String, + params: Params? = nil, + client: Client = sharedClient, + completionHandler: CompletionHandler? = nil + ) -> Request { return execute( name, params: params, @@ -133,11 +169,42 @@ open class CustomEndpoint { /// Executes a custom endpoint by name and passing the expected parameters. @discardableResult - open static func execute(_ name: String, params: Params? = nil, client: Client = sharedClient, completionHandler: ((Result) -> Void)? = nil) -> Request { + open static func execute( + _ name: String, + params: Params? = nil, + client: Client = sharedClient, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return execute( + name, + params: params, + options: Options( + client: client + ), + completionHandler: completionHandler + ) + } + + /// Executes a custom endpoint by name and passing the expected parameters. + @discardableResult + open static func execute( + _ name: String, + params: Params? = nil, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let client = options?.client ?? sharedClient var request: Request! Promise { fulfill, reject in - request = callEndpoint(name, params: params, client: client) { data, response, error in - if let response = response, response.isOK, let json = client.responseParser.parse(data) { + request = callEndpoint( + name, + params: params, + options: options + ) { data, response, error in + if let response = response, + response.isOK, + let json = client.responseParser.parse(data) + { fulfill(json) } else { reject(buildError(data, response, error, client)) @@ -153,7 +220,12 @@ open class CustomEndpoint { /// Executes a custom endpoint by name and passing the expected parameters. @discardableResult - open static func execute(_ name: String, params: Params? = nil, client: Client = sharedClient, completionHandler: CompletionHandler<[JsonDictionary]>? = nil) -> Request { + open static func execute( + _ name: String, + params: Params? = nil, + client: Client = sharedClient, + completionHandler: CompletionHandler<[JsonDictionary]>? = nil + ) -> Request { return execute( name, params: params, @@ -170,11 +242,42 @@ open class CustomEndpoint { /// Executes a custom endpoint by name and passing the expected parameters. @discardableResult - open static func execute(_ name: String, params: Params? = nil, client: Client = sharedClient, completionHandler: ((Result<[JsonDictionary], Swift.Error>) -> Void)? = nil) -> Request { + open static func execute( + _ name: String, + params: Params? = nil, + client: Client = sharedClient, + completionHandler: ((Result<[JsonDictionary], Swift.Error>) -> Void)? = nil + ) -> Request { + return execute( + name, + params: params, + options: Options( + client: client + ), + completionHandler: completionHandler + ) + } + + /// Executes a custom endpoint by name and passing the expected parameters. + @discardableResult + open static func execute( + _ name: String, + params: Params? = nil, + options: Options? = nil, + completionHandler: ((Result<[JsonDictionary], Swift.Error>) -> Void)? = nil + ) -> Request { + let client = options?.client ?? sharedClient var request: Request! Promise<[JsonDictionary]> { fulfill, reject in - request = callEndpoint(name, params: params, client: client) { data, response, error in - if let response = response, response.isOK, let json = client.responseParser.parseArray(data) { + request = callEndpoint( + name, + params: params, + options: options + ) { data, response, error in + if let response = response, + response.isOK, + let json = client.responseParser.parseArray(data) + { fulfill(json) } else { reject(buildError(data, response, error, client)) @@ -192,7 +295,12 @@ open class CustomEndpoint { /// Executes a custom endpoint by name and passing the expected parameters. @discardableResult - open static func execute(_ name: String, params: Params? = nil, client: Client = sharedClient, completionHandler: CompletionHandler? = nil) -> Request { + open static func execute( + _ name: String, + params: Params? = nil, + client: Client = sharedClient, + completionHandler: CompletionHandler? = nil + ) -> Request { return execute( name, params: params, @@ -209,11 +317,42 @@ open class CustomEndpoint { /// Executes a custom endpoint by name and passing the expected parameters. @discardableResult - open static func execute(_ name: String, params: Params? = nil, client: Client = sharedClient, completionHandler: ((Result) -> Void)? = nil) -> Request { + open static func execute( + _ name: String, + params: Params? = nil, + client: Client = sharedClient, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return execute( + name, + params: params, + options: Options( + client: client + ), + completionHandler: completionHandler + ) + } + + /// Executes a custom endpoint by name and passing the expected parameters. + @discardableResult + open static func execute( + _ name: String, + params: Params? = nil, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let client = options?.client ?? sharedClient var request: Request! Promise { fulfill, reject in - request = callEndpoint(name, params: params, client: client) { data, response, error in - if let response = response, response.isOK, let obj: T = client.responseParser.parse(data) { + request = callEndpoint( + name, + params: params, + options: options + ) { data, response, error in + if let response = response, + response.isOK, + let obj: T = client.responseParser.parse(data) + { fulfill(obj) } else { reject(buildError(data, response, error, client)) @@ -229,7 +368,12 @@ open class CustomEndpoint { /// Executes a custom endpoint by name and passing the expected parameters. @discardableResult - open static func execute(_ name: String, params: Params? = nil, client: Client = sharedClient, completionHandler: CompletionHandler<[T]>? = nil) -> Request { + open static func execute( + _ name: String, + params: Params? = nil, + client: Client = sharedClient, + completionHandler: CompletionHandler<[T]>? = nil + ) -> Request { return execute( name, params: params, @@ -246,11 +390,42 @@ open class CustomEndpoint { /// Executes a custom endpoint by name and passing the expected parameters. @discardableResult - open static func execute(_ name: String, params: Params? = nil, client: Client = sharedClient, completionHandler: ((Result<[T], Swift.Error>) -> Void)? = nil) -> Request { + open static func execute( + _ name: String, + params: Params? = nil, + client: Client = sharedClient, + completionHandler: ((Result<[T], Swift.Error>) -> Void)? = nil + ) -> Request { + return execute( + name, + params: params, + options: Options( + client: client + ), + completionHandler: completionHandler + ) + } + + /// Executes a custom endpoint by name and passing the expected parameters. + @discardableResult + open static func execute( + _ name: String, + params: Params? = nil, + options: Options? = nil, + completionHandler: ((Result<[T], Swift.Error>) -> Void)? = nil + ) -> Request { + let client = options?.client ?? sharedClient var request: Request! Promise<[T]> { fulfill, reject in - request = callEndpoint(name, params: params, client: client) { data, response, error in - if let response = response, response.isOK, let objArray: [T] = client.responseParser.parse(data) { + request = callEndpoint( + name, + params: params, + options: options + ) { data, response, error in + if let response = response, + response.isOK, + let objArray: [T] = client.responseParser.parse(data) + { fulfill(objArray) } else { reject(buildError(data, response, error, client)) diff --git a/Kinvey/Kinvey/DataStore.swift b/Kinvey/Kinvey/DataStore.swift index f85b9bbc9..d44bb2345 100644 --- a/Kinvey/Kinvey/DataStore.swift +++ b/Kinvey/Kinvey/DataStore.swift @@ -215,8 +215,15 @@ open class DataStore where T: NSObject { - returns: A `Request` instance which will allow cancel the request later */ @discardableResult - open func find(_ id: String, readPolicy: ReadPolicy? = nil, completionHandler: @escaping ObjectCompletionHandler) -> Request { - return find(id, readPolicy: readPolicy) { (result: Result) in + open func find( + _ id: String, + readPolicy: ReadPolicy? = nil, + completionHandler: @escaping ObjectCompletionHandler + ) -> Request { + return find( + id, + readPolicy: readPolicy + ) { (result: Result) in switch result { case .success(let obj): completionHandler(obj, nil) @@ -238,11 +245,44 @@ open class DataStore where T: NSObject { - returns: A `Request` instance which will allow cancel the request later */ @discardableResult - open func find(_ id: String, readPolicy: ReadPolicy? = nil, completionHandler: @escaping (Result) -> Void) -> Request { + open func find( + _ id: String, + readPolicy: ReadPolicy? = nil, + completionHandler: @escaping (Result) -> Void + ) -> Request { + return find( + id, + options: Options( + readPolicy: readPolicy + ), + completionHandler: completionHandler + ) + } + + /** + Gets a single record using the `_id` of the record. + + PS: This method is just a shortcut for `findById()` + - parameter id: The `_id` value of the entity to be find + - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` + - parameter completionHandler: Completion handler to be called once the respose returns + - returns: A `Request` instance which will allow cancel the request later + */ + @discardableResult + open func find( + _ id: String, + options: Options? = nil, + completionHandler: @escaping (Result) -> Void + ) -> Request { validate(id: id) - let readPolicy = readPolicy ?? self.readPolicy - let operation = GetOperation(id: id, readPolicy: readPolicy, cache: cache, client: client) + let readPolicy = options?.readPolicy ?? self.readPolicy + let operation = GetOperation( + id: id, + readPolicy: readPolicy, + cache: cache, + options: options + ) let request = operation.execute { result in DispatchQueue.main.async { completionHandler(result) @@ -262,8 +302,17 @@ open class DataStore where T: NSObject { - returns: A `Request` instance which will allow cancel the request later */ @discardableResult - open func find(_ query: Query = Query(), deltaSet: Bool? = nil, readPolicy: ReadPolicy? = nil, completionHandler: @escaping ArrayCompletionHandler) -> Request { - return find(query, deltaSet: deltaSet, readPolicy: readPolicy) { (result: Result<[T], Swift.Error>) in + open func find( + _ query: Query = Query(), + deltaSet: Bool? = nil, + readPolicy: ReadPolicy? = nil, + completionHandler: @escaping ArrayCompletionHandler + ) -> Request { + return find( + query, + deltaSet: deltaSet, + readPolicy: readPolicy + ) { (result: Result<[T], Swift.Error>) in switch result { case .success(let objs): completionHandler(objs, nil) @@ -282,10 +331,45 @@ open class DataStore where T: NSObject { - returns: A `Request` instance which will allow cancel the request later */ @discardableResult - open func find(_ query: Query = Query(), deltaSet: Bool? = nil, readPolicy: ReadPolicy? = nil, completionHandler: @escaping (Result<[T], Swift.Error>) -> Void) -> Request { - let readPolicy = readPolicy ?? self.readPolicy - let deltaSet = deltaSet ?? self.deltaSet - let operation = FindOperation(query: Query(query: query, persistableType: T.self), deltaSet: deltaSet, readPolicy: readPolicy, cache: cache, client: client) + open func find( + _ query: Query = Query(), + deltaSet: Bool? = nil, + readPolicy: ReadPolicy? = nil, + completionHandler: @escaping (Result<[T], Swift.Error>) -> Void + ) -> Request { + return find( + query, + options: Options( + deltaSet: deltaSet, + readPolicy: readPolicy + ), + completionHandler: completionHandler + ) + } + + /** + Gets a list of records that matches with the query passed by parameter. + - parameter query: The query used to filter the results + - parameter deltaSet: Enforces delta set cache otherwise use the client's `deltaSet` value. Default value: `false` + - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` + - parameter completionHandler: Completion handler to be called once the respose returns + - returns: A `Request` instance which will allow cancel the request later + */ + @discardableResult + open func find( + _ query: Query = Query(), + options: Options? = nil, + completionHandler: @escaping (Result<[T], Swift.Error>) -> Void + ) -> Request { + let readPolicy = options?.readPolicy ?? self.readPolicy + let deltaSet = options?.deltaSet ?? self.deltaSet + let operation = FindOperation( + query: Query(query: query, persistableType: T.self), + deltaSet: deltaSet, + readPolicy: readPolicy, + cache: cache, + options: options + ) let request = operation.execute { result in DispatchQueue.main.async { completionHandler(result) @@ -304,8 +388,15 @@ open class DataStore where T: NSObject { - returns: A `Request` instance which will allow cancel the request later */ @discardableResult - open func count(_ query: Query? = nil, readPolicy: ReadPolicy? = nil, completionHandler: IntCompletionHandler?) -> Request { - return count(query, readPolicy: readPolicy) { (result: Result) in + open func count( + _ query: Query? = nil, + readPolicy: ReadPolicy? = nil, + completionHandler: IntCompletionHandler? + ) -> Request { + return count( + query, + readPolicy: readPolicy + ) { (result: Result) in switch result { case .success(let count): completionHandler?(count, nil) @@ -327,9 +418,40 @@ open class DataStore where T: NSObject { - returns: A `Request` instance which will allow cancel the request later */ @discardableResult - open func count(_ query: Query? = nil, readPolicy: ReadPolicy? = nil, completionHandler: ((Result) -> Void)?) -> Request { - let readPolicy = readPolicy ?? self.readPolicy - let operation = CountOperation(query: Query(query: query ?? Query(), persistableType: T.self), readPolicy: readPolicy, cache: cache, client: client) + open func count( + _ query: Query? = nil, + readPolicy: ReadPolicy? = nil, + completionHandler: ((Result) -> Void)? + ) -> Request { + return count( + query, + options: Options( + readPolicy: readPolicy + ), + completionHandler: completionHandler + ) + } + + /** + Gets a count of how many records that matches with the (optional) query passed by parameter. + - parameter query: The query used to filter the results + - parameter readPolicy: Enforces a different `ReadPolicy` otherwise use the client's `ReadPolicy`. Default value: `nil` + - parameter completionHandler: Completion handler to be called once the respose returns + - returns: A `Request` instance which will allow cancel the request later + */ + @discardableResult + open func count( + _ query: Query? = nil, + options: Options? = nil, + completionHandler: ((Result) -> Void)? + ) -> Request { + let readPolicy = options?.readPolicy ?? self.readPolicy + let operation = CountOperation( + query: Query(query: query ?? Query(), persistableType: T.self), + readPolicy: readPolicy, + cache: cache, + options: options + ) let request = operation.execute { result in DispatchQueue.main.async { completionHandler?(result) @@ -408,7 +530,28 @@ open class DataStore where T: NSObject { readPolicy: ReadPolicy? = nil, completionHandler: @escaping (Result<[AggregationCustomResult], Swift.Error>) -> Void ) -> Request { - let readPolicy = readPolicy ?? self.readPolicy + return group( + keys: keys, + initialObject: initialObject, + reduceJSFunction: reduceJSFunction, + condition: condition, + options: Options( + readPolicy: readPolicy + ), + completionHandler: completionHandler + ) + } + + @discardableResult + open func group( + keys: [String]? = nil, + initialObject: JsonDictionary, + reduceJSFunction: String, + condition: NSPredicate? = nil, + options: Options? = nil, + completionHandler: @escaping (Result<[AggregationCustomResult], Swift.Error>) -> Void + ) -> Request { + let readPolicy = options?.readPolicy ?? self.readPolicy let keys = keys ?? [] let aggregation: Aggregation = .custom( keys: keys, @@ -420,7 +563,7 @@ open class DataStore where T: NSObject { condition: condition, readPolicy: readPolicy, cache: cache, - client: client + options: options ) let request = operation.execute { result in switch result { @@ -501,14 +644,33 @@ open class DataStore where T: NSObject { readPolicy: ReadPolicy? = nil, completionHandler: @escaping (Result<[AggregationCountResult], Swift.Error>) -> Void ) -> Request { - let readPolicy = readPolicy ?? self.readPolicy + return group( + count: keys, + countType: countType, + condition: condition, + options: Options( + readPolicy: readPolicy + ), + completionHandler: completionHandler + ) + } + + @discardableResult + open func group( + count keys: [String], + countType: Count.Type? = nil, + condition: NSPredicate? = nil, + options: Options? = nil, + completionHandler: @escaping (Result<[AggregationCountResult], Swift.Error>) -> Void + ) -> Request { + let readPolicy = options?.readPolicy ?? self.readPolicy let aggregation: Aggregation = .count(keys: keys) let operation = AggregateOperation( aggregation: aggregation, condition: condition, readPolicy: readPolicy, cache: cache, - client: client + options: options ) let request = operation.execute { result in switch result { @@ -597,14 +759,35 @@ open class DataStore where T: NSObject { readPolicy: ReadPolicy? = nil, completionHandler: @escaping (Result<[AggregationSumResult], Swift.Error>) -> Void ) -> Request { - let readPolicy = readPolicy ?? self.readPolicy + return group( + keys: keys, + sum: sum, + sumType: sumType, + condition: condition, + options: Options( + readPolicy: readPolicy + ), + completionHandler: completionHandler + ) + } + + @discardableResult + open func group( + keys: [String], + sum: String, + sumType: Sum.Type? = nil, + condition: NSPredicate? = nil, + options: Options? = nil, + completionHandler: @escaping (Result<[AggregationSumResult], Swift.Error>) -> Void + ) -> Request { + let readPolicy = options?.readPolicy ?? self.readPolicy let aggregation: Aggregation = .sum(keys: keys, sum: sum) let operation = AggregateOperation( aggregation: aggregation, condition: condition, readPolicy: readPolicy, cache: cache, - client: client + options: options ) let request = operation.execute { result in switch result { @@ -693,14 +876,35 @@ open class DataStore where T: NSObject { readPolicy: ReadPolicy? = nil, completionHandler: @escaping (Result<[AggregationAvgResult], Swift.Error>) -> Void ) -> Request { - let readPolicy = readPolicy ?? self.readPolicy + return group( + keys: keys, + avg: avg, + avgType: avgType, + condition: condition, + options: Options( + readPolicy: readPolicy + ), + completionHandler: completionHandler + ) + } + + @discardableResult + open func group( + keys: [String], + avg: String, + avgType: Avg.Type? = nil, + condition: NSPredicate? = nil, + options: Options? = nil, + completionHandler: @escaping (Result<[AggregationAvgResult], Swift.Error>) -> Void + ) -> Request { + let readPolicy = options?.readPolicy ?? self.readPolicy let aggregation: Aggregation = .avg(keys: keys, avg: avg) let operation = AggregateOperation( aggregation: aggregation, condition: condition, readPolicy: readPolicy, cache: cache, - client: client + options: options ) let request = operation.execute { result in switch result { @@ -789,14 +993,35 @@ open class DataStore where T: NSObject { readPolicy: ReadPolicy? = nil, completionHandler: @escaping (Result<[AggregationMinResult], Swift.Error>) -> Void ) -> Request { - let readPolicy = readPolicy ?? self.readPolicy + return group( + keys: keys, + min: min, + minType: minType, + condition: condition, + options: Options( + readPolicy: readPolicy + ), + completionHandler: completionHandler + ) + } + + @discardableResult + open func group( + keys: [String], + min: String, + minType: Min.Type? = nil, + condition: NSPredicate? = nil, + options: Options? = nil, + completionHandler: @escaping (Result<[AggregationMinResult], Swift.Error>) -> Void + ) -> Request { + let readPolicy = options?.readPolicy ?? self.readPolicy let aggregation: Aggregation = .min(keys: keys, min: min) let operation = AggregateOperation( aggregation: aggregation, condition: condition, readPolicy: readPolicy, cache: cache, - client: client + options: options ) let request = operation.execute { result in switch result { @@ -885,14 +1110,35 @@ open class DataStore where T: NSObject { readPolicy: ReadPolicy? = nil, completionHandler: @escaping (Result<[AggregationMaxResult], Swift.Error>) -> Void ) -> Request { - let readPolicy = readPolicy ?? self.readPolicy + return group( + keys: keys, + max: max, + maxType: maxType, + condition: condition, + options: Options( + readPolicy: readPolicy + ), + completionHandler: completionHandler + ) + } + + @discardableResult + open func group( + keys: [String], + max: String, + maxType: Max.Type? = nil, + condition: NSPredicate? = nil, + options: Options? = nil, + completionHandler: @escaping (Result<[AggregationMaxResult], Swift.Error>) -> Void + ) -> Request { + let readPolicy = options?.readPolicy ?? self.readPolicy let aggregation: Aggregation = .max(keys: keys, max: max) let operation = AggregateOperation( aggregation: aggregation, condition: condition, readPolicy: readPolicy, cache: cache, - client: client + options: options ) let request = operation.execute { result in switch result { @@ -916,9 +1162,17 @@ open class DataStore where T: NSObject { } /// Creates or updates a record. + @available(*, deprecated: 3.6.0, message: "Please use save(_:options:completionHandler:) instead") @discardableResult - open func save(_ persistable: T, writePolicy: WritePolicy? = nil, completionHandler: ObjectCompletionHandler? = nil) -> Request { - return save(persistable, writePolicy: writePolicy) { (result: Result) in + open func save( + _ persistable: T, + writePolicy: WritePolicy? = nil, + completionHandler: ObjectCompletionHandler? = nil + ) -> Request { + return save( + persistable, + writePolicy: writePolicy + ) { (result: Result) in switch result { case .success(let obj): completionHandler?(obj, nil) @@ -928,11 +1182,38 @@ open class DataStore where T: NSObject { } } + /// Creates or updates a record. + @available(*, deprecated: 3.6.0, message: "Please use save(_:options:completionHandler:) instead") + @discardableResult + open func save( + _ persistable: T, + writePolicy: WritePolicy? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return save( + persistable, + options: Options( + writePolicy: writePolicy + ), + completionHandler: completionHandler + ) + } + /// Creates or updates a record. @discardableResult - open func save(_ persistable: T, writePolicy: WritePolicy? = nil, completionHandler: ((Result) -> Void)? = nil) -> Request { - let writePolicy = writePolicy ?? self.writePolicy - let operation = SaveOperation(persistable: persistable, writePolicy: writePolicy, sync: sync, cache: cache, client: client) + open func save( + _ persistable: T, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let writePolicy = options?.writePolicy ?? self.writePolicy + let operation = SaveOperation( + persistable: persistable, + writePolicy: writePolicy, + sync: sync, + cache: cache, + options: options + ) let request = operation.execute { result in DispatchQueue.main.async { completionHandler?(result) @@ -943,8 +1224,15 @@ open class DataStore where T: NSObject { /// Deletes a record. @discardableResult - open func remove(_ persistable: T, writePolicy: WritePolicy? = nil, completionHandler: IntCompletionHandler?) throws -> Request { - return try remove(persistable, writePolicy: writePolicy) { (result: Result) in + open func remove( + _ persistable: T, + writePolicy: WritePolicy? = nil, + completionHandler: IntCompletionHandler? + ) throws -> Request { + return try remove( + persistable, + writePolicy: writePolicy + ) { (result: Result) in switch result { case .success(let count): completionHandler?(count, nil) @@ -956,18 +1244,49 @@ open class DataStore where T: NSObject { /// Deletes a record. @discardableResult - open func remove(_ persistable: T, writePolicy: WritePolicy? = nil, completionHandler: ((Result) -> Void)?) throws -> Request { + open func remove( + _ persistable: T, + writePolicy: WritePolicy? = nil, + completionHandler: ((Result) -> Void)? + ) throws -> Request { + return try remove( + persistable, + options: Options( + writePolicy: writePolicy + ), + completionHandler: completionHandler + ) + } + + /// Deletes a record. + @discardableResult + open func remove( + _ persistable: T, + options: Options? = nil, + completionHandler: ((Result) -> Void)? + ) throws -> Request { guard let id = persistable.entityId else { log.error("Object Id is missing") throw Error.objectIdMissing } - return remove(byId: id, writePolicy:writePolicy, completionHandler: completionHandler) + return remove( + byId: id, + options: options, + completionHandler: completionHandler + ) } /// Deletes a list of records. @discardableResult - open func remove(_ array: [T], writePolicy: WritePolicy? = nil, completionHandler: IntCompletionHandler?) -> Request { - return remove(array, writePolicy: writePolicy) { (result: Result) in + open func remove( + _ array: [T], + writePolicy: WritePolicy? = nil, + completionHandler: IntCompletionHandler? + ) -> Request { + return remove( + array, + writePolicy: writePolicy + ) { (result: Result) in switch result { case .success(let count): completionHandler?(count, nil) @@ -979,27 +1298,66 @@ open class DataStore where T: NSObject { /// Deletes a list of records. @discardableResult - open func remove(_ array: [T], writePolicy: WritePolicy? = nil, completionHandler: ((Result) -> Void)?) -> Request { + open func remove( + _ array: [T], + writePolicy: WritePolicy? = nil, + completionHandler: ((Result) -> Void)? + ) -> Request { + return remove( + array, + options: Options( + writePolicy: writePolicy + ), + completionHandler: completionHandler + ) + } + + /// Deletes a list of records. + @discardableResult + open func remove( + _ array: [T], + options: Options? = nil, + completionHandler: ((Result) -> Void)? + ) -> Request { var ids: [String] = [] for persistable in array { if let id = persistable.entityId { ids.append(id) } } - return remove(byIds: ids, writePolicy:writePolicy, completionHandler: completionHandler) + return remove( + byIds: ids, + options: options, + completionHandler: completionHandler + ) } /// Deletes a record using the `_id` of the record. @discardableResult @available(*, deprecated: 3.4.0, message: "Please use `remove(byId:)` instead") - open func removeById(_ id: String, writePolicy: WritePolicy? = nil, completionHandler: IntCompletionHandler?) -> Request { - return remove(byId: id, writePolicy: writePolicy, completionHandler: completionHandler) + open func removeById( + _ id: String, + writePolicy: WritePolicy? = nil, + completionHandler: IntCompletionHandler? + ) -> Request { + return remove( + byId: id, + writePolicy: writePolicy, + completionHandler: completionHandler + ) } /// Deletes a record using the `_id` of the record. @discardableResult - open func remove(byId id: String, writePolicy: WritePolicy? = nil, completionHandler: IntCompletionHandler?) -> Request { - return remove(byId: id, writePolicy: writePolicy) { (result: Result) in + open func remove( + byId id: String, + writePolicy: WritePolicy? = nil, + completionHandler: IntCompletionHandler? + ) -> Request { + return remove( + byId: id, + writePolicy: writePolicy + ) { (result: Result) in switch result { case .success(let count): completionHandler?(count, nil) @@ -1011,11 +1369,37 @@ open class DataStore where T: NSObject { /// Deletes a record using the `_id` of the record. @discardableResult - open func remove(byId id: String, writePolicy: WritePolicy? = nil, completionHandler: ((Result) -> Void)?) -> Request { + open func remove( + byId id: String, + writePolicy: WritePolicy? = nil, + completionHandler: ((Result) -> Void)? + ) -> Request { + return remove( + byId: id, + options: Options( + writePolicy: writePolicy + ), + completionHandler: completionHandler + ) + } + + /// Deletes a record using the `_id` of the record. + @discardableResult + open func remove( + byId id: String, + options: Options? = nil, + completionHandler: ((Result) -> Void)? + ) -> Request { validate(id: id) - let writePolicy = writePolicy ?? self.writePolicy - let operation = RemoveByIdOperation(objectId: id, writePolicy: writePolicy, sync: sync, cache: cache, client: client) + let writePolicy = options?.writePolicy ?? self.writePolicy + let operation = RemoveByIdOperation( + objectId: id, + writePolicy: writePolicy, + sync: sync, + cache: cache, + options: options + ) let request = operation.execute { result in DispatchQueue.main.async { completionHandler?(result) @@ -1027,14 +1411,29 @@ open class DataStore where T: NSObject { /// Deletes a list of records using the `_id` of the records. @discardableResult @available(*, deprecated: 3.4.0, message: "Please use `remove(byIds:)` instead") - open func removeById(_ ids: [String], writePolicy: WritePolicy? = nil, completionHandler: IntCompletionHandler?) -> Request { - return remove(byIds: ids, writePolicy: writePolicy, completionHandler: completionHandler) + open func removeById( + _ ids: [String], + writePolicy: WritePolicy? = nil, + completionHandler: IntCompletionHandler? + ) -> Request { + return remove( + byIds: ids, + writePolicy: writePolicy, + completionHandler: completionHandler + ) } /// Deletes a list of records using the `_id` of the records. @discardableResult - open func remove(byIds ids: [String], writePolicy: WritePolicy? = nil, completionHandler: IntCompletionHandler?) -> Request { - return remove(byIds: ids, writePolicy: writePolicy) { (result: Result) in + open func remove( + byIds ids: [String], + writePolicy: WritePolicy? = nil, + completionHandler: IntCompletionHandler? + ) -> Request { + return remove( + byIds: ids, + writePolicy: writePolicy + ) { (result: Result) in switch result { case .success(let count): completionHandler?(count, nil) @@ -1046,7 +1445,27 @@ open class DataStore where T: NSObject { /// Deletes a list of records using the `_id` of the records. @discardableResult - open func remove(byIds ids: [String], writePolicy: WritePolicy? = nil, completionHandler: ((Result) -> Void)?) -> Request { + open func remove( + byIds ids: [String], + writePolicy: WritePolicy? = nil, + completionHandler: ((Result) -> Void)? + ) -> Request { + return remove( + byIds: ids, + options: Options( + writePolicy: writePolicy + ), + completionHandler: completionHandler + ) + } + + /// Deletes a list of records using the `_id` of the records. + @discardableResult + open func remove( + byIds ids: [String], + options: Options? = nil, + completionHandler: ((Result) -> Void)? + ) -> Request { guard !ids.isEmpty else { DispatchQueue.main.async { completionHandler?(.failure(Error.invalidOperation(description: "ids cannot be an empty array"))) @@ -1055,13 +1474,24 @@ open class DataStore where T: NSObject { } let query = Query(format: "\(T.entityIdProperty()) IN %@", ids as AnyObject) - return remove(query, writePolicy: writePolicy, completionHandler: completionHandler) + return remove( + query, + options: options, + completionHandler: completionHandler + ) } /// Deletes a list of records that matches with the query passed by parameter. @discardableResult - open func remove(_ query: Query = Query(), writePolicy: WritePolicy? = nil, completionHandler: IntCompletionHandler?) -> Request { - return remove(query, writePolicy: writePolicy) { (result: Result) in + open func remove( + _ query: Query = Query(), + writePolicy: WritePolicy? = nil, + completionHandler: IntCompletionHandler? + ) -> Request { + return remove( + query, + writePolicy: writePolicy + ) { (result: Result) in switch result { case .success(let count): completionHandler?(count, nil) @@ -1073,9 +1503,35 @@ open class DataStore where T: NSObject { /// Deletes a list of records that matches with the query passed by parameter. @discardableResult - open func remove(_ query: Query = Query(), writePolicy: WritePolicy? = nil, completionHandler: ((Result) -> Void)?) -> Request { - let writePolicy = writePolicy ?? self.writePolicy - let operation = RemoveByQueryOperation(query: Query(query: query, persistableType: T.self), writePolicy: writePolicy, sync: sync, cache: cache, client: client) + open func remove( + _ query: Query = Query(), + writePolicy: WritePolicy? = nil, + completionHandler: ((Result) -> Void)? + ) -> Request { + return remove( + query, + options: Options( + writePolicy: writePolicy + ), + completionHandler: completionHandler + ) + } + + /// Deletes a list of records that matches with the query passed by parameter. + @discardableResult + open func remove( + _ query: Query = Query(), + options: Options? = nil, + completionHandler: ((Result) -> Void)? + ) -> Request { + let writePolicy = options?.writePolicy ?? self.writePolicy + let operation = RemoveByQueryOperation( + query: Query(query: query, persistableType: T.self), + writePolicy: writePolicy, + sync: sync, + cache: cache, + options: options + ) let request = operation.execute { result in DispatchQueue.main.async { completionHandler?(result) @@ -1086,8 +1542,13 @@ open class DataStore where T: NSObject { /// Deletes all the records. @discardableResult - open func removeAll(_ writePolicy: WritePolicy? = nil, completionHandler: IntCompletionHandler?) -> Request { - return removeAll(writePolicy) { (result: Result) in + open func removeAll( + _ writePolicy: WritePolicy? = nil, + completionHandler: IntCompletionHandler? + ) -> Request { + return removeAll( + writePolicy + ) { (result: Result) in switch result { case .success(let count): completionHandler?(count, nil) @@ -1099,14 +1560,39 @@ open class DataStore where T: NSObject { /// Deletes all the records. @discardableResult - open func removeAll(_ writePolicy: WritePolicy? = nil, completionHandler: ((Result) -> Void)?) -> Request { - return remove(writePolicy: writePolicy, completionHandler: completionHandler) + open func removeAll( + _ writePolicy: WritePolicy? = nil, + completionHandler: ((Result) -> Void)? + ) -> Request { + return removeAll( + options: Options( + writePolicy: writePolicy + ), + completionHandler: completionHandler + ) + } + + /// Deletes all the records. + @discardableResult + open func removeAll( + options: Options? = nil, + completionHandler: ((Result) -> Void)? + ) -> Request { + return remove( + options: options, + completionHandler: completionHandler + ) } /// Sends to the backend all the pending records in the local cache. @discardableResult - open func push(timeout: TimeInterval? = nil, completionHandler: UIntErrorTypeArrayCompletionHandler? = nil) -> Request { - return push(timeout: timeout) { (result: Result) in + open func push( + timeout: TimeInterval? = nil, + completionHandler: UIntErrorTypeArrayCompletionHandler? = nil + ) -> Request { + return push( + timeout: timeout + ) { (result: Result) in switch result { case .success(let count): completionHandler?(count, nil) @@ -1118,15 +1604,36 @@ open class DataStore where T: NSObject { /// Sends to the backend all the pending records in the local cache. @discardableResult - open func push(timeout: TimeInterval? = nil, completionHandler: ((Result) -> Void)? = nil) -> Request { + open func push( + timeout: TimeInterval? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return push( + options: Options( + timeout: timeout + ), + completionHandler: completionHandler + ) + } + + /// Sends to the backend all the pending records in the local cache. + @discardableResult + open func push( + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { var request: Request! Promise { fulfill, reject in if type == .network { request = LocalRequest() reject(MultipleErrors(errors: [Error.invalidDataStoreType])) } else { - let operation = PushOperation(sync: sync, cache: cache, client: client) - request = operation.execute(timeout: timeout) { result in + let operation = PushOperation( + sync: sync, + cache: cache, + options: options + ) + request = operation.execute() { result in switch result { case .success(let count): fulfill(count) @@ -1146,8 +1653,15 @@ open class DataStore where T: NSObject { /// Gets the records from the backend that matches with the query passed by parameter and saves locally in the local cache. @discardableResult - open func pull(_ query: Query = Query(), deltaSet: Bool? = nil, completionHandler: DataStore.ArrayCompletionHandler? = nil) -> Request { - return pull(query, deltaSet: deltaSet) { (result: Result<[T], Swift.Error>) in + open func pull( + _ query: Query = Query(), + deltaSet: Bool? = nil, + completionHandler: DataStore.ArrayCompletionHandler? = nil + ) -> Request { + return pull( + query, + deltaSet: deltaSet + ) { (result: Result<[T], Swift.Error>) in switch result { case .success(let array): completionHandler?(array, nil) @@ -1159,7 +1673,29 @@ open class DataStore where T: NSObject { /// Gets the records from the backend that matches with the query passed by parameter and saves locally in the local cache. @discardableResult - open func pull(_ query: Query = Query(), deltaSet: Bool? = nil, deltaSetCompletionHandler: (([T]) -> Void)? = nil, completionHandler: ((Result<[T], Swift.Error>) -> Void)? = nil) -> Request { + open func pull( + _ query: Query = Query(), + deltaSet: Bool? = nil, + deltaSetCompletionHandler: (([T]) -> Void)? = nil, + completionHandler: ((Result<[T], Swift.Error>) -> Void)? = nil + ) -> Request { + return pull( + query, + options: Options( + deltaSet: deltaSet + ), + completionHandler: completionHandler + ) + } + + /// Gets the records from the backend that matches with the query passed by parameter and saves locally in the local cache. + @discardableResult + open func pull( + _ query: Query = Query(), + deltaSetCompletionHandler: (([T]) -> Void)? = nil, + options: Options? = nil, + completionHandler: ((Result<[T], Swift.Error>) -> Void)? = nil + ) -> Request { var request: Request! Promise<[T]> { fulfill, reject in if type == .network { @@ -1169,8 +1705,15 @@ open class DataStore where T: NSObject { request = LocalRequest() reject(Error.invalidOperation(description: "You must push all pending sync items before new data is pulled. Call push() on the data store instance to push pending items, or purge() to remove them.")) } else { - let deltaSet = deltaSet ?? self.deltaSet - let operation = PullOperation(query: Query(query: query, persistableType: T.self), deltaSet: deltaSet, deltaSetCompletionHandler: deltaSetCompletionHandler, readPolicy: .forceNetwork, cache: cache, client: client) + let deltaSet = options?.deltaSet ?? self.deltaSet + let operation = PullOperation( + query: Query(query: query, persistableType: T.self), + deltaSet: deltaSet, + deltaSetCompletionHandler: deltaSetCompletionHandler, + readPolicy: .forceNetwork, + cache: cache, + options: options + ) request = operation.execute { result in switch result { case .success(let array): @@ -1198,7 +1741,11 @@ open class DataStore where T: NSObject { /// Calls `push` and then `pull` methods, so it sends all the pending records in the local cache and then gets the records from the backend and saves locally in the local cache. @discardableResult - open func sync(_ query: Query = Query(), deltaSet: Bool? = nil, completionHandler: UIntArrayCompletionHandler? = nil) -> Request { + open func sync( + _ query: Query = Query(), + deltaSet: Bool? = nil, + completionHandler: UIntArrayCompletionHandler? = nil + ) -> Request { return sync(query, deltaSet: deltaSet) { (result: Result<(UInt, [T]), [Swift.Error]>) in switch result { case .success(let count, let array): @@ -1211,18 +1758,42 @@ open class DataStore where T: NSObject { /// Calls `push` and then `pull` methods, so it sends all the pending records in the local cache and then gets the records from the backend and saves locally in the local cache. @discardableResult - open func sync(_ query: Query = Query(), deltaSet: Bool? = nil, completionHandler: ((Result<(UInt, [T]), [Swift.Error]>) -> Void)? = nil) -> Request { + open func sync( + _ query: Query = Query(), + deltaSet: Bool? = nil, + completionHandler: ((Result<(UInt, [T]), [Swift.Error]>) -> Void)? = nil + ) -> Request { + return sync( + query, + options: Options( + deltaSet: deltaSet + ), + completionHandler: completionHandler + ) + } + + /// Calls `push` and then `pull` methods, so it sends all the pending records in the local cache and then gets the records from the backend and saves locally in the local cache. + @discardableResult + open func sync( + _ query: Query = Query(), + options: Options? = nil, + completionHandler: ((Result<(UInt, [T]), [Swift.Error]>) -> Void)? = nil + ) -> Request { let requests = MultiRequest() Promise<(UInt, [T])> { fulfill, reject in if type == .network { requests += LocalRequest() reject(MultipleErrors(errors: [Error.invalidDataStoreType])) } else { - let request = push() { (result: Result) in + let request = push( + options: options + ) { (result: Result) in switch result { case .success(let count): - let deltaSet = deltaSet ?? self.deltaSet - let request = self.pull(query, deltaSet: deltaSet) { (result: Result<[T], Swift.Error>) in + let request = self.pull( + query, + options: options + ) { (result: Result<[T], Swift.Error>) in switch result { case .success(let array): fulfill(count, array) @@ -1251,8 +1822,13 @@ open class DataStore where T: NSObject { /// Deletes all the pending changes in the local cache. @discardableResult - open func purge(_ query: Query = Query(), completionHandler: DataStore.IntCompletionHandler? = nil) -> Request { - return purge(query) { (result: Result) in + open func purge( + _ query: Query = Query(), + completionHandler: DataStore.IntCompletionHandler? = nil + ) -> Request { + return purge( + query + ) { (result: Result) in switch result { case .success(let count): completionHandler?(count, nil) @@ -1264,7 +1840,11 @@ open class DataStore where T: NSObject { /// Deletes all the pending changes in the local cache. @discardableResult - open func purge(_ query: Query = Query(), completionHandler: ((Result) -> Void)? = nil) -> Request { + open func purge( + _ query: Query = Query(), + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { var request: Request! Promise { fulfill, reject in if type == .network { @@ -1273,12 +1853,19 @@ open class DataStore where T: NSObject { } else { let executor = Executor() - let operation = PurgeOperation(sync: sync, cache: cache, client: client) + let operation = PurgeOperation( + sync: sync, + cache: cache, + options: options + ) request = operation.execute { result in switch result { case .success(let count): executor.execute { - self.pull(query) { (result: Result<[T], Swift.Error>) in + self.pull( + query, + options: options + ) { (result: Result<[T], Swift.Error>) in switch result { case .success: fulfill(count) @@ -1331,12 +1918,17 @@ open class DataStore where T: NSObject { */ @discardableResult open func subscribe( + options: Options? = nil, subscription: @escaping () -> Void, onNext: @escaping (T) -> Void, onStatus: @escaping (RealtimeStatus) -> Void, onError: @escaping (Swift.Error) -> Void ) -> Request { - let request = client.networkRequestFactory.buildAppDataSubscribe(collectionName: collectionName, deviceId: deviceId) + let request = client.networkRequestFactory.buildAppDataSubscribe( + collectionName: collectionName, + deviceId: deviceId, + options: options + ) Promise { fulfill, reject in do { let realtimeRouter = try self.realtimeRouter() @@ -1375,8 +1967,15 @@ open class DataStore where T: NSObject { Unsubscribe and stop listening changes in the collection */ @discardableResult - open func unsubscribe(completionHandler: @escaping (Result) -> Void) -> Request { - let request = client.networkRequestFactory.buildAppDataUnSubscribe(collectionName: collectionName, deviceId: deviceId) + open func unsubscribe( + options: Options? = nil, + completionHandler: @escaping (Result) -> Void + ) -> Request { + let request = client.networkRequestFactory.buildAppDataUnSubscribe( + collectionName: collectionName, + deviceId: deviceId, + options: options + ) Promise { fulfill, reject in do { let realtimeRouter = try self.realtimeRouter() diff --git a/Kinvey/Kinvey/FileStore.swift b/Kinvey/Kinvey/FileStore.swift index 857dfcf1d..3a8db9aab 100644 --- a/Kinvey/Kinvey/FileStore.swift +++ b/Kinvey/Kinvey/FileStore.swift @@ -106,7 +106,13 @@ open class FileStore { /// Uploads a `UIImage` in a PNG or JPEG format. @discardableResult - open func upload(_ file: FileType, image: NSImage, imageRepresentation: ImageRepresentation = .png, ttl: TTL? = nil, completionHandler: FileCompletionHandler? = nil) -> Request { + open func upload( + _ file: FileType, + image: NSImage, + imageRepresentation: ImageRepresentation = .png, + ttl: TTL? = nil, + completionHandler: FileCompletionHandler? = nil + ) -> Request { return upload( file, image: image, @@ -124,17 +130,54 @@ open class FileStore { /// Uploads a `UIImage` in a PNG or JPEG format. @discardableResult - open func upload(_ file: FileType, image: NSImage, imageRepresentation: ImageRepresentation = .png, ttl: TTL? = nil, completionHandler: ((Result) -> Void)? = nil) -> Request { + open func upload( + _ file: FileType, + image: NSImage, + imageRepresentation: ImageRepresentation = .png, + ttl: TTL? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return upload( + file, + image: image, + imageRepresentation: imageRepresentation, + options: Options( + ttl: ttl + ), + completionHandler: completionHandler + ) + } + + /// Uploads a `UIImage` in a PNG or JPEG format. + @discardableResult + open func upload( + _ file: FileType, + image: NSImage, + imageRepresentation: ImageRepresentation = .png, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { let data = imageRepresentation.data(image: image)! file.mimeType = imageRepresentation.mimeType - return upload(file, data: data, ttl: ttl, completionHandler: completionHandler) + return upload( + file, + data: data, + options: options, + completionHandler: completionHandler + ) } #else /// Uploads a `UIImage` in a PNG or JPEG format. @discardableResult - open func upload(_ file: FileType, image: UIImage, imageRepresentation: ImageRepresentation = .png, ttl: TTL? = nil, completionHandler: FileCompletionHandler? = nil) -> Request { + open func upload( + _ file: FileType, + image: UIImage, + imageRepresentation: ImageRepresentation = .png, + ttl: TTL? = nil, + completionHandler: FileCompletionHandler? = nil + ) -> Request { return upload( file, image: image, @@ -152,17 +195,53 @@ open class FileStore { /// Uploads a `UIImage` in a PNG or JPEG format. @discardableResult - open func upload(_ file: FileType, image: UIImage, imageRepresentation: ImageRepresentation = .png, ttl: TTL? = nil, completionHandler: ((Result) -> Void)? = nil) -> Request { + open func upload( + _ file: FileType, + image: UIImage, + imageRepresentation: ImageRepresentation = .png, + ttl: TTL? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return upload( + file, + image: image, + imageRepresentation: imageRepresentation, + options: Options( + ttl: ttl + ), + completionHandler: completionHandler + ) + } + + /// Uploads a `UIImage` in a PNG or JPEG format. + @discardableResult + open func upload( + _ file: FileType, + image: UIImage, + imageRepresentation: ImageRepresentation = .png, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { let data = imageRepresentation.data(image: image)! file.mimeType = imageRepresentation.mimeType - return upload(file, data: data, ttl: ttl, completionHandler: completionHandler) + return upload( + file, + data: data, + options: options, + completionHandler: completionHandler + ) } #endif /// Uploads a file using the file path. @discardableResult - open func upload(_ file: FileType, path: String, ttl: TTL? = nil, completionHandler: FileCompletionHandler? = nil) -> Request { + open func upload( + _ file: FileType, + path: String, + ttl: TTL? = nil, + completionHandler: FileCompletionHandler? = nil + ) -> Request { return upload( file, path: path, @@ -179,13 +258,46 @@ open class FileStore { /// Uploads a file using the file path. @discardableResult - open func upload(_ file: FileType, path: String, ttl: TTL? = nil, completionHandler: ((Result) -> Void)? = nil) -> Request { - return upload(file, fromSource: .url(URL(fileURLWithPath: path)), ttl: ttl, completionHandler: completionHandler) + open func upload( + _ file: FileType, + path: String, + ttl: TTL? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return upload( + file, + path: path, + options: Options( + ttl: ttl + ), + completionHandler: completionHandler + ) + } + + /// Uploads a file using the file path. + @discardableResult + open func upload( + _ file: FileType, + path: String, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return upload( + file, + fromSource: .url(URL(fileURLWithPath: path)), + options: options, + completionHandler: completionHandler + ) } /// Uploads a file using a input stream. @discardableResult - open func upload(_ file: FileType, stream: InputStream, ttl: TTL? = nil, completionHandler: FileCompletionHandler? = nil) -> Request { + open func upload( + _ file: FileType, + stream: InputStream, + ttl: TTL? = nil, + completionHandler: FileCompletionHandler? = nil + ) -> Request { return upload( file, stream: stream, @@ -202,12 +314,46 @@ open class FileStore { /// Uploads a file using a input stream. @discardableResult - open func upload(_ file: FileType, stream: InputStream, ttl: TTL? = nil, completionHandler: ((Result) -> Void)? = nil) -> Request { - return upload(file, fromSource: .stream(stream), ttl: ttl, completionHandler: completionHandler) + open func upload( + _ file: FileType, + stream: InputStream, + ttl: TTL? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return upload( + file, + stream: stream, + options: Options( + ttl: ttl + ), + completionHandler: completionHandler + ) + } + + /// Uploads a file using a input stream. + @discardableResult + open func upload( + _ file: FileType, + stream: InputStream, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return upload( + file, + fromSource: .stream(stream), + options: options, + completionHandler: completionHandler + ) } - fileprivate func getFileMetadata(_ file: FileType, ttl: TTL? = nil) -> (request: Request, promise: Promise) { - let request = self.client.networkRequestFactory.buildBlobDownloadFile(file, ttl: ttl) + fileprivate func getFileMetadata( + _ file: FileType, + options: Options? + ) -> (request: Request, promise: Promise) { + let request = self.client.networkRequestFactory.buildBlobDownloadFile( + file, + options: options + ) let promise = Promise { fulfill, reject in request.execute() { (data, response, error) -> Void in if let response = response, response.isOK, @@ -229,7 +375,12 @@ open class FileStore { /// Uploads a file using a `NSData`. @discardableResult - open func upload(_ file: FileType, data: Data, ttl: TTL? = nil, completionHandler: FileCompletionHandler? = nil) -> Request { + open func upload( + _ file: FileType, + data: Data, + ttl: TTL? = nil, + completionHandler: FileCompletionHandler? = nil + ) -> Request { return upload( file, data: data, @@ -246,8 +397,36 @@ open class FileStore { /// Uploads a file using a `NSData`. @discardableResult - open func upload(_ file: FileType, data: Data, ttl: TTL? = nil, completionHandler: ((Result) -> Void)? = nil) -> Request { - return upload(file, fromSource: .data(data), ttl: ttl, completionHandler: completionHandler) + open func upload( + _ file: FileType, + data: Data, + ttl: TTL? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return upload( + file, + data: data, + options: Options( + ttl: ttl + ), + completionHandler: completionHandler + ) + } + + /// Uploads a file using a `NSData`. + @discardableResult + open func upload( + _ file: FileType, + data: Data, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return upload( + file, + fromSource: .data(data), + options: options, + completionHandler: completionHandler + ) } fileprivate enum InputSource { @@ -259,7 +438,29 @@ open class FileStore { } /// Uploads a file using a `NSData`. - fileprivate func upload(_ file: FileType, fromSource source: InputSource, ttl: TTL? = nil, completionHandler: ((Result) -> Void)? = nil) -> Request { + fileprivate func upload( + _ file: FileType, + fromSource source: InputSource, + ttl: TTL? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return upload( + file, + fromSource: source, + options: Options( + ttl: ttl + ), + completionHandler: completionHandler + ) + } + + /// Uploads a file using a `NSData`. + fileprivate func upload( + _ file: FileType, + fromSource source: InputSource, + options: Options?, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { if file.size.value == nil { switch source { case let .data(data): @@ -277,7 +478,7 @@ open class FileStore { let requests = MultiRequest() Promise<(file: FileType, skip: Int?)> { fulfill, reject in //creating bucket let createUpdateFileEntry = { - let request = self.client.networkRequestFactory.buildBlobUploadFile(file) + let request = self.client.networkRequestFactory.buildBlobUploadFile(file, options: options) requests += request request.execute { (data, response, error) -> Void in if let response = response, response.isOK, @@ -436,7 +637,10 @@ open class FileStore { } } }.then { file in //fetching download url - let (request, promise) = self.getFileMetadata(file, ttl: ttl) + let (request, promise) = self.getFileMetadata( + file, + options: options + ) requests += request return promise }.then { file in @@ -449,7 +653,11 @@ open class FileStore { /// Refresh a `File` instance. @discardableResult - open func refresh(_ file: FileType, ttl: TTL? = nil, completionHandler: FileCompletionHandler? = nil) -> Request { + open func refresh( + _ file: FileType, + ttl: TTL? = nil, + completionHandler: FileCompletionHandler? = nil + ) -> Request { return refresh( file, ttl: ttl @@ -465,8 +673,31 @@ open class FileStore { /// Refresh a `File` instance. @discardableResult - open func refresh(_ file: FileType, ttl: TTL? = nil, completionHandler: ((Result) -> Void)? = nil) -> Request { - let (request, promise) = getFileMetadata(file, ttl: ttl) + open func refresh( + _ file: FileType, + ttl: TTL? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return refresh( + file, + options: Options( + ttl: ttl + ), + completionHandler: completionHandler + ) + } + + /// Refresh a `File` instance. + @discardableResult + open func refresh( + _ file: FileType, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let (request, promise) = getFileMetadata( + file, + options: options + ) promise.then { file in completionHandler?(.success(file)) }.catch { error in @@ -476,7 +707,14 @@ open class FileStore { } @discardableResult - fileprivate func downloadFileURL(_ file: FileType, storeType: StoreType = .cache, downloadURL: URL) -> (request: URLSessionTaskRequest, promise: Promise) { + fileprivate func downloadFileURL( + _ file: FileType, + storeType: StoreType = .cache, + downloadURL: URL + ) -> ( + request: URLSessionTaskRequest, + promise: Promise + ) { let downloadTaskRequest = URLSessionTaskRequest(client: client, url: downloadURL) let promise = Promise { fulfill, reject in let executor = Executor() @@ -568,7 +806,12 @@ open class FileStore { /// Downloads a file using the `downloadURL` of the `File` instance. @discardableResult - open func download(_ file: FileType, storeType: StoreType = .cache, ttl: TTL? = nil, completionHandler: FilePathCompletionHandler? = nil) -> Request { + open func download( + _ file: FileType, + storeType: StoreType = .cache, + ttl: TTL? = nil, + completionHandler: FilePathCompletionHandler? = nil + ) -> Request { return download( file, storeType: storeType, @@ -585,7 +828,30 @@ open class FileStore { /// Downloads a file using the `downloadURL` of the `File` instance. @discardableResult - open func download(_ file: FileType, storeType: StoreType = .cache, ttl: TTL? = nil, completionHandler: ((Result<(FileType, URL), Swift.Error>) -> Void)? = nil) -> Request { + open func download( + _ file: FileType, + storeType: StoreType = .cache, + ttl: TTL? = nil, + completionHandler: ((Result<(FileType, URL), Swift.Error>) -> Void)? = nil + ) -> Request { + return download( + file, + storeType: storeType, + options: Options( + ttl: ttl + ), + completionHandler: completionHandler + ) + } + + /// Downloads a file using the `downloadURL` of the `File` instance. + @discardableResult + open func download( + _ file: FileType, + storeType: StoreType = .cache, + options: Options? = nil, + completionHandler: ((Result<(FileType, URL), Swift.Error>) -> Void)? = nil + ) -> Request { crashIfInvalid(file: file) if storeType == .sync || storeType == .cache, @@ -601,10 +867,19 @@ open class FileStore { if storeType == .cache || storeType == .network { let multiRequest = MultiRequest() Promise<(FileType, URL)> { fulfill, reject in - if let downloadURL = file.downloadURL, file.publicAccessible || (file.expiresAt != nil && file.expiresAt!.timeIntervalSinceNow > 0) { + if let downloadURL = file.downloadURL, + file.publicAccessible || + ( + file.expiresAt != nil && + file.expiresAt!.timeIntervalSinceNow > 0 + ) + { fulfill((file, downloadURL)) } else { - let (request, promise) = getFileMetadata(file, ttl: ttl) + let (request, promise) = getFileMetadata( + file, + options: options + ) multiRequest += request promise.then { (file) -> Void in if let downloadURL = file.downloadURL { @@ -617,7 +892,11 @@ open class FileStore { } } }.then { (file, downloadURL) -> Promise<(FileType, URL)> in - let (request, promise) = self.downloadFileURL(file, storeType: storeType, downloadURL: downloadURL) + let (request, promise) = self.downloadFileURL( + file, + storeType: storeType, + downloadURL: downloadURL + ) multiRequest += (request, true) return promise.then { localUrl in return Promise<(FileType, URL)> { fulfill, reject in @@ -637,7 +916,11 @@ open class FileStore { /// Downloads a file using the `downloadURL` of the `File` instance. @discardableResult - open func download(_ file: FileType, ttl: TTL? = nil, completionHandler: FileDataCompletionHandler? = nil) -> Request { + open func download( + _ file: FileType, + ttl: TTL? = nil, + completionHandler: FileDataCompletionHandler? = nil + ) -> Request { return download( file, ttl: ttl @@ -660,23 +943,62 @@ open class FileStore { /// Downloads a file using the `downloadURL` of the `File` instance. @discardableResult - open func download(_ file: FileType, ttl: TTL? = nil, completionHandler: ((Result<(FileType, Data), Swift.Error>) -> Void)? = nil) -> Request { + open func download( + _ file: FileType, + ttl: TTL? = nil, + completionHandler: ((Result<(FileType, Data), Swift.Error>) -> Void)? = nil + ) -> Request { + return download( + file, + options: Options( + ttl: ttl + ), + completionHandler: completionHandler + ) + } + + /// Downloads a file using the `downloadURL` of the `File` instance. + @discardableResult + open func download( + _ file: FileType, + options: Options? = nil, + completionHandler: ((Result<(FileType, Data), Swift.Error>) -> Void)? = nil + ) -> Request { crashIfInvalid(file: file) let multiRequest = MultiRequest() Promise<(FileType, DownloadStage)> { fulfill, reject in - if let entityId = file.fileId, let cachedFile = cachedFile(entityId), let path = file.path, let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { + if let entityId = file.fileId, + let cachedFile = cachedFile(entityId), + let path = file.path, + let data = try? Data(contentsOf: URL(fileURLWithPath: path)) + { fulfill((cachedFile, .data(data))) return } - if let downloadURL = file.downloadURL, file.publicAccessible || (file.expiresAt != nil && file.expiresAt!.timeIntervalSinceNow > 0) { + if let downloadURL = file.downloadURL, + file.publicAccessible || + ( + file.expiresAt != nil && + file.expiresAt!.timeIntervalSinceNow > 0 + ) + { fulfill((file, .downloadURL(downloadURL))) } else { - let (request, promise) = getFileMetadata(file, ttl: ttl) + let (request, promise) = getFileMetadata( + file, + options: options + ) multiRequest += request promise.then { file -> Void in - if let downloadURL = file.downloadURL, file.publicAccessible || (file.expiresAt != nil && file.expiresAt!.timeIntervalSinceNow > 0) { + if let downloadURL = file.downloadURL, + file.publicAccessible || + ( + file.expiresAt != nil && + file.expiresAt!.timeIntervalSinceNow > 0 + ) + { fulfill((file, .downloadURL(downloadURL))) } else { throw Error.invalidResponse(httpResponse: nil, data: nil) @@ -688,7 +1010,10 @@ open class FileStore { }.then { (file, downloadStage) -> Promise in switch downloadStage { case .downloadURL(let downloadURL): - let (request, promise) = self.downloadFileData(file, downloadURL: downloadURL) + let (request, promise) = self.downloadFileData( + file, + downloadURL: downloadURL + ) multiRequest += (request, addProgress: true) return promise case .data(let data): @@ -706,8 +1031,14 @@ open class FileStore { /// Deletes a file instance in the backend. @discardableResult - open func remove(_ file: FileType, completionHandler: UIntCompletionHandler? = nil) -> Request { - return remove(file) { (result: Result) in + open func remove( + _ file: FileType, + completionHandler: UIntCompletionHandler? = nil + ) -> Request { + return remove( + file, + options: nil + ) { (result: Result) in switch result { case .success(let count): completionHandler?(count, nil) @@ -719,8 +1050,28 @@ open class FileStore { /// Deletes a file instance in the backend. @discardableResult - open func remove(_ file: FileType, completionHandler: ((Result) -> Void)? = nil) -> Request { - let request = client.networkRequestFactory.buildBlobDeleteFile(file) + open func remove( + _ file: FileType, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return remove( + file, + options: nil, + completionHandler: completionHandler + ) + } + + /// Deletes a file instance in the backend. + @discardableResult + open func remove( + _ file: FileType, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let request = client.networkRequestFactory.buildBlobDeleteFile( + file, + options: options + ) Promise { fulfill, reject in request.execute({ (data, response, error) -> Void in if let response = response, response.isOK, @@ -746,7 +1097,11 @@ open class FileStore { /// Gets a list of files that matches with the query passed by parameter. @discardableResult - open func find(_ query: Query = Query(), ttl: TTL? = nil, completionHandler: FileArrayCompletionHandler? = nil) -> Request { + open func find( + _ query: Query = Query(), + ttl: TTL? = nil, + completionHandler: FileArrayCompletionHandler? = nil + ) -> Request { return find( query, ttl: ttl @@ -762,8 +1117,31 @@ open class FileStore { /// Gets a list of files that matches with the query passed by parameter. @discardableResult - open func find(_ query: Query = Query(), ttl: TTL? = nil, completionHandler: ((Result<[FileType], Swift.Error>) -> Void)? = nil) -> Request { - let request = client.networkRequestFactory.buildBlobQueryFile(query, ttl: ttl) + open func find( + _ query: Query = Query(), + ttl: TTL? = nil, + completionHandler: ((Result<[FileType], Swift.Error>) -> Void)? = nil + ) -> Request { + return find( + query, + options: Options( + ttl: ttl + ), + completionHandler: completionHandler + ) + } + + /// Gets a list of files that matches with the query passed by parameter. + @discardableResult + open func find( + _ query: Query = Query(), + options: Options? = nil, + completionHandler: ((Result<[FileType], Swift.Error>) -> Void)? = nil + ) -> Request { + let request = client.networkRequestFactory.buildBlobQueryFile( + query, + options: options + ) Promise<[FileType]> { fulfill, reject in request.execute { (data, response, error) -> Void in if let response = response, diff --git a/Kinvey/Kinvey/FindOperation.swift b/Kinvey/Kinvey/FindOperation.swift index bc9ee0b19..d65e98713 100644 --- a/Kinvey/Kinvey/FindOperation.swift +++ b/Kinvey/Kinvey/FindOperation.swift @@ -28,12 +28,24 @@ internal class FindOperation: ReadOperation typealias ResultsHandler = ([JsonDictionary]) -> Void let resultsHandler: ResultsHandler? - init(query: Query, deltaSet: Bool, deltaSetCompletionHandler: (([T]) -> Void)? = nil, readPolicy: ReadPolicy, cache: AnyCache?, client: Client, resultsHandler: ResultsHandler? = nil) { + init( + query: Query, + deltaSet: Bool, + deltaSetCompletionHandler: (([T]) -> Void)? = nil, + readPolicy: ReadPolicy, + cache: AnyCache?, + options: Options?, + resultsHandler: ResultsHandler? = nil + ) { self.query = query self.deltaSet = deltaSet self.deltaSetCompletionHandler = deltaSetCompletionHandler self.resultsHandler = resultsHandler - super.init(readPolicy: readPolicy, cache: cache, client: client) + super.init( + readPolicy: readPolicy, + cache: cache, + options: options + ) } @discardableResult @@ -56,7 +68,11 @@ internal class FindOperation: ReadOperation func executeNetwork(_ completionHandler: CompletionHandler? = nil) -> Request { let deltaSet = self.deltaSet && (cache != nil ? !cache!.isEmpty() : false) let fields: Set? = deltaSet ? [Entity.Key.entityId, "\(Entity.Key.metadata).\(Metadata.Key.lastModifiedTime)"] : nil - let request = client.networkRequestFactory.buildAppDataFindByQuery(collectionName: T.collectionName(), query: fields != nil ? Query(query) { $0.fields = fields } : query) + let request = client.networkRequestFactory.buildAppDataFindByQuery( + collectionName: T.collectionName(), + query: fields != nil ? Query(query) { $0.fields = fields } : query, + options: options + ) request.execute() { data, response, error in if let response = response, response.isOK, let jsonArray = self.client.responseParser.parseArray(data) @@ -78,7 +94,13 @@ internal class FindOperation: ReadOperation let allIds = Set(allIds[offset...limit]) let promise = Promise<[T]> { fulfill, reject in let query = Query(format: "\(Entity.Key.entityId) IN %@", allIds) - let operation = FindOperation(query: query, deltaSet: false, readPolicy: .forceNetwork, cache: cache, client: self.client) { jsonArray in + let operation = FindOperation( + query: query, + deltaSet: false, + readPolicy: .forceNetwork, + cache: cache, + options: self.options + ) { jsonArray in for (key, value) in self.reduceToIdsLmts(jsonArray) { newRefObjs[key] = value } @@ -96,7 +118,11 @@ internal class FindOperation: ReadOperation } when(fulfilled: promises).then { results -> Void in if self.mustRemoveCachedRecords { - self.removeCachedRecords(cache, keys: refObjs.keys, deleted: deltaSet.deleted) + self.removeCachedRecords( + cache, + keys: refObjs.keys, + deleted: deltaSet.deleted + ) } if let deltaSetCompletionHandler = self.deltaSetCompletionHandler { deltaSetCompletionHandler(results.flatMap { $0 }) @@ -108,14 +134,26 @@ internal class FindOperation: ReadOperation } else if allIds.count > 0 { let query = Query(format: "\(Entity.Key.entityId) IN %@", allIds) var newRefObjs: [String : String]? = nil - let operation = FindOperation(query: query, deltaSet: false, readPolicy: .forceNetwork, cache: cache, client: self.client) { jsonArray in + let operation = FindOperation( + query: query, + deltaSet: false, + readPolicy: .forceNetwork, + cache: cache, + options: self.options + ) { jsonArray in newRefObjs = self.reduceToIdsLmts(jsonArray) } operation.execute { (result) -> Void in switch result { case .success: - if self.mustRemoveCachedRecords, let refObjs = newRefObjs { - self.removeCachedRecords(cache, keys: refObjs.keys, deleted: deltaSet.deleted) + if self.mustRemoveCachedRecords, + let refObjs = newRefObjs + { + self.removeCachedRecords( + cache, + keys: refObjs.keys, + deleted: deltaSet.deleted + ) } self.executeLocal(completionHandler) case .failure(let error): @@ -130,8 +168,15 @@ internal class FindOperation: ReadOperation if let cache = self.cache { if self.mustRemoveCachedRecords { let refObjs = self.reduceToIdsLmts(jsonArray) - let deltaSet = self.computeDeltaSet(self.query, refObjs: refObjs) - self.removeCachedRecords(cache, keys: refObjs.keys, deleted: deltaSet.deleted) + let deltaSet = self.computeDeltaSet( + self.query, + refObjs: refObjs + ) + self.removeCachedRecords( + cache, + keys: refObjs.keys, + deleted: deltaSet.deleted + ) } cache.save(entities: entities) } diff --git a/Kinvey/Kinvey/GetOperation.swift b/Kinvey/Kinvey/GetOperation.swift index ea0ca7148..68474f3ce 100644 --- a/Kinvey/Kinvey/GetOperation.swift +++ b/Kinvey/Kinvey/GetOperation.swift @@ -12,9 +12,18 @@ internal class GetOperation: ReadOperation, R let id: String - init(id: String, readPolicy: ReadPolicy, cache: AnyCache?, client: Client) { + init( + id: String, + readPolicy: ReadPolicy, + cache: AnyCache?, + options: Options? + ) { self.id = id - super.init(readPolicy: readPolicy, cache: cache, client: client) + super.init( + readPolicy: readPolicy, + cache: cache, + options: options + ) } func executeLocal(_ completionHandler: CompletionHandler?) -> Request { @@ -30,7 +39,11 @@ internal class GetOperation: ReadOperation, R } func executeNetwork(_ completionHandler: CompletionHandler?) -> Request { - let request = client.networkRequestFactory.buildAppDataGetById(collectionName: T.collectionName(), id: id) + let request = client.networkRequestFactory.buildAppDataGetById( + collectionName: T.collectionName(), + id: id, + options: options + ) request.execute() { data, response, error in if let response = response, response.isOK, diff --git a/Kinvey/Kinvey/HttpRequest.swift b/Kinvey/Kinvey/HttpRequest.swift index fb2b5346b..998ed3a59 100644 --- a/Kinvey/Kinvey/HttpRequest.swift +++ b/Kinvey/Kinvey/HttpRequest.swift @@ -24,6 +24,7 @@ struct KinveyHeaderField { static let requestId = "X-Kinvey-Request-Id" static let clientAppVersion = "X-Kinvey-Client-App-Version" + static let customRequestProperties = "X-Kinvey-Custom-Request-Properties" static let apiVersion = "X-Kinvey-API-Version" static let deviceInformation = "X-Kinvey-Device-Information" @@ -290,6 +291,7 @@ internal class HttpRequest: TaskProgressRequest, Request { } } } + let options: Options? let client: Client internal var executing: Bool { @@ -304,16 +306,21 @@ internal class HttpRequest: TaskProgressRequest, Request { } } - init(request: URLRequest, timeout: TimeInterval? = nil, client: Client = sharedClient) { + init( + request: URLRequest, + options: Options? + ) { self.httpMethod = HttpMethod.parse(request.httpMethod!) self.endpoint = Endpoint.url(url: request.url!) + self.options = options + let client = options?.client ?? sharedClient self.client = client if request.value(forHTTPHeaderField: HttpHeaderKey.authorization.rawValue) == nil { self.credential = client.activeUser ?? client } self.request = request - if let timeout = timeout { + if let timeout = options?.timeout { self.request.timeoutInterval = timeout } self.request.setValue(UUID().uuidString, forHTTPHeaderField: KinveyHeaderField.requestId) @@ -324,18 +331,19 @@ internal class HttpRequest: TaskProgressRequest, Request { endpoint: Endpoint, credential: Credential? = nil, body: Body? = nil, - timeout: TimeInterval? = nil, - client: Client = sharedClient + options: Options? ) { self.httpMethod = httpMethod self.endpoint = endpoint + self.options = options + let client = options?.client ?? sharedClient self.client = client self.credential = credential ?? client let url = endpoint.url request = URLRequest(url: url) request.httpMethod = httpMethod.stringValue - if let timeout = timeout { + if let timeout = options?.timeout { request.timeoutInterval = timeout } if let body = body { @@ -355,9 +363,18 @@ internal class HttpRequest: TaskProgressRequest, Request { for header in headers { request.setValue(header.value, forHTTPHeaderField: header.name) } - if let clientAppVersion = client.clientAppVersion { + if let clientAppVersion = options?.clientAppVersion ?? client.options?.clientAppVersion { request.setValue(clientAppVersion, forHTTPHeaderField: KinveyHeaderField.clientAppVersion) } + + if let customRequestProperties = self.options?.customRequestProperties ?? client.options?.customRequestProperties, + customRequestProperties.count > 0, + let data = try? JSONSerialization.data(withJSONObject: customRequestProperties), + let customRequestPropertiesString = String(data: data, encoding: .utf8) + { + request.setValue(customRequestPropertiesString, forHTTPHeaderField: KinveyHeaderField.customRequestProperties) + } + if let url = request.url, let query = url.query, query.contains("+"), diff --git a/Kinvey/Kinvey/HttpRequestFactory.swift b/Kinvey/Kinvey/HttpRequestFactory.swift index 4cf528625..fddd56137 100644 --- a/Kinvey/Kinvey/HttpRequestFactory.swift +++ b/Kinvey/Kinvey/HttpRequestFactory.swift @@ -19,8 +19,17 @@ class HttpRequestFactory: RequestFactory { typealias CompletionHandler = (Data?, URLResponse?, NSError?) -> Void - func buildUserSignUp(username: String? = nil, password: String? = nil, user: User? = nil) -> HttpRequest { - let request = HttpRequest(httpMethod: .post, endpoint: Endpoint.user(client: client, query: nil), client: client) + func buildUserSignUp( + username: String? = nil, + password: String? = nil, + user: User? = nil, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + httpMethod: .post, + endpoint: Endpoint.user(client: client, query: nil), + options: options + ) request.request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -38,36 +47,79 @@ class HttpRequestFactory: RequestFactory { return request } - func buildUserDelete(userId: String, hard: Bool) -> HttpRequest { - - - let request = HttpRequest(httpMethod: .delete, endpoint: Endpoint.userDelete(client: client, userId: userId, hard: hard), credential: client.activeUser, client: client) + func buildUserDelete( + userId: String, + hard: Bool, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + httpMethod: .delete, + endpoint: Endpoint.userDelete(client: client, userId: userId, hard: hard), + credential: client.activeUser, + options: options + ) //FIXME: make it configurable request.request.setValue("2", forHTTPHeaderField: "X-Kinvey-API-Version") return request } - func buildUserSocial(_ authSource: AuthSource, authData: [String : Any], endpoint: Endpoint) -> HttpRequest { + func buildUserSocial( + _ authSource: AuthSource, + authData: [String : Any], + endpoint: Endpoint, + options: Options? + ) -> HttpRequest { let bodyObject = [ "_socialIdentity" : [ authSource.rawValue : authData ] ] - let request = HttpRequest(httpMethod: .post, endpoint: endpoint, body: Body.json(json: bodyObject), client: client) + let request = HttpRequest( + httpMethod: .post, + endpoint: endpoint, + body: Body.json(json: bodyObject), + options: options + ) return request } - func buildUserSocialLogin(_ authSource: AuthSource, authData: [String : Any]) -> HttpRequest { - return buildUserSocial(authSource, authData: authData, endpoint: Endpoint.userLogin(client: client)) + func buildUserSocialLogin( + _ authSource: AuthSource, + authData: [String : Any], + options: Options? + ) -> HttpRequest { + return buildUserSocial( + authSource, + authData: authData, + endpoint: Endpoint.userLogin(client: client), + options: options + ) } - func buildUserSocialCreate(_ authSource: AuthSource, authData: [String : Any]) -> HttpRequest { - return buildUserSocial(authSource, authData: authData, endpoint: Endpoint.user(client: client, query: nil)) + func buildUserSocialCreate( + _ authSource: AuthSource, + authData: [String : Any], + options: Options? + ) -> HttpRequest { + return buildUserSocial( + authSource, + authData: authData, + endpoint: Endpoint.user(client: client, query: nil), + options: options + ) } - func buildUserLogin(username: String, password: String) -> HttpRequest { - let request = HttpRequest(httpMethod: .post, endpoint: Endpoint.userLogin(client: client), client: client) + func buildUserLogin( + username: String, + password: String, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + httpMethod: .post, + endpoint: Endpoint.userLogin(client: client), + options: options + ) request.request.setValue("application/json", forHTTPHeaderField: "Content-Type") let bodyObject = [ @@ -78,8 +130,15 @@ class HttpRequestFactory: RequestFactory { return request } - func buildUserExists(username: String) -> HttpRequest { - let request = HttpRequest(httpMethod: .post, endpoint: Endpoint.userExistsByUsername(client: client), client: client) + func buildUserExists( + username: String, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + httpMethod: .post, + endpoint: Endpoint.userExistsByUsername(client: client), + options: options + ) request.request.httpMethod = "POST" request.request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -89,18 +148,41 @@ class HttpRequestFactory: RequestFactory { return request } - func buildUserGet(userId: String) -> HttpRequest { - let request = HttpRequest(endpoint: Endpoint.userById(client: client, userId: userId), credential: client.activeUser, client: client) + func buildUserGet( + userId: String, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + endpoint: Endpoint.userById(client: client, userId: userId), + credential: client.activeUser, + options: options + ) return request } - func buildUserFind(query: Query) -> HttpRequest { - let request = HttpRequest(endpoint: Endpoint.user(client: client, query: query), credential: client.activeUser, client: client) + func buildUserFind( + query: Query, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + endpoint: Endpoint.user(client: client, query: query), + credential: client.activeUser, + options: options + ) return request } - func buildUserSave(user: User, newPassword: String?) -> HttpRequest { - let request = HttpRequest(httpMethod: .put, endpoint: Endpoint.userById(client: client, userId: user.userId), credential: client.activeUser, client: client) + func buildUserSave( + user: User, + newPassword: String?, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + httpMethod: .put, + endpoint: Endpoint.userById(client: client, userId: user.userId), + credential: client.activeUser, + options: options + ) request.request.setValue("application/json", forHTTPHeaderField: "Content-Type") var bodyObject = user.toJSON() @@ -113,8 +195,17 @@ class HttpRequestFactory: RequestFactory { return request } - func buildUserLookup(user: User, userQuery: UserQuery) -> HttpRequest { - let request = HttpRequest(httpMethod: .post, endpoint: Endpoint.userLookup(client: client), credential: client.activeUser, client: client) + func buildUserLookup( + user: User, + userQuery: UserQuery, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + httpMethod: .post, + endpoint: Endpoint.userLookup(client: client), + credential: client.activeUser, + options: options + ) request.request.setValue("application/json", forHTTPHeaderField: "Content-Type") let bodyObject = userQuery.toJSON() @@ -123,13 +214,29 @@ class HttpRequestFactory: RequestFactory { return request } - func buildUserResetPassword(usernameOrEmail: String) -> HttpRequest { - let request = HttpRequest(httpMethod: .post, endpoint: Endpoint.userResetPassword(usernameOrEmail: usernameOrEmail, client: client), credential: client, client: client) + func buildUserResetPassword( + usernameOrEmail: String, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + httpMethod: .post, + endpoint: Endpoint.userResetPassword(usernameOrEmail: usernameOrEmail, client: client), + credential: client, + options: options + ) return request } - func buildUserForgotUsername(email: String) -> HttpRequest { - let request = HttpRequest(httpMethod: .post, endpoint: Endpoint.userForgotUsername(client: client), credential: client, client: client) + func buildUserForgotUsername( + email: String, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + httpMethod: .post, + endpoint: Endpoint.userForgotUsername(client: client), + credential: client, + options: options + ) request.request.setValue("application/json", forHTTPHeaderField: "Content-Type") let bodyObject = ["email" : email] @@ -137,33 +244,79 @@ class HttpRequestFactory: RequestFactory { return request } - func buildUserMe() -> HttpRequest { - let request = HttpRequest(endpoint: Endpoint.userMe(client: client), credential: client.activeUser, client: client) + func buildUserMe( + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + endpoint: Endpoint.userMe(client: client), + credential: client.activeUser, + options: options + ) return request } - func buildAppDataPing() -> HttpRequest { - let request = HttpRequest(httpMethod: .get, endpoint: Endpoint.appDataPing(client: client), client: client) + func buildAppDataPing(options: Options?) -> HttpRequest { + let request = HttpRequest( + httpMethod: .get, + endpoint: Endpoint.appDataPing(client: client), + options: options + ) return request } - func buildAppDataGetById(collectionName: String, id: String) -> HttpRequest { - let request = HttpRequest(endpoint: Endpoint.appDataById(client: client, collectionName: collectionName, id: id), credential: client.activeUser, client: client) + func buildAppDataGetById( + collectionName: String, + id: String, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + endpoint: Endpoint.appDataById(client: client, collectionName: collectionName, id: id), + credential: client.activeUser, + options: options + ) return request } - func buildAppDataFindByQuery(collectionName: String, query: Query) -> HttpRequest { - let request = HttpRequest(endpoint: Endpoint.appDataByQuery(client: client, collectionName: collectionName, query: query.isEmpty ? nil : query), credential: client.activeUser, client: client) + func buildAppDataFindByQuery( + collectionName: String, + query: Query, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + endpoint: Endpoint.appDataByQuery(client: client, collectionName: collectionName, query: query.isEmpty ? nil : query), + credential: client.activeUser, + options: options + ) return request } - func buildAppDataCountByQuery(collectionName: String, query: Query?) -> HttpRequest { - let request = HttpRequest(endpoint: Endpoint.appDataCount(client: client, collectionName: collectionName, query: query), credential: client.activeUser, client: client) + func buildAppDataCountByQuery( + collectionName: String, + query: Query?, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + endpoint: Endpoint.appDataCount(client: client, collectionName: collectionName, query: query), + credential: client.activeUser, + options: options + ) return request } - func buildAppDataGroup(collectionName: String, keys: [String], initialObject: [String : Any], reduceJSFunction: String, condition: NSPredicate?) -> HttpRequest { - let request = HttpRequest(httpMethod: .post, endpoint: Endpoint.appDataGroup(client: client, collectionName: collectionName), credential: client.activeUser, client: client) + func buildAppDataGroup( + collectionName: String, + keys: [String], + initialObject: [String : Any], + reduceJSFunction: String, + condition: NSPredicate?, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + httpMethod: .post, + endpoint: Endpoint.appDataGroup(client: client, collectionName: collectionName), + credential: client.activeUser, + options: options + ) var json: [String : Any] = [ "key" : keys, "initial" : initialObject, @@ -176,7 +329,10 @@ class HttpRequestFactory: RequestFactory { return request } - func buildAppDataSave(_ persistable: T) -> HttpRequest { + func buildAppDataSave( + _ persistable: T, + options: Options? + ) -> HttpRequest { let collectionName = T.collectionName() var bodyObject = persistable.toJSON() let objId = bodyObject[Entity.Key.entityId] as? String @@ -185,7 +341,7 @@ class HttpRequestFactory: RequestFactory { httpMethod: isNewObj ? .post : .put, endpoint: isNewObj ? Endpoint.appData(client: client, collectionName: collectionName) : Endpoint.appDataById(client: client, collectionName: collectionName, id: objId!), credential: client.activeUser, - client: client + options: options ) request.request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -198,32 +354,40 @@ class HttpRequestFactory: RequestFactory { return request } - func buildAppDataRemoveByQuery(collectionName: String, query: Query) -> HttpRequest { + func buildAppDataRemoveByQuery( + collectionName: String, + query: Query, + options: Options? + ) -> HttpRequest { let request = HttpRequest( httpMethod: .delete, endpoint: Endpoint.appDataByQuery(client: client, collectionName: collectionName, query: query), credential: client.activeUser, - client: client + options: options ) return request } - func buildAppDataRemoveById(collectionName: String, objectId: String) -> HttpRequest { + func buildAppDataRemoveById( + collectionName: String, + objectId: String, + options: Options? + ) -> HttpRequest { let request = HttpRequest( httpMethod: .delete, endpoint: Endpoint.appDataById(client: client, collectionName: collectionName, id: objectId), credential: client.activeUser, - client: client + options: options ) return request } - func buildPushRegisterDevice(_ deviceToken: Data) -> HttpRequest { + func buildPushRegisterDevice(_ deviceToken: Data, options: Options?) -> HttpRequest { let request = HttpRequest( httpMethod: .post, endpoint: Endpoint.pushRegisterDevice(client: client), credential: client.activeUser, - client: client + options: options ) let bodyObject = [ @@ -235,12 +399,12 @@ class HttpRequestFactory: RequestFactory { return request } - func buildPushUnRegisterDevice(_ deviceToken: Data) -> HttpRequest { + func buildPushUnRegisterDevice(_ deviceToken: Data, options: Options?) -> HttpRequest { let request = HttpRequest( httpMethod: .post, endpoint: Endpoint.pushUnRegisterDevice(client: client), credential: client.activeUser, - client: client + options: options ) let bodyObject = [ @@ -252,12 +416,19 @@ class HttpRequestFactory: RequestFactory { return request } - func buildBlobUploadFile(_ file: File) -> HttpRequest { + func buildBlobUploadFile( + _ file: File, + options: Options? + ) -> HttpRequest { let request = HttpRequest( httpMethod: file.fileId == nil ? .post : .put, - endpoint: Endpoint.blobUpload(client: client, fileId: file.fileId, tls: true), + endpoint: Endpoint.blobUpload( + client: client, + fileId: file.fileId, + tls: true + ), credential: client.activeUser, - client: client + options: options ) let bodyObject = file.toJSON() @@ -274,52 +445,81 @@ class HttpRequestFactory: RequestFactory { return nil } - func buildBlobDownloadFile(_ file: File, ttl: TTL?) -> HttpRequest { + func buildBlobDownloadFile( + _ file: File, + options: Options? + ) -> HttpRequest { + let ttl = options?.ttl let request = HttpRequest( httpMethod: .get, - endpoint: Endpoint.blobDownload(client: client, fileId: file.fileId!, query: nil, tls: true, ttlInSeconds: ttlInSeconds(ttl)), + endpoint: Endpoint.blobDownload( + client: client, + fileId: file.fileId!, + query: nil, + tls: true, + ttlInSeconds: ttlInSeconds(ttl) + ), credential: client.activeUser, - client: client + options: options ) return request } - func buildBlobDeleteFile(_ file: File) -> HttpRequest { + func buildBlobDeleteFile( + _ file: File, + options: Options? + ) -> HttpRequest { let request = HttpRequest( httpMethod: .delete, endpoint: Endpoint.blobById(client: client, fileId: file.fileId!), credential: client.activeUser, - client: client + options: options ) return request } - func buildBlobQueryFile(_ query: Query, ttl: TTL?) -> HttpRequest { + func buildBlobQueryFile( + _ query: Query, + options: Options? + ) -> HttpRequest { + let ttl = options?.ttl let request = HttpRequest( httpMethod: .get, - endpoint: Endpoint.blobDownload(client: client, fileId: nil, query: query, tls: true, ttlInSeconds: ttlInSeconds(ttl)), + endpoint: Endpoint.blobDownload( + client: client, + fileId: nil, + query: query, + tls: true, + ttlInSeconds: ttlInSeconds(ttl) + ), credential: client.activeUser, - client: client + options: options ) return request } - func buildCustomEndpoint(_ name: String) -> HttpRequest { + func buildCustomEndpoint( + _ name: String, + options: Options? + ) -> HttpRequest { let request = HttpRequest( httpMethod: .post, endpoint: Endpoint.customEndpooint(client: client, name: name), credential: client.activeUser, - client: client + options: options ) return request } - func buildSendEmailConfirmation(forUsername username: String) -> HttpRequest { + func buildSendEmailConfirmation( + forUsername username: String, + options: Options? + ) -> HttpRequest { let request = HttpRequest( httpMethod: .post, endpoint: Endpoint.sendEmailConfirmation(client: client, username: username), credential: client, - client: client + options: options ) return request } @@ -334,128 +534,225 @@ class HttpRequestFactory: RequestFactory { } } - func buildOAuthToken(redirectURI: URL, code: String, clientId: String?) -> HttpRequest { + func buildOAuthToken( + redirectURI: URL, + code: String, + options: Options? + ) -> HttpRequest { var params = [ "grant_type" : "authorization_code", "redirect_uri" : redirectURI.absoluteString, "code" : code ] - set(¶ms, clientId: clientId) + set(¶ms, clientId: options?.clientId) let request = HttpRequest( httpMethod: .post, endpoint: Endpoint.oauthToken(client: client), credential: client, body: Body.formUrlEncoded(params: params), - client: client + options: options ) return request } - func buildOAuthGrantAuth(redirectURI: URL, clientId: String?) -> HttpRequest { + func buildOAuthGrantAuth( + redirectURI: URL, + options: Options? + ) -> HttpRequest { var json = [ "redirect_uri" : redirectURI.absoluteString, "response_type" : "code" ] + let clientId = options?.clientId set(&json, clientId: clientId) let request = HttpRequest( httpMethod: .post, - endpoint: Endpoint.oauthAuth(client: client, clientId: clientId, redirectURI: redirectURI, loginPage: false), + endpoint: Endpoint.oauthAuth( + client: client, + clientId: clientId, + redirectURI: redirectURI, + loginPage: false + ), credential: client, body: Body.json(json: json), - client: client + options: options ) return request } - func buildOAuthGrantAuthenticate(redirectURI: URL, clientId: String?, tempLoginUri: URL, username: String, password: String) -> HttpRequest { + func buildOAuthGrantAuthenticate( + redirectURI: URL, + tempLoginUri: URL, + username: String, + password: String, + options: Options? + ) -> HttpRequest { var params = [ "response_type" : "code", "redirect_uri" : redirectURI.absoluteString, "username" : username, "password" : password ] - set(¶ms, clientId: clientId) + set(¶ms, clientId: options?.clientId) let request = HttpRequest( httpMethod: .post, endpoint: Endpoint.url(url: tempLoginUri), credential: client, body: Body.formUrlEncoded(params: params), - client: client + options: options ) return request } - func buildOAuthGrantRefreshToken(refreshToken: String, clientId: String?) -> HttpRequest { + func buildOAuthGrantRefreshToken( + refreshToken: String, + options: Options? + ) -> HttpRequest { var params = [ "grant_type" : "refresh_token", "refresh_token" : refreshToken ] - set(¶ms, clientId: clientId) + set(¶ms, clientId: options?.clientId) let request = HttpRequest( httpMethod: .post, endpoint: Endpoint.oauthToken(client: client), credential: client, body: Body.formUrlEncoded(params: params), - client: client + options: options ) return request } // MARK: Realtime - func buildUserRegisterRealtime(user: User, deviceId: String) -> HttpRequest { - let request = HttpRequest(httpMethod: .post, endpoint: Endpoint.userRegisterRealtime(client: client, user: user), credential: client.activeUser, client: client) + func buildUserRegisterRealtime( + user: User, + deviceId: String, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + httpMethod: .post, + endpoint: Endpoint.userRegisterRealtime(client: client, user: user), + credential: client.activeUser, + options: options + ) request.setBody(json: [ "deviceId" : deviceId ]) return request } - func buildUserUnregisterRealtime(user: User, deviceId: String) -> HttpRequest { - let request = HttpRequest(httpMethod: .post, endpoint: Endpoint.userUnregisterRealtime(client: client, user: user), credential: client.activeUser, client: client) + func buildUserUnregisterRealtime( + user: User, + deviceId: String, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + httpMethod: .post, + endpoint: Endpoint.userUnregisterRealtime(client: client, user: user), + credential: client.activeUser, + options: options + ) request.setBody(json: [ "deviceId" : deviceId ]) return request } - func buildAppDataSubscribe(collectionName: String, deviceId: String) -> HttpRequest { - let request = HttpRequest(httpMethod: .post, endpoint: Endpoint.appDataSubscribe(client: client, collectionName: collectionName), credential: client.activeUser, client: client) + func buildAppDataSubscribe( + collectionName: String, + deviceId: String, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + httpMethod: .post, + endpoint: Endpoint.appDataSubscribe(client: client, collectionName: collectionName), + credential: client.activeUser, + options: options + ) request.setBody(json: [ "deviceId" : deviceId ]) return request } - func buildAppDataUnSubscribe(collectionName: String, deviceId: String) -> HttpRequest { - let request = HttpRequest(httpMethod: .post, endpoint: Endpoint.appDataUnSubscribe(client: client, collectionName: collectionName), credential: client.activeUser, client: client) + func buildAppDataUnSubscribe( + collectionName: String, + deviceId: String, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + httpMethod: .post, + endpoint: Endpoint.appDataUnSubscribe(client: client, collectionName: collectionName), + credential: client.activeUser, + options: options + ) request.setBody(json: [ "deviceId" : deviceId ]) return request } - func buildLiveStreamGrantAccess(streamName: String, userId: String, acl: LiveStreamAcl) -> HttpRequest { - let request = HttpRequest(httpMethod: .put, endpoint: Endpoint.liveStreamByUser(client: client, streamName: streamName, userId: userId), credential: client.activeUser, client: client) + func buildLiveStreamGrantAccess( + streamName: String, + userId: String, + acl: LiveStreamAcl, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + httpMethod: .put, + endpoint: Endpoint.liveStreamByUser(client: client, streamName: streamName, userId: userId), + credential: client.activeUser, + options: options + ) request.setBody(json: acl.toJSON()) return request } - func buildLiveStreamPublish(streamName: String, userId: String) -> HttpRequest { - let request = HttpRequest(httpMethod: .post, endpoint: Endpoint.liveStreamPublish(client: client, streamName: streamName, userId: userId), credential: client.activeUser, client: client) + func buildLiveStreamPublish( + streamName: String, + userId: String, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + httpMethod: .post, + endpoint: Endpoint.liveStreamPublish(client: client, streamName: streamName, userId: userId), + credential: client.activeUser, + options: options + ) return request } - func buildLiveStreamSubscribe(streamName: String, userId: String, deviceId: String) -> HttpRequest { - let request = HttpRequest(httpMethod: .post, endpoint: Endpoint.liveStreamSubscribe(client: client, streamName: streamName, userId: userId), credential: client.activeUser, client: client) + func buildLiveStreamSubscribe( + streamName: String, + userId: String, + deviceId: String, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + httpMethod: .post, + endpoint: Endpoint.liveStreamSubscribe(client: client, streamName: streamName, userId: userId), + credential: client.activeUser, + options: options + ) request.setBody(json: [ "deviceId" : deviceId ]) return request } - func buildLiveStreamUnsubscribe(streamName: String, userId: String, deviceId: String) -> HttpRequest { - let request = HttpRequest(httpMethod: .post, endpoint: Endpoint.liveStreamUnsubscribe(client: client, streamName: streamName, userId: userId), credential: client.activeUser, client: client) + func buildLiveStreamUnsubscribe( + streamName: String, + userId: String, + deviceId: String, + options: Options? + ) -> HttpRequest { + let request = HttpRequest( + httpMethod: .post, + endpoint: Endpoint.liveStreamUnsubscribe(client: client, streamName: streamName, userId: userId), + credential: client.activeUser, + options: options + ) request.setBody(json: [ "deviceId" : deviceId ]) diff --git a/Kinvey/Kinvey/MIC.swift b/Kinvey/Kinvey/MIC.swift index b7e3755fe..1048dff37 100644 --- a/Kinvey/Kinvey/MIC.swift +++ b/Kinvey/Kinvey/MIC.swift @@ -52,12 +52,12 @@ open class MIC { open class func urlForLogin( redirectURI: URL, loginPage: Bool = true, - clientId: String? = nil, - client: Client = sharedClient + options: Options? = nil ) -> URL { + let client = options?.client ?? sharedClient return Endpoint.oauthAuth( client: client, - clientId: clientId, + clientId: options?.clientId, redirectURI: redirectURI, loginPage: loginPage ).url @@ -67,16 +67,27 @@ open class MIC { class func login( redirectURI: URL, code: String, - clientId: String?, - client: Client = sharedClient, + options: Options? = nil, completionHandler: ((Result) -> Void)? = nil ) -> Request { + let client = options?.client ?? sharedClient let requests = MultiRequest() Promise { fulfill, reject in - let request = client.networkRequestFactory.buildOAuthToken(redirectURI: redirectURI, code: code, clientId: clientId) + let request = client.networkRequestFactory.buildOAuthToken( + redirectURI: redirectURI, + code: code, + options: options + ) request.execute { (data, response, error) in - if let response = response, response.isOK, let authData = client.responseParser.parse(data) { - requests += User.login(authSource: .kinvey, authData, clientId: clientId, client: client) { (result: Result) in + if let response = response, + response.isOK, + let authData = client.responseParser.parse(data) + { + requests += User.login( + authSource: .kinvey, + authData, + options: options + ) { (result: Result) in switch result { case .success(let user): fulfill(user) @@ -102,12 +113,15 @@ open class MIC { redirectURI: URL, username: String, password: String, - clientId: String?, - client: Client = sharedClient, + options: Options? = nil, completionHandler: ((Result) -> Void)? = nil ) -> Request { + let client = options?.client ?? sharedClient let requests = MultiRequest() - let request = client.networkRequestFactory.buildOAuthGrantAuth(redirectURI: redirectURI, clientId: clientId) + let request = client.networkRequestFactory.buildOAuthGrantAuth( + redirectURI: redirectURI, + options: options + ) Promise { fulfill, reject in request.execute { (data, response, error) in if let response = response, @@ -124,8 +138,18 @@ open class MIC { requests += request }.then { tempLoginUrl in return Promise { fulfill, reject in - let request = client.networkRequestFactory.buildOAuthGrantAuthenticate(redirectURI: redirectURI, clientId: clientId, tempLoginUri: tempLoginUrl, username: username, password: password) - let urlSession = URLSession(configuration: client.urlSession.configuration, delegate: URLSessionDelegateAdapter(), delegateQueue: nil) + let request = client.networkRequestFactory.buildOAuthGrantAuthenticate( + redirectURI: redirectURI, + tempLoginUri: tempLoginUrl, + username: username, + password: password, + options: options + ) + let urlSession = URLSession( + configuration: client.urlSession.configuration, + delegate: URLSessionDelegateAdapter(), + delegateQueue: nil + ) request.execute(urlSession: urlSession) { (data, response, error) in if let response = response, let httpResponse = response as? HttpResponse, @@ -134,7 +158,11 @@ open class MIC { let url = URL(string: location), let code = parseCode(redirectURI: redirectURI, url: url) { - requests += login(redirectURI: redirectURI, code: code, clientId: clientId, client: client) { result in + requests += login( + redirectURI: redirectURI, + code: code, + options: options + ) { result in switch result { case .success(let user): fulfill(user as! U) @@ -165,7 +193,12 @@ open class MIC { completionHandler: User.UserHandler? = nil ) -> Request { let requests = MultiRequest() - let request = client.networkRequestFactory.buildOAuthGrantRefreshToken(refreshToken: refreshToken, clientId: clientId) + let request = client.networkRequestFactory.buildOAuthGrantRefreshToken( + refreshToken: refreshToken, + options: Options( + clientId: clientId + ) + ) request.execute { (data, response, error) in if let response = response, response.isOK, let authData = client.responseParser.parse(data) { requests += User.login(authSource: .kinvey, authData, client: client, completionHandler: completionHandler) @@ -219,10 +252,8 @@ class MICLoginViewController: UIViewController, WKNavigationDelegate, UIWebViewD var activityIndicatorView: UIActivityIndicatorView! let redirectURI: URL - let timeout: TimeInterval? let forceUIWebView: Bool - let clientId: String? - let client: Client + let options: Options? let completionHandler: UserHandler var webView: UIView! @@ -234,12 +265,16 @@ class MICLoginViewController: UIViewController, WKNavigationDelegate, UIWebViewD } } - init(redirectURI: URL, userType: UserType.Type, timeout: TimeInterval? = nil, forceUIWebView: Bool = false, clientId: String?, client: Client = sharedClient, completionHandler: @escaping UserHandler) { + init( + redirectURI: URL, + userType: UserType.Type, + forceUIWebView: Bool = false, + options: Options?, + completionHandler: @escaping UserHandler + ) { self.redirectURI = redirectURI - self.timeout = timeout self.forceUIWebView = forceUIWebView - self.clientId = clientId - self.client = client + self.options = options self.completionHandler = { switch $0 { case .success(let user): @@ -354,14 +389,14 @@ class MICLoginViewController: UIViewController, WKNavigationDelegate, UIWebViewD override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - let url = MIC.urlForLogin(redirectURI: redirectURI, clientId: clientId, client: client) + let url = MIC.urlForLogin(redirectURI: redirectURI, options: options) let request = URLRequest(url: url) webView( wkWebView: { $0.load(request) }, uiWebView: { $0.loadRequest(request) } ) - if let timeout = timeout, timeout > 0 { + if let timeout = options?.timeout, timeout > 0 { timer = Timer.scheduledTimer( timeInterval: timeout, target: self, @@ -405,7 +440,11 @@ class MICLoginViewController: UIViewController, WKNavigationDelegate, UIWebViewD func success(code: String) { activityIndicatorView.startAnimating() - MIC.login(redirectURI: redirectURI, code: code, clientId: clientId, client: client) { result in + MIC.login( + redirectURI: redirectURI, + code: code, + options: options + ) { result in self.activityIndicatorView.stopAnimating() self.closeViewControllerUserInteraction(result) diff --git a/Kinvey/Kinvey/Operation.swift b/Kinvey/Kinvey/Operation.swift index a3e9c2095..c14fcd99d 100644 --- a/Kinvey/Kinvey/Operation.swift +++ b/Kinvey/Kinvey/Operation.swift @@ -113,11 +113,16 @@ internal class Operation: NSObject where T: NSObject { typealias UIntArrayCompletionHandler = (UInt?, [T]?, Swift.Error?) -> Void let cache: AnyCache? + let options: Options? let client: Client - init(cache: AnyCache? = nil, client: Client) { + init( + cache: AnyCache? = nil, + options: Options? + ) { self.cache = cache - self.client = client + self.options = options + self.client = options?.client ?? sharedClient } func reduceToIdsLmts(_ jsonArray: [JsonDictionary]) -> [String : String] { diff --git a/Kinvey/Kinvey/PullOperation.swift b/Kinvey/Kinvey/PullOperation.swift index 5a1640880..0158be9a9 100644 --- a/Kinvey/Kinvey/PullOperation.swift +++ b/Kinvey/Kinvey/PullOperation.swift @@ -10,8 +10,24 @@ import Foundation internal class PullOperation: FindOperation where T: NSObject { - override init(query: Query, deltaSet: Bool, deltaSetCompletionHandler: (([T]) -> Void)?, readPolicy: ReadPolicy, cache: AnyCache?, client: Client, resultsHandler: ResultsHandler? = nil) { - super.init(query: query, deltaSet: deltaSet, deltaSetCompletionHandler: deltaSetCompletionHandler, readPolicy: readPolicy, cache: cache, client: client, resultsHandler: resultsHandler) + override init( + query: Query, + deltaSet: Bool, + deltaSetCompletionHandler: (([T]) -> Void)?, + readPolicy: ReadPolicy, + cache: AnyCache?, + options: Options?, + resultsHandler: ResultsHandler? = nil + ) { + super.init( + query: query, + deltaSet: deltaSet, + deltaSetCompletionHandler: deltaSetCompletionHandler, + readPolicy: readPolicy, + cache: cache, + options: options, + resultsHandler: resultsHandler + ) } override var mustRemoveCachedRecords: Bool { diff --git a/Kinvey/Kinvey/PurgeOperation.swift b/Kinvey/Kinvey/PurgeOperation.swift index 2232fbb38..8b49f96da 100644 --- a/Kinvey/Kinvey/PurgeOperation.swift +++ b/Kinvey/Kinvey/PurgeOperation.swift @@ -11,8 +11,16 @@ import PromiseKit internal class PurgeOperation: SyncOperation where T: NSObject { - internal override init(sync: AnySync?, cache: AnyCache?, client: Client) { - super.init(sync: sync, cache: cache, client: client) + internal override init( + sync: AnySync?, + cache: AnyCache?, + options: Options? + ) { + super.init( + sync: sync, + cache: cache, + options: options + ) } func execute(timeout: TimeInterval? = nil, completionHandler: CompletionHandler?) -> Request { @@ -28,7 +36,11 @@ internal class PurgeOperation: SyncOperation { fulfill, reject in - let request = client.networkRequestFactory.buildAppDataGetById(collectionName: T.collectionName(), id: objectId) + let request = client.networkRequestFactory.buildAppDataGetById( + collectionName: T.collectionName(), + id: objectId, + options: options + ) requests.addRequest(request) request.execute() { data, response, error in if let response = response, response.isOK, let json = self.client.responseParser.parse(data) { diff --git a/Kinvey/Kinvey/Push.swift b/Kinvey/Kinvey/Push.swift index e749da136..e31443a89 100644 --- a/Kinvey/Kinvey/Push.swift +++ b/Kinvey/Kinvey/Push.swift @@ -70,7 +70,10 @@ open class Push { private static let lock = NSLock() private static var appDelegateMethodsNeedsToRun = true - private func replaceAppDelegateMethods(_ completionHandler: ((Result) -> Void)?) { + private func replaceAppDelegateMethods( + options: Options?, + completionHandler: ((Result) -> Void)? + ) { func replaceAppDelegateMethods(_ completionHandler: ((Result) -> Void)?) { let app = UIApplication.shared let appDelegate = app.delegate! @@ -83,7 +86,12 @@ open class Push { let originalApplicationDidFailToRegisterForRemoteNotificationsWithErrorMethod = class_getInstanceMethod(appDelegateType, applicationDidFailToRegisterForRemoteNotificationsWithErrorSelector) let applicationDidRegisterForRemoteNotificationsWithDeviceTokenBlock: @convention(block) (NSObject, UIApplication, Data) -> Void = { obj, application, deviceToken in - self.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken, completionHandler: completionHandler) + self.application( + application, + didRegisterForRemoteNotificationsWithDeviceToken: deviceToken, + options: options, + completionHandler: completionHandler + ) if let originalApplicationDidRegisterForRemoteNotificationsWithDeviceTokenImplementation = self.originalApplicationDidRegisterForRemoteNotificationsWithDeviceTokenImplementation { let implementation = unsafeBitCast(originalApplicationDidRegisterForRemoteNotificationsWithDeviceTokenImplementation, to: ApplicationDidRegisterForRemoteNotificationsWithDeviceTokenImplementation.self) @@ -141,7 +149,11 @@ open class Push { ``` */ @available(iOS, deprecated: 10.0, message: "Please use registerForNotifications() instead") - open func registerForPush(forTypes types: UIUserNotificationType = [.alert, .badge, .sound], categories: Set? = nil, completionHandler: BoolCompletionHandler? = nil) { + open func registerForPush( + forTypes types: UIUserNotificationType = [.alert, .badge, .sound], + categories: Set? = nil, + completionHandler: BoolCompletionHandler? = nil + ) { registerForPush( forTypes: types, categories: categories @@ -166,8 +178,17 @@ open class Push { ``` */ @available(iOS, deprecated: 10.0, message: "Please use registerForNotifications() instead") - open func registerForPush(forTypes types: UIUserNotificationType = [.alert, .badge, .sound], categories: Set? = nil, completionHandler: ((Result) -> Void)? = nil) { - replaceAppDelegateMethods(completionHandler) + open func registerForPush( + forTypes types: UIUserNotificationType = [.alert, .badge, .sound], + categories: Set? = nil, + completionHandler: ((Result) -> Void)? = nil + ) { + replaceAppDelegateMethods( + options: Options( + client: client + ), + completionHandler: completionHandler + ) let app = UIApplication.shared let userNotificationSettings = UIUserNotificationSettings( @@ -179,10 +200,17 @@ open class Push { } @available(iOS 10.0, *) - open func registerForNotifications(authorizationOptions: UNAuthorizationOptions = [.badge, .sound, .alert, .carPlay], categories: Set? = nil, completionHandler: BoolCompletionHandler? = nil) { + open func registerForNotifications( + authorizationOptions: UNAuthorizationOptions = [.badge, .sound, .alert, .carPlay], + categories: Set? = nil, + completionHandler: BoolCompletionHandler? = nil + ) { registerForNotifications( authorizationOptions: authorizationOptions, - categories: categories + categories: categories, + options: Options( + client: client + ) ) { (result: Result) in switch result { case .success(let granted): @@ -194,13 +222,37 @@ open class Push { } @available(iOS 10.0, *) - open func registerForNotifications(authorizationOptions: UNAuthorizationOptions = [.badge, .sound, .alert, .carPlay], categories: Set? = nil, completionHandler: ((Result) -> Void)? = nil) { + open func registerForNotifications( + authorizationOptions: UNAuthorizationOptions = [.badge, .sound, .alert, .carPlay], + categories: Set? = nil, + completionHandler: ((Result) -> Void)? = nil + ) { + return registerForNotifications( + authorizationOptions: authorizationOptions, + categories: categories, + options: Options( + client: client + ), + completionHandler: completionHandler + ) + } + + @available(iOS 10.0, *) + open func registerForNotifications( + authorizationOptions: UNAuthorizationOptions = [.badge, .sound, .alert, .carPlay], + categories: Set? = nil, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) { UNUserNotificationCenter.current().requestAuthorization(options: authorizationOptions) { granted, error in if granted { if let categories = categories { UNUserNotificationCenter.current().setNotificationCategories(categories) } - self.replaceAppDelegateMethods(completionHandler) + self.replaceAppDelegateMethods( + options: options, + completionHandler: completionHandler + ) UIApplication.shared.registerForRemoteNotifications() } else { if let error = error { @@ -214,8 +266,14 @@ open class Push { #endif /// Unregister the current device to receive push notifications. - open func unRegisterDeviceToken(_ completionHandler: BoolCompletionHandler? = nil) { - unRegisterDeviceToken { (result: Result) in + open func unRegisterDeviceToken( + _ completionHandler: BoolCompletionHandler? = nil + ) { + unRegisterDeviceToken( + options: Options( + client: client + ) + ) { (result: Result) in switch result { case .success: completionHandler?(true, nil) @@ -226,14 +284,32 @@ open class Push { } /// Unregister the current device to receive push notifications. - open func unRegisterDeviceToken(_ completionHandler: ((Result) -> Void)? = nil) { + open func unRegisterDeviceToken( + _ completionHandler: ((Result) -> Void)? = nil + ) { + return unRegisterDeviceToken( + options: Options( + client: client + ), + completionHandler: completionHandler + ) + } + + /// Unregister the current device to receive push notifications. + open func unRegisterDeviceToken( + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) { Promise { fulfill, reject in guard let deviceToken = deviceToken else { reject(Error.invalidOperation(description: "Device token not found")) return } - let request = self.client.networkRequestFactory.buildPushUnRegisterDevice(deviceToken) + let request = self.client.networkRequestFactory.buildPushUnRegisterDevice( + deviceToken, + options: options + ) request.execute { (data, response, error) -> Void in if let response = response, response.isOK @@ -253,18 +329,26 @@ open class Push { /// Call this method inside your App Delegate method `application(application:didRegisterForRemoteNotificationsWithDeviceToken:completionHandler:)`. #if os(iOS) - fileprivate func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data, completionHandler: ((Result) -> Void)? = nil) { + fileprivate func application( + _ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data, + options: Options?, + completionHandler: ((Result) -> Void)? = nil + ) { self.deviceToken = deviceToken let block: () -> Void = { Promise { fulfill, reject in - let request = self.client.networkRequestFactory.buildPushRegisterDevice(deviceToken) - request.execute({ (data, response, error) -> Void in + let request = self.client.networkRequestFactory.buildPushRegisterDevice( + deviceToken, + options: options + ) + request.execute { (data, response, error) -> Void in if let response = response, response.isOK { fulfill(true) } else { reject(buildError(data, response, error, self.client)) } - }) + } }.then { success in completionHandler?(.success(success)) }.catch { error in diff --git a/Kinvey/Kinvey/PushOperation.swift b/Kinvey/Kinvey/PushOperation.swift index f1cdb4554..17f33c5f4 100644 --- a/Kinvey/Kinvey/PushOperation.swift +++ b/Kinvey/Kinvey/PushOperation.swift @@ -63,11 +63,19 @@ fileprivate class PushRequest: NSObject, Request { internal class PushOperation: SyncOperation where T: NSObject { - internal override init(sync: AnySync?, cache: AnyCache?, client: Client) { - super.init(sync: sync, cache: cache, client: client) + internal override init( + sync: AnySync?, + cache: AnyCache?, + options: Options? + ) { + super.init( + sync: sync, + cache: cache, + options: options + ) } - func execute(timeout: TimeInterval? = nil, completionHandler: CompletionHandler?) -> Request { + func execute(completionHandler: CompletionHandler?) -> Request { var count = UInt(0) var errors: [Swift.Error] = [] @@ -84,7 +92,10 @@ internal class PushOperation: SyncOperation: Operation where T: NSObje let readPolicy: ReadPolicy - init(readPolicy: ReadPolicy, cache: AnyCache?, client: Client) { + init( + readPolicy: ReadPolicy, + cache: AnyCache?, + options: Options? + ) { self.readPolicy = readPolicy - super.init(cache: cache, client: client) + super.init( + cache: cache, + options: options + ) } } diff --git a/Kinvey/Kinvey/Realtime.swift b/Kinvey/Kinvey/Realtime.swift index d0dc0af0a..394356371 100644 --- a/Kinvey/Kinvey/Realtime.swift +++ b/Kinvey/Kinvey/Realtime.swift @@ -93,11 +93,24 @@ public class LiveStream { /// Grant access to a user for the `LiveStream` @discardableResult - public func grantStreamAccess(userId: String, acl: LiveStreamAcl, completionHandler: ((Result) -> Void)? = nil) -> Request { - let request = client.networkRequestFactory.buildLiveStreamGrantAccess(streamName: name, userId: userId, acl: acl) + public func grantStreamAccess( + userId: String, + acl: LiveStreamAcl, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let request = client.networkRequestFactory.buildLiveStreamGrantAccess( + streamName: name, + userId: userId, + acl: acl, + options: options + ) Promise { fulfill, reject in request.execute() { (data, response, error) in - if let response = response, response.isOK, let _ = data { + if let response = response, + response.isOK, + let _ = data + { fulfill() } else { reject(buildError(data, response, error, self.client)) @@ -112,13 +125,23 @@ public class LiveStream { } /// Sends a message to an specific user - public func send(userId: String, message: Type, retry: Bool = true, completionHandler: ((Result) -> Void)? = nil) { + public func send( + userId: String, + message: Type, + retry: Bool = true, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) { realtimeRouterPromise.then { activeUser, realtimeRouter in return Promise<(RealtimeRouter, String)> { fulfill, reject in if let channelName = self.substreamChannelNameMap[userId] { fulfill(realtimeRouter, channelName) } else { - let request = self.client.networkRequestFactory.buildLiveStreamPublish(streamName: self.name, userId: userId) + let request = self.client.networkRequestFactory.buildLiveStreamPublish( + streamName: self.name, + userId: userId, + options: options + ) request.execute() { (data, response, error) in if let response = response, response.isOK, @@ -172,31 +195,55 @@ public class LiveStream { /// Start listening messages sent to the current active user public func listen( + options: Options? = nil, listening: @escaping () -> Void, onNext: @escaping (Type) -> Void, onStatus: @escaping (RealtimeStatus) -> Void, onError: @escaping (Swift.Error) -> Void ) { realtimeRouterPromise.then { activeUser, realtimeRouter in - self.follow(userId: activeUser.userId, following: listening, onNext: onNext, onStatus: onStatus, onError: onError) + self.follow( + userId: activeUser.userId, + options: options, + following: listening, + onNext: onNext, + onStatus: onStatus, + onError: onError + ) }.catch { error in onError(error) } } - /// /// Stop listening messages sent to the current active user - public func stopListening(completionHandler: ((Result) -> Void)? = nil) { + /// Stop listening messages sent to the current active user + public func stopListening( + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) { realtimeRouterPromise.then { activeUser, _ in - self.unfollow(userId: activeUser.userId, completionHandler: completionHandler) + self.unfollow( + userId: activeUser.userId, + options: options, + completionHandler: completionHandler + ) }.catch { error in completionHandler?(.failure(error)) } } /// Sends a message to the current active user - public func post(message: Type, completionHandler: ((Result) -> Void)? = nil) { + public func post( + message: Type, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) { realtimeRouterPromise.then { activeUser, _ in - self.send(userId: activeUser.userId, message: message, completionHandler: completionHandler) + self.send( + userId: activeUser.userId, + message: message, + options: options, + completionHandler: completionHandler + ) }.catch { error in completionHandler?(.failure(error)) } @@ -205,6 +252,7 @@ public class LiveStream { /// Start listening messages sent to an specific user public func follow( userId: String, + options: Options? = nil, following: @escaping () -> Void, onNext: @escaping (Type) -> Void, onStatus: @escaping (RealtimeStatus) -> Void, @@ -215,7 +263,12 @@ public class LiveStream { if let channelName = self.substreamChannelNameMap[userId] { fulfill(realtimeRouter, channelName) } else { - let request = self.client.networkRequestFactory.buildLiveStreamSubscribe(streamName: self.name, userId: userId, deviceId: deviceId) + let request = self.client.networkRequestFactory.buildLiveStreamSubscribe( + streamName: self.name, + userId: userId, + deviceId: deviceId, + options: options + ) request.execute() { (data, response, error) in if let response = response, response.isOK, @@ -252,10 +305,19 @@ public class LiveStream { } /// Stop listening messages sent to an specific user - public func unfollow(userId: String, completionHandler: ((Result) -> Void)? = nil) { + public func unfollow( + userId: String, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) { realtimeRouterPromise.then { activeUser, realtimeRouter in return Promise { fulfill, reject in - let request = self.client.networkRequestFactory.buildLiveStreamUnsubscribe(streamName: self.name, userId: userId, deviceId: deviceId) + let request = self.client.networkRequestFactory.buildLiveStreamUnsubscribe( + streamName: self.name, + userId: userId, + deviceId: deviceId, + options: options + ) request.execute() { (data, response, error) in if let response = response, response.isOK { fulfill(realtimeRouter) diff --git a/Kinvey/Kinvey/RemoveByIdOperation.swift b/Kinvey/Kinvey/RemoveByIdOperation.swift index d580802de..5523da751 100644 --- a/Kinvey/Kinvey/RemoveByIdOperation.swift +++ b/Kinvey/Kinvey/RemoveByIdOperation.swift @@ -12,11 +12,29 @@ internal class RemoveByIdOperation: RemoveOperation where T: let objectId: String - internal init(objectId: String, writePolicy: WritePolicy, sync: AnySync? = nil, cache: AnyCache? = nil, client: Client) { + internal init( + objectId: String, + writePolicy: WritePolicy, + sync: AnySync? = nil, + cache: AnyCache? = nil, + options: Options? + ) { self.objectId = objectId let query = Query(format: "\(T.entityIdProperty()) == %@", objectId as Any) - let httpRequest = client.networkRequestFactory.buildAppDataRemoveById(collectionName: T.collectionName(), objectId: objectId) - super.init(query: query, httpRequest: httpRequest, writePolicy: writePolicy, sync: sync, cache: cache, client: client) + let client = options?.client ?? sharedClient + let httpRequest = client.networkRequestFactory.buildAppDataRemoveById( + collectionName: T.collectionName(), + objectId: objectId, + options: options + ) + super.init( + query: query, + httpRequest: httpRequest, + writePolicy: writePolicy, + sync: sync, + cache: cache, + options: options + ) } } diff --git a/Kinvey/Kinvey/RemoveByQueryOperation.swift b/Kinvey/Kinvey/RemoveByQueryOperation.swift index f5724724f..898d337ea 100644 --- a/Kinvey/Kinvey/RemoveByQueryOperation.swift +++ b/Kinvey/Kinvey/RemoveByQueryOperation.swift @@ -10,9 +10,27 @@ import Foundation internal class RemoveByQueryOperation: RemoveOperation where T: NSObject { - init(query: Query, writePolicy: WritePolicy, sync: AnySync? = nil, cache: AnyCache? = nil, client: Client) { - let httpRequest = client.networkRequestFactory.buildAppDataRemoveByQuery(collectionName: T.collectionName(), query: query) - super.init(query: query, httpRequest: httpRequest, writePolicy: writePolicy, sync: sync, cache: cache, client: client) + init( + query: Query, + writePolicy: WritePolicy, + sync: AnySync? = nil, + cache: AnyCache? = nil, + options: Options? + ) { + let client = options?.client ?? sharedClient + let httpRequest = client.networkRequestFactory.buildAppDataRemoveByQuery( + collectionName: T.collectionName(), + query: query, + options: options + ) + super.init( + query: query, + httpRequest: httpRequest, + writePolicy: writePolicy, + sync: sync, + cache: cache, + options: options + ) } } diff --git a/Kinvey/Kinvey/RemoveOperation.swift b/Kinvey/Kinvey/RemoveOperation.swift index b16bb3b9d..72374bb19 100644 --- a/Kinvey/Kinvey/RemoveOperation.swift +++ b/Kinvey/Kinvey/RemoveOperation.swift @@ -14,10 +14,22 @@ class RemoveOperation: WriteOperation, WriteOperationTyp private let httpRequest: () -> HttpRequest lazy var request: HttpRequest = self.httpRequest() - init(query: Query, httpRequest: @autoclosure @escaping () -> HttpRequest, writePolicy: WritePolicy, sync: AnySync? = nil, cache: AnyCache? = nil, client: Client) { + init( + query: Query, + httpRequest: @autoclosure @escaping () -> HttpRequest, + writePolicy: WritePolicy, + sync: AnySync? = nil, + cache: AnyCache? = nil, + options: Options? + ) { self.query = query self.httpRequest = httpRequest - super.init(writePolicy: writePolicy, sync: sync, cache: cache, client: client) + super.init( + writePolicy: writePolicy, + sync: sync, + cache: cache, + options: options + ) } func executeLocal(_ completionHandler: CompletionHandler? = nil) -> Request { diff --git a/Kinvey/Kinvey/RequestFactory.swift b/Kinvey/Kinvey/RequestFactory.swift index 9e894b753..f4bf87f56 100644 --- a/Kinvey/Kinvey/RequestFactory.swift +++ b/Kinvey/Kinvey/RequestFactory.swift @@ -10,56 +10,97 @@ import Foundation protocol RequestFactory { - func buildUserSignUp(username: String?, password: String?, user: User?) -> HttpRequest - func buildUserDelete(userId: String, hard: Bool) -> HttpRequest - - func buildUserSocialLogin(_ authSource: AuthSource, authData: [String : Any]) -> HttpRequest - func buildUserSocialCreate(_ authSource: AuthSource, authData: [String : Any]) -> HttpRequest - - func buildUserLogin(username: String, password: String) -> HttpRequest - func buildUserExists(username: String) -> HttpRequest - func buildUserGet(userId: String) -> HttpRequest - func buildUserFind(query: Query) -> HttpRequest - func buildUserSave(user: User, newPassword: String?) -> HttpRequest - func buildUserLookup(user: User, userQuery: UserQuery) -> HttpRequest - func buildSendEmailConfirmation(forUsername: String) -> HttpRequest - func buildUserResetPassword(usernameOrEmail: String) -> HttpRequest - func buildUserForgotUsername(email: String) -> HttpRequest - func buildUserMe() -> HttpRequest - - func buildUserRegisterRealtime(user: User, deviceId: String) -> HttpRequest - func buildUserUnregisterRealtime(user: User, deviceId: String) -> HttpRequest - - func buildAppDataPing() -> HttpRequest - func buildAppDataGetById(collectionName: String, id: String) -> HttpRequest - func buildAppDataFindByQuery(collectionName: String, query: Query) -> HttpRequest - func buildAppDataCountByQuery(collectionName: String, query: Query?) -> HttpRequest - func buildAppDataGroup(collectionName: String, keys: [String], initialObject: [String : Any], reduceJSFunction: String, condition: NSPredicate?) -> HttpRequest - func buildAppDataSave(_ persistable: T) -> HttpRequest - func buildAppDataRemoveByQuery(collectionName: String, query: Query) -> HttpRequest - func buildAppDataRemoveById(collectionName: String, objectId: String) -> HttpRequest - func buildAppDataSubscribe(collectionName: String, deviceId: String) -> HttpRequest - func buildAppDataUnSubscribe(collectionName: String, deviceId: String) -> HttpRequest - - func buildPushRegisterDevice(_ deviceToken: Data) -> HttpRequest - func buildPushUnRegisterDevice(_ deviceToken: Data) -> HttpRequest - - func buildBlobUploadFile(_ file: File) -> HttpRequest - func buildBlobDownloadFile(_ file: File, ttl: TTL?) -> HttpRequest - func buildBlobDeleteFile(_ file: File) -> HttpRequest - func buildBlobQueryFile(_ query: Query, ttl: TTL?) -> HttpRequest - - func buildCustomEndpoint(_ name: String) -> HttpRequest - - func buildOAuthToken(redirectURI: URL, code: String, clientId: String?) -> HttpRequest - - func buildOAuthGrantAuth(redirectURI: URL, clientId: String?) -> HttpRequest - func buildOAuthGrantAuthenticate(redirectURI: URL, clientId: String?, tempLoginUri: URL, username: String, password: String) -> HttpRequest - func buildOAuthGrantRefreshToken(refreshToken: String, clientId: String?) -> HttpRequest - - func buildLiveStreamGrantAccess(streamName: String, userId: String, acl: LiveStreamAcl) -> HttpRequest - func buildLiveStreamPublish(streamName: String, userId: String) -> HttpRequest - func buildLiveStreamSubscribe(streamName: String, userId: String, deviceId: String) -> HttpRequest - func buildLiveStreamUnsubscribe(streamName: String, userId: String, deviceId: String) -> HttpRequest + func buildUserSignUp(username: String?, password: String?, user: User?, options: Options?) -> HttpRequest + func buildUserDelete(userId: String, hard: Bool, options: Options?) -> HttpRequest + + func buildUserSocialLogin(_ authSource: AuthSource, authData: [String : Any], options: Options?) -> HttpRequest + func buildUserSocialCreate(_ authSource: AuthSource, authData: [String : Any], options: Options?) -> HttpRequest + + func buildUserLogin(username: String, password: String, options: Options?) -> HttpRequest + func buildUserExists(username: String, options: Options?) -> HttpRequest + func buildUserGet(userId: String, options: Options?) -> HttpRequest + func buildUserFind(query: Query, options: Options?) -> HttpRequest + func buildUserSave(user: User, newPassword: String?, options: Options?) -> HttpRequest + func buildUserLookup(user: User, userQuery: UserQuery, options: Options?) -> HttpRequest + func buildSendEmailConfirmation(forUsername: String, options: Options?) -> HttpRequest + func buildUserResetPassword(usernameOrEmail: String, options: Options?) -> HttpRequest + func buildUserForgotUsername(email: String, options: Options?) -> HttpRequest + func buildUserMe(options: Options?) -> HttpRequest + + func buildUserRegisterRealtime(user: User, deviceId: String, options: Options?) -> HttpRequest + func buildUserUnregisterRealtime(user: User, deviceId: String, options: Options?) -> HttpRequest + + func buildAppDataPing(options: Options?) -> HttpRequest + func buildAppDataGetById(collectionName: String, id: String, options: Options?) -> HttpRequest + func buildAppDataFindByQuery(collectionName: String, query: Query, options: Options?) -> HttpRequest + func buildAppDataCountByQuery(collectionName: String, query: Query?, options: Options?) -> HttpRequest + func buildAppDataGroup(collectionName: String, keys: [String], initialObject: [String : Any], reduceJSFunction: String, condition: NSPredicate?, options: Options?) -> HttpRequest + func buildAppDataSave(_ persistable: T, options: Options?) -> HttpRequest + func buildAppDataRemoveByQuery(collectionName: String, query: Query, options: Options?) -> HttpRequest + func buildAppDataRemoveById(collectionName: String, objectId: String, options: Options?) -> HttpRequest + func buildAppDataSubscribe(collectionName: String, deviceId: String, options: Options?) -> HttpRequest + func buildAppDataUnSubscribe(collectionName: String, deviceId: String, options: Options?) -> HttpRequest + + func buildPushRegisterDevice(_ deviceToken: Data, options: Options?) -> HttpRequest + func buildPushUnRegisterDevice(_ deviceToken: Data, options: Options?) -> HttpRequest + + func buildBlobUploadFile(_ file: File, options: Options?) -> HttpRequest + func buildBlobDownloadFile(_ file: File, options: Options?) -> HttpRequest + func buildBlobDeleteFile(_ file: File, options: Options?) -> HttpRequest + func buildBlobQueryFile(_ query: Query, options: Options?) -> HttpRequest + + func buildCustomEndpoint(_ name: String, options: Options?) -> HttpRequest + + func buildOAuthToken(redirectURI: URL, code: String, options: Options?) -> HttpRequest + + func buildOAuthGrantAuth(redirectURI: URL, options: Options?) -> HttpRequest + func buildOAuthGrantAuthenticate(redirectURI: URL, tempLoginUri: URL, username: String, password: String, options: Options?) -> HttpRequest + func buildOAuthGrantRefreshToken(refreshToken: String, options: Options?) -> HttpRequest + + func buildLiveStreamGrantAccess(streamName: String, userId: String, acl: LiveStreamAcl, options: Options?) -> HttpRequest + func buildLiveStreamPublish(streamName: String, userId: String, options: Options?) -> HttpRequest + func buildLiveStreamSubscribe(streamName: String, userId: String, deviceId: String, options: Options?) -> HttpRequest + func buildLiveStreamUnsubscribe(streamName: String, userId: String, deviceId: String, options: Options?) -> HttpRequest + +} + +//Allow to set a per request +public struct Options { + + public var client: Client? + public var clientId: String? + public var ttl: TTL? + public var deltaSet: Bool? + public var readPolicy: ReadPolicy? + public var writePolicy: WritePolicy? + public var timeout: TimeInterval? + + /// App version for this client instance. + public var clientAppVersion: String? + + /// Custom request properties for this client instance. + public var customRequestProperties: [String : Any]? + + public init( + client: Client? = nil, + clientId: String? = nil, + ttl: TTL? = nil, + deltaSet: Bool? = nil, + readPolicy: ReadPolicy? = nil, + writePolicy: WritePolicy? = nil, + timeout: TimeInterval? = nil, + clientAppVersion: String? = nil, + customRequestProperties: [String : Any]? = nil + ) { + self.client = client + self.clientId = clientId + self.ttl = ttl + self.deltaSet = deltaSet + self.readPolicy = readPolicy + self.writePolicy = writePolicy + self.timeout = timeout + self.clientAppVersion = clientAppVersion + self.customRequestProperties = customRequestProperties + } } diff --git a/Kinvey/Kinvey/SaveOperation.swift b/Kinvey/Kinvey/SaveOperation.swift index bca523813..5a9dc4c55 100644 --- a/Kinvey/Kinvey/SaveOperation.swift +++ b/Kinvey/Kinvey/SaveOperation.swift @@ -12,20 +12,45 @@ internal class SaveOperation: WriteOperation, WriteOperati var persistable: T - init(persistable: inout T, writePolicy: WritePolicy, sync: AnySync? = nil, cache: AnyCache? = nil, client: Client) { + init( + persistable: inout T, + writePolicy: WritePolicy, + sync: AnySync? = nil, + cache: AnyCache? = nil, + options: Options? + ) { self.persistable = persistable - super.init(writePolicy: writePolicy, sync: sync, cache: cache, client: client) + super.init( + writePolicy: writePolicy, + sync: sync, + cache: cache, + options: options + ) } - init(persistable: T, writePolicy: WritePolicy, sync: AnySync? = nil, cache: AnyCache? = nil, client: Client) { + init( + persistable: T, + writePolicy: WritePolicy, + sync: AnySync? = nil, + cache: AnyCache? = nil, + options: Options? + ) { self.persistable = persistable - super.init(writePolicy: writePolicy, sync: sync, cache: cache, client: client) + super.init( + writePolicy: writePolicy, + sync: sync, + cache: cache, + options: options + ) } func executeLocal(_ completionHandler: CompletionHandler?) -> Request { let request = LocalRequest() request.execute { () -> Void in - let request = self.client.networkRequestFactory.buildAppDataSave(self.persistable) + let request = self.client.networkRequestFactory.buildAppDataSave( + self.persistable, + options: options + ) let persistable = self.fillObject(&self.persistable) if let cache = self.cache { @@ -41,17 +66,27 @@ internal class SaveOperation: WriteOperation, WriteOperati } func executeNetwork(_ completionHandler: CompletionHandler?) -> Request { - let request = client.networkRequestFactory.buildAppDataSave(persistable) + let request = client.networkRequestFactory.buildAppDataSave( + persistable, + options: options + ) if checkRequirements(completionHandler) { request.execute() { data, response, error in if let response = response, response.isOK { let json = self.client.responseParser.parse(data) if let json = json { let persistable = T(JSON: json) - if let objectId = self.persistable.entityId, let sync = self.sync { - sync.removeAllPendingOperations(objectId, methods: ["POST", "PUT"]) + if let objectId = self.persistable.entityId, + let sync = self.sync + { + sync.removeAllPendingOperations( + objectId, + methods: ["POST", "PUT"] + ) } - if let persistable = persistable, let cache = self.cache { + if let persistable = persistable, + let cache = self.cache + { cache.remove(entity: self.persistable) cache.save(entity: persistable) } diff --git a/Kinvey/Kinvey/SyncOperation.swift b/Kinvey/Kinvey/SyncOperation.swift index c3dedc3c0..9d2ec5bd9 100644 --- a/Kinvey/Kinvey/SyncOperation.swift +++ b/Kinvey/Kinvey/SyncOperation.swift @@ -14,9 +14,16 @@ internal class SyncOperation: Operation where T: NSObje let sync: AnySync? - internal init(sync: AnySync?, cache: AnyCache?, client: Client) { + internal init( + sync: AnySync?, + cache: AnyCache?, + options: Options? + ) { self.sync = sync - super.init(cache: cache, client: client) + super.init( + cache: cache, + options: options + ) } } diff --git a/Kinvey/Kinvey/User.swift b/Kinvey/Kinvey/User.swift index feca6db64..a8665fd9a 100644 --- a/Kinvey/Kinvey/User.swift +++ b/Kinvey/Kinvey/User.swift @@ -53,7 +53,13 @@ open class User: NSObject, Credential, Mappable { /// Creates a new `User` taking (optionally) a username and password. If no `username` or `password` was provided, random values will be generated automatically. @discardableResult - open class func signup(username: String? = nil, password: String? = nil, user: U? = nil, client: Client = Kinvey.sharedClient, completionHandler: UserHandler? = nil) -> Request { + open class func signup( + username: String? = nil, + password: String? = nil, + user: U? = nil, + client: Client = Kinvey.sharedClient, + completionHandler: UserHandler? = nil + ) -> Request { return signup( username: username, password: password, @@ -71,7 +77,34 @@ open class User: NSObject, Credential, Mappable { /// Creates a new `User` taking (optionally) a username and password. If no `username` or `password` was provided, random values will be generated automatically. @discardableResult - open class func signup(username: String? = nil, password: String? = nil, user: U? = nil, client: Client = Kinvey.sharedClient, completionHandler: ((Result) -> Void)? = nil) -> Request { + open class func signup( + username: String? = nil, + password: String? = nil, + user: U? = nil, + client: Client = Kinvey.sharedClient, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return signup( + username: username, + password: password, + user: user, + options: Options( + client: client + ), + completionHandler: completionHandler + ) + } + + /// Creates a new `User` taking (optionally) a username and password. If no `username` or `password` was provided, random values will be generated automatically. + @discardableResult + open class func signup( + username: String? = nil, + password: String? = nil, + user: U? = nil, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let client = options?.client ?? sharedClient if let error = client.validate() { DispatchQueue.main.async { completionHandler?(.failure(error)) @@ -79,10 +112,18 @@ open class User: NSObject, Credential, Mappable { return LocalRequest() } - let request = client.networkRequestFactory.buildUserSignUp(username: username, password: password, user: user) + let request = client.networkRequestFactory.buildUserSignUp( + username: username, + password: password, + user: user, + options: options + ) Promise { fulfill, reject in request.execute() { (data, response, error) in - if let response = response, response.isOK, let user = client.responseParser.parseUser(data) as? U { + if let response = response, + response.isOK, + let user = client.responseParser.parseUser(data) as? U + { client.activeUser = user fulfill(user) } else { @@ -97,14 +138,45 @@ open class User: NSObject, Credential, Mappable { return request } + /// Deletes a `User` by the `userId` property. + @available(*, deprecated: 3.6.0, message: "Please use destroy(userId:hard:options:completionHandler:) instead") + @discardableResult + open class func destroy( + userId: String, + hard: Bool = true, + client: Client = Kinvey.sharedClient, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return destroy( + userId: userId, + hard: hard, + options: Options( + client: client + ), + completionHandler: completionHandler + ) + } + /// Deletes a `User` by the `userId` property. @discardableResult - open class func destroy(userId: String, hard: Bool = true, client: Client = Kinvey.sharedClient, completionHandler: ((Result) -> Void)? = nil) -> Request { - let request = client.networkRequestFactory.buildUserDelete(userId: userId, hard: hard) + open class func destroy( + userId: String, + hard: Bool = true, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let client = options?.client ?? sharedClient + let request = client.networkRequestFactory.buildUserDelete( + userId: userId, + hard: hard, + options: options + ) Promise { fulfill, reject in request.execute() { (data, response, error) in if let response = response, response.isOK { - if let activeUser = client.activeUser, activeUser.userId == userId { + if let activeUser = client.activeUser, + activeUser.userId == userId + { client.activeUser = nil } fulfill() @@ -122,8 +194,17 @@ open class User: NSObject, Credential, Mappable { /// Deletes the `User`. @discardableResult - open func destroy(hard: Bool = true, completionHandler: ((Result) -> Void)? = nil) -> Request { - return User.destroy(userId: userId, hard: hard, client: client, completionHandler: completionHandler) + open func destroy( + hard: Bool = true, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return User.destroy( + userId: userId, + hard: hard, + options: options, + completionHandler: completionHandler + ) } /** @@ -134,11 +215,19 @@ open class User: NSObject, Credential, Mappable { - parameter completionHandler: Completion handler to be called once the response returns from the server */ @discardableResult - open class func login(authSource: AuthSource, _ authData: [String : Any], createIfNotExists: Bool = true, clientId: String? = nil, client: Client = Kinvey.sharedClient, completionHandler: UserHandler? = nil) -> Request { + open class func login( + authSource: AuthSource, + _ authData: [String : Any], + createIfNotExists: Bool = true, + clientId: String? = nil, + client: Client = sharedClient, + completionHandler: UserHandler? = nil + ) -> Request { return login( authSource: authSource, authData, createIfNotExists: createIfNotExists, + clientId: clientId, client: client ) { (result: Result) in switch result { @@ -158,7 +247,42 @@ open class User: NSObject, Credential, Mappable { - parameter completionHandler: Completion handler to be called once the response returns from the server */ @discardableResult - open class func login(authSource: AuthSource, _ authData: [String : Any], createIfNotExists: Bool = true, clientId: String? = nil, client: Client = Kinvey.sharedClient, completionHandler: ((Result) -> Void)? = nil) -> Request { + open class func login( + authSource: AuthSource, + _ authData: [String : Any], + createIfNotExists: Bool = true, + clientId: String? = nil, + client: Client = sharedClient, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return login( + authSource: authSource, + authData, + createIfNotExists: createIfNotExists, + options: Options( + client: client, + clientId: clientId + ), + completionHandler: completionHandler + ) + } + + /** + Sign in a user with a social identity. + - parameter authSource: Authentication source enum + - parameter authData: Authentication data from the social provider + - parameter client: Define the `Client` to be used for all the requests for the `DataStore` that will be returned. Default value: `Kinvey.sharedClient` + - parameter completionHandler: Completion handler to be called once the response returns from the server + */ + @discardableResult + open class func login( + authSource: AuthSource, + _ authData: [String : Any], + createIfNotExists: Bool = true, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let client = options?.client ?? sharedClient if let error = client.validate() { DispatchQueue.main.async { completionHandler?(.failure(error)) @@ -168,24 +292,37 @@ open class User: NSObject, Credential, Mappable { let requests = MultiRequest() Promise { fulfill, reject in - let request = client.networkRequestFactory.buildUserSocialLogin(authSource, authData: authData) + let request = client.networkRequestFactory.buildUserSocialLogin( + authSource, + authData: authData, + options: options + ) request.execute() { (data, response, error) in - if let response = response { - if response.isOK, let user = client.responseParser.parseUser(data) as? U { - fulfill(user) - } else if response.isNotFound, createIfNotExists { - let request = client.networkRequestFactory.buildUserSocialCreate(authSource, authData: authData) - request.execute { (data, response, error) in - if let response = response, response.isOK, let user = client.responseParser.parseUser(data) as? U { - fulfill(user) - } else { - reject(buildError(data, response, error, client)) - } + if let response = response, + response.isOK, + let user = client.responseParser.parseUser(data) as? U + { + fulfill(user) + } else if let response = response, + response.isNotFound, + createIfNotExists + { + let request = client.networkRequestFactory.buildUserSocialCreate( + authSource, + authData: authData, + options: options + ) + request.execute { (data, response, error) in + if let response = response, + response.isOK, + let user = client.responseParser.parseUser(data) as? U + { + fulfill(user) + } else { + reject(buildError(data, response, error, client)) } - requests += request - } else { - reject(buildError(data, response, error, client)) } + requests += request } else { reject(buildError(data, response, error, client)) } @@ -193,7 +330,7 @@ open class User: NSObject, Credential, Mappable { requests += request }.then { user -> Void in client.activeUser = user - client.clientId = clientId + client.clientId = options?.clientId completionHandler?(.success(user)) }.catch { error in completionHandler?(.failure(error)) @@ -203,7 +340,12 @@ open class User: NSObject, Credential, Mappable { /// Sign in a user and set as a current active user. @discardableResult - open class func login(username: String, password: String, client: Client = Kinvey.sharedClient, completionHandler: UserHandler? = nil) -> Request { + open class func login( + username: String, + password: String, + client: Client = sharedClient, + completionHandler: UserHandler? = nil + ) -> Request { return login( username: username, password: password, @@ -218,9 +360,34 @@ open class User: NSObject, Credential, Mappable { } } + /// Sign in a user and set as a current active user. + @available(*, deprecated: 3.6.0, message: "Please use login(username:password:options:completionHandler:) instead") + @discardableResult + open class func login( + username: String, + password: String, + client: Client = sharedClient, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return login( + username: username, + password: password, + options: Options( + client: client + ), + completionHandler: completionHandler + ) + } + /// Sign in a user and set as a current active user. @discardableResult - open class func login(username: String, password: String, client: Client = Kinvey.sharedClient, completionHandler: ((Result) -> Void)? = nil) -> Request { + open class func login( + username: String, + password: String, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let client = options?.client ?? sharedClient if let error = client.validate() { DispatchQueue.main.async { completionHandler?(.failure(error)) @@ -228,10 +395,17 @@ open class User: NSObject, Credential, Mappable { return LocalRequest() } - let request = client.networkRequestFactory.buildUserLogin(username: username, password: password) + let request = client.networkRequestFactory.buildUserLogin( + username: username, + password: password, + options: options + ) Promise { fulfill, reject in request.execute() { (data, response, error) in - if let response = response, response.isOK, let user = client.responseParser.parseUser(data) as? U { + if let response = response, + response.isOK, + let user = client.responseParser.parseUser(data) as? U + { client.activeUser = user fulfill(user) } else { @@ -256,8 +430,40 @@ open class User: NSObject, Credential, Mappable { - parameter completionHandler: Completion handler to be called once the response returns from the server */ @discardableResult - open class func sendEmailConfirmation(forUsername username: String, client: Client = Kinvey.sharedClient, completionHandler: ((Result) -> Void)? = nil) -> Request { - let request = client.networkRequestFactory.buildSendEmailConfirmation(forUsername: username) + open class func sendEmailConfirmation( + forUsername username: String, + client: Client = sharedClient, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return sendEmailConfirmation( + forUsername: username, + options: Options( + client: client + ), + completionHandler: completionHandler + ) + } + + /** + Sends a request to confirm email address to the specified user. + + The user must have a valid email set in its `email` field, on the server, for this to work. The user will receive an email with a time-bound link to a verification web page. + + - parameter username: Username of the user that needs to send the email confirmation + - parameter client: define the `Client` to be used for all the requests for the `DataStore` that will be returned. Default value: `Kinvey.sharedClient` + - parameter completionHandler: Completion handler to be called once the response returns from the server + */ + @discardableResult + open class func sendEmailConfirmation( + forUsername username: String, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let client = options?.client ?? sharedClient + let request = client.networkRequestFactory.buildSendEmailConfirmation( + forUsername: username, + options: options + ) Promise { fulfill, reject in request.execute() { (data, response, error) in if let response = response, response.isOK { @@ -283,7 +489,10 @@ open class User: NSObject, Credential, Mappable { - parameter completionHandler: Completion handler to be called once the response returns from the server */ @discardableResult - open func sendEmailConfirmation(completionHandler: ((Result) -> Void)? = nil) -> Request { + open func sendEmailConfirmation( + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { guard let _ = email else { DispatchQueue.main.async { completionHandler?(.failure(Error.invalidOperation(description: "Email is required to send the email confirmation"))) @@ -291,12 +500,20 @@ open class User: NSObject, Credential, Mappable { return LocalRequest() } - return User.sendEmailConfirmation(forUsername: username!, client: client, completionHandler: completionHandler) + return User.sendEmailConfirmation( + forUsername: username!, + options: options, + completionHandler: completionHandler + ) } /// Sends an email to the user with a link to reset the password @discardableResult - private class func resetPassword(usernameOrEmail: String, client: Client = Kinvey.sharedClient, completionHandler: VoidHandler? = nil) -> Request { + private class func resetPassword( + usernameOrEmail: String, + client: Client = sharedClient, + completionHandler: VoidHandler? = nil + ) -> Request { return resetPassword( usernameOrEmail: usernameOrEmail, client: client @@ -312,8 +529,32 @@ open class User: NSObject, Credential, Mappable { /// Sends an email to the user with a link to reset the password @discardableResult - open class func resetPassword(usernameOrEmail: String, client: Client = Kinvey.sharedClient, completionHandler: ((Result) -> Void)? = nil) -> Request { - let request = client.networkRequestFactory.buildUserResetPassword(usernameOrEmail: usernameOrEmail) + open class func resetPassword( + usernameOrEmail: String, + client: Client = sharedClient, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return resetPassword( + usernameOrEmail: usernameOrEmail, + options: Options( + client: client + ), + completionHandler: completionHandler + ) + } + + /// Sends an email to the user with a link to reset the password + @discardableResult + open class func resetPassword( + usernameOrEmail: String, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let client = options?.client ?? sharedClient + let request = client.networkRequestFactory.buildUserResetPassword( + usernameOrEmail: usernameOrEmail, + options: options + ) Promise { fulfill, reject in request.execute() { (data, response, error) in if let response = response, response.isOK { @@ -333,24 +574,51 @@ open class User: NSObject, Credential, Mappable { /// Sends an email to the user with a link to reset the password using the `username` property. @discardableResult @available(*, deprecated: 3.3.4, message: "Use resetPassword(usernameOrEmail:) instead") - open class func resetPassword(username: String, client: Client = Kinvey.sharedClient, completionHandler: VoidHandler? = nil) -> Request { - return resetPassword(usernameOrEmail: username, client: client, completionHandler: completionHandler) + open class func resetPassword( + username: String, + client: Client = sharedClient, + completionHandler: VoidHandler? = nil + ) -> Request { + return resetPassword( + usernameOrEmail: username, + client: client, + completionHandler: completionHandler + ) } /// Sends an email to the user with a link to reset the password using the `email` property. @discardableResult @available(*, deprecated: 3.3.4, message: "Use resetPassword(usernameOrEmail:) instead") - open class func resetPassword(email: String, client: Client = Kinvey.sharedClient, completionHandler: VoidHandler? = nil) -> Request { - return resetPassword(usernameOrEmail: email, client: client, completionHandler: completionHandler) + open class func resetPassword( + email: String, + client: Client = sharedClient, + completionHandler: VoidHandler? = nil + ) -> Request { + return resetPassword( + usernameOrEmail: email, + client: client, + completionHandler: completionHandler + ) } /// Sends an email to the user with a link to reset the password. @discardableResult - open func resetPassword(completionHandler: ((Result) -> Void)? = nil) -> Request { + open func resetPassword( + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { if let email = email { - return User.resetPassword(usernameOrEmail: email, client: client, completionHandler: completionHandler) + return User.resetPassword( + usernameOrEmail: email, + options: options, + completionHandler: completionHandler + ) } else if let username = username { - return User.resetPassword(usernameOrEmail: username, client: client, completionHandler: completionHandler) + return User.resetPassword( + usernameOrEmail: username, + options: options, + completionHandler: completionHandler + ) } else if let completionHandler = completionHandler { DispatchQueue.main.async(execute: { () -> Void in completionHandler(.failure(Error.userWithoutEmailOrUsername)) @@ -366,9 +634,13 @@ open class User: NSObject, Credential, Mappable { - parameter completionHandler: Completion handler to be called once the response returns from the server */ @discardableResult - open func changePassword(newPassword: String, completionHandler: UserHandler? = nil) -> Request { + open func changePassword( + newPassword: String, + completionHandler: UserHandler? = nil + ) -> Request { return changePassword( - newPassword: newPassword + newPassword: newPassword, + options: nil ) { (result: Result) in switch result { case .success(let user): @@ -386,8 +658,34 @@ open class User: NSObject, Credential, Mappable { - parameter completionHandler: Completion handler to be called once the response returns from the server */ @discardableResult - open func changePassword(newPassword: String, completionHandler: ((Result) -> Void)? = nil) -> Request { - return save(newPassword: newPassword, completionHandler: completionHandler) + open func changePassword( + newPassword: String, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return changePassword( + newPassword: newPassword, + options: nil, + completionHandler: completionHandler + ) + } + + /** + Changes the password for the current user and automatically updates the session with a new valid session. + - parameter newPassword: A new password for the user + - parameter client: Define the `Client` to be used for all the requests for the `DataStore` that will be returned. Default value: `Kinvey.sharedClient` + - parameter completionHandler: Completion handler to be called once the response returns from the server + */ + @discardableResult + open func changePassword( + newPassword: String, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return save( + newPassword: newPassword, + options: options, + completionHandler: completionHandler + ) } /** @@ -397,8 +695,37 @@ open class User: NSObject, Credential, Mappable { - parameter completionHandler: Completion handler to be called once the response returns from the server */ @discardableResult - open class func forgotUsername(email: String, client: Client = Kinvey.sharedClient, completionHandler: ((Result) -> Void)? = nil) -> Request { - let request = client.networkRequestFactory.buildUserForgotUsername(email: email) + open class func forgotUsername( + email: String, + client: Client = sharedClient, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return forgotUsername( + email: email, + options: Options( + client: client + ), + completionHandler: completionHandler + ) + } + + /** + Sends an email with the username associated with the email provided. + - parameter email: Email associated with the user + - parameter client: Define the `Client` to be used for all the requests for the `DataStore` that will be returned. Default value: `Kinvey.sharedClient` + - parameter completionHandler: Completion handler to be called once the response returns from the server + */ + @discardableResult + open class func forgotUsername( + email: String, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let client = options?.client ?? sharedClient + let request = client.networkRequestFactory.buildUserForgotUsername( + email: email, + options: options + ) Promise { fulfill, reject in request.execute() { (data, response, error) in if let response = response, response.isOK { @@ -417,7 +744,11 @@ open class User: NSObject, Credential, Mappable { /// Checks if a `username` already exists or not. @discardableResult - open class func exists(username: String, client: Client = Kinvey.sharedClient, completionHandler: BoolHandler? = nil) -> Request { + open class func exists( + username: String, + client: Client = sharedClient, + completionHandler: BoolHandler? = nil + ) -> Request { return exists( username: username, client: client @@ -433,11 +764,39 @@ open class User: NSObject, Credential, Mappable { /// Checks if a `username` already exists or not. @discardableResult - open class func exists(username: String, client: Client = Kinvey.sharedClient, completionHandler: ((Result) -> Void)? = nil) -> Request { - let request = client.networkRequestFactory.buildUserExists(username: username) + open class func exists( + username: String, + client: Client = sharedClient, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return exists( + username: username, + options: Options( + client: client + ), + completionHandler: completionHandler + ) + } + + /// Checks if a `username` already exists or not. + @discardableResult + open class func exists( + username: String, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let client = options?.client ?? sharedClient + let request = client.networkRequestFactory.buildUserExists( + username: username, + options: options + ) Promise { fulfill, reject in request.execute() { (data, response, error) in - if let response = response, response.isOK, let json = client.responseParser.parse(data), let usernameExists = json["usernameExists"] as? Bool { + if let response = response, + response.isOK, + let json = client.responseParser.parse(data), + let usernameExists = json["usernameExists"] as? Bool + { fulfill(usernameExists) } else { reject(buildError(data, response, error, client)) @@ -453,7 +812,11 @@ open class User: NSObject, Credential, Mappable { /// Gets a `User` instance using the `userId` property. @discardableResult - open class func get(userId: String, client: Client = Kinvey.sharedClient, completionHandler: UserHandler? = nil) -> Request { + open class func get( + userId: String, + client: Client = sharedClient, + completionHandler: UserHandler? = nil + ) -> Request { return get( userId: userId, client: client @@ -469,11 +832,38 @@ open class User: NSObject, Credential, Mappable { /// Gets a `User` instance using the `userId` property. @discardableResult - open class func get(userId: String, client: Client = Kinvey.sharedClient, completionHandler: ((Result) -> Void)? = nil) -> Request { - let request = client.networkRequestFactory.buildUserGet(userId: userId) + open class func get( + userId: String, + client: Client = sharedClient, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + return get( + userId: userId, + options: Options( + client: client + ), + completionHandler: completionHandler + ) + } + + /// Gets a `User` instance using the `userId` property. + @discardableResult + open class func get( + userId: String, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let client = options?.client ?? sharedClient + let request = client.networkRequestFactory.buildUserGet( + userId: userId, + options: options + ) Promise { fulfill, reject in request.execute() { (data, response, error) in - if let response = response, response.isOK, let user = client.responseParser.parseUser(data) as? U { + if let response = response, + response.isOK, + let user = client.responseParser.parseUser(data) as? U + { fulfill(user) } else { reject(buildError(data, response, error, client)) @@ -488,12 +878,40 @@ open class User: NSObject, Credential, Mappable { } /// Gets a `User` instance using the `userId` property. + @available(*, deprecated: 3.6.0, message: "Please use find(query:options:completionHandler:) instead") @discardableResult - open func find(query: Query = Query(), client: Client = sharedClient, completionHandler: ((Result<[U], Swift.Error>) -> Void)? = nil) -> Request { - let request = client.networkRequestFactory.buildUserFind(query: query) + open func find( + query: Query = Query(), + client: Client = sharedClient, + completionHandler: ((Result<[U], Swift.Error>) -> Void)? = nil + ) -> Request { + return find( + query: query, + options: Options( + client: client + ), + completionHandler: completionHandler + ) + } + + /// Gets a `User` instance using the `userId` property. + @discardableResult + open func find( + query: Query = Query(), + options: Options? = nil, + completionHandler: ((Result<[U], Swift.Error>) -> Void)? = nil + ) -> Request { + let client = options?.client ?? self.client + let request = client.networkRequestFactory.buildUserFind( + query: query, + options: options + ) Promise<[U]> { fulfill, reject in request.execute() { (data, response, error) in - if let response = response, response.isOK, let user = client.responseParser.parseUsers(data) as? [U] { + if let response = response, + response.isOK, + let user = client.responseParser.parseUsers(data) as? [U] + { fulfill(user) } else { reject(buildError(data, response, error, client)) @@ -509,11 +927,18 @@ open class User: NSObject, Credential, Mappable { /// Refresh the user's data. @discardableResult - open func refresh(completionHandler: ((Result) -> Void)? = nil) -> Request { - let request = client.networkRequestFactory.buildUserMe() + open func refresh( + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let client = options?.client ?? self.client + let request = client.networkRequestFactory.buildUserMe(options: options) Promise { fulfill, reject in request.execute() { (data, response, error) in - if let response = response, response.isOK, let json = self.client.responseParser.parse(data) { + if let response = response, + response.isOK, + let json = self.client.responseParser.parse(data) + { let map = Map(mappingType: .fromJSON, JSON: json) self.mapping(map: map) if self == self.client.activeUser { @@ -533,7 +958,12 @@ open class User: NSObject, Credential, Mappable { } /// Default Constructor. - public init(userId: String? = nil, acl: Acl? = nil, metadata: UserMetadata? = nil, client: Client = Kinvey.sharedClient) { + public init( + userId: String? = nil, + acl: Acl? = nil, + metadata: UserMetadata? = nil, + client: Client = sharedClient + ) { self._userId = userId self.acl = acl self.metadata = metadata @@ -575,7 +1005,10 @@ open class User: NSObject, Credential, Mappable { /// Creates or updates a `User`. @discardableResult - open func save(newPassword: String? = nil, completionHandler: UserHandler? = nil) -> Request { + open func save( + newPassword: String? = nil, + completionHandler: UserHandler? = nil + ) -> Request { return save( newPassword: newPassword ) { (result: Result) in @@ -590,12 +1023,24 @@ open class User: NSObject, Credential, Mappable { /// Creates or updates a `User`. @discardableResult - open func save(newPassword: String? = nil, completionHandler: ((Result) -> Void)? = nil) -> Request { - let request = client.networkRequestFactory.buildUserSave(user: self, newPassword: newPassword) + open func save( + newPassword: String? = nil, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let client = options?.client ?? sharedClient + let request = client.networkRequestFactory.buildUserSave( + user: self, + newPassword: newPassword, + options: options + ) Promise { fulfill, reject in request.execute() { (data, response, error) in - if let response = response, response.isOK, let user = self.client.responseParser.parseUser(data) as? U { - if user.userId == self.client.activeUser?.userId { + if let response = response, + response.isOK, + let user = client.responseParser.parseUser(data) as? U + { + if user.userId == client.activeUser?.userId { self.client.activeUser = user } fulfill(user) @@ -615,7 +1060,10 @@ open class User: NSObject, Credential, Mappable { This method allows users to do exact queries for other users restricted to the `UserQuery` attributes. */ @discardableResult - open func lookup(_ userQuery: UserQuery, completionHandler: UsersHandler? = nil) -> Request { + open func lookup( + _ userQuery: UserQuery, + completionHandler: UsersHandler? = nil + ) -> Request { return lookup(userQuery) { (result: Result<[U], Swift.Error>) in switch result { case .success(let users): @@ -630,11 +1078,23 @@ open class User: NSObject, Credential, Mappable { This method allows users to do exact queries for other users restricted to the `UserQuery` attributes. */ @discardableResult - open func lookup(_ userQuery: UserQuery, completionHandler: ((Result<[U], Swift.Error>) -> Void)? = nil) -> Request { - let request = client.networkRequestFactory.buildUserLookup(user: self, userQuery: userQuery) + open func lookup( + _ userQuery: UserQuery, + options: Options? = nil, + completionHandler: ((Result<[U], Swift.Error>) -> Void)? = nil + ) -> Request { + let client = options?.client ?? sharedClient + let request = client.networkRequestFactory.buildUserLookup( + user: self, + userQuery: userQuery, + options: options + ) Promise<[U]> { fulfill, reject in request.execute() { (data, response, error) in - if let response = response, response.isOK, let users: [U] = self.client.responseParser.parseUsers(data) { + if let response = response, + response.isOK, + let users: [U] = client.responseParser.parseUsers(data) + { fulfill(users) } else { reject(buildError(data, response, error, self.client)) @@ -650,8 +1110,15 @@ open class User: NSObject, Credential, Mappable { /// Register the user to start performing realtime / live calls @discardableResult - open func registerForRealtime(completionHandler: ((Result) -> Void)? = nil) -> Request { - let request = client.networkRequestFactory.buildUserRegisterRealtime(user: self, deviceId: deviceId) + open func registerForRealtime( + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let request = client.networkRequestFactory.buildUserRegisterRealtime( + user: self, + deviceId: deviceId, + options: options + ) Promise { fulfill, reject in request.execute() { (data, response, error) in if let response = response, @@ -678,8 +1145,15 @@ open class User: NSObject, Credential, Mappable { /// Unregister the user to stop performing realtime / live calls @discardableResult - open func unregisterForRealtime(completionHandler: ((Result) -> Void)? = nil) -> Request { - let request = client.networkRequestFactory.buildUserUnregisterRealtime(user: self, deviceId: deviceId) + open func unregisterForRealtime( + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) -> Request { + let request = client.networkRequestFactory.buildUserUnregisterRealtime( + user: self, + deviceId: deviceId, + options: options + ) Promise { fulfill, reject in request.execute() { (data, response, error) in if let response = response, response.isOK { @@ -745,13 +1219,34 @@ open class User: NSObject, Credential, Mappable { clientId: String? = nil, client: Client = sharedClient, completionHandler: ((Result) -> Void)? = nil + ) { + return login( + redirectURI: redirectURI, + username: username, + password: password, + options: Options( + client: client, + clientId: clientId + ), + completionHandler: completionHandler + ) + } + + /** + Login with MIC using Automated Authorization Grant Flow. We strongly recommend use [Authorization Code Grant Flow](http://devcenter.kinvey.com/rest/guides/mobile-identity-connect#authorization-grant) instead of [Automated Authorization Grant Flow](http://devcenter.kinvey.com/rest/guides/mobile-identity-connect#automated-authorization-grant) for security reasons. + */ + open class func login( + redirectURI: URL, + username: String, + password: String, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil ) { MIC.login( redirectURI: redirectURI, username: username, password: password, - clientId: clientId, - client: client, + options: options, completionHandler: completionHandler ) } @@ -780,9 +1275,35 @@ open class User: NSObject, Credential, Mappable { } /// Performs a login using the MIC Redirect URL that contains a temporary token. - open class func login(redirectURI: URL, micURL: URL, clientId: String? = nil, client: Client = sharedClient) -> Bool { + @available(*, deprecated: 3.6.0, message: "Please use login(redirectURI:micURL:options:) instead") + open class func login( + redirectURI: URL, + micURL: URL, + clientId: String? = nil, + client: Client = sharedClient + ) -> Bool { + return login( + redirectURI: redirectURI, + micURL: micURL, + options: Options( + client: client, + clientId: clientId + ) + ) + } + + /// Performs a login using the MIC Redirect URL that contains a temporary token. + open class func login( + redirectURI: URL, + micURL: URL, + options: Options? = nil + ) -> Bool { if let code = MIC.parseCode(redirectURI: redirectURI, url: micURL) { - MIC.login(redirectURI: redirectURI, code: code, clientId: clientId, client: client) { result in + MIC.login( + redirectURI: redirectURI, + code: code, + options: options + ) { result in switch result { case .success(let user): NotificationCenter.default.post( @@ -858,6 +1379,28 @@ open class User: NSObject, Credential, Mappable { client: Client = sharedClient, completionHandler: ((Result) -> Void)? = nil ) { + presentMICViewController( + redirectURI: redirectURI, + micUserInterface: micUserInterface, + currentViewController: currentViewController, + options: Options( + client: client, + clientId: clientId, + timeout: timeout + ), + completionHandler: completionHandler + ) + } + + /// Presents the MIC View Controller to sign in a user using MIC (Mobile Identity Connect). + open class func presentMICViewController( + redirectURI: URL, + micUserInterface: MICUserInterface = .safari, + currentViewController: UIViewController? = nil, + options: Options? = nil, + completionHandler: ((Result) -> Void)? = nil + ) { + let client = options?.client ?? sharedClient if let error = client.validate() { DispatchQueue.main.async { completionHandler?(.failure(error)) @@ -870,7 +1413,10 @@ open class User: NSObject, Credential, Mappable { switch micUserInterface { case .safari: - let url = MIC.urlForLogin(redirectURI: redirectURI, clientId: clientId, client: client) + let url = MIC.urlForLogin( + redirectURI: redirectURI, + options: options + ) micVC = SFSafariViewController(url: url) micVC.modalPresentationStyle = .overCurrentContext MICSafariViewControllerSuccessNotificationObserver = NotificationCenter.default.addObserver( @@ -908,10 +1454,8 @@ open class User: NSObject, Credential, Mappable { let micLoginVC = MICLoginViewController( redirectURI: redirectURI, userType: client.userType, - timeout: timeout, forceUIWebView: forceUIWebView, - clientId: clientId, - client: client + options: options ) { (result) in switch result { case .success(let user): diff --git a/Kinvey/Kinvey/WriteOperation.swift b/Kinvey/Kinvey/WriteOperation.swift index dedddb77a..64f7385c6 100644 --- a/Kinvey/Kinvey/WriteOperation.swift +++ b/Kinvey/Kinvey/WriteOperation.swift @@ -15,10 +15,18 @@ internal class WriteOperation: Operation where T: NSObject let writePolicy: WritePolicy let sync: AnySync? - init(writePolicy: WritePolicy, sync: AnySync? = nil, cache: AnyCache? = nil, client: Client) { + init( + writePolicy: WritePolicy, + sync: AnySync? = nil, + cache: AnyCache? = nil, + options: Options? + ) { self.writePolicy = writePolicy self.sync = sync - super.init(cache: cache, client: client) + super.init( + cache: cache, + options: options + ) } } diff --git a/Kinvey/KinveyApp/AppDelegate.swift b/Kinvey/KinveyApp/AppDelegate.swift index ddcc440e2..22e3df34b 100644 --- a/Kinvey/KinveyApp/AppDelegate.swift +++ b/Kinvey/KinveyApp/AppDelegate.swift @@ -93,7 +93,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { - if User.login(redirectURI: redirectURI, micURL: url) { + if User.login( + redirectURI: redirectURI, + micURL: url, + options: nil + ) { return true } diff --git a/Kinvey/KinveyTests/DeltaSetCacheTestCase.swift b/Kinvey/KinveyTests/DeltaSetCacheTestCase.swift index 7868c477a..c7618b108 100644 --- a/Kinvey/KinveyTests/DeltaSetCacheTestCase.swift +++ b/Kinvey/KinveyTests/DeltaSetCacheTestCase.swift @@ -67,7 +67,12 @@ class DeltaSetCacheTestCase: KinveyTestCase { person.metadata = Metadata(JSON: [Metadata.LmtKey : date.toString()]) cache.save(entity: person) } - let operation = Operation(cache: AnyCache(cache), client: client) + let operation = Operation( + cache: AnyCache(cache), + options: Options( + client: client + ) + ) let query = Query() let refObjs: [JsonDictionary] = [ [ @@ -186,7 +191,13 @@ class DeltaSetCacheTestCase: KinveyTestCase { weak var expectationCreate = expectation(description: "Create") - let createOperation = SaveOperation(persistable: person, writePolicy: .forceNetwork, client: client) + let createOperation = SaveOperation( + persistable: person, + writePolicy: .forceNetwork, + options: Options( + client: client + ) + ) createOperation.execute { result in switch result { case .success: @@ -376,7 +387,13 @@ class DeltaSetCacheTestCase: KinveyTestCase { weak var expectationUpdate = expectation(description: "Update") - let updateOperation = SaveOperation(persistable: person, writePolicy: .forceNetwork, client: client) + let updateOperation = SaveOperation( + persistable: person, + writePolicy: .forceNetwork, + options: Options( + client: client + ) + ) updateOperation.execute { result in switch result { case .success: @@ -537,7 +554,13 @@ class DeltaSetCacheTestCase: KinveyTestCase { let query = Query(format: "personId == %@", personId) query.persistableType = Person.self - let createRemove = RemoveByQueryOperation(query: query, writePolicy: .forceNetwork, client: client) + let createRemove = RemoveByQueryOperation( + query: query, + writePolicy: .forceNetwork, + options: Options( + client: client + ) + ) createRemove.execute { result in switch result { case .success(let count): @@ -642,7 +665,13 @@ class DeltaSetCacheTestCase: KinveyTestCase { weak var expectationCreate = self.expectation(description: "Create") - let createOperation = SaveOperation(persistable: person, writePolicy: .forceNetwork, client: self.client) + let createOperation = SaveOperation( + persistable: person, + writePolicy: .forceNetwork, + options: Options( + client: self.client + ) + ) createOperation.execute { result in switch result { case .success: @@ -962,7 +991,13 @@ class DeltaSetCacheTestCase: KinveyTestCase { weak var expectationCreate = self.expectation(description: "Create") - let createOperation = SaveOperation(persistable: person, writePolicy: .forceNetwork, client: self.client) + let createOperation = SaveOperation( + persistable: person, + writePolicy: .forceNetwork, + options: Options( + client: self.client + ) + ) createOperation.execute { result in switch result { case .success: diff --git a/Kinvey/KinveyTests/NetworkStoreTests.swift b/Kinvey/KinveyTests/NetworkStoreTests.swift index 1962d9f4a..74671b198 100644 --- a/Kinvey/KinveyTests/NetworkStoreTests.swift +++ b/Kinvey/KinveyTests/NetworkStoreTests.swift @@ -1146,45 +1146,96 @@ class NetworkStoreTests: StoreTestCase { } func testClientAppVersion() { - class ClientAppVersionURLProtocol: URLProtocol { - - override class func canInit(with request: URLRequest) -> Bool { - return true - } - - override class func canonicalRequest(for request: URLRequest) -> URLRequest { - return request - } - - override func startLoading() { - XCTAssertEqual(request.allHTTPHeaderFields?["X-Kinvey-Client-App-Version"], "1.0.0") - - let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: "1.1", headerFields: ["Content-Type" : "application/json; charset=utf-8"])! - client!.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) - client!.urlProtocol(self, didLoad: "[]".data(using: .utf8)!) - client!.urlProtocolDidFinishLoading(self) + mockResponse { (request) -> HttpResponse in + XCTAssertEqual(request.allHTTPHeaderFields?["X-Kinvey-Client-App-Version"], "1.0.0") + return HttpResponse(json: []) + } + defer { + setURLProtocol(nil) + } + + weak var expectationFind = expectation(description: "Find") + + store.find( + options: Options( + readPolicy: .forceNetwork, + clientAppVersion: "1.0.0" + ) + ) { result in + switch result { + case .success(let results): + XCTAssertEqual(results.count, 0) + case .failure(let error): + XCTFail(error.localizedDescription) } - override func stopLoading() { + expectationFind?.fulfill() + } + + waitForExpectations(timeout: defaultTimeout) { error in + expectationFind = nil + } + } + + func testCustomRequestProperties() { + mockResponse { (request) -> HttpResponse in + XCTAssertEqual(request.allHTTPHeaderFields?["X-Kinvey-Custom-Request-Properties"], "{\"someKey\":\"someValue\"}") + return HttpResponse(json: []) + } + defer { + setURLProtocol(nil) + } + + weak var expectationFind = expectation(description: "Find") + + store.find( + options: Options( + readPolicy: .forceNetwork, + customRequestProperties: [ + "someKey" : "someValue" + ] + ) + ) { result in + switch result { + case .success(let results): + XCTAssertEqual(results.count, 0) + case .failure(let error): + XCTFail(error.localizedDescription) } + expectationFind?.fulfill() } - setURLProtocol(ClientAppVersionURLProtocol.self) - client.clientAppVersion = "1.0.0" + waitForExpectations(timeout: defaultTimeout) { error in + expectationFind = nil + } + } + + func testCustomRequestPropertiesPerRequest() { + mockResponse { (request) -> HttpResponse in + XCTAssertEqual(request.allHTTPHeaderFields?["X-Kinvey-Custom-Request-Properties"], "{\"someKeyPerRequest\":\"someValuePerRequest\"}") + return HttpResponse(json: []) + } defer { setURLProtocol(nil) - client.clientAppVersion = nil } weak var expectationFind = expectation(description: "Find") - store.find(readPolicy: .forceNetwork) { results, error in - XCTAssertNotNil(results) - XCTAssertNil(error) - - if let results = results { + let options = Options( + readPolicy: .forceNetwork, + customRequestProperties: [ + "someKeyPerRequest" : "someValuePerRequest" + ] + ) + store.find( + options: options + ) { result in + switch result { + case .success(let results): XCTAssertEqual(results.count, 0) + case .failure(let error): + XCTFail(error.localizedDescription) } expectationFind?.fulfill() diff --git a/Kinvey/KinveyTests/RealtimeTestCase.swift b/Kinvey/KinveyTests/RealtimeTestCase.swift index a5605c2d1..0f73392f3 100644 --- a/Kinvey/KinveyTests/RealtimeTestCase.swift +++ b/Kinvey/KinveyTests/RealtimeTestCase.swift @@ -357,7 +357,7 @@ class RealtimeTestCase: KinveyTestCase { } DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { - dataStoreNetwork.save(person) { (result: Result) in + dataStoreNetwork.save(person, writePolicy: nil) { (result: Result) in switch result { case .success: break @@ -904,7 +904,7 @@ class RealtimeTestCase: KinveyTestCase { let query = Query(format: "_id != %@", user.userId) query.limit = 2 - user.find(query: query) { + user.find(query: query, client: client) { switch $0 { case .success(let users): usersArray = users @@ -1166,7 +1166,7 @@ class RealtimeTestCase: KinveyTestCase { let query = Query(format: "_id != %@", user.userId) query.limit = 2 - user.find(query: query) { + user.find(query: query, client: client) { switch $0 { case .success(let users): usersArray = users diff --git a/Kinvey/KinveyTests/UserTests.swift b/Kinvey/KinveyTests/UserTests.swift index 9566f2ced..c085e9d5d 100644 --- a/Kinvey/KinveyTests/UserTests.swift +++ b/Kinvey/KinveyTests/UserTests.swift @@ -183,7 +183,7 @@ class UserTests: KinveyTestCase { if let user = client.activeUser { weak var expectationDestroyUser = expectation(description: "Destroy User") - User.destroy(userId: user.userId, completionHandler: { + User.destroy(userId: user.userId, client: client) { XCTAssertTrue(Thread.isMainThread) switch $0 { @@ -194,7 +194,7 @@ class UserTests: KinveyTestCase { } expectationDestroyUser?.fulfill() - }) + } waitForExpectations(timeout: defaultTimeout) { error in expectationDestroyUser = nil @@ -219,7 +219,7 @@ class UserTests: KinveyTestCase { weak var expectationDestroyUser = expectation(description: "Destroy User") - User.destroy(userId: user.userId, hard: true, completionHandler: { + User.destroy(userId: user.userId, hard: true, client: client) { XCTAssertTrue(Thread.isMainThread) switch $0 { @@ -230,7 +230,7 @@ class UserTests: KinveyTestCase { } expectationDestroyUser?.fulfill() - }) + } waitForExpectations(timeout: defaultTimeout) { error in expectationDestroyUser = nil @@ -255,7 +255,7 @@ class UserTests: KinveyTestCase { weak var expectationDestroyUser = expectation(description: "Destroy User") - User.destroy(userId: user.userId, hard: false, completionHandler: { + User.destroy(userId: user.userId, hard: false, client: client) { XCTAssertTrue(Thread.isMainThread) switch $0 { @@ -266,7 +266,7 @@ class UserTests: KinveyTestCase { } expectationDestroyUser?.fulfill() - }) + } waitForExpectations(timeout: defaultTimeout) { error in expectationDestroyUser = nil @@ -291,7 +291,7 @@ class UserTests: KinveyTestCase { weak var expectationDestroyUser = expectation(description: "Destroy User") - User.destroy(userId: user.userId, hard: true, completionHandler: { + User.destroy(userId: user.userId, hard: true, client: client) { XCTAssertTrue(Thread.isMainThread) switch $0 { @@ -302,7 +302,7 @@ class UserTests: KinveyTestCase { } expectationDestroyUser?.fulfill() - }) + } waitForExpectations(timeout: defaultTimeout) { error in expectationDestroyUser = nil @@ -687,7 +687,11 @@ class UserTests: KinveyTestCase { weak var expectationFind = expectation(description: "Find") - dataStore.find() { (result: Result<[Person], Swift.Error>) in + dataStore.find( + options: Options( + client: client + ) + ) { (result: Result<[Person], Swift.Error>) in switch result { case .success: break @@ -1966,7 +1970,7 @@ class UserTests: KinveyTestCase { weak var expectationForgotUsername = expectation(description: "Forgot Username") - User.forgotUsername(email: user.email!) { + User.forgotUsername(email: user.email!, client: client) { XCTAssertTrue(Thread.isMainThread) switch $0 { @@ -1991,7 +1995,7 @@ class UserTests: KinveyTestCase { weak var expectationForgotUsername = expectation(description: "Forgot Username") - User.forgotUsername(email: "\(UUID().uuidString)@kinvey.com") { + User.forgotUsername(email: "\(UUID().uuidString)@kinvey.com", client: client) { XCTAssertTrue(Thread.isMainThread) switch $0 { @@ -2714,7 +2718,13 @@ class UserTests: KinveyTestCase { weak var expectationLogin = expectation(description: "Login") - MIC.login(redirectURI: URL(string: "myCustomURIScheme://")!, code: "1234", clientId: nil) { result in + MIC.login( + redirectURI: URL(string: "myCustomURIScheme://")!, + code: "1234", + options: Options( + clientId: nil + ) + ) { result in XCTAssertTrue(Thread.isMainThread) switch result { @@ -2768,7 +2778,13 @@ class UserTests: KinveyTestCase { weak var expectationLogin = expectation(description: "Login") - MIC.login(redirectURI: URL(string: "myCustomURIScheme://")!, code: "1234", clientId: nil) { result in + MIC.login( + redirectURI: URL(string: "myCustomURIScheme://")!, + code: "1234", + options: Options( + clientId: nil + ) + ) { result in XCTAssertTrue(Thread.isMainThread) switch result { @@ -2796,7 +2812,14 @@ class UserTests: KinveyTestCase { weak var expectationLogin = expectation(description: "Login") - MIC.login(redirectURI: URL(string: "myCustomURIScheme://")!, username: UUID().uuidString, password: UUID().uuidString, clientId: nil) { result in + MIC.login( + redirectURI: URL(string: "myCustomURIScheme://")!, + username: UUID().uuidString, + password: UUID().uuidString, + options: Options( + clientId: nil + ) + ) { result in XCTAssertTrue(Thread.isMainThread) switch result { @@ -2866,7 +2889,14 @@ class UserTests: KinveyTestCase { weak var expectationLogin = expectation(description: "Login") - MIC.login(redirectURI: URL(string: "myCustomURIScheme://")!, username: UUID().uuidString, password: UUID().uuidString, clientId: nil) { result in + MIC.login( + redirectURI: URL(string: "myCustomURIScheme://")!, + username: UUID().uuidString, + password: UUID().uuidString, + options: Options( + clientId: nil + ) + ) { result in XCTAssertTrue(Thread.isMainThread) switch result { @@ -2919,7 +2949,14 @@ class UserTests: KinveyTestCase { weak var expectationLogin = expectation(description: "Login") - MIC.login(redirectURI: URL(string: "myCustomURIScheme://")!, username: UUID().uuidString, password: UUID().uuidString, clientId: nil) { result in + MIC.login( + redirectURI: URL(string: "myCustomURIScheme://")!, + username: UUID().uuidString, + password: UUID().uuidString, + options: Options( + clientId: nil + ) + ) { result in XCTAssertTrue(Thread.isMainThread) switch result { @@ -3664,7 +3701,11 @@ extension UserTests { return true } - let result = User.login(redirectURI: URL(string: "myCustomURIScheme://")!, micURL: URL(string: "myCustomURIScheme://?code=1234")!) + let result = User.login( + redirectURI: URL(string: "myCustomURIScheme://")!, + micURL: URL(string: "myCustomURIScheme://?code=1234")!, + client: client + ) XCTAssertTrue(result) waitForExpectations(timeout: defaultTimeout) { (error) in @@ -3672,7 +3713,11 @@ extension UserTests { } func testUserMICLoginWrongCode() { - let result = User.login(redirectURI: URL(string: "myCustomURIScheme://")!, micURL: URL(string: "myCustomURIScheme://?no_code=1234")!) + let result = User.login( + redirectURI: URL(string: "myCustomURIScheme://")!, + micURL: URL(string: "myCustomURIScheme://?no_code=1234")!, + client: client + ) XCTAssertFalse(result) } @@ -3728,7 +3773,11 @@ extension UserTests { return true } - let result = User.login(redirectURI: URL(string: "myCustomURIScheme://")!, micURL: URL(string: "myCustomURIScheme://?code=1234")!) + let result = User.login( + redirectURI: URL(string: "myCustomURIScheme://")!, + micURL: URL(string: "myCustomURIScheme://?code=1234")!, + client: client + ) XCTAssertTrue(result) waitForExpectations(timeout: defaultTimeout) { (error) in @@ -3753,7 +3802,11 @@ extension UserTests { weak var expectationLogin = expectation(description: "Login") let redirectURI = URL(string: "throwAnError://")! - User.presentMICViewController(redirectURI: redirectURI, timeout: 3, forceUIWebView: true) { (user, error) -> Void in + User.presentMICViewController( + redirectURI: redirectURI, + timeout: 3, + forceUIWebView: true + ) { (user, error) -> Void in XCTAssertTrue(Thread.isMainThread) XCTAssertNotNil(error) diff --git a/Kinvey/RealtimeSender/ViewController.swift b/Kinvey/RealtimeSender/ViewController.swift index 7ea03a5cf..b11fdd6dc 100644 --- a/Kinvey/RealtimeSender/ViewController.swift +++ b/Kinvey/RealtimeSender/ViewController.swift @@ -150,7 +150,11 @@ class ViewController: NSViewController { print("Requesting Login") - User.login(username: username!, password: password!) { (result: Result) in + User.login( + username: username!, + password: password!, + client: sharedClient + ) { (result: Result) in switch result { case .success(let user): print("Login Succeed") From ba3a80d8640ee03183b0181b94db84b51410b58e Mon Sep 17 00:00:00 2001 From: Victor Barros Date: Fri, 14 Jul 2017 21:49:18 -0700 Subject: [PATCH 10/11] requested changes during code review --- Kinvey/Kinvey/Client.swift | 3 +++ Kinvey/Kinvey/DataStore.swift | 32 ++++++++++++++++++++++++++++++ Kinvey/Kinvey/RequestFactory.swift | 19 +++++++++++++++++- 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/Kinvey/Kinvey/Client.swift b/Kinvey/Kinvey/Client.swift index 4e1ca6a13..34745bd59 100644 --- a/Kinvey/Kinvey/Client.swift +++ b/Kinvey/Kinvey/Client.swift @@ -92,6 +92,9 @@ open class Client: Credential { /// Timeout interval for this client instance. open var timeoutInterval: TimeInterval = 60 + /** + Hold default optional values for all calls made by this `Client` instance + */ open var options: Options? /// The default value for `apiHostName` variable. diff --git a/Kinvey/Kinvey/DataStore.swift b/Kinvey/Kinvey/DataStore.swift index d44bb2345..ecea34d6d 100644 --- a/Kinvey/Kinvey/DataStore.swift +++ b/Kinvey/Kinvey/DataStore.swift @@ -171,6 +171,7 @@ open class DataStore where T: NSObject { - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use find(_:options:completionHandler:)") @discardableResult open func find(byId id: String, readPolicy: ReadPolicy? = nil, completionHandler: @escaping ObjectCompletionHandler) -> Request { return find(byId: id, readPolicy: readPolicy) { (result: Result) in @@ -192,6 +193,7 @@ open class DataStore where T: NSObject { - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use find(_:options:completionHandler:)") @discardableResult open func find(byId id: String, readPolicy: ReadPolicy? = nil, completionHandler: @escaping (Result) -> Void) -> Request { return find(id, readPolicy: readPolicy, completionHandler: completionHandler) @@ -214,6 +216,7 @@ open class DataStore where T: NSObject { - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use find(_:options:completionHandler:)") @discardableResult open func find( _ id: String, @@ -244,6 +247,7 @@ open class DataStore where T: NSObject { - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use find(_:options:completionHandler:)") @discardableResult open func find( _ id: String, @@ -301,6 +305,7 @@ open class DataStore where T: NSObject { - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use find(_:options:completionHandler:)") @discardableResult open func find( _ query: Query = Query(), @@ -330,6 +335,7 @@ open class DataStore where T: NSObject { - parameter completionHandler: Completion handler to be called once the response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use find(_:options:completionHandler:)") @discardableResult open func find( _ query: Query = Query(), @@ -387,6 +393,7 @@ open class DataStore where T: NSObject { response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use find(_:options:completionHandler:)") @discardableResult open func count( _ query: Query? = nil, @@ -417,6 +424,7 @@ open class DataStore where T: NSObject { response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use find(_:options:completionHandler:)") @discardableResult open func count( _ query: Query? = nil, @@ -478,6 +486,7 @@ open class DataStore where T: NSObject { response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use group(keys:initialObject:reduceJSFunction:condition:options:completionHandler:)") @discardableResult open func group( keys: [String]? = nil, @@ -521,6 +530,7 @@ open class DataStore where T: NSObject { response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use group(keys:initialObject:reduceJSFunction:condition:options:completionHandler:)") @discardableResult open func group( keys: [String]? = nil, @@ -598,6 +608,7 @@ open class DataStore where T: NSObject { response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use group(count:countType:condition:options:completionHandler:)") @discardableResult open func group( count keys: [String], @@ -636,6 +647,7 @@ open class DataStore where T: NSObject { response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use group(count:countType:condition:options:completionHandler:)") @discardableResult open func group( count keys: [String], @@ -709,6 +721,7 @@ open class DataStore where T: NSObject { response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use group(keys:sum:sumType:condition:options:completionHandler:)") @discardableResult open func group( keys: [String], @@ -750,6 +763,7 @@ open class DataStore where T: NSObject { response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use group(keys:sum:sumType:condition:options:completionHandler:)") @discardableResult open func group( keys: [String], @@ -826,6 +840,7 @@ open class DataStore where T: NSObject { response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use group(keys:avg:avgType:condition:options:completionHandler:)") @discardableResult open func group( keys: [String], @@ -867,6 +882,7 @@ open class DataStore where T: NSObject { response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use group(keys:avg:avgType:condition:options:completionHandler:)") @discardableResult open func group( keys: [String], @@ -943,6 +959,7 @@ open class DataStore where T: NSObject { response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use group(keys:min:minType:condition:options:completionHandler:)") @discardableResult open func group( keys: [String], @@ -984,6 +1001,7 @@ open class DataStore where T: NSObject { response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use group(keys:min:minType:condition:options:completionHandler:)") @discardableResult open func group( keys: [String], @@ -1060,6 +1078,7 @@ open class DataStore where T: NSObject { response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use group(keys:max:maxType:condition:options:completionHandler:)") @discardableResult open func group( keys: [String], @@ -1101,6 +1120,7 @@ open class DataStore where T: NSObject { response returns - returns: A `Request` instance which will allow cancel the request later */ + @available(*, deprecated: 3.6.0, message: "Please use group(keys:max:maxType:condition:options:completionHandler:)") @discardableResult open func group( keys: [String], @@ -1223,6 +1243,7 @@ open class DataStore where T: NSObject { } /// Deletes a record. + @available(*, deprecated: 3.6.0, message: "Please use remove(_:options:completionHandler:) instead") @discardableResult open func remove( _ persistable: T, @@ -1243,6 +1264,7 @@ open class DataStore where T: NSObject { } /// Deletes a record. + @available(*, deprecated: 3.6.0, message: "Please use remove(_:options:completionHandler:) instead") @discardableResult open func remove( _ persistable: T, @@ -1277,6 +1299,7 @@ open class DataStore where T: NSObject { } /// Deletes a list of records. + @available(*, deprecated: 3.6.0, message: "Please use remove(_:options:completionHandler:) instead") @discardableResult open func remove( _ array: [T], @@ -1297,6 +1320,7 @@ open class DataStore where T: NSObject { } /// Deletes a list of records. + @available(*, deprecated: 3.6.0, message: "Please use remove(_:options:completionHandler:) instead") @discardableResult open func remove( _ array: [T], @@ -1348,6 +1372,7 @@ open class DataStore where T: NSObject { } /// Deletes a record using the `_id` of the record. + @available(*, deprecated: 3.6.0, message: "Please use remove(byId:options:completionHandler:) instead") @discardableResult open func remove( byId id: String, @@ -1368,6 +1393,7 @@ open class DataStore where T: NSObject { } /// Deletes a record using the `_id` of the record. + @available(*, deprecated: 3.6.0, message: "Please use remove(byId:options:completionHandler:) instead") @discardableResult open func remove( byId id: String, @@ -1424,6 +1450,7 @@ open class DataStore where T: NSObject { } /// Deletes a list of records using the `_id` of the records. + @available(*, deprecated: 3.6.0, message: "Please use remove(byIds:options:completionHandler:) instead") @discardableResult open func remove( byIds ids: [String], @@ -1444,6 +1471,7 @@ open class DataStore where T: NSObject { } /// Deletes a list of records using the `_id` of the records. + @available(*, deprecated: 3.6.0, message: "Please use remove(byIds:options:completionHandler:) instead") @discardableResult open func remove( byIds ids: [String], @@ -1482,6 +1510,7 @@ open class DataStore where T: NSObject { } /// Deletes a list of records that matches with the query passed by parameter. + @available(*, deprecated: 3.6.0, message: "Please use remove(_:options:completionHandler:) instead") @discardableResult open func remove( _ query: Query = Query(), @@ -1502,6 +1531,7 @@ open class DataStore where T: NSObject { } /// Deletes a list of records that matches with the query passed by parameter. + @available(*, deprecated: 3.6.0, message: "Please use remove(_:options:completionHandler:) instead") @discardableResult open func remove( _ query: Query = Query(), @@ -1541,6 +1571,7 @@ open class DataStore where T: NSObject { } /// Deletes all the records. + @available(*, deprecated: 3.6.0, message: "Please use removeAll(options:completionHandler:) instead") @discardableResult open func removeAll( _ writePolicy: WritePolicy? = nil, @@ -1559,6 +1590,7 @@ open class DataStore where T: NSObject { } /// Deletes all the records. + @available(*, deprecated: 3.6.0, message: "Please use removeAll(options:completionHandler:) instead") @discardableResult open func removeAll( _ writePolicy: WritePolicy? = nil, diff --git a/Kinvey/Kinvey/RequestFactory.swift b/Kinvey/Kinvey/RequestFactory.swift index f4bf87f56..2c7c047e0 100644 --- a/Kinvey/Kinvey/RequestFactory.swift +++ b/Kinvey/Kinvey/RequestFactory.swift @@ -64,15 +64,28 @@ protocol RequestFactory { } -//Allow to set a per request +/// Allow override custom values whenever the default value is not desired. public struct Options { + /// Custom `Client` instance public var client: Client? + + /// Custom `clientId` value used for MIC public var clientId: String? + + /// Custom `TTL` value used for cases where time-to-live value is present public var ttl: TTL? + + /// Enables / disables delta set public var deltaSet: Bool? + + /// Custom read policy for read operations public var readPolicy: ReadPolicy? + + /// Custom write policy for write operations public var writePolicy: WritePolicy? + + /// Custom timeout interval for network requests public var timeout: TimeInterval? /// App version for this client instance. @@ -81,6 +94,10 @@ public struct Options { /// Custom request properties for this client instance. public var customRequestProperties: [String : Any]? + /** + Constructor that takes the values that need to be specified and assign + default values for all the other properties + */ public init( client: Client? = nil, clientId: String? = nil, From 2e69266c0becccfe97a1b9284f22515eeea55211 Mon Sep 17 00:00:00 2001 From: Victor Barros Date: Mon, 17 Jul 2017 11:39:50 -0700 Subject: [PATCH 11/11] bump version 3.6.0 --- Kinvey.podspec | 2 +- Kinvey/Kinvey/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Kinvey.podspec b/Kinvey.podspec index d7bfaa58e..66dff44d1 100644 --- a/Kinvey.podspec +++ b/Kinvey.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "Kinvey" - s.version = "3.5.4" + s.version = "3.6.0" s.summary = "Kinvey iOS SDK" # This description is used to generate tags and improve search results. diff --git a/Kinvey/Kinvey/Info.plist b/Kinvey/Kinvey/Info.plist index 04d6ba451..03812edd8 100644 --- a/Kinvey/Kinvey/Info.plist +++ b/Kinvey/Kinvey/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 3.5.4 + 3.6.0 CFBundleSignature ???? CFBundleVersion