Relay.swift (5169B)
1 // 2 // Relay.swift 3 // damus 4 // 5 // Created by William Casarin on 2022-04-11. 6 // 7 8 import Foundation 9 10 public struct LegacyKind3RelayRWConfiguration: Codable, Sendable { 11 public let read: Bool? 12 public let write: Bool? 13 14 init(read: Bool, write: Bool) { 15 self.read = read 16 self.write = write 17 } 18 19 static let rw = LegacyKind3RelayRWConfiguration(read: true, write: true) 20 21 func toNIP65RWConfiguration() -> NIP65.RelayList.RelayItem.RWConfiguration? { 22 switch (self.read, self.write) { 23 case (false, true): return .write 24 case (true, false): return .read 25 case (true, true): return .readWrite 26 default: return nil 27 } 28 } 29 } 30 31 enum RelayVariant { 32 case regular 33 case ephemeral 34 case nwc 35 } 36 37 extension RelayPool { 38 /// Describes a relay for use in `RelayPool` 39 public struct RelayDescriptor { 40 let url: RelayURL 41 var info: NIP65.RelayList.RelayItem.RWConfiguration 42 let variant: RelayVariant 43 44 init(url: RelayURL, info: NIP65.RelayList.RelayItem.RWConfiguration, variant: RelayVariant = .regular) { 45 self.url = url 46 self.info = info 47 self.variant = variant 48 } 49 50 var ephemeral: Bool { 51 switch variant { 52 case .regular: 53 return false 54 case .ephemeral: 55 return true 56 case .nwc: 57 return true 58 } 59 } 60 61 static func nwc(url: RelayURL) -> RelayDescriptor { 62 return RelayDescriptor(url: url, info: .readWrite, variant: .nwc) 63 } 64 } 65 } 66 67 enum RelayFlags: Int { 68 case none = 0 69 case broken = 1 70 } 71 72 enum RelayAuthenticationError { 73 /// Only a public key was provided in keypair to sign challenge. 74 /// 75 /// A private key is required to sign `auth` challenge. 76 case no_private_key 77 /// No keypair was provided to sign challenge. 78 case no_key 79 } 80 enum RelayAuthenticationState: Equatable { 81 /// No `auth` request has been made from this relay 82 case none 83 /// We have received an `auth` challenge, but have not yet replied to the challenge 84 case pending 85 /// We have received an `auth` challenge and replied with an `auth` event 86 case verified 87 /// We received an `auth` challenge but failed to reply to the challenge 88 case error(RelayAuthenticationError) 89 } 90 91 struct Limitations: Codable { 92 let payment_required: Bool? 93 94 static var empty: Limitations { 95 Limitations(payment_required: nil) 96 } 97 } 98 99 struct Admission: Codable { 100 let amount: Int64 101 let unit: String 102 } 103 104 struct Subscription: Codable { 105 let amount: Int64 106 let unit: String 107 let period: Int 108 } 109 110 struct Publication: Codable { 111 let kinds: [Int] 112 let amount: Int64 113 let unit: String 114 } 115 116 struct Fees: Codable { 117 let admission: [Admission]? 118 let subscription: [Subscription]? 119 let publication: [Publication]? 120 121 static var empty: Fees { 122 Fees(admission: nil, subscription: nil, publication: nil) 123 } 124 } 125 126 struct RelayMetadata: Codable { 127 let name: String? 128 let description: String? 129 let pubkey: Pubkey? 130 let contact: String? 131 let supported_nips: [Int]? 132 let software: String? 133 let version: String? 134 let limitation: Limitations? 135 let payments_url: String? 136 let icon: String? 137 let fees: Fees? 138 139 var is_paid: Bool { 140 return limitation?.payment_required ?? false 141 } 142 } 143 144 extension RelayPool { 145 class Relay: Identifiable { 146 var descriptor: RelayDescriptor 147 let connection: RelayConnection 148 var authentication_state: RelayAuthenticationState 149 150 var flags: Int 151 152 init(descriptor: RelayDescriptor, connection: RelayConnection) { 153 self.flags = 0 154 self.descriptor = descriptor 155 self.connection = connection 156 self.authentication_state = RelayAuthenticationState.none 157 } 158 159 var is_broken: Bool { 160 return (flags & RelayFlags.broken.rawValue) == RelayFlags.broken.rawValue 161 } 162 163 var id: RelayURL { 164 return descriptor.url 165 } 166 } 167 } 168 169 extension RelayPool { 170 enum RelayError: Error { 171 case RelayAlreadyExists 172 } 173 } 174 175 176 // MARK: - Extension to bridge NIP-65 relay list structs with app-native objects 177 178 extension NIP65.RelayList { 179 static func fromLegacyContactList(_ contactList: NdbNote) throws(BridgeError) -> Self { 180 guard let relayListInfo = decode_json_relays(contactList.content) else { throw .couldNotDecodeRelayListInfo } 181 let relayItems = relayListInfo.map({ url, rwConfiguration in 182 return RelayItem(url: url, rwConfiguration: rwConfiguration.toNIP65RWConfiguration() ?? .readWrite) 183 }) 184 return NIP65.RelayList(relays: relayItems) 185 } 186 187 static func fromLegacyContactList(_ contactList: NdbNote?) throws(BridgeError) -> Self? { 188 guard let contactList = contactList else { return nil } 189 return try fromLegacyContactList(contactList) 190 } 191 192 enum BridgeError: Error { 193 case couldNotDecodeRelayListInfo 194 } 195 } 196