11import Foundation
22
3+ /// Coordinates credential lookup, prompting, login, validation, and logout for Apple Developer sessions.
4+ ///
5+ /// `AppleSessionService` is the high-level API for command-line tools and apps that want an injectable
6+ /// workflow around ``Client``. It can read credentials from environment variables or a keychain, prompt
7+ /// when credentials are missing, open a browser for federated accounts, and remember the default username
8+ /// after a successful login.
39public actor AppleSessionService {
410 public typealias EnvironmentValue = @Sendable ( String ) -> String ?
511 public typealias DefaultUsername = @Sendable ( ) -> String ?
@@ -19,25 +25,48 @@ public actor AppleSessionService {
1925 public typealias LoadData = @Sendable ( URLRequest) async throws -> ( Data , URLResponse )
2026 public typealias Log = @Sendable ( String ) -> Void
2127
28+ /// Dependencies used by ``AppleSessionService`` to interact with the host app and storage.
29+ ///
30+ /// Supply closures for environment lookup, keychain access, prompting, browser opening, networking,
31+ /// and the underlying login operations. This keeps the service testable and lets each app decide how
32+ /// credentials and user interaction should work.
2233 public struct Dependencies : Sendable {
34+ /// Reads a process environment value.
2335 public var environmentValue : EnvironmentValue
36+ /// Returns the remembered default Apple ID, if any.
2437 public var defaultUsername : DefaultUsername
38+ /// Stores or clears the remembered default Apple ID.
2539 public var setDefaultUsername : SetDefaultUsername
40+ /// Reads a string from secure storage for a username.
2641 public var keychainString : KeychainString
42+ /// Stores a string in secure storage for a username.
2743 public var keychainSet : KeychainSet
44+ /// Removes a username's stored secret.
2845 public var keychainRemove : KeychainRemove
46+ /// Prompts for a single line of input.
2947 public var readLine : ReadLine
48+ /// Prompts for a long line of input, such as a pasted browser callback URL.
3049 public var readLongLine : ReadLongLine
50+ /// Prompts for hidden input, such as a password.
3151 public var readSecureLine : ReadSecureLine
52+ /// Validates the current Apple session.
3253 public var validateSession : ValidateSession
54+ /// Performs username and password login.
3355 public var login : Login
56+ /// Checks whether an Apple ID uses federated authentication.
3457 public var checkIsFederated : CheckIsFederated
58+ /// Completes federated login from a pasted callback URL string.
3559 public var validateFederatedCallbackURL : ValidateFederatedCallbackURL
60+ /// Opens a URL in the host app or system browser.
3661 public var openURL : OpenURL
62+ /// Signs out from the underlying Apple session.
3763 public var signout : Signout
64+ /// Loads data for a URL request, used by developer-portal validation.
3865 public var loadData : LoadData
66+ /// Receives user-visible progress or recovery messages.
3967 public var log : Log
4068
69+ /// Creates a dependency container for ``AppleSessionService``.
4170 public init (
4271 environmentValue: @escaping EnvironmentValue ,
4372 defaultUsername: @escaping DefaultUsername ,
@@ -81,6 +110,7 @@ public actor AppleSessionService {
81110 private let xcodesPassword = " XCODES_PASSWORD "
82111 private let dependencies : Dependencies
83112
113+ /// Creates a service with the host-provided dependencies.
84114 public init ( dependencies: Dependencies ) {
85115 self . dependencies = dependencies
86116 }
@@ -103,12 +133,24 @@ public actor AppleSessionService {
103133 return nil
104134 }
105135
136+ /// Validates that the current session is authorized to access an Apple Developer download path.
137+ ///
138+ /// - Parameter path: The developer download path to validate, such as a path from an Xcode release.
106139 public func validateADCSession( path: String ) async throws {
107140 try await DeveloperPortalSessionService (
108141 loadData: dependencies. loadData
109142 ) . validateADCSession ( path: path)
110143 }
111144
145+ /// Ensures that a valid Apple session exists, prompting or signing in only when needed.
146+ ///
147+ /// The service first calls `validateSession`. If that fails, it looks for a username from the
148+ /// provided argument, `XCODES_USERNAME`, or the remembered default username. Passwords are read from
149+ /// `XCODES_PASSWORD`, secure storage, or `readSecureLine`. Federated accounts open the identity
150+ /// provider URL and ask the user to paste the callback URL.
151+ /// - Parameters:
152+ /// - providedUsername: A username to try before environment or default values.
153+ /// - shouldPromptForPassword: Pass `true` to ignore saved passwords and force a password prompt.
112154 public func loginIfNeeded( withUsername providedUsername: String ? = nil , shouldPromptForPassword: Bool = false ) async throws {
113155 do {
114156 try await dependencies. validateSession ( )
@@ -180,6 +222,9 @@ public actor AppleSessionService {
180222 }
181223 }
182224
225+ /// Logs in with an explicit username and password, then stores successful credentials.
226+ ///
227+ /// If Apple reports invalid credentials, the stored password for that username is removed.
183228 public func login( _ username: String , password: String ) async throws {
184229 do {
185230 try await dependencies. login ( username, password)
@@ -198,6 +243,7 @@ public actor AppleSessionService {
198243 }
199244 }
200245
246+ /// Signs out, removes the stored password, and clears the remembered default username.
201247 public func logout( ) async throws {
202248 guard let username = findUsername ( ) else { throw Error . notAuthenticated }
203249
@@ -208,10 +254,14 @@ public actor AppleSessionService {
208254}
209255
210256public extension AppleSessionService {
257+ /// Errors raised by the high-level session service before or after Apple authentication.
211258 enum Error : LocalizedError , Equatable {
259+ /// No username or password was available from dependencies or prompting.
212260 case missingUsernameOrPassword
261+ /// Logout was requested when no username could be found.
213262 case notAuthenticated
214263
264+ /// A user-visible description of the service error.
215265 public var errorDescription : String ? {
216266 switch self {
217267 case . missingUsernameOrPassword:
0 commit comments