118 lines
5.6 KiB
Swift
118 lines
5.6 KiB
Swift
//
|
|
// Copyright 2023 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
|
|
public protocol RegistrationSessionManager {
|
|
|
|
/// Restore any existing registration session that has not been completed and validate it with the server.
|
|
/// If there is no session, or if the session is invalid, returns nil.
|
|
func restoreSession() -> Guarantee<RegistrationSession?>
|
|
|
|
/// Begins a new session, first attempting to restore any existing valid session for the same number.
|
|
/// See `Registration.BeginSessionResponse` for possible responses, including errors.
|
|
func beginOrRestoreSession(e164: E164, apnsToken: String?) -> Guarantee<Registration.BeginSessionResponse>
|
|
|
|
/// Fulfill a challenge for the session (e.g. a captcha).
|
|
/// See `Registration.UpdateSessionResponse` for possible responses, including errors.
|
|
func fulfillChallenge(for session: RegistrationSession, fulfillment: Registration.ChallengeFulfillment) -> Guarantee<Registration.UpdateSessionResponse>
|
|
|
|
/// Request a verification code be sent to the session's phone number, for some transport.
|
|
/// See `Registration.UpdateSessionResponse` for possible responses, including errors.
|
|
func requestVerificationCode(for session: RegistrationSession, transport: Registration.CodeTransport) -> Guarantee<Registration.UpdateSessionResponse>
|
|
|
|
/// Submit a verification code that was previously requested.
|
|
/// See `Registration.UpdateSessionResponse` for possible responses, including errors.
|
|
func submitVerificationCode(for session: RegistrationSession, code: String) -> Guarantee<Registration.UpdateSessionResponse>
|
|
|
|
/// Completes a session, wiping it from future restoration.
|
|
/// Typically called once the session is verified and is used to complete registration.
|
|
/// Note: a session is NOT automatically completed when `RegistrationSession.verified` is true, as the registration
|
|
/// process may be interrupted before using the verified session to actually register. (e.g. the user backgrounds the app).
|
|
func clearPersistedSession(_ transaction: DBWriteTransaction)
|
|
}
|
|
|
|
public enum Registration {
|
|
|
|
public enum CodeTransport: String, Equatable {
|
|
case sms
|
|
case voice
|
|
}
|
|
|
|
public enum ChallengeFulfillment: Equatable {
|
|
case captcha(String)
|
|
case pushChallenge(String)
|
|
}
|
|
|
|
public enum BeginSessionResponse: Equatable {
|
|
case success(RegistrationSession)
|
|
/// Typically, invalid e164.
|
|
case invalidArgument
|
|
/// The server indicated the client should retry after at least this much time has passed.
|
|
case retryAfter(TimeInterval)
|
|
/// A network failure.
|
|
case networkFailure
|
|
/// Some other generic unknown error.
|
|
case genericError
|
|
}
|
|
|
|
/// Collapses a few different error responses possible from the server down to only those
|
|
/// relevant to the registration flow, in other words those that should have distinct error behaviors.
|
|
public enum UpdateSessionResponse: Equatable {
|
|
case success(RegistrationSession)
|
|
/// Some input was incorrect or otherwise rejected; typically
|
|
/// should have the user update and retry.
|
|
/// `RegistrationSession` state should be checked regardless, as
|
|
/// (for example) a new challenge may have been requested.
|
|
case rejectedArgument(RegistrationSession)
|
|
/// This happens when the attempted operation cannot be fulfilled
|
|
/// given the current session state, and a different operation must
|
|
/// be completed first, such as a challenge.
|
|
/// For example, a verification code may have been submitted
|
|
/// despite no code being available for submission.
|
|
case disallowed(RegistrationSession)
|
|
/// This happens when the provided transport is rejected,
|
|
/// for example if attempting to send a verification code sms
|
|
/// to a landline number.
|
|
/// The user can retry with an alternative transport method.
|
|
case transportError(RegistrationSession)
|
|
/// The request was made before the required timeout; it should be
|
|
/// made again after the time specified in the `RegistrationSession`.
|
|
case retryAfterTimeout(RegistrationSession)
|
|
/// The provided session has timed out or is otherwise invalid, and a new
|
|
/// session needs to be started, throwing away all current session state.
|
|
case invalidSession
|
|
/// Something went wrong on the server or an external service.
|
|
/// If non-permanent, a retry after some delay is allowed.
|
|
/// Otherwise, registration may not be possible.
|
|
case serverFailure(ServerFailureResponse)
|
|
/// A network failure.
|
|
case networkFailure
|
|
/// Some other generic unknown error.
|
|
case genericError
|
|
}
|
|
|
|
public struct ServerFailureResponse: Equatable {
|
|
public let session: RegistrationSession
|
|
|
|
/// If true, whatever failure occurred isn't likely to be resolved by retrying.
|
|
/// Otherwise a retry after some delay is appropriate. (e.g. let the user retry)
|
|
public let isPermanent: Bool
|
|
|
|
public let reason: Reason?
|
|
|
|
public enum Reason: String, Equatable {
|
|
/// A service provider rejected sending a code to a phone number,
|
|
/// even though it is valid, e.g. for fraud reasons.
|
|
/// Typically requires resetting the entire session.
|
|
case providerRejected
|
|
/// A provider rejected a phone number as invalid.
|
|
/// Typically requires resetting the entire session.
|
|
case illegalArgument
|
|
/// A provider was unavailable, typically temporary.
|
|
case providerUnavailable
|
|
}
|
|
}
|
|
}
|