Ilya Laktyushin 32bde3d24b Update API
2024-01-30 03:25:44 +04:00

1304 lines
70 KiB
Swift

import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum AuthorizationCodeRequestError {
case invalidPhoneNumber
case limitExceeded
case generic(info: (Int, String)?)
case phoneLimitExceeded
case phoneBanned
case timeout
case appOutdated
}
func switchToAuthorizedAccount(transaction: AccountManagerModifier<TelegramAccountManagerTypes>, account: UnauthorizedAccount) {
let nextSortOrder = (transaction.getRecords().map({ record -> Int32 in
for attribute in record.attributes {
if case let .sortOrder(sortOrder) = attribute {
return sortOrder.order
}
}
return 0
}).max() ?? 0) + 1
transaction.updateRecord(account.id, { _ in
return AccountRecord(id: account.id, attributes: [
.environment(AccountEnvironmentAttribute(environment: account.testingEnvironment ? .test : .production)),
.sortOrder(AccountSortOrderAttribute(order: nextSortOrder))
], temporarySessionId: nil)
})
transaction.setCurrentId(account.id)
transaction.removeAuth()
}
private struct Regex {
let pattern: String
let options: NSRegularExpression.Options!
private var matcher: NSRegularExpression {
return try! NSRegularExpression(pattern: self.pattern, options: self.options)
}
init(_ pattern: String) {
self.pattern = pattern
self.options = []
}
func match(_ string: String, options: NSRegularExpression.MatchingOptions = []) -> Bool {
return self.matcher.numberOfMatches(in: string, options: options, range: NSMakeRange(0, string.utf16.count)) != 0
}
}
private protocol RegularExpressionMatchable {
func match(_ regex: Regex) -> Bool
}
private struct MatchString: RegularExpressionMatchable {
private let string: String
init(_ string: String) {
self.string = string
}
func match(_ regex: Regex) -> Bool {
return regex.match(self.string)
}
}
private func ~=<T: RegularExpressionMatchable>(pattern: Regex, matchable: T) -> Bool {
return matchable.match(pattern)
}
public enum SendAuthorizationCodeResult {
case sentCode(UnauthorizedAccount)
case loggedIn
}
func storeFutureLoginToken(accountManager: AccountManager<TelegramAccountManagerTypes>, token: Data) {
let _ = (accountManager.transaction { transaction -> Void in
var tokens = transaction.getStoredLoginTokens()
#if DEBUG
tokens.removeAll()
#endif
var cloudValue: [Data] = []
if let list = NSUbiquitousKeyValueStore.default.object(forKey: "T_SLTokens") as? [String] {
cloudValue = list.compactMap { string -> Data? in
guard let stringData = string.data(using: .utf8) else {
return nil
}
return Data(base64Encoded: stringData)
}
}
for data in cloudValue {
if !tokens.contains(data) {
tokens.insert(data, at: 0)
}
}
tokens.insert(token, at: 0)
if tokens.count > 20 {
tokens.removeLast(tokens.count - 20)
}
NSUbiquitousKeyValueStore.default.set(tokens.map { $0.base64EncodedString() }, forKey: "T_SLTokens")
NSUbiquitousKeyValueStore.default.synchronize()
transaction.setStoredLoginTokens(tokens)
}).start()
}
public struct AuthorizationCodePushNotificationConfiguration {
public var token: String
public var isSandbox: Bool
public init(token: String, isSandbox: Bool) {
self.token = token
self.isSandbox = isSandbox
}
}
enum SendFirebaseAuthorizationCodeError {
case generic
}
private func sendFirebaseAuthorizationCode(accountManager: AccountManager<TelegramAccountManagerTypes>, account: UnauthorizedAccount, phoneNumber: String, apiId: Int32, apiHash: String, phoneCodeHash: String, timeout: Int32?, firebaseSecret: String, syncContacts: Bool) -> Signal<Bool, SendFirebaseAuthorizationCodeError> {
//auth.requestFirebaseSms#89464b50 flags:# phone_number:string phone_code_hash:string safety_net_token:flags.0?string ios_push_secret:flags.1?string = Bool;
var flags: Int32 = 0
flags |= 1 << 1
return account.network.request(Api.functions.auth.requestFirebaseSms(flags: flags, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, safetyNetToken: nil, iosPushSecret: firebaseSecret))
|> mapError { _ -> SendFirebaseAuthorizationCodeError in
return .generic
}
|> mapToSignal { result -> Signal<Bool, SendFirebaseAuthorizationCodeError> in
return .single(true)
}
}
public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccountManagerTypes>, account: UnauthorizedAccount, phoneNumber: String, apiId: Int32, apiHash: String, pushNotificationConfiguration: AuthorizationCodePushNotificationConfiguration?, firebaseSecretStream: Signal<[String: String], NoError>, syncContacts: Bool, forcedPasswordSetupNotice: @escaping (Int32) -> (NoticeEntryKey, CodableEntry)?) -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> {
var cloudValue: [Data] = []
if let list = NSUbiquitousKeyValueStore.default.object(forKey: "T_SLTokens") as? [String] {
cloudValue = list.compactMap { string -> Data? in
guard let stringData = string.data(using: .utf8) else {
return nil
}
return Data(base64Encoded: stringData)
}
}
#if DEBUG
cloudValue.removeAll()
#endif
return accountManager.transaction { transaction -> [Data] in
return transaction.getStoredLoginTokens()
}
|> castError(AuthorizationCodeRequestError.self)
|> mapToSignal { localAuthTokens -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
var authTokens = localAuthTokens
#if DEBUG
authTokens.removeAll()
#endif
for data in cloudValue {
if !authTokens.contains(data) {
authTokens.insert(data, at: 0)
}
}
var flags: Int32 = 0
flags |= 1 << 5 //allowMissedCall
flags |= 1 << 6 //tokens
var token: String?
var appSandbox: Api.Bool?
if let pushNotificationConfiguration = pushNotificationConfiguration {
flags |= 1 << 7
flags |= 1 << 8
token = pushNotificationConfiguration.token
appSandbox = pushNotificationConfiguration.isSandbox ? .boolTrue : .boolFalse
}
let sendCode = Api.functions.auth.sendCode(phoneNumber: phoneNumber, apiId: apiId, apiHash: apiHash, settings: .codeSettings(flags: flags, logoutTokens: authTokens.map { Buffer(data: $0) }, token: token, appSandbox: appSandbox))
enum SendCodeResult {
case password(hint: String?)
case sentCode(Api.auth.SentCode)
}
let codeAndAccount = account.network.request(sendCode, automaticFloodWait: false)
|> map { result -> (SendCodeResult, UnauthorizedAccount) in
return (.sentCode(result), account)
}
|> `catch` { error -> Signal<(SendCodeResult, UnauthorizedAccount), MTRpcError> in
switch MatchString(error.errorDescription ?? "") {
case Regex("(PHONE_|USER_|NETWORK_)MIGRATE_(\\d+)"):
let range = error.errorDescription.range(of: "MIGRATE_")!
let updatedMasterDatacenterId = Int32(error.errorDescription[range.upperBound ..< error.errorDescription.endIndex])!
let updatedAccount = account.changedMasterDatacenterId(accountManager: accountManager, masterDatacenterId: updatedMasterDatacenterId)
return updatedAccount
|> mapToSignalPromotingError { updatedAccount -> Signal<(SendCodeResult, UnauthorizedAccount), MTRpcError> in
return updatedAccount.network.request(sendCode, automaticFloodWait: false)
|> map { sentCode in
return (.sentCode(sentCode), updatedAccount)
}
|> `catch` { error -> Signal<(SendCodeResult, UnauthorizedAccount), MTRpcError> in
if error.errorDescription == "SESSION_PASSWORD_NEEDED" {
return updatedAccount.network.request(Api.functions.account.getPassword(), automaticFloodWait: false)
|> mapToSignal { result -> Signal<(SendCodeResult, UnauthorizedAccount), MTRpcError> in
switch result {
case let .password(_, _, _, _, hint, _, _, _, _, _, _):
return .single((.password(hint: hint), updatedAccount))
}
}
} else {
return .fail(error)
}
}
}
case _:
return .fail(error)
}
}
|> `catch` { error -> Signal<(SendCodeResult, UnauthorizedAccount), AuthorizationCodeRequestError> in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .fail(.limitExceeded)
} else if error.errorDescription == "PHONE_NUMBER_INVALID" {
return .fail(.invalidPhoneNumber)
} else if error.errorDescription == "PHONE_NUMBER_FLOOD" {
return .fail(.phoneLimitExceeded)
} else if error.errorDescription == "PHONE_NUMBER_BANNED" {
return .fail(.phoneBanned)
} else if error.errorDescription == "UPDATE_APP_TO_LOGIN" {
return .fail(.appOutdated)
} else if error.errorDescription == "SESSION_PASSWORD_NEEDED" {
return account.network.request(Api.functions.account.getPassword(), automaticFloodWait: false)
|> mapError { error -> AuthorizationCodeRequestError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else {
return .generic(info: (Int(error.errorCode), error.errorDescription))
}
}
|> mapToSignal { result -> Signal<(SendCodeResult, UnauthorizedAccount), AuthorizationCodeRequestError> in
switch result {
case let .password(_, _, _, _, hint, _, _, _, _, _, _):
return .single((.password(hint: hint), account))
}
}
} else {
return .fail(.generic(info: (Int(error.errorCode), error.errorDescription)))
}
}
|> timeout(20.0, queue: Queue.concurrentDefaultQueue(), alternate: .fail(.timeout))
return codeAndAccount
|> mapToSignal { result, account -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
return account.postbox.transaction { transaction -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
switch result {
case let .password(hint):
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .passwordEntry(hint: hint ?? "", number: nil, code: nil, suggestReset: false, syncContacts: syncContacts)))
return .single(.sentCode(account))
case let .sentCode(sentCode):
switch sentCode {
case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout):
var parsedNextType: AuthorizationCodeNextType?
if let nextType = nextType {
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
if case let .sentCodeTypeFirebaseSms(_, _, receipt, pushTimeout, _) = type {
return firebaseSecretStream
|> map { mapping -> String? in
guard let receipt = receipt else {
return nil
}
if let value = mapping[receipt] {
return value
}
if receipt == "" && mapping.count == 1 {
return mapping.first?.value
}
return nil
}
|> filter { $0 != nil }
|> take(1)
|> timeout(Double(pushTimeout ?? 15), queue: .mainQueue(), alternate: .single(nil))
|> castError(AuthorizationCodeRequestError.self)
|> mapToSignal { firebaseSecret -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
guard let firebaseSecret = firebaseSecret else {
return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: phoneNumber, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream)
}
return sendFirebaseAuthorizationCode(accountManager: accountManager, account: account, phoneNumber: phoneNumber, apiId: apiId, apiHash: apiHash, phoneCodeHash: phoneCodeHash, timeout: codeTimeout, firebaseSecret: firebaseSecret, syncContacts: syncContacts)
|> `catch` { _ -> Signal<Bool, SendFirebaseAuthorizationCodeError> in
return .single(false)
}
|> mapError { _ -> AuthorizationCodeRequestError in
return .generic(info: nil)
}
|> mapToSignal { success -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
if success {
return account.postbox.transaction { transaction -> SendAuthorizationCodeResult in
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
return .sentCode(account)
}
|> castError(AuthorizationCodeRequestError.self)
} else {
return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: phoneNumber, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream)
}
}
}
}
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
case let .sentCodeSuccess(authorization):
switch authorization {
case let .authorization(_, otherwiseReloginDays, _, futureAuthToken, user):
if let futureAuthToken = futureAuthToken {
storeFutureLoginToken(accountManager: accountManager, token: futureAuthToken.makeData())
}
let user = TelegramUser(user: user)
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil, invalidatedChannels: [])
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: account.networkArguments.appVersion, syncContacts: syncContacts)
transaction.setState(state)
if let otherwiseReloginDays = otherwiseReloginDays, let value = forcedPasswordSetupNotice(otherwiseReloginDays) {
transaction.setNoticeEntry(key: value.0, value: value.1)
}
return accountManager.transaction { transaction -> SendAuthorizationCodeResult in
switchToAuthorizedAccount(transaction: transaction, account: account)
return .loggedIn
}
|> castError(AuthorizationCodeRequestError.self)
case .authorizationSignUpRequired:
return .never()
}
}
return .single(.sentCode(account))
}
}
|> mapError { _ -> AuthorizationCodeRequestError in
}
|> switchToLatest
}
}
}
private func internalResendAuthorizationCode(accountManager: AccountManager<TelegramAccountManagerTypes>, account: UnauthorizedAccount, number: String, apiId: Int32, apiHash: String, hash: String, syncContacts: Bool, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> {
return account.network.request(Api.functions.auth.resendCode(phoneNumber: number, phoneCodeHash: hash), automaticFloodWait: false)
|> mapError { error -> AuthorizationCodeRequestError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription == "PHONE_NUMBER_INVALID" {
return .invalidPhoneNumber
} else if error.errorDescription == "PHONE_NUMBER_FLOOD" {
return .phoneLimitExceeded
} else if error.errorDescription == "PHONE_NUMBER_BANNED" {
return .phoneBanned
} else if error.errorDescription == "UPDATE_APP_TO_LOGIN" {
return .appOutdated
} else {
return .generic(info: (Int(error.errorCode), error.errorDescription))
}
}
|> mapToSignal { sentCode -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
return account.postbox.transaction { transaction -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
switch sentCode {
case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout):
var parsedNextType: AuthorizationCodeNextType?
if let nextType = nextType {
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
if case let .sentCodeTypeFirebaseSms(_, _, receipt, pushTimeout, _) = type {
return firebaseSecretStream
|> map { mapping -> String? in
guard let receipt = receipt else {
return nil
}
if let value = mapping[receipt] {
return value
}
if receipt == "" && mapping.count == 1 {
return mapping.first?.value
}
return nil
}
|> filter { $0 != nil }
|> take(1)
|> timeout(Double(pushTimeout ?? 15), queue: .mainQueue(), alternate: .single(nil))
|> castError(AuthorizationCodeRequestError.self)
|> mapToSignal { firebaseSecret -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
guard let firebaseSecret = firebaseSecret else {
return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: number, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream)
}
return sendFirebaseAuthorizationCode(accountManager: accountManager, account: account, phoneNumber: number, apiId: apiId, apiHash: apiHash, phoneCodeHash: phoneCodeHash, timeout: codeTimeout, firebaseSecret: firebaseSecret, syncContacts: syncContacts)
|> `catch` { _ -> Signal<Bool, SendFirebaseAuthorizationCodeError> in
return .single(false)
}
|> mapError { _ -> AuthorizationCodeRequestError in
return .generic(info: nil)
}
|> mapToSignal { success -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
if success {
return account.postbox.transaction { transaction -> SendAuthorizationCodeResult in
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
return .sentCode(account)
}
|> castError(AuthorizationCodeRequestError.self)
} else {
return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: number, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream)
}
}
}
}
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
return .single(.sentCode(account))
case .sentCodeSuccess:
return .single(.loggedIn)
}
}
|> mapError { _ -> AuthorizationCodeRequestError in }
|> switchToLatest
}
}
public func resendAuthorizationCode(accountManager: AccountManager<TelegramAccountManagerTypes>, account: UnauthorizedAccount, apiId: Int32, apiHash: String, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal<Void, AuthorizationCodeRequestError> {
return account.postbox.transaction { transaction -> Signal<Void, AuthorizationCodeRequestError> in
if let state = transaction.getState() as? UnauthorizedAccountState {
switch state.contents {
case let .confirmationCodeEntry(number, _, hash, _, nextType, syncContacts):
if nextType != nil {
return account.network.request(Api.functions.auth.resendCode(phoneNumber: number, phoneCodeHash: hash), automaticFloodWait: false)
|> mapError { error -> AuthorizationCodeRequestError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription == "PHONE_NUMBER_INVALID" {
return .invalidPhoneNumber
} else if error.errorDescription == "PHONE_NUMBER_FLOOD" {
return .phoneLimitExceeded
} else if error.errorDescription == "PHONE_NUMBER_BANNED" {
return .phoneBanned
} else if error.errorDescription == "UPDATE_APP_TO_LOGIN" {
return .appOutdated
} else {
return .generic(info: (Int(error.errorCode), error.errorDescription))
}
}
|> mapToSignal { sentCode -> Signal<Void, AuthorizationCodeRequestError> in
return account.postbox.transaction { transaction -> Signal<Void, AuthorizationCodeRequestError> in
switch sentCode {
case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout):
var parsedNextType: AuthorizationCodeNextType?
if let nextType = nextType {
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
if case let .sentCodeTypeFirebaseSms(_, _, receipt, pushTimeout, _) = type {
return firebaseSecretStream
|> map { mapping -> String? in
guard let receipt = receipt else {
return nil
}
if let value = mapping[receipt] {
return value
}
if receipt == "" && mapping.count == 1 {
return mapping.first?.value
}
return nil
}
|> filter { $0 != nil }
|> take(1)
|> timeout(Double(pushTimeout ?? 15), queue: .mainQueue(), alternate: .single(nil))
|> castError(AuthorizationCodeRequestError.self)
|> mapToSignal { firebaseSecret -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
guard let firebaseSecret = firebaseSecret else {
return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: number, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream)
}
return sendFirebaseAuthorizationCode(accountManager: accountManager, account: account, phoneNumber: number, apiId: apiId, apiHash: apiHash, phoneCodeHash: phoneCodeHash, timeout: codeTimeout, firebaseSecret: firebaseSecret, syncContacts: syncContacts)
|> `catch` { _ -> Signal<Bool, SendFirebaseAuthorizationCodeError> in
return .single(false)
}
|> mapError { _ -> AuthorizationCodeRequestError in
return .generic(info: nil)
}
|> mapToSignal { success -> Signal<SendAuthorizationCodeResult, AuthorizationCodeRequestError> in
if success {
return account.postbox.transaction { transaction -> SendAuthorizationCodeResult in
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
return .sentCode(account)
}
|> castError(AuthorizationCodeRequestError.self)
} else {
return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: number, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream)
}
}
}
|> map { _ -> Void in return Void() }
}
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
case .sentCodeSuccess:
break
}
return .single(Void())
}
|> mapError { _ -> AuthorizationCodeRequestError in }
|> switchToLatest
}
} else {
return .fail(.generic(info: nil))
}
default:
return .complete()
}
} else {
return .fail(.generic(info: nil))
}
}
|> mapError { _ -> AuthorizationCodeRequestError in
}
|> switchToLatest
}
public enum AuthorizationCodeVerificationError {
case invalidCode
case limitExceeded
case generic
case codeExpired
case invalidEmailToken
case invalidEmailAddress
}
private enum AuthorizationCodeResult {
case authorization(Api.auth.Authorization)
case password(hint: String)
case signUp
}
public enum AuthorizationCode: PostboxCoding, Equatable {
private enum CodeType: Int32 {
case phoneCode = 0
case emailCode = 1
case appleToken = 2
case googleToken = 3
}
public enum EmailVerification: Equatable {
case emailCode(String)
case appleToken(String)
case googleToken(String)
}
case phoneCode(String)
case emailVerification(EmailVerification)
public init(decoder: PostboxDecoder) {
let type = decoder.decodeInt32ForKey("t", orElse: 0)
switch type {
case CodeType.phoneCode.rawValue:
self = .phoneCode(decoder.decodeStringForKey("c", orElse: ""))
case CodeType.emailCode.rawValue:
self = .emailVerification(.emailCode(decoder.decodeStringForKey("c", orElse: "")))
case CodeType.appleToken.rawValue:
self = .emailVerification(.appleToken(decoder.decodeStringForKey("c", orElse: "")))
case CodeType.googleToken.rawValue:
self = .emailVerification(.googleToken(decoder.decodeStringForKey("c", orElse: "")))
default:
assertionFailure()
self = .phoneCode("")
}
}
public func encode(_ encoder: PostboxEncoder) {
switch self {
case let .phoneCode(code):
encoder.encodeInt32(CodeType.phoneCode.rawValue, forKey: "t")
encoder.encodeString(code, forKey: "c")
case let .emailVerification(verification):
switch verification {
case let .emailCode(code):
encoder.encodeInt32(CodeType.emailCode.rawValue, forKey: "t")
encoder.encodeString(code, forKey: "c")
case let .appleToken(token):
encoder.encodeInt32(CodeType.appleToken.rawValue, forKey: "t")
encoder.encodeString(token, forKey: "c")
case let .googleToken(token):
encoder.encodeInt32(CodeType.googleToken.rawValue, forKey: "t")
encoder.encodeString(token, forKey: "c")
}
}
}
}
public struct AuthorizationSignUpData {
let number: String
let codeHash: String
let code: AuthorizationCode
let termsOfService: UnauthorizedAccountTermsOfService?
let syncContacts: Bool
}
public enum AuthorizeWithCodeResult {
case signUp(AuthorizationSignUpData)
case loggedIn
}
public enum AuthorizationSendEmailCodeError {
case generic
case limitExceeded
case codeExpired
case timeout
case invalidEmail
case emailNotAllowed
}
public enum AuthorizationEmailVerificationError {
case generic
case limitExceeded
case codeExpired
case invalidCode
case timeout
case invalidEmailToken
case emailNotAllowed
}
public struct ChangeLoginEmailData: Equatable {
public let email: String
public let length: Int32
}
public func sendLoginEmailChangeCode(account: Account, email: String) -> Signal<ChangeLoginEmailData, AuthorizationSendEmailCodeError> {
return account.network.request(Api.functions.account.sendVerifyEmailCode(purpose: .emailVerifyPurposeLoginChange, email: email), automaticFloodWait: false)
|> `catch` { error -> Signal<Api.account.SentEmailCode, AuthorizationSendEmailCodeError> in
let errorDescription = error.errorDescription ?? ""
if errorDescription.hasPrefix("FLOOD_WAIT") {
return .fail(.limitExceeded)
} else if errorDescription == "CODE_HASH_EXPIRED" || errorDescription == "PHONE_CODE_EXPIRED" {
return .fail(.codeExpired)
} else if errorDescription.hasPrefix("EMAIL_INVALID") {
return .fail(.invalidEmail)
} else if errorDescription.hasPrefix("EMAIL_NOT_ALLOWED") {
return .fail(.emailNotAllowed)
} else {
return .fail(.generic)
}
}
|> map { result -> ChangeLoginEmailData in
switch result {
case let .sentEmailCode(_, length):
return ChangeLoginEmailData(email: email, length: length)
}
}
}
public func sendLoginEmailCode(account: UnauthorizedAccount, email: String) -> Signal<Never, AuthorizationSendEmailCodeError> {
return account.postbox.transaction { transaction -> Signal<Never, AuthorizationSendEmailCodeError> in
if let state = transaction.getState() as? UnauthorizedAccountState {
switch state.contents {
case let .confirmationCodeEntry(phoneNumber, _, phoneCodeHash, _, _, syncContacts):
return account.network.request(Api.functions.account.sendVerifyEmailCode(purpose: .emailVerifyPurposeLoginSetup(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), email: email), automaticFloodWait: false)
|> `catch` { error -> Signal<Api.account.SentEmailCode, AuthorizationSendEmailCodeError> in
let errorDescription = error.errorDescription ?? ""
if errorDescription.hasPrefix("FLOOD_WAIT") {
return .fail(.limitExceeded)
} else if errorDescription == "CODE_HASH_EXPIRED" || errorDescription == "PHONE_CODE_EXPIRED" {
return .fail(.codeExpired)
} else if errorDescription.hasPrefix("EMAIL_INVALID") {
return .fail(.invalidEmail)
} else if errorDescription.hasPrefix("EMAIL_NOT_ALLOWED") {
return .fail(.emailNotAllowed)
} else {
return .fail(.generic)
}
}
|> mapToSignal { result -> Signal<Never, AuthorizationSendEmailCodeError> in
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
switch result {
case let .sentEmailCode(emailPattern, length):
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: .email(emailPattern: emailPattern, length: length, resetAvailablePeriod: nil, resetPendingDate: nil, appleSignInAllowed: false, setup: true), hash: phoneCodeHash, timeout: nil, nextType: nil, syncContacts: syncContacts)))
}
return .complete()
}
|> switchToLatest
|> mapError { _ -> AuthorizationSendEmailCodeError in
}
|> ignoreValues
}
default:
return .fail(.generic)
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> AuthorizationSendEmailCodeError in
}
|> switchToLatest
|> ignoreValues
}
public func verifyLoginEmailChange(account: Account, code: AuthorizationCode.EmailVerification) -> Signal<Never, AuthorizationEmailVerificationError> {
let verification: Api.EmailVerification
switch code {
case let .emailCode(code):
verification = .emailVerificationCode(code: code)
case let .appleToken(token):
verification = .emailVerificationApple(token: token)
case let .googleToken(token):
verification = .emailVerificationGoogle(token: token)
}
return account.network.request(Api.functions.account.verifyEmail(purpose: .emailVerifyPurposeLoginChange, verification: verification), automaticFloodWait: false)
|> `catch` { error -> Signal<Api.account.EmailVerified, AuthorizationEmailVerificationError> in
let errorDescription = error.errorDescription ?? ""
if errorDescription.hasPrefix("FLOOD_WAIT") {
return .fail(.limitExceeded)
} else if errorDescription == "CODE_HASH_EXPIRED" || errorDescription == "PHONE_CODE_EXPIRED" || errorDescription == "EMAIL_VERIFY_EXPIRED" {
return .fail(.codeExpired)
} else if errorDescription == "CODE_INVALID" {
return .fail(.invalidCode)
} else if errorDescription == "EMAIL_TOKEN_INVALID" {
return .fail(.invalidEmailToken)
} else if errorDescription == "EMAIL_NOT_ALLOWED" {
return .fail(.emailNotAllowed)
} else {
return .fail(.generic)
}
}
|> mapToSignal { _ -> Signal<Never, AuthorizationEmailVerificationError> in
return .complete()
}
}
public func verifyLoginEmailSetup(account: UnauthorizedAccount, code: AuthorizationCode.EmailVerification) -> Signal<Never, AuthorizationEmailVerificationError> {
return account.postbox.transaction { transaction -> Signal<Never, AuthorizationEmailVerificationError> in
if let state = transaction.getState() as? UnauthorizedAccountState {
switch state.contents {
case let .confirmationCodeEntry(phoneNumber, _, phoneCodeHash, _, _, syncContacts):
let verification: Api.EmailVerification
switch code {
case let .emailCode(code):
verification = .emailVerificationCode(code: code)
case let .appleToken(token):
verification = .emailVerificationApple(token: token)
case let .googleToken(token):
verification = .emailVerificationGoogle(token: token)
}
return account.network.request(Api.functions.account.verifyEmail(purpose: .emailVerifyPurposeLoginSetup(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), verification: verification), automaticFloodWait: false)
|> `catch` { error -> Signal<Api.account.EmailVerified, AuthorizationEmailVerificationError> in
let errorDescription = error.errorDescription ?? ""
if errorDescription.hasPrefix("FLOOD_WAIT") {
return .fail(.limitExceeded)
} else if errorDescription == "CODE_HASH_EXPIRED" || errorDescription == "PHONE_CODE_EXPIRED" || errorDescription == "EMAIL_VERIFY_EXPIRED" {
return .fail(.codeExpired)
} else if errorDescription == "CODE_INVALID" {
return .fail(.invalidCode)
} else if errorDescription == "EMAIL_TOKEN_INVALID" {
return .fail(.invalidEmailToken)
} else if errorDescription == "EMAIL_NOT_ALLOWED" {
return .fail(.emailNotAllowed)
} else {
return .fail(.generic)
}
}
|> mapToSignal { result -> Signal<Never, AuthorizationEmailVerificationError> in
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
switch result {
case let .emailVerifiedLogin(_, sentCode):
switch sentCode {
case let .sentCode(_, type, phoneCodeHash, nextType, timeout):
var parsedNextType: AuthorizationCodeNextType?
if let nextType = nextType {
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType, syncContacts: syncContacts)))
case .sentCodeSuccess:
break
}
case .emailVerified:
break
}
return .complete()
}
|> switchToLatest
|> mapError { _ -> AuthorizationEmailVerificationError in
}
|> ignoreValues
}
default:
return .fail(.generic)
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> AuthorizationEmailVerificationError in
}
|> switchToLatest
|> ignoreValues
}
public enum AuthorizationEmailResetError {
case generic
case limitExceeded
case codeExpired
case alreadyInProgress
}
public func resetLoginEmail(account: UnauthorizedAccount, phoneNumber: String, phoneCodeHash: String) -> Signal<Never, AuthorizationEmailResetError> {
return account.postbox.transaction { transaction -> Signal<Never, AuthorizationEmailResetError> in
if let state = transaction.getState() as? UnauthorizedAccountState {
switch state.contents {
case let .confirmationCodeEntry(phoneNumber, _, phoneCodeHash, _, _, syncContacts):
return account.network.request(Api.functions.auth.resetLoginEmail(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), automaticFloodWait: false)
|> `catch` { error -> Signal<Api.auth.SentCode, AuthorizationEmailResetError> in
let errorDescription = error.errorDescription ?? ""
if errorDescription.hasPrefix("FLOOD_WAIT") {
return .fail(.limitExceeded)
} else if errorDescription == "CODE_HASH_EXPIRED" || errorDescription == "PHONE_CODE_EXPIRED" {
return .fail(.codeExpired)
} else if errorDescription == "TASK_ALREADY_EXISTS" {
return .fail(.alreadyInProgress)
} else {
return .fail(.generic)
}
}
|> mapToSignal { sentCode -> Signal<Never, AuthorizationEmailResetError> in
return account.postbox.transaction { transaction -> Signal<Never, NoError> in
switch sentCode {
case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout):
var parsedNextType: AuthorizationCodeNextType?
if let nextType = nextType {
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts)))
return .complete()
case .sentCodeSuccess:
return .complete()
}
}
|> switchToLatest
|> mapError { _ -> AuthorizationEmailResetError in
}
|> ignoreValues
}
default:
return .fail(.generic)
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> AuthorizationEmailResetError in
}
|> switchToLatest
|> ignoreValues
}
public func authorizeWithCode(accountManager: AccountManager<TelegramAccountManagerTypes>, account: UnauthorizedAccount, code: AuthorizationCode, termsOfService: UnauthorizedAccountTermsOfService?, forcedPasswordSetupNotice: @escaping (Int32) -> (NoticeEntryKey, CodableEntry)?) -> Signal<AuthorizeWithCodeResult, AuthorizationCodeVerificationError> {
return account.postbox.transaction { transaction -> Signal<AuthorizeWithCodeResult, AuthorizationCodeVerificationError> in
if let state = transaction.getState() as? UnauthorizedAccountState {
switch state.contents {
case let .confirmationCodeEntry(number, _, hash, _, _, syncContacts):
var flags: Int32 = 0
var phoneCode: String?
var emailVerification: Api.EmailVerification?
switch code {
case let .phoneCode(code):
flags = 1 << 0
phoneCode = code
case let .emailVerification(verification):
flags = 1 << 1
switch verification {
case let .emailCode(code):
emailVerification = .emailVerificationCode(code: code)
case let .appleToken(token):
emailVerification = .emailVerificationApple(token: token)
case let .googleToken(token):
emailVerification = .emailVerificationGoogle(token: token)
}
}
return account.network.request(Api.functions.auth.signIn(flags: flags, phoneNumber: number, phoneCodeHash: hash, phoneCode: phoneCode, emailVerification: emailVerification), automaticFloodWait: false)
|> map { authorization in
return .authorization(authorization)
}
|> `catch` { error -> Signal<AuthorizationCodeResult, AuthorizationCodeVerificationError> in
switch (error.errorCode, error.errorDescription ?? "") {
case (401, "SESSION_PASSWORD_NEEDED"):
return account.network.request(Api.functions.account.getPassword(), automaticFloodWait: false)
|> mapError { error -> AuthorizationCodeVerificationError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<AuthorizationCodeResult, AuthorizationCodeVerificationError> in
switch result {
case let .password(_, _, _, _, hint, _, _, _, _, _, _):
return .single(.password(hint: hint ?? ""))
}
}
case let (_, errorDescription):
if errorDescription.hasPrefix("FLOOD_WAIT") {
return .fail(.limitExceeded)
} else if errorDescription == "PHONE_CODE_INVALID" || errorDescription == "EMAIL_CODE_INVALID" {
return .fail(.invalidCode)
} else if errorDescription == "CODE_HASH_EXPIRED" || errorDescription == "PHONE_CODE_EXPIRED" {
return .fail(.codeExpired)
} else if errorDescription == "PHONE_NUMBER_UNOCCUPIED" {
return .single(.signUp)
} else if errorDescription == "EMAIL_TOKEN_INVALID" {
return .fail(.invalidEmailToken)
} else if errorDescription == "EMAIL_ADDRESS_INVALID" {
return .fail(.invalidEmailAddress)
} else {
return .fail(.generic)
}
}
}
|> mapToSignal { result -> Signal<AuthorizeWithCodeResult, AuthorizationCodeVerificationError> in
return account.postbox.transaction { transaction -> Signal<AuthorizeWithCodeResult, NoError> in
switch result {
case .signUp:
return .single(.signUp(AuthorizationSignUpData(number: number, codeHash: hash, code: code, termsOfService: termsOfService, syncContacts: syncContacts)))
case let .password(hint):
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .passwordEntry(hint: hint, number: number, code: code, suggestReset: false, syncContacts: syncContacts)))
return .single(.loggedIn)
case let .authorization(authorization):
switch authorization {
case let .authorization(_, otherwiseReloginDays, _, futureAuthToken, user):
if let futureAuthToken = futureAuthToken {
storeFutureLoginToken(accountManager: accountManager, token: futureAuthToken.makeData())
}
let user = TelegramUser(user: user)
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil, invalidatedChannels: [])
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: account.networkArguments.appVersion, syncContacts: syncContacts)
transaction.setState(state)
if let otherwiseReloginDays = otherwiseReloginDays, let value = forcedPasswordSetupNotice(otherwiseReloginDays) {
transaction.setNoticeEntry(key: value.0, value: value.1)
}
return accountManager.transaction { transaction -> AuthorizeWithCodeResult in
switchToAuthorizedAccount(transaction: transaction, account: account)
return .loggedIn
}
case let .authorizationSignUpRequired(_, termsOfService):
return .single(.signUp(AuthorizationSignUpData(number: number, codeHash: hash, code: code, termsOfService: termsOfService.flatMap(UnauthorizedAccountTermsOfService.init(apiTermsOfService:)), syncContacts: syncContacts)))
}
}
}
|> switchToLatest
|> mapError { _ -> AuthorizationCodeVerificationError in
}
}
default:
return .fail(.generic)
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> AuthorizationCodeVerificationError in
}
|> switchToLatest
}
public func beginSignUp(account: UnauthorizedAccount, data: AuthorizationSignUpData) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Void in
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .signUp(number: data.number, codeHash: data.codeHash, firstName: "", lastName: "", termsOfService: data.termsOfService, syncContacts: data.syncContacts)))
}
|> ignoreValues
}
public enum AuthorizationPasswordVerificationError {
case limitExceeded
case invalidPassword
case generic
}
public func authorizeWithPassword(accountManager: AccountManager<TelegramAccountManagerTypes>, account: UnauthorizedAccount, password: String, syncContacts: Bool) -> Signal<Void, AuthorizationPasswordVerificationError> {
return verifyPassword(account, password: password)
|> `catch` { error -> Signal<Api.auth.Authorization, AuthorizationPasswordVerificationError> in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .fail(.limitExceeded)
} else if error.errorDescription == "PASSWORD_HASH_INVALID" {
return .fail(.invalidPassword)
} else {
return .fail(.generic)
}
}
|> mapToSignal { result -> Signal<Void, AuthorizationPasswordVerificationError> in
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
switch result {
case let .authorization(_, _, _, futureAuthToken, user):
if let futureAuthToken = futureAuthToken {
storeFutureLoginToken(accountManager: accountManager, token: futureAuthToken.makeData())
}
let user = TelegramUser(user: user)
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil, invalidatedChannels: [])
/*transaction.updatePeersInternal([user], update: { current, peer -> Peer? in
return peer
})*/
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: account.networkArguments.appVersion, syncContacts: syncContacts)
transaction.setState(state)
return accountManager.transaction { transaction -> Void in
switchToAuthorizedAccount(transaction: transaction, account: account)
}
case .authorizationSignUpRequired:
return .complete()
}
}
|> switchToLatest
|> mapError { _ -> AuthorizationPasswordVerificationError in
}
}
}
public enum PasswordRecoveryRequestError {
case limitExceeded
case generic
}
public enum PasswordRecoveryOption {
case none
case email(pattern: String)
}
public enum PasswordRecoveryError {
case invalidCode
case limitExceeded
case expired
case generic
}
func _internal_checkPasswordRecoveryCode(network: Network, code: String) -> Signal<Never, PasswordRecoveryError> {
return network.request(Api.functions.auth.checkRecoveryPassword(code: code), automaticFloodWait: false)
|> mapError { error -> PasswordRecoveryError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription.hasPrefix("PASSWORD_RECOVERY_EXPIRED") {
return .expired
} else {
return .invalidCode
}
}
|> mapToSignal { result -> Signal<Never, PasswordRecoveryError> in
return .complete()
}
}
public final class RecoveredAccountData {
let authorization: Api.auth.Authorization
init(authorization: Api.auth.Authorization) {
self.authorization = authorization
}
}
public func loginWithRecoveredAccountData(accountManager: AccountManager<TelegramAccountManagerTypes>, account: UnauthorizedAccount, recoveredAccountData: RecoveredAccountData, syncContacts: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
switch recoveredAccountData.authorization {
case let .authorization(_, _, _, futureAuthToken, user):
if let futureAuthToken = futureAuthToken {
storeFutureLoginToken(accountManager: accountManager, token: futureAuthToken.makeData())
}
let user = TelegramUser(user: user)
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil, invalidatedChannels: [])
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: account.networkArguments.appVersion, syncContacts: syncContacts)
transaction.setState(state)
return accountManager.transaction { transaction -> Void in
switchToAuthorizedAccount(transaction: transaction, account: account)
}
case .authorizationSignUpRequired:
return .complete()
}
}
|> switchToLatest
|> ignoreValues
}
func _internal_performPasswordRecovery(network: Network, code: String, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<RecoveredAccountData, PasswordRecoveryError> {
return _internal_twoStepAuthData(network)
|> mapError { _ -> PasswordRecoveryError in
return .generic
}
|> mapToSignal { authData -> Signal<RecoveredAccountData, PasswordRecoveryError> in
let newSettings: Api.account.PasswordInputSettings?
switch updatedPassword {
case .none:
newSettings = nil
case let .password(password, hint, email):
var flags: Int32 = 1 << 0
if email != nil {
flags |= (1 << 1)
}
guard let (updatedPasswordHash, updatedPasswordDerivation) = passwordUpdateKDF(encryptionProvider: network.encryptionProvider, password: password, derivation: authData.nextPasswordDerivation) else {
return .fail(.invalidCode)
}
newSettings = Api.account.PasswordInputSettings.passwordInputSettings(flags: flags, newAlgo: updatedPasswordDerivation.apiAlgo, newPasswordHash: Buffer(data: updatedPasswordHash), hint: hint, email: email, newSecureSettings: nil)
}
var flags: Int32 = 0
if newSettings != nil {
flags |= 1 << 0
}
return network.request(Api.functions.auth.recoverPassword(flags: flags, code: code, newSettings: newSettings), automaticFloodWait: false)
|> mapError { error -> PasswordRecoveryError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription.hasPrefix("PASSWORD_RECOVERY_EXPIRED") {
return .expired
} else {
return .invalidCode
}
}
|> mapToSignal { result -> Signal<RecoveredAccountData, PasswordRecoveryError> in
return .single(RecoveredAccountData(authorization: result))
}
}
}
func _internal_invalidateLoginCodes(network: Network, codes: [String]) -> Signal<Never, NoError> {
return network.request(Api.functions.account.invalidateSignInCodes(codes: codes))
|> ignoreValues
|> `catch` { _ -> Signal<Never, NoError> in
return .never()
}
}
public enum AccountResetError {
case generic
case limitExceeded
}
public func performAccountReset(account: UnauthorizedAccount) -> Signal<Void, AccountResetError> {
return account.network.request(Api.functions.account.deleteAccount(flags: 0, reason: "", password: nil))
|> map { _ -> Int32? in return nil }
|> `catch` { error -> Signal<Int32?, AccountResetError> in
if error.errorDescription.hasPrefix("2FA_CONFIRM_WAIT_") {
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "2FA_CONFIRM_WAIT_".count)...])
if let value = Int32(timeout) {
return .single(value)
} else {
return .fail(.generic)
}
} else if error.errorDescription == "2FA_RECENT_CONFIRM" {
return .fail(.limitExceeded)
} else {
return .fail(.generic)
}
}
|> mapToSignal { timeout -> Signal<Void, AccountResetError> in
return account.postbox.transaction { transaction -> Void in
guard let state = transaction.getState() as? UnauthorizedAccountState else {
return
}
var number: String?
var syncContacts: Bool?
if case let .passwordEntry(_, numberValue, _, _, syncContactsValue) = state.contents {
number = numberValue
syncContacts = syncContactsValue
} else if case let .awaitingAccountReset(_, numberValue, syncContactsValue) = state.contents {
number = numberValue
syncContacts = syncContactsValue
}
if let number = number, let syncContacts = syncContacts {
if let timeout = timeout {
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: state.isTestingEnvironment, masterDatacenterId: state.masterDatacenterId, contents: .awaitingAccountReset(protectedUntil: timestamp + timeout, number: number, syncContacts: syncContacts)))
} else {
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: state.isTestingEnvironment, masterDatacenterId: state.masterDatacenterId, contents: .empty))
}
}
}
|> mapError { _ -> AccountResetError in }
}
}
public enum SignUpError {
case generic
case limitExceeded
case codeExpired
case invalidFirstName
case invalidLastName
}
public func signUpWithName(accountManager: AccountManager<TelegramAccountManagerTypes>, account: UnauthorizedAccount, firstName: String, lastName: String, avatarData: Data?, avatarVideo: Signal<UploadedPeerPhotoData?, NoError>?, videoStartTimestamp: Double?, disableJoinNotifications: Bool = false, forcedPasswordSetupNotice: @escaping (Int32) -> (NoticeEntryKey, CodableEntry)?) -> Signal<Void, SignUpError> {
return account.postbox.transaction { transaction -> Signal<Void, SignUpError> in
if let state = transaction.getState() as? UnauthorizedAccountState, case let .signUp(number, codeHash, _, _, _, syncContacts) = state.contents {
var flags: Int32 = 0
if disableJoinNotifications {
flags |= (1 << 0)
}
return account.network.request(Api.functions.auth.signUp(flags: flags, phoneNumber: number, phoneCodeHash: codeHash, firstName: firstName, lastName: lastName))
|> mapError { error -> SignUpError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription == "PHONE_CODE_EXPIRED" {
return .codeExpired
} else if error.errorDescription == "FIRSTNAME_INVALID" {
return .invalidFirstName
} else if error.errorDescription == "LASTNAME_INVALID" {
return .invalidLastName
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<Void, SignUpError> in
switch result {
case let .authorization(_, otherwiseReloginDays, _, futureAuthToken, user):
if let futureAuthToken = futureAuthToken {
storeFutureLoginToken(accountManager: accountManager, token: futureAuthToken.makeData())
}
let user = TelegramUser(user: user)
let appliedState = account.postbox.transaction { transaction -> Void in
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil, invalidatedChannels: [])
if let hole = account.postbox.seedConfiguration.initializeChatListWithHole.topLevel {
transaction.replaceChatListHole(groupId: .root, index: hole.index, hole: nil)
}
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: account.networkArguments.appVersion, syncContacts: syncContacts)
transaction.setState(state)
if let otherwiseReloginDays = otherwiseReloginDays, let value = forcedPasswordSetupNotice(otherwiseReloginDays) {
transaction.setNoticeEntry(key: value.0, value: value.1)
}
}
|> castError(SignUpError.self)
let switchedAccounts = accountManager.transaction { transaction -> Void in
switchToAuthorizedAccount(transaction: transaction, account: account)
}
|> castError(SignUpError.self)
if let avatarData = avatarData {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
account.postbox.mediaBox.storeResourceData(resource.id, data: avatarData)
return _internal_updatePeerPhotoInternal(postbox: account.postbox, network: account.network, stateManager: nil, accountPeerId: user.id, peer: .single(user), photo: _internal_uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource), video: avatarVideo, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { _, _ in .single([:]) })
|> `catch` { _ -> Signal<UpdatePeerPhotoStatus, SignUpError> in
return .complete()
}
|> mapToSignal { result -> Signal<Void, SignUpError> in
switch result {
case .complete:
return .complete()
case .progress:
return .never()
}
}
|> then(appliedState)
|> then(switchedAccounts)
} else {
return appliedState
|> then(switchedAccounts)
}
case .authorizationSignUpRequired:
return .fail(.generic)
}
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> SignUpError in
}
|> switchToLatest
}
public enum AuthorizationStateReset {
case empty
}
public func resetAuthorizationState(account: UnauthorizedAccount, to value: AuthorizationStateReset) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Void in
if let state = transaction.getState() as? UnauthorizedAccountState {
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: state.isTestingEnvironment, masterDatacenterId: state.masterDatacenterId, contents: .empty))
}
}
}