Skip to content

Commit bcb6b5f

Browse files
authored
Add retries to file uploader (#82)
* Add retries to file uploader * Bump version * Improve retries
1 parent 752a5b5 commit bcb6b5f

File tree

3 files changed

+59
-17
lines changed

3 files changed

+59
-17
lines changed

biome.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"noImplicitAnyLet": "off"
2626
},
2727
"style": {
28-
"noParameterAssign": "off"
28+
"noParameterAssign": "off",
29+
"useExponentiationOperator": "off"
2930
},
3031
"complexity": {
3132
"noForEach": "off",

packages/dashboard/src/appUtils.js

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,40 @@ function mapFile(obj, prefix) {
2121
};
2222
}
2323

24+
export async function retryWithBackoff(
25+
operation,
26+
maxAttempts = 3,
27+
initialDelay = 1000,
28+
maxDelay = 10000,
29+
backoffFactor = 2,
30+
) {
31+
let lastError = null;
32+
33+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
34+
try {
35+
return await operation();
36+
} catch (error) {
37+
lastError = error instanceof Error ? error : new Error(String(error));
38+
39+
if (attempt === maxAttempts) {
40+
throw lastError;
41+
}
42+
43+
// Calculate delay with exponential backoff
44+
const delay = Math.min(
45+
initialDelay * Math.pow(backoffFactor, attempt - 1),
46+
maxDelay,
47+
);
48+
49+
// Wait before next attempt
50+
await new Promise((resolve) => setTimeout(resolve, delay));
51+
}
52+
}
53+
54+
// This line should never be reached due to the throw above
55+
throw lastError || new Error("Unexpected error in retryWithBackoff");
56+
}
57+
2458
export const timeSince = (date) => {
2559
const seconds = Math.floor((new Date() - date) / 1000);
2660

@@ -180,22 +214,29 @@ export const apiHandler = {
180214
},
181215
});
182216
},
183-
uploadObjects: (file, key, bucket, callback) => {
184-
// console.log(key)
185-
return api.post(`/buckets/${bucket}/upload`, file, {
186-
params: {
187-
key: encode(key),
188-
httpMetadata: encode(
189-
JSON.stringify({
190-
contentType: file.type,
191-
}),
192-
),
217+
uploadObjects: async (file, key, bucket, callback) => {
218+
return await retryWithBackoff(
219+
async () => {
220+
return await api.post(`/buckets/${bucket}/upload`, file, {
221+
params: {
222+
key: encode(key),
223+
httpMetadata: encode(
224+
JSON.stringify({
225+
contentType: file.type,
226+
}),
227+
),
228+
},
229+
headers: {
230+
"Content-Type": "multipart/form-data",
231+
},
232+
onUploadProgress: callback,
233+
});
193234
},
194-
headers: {
195-
"Content-Type": "multipart/form-data",
196-
},
197-
onUploadProgress: callback,
198-
});
235+
5,
236+
1000,
237+
10000,
238+
2,
239+
);
199240
},
200241
listObjects: async (bucket, prefix, delimiter = "/", cursor = null) => {
201242
return await api.get(

packages/worker/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "r2-explorer",
3-
"version": "1.1.5",
3+
"version": "1.1.6",
44
"description": "A Google Drive Interface for your Cloudflare R2 Buckets",
55
"main": "./dist/index.js",
66
"module": "./dist/index.mjs",

0 commit comments

Comments
 (0)