Skip to content

Commit af8eedd

Browse files
committed
retry on 503 when paused + rotate loading messages
1 parent d808ce8 commit af8eedd

File tree

4 files changed

+71
-29
lines changed

4 files changed

+71
-29
lines changed
Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
1+
import { ReactNode, useEffect, useState } from 'react'
12
import { LoadingMessage } from '../LoadingMessage'
23
import { FetchingTokenState } from '@/types/auth'
34

5+
function useCarousel(message: ReactNode[], interval = 10_000) {
6+
const [index, setIndex] = useState(0)
7+
8+
useEffect(() => {
9+
const timeout = setInterval(() => {
10+
setIndex((index) => (index + 1) % message.length)
11+
}, interval)
12+
13+
return () => {
14+
clearInterval(timeout)
15+
}
16+
}, [message.length, interval])
17+
18+
return message[index] ?? ''
19+
}
20+
421
interface FetchingTokenProps {
522
state: FetchingTokenState
623
}
724

825
export function FetchingToken({ state }: FetchingTokenProps) {
9-
return (
10-
<LoadingMessage>
11-
Setting up profile for {state.stack.name}...
12-
</LoadingMessage>
13-
)
26+
const message = useCarousel([
27+
`Setting up profile for ${state.stack.name}...`,
28+
`Waiting for the stack to become available...`,
29+
'This is taking a while, hang tight...',
30+
])
31+
32+
return <LoadingMessage>{message}</LoadingMessage>
1433
}

src/handlers/auth/states.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ export class SignInStateMachine extends EventEmitter<StateEventMap> {
215215
})
216216

217217
const apiTokenResponse = await fetchPersonalToken(
218-
stack.id,
218+
stack,
219219
grant.token,
220220
this.#signal
221221
)

src/services/k6/index.ts

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Stack } from '@/types/auth'
2+
import { timeout } from '@/utils/async'
13
import { z } from 'zod'
24

35
const PersonalTokenResponseSchema = z.object({
@@ -15,36 +17,52 @@ interface NotAMember {
1517

1618
export type FetchPersonalTokenRespone = TokenExchanged | NotAMember
1719

20+
const MAX_RETRIES = 5
21+
1822
export async function fetchPersonalToken(
19-
stackId: string,
23+
stack: Stack,
2024
token: string,
2125
signal: AbortSignal
2226
): Promise<FetchPersonalTokenRespone> {
23-
const response = await fetch(`${K6_API_URL}/api_token`, {
24-
headers: {
25-
'X-Stack-Id': stackId,
26-
Authorization: `Bearer ${token}`,
27-
},
28-
signal,
29-
})
30-
31-
if (response.status === 403) {
32-
return {
33-
type: 'not-a-member',
27+
for (let i = 0; i < MAX_RETRIES; ++i) {
28+
const response = await fetch(`${K6_API_URL}/api_token`, {
29+
headers: {
30+
'X-Stack-Id': stack.id,
31+
Authorization: `Bearer ${token}`,
32+
},
33+
signal,
34+
})
35+
36+
// It might take a while for the stack to wake up after being paused
37+
// and the permissions check might timeout, so we retry a few times.
38+
if (response.status === 503 && stack.status === 'paused') {
39+
await timeout(1000)
40+
41+
continue
3442
}
35-
}
3643

37-
if (response.status !== 200) {
38-
throw new Error(
39-
`Failed to fetch personal token. The server responsed with ${response.status}.`
40-
)
41-
}
44+
if (response.status === 403) {
45+
return {
46+
type: 'not-a-member',
47+
}
48+
}
49+
50+
if (response.status !== 200) {
51+
throw new Error(
52+
`Failed to fetch personal token. The server responsed with ${response.status}.`
53+
)
54+
}
4255

43-
const data: unknown = await response.json()
44-
const parsed = PersonalTokenResponseSchema.parse(data)
56+
const data: unknown = await response.json()
57+
const parsed = PersonalTokenResponseSchema.parse(data)
4558

46-
return {
47-
type: 'token-exchanged',
48-
token: parsed.personal_token,
59+
return {
60+
type: 'token-exchanged',
61+
token: parsed.personal_token,
62+
}
4963
}
64+
65+
throw new Error(
66+
`Failed to fetch personal token after ${MAX_RETRIES} retries.`
67+
)
5068
}

src/utils/async.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export function timeout(milliseconds: number) {
2+
return new Promise((resolve) => {
3+
setTimeout(resolve, milliseconds)
4+
})
5+
}

0 commit comments

Comments
 (0)