feat(api, ui): handle TUS lockups, automatic chunk token refresh, and add upload cancellation
Some checks failed
Automated Container Build / build-and-push (push) Failing after 9s
Some checks failed
Automated Container Build / build-and-push (push) Failing after 9s
This commit is contained in:
parent
36d97ac330
commit
91fc088389
2 changed files with 71 additions and 17 deletions
|
|
@ -95,6 +95,7 @@ class ApiClient {
|
|||
}
|
||||
|
||||
private async refreshAccessToken(): Promise<boolean> {
|
||||
if (!this.refreshToken) return false;
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/auth/refresh`, {
|
||||
method: 'POST',
|
||||
|
|
@ -105,17 +106,35 @@ class ApiClient {
|
|||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
this.accessToken = data.access_token;
|
||||
localStorage.setItem('access_token', data.access_token);
|
||||
this.setTokens(data.access_token, data.refresh_token);
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
|
||||
// Refresh failed
|
||||
this.clearTokens();
|
||||
return false;
|
||||
} catch (e) {
|
||||
this.clearTokens();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.clearTokens();
|
||||
if (typeof window !== 'undefined') window.dispatchEvent(new Event('auth_error'));
|
||||
return false;
|
||||
async ensureTokenFresh(): Promise<boolean> {
|
||||
if (!this.accessToken) return false;
|
||||
|
||||
try {
|
||||
const parts = this.accessToken.split('.');
|
||||
if (parts.length !== 3) return false;
|
||||
const payload = JSON.parse(atob(parts[1]));
|
||||
const exp = payload.exp * 1000;
|
||||
// If expiring within 60 seconds, refresh it
|
||||
if (Date.now() > exp - 60000) {
|
||||
return await this.refreshAccessToken();
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Setup ---
|
||||
|
|
@ -227,13 +246,17 @@ class ApiClient {
|
|||
endpoint: `${API_BASE}/api/tus/`,
|
||||
chunkSize: 5 * 1024 * 1024, // 5MB chunks bypass Next.js 10MB proxy limit
|
||||
retryDelays: [0, 3000, 5000, 10000, 20000],
|
||||
headers: this.getStreamHeaders(),
|
||||
metadata: {
|
||||
filename: file.name,
|
||||
filetype: file.type,
|
||||
path: destPath,
|
||||
overwrite: options?.overwrite ? "true" : "false",
|
||||
},
|
||||
onBeforeRequest: async (req) => {
|
||||
await this.ensureTokenFresh();
|
||||
const xhr = req.getUnderlyingObject();
|
||||
xhr.setRequestHeader('Authorization', `Bearer ${this.accessToken}`);
|
||||
},
|
||||
onError: function (error) {
|
||||
reject(error);
|
||||
},
|
||||
|
|
@ -245,6 +268,15 @@ class ApiClient {
|
|||
resolve();
|
||||
},
|
||||
});
|
||||
|
||||
if (options?.signal) {
|
||||
options.signal.addEventListener('abort', () => {
|
||||
upload.abort(true).then(() => {
|
||||
reject(new Error('Upload canceled'));
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
upload.start();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue