Skip to content

Commit dde050c

Browse files
committed
feat: add explicit support for subdomain gateways
1 parent 8db7792 commit dde050c

File tree

4 files changed

+60
-19
lines changed

4 files changed

+60
-19
lines changed

packages/block-brokers/src/trustless-gateway/broker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ ProgressOptions<TrustlessGatewayGetBlockProgressEvents>
1919
constructor (components: TrustlessGatewayComponents, init: TrustlessGatewayBlockBrokerInit = {}) {
2020
this.log = components.logger.forComponent('helia:trustless-gateway-block-broker')
2121
this.gateways = (init.gateways ?? DEFAULT_TRUSTLESS_GATEWAYS)
22-
.map((gatewayOrUrl) => {
23-
return new TrustlessGateway(gatewayOrUrl)
22+
.map((gw) => {
23+
return new TrustlessGateway(gw.url, gw.isSubdomain)
2424
})
2525
}
2626

packages/block-brokers/src/trustless-gateway/index.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,27 @@ import type { BlockRetriever } from '@helia/interface/src/blocks.js'
33
import type { ComponentLogger } from '@libp2p/interface'
44
import type { ProgressEvent } from 'progress-events'
55

6-
export const DEFAULT_TRUSTLESS_GATEWAYS = [
7-
// 2023-10-03: IPNS, Origin, and Block/CAR support from https://ipfs-public-gateway-checker.on.fleek.co/
8-
'https://trustless-gateway.link',
6+
export const DEFAULT_TRUSTLESS_GATEWAYS: TrustlessGatewayUrl[] = [
7+
// 2024-02-20: IPNS and Block/CAR support from https://ipfs.github.io/public-gateway-checker/
8+
{ url: 'https://trustless-gateway.link', isSubdomain: false },
99

10-
// 2023-10-03: IPNS, Origin, and Block/CAR support from https://ipfs-public-gateway-checker.on.fleek.co/
11-
'https://cloudflare-ipfs.com',
10+
// 2024-02-20: IPNS and Block/CAR support from https://ipfs.github.io/public-gateway-checker/
11+
{ url: 'https://cloudflare-ipfs.com', isSubdomain: false },
1212

13-
// 2023-10-03: IPNS, Origin, and Block/CAR support from https://ipfs-public-gateway-checker.on.fleek.co/
14-
'https://4everland.io'
13+
// 2024-02-20: IPNS, Origin, and Block/CAR support from https://ipfs.github.io/public-gateway-checker/
14+
{ url: 'https://4everland.io', isSubdomain: true },
1515
]
1616

17+
interface TrustlessGatewayUrl {
18+
url: string | URL
19+
isSubdomain: boolean
20+
}
21+
1722
export type TrustlessGatewayGetBlockProgressEvents =
1823
ProgressEvent<'trustless-gateway:get-block:fetch', URL>
1924

2025
export interface TrustlessGatewayBlockBrokerInit {
21-
gateways?: Array<string | URL>
26+
gateways?: Array<TrustlessGatewayUrl>
2227
}
2328

2429
export interface TrustlessGatewayComponents {

packages/block-brokers/src/trustless-gateway/trustless-gateway.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { CID } from 'multiformats/cid'
2+
import { base32 } from 'multiformats/bases/base32'
23

34
/**
45
* A `TrustlessGateway` keeps track of the number of attempts, errors, and
@@ -8,6 +9,12 @@ import type { CID } from 'multiformats/cid'
89
*/
910
export class TrustlessGateway {
1011
public readonly url: URL
12+
13+
/**
14+
* Whether this gateway is a subdomain resolution style gateway
15+
*/
16+
public isSubdomain: boolean
17+
1118
/**
1219
* The number of times this gateway has been attempted to be used to fetch a
1320
* block. This includes successful, errored, and aborted attempts. By counting
@@ -36,34 +43,36 @@ export class TrustlessGateway {
3643
*/
3744
#successes = 0
3845

39-
constructor (url: URL | string) {
46+
constructor(url: URL | string, isSubdomain: boolean = false) {
4047
this.url = url instanceof URL ? url : new URL(url)
48+
this.isSubdomain = isSubdomain
4149
}
4250

4351
/**
4452
* Fetch a raw block from `this.url` following the specification defined at
4553
* https://specs.ipfs.tech/http-gateways/trustless-gateway/
4654
*/
47-
async getRawBlock (cid: CID, signal?: AbortSignal): Promise<Uint8Array> {
48-
const gwUrl = this.url
49-
gwUrl.pathname = `/ipfs/${cid.toString()}`
55+
async getRawBlock(cid: CID, signal?: AbortSignal): Promise<Uint8Array> {
56+
const gwUrl = this.getGwUrl(cid)
5057

5158
// necessary as not every gateway supports dag-cbor, but every should support
5259
// sending raw block as-is
5360
gwUrl.search = '?format=raw'
5461

5562
if (signal?.aborted === true) {
56-
throw new Error(`Signal to fetch raw block for CID ${cid} from gateway ${this.url} was aborted prior to fetch`)
63+
throw new Error(
64+
`Signal to fetch raw block for CID ${cid} from gateway ${this.url} was aborted prior to fetch`,
65+
)
5766
}
5867

5968
try {
6069
this.#attempts++
6170
const res = await fetch(gwUrl.toString(), {
6271
signal,
6372
headers: {
64-
// also set header, just in case ?format= is filtered out by some
65-
// reverse proxy
66-
Accept: 'application/vnd.ipld.raw'
73+
// also set header, just in case ?format= is filtered out by some
74+
// reverse proxy
75+
Accept: 'application/vnd.ipld.raw',
6776
},
6877
cache: 'force-cache'
6978
})
@@ -84,6 +93,20 @@ export class TrustlessGateway {
8493
}
8594
}
8695

96+
/**
97+
* Construct the Gateway URL for a CID
98+
*/
99+
getGwUrl(cid: CID): URL {
100+
const gwUrl = new URL(this.url)
101+
102+
if (this.isSubdomain) {
103+
gwUrl.hostname = `${cid.toString(base32)}.ipfs.${gwUrl.hostname}`
104+
} else {
105+
gwUrl.pathname = `/ipfs/${cid.toString()}`
106+
}
107+
return gwUrl
108+
}
109+
87110
/**
88111
* Encapsulate the logic for determining whether a gateway is considered
89112
* reliable, for prioritization. This is based on the number of successful attempts made

packages/block-brokers/test/trustless-gateway.spec.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ describe('trustless-gateway-block-broker', () => {
3737

3838
gateways = [
3939
stubConstructor(TrustlessGateway, 'http://localhost:8080'),
40-
stubConstructor(TrustlessGateway, 'http://localhost:8081'),
40+
stubConstructor(TrustlessGateway, 'http://localhost:8081', true),
4141
stubConstructor(TrustlessGateway, 'http://localhost:8082'),
4242
stubConstructor(TrustlessGateway, 'http://localhost:8083')
4343
]
@@ -150,4 +150,17 @@ describe('trustless-gateway-block-broker', () => {
150150
expect(gateways[1].getRawBlock.calledWith(cid1, Sinon.match.any)).to.be.false()
151151
expect(gateways[2].getRawBlock.calledWith(cid1, Sinon.match.any)).to.be.false()
152152
})
153+
154+
it('constructs the gateway url for the cid for both path and subdomain gateways', async () => {
155+
const pathGw = new TrustlessGateway('http://localhost:8080')
156+
const subdomainGw = new TrustlessGateway('https://dweb.link', true)
157+
158+
expect(pathGw.getGwUrl(blocks[0].cid).hostname).to.equal(`localhost`)
159+
expect(pathGw.getGwUrl(blocks[0].cid).toString()).to.equal(`http://localhost:8080/ipfs/bafkreiefnkxuhnq3536qo2i2w3tazvifek4mbbzb6zlq3ouhprjce5c3aq`)
160+
expect(pathGw.getGwUrl(blocks[1].cid).toString()).to.equal(`http://localhost:8080/ipfs/${blocks[1].cid.toString()}`)
161+
162+
expect(subdomainGw.getGwUrl(blocks[0].cid).hostname).to.equal(`bafkreiefnkxuhnq3536qo2i2w3tazvifek4mbbzb6zlq3ouhprjce5c3aq.ipfs.dweb.link`)
163+
expect(subdomainGw.getGwUrl(blocks[0].cid).toString()).to.equal(`https://bafkreiefnkxuhnq3536qo2i2w3tazvifek4mbbzb6zlq3ouhprjce5c3aq.ipfs.dweb.link/`)
164+
expect(subdomainGw.getGwUrl(blocks[1].cid).toString()).to.equal(`https://${blocks[1].cid.toString()}.ipfs.dweb.link/`)
165+
})
153166
})

0 commit comments

Comments
 (0)