Files
jmap-webmail/lib/jmap/retry.ts
T
Matthieu MALVACHE 27911dfe76 feat: add UX & robustness improvements (iframe rendering, retry, mobile, polish)
P0 Core:
- Sandboxed iframe rendering for rich HTML emails (CSS isolation)
- API retry with exponential backoff for transient JMAP failures
- Mobile bottom action bar with touch-friendly email actions
- Long-press context menu + tap-to-expand submenus on touch devices

P1 Polish:
- Tag counts sidebar section with JMAP batch queries
- Empty folder option for Junk/Trash with batch delete
- Extra-compact density option (28px rows, 44px on touch)
- SPF/DKIM/DMARC security tooltips with plain-language explanations
- Resizable sidebars with drag, touch, and keyboard support
- Expandable sender info panel in email viewer

All 8 locales updated (en/fr/ja/es/it/de/nl/pt).
2026-03-16 15:40:24 +01:00

51 lines
1.5 KiB
TypeScript

const RETRYABLE_STATUS_CODES = new Set([429, 502, 503, 504]);
interface RetryOptions {
maxRetries?: number;
baseDelay?: number;
signal?: AbortSignal;
}
export async function retryWithBackoff(
fn: () => Promise<Response>,
options: RetryOptions = {}
): Promise<Response> {
const { maxRetries = 3, baseDelay = 500, signal } = options;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
if (signal?.aborted) {
throw new DOMException('Aborted', 'AbortError');
}
try {
const response = await fn();
if (attempt < maxRetries && RETRYABLE_STATUS_CODES.has(response.status)) {
let delay: number;
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
const retrySeconds = retryAfter ? parseInt(retryAfter, 10) : NaN;
delay = !isNaN(retrySeconds) ? retrySeconds * 1000 : baseDelay * Math.pow(2, attempt);
} else {
delay = baseDelay * Math.pow(2, attempt);
}
const jitter = delay * (0.8 + Math.random() * 0.4);
await new Promise(resolve => setTimeout(resolve, jitter));
continue;
}
return response;
} catch (error) {
if (error instanceof TypeError && attempt < maxRetries) {
const delay = baseDelay * Math.pow(2, attempt);
const jitter = delay * (0.8 + Math.random() * 0.4);
await new Promise(resolve => setTimeout(resolve, jitter));
continue;
}
throw error;
}
}
throw new Error('Retry loop exited unexpectedly');
}