@@ -10,140 +10,17 @@ import Nuke
1010import IDeviceSwift
1111import OSLog
1212
13- @main
14- struct FeatherApp : App {
15- @UIApplicationDelegateAdaptor ( AppDelegate . self) var appDelegate
16-
17- let heartbeat = HeartbeatManager . shared
18-
19- @StateObject var downloadManager = DownloadManager . shared
20- let storage = Storage . shared
21-
22- var body : some Scene {
23- WindowGroup {
24- VStack {
25- DownloadHeaderView ( downloadManager: downloadManager)
26- . transition ( . move( edge: . top) . combined ( with: . opacity) )
27- VariedTabbarView ( )
28- . environment ( \. managedObjectContext, storage. context)
29- . onOpenURL ( perform: _handleURL)
30- . transition ( . move( edge: . top) . combined ( with: . opacity) )
31- }
32- . animation ( . smooth, value: downloadManager. manualDownloads. description)
33- . onReceive ( NotificationCenter . default. publisher ( for: . heartbeatInvalidHost) ) { _ in
34- DispatchQueue . main. async {
35- UIAlertController . showAlertWithOk (
36- title: " InvalidHostID " ,
37- message: . localized( " Your pairing file is invalid and is incompatible with your device, please import a valid pairing file. " )
38- )
39- }
40- }
41- // dear god help me
42- . onAppear {
43- if let style = UIUserInterfaceStyle ( rawValue: UserDefaults . standard. integer ( forKey: " Feather.userInterfaceStyle " ) ) {
44- UIApplication . topViewController ( ) ? . view. window? . overrideUserInterfaceStyle = style
45- }
46-
47- UIApplication . topViewController ( ) ? . view. window? . tintColor = UIColor ( Color ( hex: UserDefaults . standard. string ( forKey: " Feather.userTintColor " ) ?? " #848ef9 " ) )
48- }
49- }
50- }
51-
52- private func _handleURL( _ url: URL ) {
53- if url. scheme == " feather " {
54- /// feather://import-certificate?p12=<base64>&mobileprovision=<base64>&password=<base64>
55- if url. host == " import-certificate " {
56- guard
57- let components = URLComponents ( url: url, resolvingAgainstBaseURL: false ) ,
58- let queryItems = components. queryItems
59- else {
60- return
61- }
62-
63- func queryValue( _ name: String ) -> String ? {
64- queryItems. first ( where: { $0. name == name } ) ? . value? . removingPercentEncoding
65- }
66-
67- guard
68- let p12Base64 = queryValue ( " p12 " ) ,
69- let provisionBase64 = queryValue ( " mobileprovision " ) ,
70- let passwordBase64 = queryValue ( " password " ) ,
71- let passwordData = Data ( base64Encoded: passwordBase64) ,
72- let password = String ( data: passwordData, encoding: . utf8)
73- else {
74- return
75- }
76-
77- let generator = UINotificationFeedbackGenerator ( )
78- generator. prepare ( )
79-
80- guard
81- let p12URL = FileManager . default. decodeAndWrite ( base64: p12Base64, pathComponent: " .p12 " ) ,
82- let provisionURL = FileManager . default. decodeAndWrite ( base64: provisionBase64, pathComponent: " .mobileprovision " ) ,
83- FR . checkPasswordForCertificate ( for: p12URL, with: password, using: provisionURL)
84- else {
85- generator. notificationOccurred ( . error)
86- return
87- }
88-
89- FR . handleCertificateFiles (
90- p12URL: p12URL,
91- provisionURL: provisionURL,
92- p12Password: password
93- ) { error in
94- if let error = error {
95- UIAlertController . showAlertWithOk ( title: . localized( " Error " ) , message: error. localizedDescription)
96- } else {
97- generator. notificationOccurred ( . success)
98- }
99- }
100-
101- return
102- }
103- /// feather://export-certificate?callback_template=<template>
104- /// ?callback_template=: This is how we callback to the application requesting the certificate, this will be a url scheme
105- /// example: livecontainer%3A%2F%2Fcertificate%3Fcert%3D%24%28BASE64_CERT%29%26password%3D%24%28PASSWORD%29
106- /// decoded: livecontainer://certificate?cert=$(BASE64_CERT)&password=$(PASSWORD)
107- /// $(BASE64_CERT) and $(PASSWORD) must be presenting in the callback template so we can replace them with the proper content
108- if url. host == " export-certificate " {
109- guard
110- let components = URLComponents ( url: url, resolvingAgainstBaseURL: false )
111- else {
112- return
113- }
114-
115- let queryItems = components. queryItems? . reduce ( into: [ String: String] ( ) ) { $0 [ $1. name. lowercased ( ) ] = $1. value } ?? [ : ]
116- guard let callbackTemplate = queryItems [ " callback_template " ] ? . removingPercentEncoding else { return }
117-
118- FR . exportCertificateAndOpenUrl ( using: callbackTemplate)
119- }
120- /// feather://source/<url>
121- if let fullPath = url. validatedScheme ( after: " /source/ " ) {
122- FR . handleSource ( fullPath) { }
123- }
124- /// feather://install/<url.ipa>
125- if
126- let fullPath = url. validatedScheme ( after: " /install/ " ) ,
127- let downloadURL = URL ( string: fullPath)
128- {
129- _ = DownloadManager . shared. startDownload ( from: downloadURL)
130- }
131- } else {
132- if url. pathExtension == " ipa " || url. pathExtension == " tipa " {
133- if FileManager . default. isFileFromFileProvider ( at: url) {
134- guard url. startAccessingSecurityScopedResource ( ) else { return }
135- FR . handlePackageFile ( url) { _ in }
136- } else {
137- FR . handlePackageFile ( url) { _ in }
138- }
139-
140- return
141- }
142- }
13+ @main enum Entry {
14+ static func main( ) {
15+ let delegate = AppDelegate ( )
16+ UIApplication . shared. delegate = delegate
17+ _ = UIApplicationMain ( CommandLine . argc, CommandLine . unsafeArgv, nil , NSStringFromClass ( AppDelegate . self) )
14318 }
14419}
14520
146- class AppDelegate : NSObject , UIApplicationDelegate {
21+ final class AppDelegate : UIResponder , UIApplicationDelegate {
22+ var window : UIWindow ?
23+
14724 func application(
14825 _ application: UIApplication ,
14926 didFinishLaunchingWithOptions launchOptions: [ UIApplication . LaunchOptionsKey : Any ] ? = nil
@@ -152,6 +29,17 @@ class AppDelegate: NSObject, UIApplicationDelegate {
15229 _createDocumentsDirectories ( )
15330 ResetView . clearWorkCache ( )
15431 _addDefaultCertificates ( )
32+
33+ let tc = TabController ( )
34+ window = UIWindow ( frame: UIScreen . main. bounds)
35+ window? . rootViewController = tc
36+ window? . makeKeyAndVisible ( )
37+
38+ DispatchQueue . main. async {
39+ self . window!. tintColor = UIColor ( Color ( hex: UserDefaults . standard. string ( forKey: " Feather.userTintColor " ) ?? " #848ef9 " ) )
40+ self . window!. overrideUserInterfaceStyle = UIUserInterfaceStyle ( rawValue: UserDefaults . standard. integer ( forKey: " Feather.userInterfaceStyle " ) ) ?? . unspecified
41+ }
42+
15543 return true
15644 }
15745
@@ -164,10 +52,13 @@ class AppDelegate: NSObject, UIApplicationDelegate {
16452 config. urlCache = nil
16553 return DataLoader ( configuration: config)
16654 } ( )
167- let dataCache = try ? DataCache ( name: " thewonderofyou.Feather.datacache " ) // disk cache
168- let imageCache = Nuke . ImageCache ( ) // memory cache
55+
56+ let dataCache = try ? DataCache ( name: " \( Bundle . main. bundleIdentifier!) .datacache " )
57+ let imageCache = Nuke . ImageCache ( )
58+
16959 dataCache? . sizeLimit = 500 * 1024 * 1024
17060 imageCache. costLimit = 100 * 1024 * 1024
61+
17162 $0. dataCache = dataCache
17263 $0. imageCache = imageCache
17364 $0. dataLoader = dataLoader
@@ -243,5 +134,83 @@ class AppDelegate: NSObject, UIApplicationDelegate {
243134 Logger . misc. error ( " Failed to list signing-assets: \( error) " )
244135 }
245136 }
137+
138+ func application( _: UIApplication , open url: URL , options _: [ UIApplication . OpenURLOptionsKey : Any ] = [ : ] ) -> Bool {
139+ if url. scheme == " feather " {
140+ /// feather://import-certificate?p12=<base64>&mobileprovision=<base64>&password=<base64>
141+ if url. host == " import-certificate " {
142+ guard
143+ let components = URLComponents ( url: url, resolvingAgainstBaseURL: false ) ,
144+ let queryItems = components. queryItems
145+ else {
146+ return false
147+ }
148+
149+ func queryValue( _ name: String ) -> String ? {
150+ queryItems. first ( where: { $0. name == name } ) ? . value? . removingPercentEncoding
151+ }
152+
153+ guard
154+ let p12Base64 = queryValue ( " p12 " ) ,
155+ let provisionBase64 = queryValue ( " mobileprovision " ) ,
156+ let passwordBase64 = queryValue ( " password " ) ,
157+ let passwordData = Data ( base64Encoded: passwordBase64) ,
158+ let password = String ( data: passwordData, encoding: . utf8)
159+ else {
160+ return false
161+ }
162+
163+ let generator = UINotificationFeedbackGenerator ( )
164+ generator. prepare ( )
165+
166+ guard
167+ let p12URL = FileManager . default. decodeAndWrite ( base64: p12Base64, pathComponent: " .p12 " ) ,
168+ let provisionURL = FileManager . default. decodeAndWrite ( base64: provisionBase64, pathComponent: " .mobileprovision " ) ,
169+ FR . checkPasswordForCertificate ( for: p12URL, with: password, using: provisionURL)
170+ else {
171+ generator. notificationOccurred ( . error)
172+ return false
173+ }
174+
175+ FR . handleCertificateFiles (
176+ p12URL: p12URL,
177+ provisionURL: provisionURL,
178+ p12Password: password
179+ ) { error in
180+ if let error = error {
181+ UIAlertController . showAlertWithOk ( title: . localized( " Error " ) , message: error. localizedDescription)
182+ } else {
183+ generator. notificationOccurred ( . success)
184+ }
185+ }
186+
187+ return true
188+ }
189+ /// feather://source/<url>
190+ if let fullPath = url. validatedScheme ( after: " /source/ " ) {
191+ FR . handleSource ( fullPath) { }
192+ }
193+ /// feather://install/<url.ipa>
194+ if
195+ let fullPath = url. validatedScheme ( after: " /install/ " ) ,
196+ let downloadURL = URL ( string: fullPath)
197+ {
198+ _ = DownloadManager . shared. startDownload ( from: downloadURL)
199+ }
200+ } else {
201+ if url. pathExtension == " ipa " || url. pathExtension == " tipa " {
202+ if FileManager . default. isFileFromFileProvider ( at: url) {
203+ guard url. startAccessingSecurityScopedResource ( ) else { return false }
204+ FR . handlePackageFile ( url) { _ in }
205+ } else {
206+ FR . handlePackageFile ( url) { _ in }
207+ }
208+
209+ return true
210+ }
211+ }
212+
213+ return false
214+ }
246215
247216}
0 commit comments