-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathVssBackupClient.swift
More file actions
129 lines (107 loc) · 4.41 KB
/
VssBackupClient.swift
File metadata and controls
129 lines (107 loc) · 4.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import Foundation
import VssRustClientFfi
class VssBackupClient {
static let shared = VssBackupClient()
private var isSetup: Task<Void, Error>?
private init() {}
func reset() {
isSetup = nil
}
func setup(walletIndex: Int = 0) async throws {
do {
try await withTimeout(seconds: 30) {
Logger.debug("VSS client setting up…", context: "VssBackupClient")
let vssUrl = Env.vssServerUrl
let lnurlAuthServerUrl = Env.lnurlAuthServerUrl
Logger.debug("Building VSS client with vssUrl: '\(vssUrl)'", context: "VssBackupClient")
Logger.debug("Building VSS client with lnurlAuthServerUrl: '\(lnurlAuthServerUrl)'", context: "VssBackupClient")
let storeId = try await VssStoreIdProvider.shared.getVssStoreId(walletIndex: walletIndex)
if !lnurlAuthServerUrl.isEmpty {
guard let mnemonic = try Keychain.loadString(key: .bip39Mnemonic(index: walletIndex)) else {
throw CustomServiceError.mnemonicNotFound
}
let passphrase = try Keychain.loadString(key: .bip39Passphrase(index: walletIndex))
try await vssNewClientWithLnurlAuth(
baseUrl: vssUrl,
storeId: storeId,
mnemonic: mnemonic,
passphrase: passphrase,
lnurlAuthServerUrl: lnurlAuthServerUrl
)
} else {
try await vssNewClient(
baseUrl: vssUrl,
storeId: storeId
)
}
Logger.info("VSS client setup with server: '\(vssUrl)'", context: "VssBackupClient")
}
} catch {
Logger.error("VSS client setup error: \(error)", context: "VssBackupClient")
throw error
}
}
func putObject(key: String, data: Data) async throws -> VssItem {
try await awaitSetup()
Logger.debug("VSS 'putObject' call for '\(key)'", context: "VssBackupClient")
do {
let item = try await vssStore(key: key, value: data)
Logger.debug("VSS 'putObject' success for '\(key)' at version: \(item.version)", context: "VssBackupClient")
return item
} catch {
Logger.debug("VSS 'putObject' error for '\(key)': \(error)", context: "VssBackupClient")
throw error
}
}
func getObject(key: String) async throws -> VssItem? {
try await awaitSetup()
Logger.debug("VSS 'getObject' call for '\(key)'", context: "VssBackupClient")
do {
let item = try await vssGet(key: key)
if let item {
Logger.debug("VSS 'getObject' success for '\(key)'", context: "VssBackupClient")
} else {
Logger.debug("VSS 'getObject' success null for '\(key)'", context: "VssBackupClient")
}
return item
} catch {
Logger.debug("VSS 'getObject' error for '\(key)': \(error)", context: "VssBackupClient")
throw error
}
}
private func awaitSetup() async throws {
if let existingSetup = isSetup {
do {
try await existingSetup.value
return // ✅ Don't create another setup if one succeeded!
} catch let error as CancellationError {
isSetup = nil
throw error
}
}
let setupTask = Task {
try await setup()
}
isSetup = setupTask
do {
try await setupTask.value
} catch let error as CancellationError {
isSetup = nil
throw error
}
}
private func withTimeout<T>(seconds: TimeInterval, operation: @escaping () async throws -> T) async throws -> T {
try await withThrowingTaskGroup(of: T.self) { group in
group.addTask {
try await operation()
}
group.addTask {
try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
throw AppError(message: "Operation timed out", debugMessage: "Timeout after \(seconds) seconds")
}
let result = try await group.next()!
group.cancelAll()
return result
}
}
}