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

Commit b82e038

Browse files
authored
Implement certificate status tracking in CertificateView
Added certificate status tracking and updated UI to display status information.
1 parent f814cf6 commit b82e038

1 file changed

Lines changed: 109 additions & 26 deletions

File tree

Sources/prosign/views/CertificateView.swift

Lines changed: 109 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
// CertificateView.swift
21
import SwiftUI
32
import UniformTypeIdentifiers
3+
import ProStoreTools
44
// Centralized types to avoid conflicts
55
struct CertificateFileItem {
66
var name: String = ""
@@ -11,16 +11,52 @@ struct CustomCertificate: Identifiable {
1111
let displayName: String
1212
let folderName: String
1313
}
14+
15+
enum CertStatus {
16+
case loading
17+
case signed(Date?)
18+
case revoked(Date?)
19+
case unknown
20+
}
21+
22+
extension CertStatus {
23+
var description: String {
24+
switch self {
25+
case .loading:
26+
return "Status: Loading"
27+
case .signed:
28+
return "Status: Signed"
29+
case .revoked:
30+
return "Status: Revoked"
31+
case .unknown:
32+
return "Status: Unknown"
33+
}
34+
}
35+
36+
var color: Color {
37+
switch self {
38+
case .loading:
39+
return .black
40+
case .signed:
41+
return .green
42+
case .revoked:
43+
return .red
44+
case .unknown:
45+
return .yellow
46+
}
47+
}
48+
}
1449
// MARK: - CertificateView (List + Add/Edit launchers)
1550
struct CertificateView: View {
1651
@State private var customCertificates: [CustomCertificate] = []
52+
@State private var certStatuses: [String: CertStatus] = [:]
1753
@State private var showAddCertificateSheet = false
1854
@State private var editingCertificate: CustomCertificate? = nil // Used only for edit sheet (.sheet(item:))
1955
@State private var selectedCert: String? = nil
2056
@State private var showingDeleteAlert = false
2157
@State private var certToDelete: CustomCertificate?
2258
@State private var newlyAddedFolder: String? = nil
23-
59+
2460
var body: some View {
2561
// <-- Removed nested NavigationStack to avoid hiding the title from the parent stack
2662
ScrollView {
@@ -32,6 +68,12 @@ struct CertificateView: View {
3268
.font(.title2)
3369
.fontWeight(.semibold)
3470
.foregroundColor(.primary)
71+
if let status = certStatuses[cert.folderName] {
72+
Text(status.description)
73+
.font(.caption)
74+
.fontWeight(.medium)
75+
.foregroundColor(status.color)
76+
}
3577
}
3678
.padding(20)
3779
.frame(maxWidth: .infinity)
@@ -53,7 +95,7 @@ struct CertificateView: View {
5395
UserDefaults.standard.set(selectedCert, forKey: "selectedCertificateFolder")
5496
}
5597
}
56-
98+
5799
HStack {
58100
Button(action: {
59101
// EDIT: trigger identifiable sheet
@@ -66,9 +108,9 @@ struct CertificateView: View {
66108
.background(Color(.systemGray6).opacity(0.8))
67109
.clipShape(Circle())
68110
}
69-
111+
70112
Spacer()
71-
113+
72114
Button(action: {
73115
if customCertificates.count > 1 {
74116
certToDelete = cert
@@ -134,13 +176,53 @@ struct CertificateView: View {
134176
reloadCertificatesAndEnsureSelection()
135177
}
136178
}
137-
179+
138180
private func reloadCertificatesAndEnsureSelection() {
139181
customCertificates = CertificateFileManager.shared.loadCertificates()
140182
selectedCert = UserDefaults.standard.string(forKey: "selectedCertificateFolder")
141183
ensureSelection()
184+
checkStatuses()
185+
}
186+
187+
private func checkStatuses() {
188+
for cert in customCertificates {
189+
let folderName = cert.folderName
190+
certStatuses[folderName] = .loading
191+
192+
let certDir = CertificateFileManager.shared.certificatesDirectory.appendingPathComponent(folderName)
193+
let p12URL = certDir.appendingPathComponent("certificate.p12")
194+
let provURL = certDir.appendingPathComponent("profile.mobileprovision")
195+
let passwordURL = certDir.appendingPathComponent("password.txt")
196+
197+
let p12Password: String
198+
if let passwordData = try? Data(contentsOf: passwordURL),
199+
let passwordStr = String(data: passwordData, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) {
200+
p12Password = passwordStr
201+
} else {
202+
p12Password = ""
203+
}
204+
205+
ProStoreTools.checkRevokage(
206+
p12URL: p12URL,
207+
provURL: provURL,
208+
p12Password: p12Password
209+
) { status, expirationDate, error in
210+
DispatchQueue.main.async {
211+
let newStatus: CertStatus
212+
switch status {
213+
case 0:
214+
newStatus = .signed(expirationDate)
215+
case 1, 2:
216+
newStatus = .revoked(expirationDate)
217+
default:
218+
newStatus = .unknown
219+
}
220+
self.certStatuses[folderName] = newStatus
221+
}
222+
}
223+
}
142224
}
143-
225+
144226
private func ensureSelection() {
145227
if selectedCert == nil || !customCertificates.contains(where: { $0.folderName == selectedCert }) {
146228
if let firstCert = customCertificates.first {
@@ -149,11 +231,11 @@ struct CertificateView: View {
149231
}
150232
}
151233
}
152-
234+
153235
private func deleteCertificate(_ cert: CustomCertificate) {
154236
try? CertificateFileManager.shared.deleteCertificate(folderName: cert.folderName)
155237
customCertificates = CertificateFileManager.shared.loadCertificates()
156-
238+
157239
if selectedCert == cert.folderName {
158240
if let newSelection = customCertificates.first {
159241
selectedCert = newSelection.folderName
@@ -164,14 +246,15 @@ struct CertificateView: View {
164246
}
165247
}
166248
ensureSelection()
249+
checkStatuses()
167250
}
168251
}
169252
// MARK: - Add / Edit View
170253
struct AddCertificateView: View {
171254
@Environment(\.dismiss) private var dismiss
172255
let editingCertificate: CustomCertificate?
173256
let onSave: ((String) -> Void)?
174-
257+
175258
@State private var p12File: CertificateFileItem?
176259
@State private var provFile: CertificateFileItem?
177260
@State private var password = ""
@@ -180,12 +263,12 @@ struct AddCertificateView: View {
180263
@State private var errorMessage = ""
181264
@State private var displayName: String = ""
182265
@State private var hasLoadedForEdit = false
183-
266+
184267
init(editingCertificate: CustomCertificate? = nil, onSave: ((String) -> Void)? = nil) {
185268
self.editingCertificate = editingCertificate
186269
self.onSave = onSave
187270
}
188-
271+
189272
var body: some View {
190273
// Use a NavigationStack inside the sheet so the sheet has its own nav bar
191274
NavigationStack {
@@ -205,7 +288,7 @@ struct AddCertificateView: View {
205288
}
206289
}
207290
.disabled(isChecking)
208-
291+
209292
Button(action: { activeSheet = .prov }) {
210293
HStack {
211294
Image(systemName: "gearshape.fill")
@@ -221,20 +304,20 @@ struct AddCertificateView: View {
221304
}
222305
.disabled(isChecking)
223306
}
224-
307+
225308
Section(header: Text("Display Name")) {
226309
TextField("Optional Display Name", text: $displayName)
227310
.disabled(isChecking)
228311
}
229-
312+
230313
Section(header: Text("Password")) {
231314
SecureField("Enter Password", text: $password)
232315
.disabled(isChecking)
233316
Text("Enter the password for the certificate. Leave it blank if there is no password needed.")
234317
.font(.caption)
235318
.foregroundColor(.secondary)
236319
}
237-
320+
238321
if !errorMessage.isEmpty {
239322
Text(errorMessage)
240323
.foregroundColor(.red)
@@ -250,7 +333,7 @@ struct AddCertificateView: View {
250333
}
251334
.disabled(isChecking)
252335
}
253-
336+
254337
ToolbarItem(placement: .navigationBarTrailing) {
255338
if isChecking {
256339
ProgressView()
@@ -283,17 +366,17 @@ struct AddCertificateView: View {
283366
}
284367
} // NavigationStack
285368
}
286-
369+
287370
private func loadForEdit(cert: CustomCertificate) {
288371
let certFolder = CertificateFileManager.shared.certificatesDirectory.appendingPathComponent(cert.folderName)
289372
let p12URL = certFolder.appendingPathComponent("certificate.p12")
290373
let provURL = certFolder.appendingPathComponent("profile.mobileprovision")
291374
let passwordURL = certFolder.appendingPathComponent("password.txt")
292375
let nameURL = certFolder.appendingPathComponent("name.txt")
293-
376+
294377
p12File = CertificateFileItem(name: "certificate.p12", url: p12URL)
295378
provFile = CertificateFileItem(name: "profile.mobileprovision", url: provURL)
296-
379+
297380
if let pwData = try? Data(contentsOf: passwordURL), let pw = String(data: pwData, encoding: .utf8) {
298381
password = pw
299382
}
@@ -303,10 +386,10 @@ struct AddCertificateView: View {
303386
}
304387
private func saveCertificate() {
305388
guard let p12URL = p12File?.url, let provURL = provFile?.url else { return }
306-
389+
307390
isChecking = true
308391
errorMessage = ""
309-
392+
310393
let workItem: DispatchWorkItem = DispatchWorkItem {
311394
do {
312395
var p12Data: Data
@@ -326,17 +409,17 @@ struct AddCertificateView: View {
326409
p12Data = try Data(contentsOf: p12URL)
327410
provData = try Data(contentsOf: provURL)
328411
}
329-
412+
330413
let checkResult = CertificatesManager.check(p12Data: p12Data, password: self.password, mobileProvisionData: provData)
331414
var dispatchError: String?
332-
415+
333416
switch checkResult {
334417
case .success(.success):
335418
// Generate displayName from cert if not set
336419
if localDisplayName.isEmpty {
337420
localDisplayName = CertificatesManager.getCertificateName(mobileProvisionData: provData) ?? "Custom Certificate"
338421
}
339-
422+
340423
if let folder = self.editingCertificate?.folderName {
341424
try CertificateFileManager.shared.updateCertificate(folderName: folder, p12Data: p12Data, provData: provData, password: self.password, displayName: localDisplayName)
342425
} else {
@@ -350,7 +433,7 @@ struct AddCertificateView: View {
350433
case .failure(let error):
351434
dispatchError = "Error: \(error.localizedDescription)"
352435
}
353-
436+
354437
DispatchQueue.main.async {
355438
self.isChecking = false
356439
if let err = dispatchError {

0 commit comments

Comments
 (0)