Skip to content
This repository was archived by the owner on Mar 7, 2026. It is now read-only.

Commit b521475

Browse files
authored
Refactor SetupView to use CertServerManager
Added CertServerManager to handle certificate serving and opening. Updated SetupView to use the new manager for SSL certificate generation and server management.
1 parent 8ba2bbc commit b521475

1 file changed

Lines changed: 137 additions & 50 deletions

File tree

Sources/prostore/views/SetupView.swift

Lines changed: 137 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,125 @@ import SwiftUI
22
import UIKit
33
import 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
584
struct 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'.\nEnter 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'.\nEnable '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!\nYou'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
160240
struct 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

Comments
 (0)