Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: upgrade to helia 5 #107

Merged
merged 4 commits into from
Oct 14, 2024
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
66 changes: 33 additions & 33 deletions packages/verified-fetch/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,52 +57,52 @@
"release": "aegir release"
},
"dependencies": {
"@helia/block-brokers": "^3.0.1",
"@helia/car": "^3.1.5",
"@helia/http": "^1.0.8",
"@helia/interface": "^4.3.0",
"@helia/ipns": "^7.2.2",
"@helia/routers": "^1.1.0",
"@ipld/dag-cbor": "^9.2.0",
"@ipld/dag-json": "^10.2.0",
"@ipld/dag-pb": "^4.1.0",
"@libp2p/interface": "^1.4.0",
"@libp2p/kad-dht": "^12.0.17",
"@libp2p/peer-id": "^4.1.2",
"@helia/block-brokers": "^4.0.0",
"@helia/car": "^4.0.0",
"@helia/http": "^2.0.0",
"@helia/interface": "^5.0.0",
"@helia/ipns": "^8.0.0",
"@helia/routers": "^2.0.0",
"@helia/unixfs": "^4.0.0",
"@ipld/dag-cbor": "^9.2.1",
"@ipld/dag-json": "^10.2.2",
"@ipld/dag-pb": "^4.1.2",
"@libp2p/interface": "^2.1.3",
"@libp2p/kad-dht": "^14.0.1",
"@libp2p/peer-id": "^5.0.5",
"@multiformats/dns": "^1.0.6",
"cborg": "^4.2.0",
"cborg": "^4.2.4",
"hashlru": "^2.3.0",
"interface-blockstore": "^5.2.10",
"interface-datastore": "^8.2.11",
"ipfs-unixfs-exporter": "^13.5.0",
"it-map": "^3.1.0",
"interface-blockstore": "^5.3.1",
"interface-datastore": "^8.3.1",
"ipfs-unixfs-exporter": "^13.6.1",
"it-map": "^3.1.1",
"it-pipe": "^3.0.1",
"it-tar": "^6.0.5",
"it-to-browser-readablestream": "^2.0.9",
"lru-cache": "^10.2.2",
"multiformats": "^13.1.0",
"progress-events": "^1.0.0",
"multiformats": "^13.3.0",
"progress-events": "^1.0.1",
"uint8arrays": "^5.1.0"
},
"devDependencies": {
"@helia/dag-cbor": "^3.0.4",
"@helia/dag-json": "^3.0.4",
"@helia/json": "^3.0.4",
"@helia/unixfs": "^3.0.6",
"@helia/utils": "^0.3.1",
"@ipld/car": "^5.3.0",
"@libp2p/interface-compliance-tests": "^5.4.5",
"@libp2p/logger": "^4.0.13",
"@libp2p/peer-id-factory": "^4.1.2",
"@helia/dag-cbor": "^4.0.0",
"@helia/dag-json": "^4.0.0",
"@helia/json": "^4.0.0",
"@helia/utils": "^1.0.0",
"@ipld/car": "^5.3.2",
"@libp2p/crypto": "^5.0.5",
"@libp2p/interface-compliance-tests": "^6.1.6",
"@libp2p/logger": "^5.1.1",
"@sgtpooki/file-type": "^1.0.1",
"@types/sinon": "^17.0.3",
"aegir": "^42.2.11",
"blockstore-core": "^4.4.1",
"blockstore-core": "^5.0.2",
"browser-readablestream-to-it": "^2.0.7",
"datastore-core": "^9.2.9",
"helia": "^4.2.2",
"ipfs-unixfs-importer": "^15.2.5",
"ipns": "^9.1.0",
"datastore-core": "^10.0.2",
"helia": "^5.0.0",
"ipfs-unixfs-importer": "^15.3.1",
"ipns": "^10.0.0",
"it-all": "^3.0.6",
"it-drain": "^3.0.7",
"it-last": "^3.0.6",
Expand Down
26 changes: 26 additions & 0 deletions packages/verified-fetch/src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export class InvalidRangeError extends Error {
static name = 'InvalidRangeError'

constructor (message = 'Invalid range request') {
super(message)
this.name = 'InvalidRangeError'
}
}

export class NoContentError extends Error {
static name = 'NoContentError'

constructor (message = 'No content found') {
super(message)
this.name = 'NoContentError'
}
}

export class SubdomainNotSupportedError extends Error {
static name = 'SubdomainNotSupportedError'

constructor (message = 'Subdomain not supported') {
super(message)
this.name = 'SubdomainNotSupportedError'
}

Check warning on line 25 in packages/verified-fetch/src/errors.ts

View check run for this annotation

Codecov / codecov/patch

packages/verified-fetch/src/errors.ts#L23-L25

Added lines #L23 - L25 were not covered by tests
}
5 changes: 3 additions & 2 deletions packages/verified-fetch/src/utils/byte-range-context.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { InvalidRangeError } from '../errors.js'
import { calculateByteRangeIndexes, getHeader } from './request-headers.js'
import { getContentRangeHeader } from './response-headers.js'
import type { SupportedBodyTypes } from '../types.js'
Expand Down Expand Up @@ -32,7 +33,7 @@
*/
const match = rangeHeader.match(/^bytes=(?<start>\d+)?-(?<end>\d+)?$/)
if (match?.groups == null) {
throw new Error('Invalid range request')
throw new InvalidRangeError('Invalid range request')
}

const { start, end } = match.groups
Expand Down Expand Up @@ -289,7 +290,7 @@
public get contentRangeHeaderValue (): string {
if (!this.isValidRangeRequest) {
this.log.error('cannot get contentRangeHeaderValue for invalid range request')
throw new Error('Invalid range request')
throw new InvalidRangeError('Invalid range request')

Check warning on line 293 in packages/verified-fetch/src/utils/byte-range-context.ts

View check run for this annotation

Codecov / codecov/patch

packages/verified-fetch/src/utils/byte-range-context.ts#L293

Added line #L293 was not covered by tests
}

return getContentRangeHeader({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AbortError, type ComponentLogger } from '@libp2p/interface'
import { CustomProgressEvent } from 'progress-events'
import { NoContentError } from '../errors.js'
import type { VerifiedFetchInit } from '../index.js'

/**
Expand All @@ -12,7 +13,7 @@ export async function getStreamFromAsyncIterable (iterator: AsyncIterable<Uint8A

if (done === true) {
log.error('no content found for path', path)
throw new Error('No content found')
throw new NoContentError()
}

const stream = new ReadableStream({
Expand Down
6 changes: 3 additions & 3 deletions packages/verified-fetch/src/utils/get-tar-stream.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CodeError } from '@libp2p/interface'
import { NotUnixFSError } from '@helia/unixfs/errors'
import { exporter, recursive, type UnixFSEntry } from 'ipfs-unixfs-exporter'
import map from 'it-map'
import { pipe } from 'it-pipe'
Expand Down Expand Up @@ -28,7 +28,7 @@

function toTarImportCandidate (entry: UnixFSEntry): TarImportCandidate {
if (!EXPORTABLE.includes(entry.type)) {
throw new CodeError('Not a UnixFS node', 'ERR_NOT_UNIXFS')
throw new NotUnixFSError(`${entry.type} is not a UnixFS node`)

Check warning on line 31 in packages/verified-fetch/src/utils/get-tar-stream.ts

View check run for this annotation

Codecov / codecov/patch

packages/verified-fetch/src/utils/get-tar-stream.ts#L31

Added line #L31 was not covered by tests
}

const candidate: TarImportCandidate = {
Expand Down Expand Up @@ -64,5 +64,5 @@
return
}

throw new CodeError('Not a UnixFS node', 'ERR_NOT_UNIXFS')
throw new NotUnixFSError('Not a UnixFS node')

Check warning on line 67 in packages/verified-fetch/src/utils/get-tar-stream.ts

View check run for this annotation

Codecov / codecov/patch

packages/verified-fetch/src/utils/get-tar-stream.ts#L67

Added line #L67 was not covered by tests
}
3 changes: 2 additions & 1 deletion packages/verified-fetch/src/utils/handle-redirects.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type AbortOptions, type ComponentLogger } from '@libp2p/interface'
import { SubdomainNotSupportedError } from '../errors.js'
import { type VerifiedFetchInit, type Resource } from '../index.js'
import { matchURLString } from './parse-url-string.js'
import { movedPermanentlyResponse } from './responses.js'
Expand Down Expand Up @@ -82,7 +83,7 @@
return movedPermanentlyResponse(resource.toString(), subdomainUrl.href)
} else {
log('subdomain not supported, subdomain failed with status %s %s', subdomainTest.status, subdomainTest.statusText)
throw new Error('subdomain not supported')
throw new SubdomainNotSupportedError('subdomain not supported')

Check warning on line 86 in packages/verified-fetch/src/utils/handle-redirects.ts

View check run for this annotation

Codecov / codecov/patch

packages/verified-fetch/src/utils/handle-redirects.ts#L86

Added line #L86 was not covered by tests
}
} catch (err: any) {
log('subdomain not supported', err)
Expand Down
9 changes: 6 additions & 3 deletions packages/verified-fetch/src/utils/parse-url-string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { TLRU } from './tlru.js'
import type { RequestFormatShorthand } from '../types.js'
import type { DNSLinkResolveResult, IPNS, IPNSResolveResult, IPNSRoutingEvents, ResolveDNSLinkProgressEvents, ResolveProgressEvents, ResolveResult } from '@helia/ipns'
import type { AbortOptions, ComponentLogger } from '@libp2p/interface'
import type { AbortOptions, ComponentLogger, PeerId } from '@libp2p/interface'
import type { ProgressOptions } from 'progress-events'

const ipnsCache = new TLRU<DNSLinkResolveResult | IPNSResolveResult>(1000)
Expand Down Expand Up @@ -174,11 +174,14 @@
log.trace('resolved %s to %c from cache', cidOrPeerIdOrDnsLink, cid)
} else {
log.trace('Attempting to resolve PeerId for %s', cidOrPeerIdOrDnsLink)
let peerId = null
let peerId: PeerId | undefined
try {
// try resolving as an IPNS name
peerId = peerIdFromString(cidOrPeerIdOrDnsLink)
resolveResult = await ipns.resolve(peerId, options)
if (peerId.publicKey == null) {
throw new TypeError('cidOrPeerIdOrDnsLink contains no public key')

Check warning on line 182 in packages/verified-fetch/src/utils/parse-url-string.ts

View check run for this annotation

Codecov / codecov/patch

packages/verified-fetch/src/utils/parse-url-string.ts#L182

Added line #L182 was not covered by tests
}
resolveResult = await ipns.resolve(peerId.publicKey, options)
cid = resolveResult?.cid
resolvedPath = resolveResult?.path
log.trace('resolved %s to %c', cidOrPeerIdOrDnsLink, cid)
Expand Down
10 changes: 6 additions & 4 deletions packages/verified-fetch/src/utils/request-headers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { InvalidRangeError } from '../errors.js'

export function getHeader (headers: HeadersInit | undefined, header: string): string | undefined {
if (headers == null) {
return undefined
Expand Down Expand Up @@ -26,16 +28,16 @@ export function getHeader (headers: HeadersInit | undefined, header: string): st
// eslint-disable-next-line complexity
export function calculateByteRangeIndexes (start: number | undefined, end: number | undefined, fileSize?: number): { byteSize?: number, start?: number, end?: number } {
if ((start ?? 0) > (end ?? Infinity)) {
throw new Error('Invalid range: Range-start index is greater than range-end index.')
throw new InvalidRangeError('Invalid range: Range-start index is greater than range-end index.')
}
if (start != null && (end ?? 0) >= (fileSize ?? Infinity)) {
throw new Error('Invalid range: Range-end index is greater than or equal to the size of the file.')
throw new InvalidRangeError('Invalid range: Range-end index is greater than or equal to the size of the file.')
}
if (start == null && (end ?? 0) > (fileSize ?? Infinity)) {
throw new Error('Invalid range: Range-end index is greater than the size of the file.')
throw new InvalidRangeError('Invalid range: Range-end index is greater than the size of the file.')
}
if (start != null && start < 0) {
throw new Error('Invalid range: Range-start index cannot be negative.')
throw new InvalidRangeError('Invalid range: Range-start index cannot be negative.')
}

if (start != null && end != null) {
Expand Down
5 changes: 3 additions & 2 deletions packages/verified-fetch/src/utils/response-headers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { InvalidRangeError } from '../errors.js'
import type { CID } from 'multiformats/cid'

interface CacheControlHeaderOptions {
Expand Down Expand Up @@ -46,10 +47,10 @@ export function getContentRangeHeader ({ byteStart, byteEnd, byteSize }: { byteS
const total = byteSize ?? '*' // if we don't know the total size, we should use *

if ((byteEnd ?? 0) >= (byteSize ?? Infinity)) {
throw new Error('Invalid range: Range-end index is greater than or equal to the size of the file.')
throw new InvalidRangeError('Invalid range: Range-end index is greater than or equal to the size of the file.')
}
if ((byteStart ?? 0) >= (byteSize ?? Infinity)) {
throw new Error('Invalid range: Range-start index is greater than or equal to the size of the file.')
throw new InvalidRangeError('Invalid range: Range-start index is greater than or equal to the size of the file.')
}

if (byteStart != null && byteEnd == null) {
Expand Down
5 changes: 3 additions & 2 deletions packages/verified-fetch/src/utils/walk-path.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CodeError, type Logger } from '@libp2p/interface'
import { DoesNotExistError } from '@helia/unixfs/errors'
import { type Logger } from '@libp2p/interface'
import { type Blockstore } from 'interface-blockstore'
import { walkPath as exporterWalk, type ExporterOptions, type ReadableStorage, type ObjectNode, type UnixFSEntry } from 'ipfs-unixfs-exporter'
import { type FetchHandlerFunctionArg } from '../types.js'
Expand Down Expand Up @@ -28,7 +29,7 @@
}

if (terminalElement == null) {
throw new CodeError('No terminal element found', 'ERR_NO_TERMINAL_ELEMENT')
throw new DoesNotExistError('No terminal element found')

Check warning on line 32 in packages/verified-fetch/src/utils/walk-path.ts

View check run for this annotation

Codecov / codecov/patch

packages/verified-fetch/src/utils/walk-path.ts#L32

Added line #L32 was not covered by tests
}

return {
Expand Down
9 changes: 4 additions & 5 deletions packages/verified-fetch/src/verified-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { setCacheControlHeader, setIpfsRoots } from './utils/response-headers.js
import { badRequestResponse, movedPermanentlyResponse, notAcceptableResponse, notSupportedResponse, okResponse, badRangeResponse, okRangeResponse, badGatewayResponse, notFoundResponse } from './utils/responses.js'
import { selectOutputType } from './utils/select-output-type.js'
import { handlePathWalking, isObjectNode } from './utils/walk-path.js'
import type { CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions, Resource, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
import type { CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions, Resource, ResourceDetail, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
import type { FetchHandlerFunctionArg, RequestFormatShorthand } from './types.js'
import type { Helia, SessionBlockstore } from '@helia/interface'
import type { Blockstore } from 'interface-blockstore'
Expand Down Expand Up @@ -167,7 +167,7 @@ export class VerifiedFetch {
// just read it out..
const routingKey = uint8ArrayConcat([
uint8ArrayFromString('/ipns/'),
peerId.toBytes()
peerId.toMultihash().bytes
])
const datastoreKey = new Key('/dht/record/' + uint8ArrayToString(routingKey, 'base32'), false)
const buf = await this.helia.datastore.get(datastoreKey, options)
Expand All @@ -185,7 +185,7 @@ export class VerifiedFetch {
*/
private async handleCar ({ resource, cid, session, options }: FetchHandlerFunctionArg): Promise<Response> {
const blockstore = this.getBlockstore(cid, resource, session, options)
const c = car({ blockstore, dagWalkers: this.helia.dagWalkers })
const c = car({ blockstore, getCodec: this.helia.getCodec })
const stream = toBrowserReadableStream(c.stream(cid, options))

const response = okResponse(resource, stream)
Expand Down Expand Up @@ -481,8 +481,7 @@ export class VerifiedFetch {

const options = convertOptions(opts)

options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { resource }))

options?.onProgress?.(new CustomProgressEvent<ResourceDetail>('verified-fetch:request:start', { resource }))
// resolve the CID/path from the requested resource
let cid: ParsedUrlStringResults['cid']
let path: ParsedUrlStringResults['path']
Expand Down
10 changes: 6 additions & 4 deletions packages/verified-fetch/test/abort-handling.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { dagCbor } from '@helia/dag-cbor'
import { type DNSLinkResolveResult, type IPNS, type IPNSResolveResult } from '@helia/ipns'
import { unixfs } from '@helia/unixfs'
import { generateKeyPair } from '@libp2p/crypto/keys'
import { stop, type ComponentLogger, type Logger } from '@libp2p/interface'
import { prefixLogger, logger as libp2pLogger } from '@libp2p/logger'
import { createEd25519PeerId } from '@libp2p/peer-id-factory'
import { peerIdFromPrivateKey } from '@libp2p/peer-id'
import { expect } from 'aegir/chai'
import browserReadableStreamToIt from 'browser-readablestream-to-it'
import { fixedSize } from 'ipfs-unixfs-importer/chunker'
Expand Down Expand Up @@ -102,11 +103,12 @@ describe('abort-handling', function () {
hello: 'world'
})

const peerId = await createEd25519PeerId()
const key = await generateKeyPair('Ed25519')
const peerId = peerIdFromPrivateKey(key)

await name.publish(peerId, cid, { lifetime: 1000 * 60 * 60 })
await name.publish(key, cid, { lifetime: 1000 * 60 * 60 })

await expect(makeAbortedRequest(verifiedFetch, [`ipns://${peerId}`], peerIdResolverCalled.promise)).to.eventually.be.rejectedWith('aborted')
await expect(makeAbortedRequest(verifiedFetch, [`ipns://${peerId.toString()}`], peerIdResolverCalled.promise)).to.eventually.be.rejectedWith('aborted')
expect(peerIdResolver.callCount).to.equal(1)
expect(dnsLinkResolver.callCount).to.equal(0) // not called because signal abort was detected
expect(blockRetriever.retrieve.callCount).to.equal(0) // not called because we never got the cid
Expand Down
13 changes: 8 additions & 5 deletions packages/verified-fetch/test/accept-header.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { dagJson } from '@helia/dag-json'
import { ipns } from '@helia/ipns'
import * as ipldDagCbor from '@ipld/dag-cbor'
import * as ipldDagJson from '@ipld/dag-json'
import { generateKeyPair } from '@libp2p/crypto/keys'
import { stop } from '@libp2p/interface'
import { createEd25519PeerId } from '@libp2p/peer-id-factory'
import { peerIdFromPrivateKey } from '@libp2p/peer-id'
import { expect } from 'aegir/chai'
import * as cborg from 'cborg'
import { marshal } from 'ipns'
import { marshalIPNSRecord } from 'ipns'
import { CID } from 'multiformats/cid'
import * as raw from 'multiformats/codecs/raw'
import { sha256 } from 'multiformats/hashes/sha2'
Expand Down Expand Up @@ -269,15 +270,17 @@ describe('accept header', () => {
})

it('should support fetching IPNS records', async () => {
const peerId = await createEd25519PeerId()
const key = await generateKeyPair('Ed25519')
const peerId = peerIdFromPrivateKey(key)

const obj = {
hello: 'world'
}
const c = dagCbor(helia)
const cid = await c.add(obj)

const i = ipns(helia)
const record = await i.publish(peerId, cid)
const record = await i.publish(key, cid)

const resp = await verifiedFetch.fetch(`ipns://${peerId}`, {
headers: {
Expand All @@ -288,7 +291,7 @@ describe('accept header', () => {
expect(resp.headers.get('content-type')).to.equal('application/vnd.ipfs.ipns-record')
const buf = await resp.arrayBuffer()

expect(new Uint8Array(buf)).to.equalBytes(marshal(record))
expect(new Uint8Array(buf)).to.equalBytes(marshalIPNSRecord(record))
})

shouldNotAcceptCborWith({
Expand Down
Loading
Loading