* Start OAuth flow without waiting for callback (for deeplink-based flows) * Returns the authorization URL and state/verifier for later token exchange * @param preloadedMetadata - Optional pre-fetched OAuth metadata to avoid duplicate fetch
(preloadedMetadata?: OAuthMetadata)
| 767 | * @param preloadedMetadata - Optional pre-fetched OAuth metadata to avoid duplicate fetch |
| 768 | */ |
| 769 | async startAuthFlow(preloadedMetadata?: OAuthMetadata): Promise<{ |
| 770 | authUrl: string; |
| 771 | state: string; |
| 772 | codeVerifier: string; |
| 773 | tokenEndpoint: string; |
| 774 | clientId: string; |
| 775 | clientSecret?: string; |
| 776 | }> { |
| 777 | this.callbacks.onStatus('Fetching OAuth server configuration...'); |
| 778 | const metadata = preloadedMetadata || await this.getServerMetadata(); |
| 779 | |
| 780 | // Register client if endpoint available |
| 781 | let clientId: string; |
| 782 | let clientSecret: string | undefined; |
| 783 | if (metadata.registration_endpoint) { |
| 784 | // Try primary client name first, fall back to alternative if rejected |
| 785 | this.callbacks.onStatus(`Registering client as '${CLIENT_NAME}'...`); |
| 786 | try { |
| 787 | const client = await this.registerClient(metadata.registration_endpoint, CLIENT_NAME); |
| 788 | clientId = client.client_id; |
| 789 | clientSecret = client.client_secret; |
| 790 | this.callbacks.onStatus(`Registered as client: ${clientId}`); |
| 791 | } catch (error) { |
| 792 | // Try fallback client name (some servers have allowlists) |
| 793 | this.callbacks.onStatus(`Registration as '${CLIENT_NAME}' failed, trying '${FALLBACK_CLIENT_NAME}'...`); |
| 794 | const client = await this.registerClient(metadata.registration_endpoint, FALLBACK_CLIENT_NAME); |
| 795 | clientId = client.client_id; |
| 796 | clientSecret = client.client_secret; |
| 797 | this.callbacks.onStatus(`Registered as client: ${clientId}`); |
| 798 | } |
| 799 | } else { |
| 800 | // No registration endpoint - use default client ID |
| 801 | clientId = '1code'; |
| 802 | } |
| 803 | |
| 804 | const pkce = generatePKCE(); |
| 805 | const state = generateState(); |
| 806 | const redirectUri = this.config.redirectUri || `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`; |
| 807 | |
| 808 | const authUrl = new URL(metadata.authorization_endpoint); |
| 809 | authUrl.searchParams.set('response_type', 'code'); |
| 810 | authUrl.searchParams.set('client_id', clientId); |
| 811 | authUrl.searchParams.set('redirect_uri', redirectUri); |
| 812 | authUrl.searchParams.set('state', state); |
| 813 | authUrl.searchParams.set('code_challenge', pkce.challenge); |
| 814 | authUrl.searchParams.set('code_challenge_method', 'S256'); |
| 815 | |
| 816 | return { |
| 817 | authUrl: authUrl.toString(), |
| 818 | state, |
| 819 | codeVerifier: pkce.verifier, |
| 820 | tokenEndpoint: metadata.token_endpoint, |
| 821 | clientId, |
| 822 | clientSecret, |
| 823 | }; |
| 824 | } |
| 825 | |
| 826 | /** |
no test coverage detected