(
opts: AuthorizeFlowOptions = {},
)
| 122 | } |
| 123 | |
| 124 | export async function startAuthorizationCodeFlow( |
| 125 | opts: AuthorizeFlowOptions = {}, |
| 126 | ): Promise<AuthorizeFlowResult> { |
| 127 | const clientId = resolveClientId(); |
| 128 | const scope = opts.scope ?? DEFAULT_SCOPES; |
| 129 | const pkce = generatePkcePair(); |
| 130 | const state = generateState(); |
| 131 | |
| 132 | const loopback = await startLoopback({ state, timeoutMs: opts.timeoutMs }); |
| 133 | const authorizeUrl = buildAuthorizeUrl({ |
| 134 | clientId, |
| 135 | redirectUri: loopback.redirectUri, |
| 136 | scope, |
| 137 | state, |
| 138 | challenge: pkce.challenge, |
| 139 | }); |
| 140 | |
| 141 | // Print the host+path only (no state / code_challenge) so live |
| 142 | // values can't leak into scrollback / CI logs during the 120s window. |
| 143 | console.log(`Opening browser to ${c.dim(authorizeHost(authorizeUrl))} ...`); |
| 144 | const { opened } = await openBrowser(authorizeUrl); |
| 145 | if (!opened) { |
| 146 | // openBrowser already printed the manual URL; surface it as the |
| 147 | // last on-screen instruction so it isn't buried above "Waiting…". |
| 148 | console.log(c.dim("(open the URL above to continue)")); |
| 149 | } |
| 150 | console.log(`Waiting for callback on ${c.accent(loopback.redirectUri)} ...`); |
| 151 | |
| 152 | let codeResult; |
| 153 | try { |
| 154 | codeResult = await loopback.result; |
| 155 | } catch (err) { |
| 156 | await loopback.close().catch(() => {}); |
| 157 | throw err; |
| 158 | } |
| 159 | |
| 160 | const tokens = await exchangeCodeForTokens({ |
| 161 | clientId, |
| 162 | code: codeResult.code, |
| 163 | redirectUri: codeResult.redirectUri, |
| 164 | verifier: pkce.verifier, |
| 165 | fetchImpl: opts.fetchImpl, |
| 166 | }); |
| 167 | |
| 168 | // Fresh login → clean OAuth block (no inherited refresh_token). |
| 169 | await persistOAuth(tokens, { preserveMissing: false }); |
| 170 | return { tokens }; |
| 171 | } |
| 172 | |
| 173 | export async function refreshTokens( |
| 174 | refresh_token: string, |
no test coverage detected