TeraSessionBootstrap.swift (3767B)
1 import Foundation 2 import UIKit 3 4 extension TeraSessionStore { 5 @MainActor 6 static func production( 7 bundle: Bundle = .main, 8 runtimeClient: TeraRuntimeClient = .production() 9 ) throws -> TeraSessionStore { 10 guard let bundleIdentifier = bundle.bundleIdentifier else { 11 throw TeraConfigurationError.missing("bundle_identifier") 12 } 13 let qualification = try TeraRemoteQualificationEnvironment.current() 14 #if DEBUG 15 let qualificationEvidenceStore = try TeraRemoteQualificationEvidence.prepare() 16 #else 17 let qualificationEvidenceStore: TeraRemoteQualificationEvidenceStore? = nil 18 #endif 19 let servicePrefix = 20 try qualification?.keychainServicePrefix 21 ?? requiredString( 22 "TERA_IOS_KEYCHAIN_SERVICE_PREFIX", bundle: bundle 23 ) 24 let bootstrap = try TeraConfigurationBootstrap( 25 runtimeMode: qualification?.runtimeMode 26 ?? requiredString("TERA_IOS_RUNTIME_MODE", bundle: bundle), 27 relayURLs: qualification?.relayURLs 28 ?? array("TERA_IOS_NOSTR_RELAY_URLS", bundle: bundle), 29 blossomOrigins: qualification?.blossomOrigins 30 ?? array("TERA_IOS_BLOSSOM_ORIGINS", bundle: bundle), 31 keychainServicePrefix: servicePrefix, 32 bundleIdentifier: bundleIdentifier, 33 appMetadata: metadata(bundleIdentifier: bundleIdentifier, bundle: bundle) 34 ) 35 let roots = try TeraRemoteQualificationEnvironment.applicationFileRoots( 36 appIdentifier: bundleIdentifier 37 ) 38 let protectedData = TeraProtectedDataMonitor( 39 available: UIApplication.shared.isProtectedDataAvailable 40 ) 41 return try TeraSessionStore( 42 configurationStore: TeraConfigurationStore(bootstrap: bootstrap, roots: roots), 43 identityStore: .production( 44 servicePrefix: servicePrefix, 45 protectedDataAvailable: { protectedData.isAvailable() }, 46 qualification: qualification 47 ), 48 runtimeClient: runtimeClient, 49 roots: roots, 50 protectedData: protectedData, 51 automatesQualificationIdentity: qualification?.automatesIdentity == true, 52 qualificationEvidenceStore: qualificationEvidenceStore 53 ) 54 } 55 56 @MainActor 57 private static func metadata(bundleIdentifier: String, bundle: Bundle) -> TeraRuntimeAppMetadata { 58 TeraRuntimeAppMetadata( 59 bundleIdentifier: bundleIdentifier, 60 version: bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String 61 ?? "0", 62 buildNumber: bundle.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "0", 63 buildSHA: normalizedOptional( 64 bundle.object(forInfoDictionaryKey: "GIT_SHA") as? String 65 ) 66 ) 67 } 68 69 @MainActor 70 private static func requiredString(_ key: String, bundle: Bundle) throws -> String { 71 guard let value = normalizedOptional(bundle.object(forInfoDictionaryKey: key) as? String) else { 72 throw TeraConfigurationError.missing(key) 73 } 74 return value 75 } 76 77 @MainActor 78 private static func array(_ key: String, bundle: Bundle) -> [String] { 79 if let values = bundle.object(forInfoDictionaryKey: key) as? [String] { 80 return values.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } 81 .filter { !$0.isEmpty } 82 } 83 guard let raw = normalizedOptional(bundle.object(forInfoDictionaryKey: key) as? String) else { 84 return [] 85 } 86 return raw.components(separatedBy: CharacterSet(charactersIn: ",; \n\r\t")) 87 .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } 88 .filter { !$0.isEmpty } 89 } 90 91 @MainActor 92 private static func normalizedOptional(_ value: String?) -> String? { 93 guard let value else { return nil } 94 let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) 95 return trimmed.isEmpty || trimmed == "unknown" ? nil : trimmed 96 } 97 }