This repository has been archived by the owner. It is now read-only.
Permalink
Cannot retrieve contributors at this time
183 lines (159 sloc)
8.35 KB
| /* This Source Code Form is subject to the terms of the Mozilla Public | |
| * License, v. 2.0. If a copy of the MPL was not distributed with this | |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | |
| import Foundation | |
| import Deferred | |
| import Shared | |
| import SwiftyJSON | |
| private let log = Logger.syncLogger | |
| /// The current version of the device registration. We use this to re-register | |
| /// devices after we update what we send on device registration. | |
| private let DeviceRegistrationVersion = 2 | |
| public enum FxADeviceRegistrationResult { | |
| case registered | |
| case updated | |
| case alreadyRegistered | |
| } | |
| public enum FxADeviceRegistratorError: MaybeErrorType { | |
| case accountDeleted | |
| case currentDeviceNotFound | |
| case invalidSession | |
| case unknownDevice | |
| public var description: String { | |
| switch self { | |
| case .accountDeleted: return "Account no longer exists." | |
| case .currentDeviceNotFound: return "Current device not found." | |
| case .invalidSession: return "Session token was invalid." | |
| case .unknownDevice: return "Unknown device." | |
| } | |
| } | |
| } | |
| open class FxADeviceRegistration: NSObject, NSCoding { | |
| /// The device identifier identifying this device. A device is uniquely identified | |
| /// across the lifetime of a Firefox Account. | |
| public let id: String | |
| /// The version of the device registration. We use this to re-register | |
| /// devices after we update what we send on device registration. | |
| let version: Int | |
| /// The last time we successfully (re-)registered with the server. | |
| let lastRegistered: Timestamp | |
| init(id: String, version: Int, lastRegistered: Timestamp) { | |
| self.id = id | |
| self.version = version | |
| self.lastRegistered = lastRegistered | |
| } | |
| public convenience required init(coder: NSCoder) { | |
| let id = coder.decodeObject(forKey: "id") as! String | |
| let version = coder.decodeAsInt(forKey: "version") | |
| let lastRegistered = coder.decodeAsUInt64(forKey: "lastRegistered") | |
| self.init(id: id, version: version, lastRegistered: lastRegistered) | |
| } | |
| open func encode(with aCoder: NSCoder) { | |
| aCoder.encode(id, forKey: "id") | |
| aCoder.encode(version, forKey: "version") | |
| aCoder.encode(NSNumber(value: lastRegistered), forKey: "lastRegistered") | |
| } | |
| open func toJSON() -> JSON { | |
| return JSON([ | |
| "id": id, | |
| "version": version, | |
| "lastRegistered": lastRegistered, | |
| ]) | |
| } | |
| } | |
| open class FxADeviceRegistrator { | |
| open static func registerOrUpdateDevice(_ account: FirefoxAccount, sessionToken: NSData, client: FxAClient10? = nil) -> Deferred<Maybe<FxADeviceRegistrationResult>> { | |
| // If we've already registered, the registration version is up-to-date, *and* we've (re-)registered | |
| // within the last week, do nothing. We re-register weekly as a sanity check. | |
| if let registration = account.deviceRegistration, registration.version == DeviceRegistrationVersion && | |
| Date.now() < registration.lastRegistered + OneWeekInMilliseconds { | |
| return deferMaybe(.alreadyRegistered) | |
| } | |
| let pushParams: FxADevicePushParams? | |
| if let pushRegistration = account.pushRegistration { | |
| let subscription = pushRegistration.defaultSubscription | |
| pushParams = FxADevicePushParams(callback: subscription.endpoint.absoluteString, publicKey: subscription.p256dhPublicKey, authKey: subscription.authKey) | |
| } else { | |
| pushParams = nil | |
| } | |
| let client = client ?? FxAClient10(authEndpoint: account.configuration.authEndpointURL, oauthEndpoint: account.configuration.oauthEndpointURL, profileEndpoint: account.configuration.profileEndpointURL) | |
| let device: FxADevice | |
| let registrationResult: FxADeviceRegistrationResult | |
| if let registration = account.deviceRegistration { | |
| device = FxADevice.forUpdate(account.deviceName, id: registration.id, push: pushParams) | |
| registrationResult = .updated | |
| } else { | |
| device = FxADevice.forRegister(account.deviceName, type: "mobile", push: pushParams) | |
| registrationResult = .registered | |
| } | |
| let registeredDevice = client.registerOrUpdate(device: device, withSessionToken: sessionToken) | |
| let registration: Deferred<Maybe<FxADeviceRegistration>> = registeredDevice.bind { result in | |
| if let device = result.successValue { | |
| return deferMaybe(FxADeviceRegistration(id: device.id!, version: DeviceRegistrationVersion, lastRegistered: Date.now())) | |
| } | |
| // Recover from the error -- if we can. | |
| if let error = result.failureValue as? FxAClientError, | |
| case .remote(let remoteError) = error { | |
| switch remoteError.code { | |
| case FxAccountRemoteError.DeviceSessionConflict: | |
| return recoverFromDeviceSessionConflict(account, client: client, sessionToken: sessionToken) | |
| case FxAccountRemoteError.InvalidAuthenticationToken: | |
| return recoverFromTokenError(account, client: client) | |
| case FxAccountRemoteError.UnknownDevice: | |
| return recoverFromUnknownDevice(account) | |
| default: break | |
| } | |
| } | |
| // Not an error we can recover from. Rethrow it and fall back to the failure handler. | |
| return deferMaybe(result.failureValue!) | |
| } | |
| // Post-recovery. We either registered or we didn't, but update the account either way. | |
| return registration.bind { result in | |
| switch result { | |
| case .success(let registration): | |
| account.deviceRegistration = registration | |
| return deferMaybe(registrationResult) | |
| case .failure(let error): | |
| log.error("Device registration failed: \(error.description)") | |
| if let registration = account.deviceRegistration { | |
| account.deviceRegistration = FxADeviceRegistration(id: registration.id, version: 0, lastRegistered: registration.lastRegistered) | |
| } | |
| return deferMaybe(error) | |
| } | |
| } | |
| } | |
| fileprivate static func recoverFromDeviceSessionConflict(_ account: FirefoxAccount, client: FxAClient10, sessionToken: NSData) -> Deferred<Maybe<FxADeviceRegistration>> { | |
| // FxA has already associated this session with a different device id. | |
| // Perhaps we were beaten in a race to register. Handle the conflict: | |
| // 1. Fetch the list of devices for the current user from FxA. | |
| // 2. Look for ourselves in the list. | |
| // 3. If we find a match, set the correct device id and device registration | |
| // version on the account data and return the correct device id. At next | |
| // sync or next sign-in, registration is retried and should succeed. | |
| log.warning("Device session conflict. Attempting to find the current device ID…") | |
| return client.devices(withSessionToken: sessionToken) >>== { response in | |
| guard let currentDevice = response.devices.find({ $0.isCurrentDevice }) else { | |
| return deferMaybe(FxADeviceRegistratorError.currentDeviceNotFound) | |
| } | |
| return deferMaybe(FxADeviceRegistration(id: currentDevice.id!, version: 0, lastRegistered: Date.now())) | |
| } | |
| } | |
| fileprivate static func recoverFromTokenError(_ account: FirefoxAccount, client: FxAClient10) -> Deferred<Maybe<FxADeviceRegistration>> { | |
| return client.status(forUID: account.uid) >>== { status in | |
| _ = account.makeDoghouse() | |
| if !status.exists { | |
| // TODO: Should be in an "I have an iOS account, but the FxA is gone." state. | |
| // This will do for now... | |
| return deferMaybe(FxADeviceRegistratorError.accountDeleted) | |
| } | |
| return deferMaybe(FxADeviceRegistratorError.invalidSession) | |
| } | |
| } | |
| fileprivate static func recoverFromUnknownDevice(_ account: FirefoxAccount) -> Deferred<Maybe<FxADeviceRegistration>> { | |
| // FxA did not recognize the device ID. Handle it by clearing the registration on the account data. | |
| // At next sync or next sign-in, registration is retried and should succeed. | |
| log.warning("Unknown device ID. Clearing the local device data.") | |
| account.deviceRegistration = nil | |
| return deferMaybe(FxADeviceRegistratorError.unknownDevice) | |
| } | |
| } |