(
tokenEndpoint: string,
code: string,
codeVerifier: string,
clientId: string,
redirectUri?: string,
clientSecret?: string
)
| 571 | |
| 572 | // Exchange authorization code for tokens |
| 573 | private async exchangeCodeForTokens( |
| 574 | tokenEndpoint: string, |
| 575 | code: string, |
| 576 | codeVerifier: string, |
| 577 | clientId: string, |
| 578 | redirectUri?: string, |
| 579 | clientSecret?: string |
| 580 | ): Promise<OAuthTokens> { |
| 581 | const uri = redirectUri || this.config.redirectUri || `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`; |
| 582 | |
| 583 | const params = new URLSearchParams({ |
| 584 | grant_type: 'authorization_code', |
| 585 | code, |
| 586 | redirect_uri: uri, |
| 587 | client_id: clientId, |
| 588 | code_verifier: codeVerifier, |
| 589 | }); |
| 590 | |
| 591 | // Add client_secret if provided (some servers require it) |
| 592 | if (clientSecret) { |
| 593 | params.set('client_secret', clientSecret); |
| 594 | } |
| 595 | |
| 596 | const response = await fetch(tokenEndpoint, { |
| 597 | method: 'POST', |
| 598 | headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, |
| 599 | body: params.toString(), |
| 600 | }); |
| 601 | |
| 602 | if (!response.ok) { |
| 603 | const error = await response.text(); |
| 604 | throw new Error(`Failed to exchange code for tokens: ${error}`); |
| 605 | } |
| 606 | |
| 607 | const data = await response.json() as { |
| 608 | access_token: string; |
| 609 | refresh_token?: string; |
| 610 | expires_in?: number; |
| 611 | token_type?: string; |
| 612 | }; |
| 613 | |
| 614 | return { |
| 615 | accessToken: data.access_token, |
| 616 | refreshToken: data.refresh_token, |
| 617 | expiresAt: data.expires_in ? Date.now() + data.expires_in * 1000 : undefined, |
| 618 | tokenType: data.token_type || 'Bearer', |
| 619 | }; |
| 620 | } |
| 621 | |
| 622 | // Refresh access token |
| 623 | async refreshAccessToken( |
no test coverage detected