field_ios

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

TeraSupportingBackend.swift (8975B)


      1 import Foundation
      2 @testable import TeraApp
      3 
      4 actor SupportingBackend: TeraRuntimeBackend {
      5     struct SearchRequest: Sendable {
      6         let contextID: String
      7         let query: String
      8     }
      9 
     10     private let searchDelayNanoseconds: UInt64
     11     private var request: SearchRequest?
     12     private var closed = false
     13     private var settings = SupportingBackend.initialSettings()
     14     private var savedProfile: TeraProfileMetadataInput?
     15 
     16     init(searchDelayNanoseconds: UInt64 = 0) {
     17         self.searchDelayNanoseconds = searchDelayNanoseconds
     18     }
     19 
     20     func snapshotValue() -> TeraRuntimeSnapshot {
     21         TeraRuntimeSnapshot(
     22           identity: TeraRuntimeIdentity(
     23             publicKeyHex: String(repeating: "a", count: 64),
     24             hostSignerConfigured: true
     25           ),
     26           relay: nil,
     27           blossomConfiguration: nil,
     28           blossomEvidence: nil,
     29           crateName: "tera_ffi",
     30           crateVersion: "0.1.0-alpha",
     31           isClosed: closed
     32         )
     33     }
     34 
     35     func snapshot() -> TeraRuntimeSnapshot {
     36         snapshotValue()
     37     }
     38 
     39     func todayPage(request _: TeraTodayPageRequest) throws -> TeraTodayPage {
     40         throw unsupported()
     41     }
     42 
     43     func refreshToday(
     44       context _: TeraLocalNetwork,
     45       nowUnixSeconds _: UInt64,
     46       update _: TeraTodayProjectionUpdate, backfillCursor _: String?
     47     ) throws -> TeraTodaySyncReceipt {
     48         throw unsupported()
     49     }
     50 
     51     func search(
     52       context: TeraLocalNetwork,
     53       query: String,
     54       limit _: UInt16,
     55       asOfUnixSeconds _: UInt64
     56     ) async throws -> [TeraSearchResult] {
     57         request = SearchRequest(contextID: context.id, query: query)
     58         if searchDelayNanoseconds > 0 {
     59             try await Task.sleep(nanoseconds: searchDelayNanoseconds)
     60         }
     61         let card = Self.card()
     62         let profile = Self.profile()
     63         return [
     64           TeraSearchResult(type: .card, id: "card", card: card, profile: nil),
     65           TeraSearchResult(type: .card, id: "card", card: card, profile: nil),
     66           TeraSearchResult(type: .profile, id: "profile", card: nil, profile: profile),
     67         ]
     68     }
     69 
     70     func me(
     71       context _: TeraLocalNetwork,
     72       asOfUnixSeconds _: UInt64
     73     ) -> TeraMeSnapshot {
     74         TeraMeSnapshot(
     75           publicKey: String(repeating: "a", count: 64),
     76           profile: Self.profile(),
     77           cards: [Self.card()]
     78         )
     79     }
     80 
     81     func mobileSettings() -> TeraMobileSettings {
     82         settings
     83     }
     84 
     85     func replaceMobileSettings(
     86         input: TeraReplaceSettings
     87     ) throws -> TeraSettingsTransition {
     88         guard input.expectedRevision == settings.revision else {
     89             throw failure(code: "settings_revision_conflict")
     90         }
     91         guard input.relays.allSatisfy({ $0.url.hasPrefix("ws://") || $0.url.hasPrefix("wss://") }) else {
     92             throw failure(code: "invalid_relay_endpoint")
     93         }
     94         settings = TeraMobileSettings(
     95           revision: settings.revision + 1,
     96           identity: settings.identity,
     97           networkEnvironment: input.networkEnvironment,
     98           relays: input.relays,
     99           blossomAuthority: input.blossomAuthority,
    100           blossomPrimaryOrigin: input.blossomPrimaryOrigin,
    101           blossomFallbackOrigins: input.blossomFallbackOrigins,
    102           allowCellularDownloads: input.allowCellularDownloads,
    103           allowCellularUploads: input.allowCellularUploads,
    104           allowBackgroundTransfers: input.allowBackgroundTransfers,
    105           mediaCacheBytes: input.mediaCacheBytes,
    106           mediaCacheArtifacts: input.mediaCacheArtifacts
    107         )
    108         return TeraSettingsTransition(
    109           settings: settings,
    110           runtimeRestartRequired: true,
    111           outboxRequeueRequired: true,
    112           mediaCacheInvalidationRequired: true
    113         )
    114     }
    115 
    116     func saveProfileMetadata(input: TeraProfileMetadataInput) -> TeraProfileStatus {
    117         savedProfile = input
    118         return profileStatus(state: .queued, revision: 1, settlement: nil)
    119     }
    120 
    121     func advanceProfile(operationID _: String) -> TeraProfileStatus {
    122         profileStatus(
    123           state: .complete,
    124           revision: 2,
    125           settlement: TeraOperationSettlement(
    126             artifacts: 0,
    127             signed: 1,
    128             admitted: 1,
    129             pending: 0,
    130             retryable: 0,
    131             indeterminate: 0,
    132             failedTerminal: 0,
    133             cancelled: 0,
    134             deliveryPlans: 1,
    135             deliverySatisfied: 1,
    136             deliveryPending: 0,
    137             deliveryRetryable: 0,
    138             deliveryExhausted: 0,
    139             deliveryFailedTerminal: 0,
    140             deliveryCancelled: 0
    141           )
    142         )
    143     }
    144 
    145     func subscribe(
    146       bufferCapacity _: Int,
    147       receive _: @escaping @Sendable (TeraRuntimeChange) async -> Void
    148     ) -> any TeraRuntimeSubscriptionToken {
    149         SupportingSubscriptionToken()
    150     }
    151 
    152     func shutdown() -> TeraRuntimeShutdownReceipt {
    153         let wasClosed = closed
    154         closed = true
    155         return TeraRuntimeShutdownReceipt(state: "closed", alreadyClosed: wasClosed)
    156     }
    157 
    158     func lastSearchRequest() -> SearchRequest? {
    159         request
    160     }
    161 
    162     func settingsRevision() -> UInt64 {
    163         settings.revision
    164     }
    165 }
    166 
    167 extension SupportingBackend {
    168     private func failure(code: String) -> TeraRuntimeFailure {
    169         TeraRuntimeFailure(
    170           schemaVersion: 1,
    171           code: code,
    172           category: "invalid_argument",
    173           retryable: false,
    174           recoveryActions: [],
    175           operationID: "test.settings",
    176           capabilityID: nil,
    177           safeMessage: "The settings value is invalid."
    178         )
    179     }
    180 
    181     private func profileStatus(
    182       state: TeraOutboxState,
    183       revision: UInt64,
    184       settlement: TeraOperationSettlement?
    185     ) -> TeraProfileStatus {
    186         TeraProfileStatus(
    187           id: "profile-operation",
    188           revision: revision,
    189           authorPublicKey: String(repeating: "a", count: 64),
    190           state: state,
    191           deliveryID: state == .complete ? "delivery" : nil,
    192           createdAtUnixMilliseconds: 1,
    193           updatedAtUnixMilliseconds: revision,
    194           settlement: settlement
    195         )
    196     }
    197 
    198     private static func initialSettings() -> TeraMobileSettings {
    199         TeraMobileSettings(
    200           revision: 1,
    201           identity: TeraSettingsIdentityState(
    202             identities: [
    203               TeraSettingsIdentity(
    204                 id: "identity",
    205                 publicKeyHex: String(repeating: "a", count: 64)
    206               ),
    207             ],
    208             activeIdentityID: "identity",
    209             lockState: .unlocked,
    210             pendingImportOperationID: nil
    211           ),
    212           networkEnvironment: .simulator,
    213           relays: [
    214             TeraRelayPreference(url: "ws://127.0.0.1:7447", access: .readWrite),
    215           ],
    216           blossomAuthority: .loopbackDevelopment,
    217           blossomPrimaryOrigin: "http://127.0.0.1:3000",
    218           blossomFallbackOrigins: [],
    219           allowCellularDownloads: true,
    220           allowCellularUploads: true,
    221           allowBackgroundTransfers: true,
    222           mediaCacheBytes: 256 * 1_048_576,
    223           mediaCacheArtifacts: 1024
    224         )
    225     }
    226 
    227     private func unsupported() -> TeraRuntimeFailure {
    228         .local(
    229           operation: "test.supporting",
    230           code: "test.unsupported",
    231           safeMessage: "Unsupported test operation."
    232         )
    233     }
    234 
    235     private static func profile() -> TeraProfileSummary {
    236         TeraProfileSummary(
    237           authorPublicKey: String(repeating: "a", count: 64),
    238           name: "moss",
    239           displayName: "Moss Farm",
    240           about: "Local roots",
    241           picture: nil,
    242           banner: nil,
    243           nip05: "moss@example.com",
    244           website: "https://moss.example",
    245           lightningAddress: "moss@example.com"
    246         )
    247     }
    248 
    249     private static func card() -> TeraTodayCard {
    250         TeraTodayCard(
    251           id: "card",
    252           type: .foodAvailability,
    253           sourceEventID: String(repeating: "e", count: 64),
    254           sourceAddress: nil,
    255           authorPublicKey: String(repeating: "a", count: 64),
    256           contractID: "radroots.food_availability.v1",
    257           title: "Carrots",
    258           content: "Freshly picked",
    259           authoredAtUnixSeconds: 1_800_000_000,
    260           effectiveAtUnixSeconds: 1_800_000_000,
    261           calendarTiming: nil,
    262           location: "Metchosin",
    263           priceAmount: "3",
    264           priceCurrency: "CAD",
    265           priceUnit: "lb",
    266           quantity: "12",
    267           foodSummary: "Fresh carrots",
    268           foodPublishedAtUnixSeconds: 1_800_000_000,
    269           foodStatus: "active",
    270           contextRank: 1,
    271           inclusionReason: "local",
    272           media: [],
    273           lifecycle: .active,
    274           rankDigest: nil,
    275           authorProfile: profile(),
    276           thread: [],
    277           localOperationID: nil,
    278           localOperationState: nil
    279         )
    280     }
    281 }
    282 
    283 private actor SupportingSubscriptionToken: TeraRuntimeSubscriptionToken {
    284     func cancel() {}
    285 }