this repo has no description
at main 230 lines 6.6 kB view raw view rendered
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- **Automatic Token Management** - Built-in support for JWT access/refresh token handling with automatic retry on expiration 15- **Multiple Parameter Encodings** - URL, JSON, and combined encoding strategies for request parameters 16- **AT Protocol Error Handling** - Typed error responses matching AT Protocol error specifications 17- **Testable Architecture** - Protocol-based design allows easy mocking for unit tests 18 19## Requirements 20 21- Swift 6.2+ 22- iOS 26.0+ / macOS 26.0+ / watchOS 26.0+ / tvOS 26.0+ / Mac Catalyst 26.0+ 23 24## Installation 25 26### Swift Package Manager 27 28Add CoreATProtocol to your `Package.swift` dependencies: 29 30```swift 31dependencies: [ 32 .package(url: "https://tangled.org/@sparrowtek.com/CoreATProtocol", branch: "main"), 33] 34``` 35 36Then add it to your target dependencies: 37 38```swift 39.target( 40 name: "YourTarget", 41 dependencies: ["CoreATProtocol"] 42), 43``` 44 45Or in Xcode: File > Add Package Dependencies and enter: 46``` 47https://tangled.org/@sparrowtek.com/CoreATProtocol 48``` 49 50## Usage 51 52### Initial Setup 53 54Configure the environment with your host URL and authentication tokens: 55 56```swift 57import CoreATProtocol 58 59// Setup with host and tokens 60await setup( 61 hostURL: "https://bsky.social", 62 accessJWT: "your-access-token", 63 refreshJWT: "your-refresh-token" 64) 65 66// Or update tokens later 67await updateTokens(access: newAccessToken, refresh: newRefreshToken) 68 69// Change host 70await update(hostURL: "https://different-pds.example") 71``` 72 73### Defining Endpoints 74 75Create endpoints by conforming to `EndpointType`: 76 77```swift 78import CoreATProtocol 79 80enum MyEndpoint: EndpointType { 81 case getProfile(actor: String) 82 case createPost(text: String) 83 84 var baseURL: URL { 85 get async { 86 URL(string: APEnvironment.current.host ?? "https://bsky.social")! 87 } 88 } 89 90 var path: String { 91 switch self { 92 case .getProfile: 93 return "/xrpc/app.bsky.actor.getProfile" 94 case .createPost: 95 return "/xrpc/com.atproto.repo.createRecord" 96 } 97 } 98 99 var httpMethod: HTTPMethod { 100 switch self { 101 case .getProfile: return .get 102 case .createPost: return .post 103 } 104 } 105 106 var task: HTTPTask { 107 get async { 108 switch self { 109 case .getProfile(let actor): 110 return .requestParameters(encoding: .urlEncoding(parameters: ["actor": actor])) 111 case .createPost(let text): 112 let body: [String: Any] = ["text": text] 113 return .requestParameters(encoding: .jsonEncoding(parameters: body)) 114 } 115 } 116 } 117 118 var headers: HTTPHeaders? { 119 get async { nil } 120 } 121} 122``` 123 124### Making Requests 125 126Use `NetworkRouter` to execute requests: 127 128```swift 129@APActor 130class MyATClient { 131 private let router = NetworkRouter<MyEndpoint>() 132 133 init() { 134 router.delegate = APEnvironment.current.routerDelegate 135 } 136 137 func getProfile(actor: String) async throws -> ProfileResponse { 138 try await router.execute(.getProfile(actor: actor)) 139 } 140} 141``` 142 143### Custom JSON Decoding 144 145Use the pre-configured AT Protocol decoder for proper date handling: 146 147```swift 148let router = NetworkRouter<MyEndpoint>(decoder: .atDecoder) 149``` 150 151### Error Handling 152 153Handle AT Protocol specific errors: 154 155```swift 156do { 157 let profile: Profile = try await router.execute(.getProfile(actor: "did:plc:example")) 158} catch let error as AtError { 159 switch error { 160 case .message(let errorMessage): 161 print("AT Error: \(errorMessage.error) - \(errorMessage.message ?? "")") 162 case .network(let networkError): 163 switch networkError { 164 case .statusCode(let code, let data): 165 print("HTTP \(code?.rawValue ?? 0)") 166 case .encodingFailed: 167 print("Failed to encode request") 168 default: 169 print("Network error: \(networkError)") 170 } 171 } 172} 173``` 174 175## Architecture 176 177### Core Components 178 179| Component | Description | 180|-----------|-------------| 181| `APActor` | Global actor ensuring thread-safe access to AT Protocol state | 182| `APEnvironment` | Singleton holding host URL, tokens, and delegates | 183| `NetworkRouter` | Generic router executing typed endpoint requests | 184| `EndpointType` | Protocol defining API endpoint requirements | 185| `ParameterEncoding` | Enum supporting URL, JSON, and hybrid encoding | 186| `AtError` | AT Protocol error types with message parsing | 187 188### Thread Safety 189 190All AT Protocol operations are isolated to `@APActor` ensuring thread-safe access: 191 192```swift 193@APActor 194public func myFunction() async { 195 // Safe access to APEnvironment.current 196} 197``` 198 199## Parameter Encoding Options 200 201```swift 202// URL query parameters 203.urlEncoding(parameters: ["key": "value"]) 204 205// JSON body 206.jsonEncoding(parameters: ["key": "value"]) 207 208// Pre-encoded JSON data 209.jsonDataEncoding(data: jsonData) 210 211// Encodable objects 212.jsonEncodableEncoding(encodable: myStruct) 213 214// Combined URL + JSON body 215.urlAndJsonEncoding(urlParameters: ["q": "search"], bodyParameters: ["data": "value"]) 216``` 217 218## Related Packages 219 220- **[bskyKit](https://tangled.org/@sparrowtek.com/bskyKit)** - Bluesky-specific lexicon implementations built on CoreATProtocol 221 222## License 223 224This project is licensed under an [MIT license](https://tangled.org/sparrowtek.com/CoreATProtocol/blob/main/LICENSE). 225 226## Contributing 227 228It 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. 229 230By 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).