(
discovery: CliLoginDiscovery,
grant: DeviceCodeGrant,
options: PollForDeviceTokensOptions = {},
)
| 263 | * `access_denied` / `expired_token` are terminal). |
| 264 | */ |
| 265 | export const pollForDeviceTokens = async ( |
| 266 | discovery: CliLoginDiscovery, |
| 267 | grant: DeviceCodeGrant, |
| 268 | options: PollForDeviceTokensOptions = {}, |
| 269 | ): Promise<DeviceTokens> => { |
| 270 | const now = options.now ?? (() => Date.now()); |
| 271 | const deadline = now() + grant.expiresInSeconds * 1000; |
| 272 | let intervalMs = Math.max(1, grant.intervalSeconds) * 1000; |
| 273 | |
| 274 | for (;;) { |
| 275 | if (now() >= deadline) { |
| 276 | throw new DeviceLoginError("Login timed out before it was approved."); |
| 277 | } |
| 278 | await sleep(intervalMs); |
| 279 | |
| 280 | const response = await post( |
| 281 | discovery.tokenEndpoint, |
| 282 | { |
| 283 | grant_type: DEVICE_CODE_GRANT_TYPE, |
| 284 | device_code: grant.deviceCode, |
| 285 | client_id: discovery.clientId, |
| 286 | }, |
| 287 | discovery.requestFormat, |
| 288 | options, |
| 289 | ); |
| 290 | const body = await readJson(response); |
| 291 | |
| 292 | if (response.ok) { |
| 293 | const accessToken = asString(body.access_token); |
| 294 | if (!accessToken) { |
| 295 | throw new DeviceLoginError("Token response was missing an access token."); |
| 296 | } |
| 297 | const claims = decodeAccessTokenClaims(accessToken); |
| 298 | return { |
| 299 | accessToken, |
| 300 | refreshToken: asString(body.refresh_token), |
| 301 | expiresAt: deriveExpiresAt({ accessToken, expiresIn: asNumber(body.expires_in) }), |
| 302 | email: |
| 303 | readUserEmail(body) ?? (typeof claims?.email === "string" ? claims.email : undefined), |
| 304 | organizationId: |
| 305 | asString(body.organization_id) ?? |
| 306 | (typeof claims?.org_id === "string" ? claims.org_id : undefined), |
| 307 | }; |
| 308 | } |
| 309 | |
| 310 | const error = asString(body.error); |
| 311 | if (error === "authorization_pending") continue; |
| 312 | if (error === "slow_down") { |
| 313 | intervalMs += 5000; |
| 314 | continue; |
| 315 | } |
| 316 | if (error === "access_denied") { |
| 317 | throw new DeviceLoginError("Login was denied."); |
| 318 | } |
| 319 | if (error === "expired_token") { |
| 320 | throw new DeviceLoginError("The login request expired before it was approved."); |
| 321 | } |
| 322 | throw new DeviceLoginError( |
no test coverage detected