FieldTelemetryUITestProbe.swift (4474B)
1 import Foundation 2 import RadrootsKit 3 4 enum FieldTelemetryUITestProbe { 5 private static let enabledKey = "RADROOTS_FIELD_IOS_UI_TEST_TELEMETRY_PROBE" 6 private static let redactionPolicy = RadrootsTelemetryRedactionPolicy.default 7 8 static var isRequested: Bool { 9 ProcessInfo.processInfo.environment[enabledKey] == "true" 10 } 11 12 static func value(recordedBy telemetry: FieldTelemetry) async -> String? { 13 guard isRequested else { 14 return nil 15 } 16 let events = await telemetry.recordedEventsForUITest() 17 return value(events: events) 18 } 19 20 private static func value(events: [RadrootsTelemetryEvent]) -> String { 21 let eventNames = Set(events.map(\.name)) 22 let fieldKeys = Set(events.flatMap { event in 23 event.fields.map(\.key) 24 }) 25 let values = events.flatMap(stringValues) 26 return [ 27 "event_count=\(events.count)", 28 "event_names=\(eventNames.sorted().joined(separator: ","))", 29 "field_keys=\(fieldKeys.sorted().joined(separator: ","))", 30 "runtime_logging_seen=\(eventNames.contains("field_ios.runtime.logging_initialized"))", 31 "startup_success_seen=\(eventNames.contains("field_ios.startup.success"))", 32 "relay_status_seen=\(eventNames.contains("field_ios.relay.status_changed"))", 33 "identity_create_seen=\(eventNames.contains("field_ios.identity_custody.create"))", 34 "user_presence_save_identity_seen=\(eventNames.contains("field_ios.user_presence.save_identity"))", 35 "document_diagnostics_export_seen=\(eventNames.contains("field_ios.document_interchange.diagnostics_export"))", 36 "document_relay_config_export_seen=\(eventNames.contains("field_ios.document_interchange.relay_config_export"))", 37 "document_relay_config_import_seen=\(eventNames.contains("field_ios.document_interchange.relay_config_import"))", 38 "document_public_share_prepare_seen=\(eventNames.contains("field_ios.document_interchange.public_share_prepare"))", 39 "capture_support_refreshed_seen=\(eventNames.contains("field_ios.capture.support_refreshed"))", 40 "capture_import_photo_seen=\(eventNames.contains("field_ios.capture.import_photo"))", 41 "capture_scan_document_seen=\(eventNames.contains("field_ios.capture.scan_document"))", 42 "external_action_open_seen=\(eventNames.contains("field_ios.external_action.open"))", 43 "unsafe_values_present=\(values.contains(where: containsUnsafeValue))", 44 "relay_url_values_present=\(values.contains(where: containsRelayURL))", 45 "secret_like_values_present=\(values.contains(where: containsSecretLikeValue))", 46 "path_like_values_present=\(values.contains(where: containsPathLikeValue))", 47 "npub_values_present=\(values.contains(where: containsNpubValue))" 48 ].joined(separator: ";") 49 } 50 51 private static func stringValues(from event: RadrootsTelemetryEvent) -> [String] { 52 var values = event.message.map { [$0] } ?? [] 53 values.append(contentsOf: event.fields.map { field in 54 field.value.renderedValue 55 }) 56 return values 57 } 58 59 private static func containsUnsafeValue(_ value: String) -> Bool { 60 redactionPolicy.containsUnsafeValue(value) 61 || containsRelayURL(value) 62 || containsSecretLikeValue(value) 63 || containsPathLikeValue(value) 64 || containsNpubValue(value) 65 } 66 67 private static func containsRelayURL(_ value: String) -> Bool { 68 let normalized = value.lowercased() 69 return normalized.contains("ws://") || normalized.contains("wss://") 70 } 71 72 private static func containsSecretLikeValue(_ value: String) -> Bool { 73 let normalized = value.lowercased() 74 return normalized.contains("nsec") 75 || normalized.range(of: "[a-f0-9]{64}", options: .regularExpression) != nil 76 } 77 78 private static func containsPathLikeValue(_ value: String) -> Bool { 79 let normalized = value.lowercased() 80 return normalized.contains("/users/") 81 || normalized.contains("/private/var/") 82 || normalized.contains("/var/mobile/containers/") 83 || normalized.contains("/var/folders/") 84 || normalized.contains("file:///") 85 } 86 87 private static func containsNpubValue(_ value: String) -> Bool { 88 value.lowercased().contains("npub") 89 } 90 }