diff --git a/Package.resolved b/Package.resolved index 450099eed..98582486d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "daa9708223b4b4318fb6448ca44801dfabcddc6f", - "version" : "3.0.0" + "revision" : "59dedf0f4ff1e9147de0806a54c6043861eb0870", + "version" : "3.1.0" } }, { diff --git a/Package.swift b/Package.swift index fa8c4e038..62ed4a886 100644 --- a/Package.swift +++ b/Package.swift @@ -37,8 +37,8 @@ let package = Package( .package(url: "https://github.com/duckduckgo/TrackerRadarKit", exact: "1.2.1"), .package(url: "https://github.com/duckduckgo/sync_crypto", exact: "0.2.0"), .package(url: "https://github.com/gumob/PunycodeSwift.git", exact: "2.1.0"), + .package(url: "https://github.com/duckduckgo/privacy-dashboard", exact: "3.1.0" ), .package(url: "https://github.com/duckduckgo/content-scope-scripts", exact: "4.52.0"), - .package(url: "https://github.com/duckduckgo/privacy-dashboard", exact: "3.0.0"), .package(url: "https://github.com/httpswift/swifter.git", exact: "1.5.0"), .package(url: "https://github.com/duckduckgo/bloom_cpp.git", exact: "3.0.0"), .package(url: "https://github.com/duckduckgo/wireguard-apple", exact: "1.1.1") diff --git a/Sources/PrivacyDashboard/Model/CookieConsentInfo.swift b/Sources/PrivacyDashboard/Model/CookieConsentInfo.swift index c86872932..475d24931 100644 --- a/Sources/PrivacyDashboard/Model/CookieConsentInfo.swift +++ b/Sources/PrivacyDashboard/Model/CookieConsentInfo.swift @@ -24,7 +24,7 @@ public struct CookieConsentInfo: Encodable { let optoutFailed: Bool? let selftestFailed: Bool? let configurable = true - + public init(consentManaged: Bool, cosmetic: Bool?, optoutFailed: Bool?, selftestFailed: Bool?) { self.consentManaged = consentManaged self.cosmetic = cosmetic diff --git a/Sources/PrivacyDashboard/Model/ProtectionStatus.swift b/Sources/PrivacyDashboard/Model/ProtectionStatus.swift index 22020c81b..2fc310faa 100644 --- a/Sources/PrivacyDashboard/Model/ProtectionStatus.swift +++ b/Sources/PrivacyDashboard/Model/ProtectionStatus.swift @@ -20,7 +20,7 @@ import Foundation public struct ProtectionStatus: Encodable { - + let unprotectedTemporary: Bool let enabledFeatures: [String] let allowlisted: Bool diff --git a/Sources/PrivacyDashboard/Model/TrackerInfo.swift b/Sources/PrivacyDashboard/Model/TrackerInfo.swift index 9782b349a..8b5b051b7 100644 --- a/Sources/PrivacyDashboard/Model/TrackerInfo.swift +++ b/Sources/PrivacyDashboard/Model/TrackerInfo.swift @@ -26,11 +26,11 @@ public struct TrackerInfo: Encodable { case requests case installedSurrogates } - + public private (set) var trackers = Set() private(set) var thirdPartyRequests = Set() public private(set) var installedSurrogates = Set() - + public init() { } // MARK: - Collecting detected elements @@ -43,12 +43,12 @@ public struct TrackerInfo: Encodable { public mutating func add(detectedThirdPartyRequest request: DetectedRequest) { thirdPartyRequests.insert(request) } - + public mutating func addInstalledSurrogateHost(_ host: String, for tracker: DetectedRequest, onPageWithURL url: URL) { guard tracker.pageUrl == url.absoluteString else { return } installedSurrogates.insert(host) } - + // MARK: - Helper accessors public var trackersBlocked: [DetectedRequest] { @@ -67,5 +67,5 @@ public struct TrackerInfo: Encodable { try container.encode(allRequests, forKey: .requests) try container.encode(installedSurrogates, forKey: .installedSurrogates) } - + } diff --git a/Sources/PrivacyDashboard/PrivacyDashboardController.swift b/Sources/PrivacyDashboard/PrivacyDashboardController.swift index 085302444..9c715d5ef 100644 --- a/Sources/PrivacyDashboard/PrivacyDashboardController.swift +++ b/Sources/PrivacyDashboard/PrivacyDashboardController.swift @@ -26,58 +26,77 @@ public enum PrivacyDashboardOpenSettingsTarget: String { case cookiePopupManagement = "cpm" } -public protocol PrivacyDashboardControllerDelegate: AnyObject { - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, - didChangeProtectionSwitch protectionState: ProtectionState) - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didRequestOpenUrlInNewTab url: URL) - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, - didRequestOpenSettings target: PrivacyDashboardOpenSettingsTarget) - +/// Navigation delegate for the pages provided by the PrivacyDashboardController +public protocol PrivacyDashboardNavigationDelegate: AnyObject { #if os(iOS) func privacyDashboardControllerDidTapClose(_ privacyDashboardController: PrivacyDashboardController) - func privacyDashboardControllerDidRequestShowReportBrokenSite(_ privacyDashboardController: PrivacyDashboardController) #endif - -#if os(macOS) + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didSetHeight height: Int) - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, +} + +/// `Report broken site` web page delegate +public protocol PrivacyDashboardReportBrokenSiteDelegate: AnyObject { + + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didRequestSubmitBrokenSiteReportWithCategory category: String, description: String) - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, - didSetPermission permissionName: String, - to state: PermissionAuthorizationState) - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, - setPermission permissionName: String, - paused: Bool) -#endif + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + reportBrokenSiteDidChangeProtectionSwitch protectionState: ProtectionState) +} + +/// `Privacy Dasboard` web page delegate +public protocol PrivacyDashboardControllerDelegate: AnyObject { + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + didChangeProtectionSwitch protectionState: ProtectionState) + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + didRequestOpenUrlInNewTab url: URL) + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + didRequestOpenSettings target: PrivacyDashboardOpenSettingsTarget) + func privacyDashboardControllerDidRequestShowReportBrokenSite(_ privacyDashboardController: PrivacyDashboardController) + +#if os(macOS) + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + didSetPermission permissionName: String, to state: PermissionAuthorizationState) + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + setPermission permissionName: String, paused: Bool) +#endif } -@MainActor -public final class PrivacyDashboardController: NSObject { +/// This controller provides two type of user experiences +/// 1- `Privacy Dashboard` with the possibility to navigate to the `Report broken site` page +/// 2- Direct access to the `Report broken site` page +/// Which flow is used is decided at `setup(...)` time, where if `reportBrokenSiteOnly` is true then the `Report broken site` page is opened directly. +@MainActor public final class PrivacyDashboardController: NSObject { - public weak var delegate: PrivacyDashboardControllerDelegate? + // Delegates + public weak var privacyDashboardDelegate: PrivacyDashboardControllerDelegate? + public weak var privacyDashboardNavigationDelegate: PrivacyDashboardNavigationDelegate? + public weak var privacyDashboardReportBrokenSiteDelegate: PrivacyDashboardReportBrokenSiteDelegate? @Published public var theme: PrivacyDashboardTheme? public var preferredLocale: String? @Published public var allowedPermissions: [AllowedPermission] = [] - public private(set) weak var privacyInfo: PrivacyInfo? - private weak var webView: WKWebView? + private weak var webView: WKWebView? private let privacyDashboardScript = PrivacyDashboardUserScript() private var cancellables = Set() - + public init(privacyInfo: PrivacyInfo?) { self.privacyInfo = privacyInfo } - public func setup(for webView: WKWebView) { + /// Configure the webview for showing `Privacy Dasboard` or `Report broken site` + /// - Parameters: + /// - webView: The webview to use + /// - reportBrokenSiteOnly: true = `Report broken site`, false = `Privacy Dasboard` + public func setup(for webView: WKWebView, reportBrokenSiteOnly: Bool) { self.webView = webView - webView.navigationDelegate = self setupPrivacyDashboardUserScript() - loadPrivacyDashboardHTML() + loadPrivacyDashboardHTML(reportBrokenSiteOnly: reportBrokenSiteOnly) } public func updatePrivacyInfo(_ privacyInfo: PrivacyInfo?) { @@ -112,20 +131,25 @@ public final class PrivacyDashboardController: NSObject { privacyDashboardScript.delegate = self webView.configuration.userContentController.addUserScript(privacyDashboardScript.makeWKUserScriptSync()) - + privacyDashboardScript.messageNames.forEach { messageName in webView.configuration.userContentController.add(privacyDashboardScript, name: messageName) } } - private func loadPrivacyDashboardHTML() { - guard let url = Bundle.privacyDashboardURL else { return } + private func loadPrivacyDashboardHTML(reportBrokenSiteOnly: Bool) { + guard var url = Bundle.privacyDashboardURL else { return } + + if reportBrokenSiteOnly { + url = url.appendingParameter(name: "screen", value: ProtectionState.EventOriginScreen.breakageForm.rawValue) + } + webView?.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent().deletingLastPathComponent()) } } extension PrivacyDashboardController: WKNavigationDelegate { - + public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { subscribeToDataModelChanges() @@ -155,7 +179,7 @@ extension PrivacyDashboardController: WKNavigationDelegate { }) .store(in: &cancellables) } - + private func subscribeToTrackerInfo() { privacyInfo?.$trackerInfo .receive(on: DispatchQueue.main) @@ -234,53 +258,55 @@ extension PrivacyDashboardController: WKNavigationDelegate { } extension PrivacyDashboardController: PrivacyDashboardUserScriptDelegate { - + func userScript(_ userScript: PrivacyDashboardUserScript, didRequestOpenSettings target: String) { let settingsTarget = PrivacyDashboardOpenSettingsTarget(rawValue: target) ?? .general - delegate?.privacyDashboardController(self, didRequestOpenSettings: settingsTarget) + privacyDashboardDelegate?.privacyDashboardController(self, didRequestOpenSettings: settingsTarget) } - + func userScript(_ userScript: PrivacyDashboardUserScript, didChangeProtectionState protectionState: ProtectionState) { - delegate?.privacyDashboardController(self, didChangeProtectionSwitch: protectionState) + + switch protectionState.eventOrigin.screen { + case .primaryScreen: + privacyDashboardDelegate?.privacyDashboardController(self, didChangeProtectionSwitch: protectionState) + case .breakageForm: + privacyDashboardReportBrokenSiteDelegate?.privacyDashboardController(self, reportBrokenSiteDidChangeProtectionSwitch: protectionState) + } + } func userScript(_ userScript: PrivacyDashboardUserScript, didRequestOpenUrlInNewTab url: URL) { - delegate?.privacyDashboardController(self, didRequestOpenUrlInNewTab: url) + privacyDashboardDelegate?.privacyDashboardController(self, didRequestOpenUrlInNewTab: url) } - + func userScriptDidRequestClosing(_ userScript: PrivacyDashboardUserScript) { #if os(iOS) - delegate?.privacyDashboardControllerDidTapClose(self) + privacyDashboardNavigationDelegate?.privacyDashboardControllerDidTapClose(self) #endif } func userScriptDidRequestShowReportBrokenSite(_ userScript: PrivacyDashboardUserScript) { -#if os(iOS) - delegate?.privacyDashboardControllerDidRequestShowReportBrokenSite(self) -#endif + privacyDashboardDelegate?.privacyDashboardControllerDidRequestShowReportBrokenSite(self) } func userScript(_ userScript: PrivacyDashboardUserScript, setHeight height: Int) { -#if os(macOS) - delegate?.privacyDashboardController(self, didSetHeight: height) -#endif + privacyDashboardNavigationDelegate?.privacyDashboardController(self, didSetHeight: height) } func userScript(_ userScript: PrivacyDashboardUserScript, didRequestSubmitBrokenSiteReportWithCategory category: String, description: String) { -#if os(macOS) - delegate?.privacyDashboardController(self, didRequestSubmitBrokenSiteReportWithCategory: category, description: description) -#endif + privacyDashboardReportBrokenSiteDelegate?.privacyDashboardController(self, didRequestSubmitBrokenSiteReportWithCategory: category, + description: description) } func userScript(_ userScript: PrivacyDashboardUserScript, didSetPermission permission: String, to state: PermissionAuthorizationState) { #if os(macOS) - delegate?.privacyDashboardController(self, didSetPermission: permission, to: state) + privacyDashboardDelegate?.privacyDashboardController(self, didSetPermission: permission, to: state) #endif } func userScript(_ userScript: PrivacyDashboardUserScript, setPermission permission: String, paused: Bool) { #if os(macOS) - delegate?.privacyDashboardController(self, setPermission: permission, paused: paused) + privacyDashboardDelegate?.privacyDashboardController(self, setPermission: permission, paused: paused) #endif } } diff --git a/Sources/PrivacyDashboard/PrivacyInfo.swift b/Sources/PrivacyDashboard/PrivacyInfo.swift index 41d937793..fd488e382 100644 --- a/Sources/PrivacyDashboard/PrivacyInfo.swift +++ b/Sources/PrivacyDashboard/PrivacyInfo.swift @@ -35,7 +35,7 @@ public final class PrivacyInfo { self.url = url self.parentEntity = parentEntity self.protectionStatus = protectionStatus - + trackerInfo = TrackerInfo() } diff --git a/Sources/PrivacyDashboard/UserScript/PrivacyDashboardUserScript.swift b/Sources/PrivacyDashboard/UserScript/PrivacyDashboardUserScript.swift index 77b9c5c5e..1df481f4d 100644 --- a/Sources/PrivacyDashboard/UserScript/PrivacyDashboardUserScript.swift +++ b/Sources/PrivacyDashboard/UserScript/PrivacyDashboardUserScript.swift @@ -42,11 +42,11 @@ public enum PrivacyDashboardTheme: String, Encodable { public struct ProtectionState: Decodable { public let isProtected: Bool public let eventOrigin: EventOrigin - + public struct EventOrigin: Decodable { public let screen: EventOriginScreen } - + public enum EventOriginScreen: String, Decodable { case primaryScreen case breakageForm @@ -54,7 +54,7 @@ public struct ProtectionState: Decodable { } final class PrivacyDashboardUserScript: NSObject, StaticUserScript { - + enum MessageNames: String, CaseIterable { case privacyDashboardSetProtection case privacyDashboardSetSize @@ -66,21 +66,21 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { case privacyDashboardSetPermission case privacyDashboardSetPermissionPaused } - + static var injectionTime: WKUserScriptInjectionTime { .atDocumentStart } static var forMainFrameOnly: Bool { false } static var source: String = "" static var script: WKUserScript = PrivacyDashboardUserScript.makeWKUserScript() var messageNames: [String] { MessageNames.allCases.map(\.rawValue) } - + weak var delegate: PrivacyDashboardUserScriptDelegate? - + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard let messageType = MessageNames(rawValue: message.name) else { assertionFailure("PrivacyDashboardUserScript: unexpected message name \(message.name)") return } - + switch messageType { case .privacyDashboardSetProtection: handleSetProtection(message: message) @@ -104,14 +104,14 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { } // MARK: - JS message handlers - + private func handleSetProtection(message: WKScriptMessage) { - + guard let protectionState: ProtectionState = DecodableHelper.decode(from: message.messageBody) else { assertionFailure("privacyDashboardSetProtection: expected ProtectionState") return } - + delegate?.userScript(self, didChangeProtectionState: protectionState) } @@ -121,10 +121,10 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("privacyDashboardSetHeight: expected height to be an Int") return } - + delegate?.userScript(self, setHeight: height) } - + private func handleClose() { delegate?.userScriptDidRequestClosing(self) } @@ -140,7 +140,7 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("privacyDashboardSetHeight: expected { category: String, description: String }") return } - + delegate?.userScript(self, didRequestSubmitBrokenSiteReportWithCategory: category, description: description) } @@ -152,10 +152,10 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("handleOpenUrlInNewTab: expected { url: '...' } ") return } - + delegate?.userScript(self, didRequestOpenUrlInNewTab: url) } - + private func handleOpenSettings(message: WKScriptMessage) { guard let dict = message.body as? [String: Any], let target = dict["target"] as? String @@ -163,10 +163,10 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("handleOpenSettings: expected { target: '...' } ") return } - + delegate?.userScript(self, didRequestOpenSettings: target) } - + private func handleSetPermission(message: WKScriptMessage) { guard let dict = message.body as? [String: Any], let permission = dict["permission"] as? String, @@ -175,10 +175,10 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("privacyDashboardSetPermission: expected { permission: PermissionType, value: PermissionAuthorizationState }") return } - + delegate?.userScript(self, didSetPermission: permission, to: state) } - + private func handleSetPermissionPaused(message: WKScriptMessage) { guard let dict = message.body as? [String: Any], let permission = dict["permission"] as? String, @@ -187,10 +187,10 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("handleSetPermissionPaused: expected { permission: PermissionType, paused: Bool }") return } - + delegate?.userScript(self, setPermission: permission, paused: paused) } - + // MARK: - Calls to script's JS API func setTrackerInfo(_ tabUrl: URL, trackerInfo: TrackerInfo, webView: WKWebView) { @@ -198,15 +198,15 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("Can't encode trackerInfoViewModel into JSON") return } - + guard let safeTabUrl = try? JSONEncoder().encode(tabUrl).utf8String() else { assertionFailure("Can't encode tabUrl into JSON") return } - + evaluate(js: "window.onChangeRequestData(\(safeTabUrl), \(trackerBlockingDataJson))", in: webView) } - + func setProtectionStatus(_ protectionStatus: ProtectionStatus, webView: WKWebView) { guard let protectionStatusJson = try? JSONEncoder().encode(protectionStatus).utf8String() else { assertionFailure("Can't encode mockProtectionStatus into JSON") @@ -215,42 +215,42 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { evaluate(js: "window.onChangeProtectionStatus(\(protectionStatusJson))", in: webView) } - + func setUpgradedHttps(_ upgradedHttps: Bool, webView: WKWebView) { evaluate(js: "window.onChangeUpgradedHttps(\(upgradedHttps))", in: webView) } - + func setParentEntity(_ parentEntity: Entity?, webView: WKWebView) { if parentEntity == nil { return } - + guard let parentEntityJson = try? JSONEncoder().encode(parentEntity).utf8String() else { assertionFailure("Can't encode parentEntity into JSON") return } - + evaluate(js: "window.onChangeParentEntity(\(parentEntityJson))", in: webView) } - + func setTheme(_ theme: PrivacyDashboardTheme?, webView: WKWebView) { if theme == nil { return } - + guard let themeJson = try? JSONEncoder().encode(theme).utf8String() else { assertionFailure("Can't encode themeName into JSON") return } - + evaluate(js: "window.onChangeTheme(\(themeJson))", in: webView) } - + func setServerTrust(_ serverTrustViewModel: ServerTrustViewModel, webView: WKWebView) { guard let certificateDataJson = try? JSONEncoder().encode(serverTrustViewModel).utf8String() else { assertionFailure("Can't encode serverTrustViewModel into JSON") return } - + evaluate(js: "window.onChangeCertificateData(\(certificateDataJson))", in: webView) } - + func setIsPendingUpdates(_ isPendingUpdates: Bool, webView: WKWebView) { evaluate(js: "window.onIsPendingUpdates(\(isPendingUpdates))", in: webView) } @@ -283,17 +283,17 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { self.evaluate(js: "window.onChangeAllowedPermissions(\(allowedPermissionsJson))", in: webView) } - + private func evaluate(js: String, in webView: WKWebView) { webView.evaluateJavaScript(js) } - + } extension Data { - + func utf8String() -> String? { return String(data: self, encoding: .utf8) } - + } diff --git a/Sources/PrivacyDashboard/ViewModel/ServerTrustViewModel.swift b/Sources/PrivacyDashboard/ViewModel/ServerTrustViewModel.swift index eb94d1db3..0862cb89d 100644 --- a/Sources/PrivacyDashboard/ViewModel/ServerTrustViewModel.swift +++ b/Sources/PrivacyDashboard/ViewModel/ServerTrustViewModel.swift @@ -19,28 +19,28 @@ import Foundation public struct ServerTrustViewModel: Encodable { - + struct SecCertificateViewModel: Encodable { - + let summary: String? let commonName: String? let emails: [String]? let publicKey: SecKeyViewModel? - + public init(secCertificate: SecCertificate) { summary = SecCertificateCopySubjectSummary(secCertificate) as String? ?? "" - + var commonName: CFString? SecCertificateCopyCommonName(secCertificate, &commonName) self.commonName = commonName as String? ?? "" - + var emails: CFArray? if errSecSuccess == SecCertificateCopyEmailAddresses(secCertificate, &emails) { self.emails = emails as? [String] } else { self.emails = nil } - + var secTrust: SecTrust? if errSecSuccess == SecTrustCreateWithCertificates(secCertificate, SecPolicyCreateBasicX509(), &secTrust), let certTrust = secTrust { if #available(iOS 14.0, macOS 11.0, *) { @@ -53,11 +53,11 @@ public struct ServerTrustViewModel: Encodable { publicKey = nil } } - + } - + struct SecKeyViewModel: Encodable { - + static func typeToString(_ type: String) -> String? { switch type as CFString { case kSecAttrKeyTypeRSA: return "RSA" @@ -66,14 +66,14 @@ public struct ServerTrustViewModel: Encodable { default: return nil } } - + let keyId: Data? let externalRepresentation: Data? - + let bitSize: Int? let blockSize: Int? let effectiveSize: Int? - + let canDecrypt: Bool let canDerive: Bool let canEncrypt: Bool @@ -81,20 +81,20 @@ public struct ServerTrustViewModel: Encodable { let canUnwrap: Bool let canVerify: Bool let canWrap: Bool - + let isPermanent: Bool? let type: String? - + init?(secKey: SecKey?) { guard let secKey = secKey else { return nil } - + blockSize = SecKeyGetBlockSize(secKey) externalRepresentation = SecKeyCopyExternalRepresentation(secKey, nil) as Data? - + let attrs: NSDictionary? = SecKeyCopyAttributes(secKey) - + bitSize = attrs?[kSecAttrKeySizeInBits] as? Int effectiveSize = attrs?[kSecAttrEffectiveKeySize] as? Int canDecrypt = attrs?[kSecAttrCanDecrypt] as? Bool ?? false @@ -106,36 +106,36 @@ public struct ServerTrustViewModel: Encodable { canWrap = attrs?[kSecAttrCanWrap] as? Bool ?? false isPermanent = attrs?[kSecAttrIsPermanent] as? Bool ?? false keyId = attrs?[kSecAttrApplicationLabel] as? Data - + if let type = attrs?[kSecAttrType] as? String { self.type = Self.typeToString(type) } else { self.type = nil } - + } - + } - + let secCertificateViewModels: [SecCertificateViewModel] - + public init?(serverTrust: SecTrust?) { guard let serverTrust = serverTrust else { return nil } - + let secTrust = serverTrust let count = SecTrustGetCertificateCount(secTrust) guard count != 0 else { return nil } - + var secCertificateViewModels = [SecCertificateViewModel]() for i in 0 ..< count { guard let certificate = SecTrustGetCertificateAtIndex(secTrust, i) else { return nil } let certificateViewModel = SecCertificateViewModel(secCertificate: certificate) secCertificateViewModels.append(certificateViewModel) } - + self.secCertificateViewModels = secCertificateViewModels } - + }