Swiftgram/TelegramCore/Account.swift
Peter 0b96d69daa Fixed unread counter category for private channels
Added support for "contact joined" service messages
Updated password recovery API
Updated Localization APIs
Added limited contact presence polling after getDifference
2018-11-11 17:46:21 +04:00

1200 lines
58 KiB
Swift

import Foundation
#if os(macOS)
import PostboxMac
import SwiftSignalKitMac
import MtProtoKitMac
#else
import Postbox
import SwiftSignalKit
import MtProtoKitDynamic
#endif
import TelegramCorePrivateModule
public protocol AccountState: PostboxCoding {
func equalsTo(_ other: AccountState) -> Bool
}
public func ==(lhs: AccountState, rhs: AccountState) -> Bool {
return lhs.equalsTo(rhs)
}
public class AuthorizedAccountState: AccountState {
public final class State: PostboxCoding, Equatable, CustomStringConvertible {
let pts: Int32
let qts: Int32
let date: Int32
let seq: Int32
init(pts: Int32, qts: Int32, date: Int32, seq: Int32) {
self.pts = pts
self.qts = qts
self.date = date
self.seq = seq
}
public init(decoder: PostboxDecoder) {
self.pts = decoder.decodeInt32ForKey("pts", orElse: 0)
self.qts = decoder.decodeInt32ForKey("qts", orElse: 0)
self.date = decoder.decodeInt32ForKey("date", orElse: 0)
self.seq = decoder.decodeInt32ForKey("seq", orElse: 0)
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.pts, forKey: "pts")
encoder.encodeInt32(self.qts, forKey: "qts")
encoder.encodeInt32(self.date, forKey: "date")
encoder.encodeInt32(self.seq, forKey: "seq")
}
public var description: String {
return "(pts: \(pts), qts: \(qts), seq: \(seq), date: \(date))"
}
}
let isTestingEnvironment: Bool
let masterDatacenterId: Int32
let peerId: PeerId
let state: State?
public required init(decoder: PostboxDecoder) {
self.isTestingEnvironment = decoder.decodeInt32ForKey("isTestingEnvironment", orElse: 0) != 0
self.masterDatacenterId = decoder.decodeInt32ForKey("masterDatacenterId", orElse: 0)
self.peerId = PeerId(decoder.decodeInt64ForKey("peerId", orElse: 0))
self.state = decoder.decodeObjectForKey("state", decoder: { return State(decoder: $0) }) as? State
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.isTestingEnvironment ? 1 : 0, forKey: "isTestingEnvironment")
encoder.encodeInt32(self.masterDatacenterId, forKey: "masterDatacenterId")
encoder.encodeInt64(self.peerId.toInt64(), forKey: "peerId")
if let state = self.state {
encoder.encodeObject(state, forKey: "state")
}
}
public init(isTestingEnvironment: Bool, masterDatacenterId: Int32, peerId: PeerId, state: State?) {
self.isTestingEnvironment = isTestingEnvironment
self.masterDatacenterId = masterDatacenterId
self.peerId = peerId
self.state = state
}
func changedState(_ state: State) -> AuthorizedAccountState {
return AuthorizedAccountState(isTestingEnvironment: self.isTestingEnvironment, masterDatacenterId: self.masterDatacenterId, peerId: self.peerId, state: state)
}
public func equalsTo(_ other: AccountState) -> Bool {
if let other = other as? AuthorizedAccountState {
return self.isTestingEnvironment == other.isTestingEnvironment && self.masterDatacenterId == other.masterDatacenterId &&
self.peerId == other.peerId &&
self.state == other.state
} else {
return false
}
}
}
public func ==(lhs: AuthorizedAccountState.State, rhs: AuthorizedAccountState.State) -> Bool {
return lhs.pts == rhs.pts &&
lhs.qts == rhs.qts &&
lhs.date == rhs.date &&
lhs.seq == rhs.seq
}
public class UnauthorizedAccount {
public let networkArguments: NetworkInitializationArguments
public let id: AccountRecordId
public let rootPath: String
public let basePath: String
public let testingEnvironment: Bool
public let postbox: Postbox
public let network: Network
public var masterDatacenterId: Int32 {
return Int32(self.network.mtProto.datacenterId)
}
public let shouldBeServiceTaskMaster = Promise<AccountServiceTaskMasterMode>()
init(networkArguments: NetworkInitializationArguments, id: AccountRecordId, rootPath: String, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, shouldKeepAutoConnection: Bool = true) {
self.networkArguments = networkArguments
self.id = id
self.rootPath = rootPath
self.basePath = basePath
self.testingEnvironment = testingEnvironment
self.postbox = postbox
self.network = network
network.shouldKeepConnection.set(self.shouldBeServiceTaskMaster.get()
|> map { mode -> Bool in
switch mode {
case .now, .always:
return true
case .never:
return false
}
})
}
public func changedMasterDatacenterId(_ masterDatacenterId: Int32) -> Signal<UnauthorizedAccount, NoError> {
if masterDatacenterId == Int32(self.network.mtProto.datacenterId) {
return .single(self)
} else {
let postbox = self.postbox
let keychain = Keychain(get: { key in
return postbox.keychainEntryForKey(key)
}, set: { (key, data) in
postbox.setKeychainEntryForKey(key, value: data)
}, remove: { key in
postbox.removeKeychainEntryForKey(key)
})
return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?) in
return (transaction.getPreferencesEntry(key: PreferencesKeys.localizationSettings) as? LocalizationSettings, transaction.getPreferencesEntry(key: PreferencesKeys.proxySettings) as? ProxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings) as? NetworkSettings)
} |> mapToSignal { (localizationSettings, proxySettings, networkSettings) -> Signal<UnauthorizedAccount, NoError> in
return initializedNetwork(arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil)
|> map { network in
let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network)
updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get())
return updated
}
}
}
}
}
func accountNetworkUsageInfoPath(basePath: String) -> String {
return basePath + "/network-usage"
}
public func accountRecordIdPathName(_ id: AccountRecordId) -> String {
return "account-\(UInt64(bitPattern: id.int64))"
}
public enum AccountResult {
case upgrading
case unauthorized(UnauthorizedAccount)
case authorized(Account)
}
let telegramPostboxSeedConfiguration: SeedConfiguration = {
var initializeMessageNamespacesWithHoles: [(PeerId.Namespace, MessageId.Namespace)] = []
for peerNamespace in peerIdNamespacesWithInitialCloudMessageHoles {
initializeMessageNamespacesWithHoles.append((peerNamespace, Namespaces.Message.Cloud))
}
return SeedConfiguration(initializeChatListWithHole: (topLevel: ChatListHole(index: MessageIndex(id: MessageId(peerId: PeerId(namespace: Namespaces.Peer.Empty, id: 0), namespace: Namespaces.Message.Cloud, id: 1), timestamp: Int32.max - 1)), groups: ChatListHole(index: MessageIndex(id: MessageId(peerId: PeerId(namespace: Namespaces.Peer.Empty, id: 0), namespace: Namespaces.Message.Cloud, id: 1), timestamp: 1))), initializeMessageNamespacesWithHoles: initializeMessageNamespacesWithHoles, existingMessageTags: MessageTags.all, messageTagsWithSummary: MessageTags.unseenPersonalMessage, existingGlobalMessageTags: GlobalMessageTags.all, peerNamespacesRequiringMessageTextIndex: [Namespaces.Peer.SecretChat], peerSummaryCounterTags: { peer in
if let peer = peer as? TelegramChannel {
switch peer.info {
case .group:
if let addressName = peer.addressName, !addressName.isEmpty {
return [.publicGroups]
} else {
return [.regularChatsAndPrivateGroups]
}
case .broadcast:
return [.channels]
}
} else {
return [.regularChatsAndPrivateGroups]
}
})
}()
public func accountWithId(networkArguments: NetworkInitializationArguments, id: AccountRecordId, supplementary: Bool, rootPath: String, beginWithTestingEnvironment: Bool, auxiliaryMethods: AccountAuxiliaryMethods, shouldKeepAutoConnection: Bool = true) -> Signal<AccountResult, NoError> {
let path = "\(rootPath)/\(accountRecordIdPathName(id))"
let postbox = openPostbox(basePath: path + "/postbox", globalMessageIdsNamespace: Namespaces.Message.Cloud, seedConfiguration: telegramPostboxSeedConfiguration)
return postbox
|> mapToSignal { result -> Signal<AccountResult, NoError> in
switch result {
case .upgrading:
return .single(.upgrading)
case let .postbox(postbox):
return postbox.stateView()
|> take(1)
|> mapToSignal { view -> Signal<AccountResult, NoError> in
return postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?) in
return (transaction.getPreferencesEntry(key: PreferencesKeys.localizationSettings) as? LocalizationSettings, transaction.getPreferencesEntry(key: PreferencesKeys.proxySettings) as? ProxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings) as? NetworkSettings)
}
|> mapToSignal { (localizationSettings, proxySettings, networkSettings) -> Signal<AccountResult, NoError> in
let accountState = view.state
let keychain = Keychain(get: { key in
return postbox.keychainEntryForKey(key)
}, set: { (key, data) in
postbox.setKeychainEntryForKey(key, value: data)
}, remove: { key in
postbox.removeKeychainEntryForKey(key)
})
if let accountState = accountState {
switch accountState {
case let unauthorizedState as UnauthorizedAccountState:
return initializedNetwork(arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil)
|> map { network -> AccountResult in
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
}
case let authorizedState as AuthorizedAccountState:
return postbox.transaction { transaction -> String? in
return (transaction.getPeer(authorizedState.peerId) as? TelegramUser)?.phone
}
|> mapToSignal { phoneNumber in
return initializedNetwork(arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber)
|> map { network -> AccountResult in
return .authorized(Account(id: id, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, postbox: postbox, network: network, networkArguments: networkArguments, peerId: authorizedState.peerId, auxiliaryMethods: auxiliaryMethods))
}
}
case _:
assertionFailure("Unexpected accountState \(accountState)")
}
}
return initializedNetwork(arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil)
|> map { network -> AccountResult in
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
}
}
}
}
}
}
public enum TwoStepPasswordDerivation {
case unknown
case sha256_sha256_PBKDF2_HMAC_sha512_sha256_srp(salt1: Data, salt2: Data, iterations: Int32, g: Int32, p: Data)
fileprivate init(_ apiAlgo: Api.PasswordKdfAlgo) {
switch apiAlgo {
case .passwordKdfAlgoUnknown:
self = .unknown
case let .passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(salt1, salt2, g, p):
self = .sha256_sha256_PBKDF2_HMAC_sha512_sha256_srp(salt1: salt1.makeData(), salt2: salt2.makeData(), iterations: 100000, g: g, p: p.makeData())
}
}
var apiAlgo: Api.PasswordKdfAlgo {
switch self {
case .unknown:
return .passwordKdfAlgoUnknown
case let .sha256_sha256_PBKDF2_HMAC_sha512_sha256_srp(salt1, salt2, iterations, g, p):
precondition(iterations == 100000)
return .passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(salt1: Buffer(data: salt1), salt2: Buffer(data: salt2), g: g, p: Buffer(data: p))
}
}
}
public enum TwoStepSecurePasswordDerivation {
case unknown
case sha512(salt: Data)
case PBKDF2_HMAC_sha512(salt: Data, iterations: Int32)
init(_ apiAlgo: Api.SecurePasswordKdfAlgo) {
switch apiAlgo {
case .securePasswordKdfAlgoUnknown:
self = .unknown
case let .securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(salt):
self = .PBKDF2_HMAC_sha512(salt: salt.makeData(), iterations: 100000)
case let .securePasswordKdfAlgoSHA512(salt):
self = .sha512(salt: salt.makeData())
}
}
var apiAlgo: Api.SecurePasswordKdfAlgo {
switch self {
case .unknown:
return .securePasswordKdfAlgoUnknown
case let .PBKDF2_HMAC_sha512(salt, iterations):
precondition(iterations == 100000)
return .securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(salt: Buffer(data: salt))
case let .sha512(salt):
return .securePasswordKdfAlgoSHA512(salt: Buffer(data: salt))
}
}
}
public struct TwoStepSRPSessionData {
public let id: Int64
public let B: Data
}
public struct TwoStepAuthData {
public let nextPasswordDerivation: TwoStepPasswordDerivation
public let currentPasswordDerivation: TwoStepPasswordDerivation?
public let srpSessionData: TwoStepSRPSessionData?
public let hasRecovery: Bool
public let hasSecretValues: Bool
public let currentHint: String?
public let unconfirmedEmailPattern: String?
public let secretRandom: Data
public let nextSecurePasswordDerivation: TwoStepSecurePasswordDerivation
}
public func twoStepAuthData(_ network: Network) -> Signal<TwoStepAuthData, MTRpcError> {
return network.request(Api.functions.account.getPassword())
|> map { config -> TwoStepAuthData in
switch config {
case let .password(flags, currentAlgo, srpB, srpId, hint, emailUnconfirmedPattern, newAlgo, newSecureAlgo, secureRandom):
let hasRecovery = (flags & (1 << 0)) != 0
let hasSecureValues = (flags & (1 << 1)) != 0
let currentDerivation = currentAlgo.flatMap(TwoStepPasswordDerivation.init)
let nextDerivation = TwoStepPasswordDerivation(newAlgo)
let nextSecureDerivation = TwoStepSecurePasswordDerivation(newSecureAlgo)
switch nextSecureDerivation {
case .unknown:
break
case .PBKDF2_HMAC_sha512:
break
case .sha512:
preconditionFailure()
}
var srpSessionData: TwoStepSRPSessionData?
if let srpB = srpB, let srpId = srpId {
srpSessionData = TwoStepSRPSessionData(id: srpId, B: srpB.makeData())
}
return TwoStepAuthData(nextPasswordDerivation: nextDerivation, currentPasswordDerivation: currentDerivation, srpSessionData: srpSessionData, hasRecovery: hasRecovery, hasSecretValues: hasSecureValues, currentHint: hint, unconfirmedEmailPattern: emailUnconfirmedPattern, secretRandom: secureRandom.makeData(), nextSecurePasswordDerivation: nextSecureDerivation)
}
}
}
public func hexString(_ data: Data) -> String {
let hexString = NSMutableString()
data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
for i in 0 ..< data.count {
hexString.appendFormat("%02x", UInt(bytes.advanced(by: i).pointee))
}
}
return hexString as String
}
public func dataWithHexString(_ string: String) -> Data {
var hex = string
if hex.count % 2 != 0 {
return Data()
}
var data = Data()
while hex.count > 0 {
let subIndex = hex.index(hex.startIndex, offsetBy: 2)
let c = String(hex[..<subIndex])
hex = String(hex[subIndex...])
var ch: UInt32 = 0
if !Scanner(string: c).scanHexInt32(&ch) {
return Data()
}
var char = UInt8(ch)
data.append(&char, count: 1)
}
return data
}
func sha1Digest(_ data : Data) -> Data {
return data.withUnsafeBytes { bytes -> Data in
return CryptoSHA1(bytes, Int32(data.count))
}
}
func sha256Digest(_ data : Data) -> Data {
return data.withUnsafeBytes { bytes -> Data in
return CryptoSHA256(bytes, Int32(data.count))
}
}
func sha512Digest(_ data : Data) -> Data {
return data.withUnsafeBytes { bytes -> Data in
return CryptoSHA512(bytes, Int32(data.count))
}
}
func passwordUpdateKDF(password: String, derivation: TwoStepPasswordDerivation) -> (Data, TwoStepPasswordDerivation)? {
guard let passwordData = password.data(using: .utf8, allowLossyConversion: true) else {
return nil
}
switch derivation {
case .unknown:
return nil
case let .sha256_sha256_PBKDF2_HMAC_sha512_sha256_srp(salt1, salt2, iterations, gValue, p):
var nextSalt1 = salt1
var randomSalt1 = Data()
randomSalt1.count = 32
randomSalt1.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> Void in
arc4random_buf(bytes, 32)
}
nextSalt1.append(randomSalt1)
let nextSalt2 = salt2
var g = Data(count: 4)
g.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> Void in
var gValue = gValue
withUnsafeBytes(of: &gValue, { (sourceBuffer: UnsafeRawBufferPointer) -> Void in
let sourceBytes = sourceBuffer.bindMemory(to: Int8.self).baseAddress!
for i in 0 ..< 4 {
bytes.advanced(by: i).pointee = sourceBytes.advanced(by: 4 - i - 1).pointee
}
})
}
let pbkdfInnerData = sha256Digest(nextSalt2 + sha256Digest(nextSalt1 + passwordData + nextSalt1) + nextSalt2)
guard let pbkdfResult = MTPBKDF2(pbkdfInnerData, nextSalt1, iterations) else {
return nil
}
let x = sha256Digest(nextSalt2 + pbkdfResult + nextSalt2)
let gx = MTExp(g, x, p)!
return (gx, .sha256_sha256_PBKDF2_HMAC_sha512_sha256_srp(salt1: nextSalt1, salt2: nextSalt2, iterations: iterations, g: gValue, p: p))
}
}
struct PasswordKDFResult {
let id: Int64
let A: Data
let M1: Data
}
private func paddedToLength(what: Data, to: Data) -> Data {
if what.count < to.count {
var what = what
for _ in 0 ..< to.count - what.count {
what.insert(0, at: 0)
}
return what
} else {
return what
}
}
private func paddedXor(_ a: Data, _ b: Data) -> Data {
let count = max(a.count, b.count)
var a = a
var b = b
while a.count < count {
a.insert(0, at: 0)
}
while b.count < count {
b.insert(0, at: 0)
}
a.withUnsafeMutableBytes { (aBytes: UnsafeMutablePointer<UInt8>) -> Void in
b.withUnsafeBytes { (bBytes: UnsafePointer<UInt8>) -> Void in
for i in 0 ..< count {
aBytes.advanced(by: i).pointee = aBytes.advanced(by: i).pointee ^ bBytes.advanced(by: i).pointee
}
}
}
return a
}
func passwordKDF(password: String, derivation: TwoStepPasswordDerivation, srpSessionData: TwoStepSRPSessionData) -> PasswordKDFResult? {
guard let passwordData = password.data(using: .utf8, allowLossyConversion: true) else {
return nil
}
switch derivation {
case .unknown:
return nil
case let .sha256_sha256_PBKDF2_HMAC_sha512_sha256_srp(salt1, salt2, iterations, gValue, p):
var a = Data(count: p.count)
let aLength = a.count
a.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
let _ = SecRandomCopyBytes(nil, aLength, bytes)
}
var g = Data(count: 4)
g.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> Void in
var gValue = gValue
withUnsafeBytes(of: &gValue, { (sourceBuffer: UnsafeRawBufferPointer) -> Void in
let sourceBytes = sourceBuffer.bindMemory(to: Int8.self).baseAddress!
for i in 0 ..< 4 {
bytes.advanced(by: i).pointee = sourceBytes.advanced(by: 4 - i - 1).pointee
}
})
}
if !MTCheckIsSafeB(srpSessionData.B, p) {
return nil
}
let B = paddedToLength(what: srpSessionData.B, to: p)
let A = paddedToLength(what: MTExp(g, a, p)!, to: p)
let u = sha256Digest(A + B)
if MTIsZero(u) {
return nil
}
let pbkdfInnerData = sha256Digest(salt2 + sha256Digest(salt1 + passwordData + salt1) + salt2)
guard let pbkdfResult = MTPBKDF2(pbkdfInnerData, salt1, iterations) else {
return nil
}
let x = sha256Digest(salt2 + pbkdfResult + salt2)
let gx = MTExp(g, x, p)!
let k = sha256Digest(p + paddedToLength(what: g, to: p))
let s1 = MTModSub(B, MTModMul(k, gx, p)!, p)!
if !MTCheckIsSafeGAOrB(s1, p) {
return nil
}
let s2 = MTAdd(a, MTMul(u, x)!)!
let S = MTExp(s1, s2, p)!
let K = sha256Digest(paddedToLength(what: S, to: p))
let m1 = paddedXor(sha256Digest(p), sha256Digest(paddedToLength(what: g, to: p)))
let m2 = sha256Digest(salt1)
let m3 = sha256Digest(salt2)
let M = sha256Digest(m1 + m2 + m3 + A + B + K)
return PasswordKDFResult(id: srpSessionData.id, A: A, M1: M)
}
}
func securePasswordUpdateKDF(password: String, derivation: TwoStepSecurePasswordDerivation) -> (Data, TwoStepSecurePasswordDerivation)? {
guard let passwordData = password.data(using: .utf8, allowLossyConversion: true) else {
return nil
}
switch derivation {
case .unknown:
return nil
case let .sha512(salt):
var nextSalt = salt
var randomSalt = Data()
randomSalt.count = 32
randomSalt.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> Void in
arc4random_buf(bytes, 32)
}
nextSalt.append(randomSalt)
var data = Data()
data.append(nextSalt)
data.append(passwordData)
data.append(nextSalt)
return (sha512Digest(data), .sha512(salt: nextSalt))
case let .PBKDF2_HMAC_sha512(salt, iterations):
var nextSalt = salt
var randomSalt = Data()
randomSalt.count = 32
randomSalt.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> Void in
arc4random_buf(bytes, 32)
}
nextSalt.append(randomSalt)
guard let passwordHash = MTPBKDF2(passwordData, nextSalt, iterations) else {
return nil
}
return (passwordHash, .PBKDF2_HMAC_sha512(salt: nextSalt, iterations: iterations))
}
}
func securePasswordKDF(password: String, derivation: TwoStepSecurePasswordDerivation) -> Data? {
guard let passwordData = password.data(using: .utf8, allowLossyConversion: true) else {
return nil
}
switch derivation {
case .unknown:
return nil
case let .sha512(salt):
var data = Data()
data.append(salt)
data.append(passwordData)
data.append(salt)
return sha512Digest(data)
case let .PBKDF2_HMAC_sha512(salt, iterations):
guard let passwordHash = MTPBKDF2(passwordData, salt, iterations) else {
return nil
}
return passwordHash
}
}
func verifyPassword(_ account: UnauthorizedAccount, password: String) -> Signal<Api.auth.Authorization, MTRpcError> {
return twoStepAuthData(account.network)
|> mapToSignal { authData -> Signal<Api.auth.Authorization, MTRpcError> in
guard let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData else {
return .fail(MTRpcError(errorCode: 400, errorDescription: "INTERNAL_NO_PASSWORD"))
}
let kdfResult = passwordKDF(password: password, derivation: currentPasswordDerivation, srpSessionData: srpSessionData)
if let kdfResult = kdfResult {
return account.network.request(Api.functions.auth.checkPassword(password: .inputCheckPasswordSRP(srpId: kdfResult.id, A: Buffer(data: kdfResult.A), M1: Buffer(data: kdfResult.M1))), automaticFloodWait: false)
} else {
return .fail(MTRpcError(errorCode: 400, errorDescription: "KDF_ERROR"))
}
}
}
public enum AccountServiceTaskMasterMode {
case now
case always
case never
}
public struct AccountNetworkProxyState: Equatable {
public let address: String
public let hasConnectionIssues: Bool
}
public enum AccountNetworkState: Equatable {
case waitingForNetwork
case connecting(proxy: AccountNetworkProxyState?)
case updating(proxy: AccountNetworkProxyState?)
case online(proxy: AccountNetworkProxyState?)
}
public final class AccountAuxiliaryMethods {
public let updatePeerChatInputState: (PeerChatInterfaceState?, SynchronizeableChatInputState?) -> PeerChatInterfaceState?
public let fetchResource: (Account, MediaResource, Signal<[(Range<Int>, MediaBoxFetchPriority)], NoError>, MediaResourceFetchParameters?) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError>?
public let fetchResourceMediaReferenceHash: (MediaResource) -> Signal<Data?, NoError>
public init(updatePeerChatInputState: @escaping (PeerChatInterfaceState?, SynchronizeableChatInputState?) -> PeerChatInterfaceState?, fetchResource: @escaping (Account, MediaResource, Signal<[(Range<Int>, MediaBoxFetchPriority)], NoError>, MediaResourceFetchParameters?) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError>?, fetchResourceMediaReferenceHash: @escaping (MediaResource) -> Signal<Data?, NoError>) {
self.updatePeerChatInputState = updatePeerChatInputState
self.fetchResource = fetchResource
self.fetchResourceMediaReferenceHash = fetchResourceMediaReferenceHash
}
}
public struct AccountRunningImportantTasks: OptionSet {
public var rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public static let other = AccountRunningImportantTasks(rawValue: 1 << 0)
public static let pendingMessages = AccountRunningImportantTasks(rawValue: 1 << 1)
}
private struct MasterNotificationKey {
let id: Data
let data: Data
}
private func masterNotificationsKey(account: Account, ignoreDisabled: Bool) -> Signal<MasterNotificationKey, NoError> {
if let key = account.masterNotificationKey.with({ $0 }) {
return .single(key)
}
return account.postbox.transaction(ignoreDisabled: ignoreDisabled, { transaction -> MasterNotificationKey in
if let value = transaction.keychainEntryForKey("master-notification-secret"), !value.isEmpty {
let authKeyHash = sha1Digest(value)
let authKeyId = authKeyHash.subdata(in: authKeyHash.count - 8 ..< authKeyHash.count)
let keyData = MasterNotificationKey(id: authKeyId, data: value)
let _ = account.masterNotificationKey.swap(keyData)
return keyData
} else {
var secretData = Data(count: 256)
let secretDataCount = secretData.count
if !secretData.withUnsafeMutableBytes({ (bytes: UnsafeMutablePointer<Int8>) -> Bool in
let copyResult = SecRandomCopyBytes(nil, secretDataCount, bytes)
return copyResult == errSecSuccess
}) {
assertionFailure()
}
transaction.setKeychainEntry(secretData, forKey: "master-notification-secret")
let authKeyHash = sha1Digest(secretData)
let authKeyId = authKeyHash.subdata(in: authKeyHash.count - 8 ..< authKeyHash.count)
let keyData = MasterNotificationKey(id: authKeyId, data: secretData)
let _ = account.masterNotificationKey.swap(keyData)
return keyData
}
})
}
public func decryptedNotificationPayload(account: Account, data: Data) -> Signal<Data?, NoError> {
return masterNotificationsKey(account: account, ignoreDisabled: true)
|> map { secret -> Data? in
if data.subdata(in: 0 ..< 8) != secret.id {
return nil
}
let x = 8
let msgKey = data.subdata(in: 8 ..< (8 + 16))
let rawData = data.subdata(in: (8 + 16) ..< data.count)
let sha256_a = sha256Digest(msgKey + secret.data.subdata(in: x ..< (x + 36)))
let sha256_b = sha256Digest(secret.data.subdata(in: (40 + x) ..< (40 + x + 36)) + msgKey)
let aesKey = sha256_a.subdata(in: 0 ..< 8) + sha256_b.subdata(in: 8 ..< (8 + 16)) + sha256_a.subdata(in: 24 ..< (24 + 8))
let aesIv = sha256_b.subdata(in: 0 ..< 8) + sha256_a.subdata(in: 8 ..< (8 + 16)) + sha256_b.subdata(in: 24 ..< (24 + 8))
guard let data = MTAesDecrypt(rawData, aesKey, aesIv), data.count > 4 else {
return nil
}
var dataLength: Int32 = 0
data.withUnsafeBytes { (bytes: UnsafePointer<Int8>) -> Void in
memcpy(&dataLength, bytes, 4)
}
if dataLength < 0 || dataLength > data.count - 4 {
return nil
}
let checkMsgKeyLarge = sha256Digest(secret.data.subdata(in: (88 + x) ..< (88 + x + 32)) + data)
let checkMsgKey = checkMsgKeyLarge.subdata(in: 8 ..< (8 + 16))
if checkMsgKey != msgKey {
return nil
}
return data.subdata(in: 4 ..< (4 + Int(dataLength)))
}
}
public class Account {
public let id: AccountRecordId
public let basePath: String
public let testingEnvironment: Bool
public let postbox: Postbox
public let network: Network
let networkArguments: NetworkInitializationArguments
public let peerId: PeerId
public let auxiliaryMethods: AccountAuxiliaryMethods
private let serviceQueue = Queue()
public private(set) var stateManager: AccountStateManager!
private(set) var contactSyncManager: ContactSyncManager!
public private(set) var callSessionManager: CallSessionManager!
public private(set) var viewTracker: AccountViewTracker!
public private(set) var pendingMessageManager: PendingMessageManager!
public private(set) var messageMediaPreuploadManager: MessageMediaPreuploadManager!
private(set) var mediaReferenceRevalidationContext: MediaReferenceRevalidationContext!
private var peerInputActivityManager: PeerInputActivityManager!
private var localInputActivityManager: PeerInputActivityManager!
private var accountPresenceManager: AccountPresenceManager!
private var notificationAutolockReportManager: NotificationAutolockReportManager!
fileprivate let managedContactsDisposable = MetaDisposable()
fileprivate let managedStickerPacksDisposable = MetaDisposable()
private let becomeMasterDisposable = MetaDisposable()
private let managedServiceViewsDisposable = MetaDisposable()
private let managedOperationsDisposable = DisposableSet()
private var storageSettingsDisposable: Disposable?
public let graphicsThreadPool = ThreadPool(threadCount: 3, threadPriority: 0.1)
public var applicationContext: Any?
public let notificationToken = Promise<Data>()
public let voipToken = Promise<Data>()
private var notificationTokensVersionValue = 0 {
didSet {
self.notificationTokensVersionPromise.set(self.notificationTokensVersionValue)
}
}
func updateNotificationTokensVersion() {
self.notificationTokensVersionValue += 1
}
private let notificationTokensVersionPromise = ValuePromise<Int>(0)
private let notificationTokenDisposable = MetaDisposable()
private let voipTokenDisposable = MetaDisposable()
public let importableContacts = Promise<[DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData]>()
public let shouldBeServiceTaskMaster = Promise<AccountServiceTaskMasterMode>()
public let shouldKeepOnlinePresence = Promise<Bool>()
public let autolockReportDeadline = Promise<Int32?>()
public let shouldExplicitelyKeepWorkerConnections = Promise<Bool>(false)
private let networkStateValue = Promise<AccountNetworkState>(.waitingForNetwork)
public var networkState: Signal<AccountNetworkState, NoError> {
return self.networkStateValue.get()
}
private let networkTypeValue = Promise<NetworkType>()
public var networkType: Signal<NetworkType, NoError> {
return self.networkTypeValue.get()
}
private let _loggedOut = ValuePromise<Bool>(false, ignoreRepeated: true)
public var loggedOut: Signal<Bool, NoError> {
return self._loggedOut.get()
}
private let _importantTasksRunning = ValuePromise<AccountRunningImportantTasks>([], ignoreRepeated: true)
public var importantTasksRunning: Signal<AccountRunningImportantTasks, NoError> {
return self._importantTasksRunning.get()
}
fileprivate let masterNotificationKey = Atomic<MasterNotificationKey?>(value: nil)
var transformOutgoingMessageMedia: TransformOutgoingMessageMedia?
public init(id: AccountRecordId, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, networkArguments: NetworkInitializationArguments, peerId: PeerId, auxiliaryMethods: AccountAuxiliaryMethods) {
self.id = id
self.basePath = basePath
self.testingEnvironment = testingEnvironment
self.postbox = postbox
self.network = network
self.networkArguments = networkArguments
self.peerId = peerId
self.auxiliaryMethods = auxiliaryMethods
self.peerInputActivityManager = PeerInputActivityManager()
self.stateManager = AccountStateManager(account: self, peerInputActivityManager: self.peerInputActivityManager, auxiliaryMethods: auxiliaryMethods)
self.contactSyncManager = ContactSyncManager(postbox: postbox, network: network, accountPeerId: peerId, stateManager: self.stateManager)
self.callSessionManager = CallSessionManager(postbox: postbox, network: network, addUpdates: { [weak self] updates in
self?.stateManager.addUpdates(updates)
})
self.localInputActivityManager = PeerInputActivityManager()
self.accountPresenceManager = AccountPresenceManager(shouldKeepOnlinePresence: self.shouldKeepOnlinePresence.get(), network: network)
let _ = (postbox.transaction { transaction -> Void in
transaction.updatePeerPresencesInternal([peerId: TelegramUserPresence(status: .present(until: Int32.max - 1))])
}).start()
self.notificationAutolockReportManager = NotificationAutolockReportManager(deadline: self.autolockReportDeadline.get(), network: network)
self.autolockReportDeadline.set(
postbox.combinedView(keys: [.accessChallengeData])
|> map { view -> Int32? in
guard let dataView = view.views[.accessChallengeData] as? AccessChallengeDataView else {
return nil
}
guard let autolockDeadline = dataView.data.autolockDeadline else {
return nil
}
return autolockDeadline
}
|> distinctUntilChanged
)
self.viewTracker = AccountViewTracker(account: self)
self.messageMediaPreuploadManager = MessageMediaPreuploadManager()
self.mediaReferenceRevalidationContext = MediaReferenceRevalidationContext()
self.pendingMessageManager = PendingMessageManager(network: network, postbox: postbox, accountPeerId: peerId, auxiliaryMethods: auxiliaryMethods, stateManager: self.stateManager, localInputActivityManager: self.localInputActivityManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, revalidationContext: self.mediaReferenceRevalidationContext)
self.network.loggedOut = { [weak self] in
Logger.shared.log("Account", "network logged out")
if let strongSelf = self {
strongSelf._loggedOut.set(true)
strongSelf.callSessionManager.dropAll()
}
}
let previousNetworkStatus = Atomic<Bool?>(value: nil)
let networkStateQueue = Queue()
let delayNetworkStatus = self.shouldBeServiceTaskMaster.get()
|> map { mode -> Bool in
switch mode {
case .now, .always:
return true
case .never:
return false
}
}
|> distinctUntilChanged
|> deliverOn(networkStateQueue)
|> mapToSignal { value -> Signal<Bool, NoError> in
var shouldDelay = false
let _ = previousNetworkStatus.modify { previous in
if let previous = previous {
if !previous && value {
shouldDelay = true
}
} else {
shouldDelay = true
}
return value
}
if shouldDelay {
let delayedFalse = Signal<Bool, NoError>.single(false)
|> delay(3.0, queue: networkStateQueue)
return .single(true)
|> then(delayedFalse)
} else {
return .single(!value)
}
}
let networkStateSignal = combineLatest(self.stateManager.isUpdating |> deliverOn(networkStateQueue), network.connectionStatus |> deliverOn(networkStateQueue)/*, delayNetworkStatus |> deliverOn(networkStateQueue)*/)
|> map { isUpdating, connectionStatus/*, delayNetworkStatus*/ -> AccountNetworkState in
/*if delayNetworkStatus {
return .online(proxy: nil)
}*/
switch connectionStatus {
case .waitingForNetwork:
return .waitingForNetwork
case let .connecting(proxyAddress, proxyHasConnectionIssues):
var proxyState: AccountNetworkProxyState?
if let proxyAddress = proxyAddress {
proxyState = AccountNetworkProxyState(address: proxyAddress, hasConnectionIssues: proxyHasConnectionIssues)
}
return .connecting(proxy: proxyState)
case let .updating(proxyAddress):
var proxyState: AccountNetworkProxyState?
if let proxyAddress = proxyAddress {
proxyState = AccountNetworkProxyState(address: proxyAddress, hasConnectionIssues: false)
}
return .updating(proxy: proxyState)
case let .online(proxyAddress):
var proxyState: AccountNetworkProxyState?
if let proxyAddress = proxyAddress {
proxyState = AccountNetworkProxyState(address: proxyAddress, hasConnectionIssues: false)
}
if isUpdating {
return .updating(proxy: proxyState)
} else {
return .online(proxy: proxyState)
}
}
}
self.networkStateValue.set(networkStateSignal
|> distinctUntilChanged)
self.networkTypeValue.set(currentNetworkType())
let appliedNotificationToken = combineLatest(self.notificationToken.get(), self.notificationTokensVersionPromise.get())
|> distinctUntilChanged(isEqual: { $0 == $1 })
|> mapToSignal { token, _ -> Signal<Void, NoError> in
var tokenString = ""
token.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
for i in 0 ..< token.count {
let byte = bytes.advanced(by: i).pointee
tokenString = tokenString.appendingFormat("%02x", Int32(byte))
}
}
var appSandbox: Api.Bool = .boolFalse
#if DEBUG
appSandbox = .boolTrue
#endif
return masterNotificationsKey(account: self, ignoreDisabled: false)
|> mapToSignal { secret -> Signal<Void, NoError> in
return network.request(Api.functions.account.registerDevice(tokenType: 1, token: tokenString, appSandbox: appSandbox, secret: Buffer(/*data: secret.data*/), otherUids: []))
|> retryRequest
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}
}
self.notificationTokenDisposable.set(appliedNotificationToken.start())
let appliedVoipToken = combineLatest(self.voipToken.get(), self.notificationTokensVersionPromise.get())
|> distinctUntilChanged(isEqual: { $0 == $1 })
|> mapToSignal { token, _ -> Signal<Void, NoError> in
var tokenString = ""
token.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
for i in 0 ..< token.count {
let byte = bytes.advanced(by: i).pointee
tokenString = tokenString.appendingFormat("%02x", Int32(byte))
}
}
var appSandbox: Api.Bool = .boolFalse
#if DEBUG
appSandbox = .boolTrue
#endif
return masterNotificationsKey(account: self, ignoreDisabled: false)
|> mapToSignal { secret -> Signal<Void, NoError> in
return network.request(Api.functions.account.registerDevice(tokenType: 9, token: tokenString, appSandbox: appSandbox, secret: Buffer(data: secret.data), otherUids: []))
|> retryRequest
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}
}
self.voipTokenDisposable.set(appliedVoipToken.start())
let serviceTasksMasterBecomeMaster = shouldBeServiceTaskMaster.get()
|> distinctUntilChanged
|> deliverOn(self.serviceQueue)
self.becomeMasterDisposable.set(serviceTasksMasterBecomeMaster.start(next: { [weak self] value in
if let strongSelf = self, (value == .now || value == .always) {
strongSelf.postbox.becomeMasterClient()
}
}))
let shouldBeMaster = combineLatest(shouldBeServiceTaskMaster.get(), postbox.isMasterClient())
|> map { [weak self] shouldBeMaster, isMaster -> Bool in
if shouldBeMaster == .always && !isMaster {
self?.postbox.becomeMasterClient()
}
return (shouldBeMaster == .now || shouldBeMaster == .always) && isMaster
}
|> distinctUntilChanged
self.network.shouldKeepConnection.set(shouldBeMaster)
self.network.shouldExplicitelyKeepWorkerConnections.set(self.shouldExplicitelyKeepWorkerConnections.get())
let serviceTasksMaster = shouldBeMaster
|> deliverOn(self.serviceQueue)
|> mapToSignal { [weak self] value -> Signal<Void, NoError> in
if let strongSelf = self, value {
Logger.shared.log("Account", "Became master")
return managedServiceViews(accountPeerId: peerId, network: strongSelf.network, postbox: strongSelf.postbox, stateManager: strongSelf.stateManager, pendingMessageManager: strongSelf.pendingMessageManager)
} else {
Logger.shared.log("Account", "Resigned master")
return .never()
}
}
self.managedServiceViewsDisposable.set(serviceTasksMaster.start())
let pendingMessageManager = self.pendingMessageManager
self.managedOperationsDisposable.add(postbox.unsentMessageIdsView().start(next: { [weak pendingMessageManager] view in
pendingMessageManager?.updatePendingMessageIds(view.ids)
}))
self.managedOperationsDisposable.add(managedSecretChatOutgoingOperations(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedCloudChatRemoveMessagesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
self.managedOperationsDisposable.add(managedAutoremoveMessageOperations(postbox: self.postbox).start())
self.managedOperationsDisposable.add(managedGlobalNotificationSettings(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedSynchronizePinnedChatsOperations(postbox: self.postbox, network: self.network, accountPeerId: self.peerId, stateManager: self.stateManager).start())
self.managedOperationsDisposable.add(managedSynchronizeGroupedPeersOperations(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedSynchronizeInstalledStickerPacksOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager, namespace: .stickers).start())
self.managedOperationsDisposable.add(managedSynchronizeInstalledStickerPacksOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager, namespace: .masks).start())
self.managedOperationsDisposable.add(managedSynchronizeMarkFeaturedStickerPacksAsSeenOperations(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedRecentStickers(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedSynchronizeSavedGifsOperations(postbox: self.postbox, network: self.network, revalidationContext: self.mediaReferenceRevalidationContext).start())
self.managedOperationsDisposable.add(managedSynchronizeSavedStickersOperations(postbox: self.postbox, network: self.network, revalidationContext: self.mediaReferenceRevalidationContext).start())
self.managedOperationsDisposable.add(managedRecentlyUsedInlineBots(postbox: self.postbox, network: self.network, accountPeerId: peerId).start())
self.managedOperationsDisposable.add(managedLocalTypingActivities(activities: self.localInputActivityManager.allActivities(), postbox: self.postbox, network: self.network, accountPeerId: self.peerId).start())
self.managedOperationsDisposable.add(managedSynchronizeConsumeMessageContentOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
self.managedOperationsDisposable.add(managedConsumePersonalMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
self.managedOperationsDisposable.add(managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
let importantBackgroundOperations: [Signal<AccountRunningImportantTasks, NoError>] = [
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] },
self.pendingMessageManager.hasPendingMessages |> map { $0 ? AccountRunningImportantTasks.pendingMessages : [] },
self.accountPresenceManager.isPerformingUpdate() |> map { $0 ? AccountRunningImportantTasks.other : [] },
self.notificationAutolockReportManager.isPerformingUpdate() |> map { $0 ? AccountRunningImportantTasks.other : [] }
]
let importantBackgroundOperationsRunning = combineLatest(importantBackgroundOperations)
|> deliverOn(Queue())
|> map { values -> AccountRunningImportantTasks in
var result: AccountRunningImportantTasks = []
for value in values {
result.formUnion(value)
}
return result
}
self.managedOperationsDisposable.add(importantBackgroundOperationsRunning.start(next: { [weak self] value in
if let strongSelf = self {
strongSelf._importantTasksRunning.set(value)
}
}))
self.managedOperationsDisposable.add(managedConfigurationUpdates(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedVoipConfigurationUpdates(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedTermsOfServiceUpdates(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
self.managedOperationsDisposable.add(managedAppChangelog(postbox: self.postbox, network: self.network, stateManager: self.stateManager, appVersion: self.networkArguments.appVersion).start())
self.managedOperationsDisposable.add(managedProxyInfoUpdates(postbox: self.postbox, network: self.network, viewTracker: self.viewTracker).start())
self.managedOperationsDisposable.add(managedLocalizationUpdatesOperations(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedPendingPeerNotificationSettings(postbox: self.postbox, network: self.network).start())
let storagePreferencesKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.cacheStorageSettings]))
let mediaBox = postbox.mediaBox
self.storageSettingsDisposable = self.postbox.combinedView(keys: [storagePreferencesKey]).start(next: { [weak mediaBox] view in
guard let mediaBox = mediaBox else {
return
}
let settings: CacheStorageSettings = ((view.views[storagePreferencesKey] as? PreferencesView)?.values[PreferencesKeys.cacheStorageSettings] as? CacheStorageSettings) ?? CacheStorageSettings.defaultSettings
mediaBox.setMaxStoreTime(settings.defaultCacheStorageTimeout)
})
}
deinit {
self.managedContactsDisposable.dispose()
self.managedStickerPacksDisposable.dispose()
self.notificationTokenDisposable.dispose()
self.voipTokenDisposable.dispose()
self.managedServiceViewsDisposable.dispose()
self.managedOperationsDisposable.dispose()
self.storageSettingsDisposable?.dispose()
}
public func resetStateManagement() {
self.stateManager.reset()
self.contactSyncManager.beginSync(importableContacts: self.importableContacts.get())
self.managedStickerPacksDisposable.set(manageStickerPacks(network: self.network, postbox: self.postbox).start())
}
public func peerInputActivities(peerId: PeerId) -> Signal<[(PeerId, PeerInputActivity)], NoError> {
return self.peerInputActivityManager.activities(peerId: peerId)
|> map { activities in
return activities.map({ ($0.0, $0.1.activity) })
}
}
public func allPeerInputActivities() -> Signal<[PeerId: [PeerId: PeerInputActivity]], NoError> {
return self.peerInputActivityManager.allActivities()
|> map { activities in
var result: [PeerId: [PeerId: PeerInputActivity]] = [:]
for (chatPeerId, chatActivities) in activities {
result[chatPeerId] = chatActivities.mapValues({ $0.activity })
}
return result
}
}
public func updateLocalInputActivity(peerId: PeerId, activity: PeerInputActivity, isPresent: Bool) {
self.localInputActivityManager.transaction { manager in
if isPresent {
manager.addActivity(chatPeerId: peerId, peerId: self.peerId, activity: activity)
} else {
manager.removeActivity(chatPeerId: peerId, peerId: self.peerId, activity: activity)
}
}
}
}
public func accountNetworkUsageStats(account: Account, reset: ResetNetworkUsageStats) -> Signal<NetworkUsageStats, NoError> {
return networkUsageStats(basePath: account.basePath, reset: reset)
}
public func updateAccountNetworkUsageStats(account: Account, category: MediaResourceStatsCategory, delta: NetworkUsageStatsConnectionsEntry) {
updateNetworkUsageStats(basePath: account.basePath, category: category, delta: delta)
}
public typealias FetchCachedResourceRepresentation = (_ account: Account, _ resource: MediaResource, _ representation: CachedMediaResourceRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError>
public typealias TransformOutgoingMessageMedia = (_ postbox: Postbox, _ network: Network, _ media: AnyMediaReference, _ userInteractive: Bool) -> Signal<AnyMediaReference?, NoError>
public func setupAccount(_ account: Account, fetchCachedResourceRepresentation: FetchCachedResourceRepresentation? = nil, transformOutgoingMessageMedia: TransformOutgoingMessageMedia? = nil, preFetchedResourcePath: @escaping (MediaResource) -> String? = { _ in return nil }) {
account.postbox.mediaBox.preFetchedResourcePath = preFetchedResourcePath
account.postbox.mediaBox.fetchResource = { [weak account] resource, intervals, parameters -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> in
if let strongAccount = account {
if let result = fetchResource(account: strongAccount, resource: resource, intervals: intervals, parameters: parameters) {
return result
} else if let result = strongAccount.auxiliaryMethods.fetchResource(strongAccount, resource, intervals, parameters) {
return result
} else {
return .never()
}
} else {
return .never()
}
}
account.postbox.mediaBox.fetchCachedResourceRepresentation = { [weak account] resource, representation in
if let strongAccount = account, let fetchCachedResourceRepresentation = fetchCachedResourceRepresentation {
return fetchCachedResourceRepresentation(strongAccount, resource, representation)
} else {
return .never()
}
}
account.transformOutgoingMessageMedia = transformOutgoingMessageMedia
account.pendingMessageManager.transformOutgoingMessageMedia = transformOutgoingMessageMedia
}