@@ -2,43 +2,125 @@ import SwiftUI
22import UIKit
33import GCDWebServer
44
5+ // MARK: - Cert server manager
6+ final class CertServerManager : ObservableObject {
7+ private var webServer : GCDWebServer ?
8+ private var didServeOnce = false
9+
10+ /// Start a web server that serves certURL at path /ProStore.crt, open the URL in Safari
11+ func serveAndOpen( certURL: URL ) -> Bool {
12+ stopServer ( )
13+ didServeOnce = false
14+
15+ let server = GCDWebServer ( )
16+ self . webServer = server
17+
18+ server. addHandler (
19+ forMethod: " GET " ,
20+ path: " /ProStore.crt " ,
21+ request: GCDWebServerRequest . self,
22+ processBlock: { [ weak self] request in
23+ do {
24+ let data = try Data ( contentsOf: certURL)
25+ let response = GCDWebServerDataResponse ( data: data, contentType: " application/x-x509-ca-cert " )
26+ response. setValue ( " attachment; filename= \" ProStore.crt \" " , forAdditionalHeader: " Content-Disposition " )
27+
28+ DispatchQueue . main. async {
29+ guard let self = self else { return }
30+ if !self . didServeOnce {
31+ self . didServeOnce = true
32+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 2.0 ) {
33+ self . stopServer ( )
34+ }
35+ }
36+ }
37+
38+ return response
39+ } catch {
40+ return GCDWebServerResponse ( statusCode: 500 )
41+ }
42+ }
43+ )
44+
45+ let started = server. start ( withPort: 0 , bonjourName: nil )
46+ guard started, let serverURL = server. serverURL else {
47+ print ( " [CertServerManager] Failed to start web server " )
48+ self . webServer = nil
49+ return false
50+ }
51+
52+ let openURL = serverURL. appendingPathComponent ( " ProStore.crt " )
53+ DispatchQueue . main. async {
54+ UIApplication . shared. open ( openURL, options: [ : ] ) { success in
55+ if !success {
56+ print ( " [CertServerManager] Failed to open URL: \( openURL) " )
57+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 3.0 ) {
58+ self . stopServer ( )
59+ }
60+ } else {
61+ print ( " [CertServerManager] Opened URL: \( openURL) " )
62+ }
63+ }
64+ }
65+
66+ return true
67+ }
68+
69+ func stopServer( ) {
70+ if let server = webServer, server. isRunning {
71+ server. stop ( )
72+ print ( " [CertServerManager] Server stopped " )
73+ }
74+ webServer = nil
75+ didServeOnce = false
76+ }
77+
78+ deinit {
79+ stopServer ( )
80+ }
81+ }
82+
83+ // MARK: - SetupView
584struct SetupView : View {
685 var onComplete : ( ) -> Void
7-
86+
887 @State private var currentPage = 0
988 @State private var isGeneratingCert = false
1089 @State private var certGenerated = false
11-
90+ @State private var showStartFailedAlert = false
91+
92+ @StateObject private var serverManager = CertServerManager ( )
93+
1294 private let pages : [ SetupPage ] = [
1395 SetupPage ( title: " Welcome to ProStore! " ,
1496 subtitle: " Before you begin, follow these steps to make sure ProStore works perfectly. " ,
1597 imageName: " star.fill " ) ,
16-
98+
1799 SetupPage ( title: " Install the SSL Certificate " ,
18100 subtitle: " When the popup appears on the next page, click the 'Close' button. " ,
19101 imageName: " lock.shield " ) ,
20-
102+
21103 SetupPage ( title: " Install the SSL Certificate " ,
22104 subtitle: " ProStore will now automatically generate the SSL certificate and open it for installation. " ,
23105 imageName: " sparkles " ) ,
24-
106+
25107 SetupPage ( title: " Install the SSL Certificate " ,
26108 subtitle: " Go to Settings, tap 'Profile Downloaded', then 'Install'. \n Enter your passcode, and confirm by tapping 'Install' on the popup. " ,
27109 imageName: " checkmark.shield " ) ,
28-
110+
29111 SetupPage ( title: " Install the SSL Certificate " ,
30112 subtitle: " Tap the tick, then navigate to \n 'General → About → Certificate Trust Settings'. \n Enable 'ProStore' under 'Enable Full Trust for Root Certificates'. " ,
31113 imageName: " hand.thumbsup " ) ,
32-
114+
33115 SetupPage ( title: " You're finished! " ,
34116 subtitle: " Thanks for completing the setup! \n You're now ready to use ProStore. " ,
35117 imageName: " party.popper " )
36118 ]
37-
119+
38120 var body : some View {
39121 VStack ( spacing: 20 ) {
40122 Spacer ( )
41-
123+
42124 TabView ( selection: $currentPage) {
43125 ForEach ( 0 ..< pages. count, id: \. self) { index in
44126 VStack ( spacing: 20 ) {
@@ -47,16 +129,16 @@ struct SetupView: View {
47129 . scaledToFit ( )
48130 . frame ( width: 100 , height: 100 )
49131 . foregroundColor ( . accentColor)
50-
132+
51133 Text ( pages [ index] . title)
52134 . font ( . largeTitle)
53135 . bold ( )
54136 . multilineTextAlignment ( . center)
55-
137+
56138 Text ( pages [ index] . subtitle)
57139 . multilineTextAlignment ( . center)
58140 . padding ( . horizontal)
59-
141+
60142 if index == 2 && !certGenerated {
61143 if isGeneratingCert {
62144 ProgressView ( " Generating certificate... " )
@@ -75,9 +157,9 @@ struct SetupView: View {
75157 }
76158 . tabViewStyle ( . page( indexDisplayMode: . automatic) )
77159 . animation ( . easeInOut, value: currentPage)
78-
160+
79161 Spacer ( )
80-
162+
81163 HStack {
82164 if currentPage > 0 {
83165 Button ( " Back " ) {
@@ -87,9 +169,9 @@ struct SetupView: View {
87169 }
88170 . buttonStyle ( . bordered)
89171 }
90-
172+
91173 Spacer ( )
92-
174+
93175 Button ( currentPage == pages. count - 1 ? " Finish " : " Next " ) {
94176 withAnimation {
95177 if currentPage == 2 && !certGenerated {
@@ -105,60 +187,65 @@ struct SetupView: View {
105187 . buttonStyle ( . borderedProminent)
106188 }
107189 . padding ( . horizontal)
108-
190+
109191 Spacer ( minLength: 20 )
110192 }
111193 . padding ( )
194+ . alert ( " Failed to start local web server " , isPresented: $showStartFailedAlert) {
195+ Button ( " OK " , role: . cancel) { }
196+ } message: {
197+ Text ( " Couldn't start the local web server to serve the certificate. Try again or check logs. " )
198+ }
199+ . onDisappear {
200+ serverManager. stopServer ( )
201+ }
112202 }
113-
203+
204+ // MARK: - Certificate generation
114205 private func generateCertificate( ) {
115206 isGeneratingCert = true
207+ certGenerated = false
208+
116209 Task {
117210 do {
118211 let urls = try await GenerateCert . createAndSaveCerts ( )
119-
120- // Find the ProStore.pem file
121- if let proStoreCertURL = urls. first ( where: { $0. lastPathComponent == " ProStore.pem " } ) {
122- openCertificateUsingServer ( certURL: proStoreCertURL)
212+
213+ guard let proStoreCertURL = urls. first ( where: {
214+ let name = $0. lastPathComponent. lowercased ( )
215+ return name == " prostore.pem " || name == " prostore.crt " || name == " prostore.pem.crt "
216+ } ) ?? urls. first else {
217+ throw NSError ( domain: " GenerateCert " , code: - 1 , userInfo: [ NSLocalizedDescriptionKey: " ProStore certificate file not found " ] )
218+ }
219+
220+ let started = serverManager. serveAndOpen ( certURL: proStoreCertURL)
221+
222+ if !started {
223+ showStartFailedAlert = true
224+ Logger . shared. log ( " Failed to start local server for certificate. " )
225+ } else {
226+ certGenerated = true
227+ Logger . shared. log ( " Certificate generated and opened for installation. " )
123228 }
124-
125- certGenerated = true
229+
126230 isGeneratingCert = false
127- Logger . shared. log ( " Certificate generated successfully. " )
128231 } catch {
129232 isGeneratingCert = false
130233 Logger . shared. logError ( error)
131234 }
132235 }
133236 }
134-
135- private func openCertificateUsingServer( certURL: URL ) {
136- let webServer = GCDWebServer ( )
137-
138- webServer. addHandler ( forMethod: " GET " , path: " /ProStore.crt " , requestClass: GCDWebServerRequest . self) { request in
139- do {
140- let data = try Data ( contentsOf: certURL)
141- let response = GCDWebServerDataResponse ( data: data, contentType: " application/x-x509-ca-cert " )
142- response? . setValue ( " attachment; filename= \" ProStore.crt \" " , forAdditionalHeader: " Content-Disposition " )
143- return response
144- } catch {
145- return GCDWebServerResponse ( statusCode: 500 )
146- }
147- }
148-
149- webServer. start ( withPort: 0 , bonjourName: nil )
150-
151- if let serverURL = webServer. serverURL {
152- let openURL = serverURL. appendingPathComponent ( " ProStore.crt " )
153- DispatchQueue . main. async {
154- UIApplication . shared. open ( openURL, options: [ : ] , completionHandler: nil )
155- }
156- }
157- }
158237}
159238
239+ // MARK: - SetupPage model
160240struct SetupPage {
161241 let title : String
162242 let subtitle : String
163243 let imageName : String
164- }
244+ }
245+
246+ // MARK: - Logger stub
247+ final class Logger {
248+ static let shared = Logger ( )
249+ func log( _ msg: String ) { print ( " [Logger] \( msg) " ) }
250+ func logError( _ error: Error ) { print ( " [Logger] error: \( error) " ) }
251+ }
0 commit comments