tangled
alpha
login
or
join now
coreyja.com
/
backup-blue
1
fork
atom
this repo has no description
1
fork
atom
overview
issues
pulls
pipelines
Now we fetch the mini did doc from slingshot
Corey Alexander
6 months ago
01c0320b
67e88bdd
+193
-14
5 changed files
expand all
collapse all
unified
split
AtProtoBackup
ATProtocolService.swift
Account.swift
AtProtoBackup.entitlements
AtProtoBackupApp.swift
ContentView.swift
+73
AtProtoBackup/ATProtocolService.swift
···
1
1
+
//
2
2
+
// ATProtocolService.swift
3
3
+
// AtProtoBackup
4
4
+
//
5
5
+
// Created by Assistant on 8/26/25.
6
6
+
//
7
7
+
8
8
+
import Foundation
9
9
+
10
10
+
struct DIDResponse: Codable {
11
11
+
let did: String
12
12
+
}
13
13
+
14
14
+
class ATProtocolService {
15
15
+
static let shared = ATProtocolService()
16
16
+
17
17
+
private init() {}
18
18
+
19
19
+
func lookupDID(for handle: String) async throws -> (did: String, jsonData: Data) {
20
20
+
print("[ATProtocolService] lookupDID called with handle: '\(handle)'")
21
21
+
22
22
+
guard let url = URL(string: "https://slingshot.microcosm.blue/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=\(handle)") else {
23
23
+
print("[ATProtocolService] Failed to create URL")
24
24
+
throw ATProtocolError.invalidURL
25
25
+
}
26
26
+
27
27
+
print("[ATProtocolService] Making request to: \(url)")
28
28
+
let (data, response) = try await URLSession.shared.data(from: url)
29
29
+
30
30
+
print("[ATProtocolService] Received response")
31
31
+
32
32
+
guard let httpResponse = response as? HTTPURLResponse else {
33
33
+
print("[ATProtocolService] Response is not HTTPURLResponse")
34
34
+
throw ATProtocolError.invalidResponse
35
35
+
}
36
36
+
37
37
+
print("[ATProtocolService] HTTP Status Code: \(httpResponse.statusCode)")
38
38
+
39
39
+
guard httpResponse.statusCode == 200 else {
40
40
+
print("[ATProtocolService] Non-200 status code")
41
41
+
throw ATProtocolError.invalidResponse
42
42
+
}
43
43
+
44
44
+
print("[ATProtocolService] Response data length: \(data.count) bytes")
45
45
+
print("[ATProtocolService] Raw response: \(String(data: data, encoding: .utf8) ?? "Unable to decode as UTF-8")")
46
46
+
47
47
+
do {
48
48
+
let didResponse = try JSONDecoder().decode(DIDResponse.self, from: data)
49
49
+
print("[ATProtocolService] Successfully decoded - DID: \(didResponse.did)")
50
50
+
return (did: didResponse.did, jsonData: data)
51
51
+
} catch {
52
52
+
print("[ATProtocolService] Decoding error: \(error)")
53
53
+
throw ATProtocolError.decodingError
54
54
+
}
55
55
+
}
56
56
+
}
57
57
+
58
58
+
enum ATProtocolError: LocalizedError {
59
59
+
case invalidURL
60
60
+
case invalidResponse
61
61
+
case decodingError
62
62
+
63
63
+
var errorDescription: String? {
64
64
+
switch self {
65
65
+
case .invalidURL:
66
66
+
return "Invalid URL"
67
67
+
case .invalidResponse:
68
68
+
return "Invalid response from server"
69
69
+
case .decodingError:
70
70
+
return "Failed to decode response"
71
71
+
}
72
72
+
}
73
73
+
}
+6
-1
AtProtoBackup/Account.swift
···
12
12
@Model
13
13
final class Account {
14
14
var did: String
15
15
+
var handle: String
16
16
+
var jsonResponse: Data?
15
17
16
16
-
init(did: String) {
18
18
+
init(did: String, handle: String, jsonResponse: Data? = nil) {
19
19
+
print("[Account] Creating new Account - DID: \(did), Handle: \(handle)")
17
20
self.did = did
21
21
+
self.handle = handle
22
22
+
self.jsonResponse = jsonResponse
18
23
}
19
24
}
+2
AtProtoBackup/AtProtoBackup.entitlements
···
6
6
<true/>
7
7
<key>com.apple.security.files.user-selected.read-only</key>
8
8
<true/>
9
9
+
<key>com.apple.security.network.client</key>
10
10
+
<true/>
9
11
</dict>
10
12
</plist>
+1
-1
AtProtoBackup/AtProtoBackupApp.swift
···
14
14
let schema = Schema([
15
15
Account.self,
16
16
])
17
17
-
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
17
17
+
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)
18
18
19
19
do {
20
20
return try ModelContainer(for: schema, configurations: [modelConfiguration])
+111
-12
AtProtoBackup/ContentView.swift
···
13
13
@Query private var items: [Account]
14
14
@State private var showingAlert = false
15
15
@State private var inputText = ""
16
16
+
@State private var isLoading = false
17
17
+
@State private var errorMessage: String?
16
18
17
19
var body: some View {
18
20
NavigationSplitView {
19
21
List {
20
22
ForEach(items) { item in
21
23
NavigationLink {
22
22
-
Text("Item at \(item.did)")
24
24
+
VStack(alignment: .leading, spacing: 16) {
25
25
+
Text("Account Details")
26
26
+
.font(.title2)
27
27
+
.fontWeight(.bold)
28
28
+
29
29
+
VStack(alignment: .leading, spacing: 8) {
30
30
+
HStack {
31
31
+
Text("Handle:")
32
32
+
.fontWeight(.semibold)
33
33
+
Text(item.handle)
34
34
+
}
35
35
+
36
36
+
HStack {
37
37
+
Text("DID:")
38
38
+
.fontWeight(.semibold)
39
39
+
Text(item.did)
40
40
+
.lineLimit(1)
41
41
+
.truncationMode(.middle)
42
42
+
}
43
43
+
}
44
44
+
45
45
+
if let jsonData = item.jsonResponse {
46
46
+
VStack(alignment: .leading, spacing: 8) {
47
47
+
Text("JSON Response:")
48
48
+
.font(.headline)
49
49
+
50
50
+
ScrollView {
51
51
+
if let jsonObject = try? JSONSerialization.jsonObject(with: jsonData),
52
52
+
let prettyData = try? JSONSerialization.data(withJSONObject: jsonObject, options: [.prettyPrinted]),
53
53
+
let prettyString = String(data: prettyData, encoding: .utf8) {
54
54
+
Text(prettyString)
55
55
+
.font(.system(.body, design: .monospaced))
56
56
+
.textSelection(.enabled)
57
57
+
.padding()
58
58
+
.background(Color.gray.opacity(0.1))
59
59
+
.cornerRadius(8)
60
60
+
} else {
61
61
+
Text("Unable to decode JSON data")
62
62
+
.foregroundColor(.secondary)
63
63
+
}
64
64
+
}
65
65
+
}
66
66
+
} else {
67
67
+
Text("No JSON response available")
68
68
+
.foregroundColor(.secondary)
69
69
+
}
70
70
+
71
71
+
Spacer()
72
72
+
}
73
73
+
.padding()
74
74
+
.navigationTitle(item.handle)
23
75
} label: {
24
24
-
Text(item.did)
76
76
+
VStack(alignment: .leading) {
77
77
+
Text(item.handle)
78
78
+
.font(.headline)
79
79
+
Text(item.did)
80
80
+
.font(.caption)
81
81
+
.foregroundColor(.secondary)
82
82
+
}
25
83
}
26
84
}
27
85
.onDelete(perform: deleteItems)
···
46
104
47
105
} detail: {
48
106
Text("Select an item")
49
49
-
}.alert("Test", isPresented: $showingAlert, actions: {
50
50
-
TextField("Type something...", text: $inputText)
51
51
-
Button("OK") {
52
52
-
print("User entered: \(inputText)")
53
53
-
showingAlert = false
54
54
-
withAnimation {
55
55
-
modelContext.insert(Account(did: inputText))
107
107
+
}.alert("Add AT Protocol Account", isPresented: $showingAlert, actions: {
108
108
+
TextField("Enter handle (e.g., user.bsky.social)", text: $inputText)
109
109
+
Button("Add") {
110
110
+
print("[ContentView] Add button in alert pressed")
111
111
+
Task {
112
112
+
await lookupAndAddAccount()
56
113
}
57
114
}
58
58
-
Button("Cancel", role: .cancel) { }
115
115
+
Button("Cancel", role: .cancel) {
116
116
+
inputText = ""
117
117
+
errorMessage = nil
118
118
+
}
119
119
+
}, message: {
120
120
+
if let errorMessage = errorMessage {
121
121
+
Text(errorMessage)
122
122
+
} else if isLoading {
123
123
+
Text("Looking up DID...")
124
124
+
} else {
125
125
+
Text("Enter your AT Protocol handle")
126
126
+
}
59
127
})
60
128
61
129
}
62
130
63
131
64
132
private func addItem() {
133
133
+
print("[ContentView] Add button clicked")
134
134
+
inputText = ""
135
135
+
errorMessage = nil
65
136
showingAlert = true
137
137
+
}
138
138
+
139
139
+
private func lookupAndAddAccount() async {
140
140
+
print("[ContentView] lookupAndAddAccount called with handle: '\(inputText)'")
141
141
+
isLoading = true
142
142
+
errorMessage = nil
66
143
67
67
-
68
68
-
144
144
+
do {
145
145
+
print("[ContentView] Starting API lookup...")
146
146
+
let (did, jsonData) = try await ATProtocolService.shared.lookupDID(for: inputText)
147
147
+
print("[ContentView] API lookup successful - DID: \(did)")
148
148
+
149
149
+
await MainActor.run {
150
150
+
print("[ContentView] Creating and inserting Account object")
151
151
+
withAnimation {
152
152
+
let account = Account(did: did, handle: inputText, jsonResponse: jsonData)
153
153
+
modelContext.insert(account)
154
154
+
print("[ContentView] Account inserted into modelContext")
155
155
+
}
156
156
+
showingAlert = false
157
157
+
inputText = ""
158
158
+
isLoading = false
159
159
+
print("[ContentView] Alert dismissed, operation complete")
160
160
+
}
161
161
+
} catch {
162
162
+
print("[ContentView] Error during lookup: \(error)")
163
163
+
await MainActor.run {
164
164
+
errorMessage = "Error: \(error.localizedDescription)"
165
165
+
isLoading = false
166
166
+
}
167
167
+
}
69
168
}
70
169
71
170
private func deleteItems(offsets: IndexSet) {