Initial QR login API [skip ci]

This commit is contained in:
Ali 2019-11-21 18:49:32 +04:00
parent 94b880c4dc
commit 40b0b3479e
21 changed files with 952 additions and 46 deletions

View File

@ -38,7 +38,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
self.impl = NotificationViewControllerImpl(initializationData: NotificationViewControllerInitializationData(appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), setPreferredContentSize: { [weak self] size in
self.impl = NotificationViewControllerImpl(initializationData: NotificationViewControllerInitializationData(appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), setPreferredContentSize: { [weak self] size in
self?.preferredContentSize = size
})
}

View File

@ -45,7 +45,7 @@ class ShareRootController: UIViewController {
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
self.impl = ShareRootControllerImpl(initializationData: ShareRootControllerInitializationData(appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), getExtensionContext: { [weak self] in
self.impl = ShareRootControllerImpl(initializationData: ShareRootControllerInitializationData(appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), getExtensionContext: { [weak self] in
return self?.extensionContext
})
}

View File

@ -69,6 +69,7 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
let buildConfig = BuildConfig(baseAppBundleId: baseAppBundleId)
let apiId: Int32 = buildConfig.apiId
let apiHash: String = buildConfig.apiHash
let languagesCategory = "ios"
let appGroupName = "group.\(baseAppBundleId)"
@ -100,7 +101,7 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!)
account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters)
account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters)
|> mapToSignal { account -> Signal<Account?, NoError> in
if let account = account {
switch account {

View File

@ -0,0 +1,23 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "AuthTransferUI",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/TelegramCore:TelegramCore#shared",
"//submodules/SyncCore:SyncCore#shared",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/Display:Display#shared",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/AccountContext:AccountContext",
"//submodules/QrCode:QrCode",
"//submodules/Camera:Camera",
"//submodules/GlassButtonNode:GlassButtonNode",
"//submodules/AlertUI:AlertUI",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
],
)

View File

@ -0,0 +1,349 @@
import Foundation
import UIKit
import AccountContext
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Camera
import GlassButtonNode
import CoreImage
import AlertUI
import TelegramPresentationData
import TelegramCore
private func parseAuthTransferUrl(_ url: URL) -> Data? {
var tokenString: String?
if let query = url.query, let components = URLComponents(string: "/?" + query), let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "token", !value.isEmpty {
tokenString = value
}
}
}
}
if var tokenString = tokenString {
tokenString = tokenString.replacingOccurrences(of: "-", with: "+")
tokenString = tokenString.replacingOccurrences(of: "_", with: "/")
while tokenString.count % 4 != 0 {
tokenString.append("=")
}
if let data = Data(base64Encoded: tokenString) {
return data
}
}
return nil
}
private func generateFrameImage() -> UIImage? {
return generateImage(CGSize(width: 64.0, height: 64.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(4.0)
context.setLineCap(.round)
var path = CGMutablePath();
path.move(to: CGPoint(x: 2.0, y: 2.0 + 26.0))
path.addArc(tangent1End: CGPoint(x: 2.0, y: 2.0), tangent2End: CGPoint(x: 2.0 + 26.0, y: 2.0), radius: 6.0)
path.addLine(to: CGPoint(x: 2.0 + 26.0, y: 2.0))
context.addPath(path)
context.strokePath()
path.move(to: CGPoint(x: size.width - 2.0, y: 2.0 + 26.0))
path.addArc(tangent1End: CGPoint(x: size.width - 2.0, y: 2.0), tangent2End: CGPoint(x: 2.0 + 26.0, y: 2.0), radius: 6.0)
path.addLine(to: CGPoint(x: size.width - 2.0 - 26.0, y: 2.0))
context.addPath(path)
context.strokePath()
path.move(to: CGPoint(x: 2.0, y: size.height - 2.0 - 26.0))
path.addArc(tangent1End: CGPoint(x: 2.0, y: size.height - 2.0), tangent2End: CGPoint(x: 2.0 + 26.0, y: size.height - 2.0), radius: 6.0)
path.addLine(to: CGPoint(x: 2.0 + 26.0, y: size.height - 2.0))
context.addPath(path)
context.strokePath()
path.move(to: CGPoint(x: size.width - 2.0, y: size.height - 2.0 - 26.0))
path.addArc(tangent1End: CGPoint(x: size.width - 2.0, y: size.height - 2.0), tangent2End: CGPoint(x: 2.0 + 26.0, y: size.height - 2.0), radius: 6.0)
path.addLine(to: CGPoint(x: size.width - 2.0 - 26.0, y: size.height - 2.0))
context.addPath(path)
context.strokePath()
})?.stretchableImage(withLeftCapWidth: 32, topCapHeight: 32)
}
public final class AuthTransferScanScreen: ViewController {
private let context: AccountContext
private var presentationData: PresentationData
private var codeDisposable: Disposable?
private var inForegroundDisposable: Disposable?
private let approveDisposable = MetaDisposable()
public init(context: AccountContext) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
let navigationBarTheme = NavigationBarTheme(buttonColor: .white, disabledButtonColor: .white, primaryTextColor: .white, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear)
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close)))
self.statusBar.statusBarStyle = .White
self.navigationPresentation = .modalInLargeLayout
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.navigationBar?.intrinsicCanTransitionInline = false
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Wallet_Navigation_Back, style: .plain, target: nil, action: nil)
self.inForegroundDisposable = (context.sharedContext.applicationBindings.applicationInForeground
|> deliverOnMainQueue).start(next: { [weak self] inForeground in
guard let strongSelf = self else {
return
}
(strongSelf.displayNode as! AuthTransferScanScreenNode).updateInForeground(inForeground)
})
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.codeDisposable?.dispose()
self.inForegroundDisposable?.dispose()
self.approveDisposable.dispose()
}
@objc private func backPressed() {
self.dismiss()
}
override public func loadDisplayNode() {
self.displayNode = AuthTransferScanScreenNode(presentationData: self.presentationData)
self.displayNodeDidLoad()
self.codeDisposable = ((self.displayNode as! AuthTransferScanScreenNode).focusedCode.get()
|> map { code -> String? in
return code?.message
}
|> distinctUntilChanged
|> mapToSignal { code -> Signal<String?, NoError> in
return .single(code)
|> delay(0.5, queue: Queue.mainQueue())
}).start(next: { [weak self] code in
guard let strongSelf = self, let code = code else {
return
}
if let url = URL(string: code), let parsedToken = parseAuthTransferUrl(url) {
print("import token: \(parsedToken.base64EncodedString())")
strongSelf.approveDisposable.set((approveAuthTransferToken(account: strongSelf.context.account, token: parsedToken)
|> deliverOnMainQueue).start(error: { _ in
}, completed: {
guard let strongSelf = self else {
return
}
strongSelf.dismiss()
}))
}
})
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
(self.displayNode as! AuthTransferScanScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationHeight, transition: transition)
}
}
private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScrollViewDelegate {
private var presentationData: PresentationData
private let previewNode: CameraPreviewNode
private let fadeNode: ASDisplayNode
private let topDimNode: ASDisplayNode
private let bottomDimNode: ASDisplayNode
private let leftDimNode: ASDisplayNode
private let rightDimNode: ASDisplayNode
private let frameNode: ASImageNode
private let torchButtonNode: GlassButtonNode
private let titleNode: ImmediateTextNode
private let camera: Camera
private let codeDisposable = MetaDisposable()
fileprivate let focusedCode = ValuePromise<CameraCode?>(ignoreRepeated: true)
private var focusedRect: CGRect?
private var validLayout: (ContainerViewLayout, CGFloat)?
init(presentationData: PresentationData) {
self.presentationData = presentationData
self.previewNode = CameraPreviewNode()
self.previewNode.backgroundColor = .black
self.fadeNode = ASDisplayNode()
self.fadeNode.alpha = 0.0
self.fadeNode.backgroundColor = .black
self.topDimNode = ASDisplayNode()
self.topDimNode.alpha = 0.625
self.topDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
self.bottomDimNode = ASDisplayNode()
self.bottomDimNode.alpha = 0.625
self.bottomDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
self.leftDimNode = ASDisplayNode()
self.leftDimNode.alpha = 0.625
self.leftDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
self.rightDimNode = ASDisplayNode()
self.rightDimNode.alpha = 0.625
self.rightDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
self.frameNode = ASImageNode()
self.frameNode.image = generateFrameImage()
self.torchButtonNode = GlassButtonNode(icon: UIImage(bundleImageName: "Wallet/CameraFlashIcon")!, label: nil)
self.titleNode = ImmediateTextNode()
self.titleNode.displaysAsynchronously = false
self.titleNode.attributedText = NSAttributedString(string: presentationData.strings.Wallet_Qr_ScanCode, font: Font.bold(32.0), textColor: .white)
self.titleNode.maximumNumberOfLines = 0
self.titleNode.textAlignment = .center
self.camera = Camera(configuration: .init(preset: .hd1920x1080, position: .back, audio: false))
super.init()
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.addSubnode(self.previewNode)
self.addSubnode(self.fadeNode)
self.addSubnode(self.topDimNode)
self.addSubnode(self.bottomDimNode)
self.addSubnode(self.leftDimNode)
self.addSubnode(self.rightDimNode)
self.addSubnode(self.frameNode)
self.addSubnode(self.torchButtonNode)
self.addSubnode(self.titleNode)
self.torchButtonNode.addTarget(self, action: #selector(self.torchPressed), forControlEvents: .touchUpInside)
}
deinit {
self.codeDisposable.dispose()
self.camera.stopCapture(invalidate: true)
}
fileprivate func updateInForeground(_ inForeground: Bool) {
if !inForeground {
self.camera.stopCapture(invalidate: false)
} else {
self.camera.startCapture()
}
}
override func didLoad() {
super.didLoad()
self.camera.attachPreviewNode(self.previewNode)
self.camera.startCapture()
let throttledSignal = self.camera.detectedCodes
|> mapToThrottled { next -> Signal<[CameraCode], NoError> in
return .single(next) |> then(.complete() |> delay(0.3, queue: Queue.concurrentDefaultQueue()))
}
self.codeDisposable.set((throttledSignal
|> deliverOnMainQueue).start(next: { [weak self] codes in
guard let strongSelf = self else {
return
}
let filteredCodes = codes.filter { $0.message.hasPrefix("tg://") }
if let code = filteredCodes.first, CGRect(x: 0.3, y: 0.3, width: 0.4, height: 0.4).contains(code.boundingBox.center) {
strongSelf.focusedCode.set(code)
strongSelf.updateFocusedRect(code.boundingBox)
} else {
strongSelf.focusedCode.set(nil)
strongSelf.updateFocusedRect(nil)
}
}))
}
private func updateFocusedRect(_ rect: CGRect?) {
self.focusedRect = rect
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring))
}
}
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationHeight)
let sideInset: CGFloat = 66.0
let titleSpacing: CGFloat = 48.0
let bounds = CGRect(origin: CGPoint(), size: layout.size)
if case .tablet = layout.deviceMetrics.type {
if UIDevice.current.orientation == .landscapeLeft {
self.previewNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
} else if UIDevice.current.orientation == .landscapeRight {
self.previewNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
} else {
self.previewNode.transform = CATransform3DIdentity
}
}
transition.updateFrame(node: self.previewNode, frame: bounds)
transition.updateFrame(node: self.fadeNode, frame: bounds)
let frameSide = max(240.0, layout.size.width - sideInset * 2.0)
let dimHeight = ceil((layout.size.height - frameSide) / 2.0)
let dimInset = (layout.size.width - frameSide) / 2.0
let dimAlpha: CGFloat
let dimRect: CGRect
let controlsAlpha: CGFloat
if let focusedRect = self.focusedRect {
controlsAlpha = 0.0
dimAlpha = 1.0
let side = max(bounds.width * focusedRect.width, bounds.height * focusedRect.height) * 0.6
let center = CGPoint(x: (1.0 - focusedRect.center.y) * bounds.width, y: focusedRect.center.x * bounds.height)
dimRect = CGRect(x: center.x - side / 2.0, y: center.y - side / 2.0, width: side, height: side)
} else {
controlsAlpha = 1.0
dimAlpha = 0.625
dimRect = CGRect(x: dimInset, y: dimHeight, width: layout.size.width - dimInset * 2.0, height: layout.size.height - dimHeight * 2.0)
}
transition.updateAlpha(node: self.topDimNode, alpha: dimAlpha)
transition.updateAlpha(node: self.bottomDimNode, alpha: dimAlpha)
transition.updateAlpha(node: self.leftDimNode, alpha: dimAlpha)
transition.updateAlpha(node: self.rightDimNode, alpha: dimAlpha)
transition.updateFrame(node: self.topDimNode, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: dimRect.minY))
transition.updateFrame(node: self.bottomDimNode, frame: CGRect(x: 0.0, y: dimRect.maxY, width: layout.size.width, height: max(0.0, layout.size.height - dimRect.maxY)))
transition.updateFrame(node: self.leftDimNode, frame: CGRect(x: 0.0, y: dimRect.minY, width: max(0.0, dimRect.minX), height: dimRect.height))
transition.updateFrame(node: self.rightDimNode, frame: CGRect(x: dimRect.maxX, y: dimRect.minY, width: max(0.0, layout.size.width - dimRect.maxX), height: dimRect.height))
transition.updateFrame(node: self.frameNode, frame: dimRect.insetBy(dx: -2.0, dy: -2.0))
let buttonSize = CGSize(width: 72.0, height: 72.0)
transition.updateFrame(node: self.torchButtonNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - buttonSize.width) / 2.0), y: dimHeight + frameSide + 50.0), size: buttonSize))
transition.updateAlpha(node: self.titleNode, alpha: controlsAlpha)
transition.updateAlpha(node: self.torchButtonNode, alpha: controlsAlpha)
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: dimHeight - titleSize.height - titleSpacing), size: titleSize)
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
}
@objc private func torchPressed() {
self.torchButtonNode.isSelected = !self.torchButtonNode.isSelected
self.camera.setTorchActive(self.torchButtonNode.isSelected)
}
}

View File

@ -84,6 +84,7 @@ static_library(
"//submodules/DeleteChatPeerActionSheetItem:DeleteChatPeerActionSheetItem",
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
"//submodules/OpenInExternalAppUI:OpenInExternalAppUI",
"//submodules/AuthTransferUI:AuthTransferUI",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -35,6 +35,7 @@ import AppBundle
import ContextUI
import WalletUI
import PhoneNumberFormat
import AuthTransferUI
private let maximumNumberOfAccounts = 3
@ -112,6 +113,7 @@ private final class SettingsItemArguments {
let keepPhone: () -> Void
let openPhoneNumberChange: () -> Void
let accountContextAction: (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void
let openLoginDesktop: () -> Void
init(
accountManager: AccountManager,
@ -144,7 +146,8 @@ private final class SettingsItemArguments {
removeAccount: @escaping (AccountRecordId) -> Void,
keepPhone: @escaping () -> Void,
openPhoneNumberChange: @escaping () -> Void,
accountContextAction: @escaping (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void
accountContextAction: @escaping (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void,
openLoginDesktop: @escaping () -> Void
) {
self.accountManager = accountManager
self.avatarAndNameInfoContext = avatarAndNameInfoContext
@ -177,6 +180,7 @@ private final class SettingsItemArguments {
self.keepPhone = keepPhone
self.openPhoneNumberChange = openPhoneNumberChange
self.accountContextAction = accountContextAction
self.openLoginDesktop = openLoginDesktop
}
}
@ -185,6 +189,7 @@ private enum SettingsSection: Int32 {
case phone
case accounts
case proxy
case loginDesktop
case media
case generalSettings
case advanced
@ -205,6 +210,8 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
case proxy(PresentationTheme, UIImage?, String, String)
case loginDesktop(PresentationTheme, UIImage?, String)
case savedMessages(PresentationTheme, UIImage?, String)
case recentCalls(PresentationTheme, UIImage?, String)
case stickers(PresentationTheme, UIImage?, String, String, [ArchivedStickerPackItem]?)
@ -223,22 +230,24 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
var section: ItemListSectionId {
switch self {
case .userInfo, .setProfilePhoto, .setUsername:
return SettingsSection.info.rawValue
case .phoneInfo, .keepPhone, .changePhone:
return SettingsSection.phone.rawValue
case .account, .addAccount:
return SettingsSection.accounts.rawValue
case .proxy:
return SettingsSection.proxy.rawValue
case .savedMessages, .recentCalls, .stickers:
return SettingsSection.media.rawValue
case .notificationsAndSounds, .privacyAndSecurity, .dataAndStorage, .themes, .language:
return SettingsSection.generalSettings.rawValue
case .passport, .wallet, .watch :
return SettingsSection.advanced.rawValue
case .askAQuestion, .faq:
return SettingsSection.help.rawValue
case .userInfo, .setProfilePhoto, .setUsername:
return SettingsSection.info.rawValue
case .phoneInfo, .keepPhone, .changePhone:
return SettingsSection.phone.rawValue
case .account, .addAccount:
return SettingsSection.accounts.rawValue
case .proxy:
return SettingsSection.proxy.rawValue
case .loginDesktop:
return SettingsSection.loginDesktop.rawValue
case .savedMessages, .recentCalls, .stickers:
return SettingsSection.media.rawValue
case .notificationsAndSounds, .privacyAndSecurity, .dataAndStorage, .themes, .language:
return SettingsSection.generalSettings.rawValue
case .passport, .wallet, .watch :
return SettingsSection.advanced.rawValue
case .askAQuestion, .faq:
return SettingsSection.help.rawValue
}
}
@ -262,32 +271,34 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
return 1002
case .proxy:
return 1003
case .savedMessages:
case .loginDesktop:
return 1004
case .recentCalls:
case .savedMessages:
return 1005
case .stickers:
case .recentCalls:
return 1006
case .notificationsAndSounds:
case .stickers:
return 1007
case .privacyAndSecurity:
case .notificationsAndSounds:
return 1008
case .dataAndStorage:
case .privacyAndSecurity:
return 1009
case .themes:
case .dataAndStorage:
return 1010
case .language:
case .themes:
return 1011
case .wallet:
case .language:
return 1012
case .passport:
case .wallet:
return 1013
case .watch:
case .passport:
return 1014
case .askAQuestion:
case .watch:
return 1015
case .faq:
case .askAQuestion:
return 1016
case .faq:
return 1017
}
}
@ -379,6 +390,12 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
} else {
return false
}
case let .loginDesktop(lhsTheme, lhsImage, lhsText):
if case let .loginDesktop(rhsTheme, rhsImage, rhsText) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText {
return true
} else {
return false
}
case let .savedMessages(lhsTheme, lhsImage, lhsText):
if case let .savedMessages(rhsTheme, rhsImage, rhsText) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText {
return true
@ -528,6 +545,10 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: value, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.openProxy()
})
case let .loginDesktop(theme, image, text):
return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.openLoginDesktop()
})
case let .savedMessages(theme, image, text):
return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.openSavedMessages()
@ -636,6 +657,8 @@ private func settingsEntries(account: Account, presentationData: PresentationDat
entries.append(.proxy(presentationData.theme, PresentationResourcesSettings.proxy, presentationData.strings.Settings_Proxy, valueString))
}
entries.append(.loginDesktop(presentationData.theme, PresentationResourcesSettings.security, "Telegram Desktop"))
entries.append(.savedMessages(presentationData.theme, PresentationResourcesSettings.savedMessages, presentationData.strings.Settings_SavedMessages))
entries.append(.recentCalls(presentationData.theme, PresentationResourcesSettings.recentCalls, presentationData.strings.CallSettings_RecentCalls))
entries.append(.stickers(presentationData.theme, PresentationResourcesSettings.stickers, presentationData.strings.ChatSettings_Stickers, unreadTrendingStickerPacks == 0 ? "" : "\(unreadTrendingStickerPacks)", archivedPacks))
@ -1045,6 +1068,12 @@ public func settingsController(context: AccountContext, accountManager: AccountM
} else {
gesture?.cancel()
}
}, openLoginDesktop: {
let _ = (contextValue.get()
|> deliverOnMainQueue
|> take(1)).start(next: { context in
pushControllerImpl?(AuthTransferScanScreen(context: context))
})
})
changeProfilePhotoImpl = {

View File

@ -53,6 +53,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-614138572] = { return Api.account.TmpPassword.parse_tmpPassword($0) }
dict[-2103600678] = { return Api.SecureRequiredType.parse_secureRequiredType($0) }
dict[41187252] = { return Api.SecureRequiredType.parse_secureRequiredTypeOneOf($0) }
dict[1654593920] = { return Api.auth.LoginToken.parse_loginToken($0) }
dict[110008598] = { return Api.auth.LoginToken.parse_loginTokenMigrateTo($0) }
dict[957176926] = { return Api.auth.LoginToken.parse_loginTokenSuccess($0) }
dict[1064139624] = { return Api.JSONValue.parse_jsonNull($0) }
dict[-952869270] = { return Api.JSONValue.parse_jsonBool($0) }
dict[736157604] = { return Api.JSONValue.parse_jsonNumber($0) }
@ -242,6 +245,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1870238482] = { return Api.Update.parse_updateDeleteScheduledMessages($0) }
dict[-2112423005] = { return Api.Update.parse_updateTheme($0) }
dict[357013699] = { return Api.Update.parse_updateMessageReactions($0) }
dict[-2027964103] = { return Api.Update.parse_updateGeoLiveViewed($0) }
dict[1448076945] = { return Api.Update.parse_updateLoginToken($0) }
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) }
dict[367766557] = { return Api.ChannelParticipant.parse_channelParticipant($0) }
@ -594,6 +599,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[70813275] = { return Api.InputStickeredMedia.parse_inputStickeredMediaDocument($0) }
dict[82699215] = { return Api.messages.FeaturedStickers.parse_featuredStickersNotModified($0) }
dict[-123893531] = { return Api.messages.FeaturedStickers.parse_featuredStickers($0) }
dict[1375940666] = { return Api.auth.LoginTokenInfo.parse_loginTokenInfo($0) }
dict[-2048646399] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonMissed($0) }
dict[-527056480] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonDisconnect($0) }
dict[1471006352] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonHangup($0) }
@ -868,6 +874,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.SecureRequiredType:
_1.serialize(buffer, boxed)
case let _1 as Api.auth.LoginToken:
_1.serialize(buffer, boxed)
case let _1 as Api.JSONValue:
_1.serialize(buffer, boxed)
case let _1 as Api.Photo:
@ -1244,6 +1252,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.messages.FeaturedStickers:
_1.serialize(buffer, boxed)
case let _1 as Api.auth.LoginTokenInfo:
_1.serialize(buffer, boxed)
case let _1 as Api.PhoneCallDiscardReason:
_1.serialize(buffer, boxed)
case let _1 as Api.NearestDc:

View File

@ -4145,6 +4145,8 @@ public extension Api {
case updateDeleteScheduledMessages(peer: Api.Peer, messages: [Int32])
case updateTheme(theme: Api.Theme)
case updateMessageReactions(peer: Api.Peer, msgId: Int32, reactions: Api.MessageReactions)
case updateGeoLiveViewed(peer: Api.Peer, msgId: Int32)
case updateLoginToken
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -4776,6 +4778,19 @@ public extension Api {
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
reactions.serialize(buffer, true)
break
case .updateGeoLiveViewed(let peer, let msgId):
if boxed {
buffer.appendInt32(-2027964103)
}
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
break
case .updateLoginToken:
if boxed {
buffer.appendInt32(1448076945)
}
break
}
}
@ -4932,6 +4947,10 @@ public extension Api {
return ("updateTheme", [("theme", theme)])
case .updateMessageReactions(let peer, let msgId, let reactions):
return ("updateMessageReactions", [("peer", peer), ("msgId", msgId), ("reactions", reactions)])
case .updateGeoLiveViewed(let peer, let msgId):
return ("updateGeoLiveViewed", [("peer", peer), ("msgId", msgId)])
case .updateLoginToken:
return ("updateLoginToken", [])
}
}
@ -6211,6 +6230,25 @@ public extension Api {
return nil
}
}
public static func parse_updateGeoLiveViewed(_ reader: BufferReader) -> Update? {
var _1: Api.Peer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.Update.updateGeoLiveViewed(peer: _1!, msgId: _2!)
}
else {
return nil
}
}
public static func parse_updateLoginToken(_ reader: BufferReader) -> Update? {
return Api.Update.updateLoginToken
}
}
public enum PopularContact: TypeConstructorDescription {

View File

@ -494,6 +494,90 @@ public struct payments {
}
public extension Api {
public struct auth {
public enum LoginToken: TypeConstructorDescription {
case loginToken(expires: Int32, token: Buffer)
case loginTokenMigrateTo(dcId: Int32, token: Buffer)
case loginTokenSuccess(authorization: Api.auth.Authorization)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .loginToken(let expires, let token):
if boxed {
buffer.appendInt32(1654593920)
}
serializeInt32(expires, buffer: buffer, boxed: false)
serializeBytes(token, buffer: buffer, boxed: false)
break
case .loginTokenMigrateTo(let dcId, let token):
if boxed {
buffer.appendInt32(110008598)
}
serializeInt32(dcId, buffer: buffer, boxed: false)
serializeBytes(token, buffer: buffer, boxed: false)
break
case .loginTokenSuccess(let authorization):
if boxed {
buffer.appendInt32(957176926)
}
authorization.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .loginToken(let expires, let token):
return ("loginToken", [("expires", expires), ("token", token)])
case .loginTokenMigrateTo(let dcId, let token):
return ("loginTokenMigrateTo", [("dcId", dcId), ("token", token)])
case .loginTokenSuccess(let authorization):
return ("loginTokenSuccess", [("authorization", authorization)])
}
}
public static func parse_loginToken(_ reader: BufferReader) -> LoginToken? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Buffer?
_2 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.auth.LoginToken.loginToken(expires: _1!, token: _2!)
}
else {
return nil
}
}
public static func parse_loginTokenMigrateTo(_ reader: BufferReader) -> LoginToken? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Buffer?
_2 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.auth.LoginToken.loginTokenMigrateTo(dcId: _1!, token: _2!)
}
else {
return nil
}
}
public static func parse_loginTokenSuccess(_ reader: BufferReader) -> LoginToken? {
var _1: Api.auth.Authorization?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.auth.Authorization
}
let _c1 = _1 != nil
if _c1 {
return Api.auth.LoginToken.loginTokenSuccess(authorization: _1!)
}
else {
return nil
}
}
}
public enum Authorization: TypeConstructorDescription {
case authorization(flags: Int32, tmpSessions: Int32?, user: Api.User)
case authorizationSignUpRequired(flags: Int32, termsOfService: Api.help.TermsOfService?)
@ -775,6 +859,76 @@ public struct auth {
return Api.auth.CodeType.codeTypeFlashCall
}
}
public enum LoginTokenInfo: TypeConstructorDescription {
case loginTokenInfo(dcId: Int32, authKeyId: Int64, deviceModel: String, platform: String, systemVersion: String, apiId: Int32, appName: String, appVersion: String, ip: String, region: String)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .loginTokenInfo(let dcId, let authKeyId, let deviceModel, let platform, let systemVersion, let apiId, let appName, let appVersion, let ip, let region):
if boxed {
buffer.appendInt32(1375940666)
}
serializeInt32(dcId, buffer: buffer, boxed: false)
serializeInt64(authKeyId, buffer: buffer, boxed: false)
serializeString(deviceModel, buffer: buffer, boxed: false)
serializeString(platform, buffer: buffer, boxed: false)
serializeString(systemVersion, buffer: buffer, boxed: false)
serializeInt32(apiId, buffer: buffer, boxed: false)
serializeString(appName, buffer: buffer, boxed: false)
serializeString(appVersion, buffer: buffer, boxed: false)
serializeString(ip, buffer: buffer, boxed: false)
serializeString(region, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .loginTokenInfo(let dcId, let authKeyId, let deviceModel, let platform, let systemVersion, let apiId, let appName, let appVersion, let ip, let region):
return ("loginTokenInfo", [("dcId", dcId), ("authKeyId", authKeyId), ("deviceModel", deviceModel), ("platform", platform), ("systemVersion", systemVersion), ("apiId", apiId), ("appName", appName), ("appVersion", appVersion), ("ip", ip), ("region", region)])
}
}
public static func parse_loginTokenInfo(_ reader: BufferReader) -> LoginTokenInfo? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: String?
_3 = parseString(reader)
var _4: String?
_4 = parseString(reader)
var _5: String?
_5 = parseString(reader)
var _6: Int32?
_6 = reader.readInt32()
var _7: String?
_7 = parseString(reader)
var _8: String?
_8 = parseString(reader)
var _9: String?
_9 = parseString(reader)
var _10: String?
_10 = parseString(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
let _c8 = _8 != nil
let _c9 = _9 != nil
let _c10 = _10 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 {
return Api.auth.LoginTokenInfo.loginTokenInfo(dcId: _1!, authKeyId: _2!, deviceModel: _3!, platform: _4!, systemVersion: _5!, apiId: _6!, appName: _7!, appVersion: _8!, ip: _9!, region: _10!)
}
else {
return nil
}
}
}
public enum SentCodeType: TypeConstructorDescription {
case sentCodeTypeApp(length: Int32)

View File

@ -4139,6 +4139,63 @@ public extension Api {
return result
})
}
public static func exportLoginToken(apiId: Int32, apiHash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.LoginToken>) {
let buffer = Buffer()
buffer.appendInt32(434003159)
serializeInt32(apiId, buffer: buffer, boxed: false)
serializeString(apiHash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "auth.exportLoginToken", parameters: [("apiId", apiId), ("apiHash", apiHash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoginToken? in
let reader = BufferReader(buffer)
var result: Api.auth.LoginToken?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.auth.LoginToken
}
return result
})
}
public static func importLoginToken(token: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.LoginToken>) {
let buffer = Buffer()
buffer.appendInt32(-1783866140)
serializeBytes(token, buffer: buffer, boxed: false)
return (FunctionDescription(name: "auth.importLoginToken", parameters: [("token", token)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoginToken? in
let reader = BufferReader(buffer)
var result: Api.auth.LoginToken?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.auth.LoginToken
}
return result
})
}
public static func acceptLoginToken(token: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(1122447801)
serializeBytes(token, buffer: buffer, boxed: false)
return (FunctionDescription(name: "auth.acceptLoginToken", parameters: [("token", token)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
public static func checkLoginToken(token: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.LoginTokenInfo>) {
let buffer = Buffer()
buffer.appendInt32(2102383792)
serializeBytes(token, buffer: buffer, boxed: false)
return (FunctionDescription(name: "auth.checkLoginToken", parameters: [("token", token)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoginTokenInfo? in
let reader = BufferReader(buffer)
var result: Api.auth.LoginTokenInfo?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.auth.LoginTokenInfo
}
return result
})
}
}
public struct bots {
public static func sendCustomRequest(customMethod: String, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.DataJSON>) {

View File

@ -0,0 +1,144 @@
import Foundation
import Postbox
import TelegramApi
import SyncCore
import SwiftSignalKit
public struct AuthTransferExportedToken {
public let value: Data
public let validUntil: Int32
}
public struct AuthTransferTokenInfo {
public let datacenterId: Int32
public let authKeyId: Int64
public let deviceModel: String
public let platform: String
public let systemVersion: String
public let apiId: Int32
public let appName: String
public let appVersion: String
public let ip: String
public let region: String
}
public enum ExportAuthTransferTokenError {
case generic
}
public enum ExportAuthTransferTokenResult {
case displayToken(AuthTransferExportedToken)
case changeAccountAndRetry(UnauthorizedAccount)
case loggedIn
}
public func exportAuthTransferToken(accountManager: AccountManager, account: UnauthorizedAccount, syncContacts: Bool) -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> {
return account.network.request(Api.functions.auth.exportLoginToken(apiId: account.networkArguments.apiId, apiHash: account.networkArguments.apiHash))
|> mapError { _ -> ExportAuthTransferTokenError in
return .generic
}
|> mapToSignal { result -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> in
switch result {
case let .loginToken(expires, token):
return .single(.displayToken(AuthTransferExportedToken(value: token.makeData(), validUntil: expires)))
case let .loginTokenMigrateTo(dcId, token):
let updatedAccount = account.changedMasterDatacenterId(accountManager: accountManager, masterDatacenterId: dcId)
return updatedAccount
|> castError(ExportAuthTransferTokenError.self)
|> mapToSignal { updatedAccount -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> in
return updatedAccount.network.request(Api.functions.auth.importLoginToken(token: token))
|> mapError { _ -> ExportAuthTransferTokenError in
return .generic
}
|> mapToSignal { result -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> in
switch result {
case let .loginTokenSuccess(authorization):
switch authorization {
case let .authorization(_, _, user):
return updatedAccount.postbox.transaction { transaction -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> in
let user = TelegramUser(user: user)
let state = AuthorizedAccountState(isTestingEnvironment: updatedAccount.testingEnvironment, masterDatacenterId: updatedAccount.masterDatacenterId, peerId: user.id, state: nil)
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: updatedAccount.networkArguments.appVersion, syncContacts: syncContacts)
transaction.setState(state)
return accountManager.transaction { transaction -> ExportAuthTransferTokenResult in
switchToAuthorizedAccount(transaction: transaction, account: updatedAccount)
return .loggedIn
}
|> castError(ExportAuthTransferTokenError.self)
}
|> castError(ExportAuthTransferTokenError.self)
|> switchToLatest
default:
return .fail(.generic)
}
default:
return .single(.changeAccountAndRetry(updatedAccount))
}
}
}
case let .loginTokenSuccess(authorization):
switch authorization {
case let .authorization(_, _, user):
return account.postbox.transaction { transaction -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> in
let user = TelegramUser(user: user)
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil)
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: account.networkArguments.appVersion, syncContacts: syncContacts)
transaction.setState(state)
return accountManager.transaction { transaction -> ExportAuthTransferTokenResult in
switchToAuthorizedAccount(transaction: transaction, account: account)
return .loggedIn
}
|> castError(ExportAuthTransferTokenError.self)
}
|> castError(ExportAuthTransferTokenError.self)
|> switchToLatest
case let .authorizationSignUpRequired:
return .fail(.generic)
}
}
}
}
public enum GetAuthTransferTokenInfoError {
case generic
case invalid
case expired
case alreadyAccepted
}
public func getAuthTransferTokenInfo(network: Network, token: Data) -> Signal<AuthTransferTokenInfo, GetAuthTransferTokenInfoError> {
return network.request(Api.functions.auth.checkLoginToken(token: Buffer(data: token)))
|> mapError { error -> GetAuthTransferTokenInfoError in
switch error.errorDescription {
case "AUTH_TOKEN_INVALID":
return .invalid
case "AUTH_TOKEN_EXPIRED":
return .expired
case "AUTH_TOKEN_ALREADY_ACCEPTED":
return .alreadyAccepted
default:
return .generic
}
}
|> map { result -> AuthTransferTokenInfo in
switch result {
case let .loginTokenInfo(dcId, authKeyId, deviceModel, platform, systemVersion, apiId, appName, appVersion, ip, region):
return AuthTransferTokenInfo(datacenterId: dcId, authKeyId: authKeyId, deviceModel: deviceModel, platform: platform, systemVersion: systemVersion, apiId: apiId, appName: appName, appVersion: appVersion, ip: ip, region: region)
}
}
}
public enum ApproveAuthTransferTokenError {
case generic
}
public func approveAuthTransferToken(account: Account, token: Data) -> Signal<Never, ApproveAuthTransferTokenError> {
return account.network.request(Api.functions.auth.acceptLoginToken(token: Buffer(data: token)))
|> mapError { _ -> ApproveAuthTransferTokenError in
return .generic
}
|> mapToSignal { updates -> Signal<Never, ApproveAuthTransferTokenError> in
account.stateManager.addUpdates(updates)
return .complete()
}
}

View File

@ -15,7 +15,7 @@ public enum AuthorizationCodeRequestError {
case timeout
}
private func switchToAuthorizedAccount(transaction: AccountManagerModifier, account: UnauthorizedAccount) {
func switchToAuthorizedAccount(transaction: AccountManagerModifier, account: UnauthorizedAccount) {
let nextSortOrder = (transaction.getRecords().map({ record -> Int32 in
for attribute in record.attributes {
if let attribute = attribute as? AccountSortOrderAttribute {

View File

@ -399,6 +399,7 @@ func networkUsageStats(basePath: String, reset: ResetNetworkUsageStats) -> Signa
public struct NetworkInitializationArguments {
public let apiId: Int32
public let apiHash: String
public let languagesCategory: String
public let appVersion: String
public let voipMaxLayer: Int32
@ -406,8 +407,9 @@ public struct NetworkInitializationArguments {
public let autolockDeadine: Signal<Int32?, NoError>
public let encryptionProvider: EncryptionProvider
public init(apiId: Int32, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, appData: Signal<Data?, NoError>, autolockDeadine: Signal<Int32?, NoError>, encryptionProvider: EncryptionProvider) {
public init(apiId: Int32, apiHash: String, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, appData: Signal<Data?, NoError>, autolockDeadine: Signal<Int32?, NoError>, encryptionProvider: EncryptionProvider) {
self.apiId = apiId
self.apiHash = apiHash
self.languagesCategory = languagesCategory
self.appVersion = appVersion
self.voipMaxLayer = voipMaxLayer

View File

@ -370,9 +370,10 @@ final class SharedApplicationContext {
let signatureDict = BuildConfigExtra.signatureDict()
let apiId: Int32 = buildConfig.apiId
let apiHash: String = buildConfig.apiHash
let languagesCategory = "ios"
let networkArguments = NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: PresentationCallManagerImpl.voipMaxLayer, appData: self.deviceToken.get()
let networkArguments = NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: PresentationCallManagerImpl.voipMaxLayer, appData: self.deviceToken.get()
|> map { token in
let data = buildConfig.bundleData(withAppToken: token, signatureDict: signatureDict)
if let data = data, let jsonString = String(data: data, encoding: .utf8) {

View File

@ -144,7 +144,7 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
if let currentController = currentController {
controller = currentController
} else {
controller = AuthorizationSequencePhoneEntryController(sharedContext: self.sharedContext, isTestingEnvironment: self.account.testingEnvironment, otherAccountPhoneNumbers: self.otherAccountPhoneNumbers, network: self.account.network, presentationData: self.presentationData, openUrl: { [weak self] url in
controller = AuthorizationSequencePhoneEntryController(sharedContext: self.sharedContext, account: self.account, isTestingEnvironment: self.account.testingEnvironment, otherAccountPhoneNumbers: self.otherAccountPhoneNumbers, network: self.account.network, presentationData: self.presentationData, openUrl: { [weak self] url in
self?.openUrl(url)
}, back: { [weak self] in
guard let strongSelf = self else {
@ -160,6 +160,12 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
}).start()
}
})
controller.accountUpdated = { [weak self] updatedAccount in
guard let strongSelf = self else {
return
}
strongSelf.account = updatedAccount
}
controller.loginWithNumber = { [weak self, weak controller] number, syncContacts in
if let strongSelf = self {
controller?.inProgress = true

View File

@ -19,6 +19,7 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
}
private let sharedContext: SharedAccountContext
private var account: UnauthorizedAccount
private let isTestingEnvironment: Bool
private let otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)])
private let network: Network
@ -41,13 +42,15 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
}
}
var loginWithNumber: ((String, Bool) -> Void)?
var accountUpdated: ((UnauthorizedAccount) -> Void)?
private let termsDisposable = MetaDisposable()
private let hapticFeedback = HapticFeedback()
init(sharedContext: SharedAccountContext, isTestingEnvironment: Bool, otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]), network: Network, presentationData: PresentationData, openUrl: @escaping (String) -> Void, back: @escaping () -> Void) {
init(sharedContext: SharedAccountContext, account: UnauthorizedAccount, isTestingEnvironment: Bool, otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]), network: Network, presentationData: PresentationData, openUrl: @escaping (String) -> Void, back: @escaping () -> Void) {
self.sharedContext = sharedContext
self.account = account
self.isTestingEnvironment = isTestingEnvironment
self.otherAccountPhoneNumbers = otherAccountPhoneNumbers
self.network = network
@ -95,13 +98,21 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
}
override public func loadDisplayNode() {
self.displayNode = AuthorizationSequencePhoneEntryControllerNode(strings: self.presentationData.strings, theme: self.presentationData.theme, debugAction: { [weak self] in
self.displayNode = AuthorizationSequencePhoneEntryControllerNode(accountManager: self.sharedContext.accountManager, account: self.account, strings: self.presentationData.strings, theme: self.presentationData.theme, debugAction: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.view.endEditing(true)
self?.present(debugController(sharedContext: strongSelf.sharedContext, context: nil, modal: true), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}, hasOtherAccounts: self.otherAccountPhoneNumbers.0 != nil)
}, hasOtherAccounts: self.otherAccountPhoneNumbers.0 != nil)
self.controllerNode.accountUpdated = { [weak self] account in
guard let strongSelf = self else {
return
}
strongSelf.account = account
strongSelf.accountUpdated?(account)
}
if let (code, name, number) = self.currentData {
self.controllerNode.codeAndNumber = (code, name, number)
}

View File

@ -8,6 +8,9 @@ import TelegramPresentationData
import PhoneInputNode
import CountrySelectionUI
import AuthorizationUI
import QrCode
import SwiftSignalKit
import Postbox
private func emojiFlagForISOCountryCode(_ countryCode: NSString) -> String {
if countryCode.length != 2 {
@ -201,6 +204,8 @@ private final class ContactSyncNode: ASDisplayNode {
}
final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
private let accountManager: AccountManager
private var account: UnauthorizedAccount
private let strings: PresentationStrings
private let theme: PresentationTheme
private let hasOtherAccounts: Bool
@ -210,6 +215,10 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
private let phoneAndCountryNode: PhoneAndCountryNode
private let contactSyncNode: ContactSyncNode
private var qrNode: ASImageNode?
private let exportTokenDisposable = MetaDisposable()
var accountUpdated: ((UnauthorizedAccount) -> Void)?
private let debugAction: () -> Void
var currentNumber: String {
@ -245,7 +254,10 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
}
}
init(strings: PresentationStrings, theme: PresentationTheme, debugAction: @escaping () -> Void, hasOtherAccounts: Bool) {
init(accountManager: AccountManager, account: UnauthorizedAccount, strings: PresentationStrings, theme: PresentationTheme, debugAction: @escaping () -> Void, hasOtherAccounts: Bool) {
self.accountManager = accountManager
self.account = account
self.strings = strings
self.theme = theme
self.debugAction = debugAction
@ -257,7 +269,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.titleNode.attributedText = NSAttributedString(string: strings.Login_PhoneTitle, font: Font.light(30.0), textColor: theme.list.itemPrimaryTextColor)
self.noticeNode = ASTextNode()
self.noticeNode.isUserInteractionEnabled = false
self.noticeNode.isUserInteractionEnabled = true
self.noticeNode.displaysAsynchronously = false
self.noticeNode.attributedText = NSAttributedString(string: strings.Login_PhoneAndCountryHelp, font: Font.regular(16.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
@ -287,10 +299,15 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
}
}
deinit {
self.exportTokenDisposable.dispose()
}
override func didLoad() {
super.didLoad()
self.titleNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.debugTap(_:))))
self.noticeNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.debugQrTap(_:))))
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
@ -356,4 +373,62 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
}
}
}
@objc private func debugQrTap(_ recognizer: UITapGestureRecognizer) {
if self.qrNode == nil {
let qrNode = ASImageNode()
qrNode.frame = CGRect(origin: CGPoint(x: 16.0, y: 64.0 + 16.0), size: CGSize(width: 200.0, height: 200.0))
self.qrNode = qrNode
self.addSubnode(qrNode)
self.refreshQrToken()
}
}
private func refreshQrToken() {
let tokenSignal = exportAuthTransferToken(accountManager: self.accountManager, account: self.account, syncContacts: true)
self.exportTokenDisposable.set((tokenSignal
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let strongSelf = self else {
return
}
switch result {
case let .displayToken(token):
var tokenString = token.value.base64EncodedString()
print("export token \(tokenString)")
tokenString = tokenString.replacingOccurrences(of: "+", with: "-")
tokenString = tokenString.replacingOccurrences(of: "/", with: "_")
let urlString = "tg://login?token=\(tokenString)"
let _ = (qrCode(string: urlString, color: .black, backgroundColor: .white, icon: .none)
|> deliverOnMainQueue).start(next: { _, generate in
guard let strongSelf = self else {
return
}
let context = generate(TransformImageArguments(corners: ImageCorners(), imageSize: CGSize(width: 200.0, height: 200.0), boundingSize: CGSize(width: 200.0, height: 200.0), intrinsicInsets: UIEdgeInsets()))
if let image = context?.generateImage() {
strongSelf.qrNode?.image = image
}
})
let timestamp = Int32(Date().timeIntervalSince1970)
let timeout = max(5, token.validUntil - timestamp)
strongSelf.exportTokenDisposable.set((Signal<Never, NoError>.complete()
|> delay(Double(timeout), queue: .mainQueue())).start(completed: {
guard let strongSelf = self else {
return
}
strongSelf.refreshQrToken()
}))
case let .changeAccountAndRetry(account):
strongSelf.exportTokenDisposable.set(nil)
strongSelf.account = account
strongSelf.accountUpdated?(account)
strongSelf.refreshQrToken()
case .loggedIn:
strongSelf.exportTokenDisposable.set(nil)
}
}))
}
}

View File

@ -127,6 +127,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
super.didLoad()
self.listView.view.disablesInteractiveTransitionGestureRecognizer = true
self.listView.view.disablesInteractiveKeyboardGestureRecognizer = true
self.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point in
if let strongSelf = self {
let convertedPoint = strongSelf.listView.view.convert(point, from: strongSelf.view)

View File

@ -55,14 +55,16 @@ private func parseFileLocationResource(_ dict: [AnyHashable: Any]) -> TelegramMe
public struct NotificationViewControllerInitializationData {
public let appGroupPath: String
public let apiId: Int32
public let apiHash: String
public let languagesCategory: String
public let encryptionParameters: (Data, Data)
public let appVersion: String
public let bundleData: Data?
public init(appGroupPath: String, apiId: Int32, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?) {
public init(appGroupPath: String, apiId: Int32, apiHash: String, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?) {
self.appGroupPath = appGroupPath
self.apiId = apiId
self.apiHash = apiHash
self.languagesCategory = languagesCategory
self.encryptionParameters = encryptionParameters
self.appVersion = appVersion
@ -152,7 +154,7 @@ public final class NotificationViewControllerImpl {
return nil
})
sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), rootPath: rootPath, legacyBasePath: nil, legacyCache: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in })
sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), rootPath: rootPath, legacyBasePath: nil, legacyCache: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in })
presentationDataPromise.set(sharedAccountContext!.presentationData)
}

View File

@ -48,14 +48,16 @@ private enum ShareAuthorizationError {
public struct ShareRootControllerInitializationData {
public let appGroupPath: String
public let apiId: Int32
public let apiHash: String
public let languagesCategory: String
public let encryptionParameters: (Data, Data)
public let appVersion: String
public let bundleData: Data?
public init(appGroupPath: String, apiId: Int32, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?) {
public init(appGroupPath: String, apiId: Int32, apiHash: String, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?) {
self.appGroupPath = appGroupPath
self.apiId = apiId
self.apiHash = apiHash
self.languagesCategory = languagesCategory
self.encryptionParameters = encryptionParameters
self.appVersion = appVersion
@ -190,7 +192,7 @@ public class ShareRootControllerImpl {
return nil
})
let sharedContext = SharedAccountContextImpl(mainWindow: nil, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), rootPath: rootPath, legacyBasePath: nil, legacyCache: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in })
let sharedContext = SharedAccountContextImpl(mainWindow: nil, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), rootPath: rootPath, legacyBasePath: nil, legacyCache: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in })
presentationDataPromise.set(sharedContext.presentationData)
internalContext = InternalContext(sharedContext: sharedContext)
globalInternalContext = internalContext