diff --git a/src/storage/client.ts b/src/storage/client.ts index e69df8f..f1b36c9 100644 --- a/src/storage/client.ts +++ b/src/storage/client.ts @@ -150,13 +150,46 @@ export class StorageClient { * ``` */ async get(cid: string): Promise> { - const response = await this.httpClient.getBinary(`/v1/storage/get/${cid}`); + // Retry logic for content retrieval - content may not be immediately available + // after upload due to eventual consistency in IPFS Cluster + // IPFS Cluster pins can take 2-3+ seconds to complete across all nodes + const maxAttempts = 8; + let lastError: Error | null = null; - if (!response.body) { - throw new Error("Response body is null"); + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + const response = await this.httpClient.getBinary( + `/v1/storage/get/${cid}` + ); + + if (!response.body) { + throw new Error("Response body is null"); + } + + return response.body; + } catch (error: any) { + lastError = error; + + // Check if this is a 404 error (content not found) + const isNotFound = + error?.httpStatus === 404 || + error?.message?.includes("not found") || + error?.message?.includes("404"); + + // If it's not a 404 error, or this is the last attempt, give up + if (!isNotFound || attempt === maxAttempts) { + throw error; + } + + // Wait before retrying (exponential backoff: 400ms, 800ms, 1200ms, etc.) + // This gives up to ~12 seconds total wait time, covering typical pin completion + const backoffMs = attempt * 2500; + await new Promise((resolve) => setTimeout(resolve, backoffMs)); + } } - return response.body; + // This should never be reached, but TypeScript needs it + throw lastError || new Error("Failed to retrieve content"); } /**