import Foundation import MyIdSDK import UIKit /// Translates the JS payload into the SDK's own config objects. /// /// A property is assigned only when its key is present, so an omitted option /// keeps whatever default `MyIdConfig()` / `MyIdAppearance()` already set rather /// than being overwritten with a value invented here. /// /// An unrecognised enum string skips its assignment rather than throwing — a /// typo should not hard-fail a verification the user already started, and /// skipping is what actually leaves the SDK default in place. enum MyidSdkConfigBuilder { static func makeConfig( from dictionary: NSDictionary, appearance appearanceDictionary: NSDictionary ) -> MyIdConfig { let config = MyIdConfig() config.sessionId = dictionary.string(for: "sessionId") config.clientHash = dictionary.string(for: "clientHash") config.clientHashId = dictionary.string(for: "clientHashId") if let minAge = dictionary.number(for: "minAge") { config.minAge = minAge.intValue } if let distance = dictionary.number(for: "distance") { config.distance = distance.floatValue } if let showErrorScreen = dictionary.number(for: "showErrorScreen") { config.showErrorScreen = showErrorScreen.boolValue } // An unrecognised value maps to nil and the assignment is skipped, so the // SDK keeps its own default. Substituting a guess would silently change // behaviour on a typo. if let environment = dictionary.enumValue(for: "environment") { switch environment { case "DEBUG": config.environment = .debug case "PRODUCTION": config.environment = .production default: break } } if let entryType = dictionary.enumValue(for: "entryType") { switch entryType { case "IDENTIFICATION": config.entryType = .identification case "VIDEO_IDENTIFICATION": config.entryType = .videoIdentification case "FACE_DETECTION": config.entryType = .faceDetection default: break } } if let residency = dictionary.enumValue(for: "residency") { switch residency { case "USER_DEFINED": config.residency = .userDefined case "RESIDENT": config.residency = .resident case "NON_RESIDENT": config.residency = .nonResident default: break } } if let locale = dictionary.enumValue(for: "locale") { switch locale { case "ENGLISH": config.locale = .english case "RUSSIAN": config.locale = .russian // Documented platform fallback, not an unknown value: iOS has no // Cyrillic, Karakalpak or Tajik locale. case "UZBEK", "UZBEK_CYRILLIC", "KARAKALPAK", "TAJIK": config.locale = .uzbek default: break } } if let cameraShape = dictionary.enumValue(for: "cameraShape") { switch cameraShape { case "CIRCLE": config.cameraShape = .circle case "ELLIPSE": config.cameraShape = .ellipse default: break } } if let cameraSelector = dictionary.enumValue(for: "cameraSelector") { switch cameraSelector { case "FRONT": config.cameraSelector = .front case "BACK": config.cameraSelector = .back default: break } } if let presentationStyle = dictionary.enumValue(for: "presentationStyle") { switch presentationStyle { case "SHEET": config.presentationStyle = .sheet case "FULL": config.presentationStyle = .full default: break } } // Only forwarded when the caller supplied something: an empty details object // would replace the SDK's own default, which carries MyID's call-centre // number. Each field is likewise assigned only when present. if let details = dictionary["organizationDetails"] as? NSDictionary, details.count > 0 { let organizationDetails = MyIdOrganizationDetails() if let phone = details.string(for: "phone") { organizationDetails.phoneNumber = phone } if let logoName = details.string(for: "logo") { organizationDetails.logo = UIImage(named: logoName) } config.organizationDetails = organizationDetails } if appearanceDictionary.count > 0 { config.appearance = makeAppearance(from: appearanceDictionary) } return config } /// Every assignment is guarded: `MyIdAppearance()` seeds its own values, so /// writing nil for a key the caller omitted would strip the SDK's defaults /// rather than leave them alone. The same guard makes an unparseable color a /// no-op instead of a wipe. private static func makeAppearance(from dictionary: NSDictionary) -> MyIdAppearance { let appearance = MyIdAppearance() let colors: [(String, (UIColor) -> Void)] = [ ("colorPrimary", { appearance.colorPrimary = $0 }), ("colorOnPrimary", { appearance.colorOnPrimary = $0 }), ("colorError", { appearance.colorError = $0 }), ("colorOnError", { appearance.colorOnError = $0 }), ("colorOutline", { appearance.colorOutline = $0 }), ("colorDivider", { appearance.colorDivider = $0 }), ("colorSuccess", { appearance.colorSuccess = $0 }), ("colorButtonContainer", { appearance.colorButtonContainer = $0 }), ("colorButtonContainerDisabled", { appearance.colorButtonContainerDisabled = $0 }), ("colorButtonContent", { appearance.colorButtonContent = $0 }), ("colorButtonContentDisabled", { appearance.colorButtonContentDisabled = $0 }), ("colorScanButtonContainer", { appearance.colorScanButtonContainer = $0 }), ] for (key, assign) in colors { if let color = dictionary.color(for: key) { assign(color) } } if let cornerRadius = dictionary.number(for: "buttonCornerRadius") { appearance.buttonCornerRadius = cornerRadius.floatValue } return appearance } } private extension NSDictionary { func string(for key: String) -> String? { self[key] as? String } func number(for key: String) -> NSNumber? { self[key] as? NSNumber } /// Enum values travel as strings; normalise case so "debug" works like "DEBUG". func enumValue(for key: String) -> String? { string(for: key)?.uppercased() } func color(for key: String) -> UIColor? { guard let hex = string(for: key) else { return nil } return UIColor(myIdHex: hex) } } extension UIColor { /// Accepts `#RRGGBB` and `RRGGBB`. Returns nil for anything unparseable so a /// bad value leaves the SDK's own color in place. convenience init?(myIdHex hex: String) { var value = hex.trimmingCharacters(in: .whitespacesAndNewlines) if value.hasPrefix("#") { value.removeFirst() } guard value.count == 6 else { return nil } var rgb: UInt64 = 0 guard Scanner(string: value).scanHexInt64(&rgb) else { return nil } self.init( red: CGFloat((rgb & 0xFF0000) >> 16) / 255.0, green: CGFloat((rgb & 0x00FF00) >> 8) / 255.0, blue: CGFloat(rgb & 0x0000FF) / 255.0, alpha: 1.0 ) } }