···11+import Foundation
22+33+public struct AuthorizationServerMetadata: Decodable, Sendable, Equatable {
44+ public let issuer: String
55+ public let authorizationEndpoint: URL
66+ public let tokenEndpoint: URL
77+ public let pushedAuthorizationRequestEndpoint: URL?
88+ public let dpopSigningAlgValuesSupported: [String]
99+ public let scopesSupported: [String]
1010+1111+ enum CodingKeys: String, CodingKey {
1212+ case issuer
1313+ case authorizationEndpoint = "authorization_endpoint"
1414+ case tokenEndpoint = "token_endpoint"
1515+ case pushedAuthorizationRequestEndpoint = "pushed_authorization_request_endpoint"
1616+ case dpopSigningAlgValuesSupported = "dpop_signing_alg_values_supported"
1717+ case scopesSupported = "scopes_supported"
1818+ }
1919+}
···11+import Foundation
22+33+/// The static JSON file this client publishes at
44+/// `https://pdfs.at/oauth/client-metadata.json`. `clientId` is literally
55+/// that URL — this is atproto's "client metadata by URL" convention.
66+public struct ClientMetadata: Codable, Sendable, Equatable {
77+ public let clientId: String
88+ public let clientName: String
99+ public let clientURI: String
1010+ public let redirectURIs: [String]
1111+ public let grantTypes: [String]
1212+ public let responseTypes: [String]
1313+ public let scope: String
1414+ public let tokenEndpointAuthMethod: String
1515+ public let dpopBoundAccessTokens: Bool
1616+ public let applicationType: String
1717+1818+ enum CodingKeys: String, CodingKey {
1919+ case clientId = "client_id"
2020+ case clientName = "client_name"
2121+ case clientURI = "client_uri"
2222+ case redirectURIs = "redirect_uris"
2323+ case grantTypes = "grant_types"
2424+ case responseTypes = "response_types"
2525+ case scope
2626+ case tokenEndpointAuthMethod = "token_endpoint_auth_method"
2727+ case dpopBoundAccessTokens = "dpop_bound_access_tokens"
2828+ case applicationType = "application_type"
2929+ }
3030+3131+ /// The canonical metadata for pdfs. The `scope` field advertises the
3232+ /// maximum envelope of scopes we may request over the client's lifetime.
3333+ /// We list `repo:*` (wildcard) rather than enumerate every possible
3434+ /// collection NSID. Actual authorization requests narrow to specific
3535+ /// collections via progressive disclosure — see `OAuthCoordinator.ensureScope`.
3636+ public static let defaultPDFS = ClientMetadata(
3737+ clientId: "https://pdfs.at/oauth/client-metadata.json",
3838+ clientName: "pdfs",
3939+ clientURI: "https://pdfs.at",
4040+ redirectURIs: ["pdfs://oauth/callback"],
4141+ grantTypes: ["authorization_code", "refresh_token"],
4242+ responseTypes: ["code"],
4343+ scope: "atproto repo:* blob:*/*",
4444+ tokenEndpointAuthMethod: "none",
4545+ dpopBoundAccessTokens: true,
4646+ applicationType: "native"
4747+ )
4848+}