field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

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 }