FieldExternalActions.swift (3789B)
1 import Foundation 2 import RadrootsKit 3 4 public enum FieldExternalActionRecovery: String, Equatable, Sendable { 5 case appSettings 6 } 7 8 public struct FieldExternalActionRequestRecord: Equatable, Sendable { 9 public let kind: RadrootsExternalActionDestinationKind 10 public let urlString: String? 11 12 init(destination: RadrootsExternalActionDestination) { 13 self.kind = destination.kind 14 self.urlString = destination.url?.absoluteString 15 } 16 17 public var statusText: String { 18 switch kind { 19 case .appSettings: 20 "Requested app settings" 21 case .web: 22 "Requested web link" 23 case .nostr: 24 "Requested Nostr profile" 25 case .appleMaps: 26 "Requested Apple Maps" 27 } 28 } 29 } 30 31 final class FieldExternalActions: Sendable { 32 private let actions: any RadrootsExternalActions 33 34 init(actions: any RadrootsExternalActions) { 35 self.actions = actions 36 } 37 38 static func configured() -> FieldExternalActions { 39 #if DEBUG 40 if FieldUITestHarness.isRequested { 41 return FieldExternalActions(actions: uiTestExternalActions()) 42 } 43 #endif 44 return FieldExternalActions(actions: RadrootsAppleExternalActions()) 45 } 46 47 func canOpenPublicNostrProfile(npub: String) async -> Bool { 48 guard let destination = try? publicNostrProfileDestination(npub: npub) else { 49 return false 50 } 51 return await actions.canOpen(destination).canOpen 52 } 53 54 func openAppSettings() async throws -> FieldExternalActionRequestRecord { 55 let destination = RadrootsExternalActionDestination.appSettings 56 try await actions.open(RadrootsExternalActionRequest(destination: destination)) 57 return FieldExternalActionRequestRecord(destination: destination) 58 } 59 60 func openPublicNostrProfile(npub: String) async throws -> FieldExternalActionRequestRecord { 61 let destination = try publicNostrProfileDestination(npub: npub) 62 try await actions.open(RadrootsExternalActionRequest(destination: destination)) 63 return FieldExternalActionRequestRecord(destination: destination) 64 } 65 66 private func publicNostrProfileDestination(npub: String) throws -> RadrootsExternalActionDestination { 67 try RadrootsExternalActionDestination.nostr("nostr:\(npub)") 68 } 69 70 #if DEBUG 71 private static func uiTestExternalActions() -> FieldUITestExternalActions { 72 FieldUITestExternalActions( 73 defaultCanOpen: uiTestCanOpen, 74 openOutcome: uiTestOpenOutcome 75 ) 76 } 77 78 private static var uiTestCanOpen: Bool { 79 if FieldUITestHarness.string("RADROOTS_FIELD_IOS_UI_TEST_EXTERNAL_ACTIONS_NOSTR_CAN_OPEN") != nil { 80 return FieldUITestHarness.bool("RADROOTS_FIELD_IOS_UI_TEST_EXTERNAL_ACTIONS_NOSTR_CAN_OPEN", default: true) 81 } 82 if FieldUITestHarness.string("RADROOTS_FIELD_IOS_UI_TEST_EXTERNAL_ACTIONS_CAN_OPEN") != nil { 83 return FieldUITestHarness.bool("RADROOTS_FIELD_IOS_UI_TEST_EXTERNAL_ACTIONS_CAN_OPEN", default: true) 84 } 85 return true 86 } 87 88 private static var uiTestOpenOutcome: Result<Void, RadrootsExternalActionError> { 89 let raw = FieldUITestHarness.string("RADROOTS_FIELD_IOS_UI_TEST_EXTERNAL_ACTIONS_OPEN_OUTCOME")?.lowercased() 90 switch raw { 91 case nil, "", "success": 92 return .success(()) 93 case "unavailable": 94 return .failure(.unavailable("external actions are unavailable in this UI test")) 95 case "transient_failure": 96 return .failure(.transientFailure("external action failed in this UI test")) 97 default: 98 return .failure(.blockedByPolicy("unsupported UI-test external action outcome")) 99 } 100 } 101 #endif 102 }