( authCodeInput: string, codeVerifier?: string, )
| 242 | } |
| 243 | |
| 244 | export async function exchangeChatGptCodeForTokens( |
| 245 | authCodeInput: string, |
| 246 | codeVerifier?: string, |
| 247 | ): Promise<ChatGptOAuthCredentials> { |
| 248 | const verifier = codeVerifier ?? pendingCodeVerifier |
| 249 | if (!verifier) { |
| 250 | throw new Error('No PKCE verifier found. Please run /connect:chatgpt again.') |
| 251 | } |
| 252 | |
| 253 | const { code, state } = parseAuthCodeInput(authCodeInput) |
| 254 | |
| 255 | if (pendingState && state && pendingState !== state) { |
| 256 | throw new Error('OAuth state mismatch. Please restart /connect:chatgpt.') |
| 257 | } |
| 258 | |
| 259 | const response = await fetch(CHATGPT_OAUTH_TOKEN_URL, { |
| 260 | method: 'POST', |
| 261 | headers: { |
| 262 | 'Content-Type': 'application/json', |
| 263 | }, |
| 264 | body: JSON.stringify({ |
| 265 | grant_type: 'authorization_code', |
| 266 | client_id: CHATGPT_OAUTH_CLIENT_ID, |
| 267 | redirect_uri: CHATGPT_OAUTH_REDIRECT_URI, |
| 268 | code, |
| 269 | code_verifier: verifier, |
| 270 | }), |
| 271 | }) |
| 272 | |
| 273 | if (!response.ok) { |
| 274 | throw new Error( |
| 275 | `Failed to exchange ChatGPT OAuth code (status ${response.status}). Please retry /connect:chatgpt.`, |
| 276 | ) |
| 277 | } |
| 278 | |
| 279 | const data = await response.json() |
| 280 | const tokenResponse = parseOAuthTokenResponse(data) |
| 281 | |
| 282 | const credentials: ChatGptOAuthCredentials = { |
| 283 | accessToken: tokenResponse.accessToken, |
| 284 | refreshToken: tokenResponse.refreshToken, |
| 285 | expiresAt: Date.now() + tokenResponse.expiresInMs, |
| 286 | connectedAt: Date.now(), |
| 287 | } |
| 288 | |
| 289 | saveChatGptOAuthCredentials(credentials) |
| 290 | resetChatGptOAuthRateLimit() |
| 291 | pendingCodeVerifier = null |
| 292 | pendingState = null |
| 293 | |
| 294 | return credentials |
| 295 | } |
| 296 | |
| 297 | export function disconnectChatGptOAuth(): void { |
| 298 | stopChatGptOAuthServer() |
no test coverage detected