Skip to content

Commit 996af92

Browse files
authored
fix: ipni indexer fetch errors are caught (#241)
* fix: ipni indexer fetch errors are caught * fix: better error messages on ipni check failure * docs: add jsdoc to getErrorMessage util function
1 parent ff610e6 commit 996af92

File tree

3 files changed

+53
-11
lines changed

3 files changed

+53
-11
lines changed

src/core/utils/errors.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Safely extracts an error message from an unknown error value.
3+
*
4+
* @param error - The error value to extract a message from
5+
* @returns The error message string, or 'Unknown error' if the error type cannot be determined
6+
*
7+
* @example
8+
* ```typescript
9+
* try {
10+
* // some operation
11+
* } catch (error) {
12+
* const message = getErrorMessage(error)
13+
* logger.error(`Operation failed: ${message}`)
14+
* }
15+
* ```
16+
*/
17+
export function getErrorMessage(error: unknown): string {
18+
if (error instanceof Error) {
19+
return error.message
20+
}
21+
if (typeof error === 'string') {
22+
return error
23+
}
24+
return 'Unknown error'
25+
}

src/core/utils/validate-ipni-advertisement.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ProviderInfo } from '@filoz/synapse-sdk'
22
import type { CID } from 'multiformats/cid'
33
import type { Logger } from 'pino'
4+
import { getErrorMessage } from './errors.js'
45
import type { ProgressEvent, ProgressEventHandler } from './types.js'
56

67
/**
@@ -168,10 +169,17 @@ export async function waitForIpniProviderResults(
168169
fetchOptions.signal = options?.signal
169170
}
170171

171-
const response = await fetch(`${ipniIndexerUrl}/cid/${ipfsRootCid}`, fetchOptions)
172+
let response: Response | undefined
173+
try {
174+
response = await fetch(`${ipniIndexerUrl}/cid/${ipfsRootCid}`, fetchOptions)
175+
} catch (fetchError) {
176+
lastActualMultiaddrs = new Set()
177+
lastFailureReason = `Failed to query IPNI indexer: ${getErrorMessage(fetchError)}`
178+
options?.logger?.warn({ error: fetchError }, `${lastFailureReason}. Retrying...`)
179+
}
172180

173181
// Parse and validate response
174-
if (response.ok) {
182+
if (response?.ok) {
175183
let providerResults: ProviderResult[] = []
176184
try {
177185
const body = (await response.json()) as IpniIndexerResponse
@@ -183,7 +191,7 @@ export async function waitForIpniProviderResults(
183191
} catch (parseError) {
184192
// Clear actual multiaddrs on parse error
185193
lastActualMultiaddrs = new Set()
186-
lastFailureReason = 'Failed to parse IPNI response body'
194+
lastFailureReason = `Failed to parse IPNI response body: ${getErrorMessage(parseError)}`
187195
options?.logger?.warn({ error: parseError }, `${lastFailureReason}. Retrying...`)
188196
}
189197

@@ -239,6 +247,13 @@ export async function waitForIpniProviderResults(
239247
`${lastFailureReason}. Retrying...`
240248
)
241249
}
250+
} else if (response != null) {
251+
lastActualMultiaddrs = new Set()
252+
lastFailureReason = `IPNI indexer request failed with status ${response.status}`
253+
options?.logger?.info(
254+
{ status: response.status, statusText: response.statusText },
255+
`${lastFailureReason}. Retrying...`
256+
)
242257
}
243258

244259
// Retry or fail
@@ -311,7 +326,8 @@ export function serviceURLToMultiaddr(serviceURL: string, logger?: Logger): stri
311326

312327
return `/dns/${url.hostname}/tcp/${port}/${protocolComponent}`
313328
} catch (error) {
314-
logger?.warn({ serviceURL, error }, 'Unable to derive IPNI multiaddr from serviceURL')
329+
const reason = getErrorMessage(error)
330+
logger?.warn({ serviceURL, error }, `Unable to derive IPNI multiaddr from serviceURL: ${reason}`)
315331
return undefined
316332
}
317333
}

src/test/unit/validate-ipni-advertisement.test.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -304,14 +304,15 @@ describe('waitForIpniProviderResults', () => {
304304
})
305305

306306
describe('edge cases', () => {
307-
it('should handle fetch throwing an error', async () => {
308-
mockFetch.mockRejectedValueOnce(new Error('Network error'))
309-
310-
const promise = waitForIpniProviderResults(testCid, {})
311-
const expectPromise = expect(promise).rejects.toThrow('Network error')
307+
it('should retry when fetch throws before succeeding within maxAttempts', async () => {
308+
mockFetch.mockRejectedValueOnce(new Error('Network error')).mockResolvedValueOnce(successResponse())
312309

310+
const promise = waitForIpniProviderResults(testCid, { maxAttempts: 2, delayMs: 1 })
313311
await vi.runAllTimersAsync()
314-
await expectPromise
312+
const result = await promise
313+
314+
expect(result).toBe(true)
315+
expect(mockFetch).toHaveBeenCalledTimes(2)
315316
})
316317

317318
it('should handle different CID formats', async () => {
@@ -403,7 +404,7 @@ describe('waitForIpniProviderResults', () => {
403404
})
404405

405406
const expectPromise = expect(promise).rejects.toThrow(
406-
'Failed to parse IPNI response body. Expected multiaddrs: [/dns/expected.example.com/tcp/443/https]. Actual multiaddrs in response: []'
407+
`IPFS root CID "${testCid.toString()}" does not have expected IPNI ProviderResults after 2 attempts. Last observation: Failed to parse IPNI response body: Invalid JSON. Expected multiaddrs: [/dns/expected.example.com/tcp/443/https]. Actual multiaddrs in response: []`
407408
)
408409

409410
await vi.runAllTimersAsync()

0 commit comments

Comments
 (0)