Skip to content

Commit c3a828c

Browse files
committed
ref: more robust auth
1 parent c7ff60d commit c3a828c

2 files changed

Lines changed: 161 additions & 7 deletions

File tree

sources/client/clientMe.ts

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
import type { CommandContext } from "@/commands/types";
2+
import {
3+
NetworkError,
4+
ServerError,
5+
} from "@/commands/auth/appPairingRequest";
26

37
type ClientUser = {
48
id: number;
59
first_name: string;
610
last_name: string | null;
711
};
812

13+
const MAX_RETRIES = 10;
14+
const MAX_BACKOFF_MS = 30000;
15+
916
export async function fetchClientMe(
1017
context: CommandContext,
1118
token: string
1219
): Promise<ClientUser> {
13-
const response = await context.client.fetch("/v1/me", {
14-
method: "GET",
15-
headers: {
16-
Authorization: `Bearer ${token}`,
17-
},
18-
});
20+
const response = await fetchWithRetry(context, token);
1921

2022
if (!response.ok) {
2123
const errorPayload = await safeJson(response);
@@ -40,6 +42,75 @@ export async function fetchClientMe(
4042
};
4143
}
4244

45+
async function fetchWithRetry(
46+
context: CommandContext,
47+
token: string
48+
): Promise<Response> {
49+
let lastError: Error | null = null;
50+
let lastErrorType: "network" | "server" | null = null;
51+
52+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
53+
try {
54+
const response = await context.client.fetch("/v1/me", {
55+
method: "GET",
56+
headers: {
57+
Authorization: `Bearer ${token}`,
58+
},
59+
});
60+
61+
if (response.status >= 500 && response.status < 600) {
62+
lastErrorType = "server";
63+
lastError = new ServerError(`Server error: ${response.status}`);
64+
if (attempt < MAX_RETRIES) {
65+
console.log(
66+
`Server is temporarily unavailable, retrying... (attempt ${attempt} of ${MAX_RETRIES})`
67+
);
68+
await sleep(getBackoffDelay(attempt));
69+
continue;
70+
}
71+
}
72+
73+
return response;
74+
} catch (error) {
75+
lastErrorType = "network";
76+
lastError =
77+
error instanceof Error
78+
? new NetworkError(error.message)
79+
: new NetworkError("Unknown network error");
80+
81+
if (attempt < MAX_RETRIES) {
82+
console.log(
83+
`Network connection issue, retrying... (attempt ${attempt} of ${MAX_RETRIES})`
84+
);
85+
await sleep(getBackoffDelay(attempt));
86+
continue;
87+
}
88+
}
89+
}
90+
91+
if (lastErrorType === "network") {
92+
throw new NetworkError(
93+
"Unable to connect to Bee services. Please check your internet connection and try again."
94+
);
95+
}
96+
97+
if (lastErrorType === "server") {
98+
throw new ServerError(
99+
"Bee servers are currently experiencing issues. Please try again later."
100+
);
101+
}
102+
103+
throw lastError ?? new Error("Request failed after multiple retries.");
104+
}
105+
106+
function getBackoffDelay(attempt: number): number {
107+
return Math.min(1000 * Math.pow(2, attempt - 1), MAX_BACKOFF_MS);
108+
}
109+
110+
async function sleep(ms: number): Promise<void> {
111+
await new Promise((resolve) => setTimeout(resolve, ms));
112+
}
113+
43114
async function safeJson(
44115
response: Response
45116
): Promise<Record<string, unknown> | null> {

sources/commands/auth/appPairingRequest.ts

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,28 @@ export type AppPairingRequest =
55
| { status: "completed"; requestId: string; encryptedToken: string }
66
| { status: "expired"; requestId: string };
77

8+
export class NetworkError extends Error {
9+
constructor(message: string) {
10+
super(message);
11+
this.name = "NetworkError";
12+
}
13+
}
14+
15+
export class ServerError extends Error {
16+
constructor(message: string) {
17+
super(message);
18+
this.name = "ServerError";
19+
}
20+
}
21+
822
const PAIRING_API_URLS: Record<Environment, string> = {
923
prod: "https://auth.beeai-services.com",
1024
staging: "https://public-api.korshaks.people.amazon.dev",
1125
};
1226

1327
const PAIRING_PATH = "/apps/pairing/request";
28+
const MAX_RETRIES = 10;
29+
const MAX_BACKOFF_MS = 30000;
1430

1531
export async function requestAppPairing(
1632
env: Environment,
@@ -86,7 +102,74 @@ async function fetchPairing(
86102
init: RequestInit
87103
): Promise<Response> {
88104
const url = new URL(PAIRING_PATH, PAIRING_API_URLS[env]);
89-
return fetch(url, init);
105+
return await fetchWithRetry(url, init);
106+
}
107+
108+
async function fetchWithRetry(
109+
url: URL,
110+
init: RequestInit
111+
): Promise<Response> {
112+
let lastError: Error | null = null;
113+
let lastErrorType: "network" | "server" | null = null;
114+
115+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
116+
try {
117+
const response = await fetch(url, init);
118+
119+
if (response.status >= 500 && response.status < 600) {
120+
lastErrorType = "server";
121+
lastError = new ServerError(
122+
`Server error: ${response.status}`
123+
);
124+
if (attempt < MAX_RETRIES) {
125+
console.log(
126+
`Server is temporarily unavailable, retrying... (attempt ${attempt} of ${MAX_RETRIES})`
127+
);
128+
await sleep(getBackoffDelay(attempt));
129+
continue;
130+
}
131+
}
132+
133+
return response;
134+
} catch (error) {
135+
lastErrorType = "network";
136+
lastError =
137+
error instanceof Error
138+
? new NetworkError(error.message)
139+
: new NetworkError("Unknown network error");
140+
141+
if (attempt < MAX_RETRIES) {
142+
console.log(
143+
`Network connection issue, retrying... (attempt ${attempt} of ${MAX_RETRIES})`
144+
);
145+
await sleep(getBackoffDelay(attempt));
146+
continue;
147+
}
148+
}
149+
}
150+
151+
if (lastErrorType === "network") {
152+
throw new NetworkError(
153+
"Unable to connect to Bee services. Please check your internet connection and try again."
154+
);
155+
}
156+
157+
if (lastErrorType === "server") {
158+
throw new ServerError(
159+
"Bee servers are currently experiencing issues. Please try again later."
160+
);
161+
}
162+
163+
throw lastError ?? new Error("Request failed after multiple retries.");
164+
}
165+
166+
function getBackoffDelay(attempt: number): number {
167+
const delay = Math.min(1000 * Math.pow(2, attempt - 1), MAX_BACKOFF_MS);
168+
return delay;
169+
}
170+
171+
async function sleep(ms: number): Promise<void> {
172+
await new Promise((resolve) => setTimeout(resolve, ms));
90173
}
91174

92175
async function safeJson(

0 commit comments

Comments
 (0)