| 239 | } |
| 240 | |
| 241 | private async exchangeCode(tokenEndpoint: string, code: string): Promise<TokenResponse> { |
| 242 | const body = new URLSearchParams({ |
| 243 | grant_type: "authorization_code", |
| 244 | code, |
| 245 | redirect_uri: this.callbackUrl, |
| 246 | client_id: this.clientId, |
| 247 | client_secret: this.clientSecret, |
| 248 | }).toString(); |
| 249 | |
| 250 | const result = await fetchJSON<TokenResponse>(tokenEndpoint, { |
| 251 | method: "POST", |
| 252 | body, |
| 253 | }); |
| 254 | |
| 255 | if (result.error) throw new Error(`Token exchange failed: ${result.error}`); |
| 256 | return result; |
| 257 | } |
| 258 | |
| 259 | private async getUserInfo(userinfoEndpoint: string, accessToken: string): Promise<UserInfoResponse> { |
| 260 | return fetchJSON<UserInfoResponse>(userinfoEndpoint, { |