Skip to content

Commit

Permalink
feat!: upgrade to helia v5 (#107)
Browse files Browse the repository at this point in the history
* chore: upgrade to helia 5

* fix: use error types with name property

* chore: define custom error types

* fix: deps

---------

Co-authored-by: Daniel N <[email protected]>
  • Loading branch information
2color and 2color authored Oct 14, 2024
1 parent 060e726 commit 91a6473
Show file tree
Hide file tree
Showing 17 changed files with 150 additions and 91 deletions.
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'
}
}
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 @@ function getByteRangeFromHeader (rangeHeader: string): { start: string, end: str
*/
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 @@ export class ByteRangeContext {
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')
}

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 toHeader (file: UnixFSEntry): Partial<TarEntryHeader> & { name: string

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`)
}

const candidate: TarImportCandidate = {
Expand Down Expand Up @@ -64,5 +64,5 @@ export async function * tarStream (ipfsPath: string, blockstore: Blockstore, opt
return
}

throw new CodeError('Not a UnixFS node', 'ERR_NOT_UNIXFS')
throw new NotUnixFSError('Not a UnixFS node')
}
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 @@ export async function getRedirectResponse ({ resource, options, logger, cid, fet
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')
}
} 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 { CID } from 'multiformats/cid'
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 @@ export async function parseUrlString ({ urlString, ipns, logger }: ParseUrlStrin
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')
}
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 @@ export async function walkPath (blockstore: ReadableStorage, path: string, optio
}

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

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

0 comments on commit 91a6473

Please sign in to comment.