Use atproto actions with ease in iOS shortcuts
1//
2// Resolvers.swift
3// shortcut
4//
5// Created by Bailey Townsend on 7/2/25.
6//
7
8import ATCommonWeb
9import ATIdentityTools
10import ATProtoKit
11import Cache
12import Foundation
13
14public struct Resolvers {
15
16 let handleResolver: HandleResolver
17 var didCache: Storage<String, String>?
18 var didDocCache: Storage<String, CommonDIDDocument>?
19
20 init() {
21 self.handleResolver = HandleResolver()
22 let one_hour = Expiry.seconds(TimeInterval(3_600))
23 let didCacheDiskConfig = DiskConfig(name: "DidCache", expiry: one_hour)
24 let didDocCacheDiskConfig = DiskConfig(name: "DidDocCache", expiry: one_hour)
25 let memoryConfig = MemoryConfig(
26 expiry: one_hour, countLimit: 10, totalCostLimit: 20)
27
28 do {
29 self.didCache = try Storage<String, String>(
30 diskConfig: didCacheDiskConfig,
31 memoryConfig: memoryConfig,
32 fileManager: FileManager(),
33 transformer: TransformerFactory.forCodable(
34 ofType: String.self
35 )
36 )
37
38 self.didDocCache = try Storage<String, CommonDIDDocument>(
39 diskConfig: didDocCacheDiskConfig,
40 memoryConfig: memoryConfig,
41 fileManager: FileManager(),
42 transformer: TransformerFactory.forCodable(
43 ofType: CommonDIDDocument.self
44 ))
45 } catch {
46 print(error)
47
48 self.didCache = nil
49 self.didDocCache = nil
50 }
51 }
52
53 public func resolveDid(handle: String) async throws -> String? {
54
55 if let cache = self.didCache {
56 if cache.objectExists(forKey: handle) {
57 let result = try cache.entry(forKey: handle)
58 if result.expiry.isExpired {
59 try cache.removeObject(forKey: handle)
60 }
61 return result.object
62 }
63 }
64
65 var didToReturn: String
66
67 if handle.hasSuffix(".bsky.social") {
68 let config = ATProtocolConfiguration()
69 let atProto = await ATProtoKit(
70 sessionConfiguration: config)
71
72 do {
73 let did = try await atProto.resolveHandle(from: handle)
74 didToReturn = did.did
75 } catch {
76 throw LoginError.handleDoesNotResolve
77 }
78 } else {
79 guard let did = try await handleResolver.resolve(handle: handle) else {
80 throw LoginError.handleDoesNotResolve
81 }
82 didToReturn = did
83 }
84
85 do {
86 try self.didCache?.setObject(didToReturn, forKey: handle)
87 } catch {
88 print(error)
89 }
90 return didToReturn
91
92 }
93
94 public func getDidDoc(handle: String) async throws -> CommonDIDDocument {
95
96 do {
97
98 guard let did = try await self.resolveDid(handle: handle) else {
99 throw LoginError.handleDoesNotResolve
100 }
101
102 if let cache = self.didDocCache {
103 if cache.objectExists(forKey: handle) {
104 let result = try cache.entry(forKey: handle)
105 if result.expiry.isExpired {
106 try cache.removeObject(forKey: handle)
107 }
108 return result.object
109 }
110 }
111
112 var didResolver = DIDResolver()
113
114 let didDoc = try await didResolver.resolve(did: did)
115 do {
116 try self.didDocCache?.setObject(didDoc, forKey: handle)
117 } catch {
118 print(error)
119 }
120 return didDoc
121
122 } catch {
123 throw error
124 }
125 }
126
127 public func getDidDoc(did: String) async throws -> CommonDIDDocument {
128
129 do {
130
131 if let cache = self.didDocCache {
132 if cache.objectExists(forKey: did) {
133 let result = try cache.entry(forKey: did)
134 if result.expiry.isExpired {
135 try cache.removeObject(forKey: did)
136 }
137 return result.object
138 }
139 }
140
141 var didResolver = DIDResolver()
142
143 let didDoc = try await didResolver.resolve(did: did)
144 do {
145 try self.didDocCache?.setObject(didDoc, forKey: did)
146 } catch {
147 print(error)
148 }
149 return didDoc
150
151 } catch {
152 throw error
153 }
154 }
155
156}