(
authURLHandler: (url: string, automaticUrl?: string) => Promise<void>,
options?: {
loginWithClaudeAi?: boolean
inferenceOnly?: boolean
expiresIn?: number
orgUUID?: string
loginHint?: string
loginMethod?: string
/**
* Don't call openBrowser(). Caller takes both URLs via authURLHandler
* and decides how/where to open them. Used by the SDK control protocol
* (claude_authenticate) where the SDK client owns the user's display,
* not this process.
*/
skipBrowserOpen?: boolean
},
)
| 30 | } |
| 31 | |
| 32 | async startOAuthFlow( |
| 33 | authURLHandler: (url: string, automaticUrl?: string) => Promise<void>, |
| 34 | options?: { |
| 35 | loginWithClaudeAi?: boolean |
| 36 | inferenceOnly?: boolean |
| 37 | expiresIn?: number |
| 38 | orgUUID?: string |
| 39 | loginHint?: string |
| 40 | loginMethod?: string |
| 41 | /** |
| 42 | * Don't call openBrowser(). Caller takes both URLs via authURLHandler |
| 43 | * and decides how/where to open them. Used by the SDK control protocol |
| 44 | * (claude_authenticate) where the SDK client owns the user's display, |
| 45 | * not this process. |
| 46 | */ |
| 47 | skipBrowserOpen?: boolean |
| 48 | }, |
| 49 | ): Promise<OAuthTokens> { |
| 50 | // Create OAuth callback listener and start it |
| 51 | this.authCodeListener = new AuthCodeListener() |
| 52 | this.port = await this.authCodeListener.start() |
| 53 | |
| 54 | // Generate PKCE values and state |
| 55 | const codeChallenge = crypto.generateCodeChallenge(this.codeVerifier) |
| 56 | const state = crypto.generateState() |
| 57 | |
| 58 | // Build auth URLs for both automatic and manual flows |
| 59 | const opts = { |
| 60 | codeChallenge, |
| 61 | state, |
| 62 | port: this.port, |
| 63 | loginWithClaudeAi: options?.loginWithClaudeAi, |
| 64 | inferenceOnly: options?.inferenceOnly, |
| 65 | orgUUID: options?.orgUUID, |
| 66 | loginHint: options?.loginHint, |
| 67 | loginMethod: options?.loginMethod, |
| 68 | } |
| 69 | const manualFlowUrl = client.buildAuthUrl({ ...opts, isManual: true }) |
| 70 | const automaticFlowUrl = client.buildAuthUrl({ ...opts, isManual: false }) |
| 71 | |
| 72 | // Wait for either automatic or manual auth code |
| 73 | const authorizationCode = await this.waitForAuthorizationCode( |
| 74 | state, |
| 75 | async () => { |
| 76 | if (options?.skipBrowserOpen) { |
| 77 | // Hand both URLs to the caller. The automatic one still works |
| 78 | // if the caller opens it on the same host (localhost listener |
| 79 | // is running); the manual one works from anywhere. |
| 80 | await authURLHandler(manualFlowUrl, automaticFlowUrl) |
| 81 | } else { |
| 82 | await authURLHandler(manualFlowUrl) // Show manual option to user |
| 83 | await openBrowser(automaticFlowUrl) // Try automatic flow |
| 84 | } |
| 85 | }, |
| 86 | ) |
| 87 | |
| 88 | // Check if the automatic flow is still active (has a pending response) |
| 89 | const isAutomaticFlow = this.authCodeListener?.hasPendingResponse() ?? false |
no test coverage detected