2019-07-30 16:54:44 -07:00
//
// L a u n c h V i e w C o n t r o l l e r . s w i f t
// A l t S t o r e
//
// C r e a t e d b y R i l e y T e s t u t o n 7 / 3 0 / 1 9 .
// C o p y r i g h t © 2 0 1 9 R i l e y T e s t u t . A l l r i g h t s r e s e r v e d .
//
import UIKit
import Roxas
2022-11-02 17:58:59 -07:00
import EmotionalDamage
import minimuxer
2023-09-08 15:04:03 -05:00
import WidgetKit
2019-07-30 16:54:44 -07:00
2020-09-03 16:39:08 -07:00
import AltStoreCore
2022-11-16 13:14:04 -07:00
import UniformTypeIdentifiers
2020-09-03 16:39:08 -07:00
2023-04-01 16:02:12 -07:00
let pairingFileName = " ALTPairingFile.mobiledevicepairing "
2023-01-04 09:52:12 -05:00
final class LaunchViewController : RSTLaunchViewController , UIDocumentPickerDelegate
2019-07-30 16:54:44 -07:00
{
2019-11-04 12:32:36 -08:00
private var didFinishLaunching = false
2024-02-15 19:31:11 -06:00
private var destinationViewController : TabBarController !
2020-01-13 13:53:04 -08:00
2019-07-30 16:54:44 -07:00
override var launchConditions : [ RSTLaunchCondition ] {
let isDatabaseStarted = RSTLaunchCondition ( condition : { DatabaseManager . shared . isStarted } ) { ( completionHandler ) in
DatabaseManager . shared . start ( completionHandler : completionHandler )
}
return [ isDatabaseStarted ]
}
2019-11-04 12:32:36 -08:00
override var childForStatusBarStyle : UIViewController ? {
return self . children . first
}
override var childForStatusBarHidden : UIViewController ? {
return self . children . first
}
2020-01-13 13:53:04 -08:00
override func viewDidLoad ( )
{
2022-11-08 14:15:09 -05:00
defer {
// C r e a t e d e s t i n a t i o n V i e w C o n t r o l l e r n o w s o v i e w c o n t r o l l e r s c a n r e g i s t e r f o r r e c e i v i n g N o t i f i c a t i o n s .
self . destinationViewController = self . storyboard ! . instantiateViewController ( withIdentifier : " tabBarController " ) as ! TabBarController
}
2020-01-13 13:53:04 -08:00
super . viewDidLoad ( )
2022-11-13 17:40:33 -08:00
}
override func viewDidAppear ( _ animated : Bool ) {
super . viewDidAppear ( true )
2024-06-17 09:43:25 +10:00
if #available ( iOS 17 , * ) , ! UserDefaults . standard . sidejitenable {
DispatchQueue . global ( ) . async {
self . isSideJITServerDetected ( ) { result in
DispatchQueue . main . async {
switch result {
case . success ( ) :
let dialogMessage = UIAlertController ( title : " SideJITServer Detected " , message : " Would you like to enable SideJITServer " , preferredStyle : . alert )
// C r e a t e O K b u t t o n w i t h a c t i o n h a n d l e r
let ok = UIAlertAction ( title : " OK " , style : . default , handler : { ( action ) -> Void in
UserDefaults . standard . sidejitenable = true
} )
let cancel = UIAlertAction ( title : " Cancel " , style : . cancel )
// A d d O K b u t t o n t o a d i a l o g m e s s a g e
dialogMessage . addAction ( ok )
dialogMessage . addAction ( cancel )
// P r e s e n t A l e r t t o
self . present ( dialogMessage , animated : true , completion : nil )
case . failure ( _ ) :
print ( " Cannot find sideJITServer " )
}
}
}
}
}
if #available ( iOS 17 , * ) , UserDefaults . standard . sidejitenable {
DispatchQueue . global ( ) . async {
self . askfornetwork ( )
}
print ( " SideJITServer Enabled " )
}
2023-01-04 09:32:04 -05:00
#if ! targetEnvironment ( simulator )
2022-11-08 14:15:09 -05:00
start_em_proxy ( bind_addr : Consts . Proxy . serverURL )
2022-11-02 17:58:59 -07:00
2022-11-08 14:15:09 -05:00
guard let pf = fetchPairingFile ( ) else {
2022-11-13 17:40:33 -08:00
displayError ( " Device pairing file not found. " )
2022-11-08 14:15:09 -05:00
return
}
2022-11-16 13:14:04 -07:00
start_minimuxer_threads ( pf )
2023-01-04 09:32:04 -05:00
#endif
2022-11-08 14:15:09 -05:00
}
2024-06-17 09:43:25 +10:00
func askfornetwork ( ) {
let address = UserDefaults . standard . textInputSideJITServerurl ? ? " "
var SJSURL = address
if ( UserDefaults . standard . textInputSideJITServerurl ? ? " " ) . isEmpty {
SJSURL = " http://sidejitserver._http._tcp.local:8080 "
}
// C r e a t e a n e t w o r k o p e r a t i o n a t l a u n c h t o R e f r e s h S i d e J I T S e r v e r
let url = URL ( string : " \( SJSURL ) /re/ " ) !
let task = URLSession . shared . dataTask ( with : url ) { ( data , response , error ) in
print ( data )
}
task . resume ( )
}
func isSideJITServerDetected ( completion : @ escaping ( Result < Void , Error > ) -> Void ) {
let address = UserDefaults . standard . textInputSideJITServerurl ? ? " "
var SJSURL = address
if ( UserDefaults . standard . textInputSideJITServerurl ? ? " " ) . isEmpty {
SJSURL = " http://sidejitserver._http._tcp.local:8080 "
}
// C r e a t e a n e t w o r k o p e r a t i o n a t l a u n c h t o R e f r e s h S i d e J I T S e r v e r
let url = URL ( string : SJSURL ) !
let task = URLSession . shared . dataTask ( with : url ) { ( data , response , error ) in
if let error = error {
print ( " No SideJITServer on Network " )
completion ( . failure ( error ) )
return
}
completion ( . success ( ( ) ) )
}
task . resume ( )
return
}
2022-11-08 14:15:09 -05:00
func fetchPairingFile ( ) -> String ? {
let filename = " ALTPairingFile.mobiledevicepairing "
let fm = FileManager . default
let documentsPath = fm . documentsDirectory . appendingPathComponent ( " / \( filename ) " )
if fm . fileExists ( atPath : documentsPath . path ) , let contents = try ? String ( contentsOf : documentsPath ) , ! contents . isEmpty {
print ( " Loaded ALTPairingFile from \( documentsPath . path ) " )
return contents
} else if
let appResourcePath = Bundle . main . url ( forResource : " ALTPairingFile " , withExtension : " mobiledevicepairing " ) ,
fm . fileExists ( atPath : appResourcePath . path ) ,
let data = fm . contents ( atPath : appResourcePath . path ) ,
let contents = String ( data : data , encoding : . utf8 ) ,
2024-02-23 20:11:00 -05:00
! contents . isEmpty ,
! UserDefaults . standard . isPairingReset {
2022-11-08 14:15:09 -05:00
print ( " Loaded ALTPairingFile from \( appResourcePath . path ) " )
return contents
2024-02-23 20:11:00 -05:00
} else if let plistString = Bundle . main . object ( forInfoDictionaryKey : " ALTPairingFile " ) as ? String , ! plistString . isEmpty , ! plistString . contains ( " insert pairing file here " ) , ! UserDefaults . standard . isPairingReset {
2022-11-08 14:15:09 -05:00
print ( " Loaded ALTPairingFile from Info.plist " )
return plistString
} else {
2022-11-16 13:14:04 -07:00
// S h o w a n a l e r t e x p l a i n i n g t h e p a i r i n g f i l e
// C r e a t e n e w A l e r t
2024-08-15 22:20:53 -07:00
let dialogMessage = UIAlertController ( title : " Pairing File " , message : " Select the pairing file or select \" Help \" for help. " , preferredStyle : . alert )
2022-11-16 13:14:04 -07:00
// C r e a t e O K b u t t o n w i t h a c t i o n h a n d l e r
let ok = UIAlertAction ( title : " OK " , style : . default , handler : { ( action ) -> Void in
// T r y t o l o a d i t f r o m a f i l e p i c k e r
var types = UTType . types ( tag : " plist " , tagClass : UTTagClass . filenameExtension , conformingTo : nil )
2022-12-17 21:13:53 +00:00
types . append ( contentsOf : UTType . types ( tag : " mobiledevicepairing " , tagClass : UTTagClass . filenameExtension , conformingTo : UTType . data ) )
types . append ( . xml )
2022-11-16 13:14:04 -07:00
let documentPickerController = UIDocumentPickerViewController ( forOpeningContentTypes : types )
2022-12-03 17:24:07 -05:00
documentPickerController . shouldShowFileExtensions = true
2022-11-16 13:14:04 -07:00
documentPickerController . delegate = self
self . present ( documentPickerController , animated : true , completion : nil )
2024-02-23 20:11:00 -05:00
UserDefaults . standard . isPairingReset = false
2022-11-16 13:14:04 -07:00
} )
2024-08-15 22:20:53 -07:00
// A d d " h e l p " b u t t o n t o t a k e u s e r t o w i k i
let wikiOption = UIAlertAction ( title : " Help " , style : . default ) { ( action ) in
let wikiURL : String = " https://docs.sidestore.io/docs/getting-started/pairing-file "
if let url = URL ( string : wikiURL ) {
UIApplication . shared . open ( url )
}
sleep ( 2 )
exit ( 0 )
}
// A d d b u t t o n s t o d i a l o g m e s s a g e
dialogMessage . addAction ( wikiOption )
2022-11-16 13:14:04 -07:00
dialogMessage . addAction ( ok )
2024-07-12 02:40:26 -07:00
2022-11-16 13:14:04 -07:00
// P r e s e n t A l e r t t o
self . present ( dialogMessage , animated : true , completion : nil )
2024-01-30 10:52:22 +09:00
let dialogMessage2 = UIAlertController ( title : " Analytics " , message : " This app contains anonymous analytics for research and project development. By continuing to use this app, you are consenting to this data collection " , preferredStyle : . alert )
2024-01-30 10:04:33 +09:00
2024-01-30 10:52:22 +09:00
let ok2 = UIAlertAction ( title : " OK " , style : . default , handler : { ( action ) -> Void in } )
2024-01-30 10:04:33 +09:00
2024-01-30 10:52:22 +09:00
dialogMessage2 . addAction ( ok2 )
self . present ( dialogMessage2 , animated : true , completion : nil )
2024-01-30 10:04:33 +09:00
2022-11-08 14:15:09 -05:00
return nil
}
}
2022-11-16 16:41:02 -05:00
2022-11-08 14:15:09 -05:00
func displayError ( _ msg : String ) {
2022-11-13 17:40:33 -08:00
print ( msg )
// C r e a t e a n e w a l e r t
let dialogMessage = UIAlertController ( title : " Error launching SideStore " , message : msg , preferredStyle : . alert )
// P r e s e n t a l e r t t o u s e r
self . present ( dialogMessage , animated : true , completion : nil )
2020-01-13 13:53:04 -08:00
}
2022-11-16 13:14:04 -07:00
func documentPicker ( _ controller : UIDocumentPickerViewController , didPickDocumentsAt urls : [ URL ] ) {
let url = urls [ 0 ]
let isSecuredURL = url . startAccessingSecurityScopedResource ( ) = = true
do {
// R e a d t o a s t r i n g
let data1 = try Data ( contentsOf : urls [ 0 ] )
let pairing_string = String ( bytes : data1 , encoding : . utf8 )
if pairing_string = = nil {
displayError ( " Unable to read pairing file " )
}
// S a v e t o a f i l e f o r n e x t l a u n c h
2023-04-01 16:02:12 -07:00
let pairingFile = FileManager . default . documentsDirectory . appendingPathComponent ( " \( pairingFileName ) " )
try pairing_string ? . write ( to : pairingFile , atomically : true , encoding : String . Encoding . utf8 )
2022-11-16 13:14:04 -07:00
// S t a r t m i n i m u x e r n o w t h a t w e h a v e a f i l e
start_minimuxer_threads ( pairing_string ! )
} catch {
displayError ( " Unable to read pairing file " )
}
if ( isSecuredURL ) {
url . stopAccessingSecurityScopedResource ( )
}
controller . dismiss ( animated : true , completion : nil )
}
func documentPickerWasCancelled ( _ controller : UIDocumentPickerViewController ) {
2022-12-17 21:13:53 +00:00
displayError ( " Choosing a pairing file was cancelled. Please re-open the app and try again. " )
2022-11-16 13:14:04 -07:00
}
func start_minimuxer_threads ( _ pairing_file : String ) {
2023-04-01 16:02:12 -07:00
target_minimuxer_address ( )
let documentsDirectory = FileManager . default . documentsDirectory . absoluteString
do {
try start ( pairing_file , documentsDirectory )
} catch {
try ! FileManager . default . removeItem ( at : FileManager . default . documentsDirectory . appendingPathComponent ( " \( pairingFileName ) " ) )
2023-04-11 21:04:07 -07:00
displayError ( " minimuxer failed to start, please restart SideStore. \( ( error as ? LocalizedError ) ? . failureReason ? ? " UNKNOWN ERROR!!!!!! REPORT TO GITHUB ISSUES! " ) " )
2022-11-16 13:14:04 -07:00
}
2023-10-20 21:43:51 -04:00
if #available ( iOS 17 , * ) {
// TODO: i O S 1 7 a n d a b o v e h a v e a n e w J I T i m p l e m e n t a t i o n t h a t i s c o m p l e t e l y b r o k e n i n S i d e S t o r e : (
}
else {
start_auto_mounter ( documentsDirectory )
}
2024-02-15 19:31:11 -06:00
// C r e a t e d e s t i n a t i o n V i e w C o n t r o l l e r n o w s o v i e w c o n t r o l l e r s c a n r e g i s t e r f o r r e c e i v i n g N o t i f i c a t i o n s .
self . destinationViewController = self . storyboard ! . instantiateViewController ( withIdentifier : " tabBarController " ) as ? TabBarController
2022-11-16 13:14:04 -07:00
}
2019-07-30 16:54:44 -07:00
}
extension LaunchViewController
{
override func handleLaunchError ( _ error : Error )
{
do
{
throw error
}
catch let error as NSError
{
2022-11-05 23:50:07 -07:00
let title = error . userInfo [ NSLocalizedFailureErrorKey ] as ? String ? ? NSLocalizedString ( " Unable to Launch SideStore " , comment : " " )
2019-07-30 16:54:44 -07:00
2022-09-20 13:19:17 -05:00
let errorDescription : String
if #available ( iOS 14.5 , * )
{
let errorMessages = [ error . debugDescription ] + error . underlyingErrors . map { ( $0 as NSError ) . debugDescription }
errorDescription = errorMessages . joined ( separator : " \n \n " )
}
else
{
errorDescription = error . debugDescription
}
let alertController = UIAlertController ( title : title , message : errorDescription , preferredStyle : . alert )
2019-07-30 16:54:44 -07:00
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Retry " , comment : " " ) , style : . default , handler : { ( action ) in
self . handleLaunchConditions ( )
} ) )
self . present ( alertController , animated : true , completion : nil )
}
}
override func finishLaunching ( )
{
super . finishLaunching ( )
2019-11-04 12:32:36 -08:00
guard ! self . didFinishLaunching else { return }
2019-07-30 16:54:44 -07:00
AppManager . shared . update ( )
2023-05-16 15:46:37 -05:00
AppManager . shared . updatePatronsIfNeeded ( )
2019-08-28 11:13:22 -07:00
PatreonAPI . shared . refreshPatreonAccount ( )
2019-07-30 16:54:44 -07:00
2023-12-07 17:30:46 -06:00
AppManager . shared . updateAllSources { result in
guard case . failure ( let error ) = result else { return }
Logger . main . error ( " Failed to update sources on launch. \( error . localizedDescription , privacy : . public ) " )
2024-02-15 19:31:11 -06:00
let toastView = ToastView ( error : error )
toastView . addTarget ( self . destinationViewController , action : #selector ( TabBarController . presentSources ) , for : . touchUpInside )
toastView . show ( in : self . destinationViewController . selectedViewController ? ? self . destinationViewController )
2023-12-07 17:30:46 -06:00
}
2023-05-16 15:46:37 -05:00
self . updateKnownSources ( )
2023-09-08 15:04:03 -05:00
WidgetCenter . shared . reloadAllTimelines ( )
2019-11-04 12:32:36 -08:00
// A d d v i e w c o n t r o l l e r a s c h i l d ( r a t h e r t h a n p r e s e n t i n g m o d a l l y )
// s o t i n t a d j u s t m e n t + c a r d p r e s e n t a t i o n s w o r k s c o r r e c t l y .
2020-01-13 13:53:04 -08:00
self . destinationViewController . view . frame = CGRect ( x : 0 , y : 0 , width : self . view . bounds . width , height : self . view . bounds . height )
self . destinationViewController . view . alpha = 0.0
self . addChild ( self . destinationViewController )
self . view . addSubview ( self . destinationViewController . view , pinningEdgesWith : . zero )
self . destinationViewController . didMove ( toParent : self )
2019-11-04 12:32:36 -08:00
UIView . animate ( withDuration : 0.2 ) {
2020-01-13 13:53:04 -08:00
self . destinationViewController . view . alpha = 1.0
2019-11-04 12:32:36 -08:00
}
self . didFinishLaunching = true
2019-07-30 16:54:44 -07:00
}
}
2023-05-16 15:46:37 -05:00
private extension LaunchViewController
{
func updateKnownSources ( )
{
AppManager . shared . updateKnownSources { result in
switch result
{
case . failure ( let error ) : print ( " [ALTLog] Failed to update known sources: " , error )
case . success ( ( _ , let blockedSources ) ) :
DatabaseManager . shared . persistentContainer . performBackgroundTask { context in
let blockedSourceIDs = Set ( blockedSources . lazy . map { $0 . identifier } )
let blockedSourceURLs = Set ( blockedSources . lazy . compactMap { $0 . sourceURL } )
let predicate = NSPredicate ( format : " %K IN %@ OR %K IN %@ " ,
# keyPath ( Source . identifier ) , blockedSourceIDs ,
# keyPath ( Source . sourceURL ) , blockedSourceURLs )
let sourceErrors = Source . all ( satisfying : predicate , in : context ) . map { ( source ) in
let blockedSource = blockedSources . first { $0 . identifier = = source . identifier }
return SourceError . blocked ( source , bundleIDs : blockedSource ? . bundleIDs , existingSource : source )
}
guard ! sourceErrors . isEmpty else { return }
Task {
for error in sourceErrors
{
let title = String ( format : NSLocalizedString ( " “%@” Blocked " , comment : " " ) , error . $ source . name )
let message = [ error . localizedDescription , error . recoverySuggestion ] . compactMap { $0 } . joined ( separator : " \n \n " )
await self . presentAlert ( title : title , message : message )
}
}
}
}
}
}
}