//////////////////////////////////////////////////////////////////////////// // // Copyright 2016 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////// import Realm import Foundation /** An object representing a Realm Object Server user. - see: `RLMSyncUser` */ public typealias SyncUser = RLMSyncUser /** An immutable data object representing information retrieved from the Realm Object Server about a particular user. - see: `RLMSyncUserInfo` */ public typealias SyncUserInfo = RLMSyncUserInfo /** An immutable data object representing an account belonging to a particular user. - see: `SyncUserInfo`, `RLMSyncUserAccountInfo` */ public typealias SyncUserAccountInfo = RLMSyncUserAccountInfo /** A singleton which configures and manages the Realm Object Server synchronization-related functionality. - see: `RLMSyncManager` */ public typealias SyncManager = RLMSyncManager extension SyncManager { /// The sole instance of the singleton. public static var shared: SyncManager { return __shared() } } /** A session object which represents communication between the client and server for a specific Realm. - see: `RLMSyncSession` */ public typealias SyncSession = RLMSyncSession /** A closure type for a closure which can be set on the `SyncManager` to allow errors to be reported to the application. - see: `RLMSyncErrorReportingBlock` */ public typealias ErrorReportingBlock = RLMSyncErrorReportingBlock /** A closure type for a closure which is used by certain APIs to asynchronously return a `SyncUser` object to the application. - see: `RLMUserCompletionBlock` */ public typealias UserCompletionBlock = RLMUserCompletionBlock /** An error associated with the SDK's synchronization functionality. All errors reported by an error handler registered on the `SyncManager` are of this type. - see: `RLMSyncError` */ public typealias SyncError = RLMSyncError extension SyncError { /** An opaque token allowing the user to take action after certain types of errors have been reported. - see: `RLMSyncErrorActionToken` */ public typealias ActionToken = RLMSyncErrorActionToken /** Given a client reset error, extract and return the recovery file path and the action token. The action token can be passed into `SyncSession.immediatelyHandleError(_:)` to immediately delete the local copy of the Realm which experienced the client reset error. The local copy of the Realm must be deleted before your application attempts to open the Realm again. The recovery file path is the path to which the current copy of the Realm on disk will be saved once the client reset occurs. - warning: Do not call `SyncSession.immediatelyHandleError(_:)` until you are sure that all references to the Realm and managed objects belonging to the Realm have been nil'ed out, and that all autorelease pools containing these references have been drained. - see: `SyncError.ActionToken`, `SyncSession.immediatelyHandleError(_:)` */ public func clientResetInfo() -> (String, SyncError.ActionToken)? { if code == SyncError.clientResetError, let recoveryPath = userInfo[kRLMSyncPathOfRealmBackupCopyKey] as? String, let token = _nsError.__rlmSync_errorActionToken() { return (recoveryPath, token) } return nil } /** Given a permission denied error, extract and return the action token. This action token can be passed into `SyncSession.immediatelyHandleError(_:)` to immediately delete the local copy of the Realm which experienced the permission denied error. The local copy of the Realm must be deleted before your application attempts to open the Realm again. - warning: Do not call `SyncSession.immediatelyHandleError(_:)` until you are sure that all references to the Realm and managed objects belonging to the Realm have been nil'ed out, and that all autorelease pools containing these references have been drained. - see: `SyncError.ActionToken`, `SyncSession.immediatelyHandleError(_:)` */ public func deleteRealmUserInfo() -> SyncError.ActionToken? { return _nsError.__rlmSync_errorActionToken() } } /** An error associated with network requests made to the authentication server. This type of error may be returned in the callback block to `SyncUser.logIn()` upon certain types of failed login attempts (for example, if the request is malformed or if the server is experiencing an issue). - see: `RLMSyncAuthError` */ public typealias SyncAuthError = RLMSyncAuthError /** An error associated with retrieving or modifying user permissions to access a synchronized Realm. - see: `RLMSyncPermissionError` */ public typealias SyncPermissionError = RLMSyncPermissionError /** An enum which can be used to specify the level of logging. - see: `RLMSyncLogLevel` */ public typealias SyncLogLevel = RLMSyncLogLevel /** A data type whose values represent different authentication providers that can be used with the Realm Object Server. - see: `RLMIdentityProvider` */ public typealias Provider = RLMIdentityProvider /** A `SyncConfiguration` represents configuration parameters for Realms intended to sync with a Realm Object Server. */ public struct SyncConfiguration { /// The `SyncUser` who owns the Realm that this configuration should open. public let user: SyncUser /** The URL of the Realm on the Realm Object Server that this configuration should open. - warning: The URL must be absolute (e.g. `realms://example.com/~/foo`), and cannot end with `.realm`, `.realm.lock` or `.realm.management`. */ public let realmURL: URL /** A policy that determines what should happen when all references to Realms opened by this configuration go out of scope. */ internal let stopPolicy: RLMSyncStopPolicy /** Whether the SSL certificate of the Realm Object Server should be validated. */ public let enableSSLValidation: Bool /** Whether this Realm should be opened in 'partial synchronization' mode. Partial synchronization mode means that no objects are synchronized from the remote Realm except those matching queries that the user explicitly specifies. -warning: Partial synchronization is a tech preview. Its APIs are subject to change. */ public let isPartial: Bool internal init(config: RLMSyncConfiguration) { self.user = config.user self.realmURL = config.realmURL self.stopPolicy = config.stopPolicy self.enableSSLValidation = config.enableSSLValidation self.isPartial = config.isPartial } func asConfig() -> RLMSyncConfiguration { let config = RLMSyncConfiguration(user: user, realmURL: realmURL) config.stopPolicy = stopPolicy config.enableSSLValidation = enableSSLValidation config.isPartial = isPartial return config } /** Initialize a sync configuration with a user and a Realm URL. Additional settings can be optionally specified. Descriptions of these settings follow. `enableSSLValidation` is true by default. It can be disabled for debugging purposes. - warning: The URL must be absolute (e.g. `realms://example.com/~/foo`), and cannot end with `.realm`, `.realm.lock` or `.realm.management`. - warning: NEVER disable SSL validation for a system running in production. */ public init(user: SyncUser, realmURL: URL, enableSSLValidation: Bool = true, isPartial: Bool = false) { self.user = user self.realmURL = realmURL self.stopPolicy = .afterChangesUploaded self.enableSSLValidation = enableSSLValidation self.isPartial = isPartial } } /// A `SyncCredentials` represents data that uniquely identifies a Realm Object Server user. public struct SyncCredentials { public typealias Token = String internal var token: Token internal var provider: Provider internal var userInfo: [String: Any] /** Initialize new credentials using a custom token, authentication provider, and user information dictionary. In most cases, the convenience initializers should be used instead. */ public init(customToken token: Token, provider: Provider, userInfo: [String: Any] = [:]) { self.token = token self.provider = provider self.userInfo = userInfo } internal init(_ credentials: RLMSyncCredentials) { self.token = credentials.token self.provider = credentials.provider self.userInfo = credentials.userInfo } /// Initialize new credentials using a Facebook account token. public static func facebook(token: Token) -> SyncCredentials { return SyncCredentials(RLMSyncCredentials(facebookToken: token)) } /// Initialize new credentials using a Google account token. public static func google(token: Token) -> SyncCredentials { return SyncCredentials(RLMSyncCredentials(googleToken: token)) } /// Initialize new credentials using a CloudKit account token. public static func cloudKit(token: Token) -> SyncCredentials { return SyncCredentials(RLMSyncCredentials(cloudKitToken: token)) } /// Initialize new credentials using a Realm Object Server username and password. public static func usernamePassword(username: String, password: String, register: Bool = false) -> SyncCredentials { return SyncCredentials(RLMSyncCredentials(username: username, password: password, register: register)) } /// Initialize new credentials using a Realm Object Server access token. public static func accessToken(_ accessToken: String, identity: String) -> SyncCredentials { return SyncCredentials(RLMSyncCredentials(accessToken: accessToken, identity: identity)) } /// Initialize new credentials using a JSON Web Token. public static func jwt(_ token: Token) -> SyncCredentials { return SyncCredentials(RLMSyncCredentials(jwt: token)) } /// Initialize new credentials using a nickname. public static func nickname(_ nickname: String, isAdmin: Bool = false) -> SyncCredentials { return SyncCredentials(RLMSyncCredentials(nickname: nickname, isAdmin: isAdmin)) } /// Initialize new credentials anonymously public static func anonymous() -> SyncCredentials { return SyncCredentials(RLMSyncCredentials.anonymous()) } } extension RLMSyncCredentials { internal convenience init(_ credentials: SyncCredentials) { self.init(customToken: credentials.token, provider: credentials.provider, userInfo: credentials.userInfo) } } extension SyncUser { /** Log in a user and asynchronously retrieve a user object. If the log in completes successfully, the completion block will be called, and a `SyncUser` representing the logged-in user will be passed to it. This user object can be used to open `Realm`s and retrieve `SyncSession`s. Otherwise, the completion block will be called with an error. - parameter credentials: A `SyncCredentials` object representing the user to log in. - parameter authServerURL: The URL of the authentication server (e.g. "http://realm.example.org:9080"). - parameter timeout: How long the network client should wait, in seconds, before timing out. - parameter callbackQueue: The dispatch queue upon which the callback should run. Defaults to the main queue. - parameter completion: A callback block to be invoked once the log in completes. */ public static func logIn(with credentials: SyncCredentials, server authServerURL: URL, timeout: TimeInterval = 30, callbackQueue queue: DispatchQueue = DispatchQueue.main, onCompletion completion: @escaping UserCompletionBlock) { return SyncUser.__logIn(with: RLMSyncCredentials(credentials), authServerURL: authServerURL, timeout: timeout, callbackQueue: queue, onCompletion: completion) } /// A dictionary of all valid, logged-in user identities corresponding to their `SyncUser` objects. public static var all: [String: SyncUser] { return __allUsers() } /** The logged-in user. `nil` if none exists. Only use this property if your application expects no more than one logged-in user at any given time. - warning: Throws an Objective-C exception if more than one logged-in user exists. */ public static var current: SyncUser? { return __current() } /** An optional error handler which can be set to notify the host application when the user encounters an error. - note: Check for `.invalidAccessToken` to see if the user has been remotely logged out because its refresh token expired, or because the third party authentication service providing the user's identity has logged the user out. - warning: Regardless of whether an error handler is defined, certain user errors will automatically cause the user to enter the logged out state. */ @nonobjc public var errorHandler: ((SyncUser, SyncAuthError) -> Void)? { get { return __errorHandler } set { if let newValue = newValue { __errorHandler = { (user, error) in newValue(user, error as! SyncAuthError) } } else { __errorHandler = nil } } } /** Retrieve permissions for this user. Permissions describe which synchronized Realms this user has access to and what they are allowed to do with them. Permissions are retrieved asynchronously and returned via the callback. The callback is run on the same thread that the method is invoked upon. - warning: This method must be invoked on a thread with an active run loop. - warning: Do not pass the `Results` returned by the callback between threads. - parameter callback: A callback providing either a `Results` containing the permissions, or an error describing what went wrong. */ public func retrievePermissions(callback: @escaping (SyncPermissionResults?, SyncPermissionError?) -> Void) { self.__retrievePermissions { (results, error) in guard let results = results else { callback(nil, error as! SyncPermissionError?) return } let upcasted: RLMResults = results callback(Results(upcasted as! RLMResults), nil) } } /** Create a permission offer for a Realm. A permission offer is used to grant access to a Realm this user manages to another user. Creating a permission offer produces a string token which can be passed to the recepient in any suitable way (for example, via e-mail). The operation will take place asynchronously. The token can be accepted by the recepient using the `SyncUser.acceptOffer(forToken:, callback:)` method. - parameter url: The URL of the Realm for which the permission offer should pertain. This may be the URL of any Realm which this user is allowed to manage. If the URL has a `~` wildcard it will be replaced with this user's user identity. - parameter accessLevel: What access level to grant to whoever accepts the token. - parameter expiration: Optionally, a date which indicates when the offer expires. If the recepient attempts to accept the offer after the date it will be rejected. If nil, the offer will never expire. - parameter callback: A callback indicating whether the operation succeeded or failed. If it succeeded the token will be passed in as a string. */ public func createOfferForRealm(at url: URL, accessLevel: SyncAccessLevel, expiration: Date? = nil, callback: @escaping (String?, SyncPermissionError?) -> Void) { self.__createOfferForRealm(at: url, accessLevel: accessLevel, expiration: expiration) { (token, error) in guard let token = token else { callback(nil, error as! SyncPermissionError?) return } callback(token, nil) } } } /** A value which represents a permission granted to a user to interact with a Realm. These values are passed into APIs on `SyncUser`, and returned from `SyncPermissionResults`. - see: `RLMSyncPermission` */ public typealias SyncPermission = RLMSyncPermission /** An enumeration describing possible access levels. - see: `RLMSyncAccessLevel` */ public typealias SyncAccessLevel = RLMSyncAccessLevel public extension SyncSession { /** The transfer direction (upload or download) tracked by a given progress notification block. Progress notification blocks can be registered on sessions if your app wishes to be informed how many bytes have been uploaded or downloaded, for example to show progress indicator UIs. */ public enum ProgressDirection { /// For monitoring upload progress. case upload /// For monitoring download progress. case download } /** The desired behavior of a progress notification block. Progress notification blocks can be registered on sessions if your app wishes to be informed how many bytes have been uploaded or downloaded, for example to show progress indicator UIs. */ public enum ProgressMode { /** The block will be called forever, or until it is unregistered by calling `ProgressNotificationToken.invalidate()`. Notifications will always report the latest number of transferred bytes, and the most up-to-date number of total transferrable bytes. */ case reportIndefinitely /** The block will, upon registration, store the total number of bytes to be transferred. When invoked, it will always report the most up-to-date number of transferrable bytes out of that original number of transferrable bytes. When the number of transferred bytes reaches or exceeds the number of transferrable bytes, the block will be unregistered. */ case forCurrentlyOutstandingWork } /** A token corresponding to a progress notification block. Call `invalidate()` on the token to stop notifications. If the notification block has already been automatically stopped, calling `invalidate()` does nothing. `invalidate()` should be called before the token is destroyed. */ public typealias ProgressNotificationToken = RLMProgressNotificationToken /** A struct encapsulating progress information, as well as useful helper methods. */ public struct Progress { /// The number of bytes that have been transferred. public let transferredBytes: Int /** The total number of transferrable bytes (bytes that have been transferred, plus bytes pending transfer). If the notification block is tracking downloads, this number represents the size of the changesets generated by all other clients using the Realm. If the notification block is tracking uploads, this number represents the size of the changesets representing the local changes on this client. */ public let transferrableBytes: Int /// The fraction of bytes transferred out of all transferrable bytes. If this value is 1, /// no bytes are waiting to be transferred (either all bytes have already been transferred, /// or there are no bytes to be transferred in the first place). public var fractionTransferred: Double { if transferrableBytes == 0 { return 1 } let percentage = Double(transferredBytes) / Double(transferrableBytes) return percentage > 1 ? 1 : percentage } /// Whether all pending bytes have already been transferred. public var isTransferComplete: Bool { return transferredBytes >= transferrableBytes } fileprivate init(transferred: UInt, transferrable: UInt) { transferredBytes = Int(transferred) transferrableBytes = Int(transferrable) } } /** Register a progress notification block. If the session has already received progress information from the synchronization subsystem, the block will be called immediately. Otherwise, it will be called as soon as progress information becomes available. Multiple blocks can be registered with the same session at once. Each block will be invoked on a side queue devoted to progress notifications. The token returned by this method must be retained as long as progress notifications are desired, and the `invalidate()` method should be called on it when notifications are no longer needed and before the token is destroyed. If no token is returned, the notification block will never be called again. There are a number of reasons this might be true. If the session has previously experienced a fatal error it will not accept progress notification blocks. If the block was configured in the `forCurrentlyOutstandingWork` mode but there is no additional progress to report (for example, the number of transferrable bytes and transferred bytes are equal), the block will not be called again. - parameter direction: The transfer direction (upload or download) to track in this progress notification block. - parameter mode: The desired behavior of this progress notification block. - parameter block: The block to invoke when notifications are available. - returns: A token which must be held for as long as you want notifications to be delivered. - see: `ProgressDirection`, `Progress`, `ProgressNotificationToken` */ public func addProgressNotification(for direction: ProgressDirection, mode: ProgressMode, block: @escaping (Progress) -> Void) -> ProgressNotificationToken? { return __addProgressNotification(for: (direction == .upload ? .upload : .download), mode: (mode == .reportIndefinitely ? .reportIndefinitely : .forCurrentlyOutstandingWork)) { transferred, transferrable in block(Progress(transferred: transferred, transferrable: transferrable)) } } } extension Realm { /** If the Realm is a partially synchronized Realm, fetch and synchronize the objects of a given object type that match the given query (in string format). The results will be returned asynchronously in the callback. Use `Results.observe(_:)` to be notified to changes to the set of synchronized objects. -warning: Partial synchronization is a tech preview. Its APIs are subject to change. */ public func subscribe(to objects: T.Type, where: String, completion: @escaping (Results?, Swift.Error?) -> Void) { rlmRealm.subscribe(toObjects: objects, where: `where`) { (results, error) in completion(results.map { Results($0) }, error) } } } // MARK: - Permissions and permission results extension SyncPermission: RealmCollectionValue { } /** A `Results` collection containing sync permission results. */ public typealias SyncPermissionResults = Results /** A property upon which a `SyncPermissionResults` can be sorted or queried. The raw value string can be used to construct predicates and queries manually. - warning: If building `NSPredicate`s using format strings including these raw values, use `%K` instead of `%@` as the substitution parameter. - see: `RLMSyncPermissionSortProperty` */ public typealias SyncPermissionSortProperty = RLMSyncPermissionSortProperty extension SortDescriptor { /** Construct a sort descriptor using a `SyncPermissionSortProperty`. */ public init(sortProperty: SyncPermissionSortProperty, ascending: Bool = true) { self.init(keyPath: sortProperty.rawValue, ascending: ascending) } } #if swift(>=3.1) extension Results where Element == SyncPermission { /** Return a `Results` containing the objects represented by the results, but sorted on the specified property. - see: `sorted(byKeyPath:, ascending:)` */ public func sorted(bySortProperty sortProperty: SyncPermissionSortProperty, ascending: Bool = true) -> Results { return sorted(by: [SortDescriptor(sortProperty: sortProperty, ascending: ascending)]) } } #endif // MARK: - Migration assistance /// :nodoc: @available(*, unavailable, renamed: "SyncPermission") public final class SyncPermissionValue { }