in swift-source/all/FxAClient/FxAccountManager.swift [357:554]
func stateActions(forState: AccountState, via: Event) -> Event? {
switch forState {
case .start: do {
switch via {
case .initialize: do {
if let acct = tryRestoreAccount() {
account = acct
return .accountRestored
} else {
return .accountNotFound
}
}
default: return nil
}
}
case .notAuthenticated: do {
switch via {
case .logout: do {
// Clean up internal account state and destroy the current FxA device record.
requireAccount().disconnect()
FxALog.info("Disconnected FxA account")
profile = nil
constellation = nil
accountStorage.clear()
// If we cannot instantiate FxA something is *really* wrong, crashing is a valid option.
account = createAccount()
DispatchQueue.main.async {
NotificationCenter.default.post(
name: .accountLoggedOut,
object: nil
)
}
}
case .accountNotFound: do {
account = createAccount()
}
default: break // Do nothing
}
}
case .authenticatedNoProfile: do {
switch via {
case let .authenticated(authData): do {
FxALog.info("Registering persistence callback")
requireAccount().registerPersistCallback(statePersistenceCallback)
FxALog.debug("Completing oauth flow")
do {
try requireAccount().completeOAuthFlow(code: authData.code, state: authData.state)
} catch {
// Reasons this can fail:
// - network errors
// - unknown auth state
// - authenticating via web-content; we didn't beginOAuthFlowAsync
FxALog.error("Error completing OAuth flow: \(error)")
}
FxALog.info("Initializing device")
requireConstellation().initDevice(
name: deviceConfig.name,
type: deviceConfig.deviceType,
capabilities: deviceConfig.capabilities
)
postAuthenticated(authType: authData.authType)
return Event.fetchProfile(ignoreCache: false)
}
case .accountRestored: do {
FxALog.info("Registering persistence callback")
requireAccount().registerPersistCallback(statePersistenceCallback)
FxALog.info("Ensuring device capabilities...")
requireConstellation().ensureCapabilities(capabilities: deviceConfig.capabilities)
postAuthenticated(authType: .existingAccount)
return Event.fetchProfile(ignoreCache: false)
}
case .recoveredFromAuthenticationProblem: do {
FxALog.info("Registering persistence callback")
requireAccount().registerPersistCallback(statePersistenceCallback)
FxALog.info("Initializing device")
requireConstellation().initDevice(
name: deviceConfig.name,
type: deviceConfig.deviceType,
capabilities: deviceConfig.capabilities
)
postAuthenticated(authType: .recovered)
return Event.fetchProfile(ignoreCache: false)
}
case let .changedPassword(newSessionToken): do {
do {
try requireAccount().handleSessionTokenChange(sessionToken: newSessionToken)
FxALog.info("Initializing device")
requireConstellation().initDevice(
name: deviceConfig.name,
type: deviceConfig.deviceType,
capabilities: deviceConfig.capabilities
)
postAuthenticated(authType: .existingAccount)
return Event.fetchProfile(ignoreCache: false)
} catch {
FxALog.error("Error handling the session token change: \(error)")
}
}
case let .fetchProfile(ignoreCache): do {
// Profile fetching and account authentication issues:
// https://github.com/mozilla/application-services/issues/483
FxALog.info("Fetching profile...")
do {
profile = try requireAccount().getProfile(ignoreCache: ignoreCache)
} catch {
return Event.failedToFetchProfile
}
return Event.fetchedProfile
}
default: break // Do nothing
}
}
case .authenticatedWithProfile: do {
switch via {
case .fetchedProfile: do {
DispatchQueue.main.async {
NotificationCenter.default.post(
name: .accountProfileUpdate,
object: nil,
userInfo: ["profile": self.profile!]
)
}
}
case let .fetchProfile(refresh): do {
FxALog.info("Refreshing profile...")
do {
profile = try requireAccount().getProfile(ignoreCache: refresh)
} catch {
return Event.failedToFetchProfile
}
return Event.fetchedProfile
}
default: break // Do nothing
}
}
case .authenticationProblem:
switch via {
case .authenticationError: do {
// Somewhere in the system, we've just hit an authentication problem.
// There are two main causes:
// 1) an access token we've obtain from fxalib via 'getAccessToken' expired
// 2) password was changed, or device was revoked
// We can recover from (1) and test if we're in (2) by asking the fxalib.
// If it succeeds, then we can go back to whatever
// state we were in before. Future operations that involve access tokens should
// succeed.
func onError() {
// We are either certainly in the scenario (2), or were unable to determine
// our connectivity state. Let's assume we need to re-authenticate.
// This uncertainty about real state means that, hopefully rarely,
// we will disconnect users that hit transient network errors during
// an authorization check.
// See https://github.com/mozilla-mobile/android-components/issues/3347
FxALog.error("Unable to recover from an auth problem.")
DispatchQueue.main.async {
NotificationCenter.default.post(
name: .accountAuthProblems,
object: nil
)
}
}
do {
let account = requireAccount()
let info = try account.checkAuthorizationStatus()
if !info.active {
onError()
return nil
}
account.clearAccessTokenCache()
// Make sure we're back on track by re-requesting the profile access token.
_ = try account.getAccessToken(scope: OAuthScope.profile)
return .recoveredFromAuthenticationProblem
} catch {
onError()
}
return nil
}
default: break // Do nothing
}
}
return nil
}