( providerId: string, refreshToken: string )
| 1513 | const TOKEN_REFRESH_TIMEOUT_MS = 15_000 |
| 1514 | |
| 1515 | export async function refreshOAuthToken( |
| 1516 | providerId: string, |
| 1517 | refreshToken: string |
| 1518 | ): Promise<RefreshTokenResult> { |
| 1519 | try { |
| 1520 | const provider = getBaseProviderForService(providerId) |
| 1521 | |
| 1522 | const config = getProviderAuthConfig(provider) |
| 1523 | |
| 1524 | const { headers, bodyParams, useJsonBody } = buildAuthRequest(config, refreshToken) |
| 1525 | |
| 1526 | const response = await fetch(config.tokenEndpoint, { |
| 1527 | method: 'POST', |
| 1528 | headers, |
| 1529 | body: useJsonBody ? JSON.stringify(bodyParams) : new URLSearchParams(bodyParams).toString(), |
| 1530 | signal: AbortSignal.timeout(TOKEN_REFRESH_TIMEOUT_MS), |
| 1531 | }) |
| 1532 | |
| 1533 | if (!response.ok) { |
| 1534 | const errorText = await response.text() |
| 1535 | let errorData: unknown = errorText |
| 1536 | |
| 1537 | try { |
| 1538 | errorData = JSON.parse(errorText) |
| 1539 | } catch (_e) { |
| 1540 | // Not JSON, keep as text |
| 1541 | } |
| 1542 | |
| 1543 | logger.error('Token refresh failed:', { |
| 1544 | status: response.status, |
| 1545 | statusText: response.statusText, |
| 1546 | error: errorText, |
| 1547 | parsedError: errorData, |
| 1548 | providerId, |
| 1549 | tokenEndpoint: config.tokenEndpoint, |
| 1550 | hasClientId: !!config.clientId, |
| 1551 | hasClientSecret: !!config.clientSecret, |
| 1552 | hasRefreshToken: !!refreshToken, |
| 1553 | refreshTokenPrefix: refreshToken ? `${refreshToken.substring(0, 10)}...` : 'none', |
| 1554 | }) |
| 1555 | return { |
| 1556 | ok: false, |
| 1557 | errorCode: extractErrorCode(errorData), |
| 1558 | message: `Failed to refresh token: ${response.status} ${errorText}`, |
| 1559 | } |
| 1560 | } |
| 1561 | |
| 1562 | const data = await response.json() |
| 1563 | |
| 1564 | if (data && typeof data === 'object' && data.ok === false) { |
| 1565 | logger.error('Token refresh failed:', { |
| 1566 | status: response.status, |
| 1567 | statusText: response.statusText, |
| 1568 | error: data.error, |
| 1569 | parsedError: data, |
| 1570 | providerId, |
| 1571 | tokenEndpoint: config.tokenEndpoint, |
| 1572 | hasClientId: !!config.clientId, |
no test coverage detected