Fixes session expiring when downloading apps on slow connection

This commit is contained in:
Riley Testut
2020-01-08 12:41:02 -08:00
parent e3ea200ad5
commit e0a899ee9a
10 changed files with 346 additions and 282 deletions

View File

@@ -29,7 +29,7 @@ class AppOperationContext
var app: ALTApplication?
var resignedApp: ALTApplication?
var connection: NWConnection?
var installationConnection: ServerConnection?
var installedApp: InstalledApp? {
didSet {

View File

@@ -55,7 +55,7 @@ class AuthenticationOperation: ResultOperation<(ALTSigner, ALTAppleAPISession)>
private var signer: ALTSigner?
private var session: ALTAppleAPISession?
private let dispatchQueue = DispatchQueue(label: "com.altstore.AuthenticationOperation")
private let operationQueue = OperationQueue()
private var submitCodeAction: UIAlertAction?
@@ -66,6 +66,7 @@ class AuthenticationOperation: ResultOperation<(ALTSigner, ALTAppleAPISession)>
super.init()
self.operationQueue.name = "com.altstore.AuthenticationOperation"
self.progress.totalUnitCount = 3
}
@@ -221,33 +222,6 @@ private extension AuthenticationOperation
private extension AuthenticationOperation
{
func connect(to server: Server, completionHandler: @escaping (Result<NWConnection, Error>) -> Void)
{
let connection = NWConnection(to: .service(name: server.service.name, type: server.service.type, domain: server.service.domain, interface: nil), using: .tcp)
connection.stateUpdateHandler = { [unowned connection] (state) in
switch state
{
case .failed(let error):
print("Failed to connect to service \(server.service.name).", error)
completionHandler(.failure(ConnectionError.connectionFailed))
case .cancelled:
completionHandler(.failure(OperationError.cancelled))
case .ready:
completionHandler(.success(connection))
case .waiting: break
case .setup: break
case .preparing: break
@unknown default: break
}
}
connection.start(queue: self.dispatchQueue)
}
func signIn(completionHandler: @escaping (Result<(ALTAccount, ALTAppleAPISession), Swift.Error>) -> Void)
{
func authenticate()
@@ -307,98 +281,73 @@ private extension AuthenticationOperation
func authenticate(appleID: String, password: String, completionHandler: @escaping (Result<(ALTAccount, ALTAppleAPISession), Swift.Error>) -> Void)
{
guard let server = self.group.server else { return completionHandler(.failure(OperationError.invalidParameters)) }
self.connect(to: server) { (result) in
let fetchAnisetteDataOperation = FetchAnisetteDataOperation(group: self.group)
fetchAnisetteDataOperation.resultHandler = { (result) in
switch result
{
case .failure(let error): completionHandler(.failure(error))
case .success(let connection):
case .success(let anisetteData):
let verificationHandler: ((@escaping (String?) -> Void) -> Void)?
let request = AnisetteDataRequest()
server.send(request, via: connection) { (result) in
switch result
{
case .failure(let error): completionHandler(.failure(error))
case .success:
server.receiveResponse(from: connection) { (result) in
switch result
if let presentingViewController = self.presentingViewController
{
verificationHandler = { (completionHandler) in
DispatchQueue.main.async {
let alertController = UIAlertController(title: NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: ""), message: nil, preferredStyle: .alert)
alertController.addTextField { (textField) in
textField.autocorrectionType = .no
textField.autocapitalizationType = .none
textField.keyboardType = .numberPad
NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationOperation.textFieldTextDidChange(_:)), name: UITextField.textDidChangeNotification, object: textField)
}
let submitAction = UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default) { (action) in
let textField = alertController.textFields?.first
let code = textField?.text ?? ""
completionHandler(code)
}
submitAction.isEnabled = false
alertController.addAction(submitAction)
self.submitCodeAction = submitAction
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("Cancel"), style: .cancel) { (action) in
completionHandler(nil)
})
if self.navigationController.presentingViewController != nil
{
case .failure(let error):
completionHandler(.failure(error))
case .success(.error(let response)):
completionHandler(.failure(response.error))
case .success(.anisetteData(let response)):
let verificationHandler: ((@escaping (String?) -> Void) -> Void)?
if let presentingViewController = self.presentingViewController
{
verificationHandler = { (completionHandler) in
DispatchQueue.main.async {
let alertController = UIAlertController(title: NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: ""),
message: nil, preferredStyle: .alert)
alertController.addTextField { (textField) in
textField.autocorrectionType = .no
textField.autocapitalizationType = .none
textField.keyboardType = .numberPad
NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationOperation.textFieldTextDidChange(_:)), name: UITextField.textDidChangeNotification, object: textField)
}
let submitAction = UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default) { (action) in
let textField = alertController.textFields?.first
let code = textField?.text ?? ""
completionHandler(code)
}
submitAction.isEnabled = false
alertController.addAction(submitAction)
self.submitCodeAction = submitAction
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("Cancel"), style: .cancel) { (action) in
completionHandler(nil)
})
if self.navigationController.presentingViewController != nil
{
self.navigationController.present(alertController, animated: true, completion: nil)
}
else
{
presentingViewController.present(alertController, animated: true, completion: nil)
}
}
}
}
else
{
// No view controller to present security code alert, so don't provide verificationHandler.
verificationHandler = nil
}
ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: response.anisetteData,
verificationHandler: verificationHandler) { (account, session, error) in
if let account = account, let session = session
{
completionHandler(.success((account, session)))
}
else
{
completionHandler(.failure(error ?? OperationError.unknown))
}
}
case .success:
completionHandler(.failure(ALTServerError(.unknownRequest)))
self.navigationController.present(alertController, animated: true, completion: nil)
}
else
{
presentingViewController.present(alertController, animated: true, completion: nil)
}
}
}
}
else
{
// No view controller to present security code alert, so don't provide verificationHandler.
verificationHandler = nil
}
ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: anisetteData,
verificationHandler: verificationHandler) { (account, session, error) in
if let account = account, let session = session
{
completionHandler(.success((account, session)))
}
else
{
completionHandler(.failure(error ?? OperationError.unknown))
}
}
}
}
self.operationQueue.addOperation(fetchAnisetteDataOperation)
}
func fetchTeam(for account: ALTAccount, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTTeam, Swift.Error>) -> Void)

View File

@@ -0,0 +1,65 @@
//
// FetchAnisetteDataOperation.swift
// AltStore
//
// Created by Riley Testut on 1/7/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
import AltSign
import AltKit
import Roxas
@objc(FetchAnisetteDataOperation)
class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>
{
let group: OperationGroup
init(group: OperationGroup)
{
self.group = group
super.init()
}
override func main()
{
super.main()
if let error = self.group.error
{
self.finish(.failure(error))
return
}
guard let server = self.group.server else { return self.finish(.failure(OperationError.invalidParameters)) }
ServerManager.shared.connect(to: server) { (result) in
switch result
{
case .failure(let error): self.finish(.failure(error))
case .success(let connection):
let request = AnisetteDataRequest()
connection.send(request) { (result) in
switch result
{
case .failure(let error): self.finish(.failure(error))
case .success:
connection.receiveResponse() { (result) in
switch result
{
case .failure(let error): self.finish(.failure(error))
case .success(.error(let response)): self.finish(.failure(response.error))
case .success(.anisetteData(let response)): self.finish(.success(response.anisetteData))
case .success: self.finish(.failure(ALTServerError(.unknownRequest)))
}
}
}
}
}
}
}
}

View File

@@ -41,8 +41,7 @@ class InstallAppOperation: ResultOperation<InstalledApp>
guard
let resignedApp = self.context.resignedApp,
let connection = self.context.connection,
let server = self.context.group.server
let connection = self.context.installationConnection
else { return self.finish(.failure(OperationError.invalidParameters)) }
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
@@ -73,13 +72,13 @@ class InstallAppOperation: ResultOperation<InstalledApp>
self.context.group.beginInstallationHandler?(installedApp)
let request = BeginInstallationRequest()
server.send(request, via: connection) { (result) in
connection.send(request) { (result) in
switch result
{
case .failure(let error): self.finish(.failure(error))
case .success:
self.receive(from: connection, server: server) { (result) in
self.receive(from: connection) { (result) in
switch result
{
case .success:
@@ -107,9 +106,9 @@ class InstallAppOperation: ResultOperation<InstalledApp>
private extension InstallAppOperation
{
func receive(from connection: NWConnection, server: Server, completionHandler: @escaping (Result<Void, Error>) -> Void)
func receive(from connection: ServerConnection, completionHandler: @escaping (Result<Void, Error>) -> Void)
{
server.receiveResponse(from: connection) { (result) in
connection.receiveResponse() { (result) in
do
{
let response = try result.get()
@@ -126,7 +125,7 @@ private extension InstallAppOperation
else
{
self.progress.completedUnitCount = Int64(response.progress * 100)
self.receive(from: connection, server: server, completionHandler: completionHandler)
self.receive(from: connection, completionHandler: completionHandler)
}
case .error(let response):

View File

@@ -12,13 +12,13 @@ import Network
import AltKit
@objc(SendAppOperation)
class SendAppOperation: ResultOperation<NWConnection>
class SendAppOperation: ResultOperation<ServerConnection>
{
let context: AppOperationContext
private let dispatchQueue = DispatchQueue(label: "com.altstore.SendAppOperation")
private var connection: NWConnection?
private var serverConnection: ServerConnection?
init(context: AppOperationContext)
{
@@ -45,21 +45,21 @@ class SendAppOperation: ResultOperation<NWConnection>
let fileURL = InstalledApp.refreshedIPAURL(for: app)
// Connect to server.
self.connect(to: server) { (result) in
ServerManager.shared.connect(to: server) { (result) in
switch result
{
case .failure(let error): self.finish(.failure(error))
case .success(let connection):
self.connection = connection
case .success(let serverConnection):
self.serverConnection = serverConnection
// Send app to server.
self.sendApp(at: fileURL, via: connection, server: server) { (result) in
self.sendApp(at: fileURL, via: serverConnection) { (result) in
switch result
{
case .failure(let error): self.finish(.failure(error))
case .success:
self.progress.completedUnitCount += 1
self.finish(.success(connection))
self.finish(.success(serverConnection))
}
}
}
@@ -69,34 +69,7 @@ class SendAppOperation: ResultOperation<NWConnection>
private extension SendAppOperation
{
func connect(to server: Server, completionHandler: @escaping (Result<NWConnection, Error>) -> Void)
{
let connection = NWConnection(to: .service(name: server.service.name, type: server.service.type, domain: server.service.domain, interface: nil), using: .tcp)
connection.stateUpdateHandler = { [unowned connection] (state) in
switch state
{
case .failed(let error):
print("Failed to connect to service \(server.service.name).", error)
completionHandler(.failure(ConnectionError.connectionFailed))
case .cancelled:
completionHandler(.failure(OperationError.cancelled))
case .ready:
completionHandler(.success(connection))
case .waiting: break
case .setup: break
case .preparing: break
@unknown default: break
}
}
connection.start(queue: self.dispatchQueue)
}
func sendApp(at fileURL: URL, via connection: NWConnection, server: Server, completionHandler: @escaping (Result<Void, Error>) -> Void)
func sendApp(at fileURL: URL, via connection: ServerConnection, completionHandler: @escaping (Result<Void, Error>) -> Void)
{
do
{
@@ -106,14 +79,14 @@ private extension SendAppOperation
let request = PrepareAppRequest(udid: udid, contentSize: appData.count)
print("Sending request \(request)")
server.send(request, via: connection) { (result) in
connection.send(request) { (result) in
switch result
{
case .failure(let error): completionHandler(.failure(error))
case .success:
print("Sending app data (\(appData.count) bytes)")
server.send(appData, via: connection, prependSize: false) { (result) in
connection.send(appData, prependSize: false) { (result) in
switch result
{
case .failure(let error): completionHandler(.failure(error))