diff --git a/packages/routers/package.json b/packages/routers/package.json index e58a2d032..f47227215 100644 --- a/packages/routers/package.json +++ b/packages/routers/package.json @@ -56,6 +56,7 @@ "@helia/delegated-routing-v1-http-api-client": "^3.0.0", "@helia/interface": "^4.2.0", "@libp2p/interface": "^1.1.4", + "@multiformats/uri-to-multiaddr": "^8.0.0", "ipns": "^9.0.0", "it-first": "^3.0.4", "it-map": "^3.0.5", @@ -66,6 +67,7 @@ "@libp2p/peer-id": "^4.0.7", "@libp2p/peer-id-factory": "^4.0.7", "aegir": "^42.2.5", + "it-all": "^3.0.4", "it-drain": "^3.0.5", "sinon-ts": "^2.0.0" }, diff --git a/packages/routers/src/http-gateway-routing.ts b/packages/routers/src/http-gateway-routing.ts new file mode 100644 index 000000000..081d912d6 --- /dev/null +++ b/packages/routers/src/http-gateway-routing.ts @@ -0,0 +1,93 @@ +import { peerIdSymbol } from '@libp2p/interface' +import { uriToMultiaddr } from '@multiformats/uri-to-multiaddr' +import { CID } from 'multiformats/cid' +import { identity } from 'multiformats/hashes/identity' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import type { Provider, Routing, RoutingOptions } from '@helia/interface' +import type { PeerId, PeerInfo } from '@libp2p/interface' +import type { MultihashDigest, Version } from 'multiformats' + +export interface HTTPGatwayRouterInit { + gateways?: Array +} + +// these values are from https://github.com/multiformats/multicodec/blob/master/table.csv +const TRANSPORT_IPFS_GATEWAY_HTTP_CODE = 0x0920 +const inspect = Symbol.for('nodejs.util.inspect.custom') + +class URLPeerId implements PeerId { + readonly type = 'url' + readonly multihash: MultihashDigest + readonly privateKey?: Uint8Array + readonly publicKey?: Uint8Array + readonly url: string + + constructor (url: URL) { + this.url = url.toString() + this.multihash = identity.digest(uint8ArrayFromString(this.url)) + } + + [inspect] (): string { + return `PeerId(${this.url})` + } + + readonly [peerIdSymbol] = true + + toString (): string { + return this.toCID().toString() + } + + toCID (): CID { + return CID.createV1(TRANSPORT_IPFS_GATEWAY_HTTP_CODE, this.multihash) + } + + toBytes (): Uint8Array { + return this.toCID().bytes + } + + equals (other?: PeerId | Uint8Array | string): boolean { + if (other == null) { + return false + } + + if (other instanceof Uint8Array) { + other = uint8ArrayToString(other) + } + + return other.toString() === this.toString() + } +} + +function toPeerInfo (url: string | URL): PeerInfo { + url = url.toString() + + return { + id: new URLPeerId(new URL(url)), + multiaddrs: [ + uriToMultiaddr(url) + ] + } +} + +class HTTPGatwayRouter implements Partial { + private readonly gateways: PeerInfo[] + + constructor (init: HTTPGatwayRouterInit = {}) { + this.gateways = (init.gateways ?? []).map(url => toPeerInfo(url)) + } + + async * findProviders (cid: CID, options?: RoutingOptions | undefined): AsyncIterable { + yield * this.gateways.map(info => ({ + ...info, + protocols: ['transport-ipfs-gateway-http'] + })) + } +} + +/** + * Returns a static list of HTTP Gateways as providers + */ +export function httpGatewayRouting (init: HTTPGatwayRouterInit = {}): Partial { + return new HTTPGatwayRouter(init) +} diff --git a/packages/routers/src/index.ts b/packages/routers/src/index.ts index c82e8cbd5..df504b996 100644 --- a/packages/routers/src/index.ts +++ b/packages/routers/src/index.ts @@ -4,4 +4,5 @@ * Abstraction layer over different content and peer routing mechanisms. */ export { delegatedHTTPRouting } from './delegated-http-routing.js' +export { httpGatewayRouting } from './http-gateway-routing.js' export { libp2pRouting } from './libp2p-routing.js' diff --git a/packages/routers/test/http-gateway-routing.spec.ts b/packages/routers/test/http-gateway-routing.spec.ts new file mode 100644 index 000000000..8c01f20b6 --- /dev/null +++ b/packages/routers/test/http-gateway-routing.spec.ts @@ -0,0 +1,23 @@ +import { expect } from 'aegir/chai' +import all from 'it-all' +import { CID } from 'multiformats' +import { httpGatewayRouting } from '../src/http-gateway-routing.js' + +describe('http-gateway-routing', () => { + it('should find providers', async () => { + const gateway = 'https://example.com' + const routing = httpGatewayRouting({ + gateways: [ + gateway + ] + }) + + const cid = CID.parse('bafyreidykglsfhoixmivffc5uwhcgshx4j465xwqntbmu43nb2dzqwfvae') + + const providers = await all(routing.findProviders?.(cid) ?? []) + + expect(providers).to.have.lengthOf(1) + expect(providers).to.have.nested.property('[0].protocols').that.includes('transport-ipfs-gateway-http') + expect(providers[0].multiaddrs.map(ma => ma.toString())).to.include('/dns4/example.com/tcp/443/https') + }) +})