Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 40 additions & 5 deletions packages/client/.aegir.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,55 @@ const options = {
const echo = new EchoServer()
echo.polka.use(body.raw({ type: 'application/vnd.ipfs.ipns-record'}))
echo.polka.use(body.text())
echo.polka.use(body.json())
echo.polka.use((req, res, next) => {
next()
lastCalledUrl = req.url
})
echo.polka.post('/add-providers/:cid', (req, res) => {
callCount++
providers.set(req.params.cid, req.body)
res.end()
try {

let data
try {
// when passed data from a test where `body=providers.map(prov => JSON.stringify(prov)).join('\n')`
data = { Providers: req.body.split('\n').map(line => JSON.parse(line)) }
} catch (err) {
// when passed data from a test where `body=JSON.stringify({ Providers: providers })`
data = req.body
}

providers.set(req.params.cid, data)
res.end(JSON.stringify({ success: true }))
} catch (err) {
console.error('Error in add-providers:', err)
res.statusCode = 400
res.end(JSON.stringify({
error: err.message,
code: 'ERR_INVALID_INPUT'
}))
providers.delete(req.params.cid)
}
})
echo.polka.get('/routing/v1/providers/:cid', (req, res) => {
callCount++
const records = providers.get(req.params.cid) ?? '[]'
providers.delete(req.params.cid)
res.end(records)
try {
const providerData = providers.get(req.params.cid) || { Providers: [] }
const acceptHeader = req.headers.accept

if (acceptHeader?.includes('application/x-ndjson')) {
res.setHeader('Content-Type', 'application/x-ndjson')
const providers = Array.isArray(providerData.Providers) ? providerData.Providers : []
res.end(providers.map(p => JSON.stringify(p)).join('\n'))
} else {
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(providerData))
}
} catch (err) {
console.error('Error in get providers:', err)
res.statusCode = 500
res.end(JSON.stringify({ error: err.message }))
}
})
echo.polka.post('/add-peers/:peerId', (req, res) => {
callCount++
Expand Down
26 changes: 19 additions & 7 deletions packages/client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,24 +122,34 @@
const getOptions = { headers: { Accept: 'application/x-ndjson' }, signal }
const res = await this.#makeRequest(url.toString(), getOptions)

if (res.status === 404) {
if (res == null) {
throw new BadResponseError('No response received')
}

Check warning on line 127 in packages/client/src/client.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/src/client.ts#L126-L127

Added lines #L126 - L127 were not covered by tests
if (!res.ok) {
if (res.status === 404) {

Check warning on line 129 in packages/client/src/client.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/src/client.ts#L129

Added line #L129 was not covered by tests
// https://specs.ipfs.tech/routing/http-routing-v1/#response-status-codes
// 404 (Not Found): must be returned if no matching records are found
throw new NotFoundError('No matching records found')
}
throw new NotFoundError('No matching records found')
}

Check warning on line 133 in packages/client/src/client.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/src/client.ts#L132-L133

Added lines #L132 - L133 were not covered by tests

if (res.status === 422) {
if (res.status === 422) {

Check warning on line 135 in packages/client/src/client.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/src/client.ts#L135

Added line #L135 was not covered by tests
// https://specs.ipfs.tech/routing/http-routing-v1/#response-status-codes
// 422 (Unprocessable Entity): request does not conform to schema or semantic constraints
throw new InvalidRequestError('Request does not conform to schema or semantic constraints')
throw new InvalidRequestError('Request does not conform to schema or semantic constraints')
}
throw new BadResponseError(`Unexpected status code: ${res.status}`)

Check warning on line 140 in packages/client/src/client.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/src/client.ts#L138-L140

Added lines #L138 - L140 were not covered by tests
}

if (res.body == null) {
throw new BadResponseError('Routing response had no body')
}

const contentType = res.headers.get('Content-Type')
if (contentType === 'application/json') {
if (contentType == null) {
throw new BadResponseError('No Content-Type header received')
}

Check warning on line 150 in packages/client/src/client.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/src/client.ts#L149-L150

Added lines #L149 - L150 were not covered by tests

if (contentType?.startsWith('application/json')) {
const body = await res.json()

for (const provider of body.Providers) {
Expand All @@ -148,13 +158,15 @@
yield record
}
}
} else {
} else if (contentType.includes('application/x-ndjson')) {
for await (const provider of ndjson(toIt(res.body))) {
const record = this.#conformToPeerSchema(provider)
if (record != null) {
yield record
}
}
} else {
throw new BadResponseError(`Unsupported Content-Type: ${contentType}`)

Check warning on line 169 in packages/client/src/client.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/src/client.ts#L169

Added line #L169 was not covered by tests
}
} catch (err) {
log.error('getProviders errored:', err)
Expand Down
35 changes: 35 additions & 0 deletions packages/client/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,41 @@ describe('delegated-routing-v1-http-api-client', () => {
})))
})

it('should handle different Content-Type headers for JSON responses', async () => {
const providers = [{
Protocol: 'transport-bitswap',
Schema: 'bitswap',
Metadata: 'gBI=',
ID: (await generateKeyPair('Ed25519')).publicKey.toString(),
Addrs: ['/ip4/41.41.41.41/tcp/1234']
}]

const cid = CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')
const contentTypes = [
'application/json',
'application/json; charset=utf-8',
'application/json;charset=UTF-8'
]

for (const contentType of contentTypes) {
// Add providers with proper payload structure
await fetch(`${process.env.ECHO_SERVER}/add-providers/${cid.toString()}`, {
method: 'POST',
headers: {
'Content-Type': contentType
},
body: JSON.stringify({ Providers: providers })
})

await new Promise((resolve) => setTimeout(resolve, 100))
const provs = await all(client.getProviders(cid))

expect(provs).to.have.lengthOf(1, `Failed for Content-Type: ${contentType}`)
expect(provs[0].ID.toString()).to.equal(providers[0].ID)
expect(provs[0].Addrs[0].toString()).to.equal(providers[0].Addrs[0])
}
})

it('should add filter parameters the query of the request url', async () => {
const providers = [{
Protocol: 'transport-bitswap',
Expand Down
Loading