1export interface ShowcaseRetryOptions {
2 readonly isCancelled: () => boolean;
3 readonly attemptTimeoutMs?: number;
4 readonly retryDelayMs?: number;
5}
6
7const DEFAULT_ATTEMPT_TIMEOUT_MS = 10_000;
8const DEFAULT_RETRY_DELAY_MS = 500;
9
10function delay(ms: number): Promise<void> {
11 return new Promise((resolve) => setTimeout(resolve, ms));
12}
13
14async function runAttemptWithTimeout(
15 operation: () => Promise<boolean>,
16 timeoutMs: number,
17): Promise<boolean> {
18 let timeout: ReturnType<typeof setTimeout> | null = null;
19 try {
20 return await Promise.race([
21 operation(),
22 new Promise<boolean>((resolve) => {
23 timeout = setTimeout(() => resolve(false), timeoutMs);
24 }),
25 ]);
26 } catch {
27 return false;
28 } finally {
29 if (timeout !== null) clearTimeout(timeout);
30 }
31}
32
33/** Retry transient showcase setup work until it succeeds or the owning effect unmounts. */
34export async function retryShowcaseOperation(
35 operation: () => Promise<boolean>,
36 options: ShowcaseRetryOptions,
37): Promise<boolean> {
38 const attemptTimeoutMs = options.attemptTimeoutMs ?? DEFAULT_ATTEMPT_TIMEOUT_MS;
39 const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
40
41 while (!options.isCancelled()) {
42 if (await runAttemptWithTimeout(operation, attemptTimeoutMs)) return true;
43 if (!options.isCancelled()) await delay(retryDelayMs);
44 }
45 return false;
46}