this repo has no description
1# CoreATProtocol
2
3A Swift package providing the foundational networking layer for interacting with the [AT Protocol](https://atproto.com) (Authenticated Transfer Protocol). This library handles the core HTTP communication, authentication token management, and request/response encoding required to build AT Protocol clients.
4
5## Overview
6
7CoreATProtocol is designed to be protocol-agnostic within the AT Protocol ecosystem. It provides the networking infrastructure that higher-level packages (like [bskyKit](https://tangled.org/@sparrowtek.com/bskyKit) for Bluesky) can build upon to implement specific lexicons.
8
9### Key Features
10
11- **Modern Swift Concurrency** - Built with Swift 6.2 using async/await and actors for thread-safe operations
12- **Global Actor Isolation** - Uses `@APActor` for consistent thread safety across all AT Protocol operations
13- **Flexible Network Routing** - Generic `NetworkRouter` that works with any endpoint conforming to `EndpointType`
14- **ATProto OAuth** - First-party OAuth client with DPoP, PAR, issuer validation, and identity resolution (handle or DID)
15- **Automatic Token Management** - Built-in support for JWT access/refresh token handling with automatic retry on expiration
16- **Multiple Parameter Encodings** - URL, JSON, and combined encoding strategies for request parameters
17- **AT Protocol Error Handling** - Typed error responses matching AT Protocol error specifications
18- **Testable Architecture** - Protocol-based design allows easy mocking for unit tests
19
20## Requirements
21
22- Swift 6.2+
23- iOS 17.0+ / macOS 14.0+ / watchOS 11.0+ / tvOS 17.0+ / Mac Catalyst 17.0+
24
25## Installation
26
27### Swift Package Manager
28
29Add CoreATProtocol to your `Package.swift` dependencies:
30
31```swift
32dependencies: [
33 .package(url: "https://tangled.org/@sparrowtek.com/CoreATProtocol", branch: "main"),
34]
35```
36
37Then add it to your target dependencies:
38
39```swift
40.target(
41 name: "YourTarget",
42 dependencies: ["CoreATProtocol"]
43),
44```
45
46Or in Xcode: File > Add Package Dependencies and enter:
47```
48https://tangled.org/@sparrowtek.com/CoreATProtocol
49```
50
51## Usage
52
53### Initial Setup
54
55Configure the environment with your host URL and authentication tokens:
56
57```swift
58import CoreATProtocol
59
60// Setup with host and tokens
61await setup(
62 hostURL: "https://bsky.social",
63 accessJWT: "your-access-token",
64 refreshJWT: "your-refresh-token"
65)
66
67// Or update tokens later
68await updateTokens(access: newAccessToken, refresh: newRefreshToken)
69
70// Change host
71await update(hostURL: "https://different-pds.example")
72```
73
74### OAuth Authentication
75
76Use `ATProtoOAuth` for OAuth login flows:
77
78```swift
79import CoreATProtocol
80
81let oauth = await ATProtoOAuth(
82 config: .init(
83 clientMetadataURL: "https://example.com/client-metadata.json",
84 redirectURI: "com.example.app:/oauth/callback"
85 ),
86 storage: .init(
87 retrieveLogin: { nil },
88 storeLogin: { _ in },
89 retrievePrivateKey: { nil },
90 storePrivateKey: { _ in }
91 )
92)
93
94let result = try await oauth.authenticate(identifier: "alice.bsky.social") { authURL, _ in
95 // Present authURL to the user and return callback URL.
96 fatalError("Implement user-auth callback handling")
97}
98
99await setup(hostURL: result.pdsEndpoint, accessJWT: result.accessToken, refreshJWT: result.refreshToken)
100```
101
102### Defining Endpoints
103
104Create endpoints by conforming to `EndpointType`:
105
106```swift
107import CoreATProtocol
108
109enum MyEndpoint: EndpointType {
110 case getProfile(actor: String)
111 case createPost(text: String)
112
113 var baseURL: URL {
114 get async {
115 URL(string: APEnvironment.current.host ?? "https://bsky.social")!
116 }
117 }
118
119 var path: String {
120 switch self {
121 case .getProfile:
122 return "/xrpc/app.bsky.actor.getProfile"
123 case .createPost:
124 return "/xrpc/com.atproto.repo.createRecord"
125 }
126 }
127
128 var httpMethod: HTTPMethod {
129 switch self {
130 case .getProfile: return .get
131 case .createPost: return .post
132 }
133 }
134
135 var task: HTTPTask {
136 get async {
137 switch self {
138 case .getProfile(let actor):
139 return .requestParameters(encoding: .urlEncoding(parameters: ["actor": actor]))
140 case .createPost(let text):
141 let body: [String: Any] = ["text": text]
142 return .requestParameters(encoding: .jsonEncoding(parameters: body))
143 }
144 }
145 }
146
147 var headers: HTTPHeaders? {
148 get async { nil }
149 }
150}
151```
152
153### Making Requests
154
155Use `NetworkRouter` to execute requests:
156
157```swift
158@APActor
159class MyATClient {
160 private let router = NetworkRouter<MyEndpoint>()
161
162 init() {
163 router.delegate = APEnvironment.current.routerDelegate
164 }
165
166 func getProfile(actor: String) async throws -> ProfileResponse {
167 try await router.execute(.getProfile(actor: actor))
168 }
169}
170```
171
172### Custom JSON Decoding
173
174Use the pre-configured AT Protocol decoder for proper date handling:
175
176```swift
177let router = NetworkRouter<MyEndpoint>(decoder: .atDecoder)
178```
179
180### Error Handling
181
182Handle AT Protocol specific errors:
183
184```swift
185do {
186 let profile: Profile = try await router.execute(.getProfile(actor: "did:plc:example"))
187} catch let error as AtError {
188 switch error {
189 case .message(let errorMessage):
190 print("AT Error: \(errorMessage.error) - \(errorMessage.message ?? "")")
191 case .network(let networkError):
192 switch networkError {
193 case .statusCode(let code, let data):
194 print("HTTP \(code?.rawValue ?? 0)")
195 case .encodingFailed:
196 print("Failed to encode request")
197 default:
198 print("Network error: \(networkError)")
199 }
200 }
201}
202```
203
204## Architecture
205
206### Core Components
207
208| Component | Description |
209|-----------|-------------|
210| `APActor` | Global actor ensuring thread-safe access to AT Protocol state |
211| `APEnvironment` | Singleton holding host URL, tokens, and delegates |
212| `NetworkRouter` | Generic router executing typed endpoint requests |
213| `EndpointType` | Protocol defining API endpoint requirements |
214| `ParameterEncoding` | Enum supporting URL, JSON, and hybrid encoding |
215| `AtError` | AT Protocol error types with message parsing |
216
217### Thread Safety
218
219All AT Protocol operations are isolated to `@APActor` ensuring thread-safe access:
220
221```swift
222@APActor
223public func myFunction() async {
224 // Safe access to APEnvironment.current
225}
226```
227
228## Parameter Encoding Options
229
230```swift
231// URL query parameters
232.urlEncoding(parameters: ["key": "value"])
233
234// JSON body
235.jsonEncoding(parameters: ["key": "value"])
236
237// Pre-encoded JSON data
238.jsonDataEncoding(data: jsonData)
239
240// Encodable objects
241.jsonEncodableEncoding(encodable: myStruct)
242
243// Combined URL + JSON body
244.urlAndJsonEncoding(urlParameters: ["q": "search"], bodyParameters: ["data": "value"])
245```
246
247## Related Packages
248
249- **[bskyKit](https://tangled.org/@sparrowtek.com/bskyKit)** - Bluesky-specific lexicon implementations built on CoreATProtocol
250
251## License
252
253This project is licensed under an [MIT license](https://tangled.org/sparrowtek.com/CoreATProtocol/blob/main/LICENSE).
254
255## Contributing
256
257It is always a good idea to discuss before taking on a significant task. That said, I have a strong bias towards enthusiasm. If you are excited about doing something, I'll do my best to get out of your way.
258
259By participating in this project you agree to abide by the [Contributor Code of Conduct](https://tangled.org/sparrowtek.com/CoreATProtocol/blob/main/CODE_OF_CONDUCT.md).