diff --git a/packages/edge-gateway/package.json b/packages/edge-gateway/package.json index 755bfe1..9efcbe3 100644 --- a/packages/edge-gateway/package.json +++ b/packages/edge-gateway/package.json @@ -22,7 +22,7 @@ "ipfs-core-utils": "^0.15.0", "ipfs-gateway-race": "link:../ipfs-gateway-race", "itty-router": "^2.4.5", - "multiformats": "^9.6.4", + "multiformats": "^13.3.0", "p-any": "^4.0.0", "p-defer": "^4.0.0", "p-retry": "^5.0.0", diff --git a/packages/edge-gateway/src/gateway.js b/packages/edge-gateway/src/gateway.js index ba0a02e..18e562f 100644 --- a/packages/edge-gateway/src/gateway.js +++ b/packages/edge-gateway/src/gateway.js @@ -1,5 +1,5 @@ /* eslint-env serviceworker, browser */ -/* global Response caches */ +/* global Response caches, IdentityTransformStream */ import pAny, { AggregateError } from 'p-any' import pDefer from 'p-defer' @@ -9,7 +9,8 @@ import { gatewayFetch } from 'ipfs-gateway-race' import { getCidFromSubdomainUrl, - toDenyListAnchor + toDenyListAnchor, + getCidFromEtag } from './utils/cid.js' import { getHeaders } from './utils/headers.js' import { getCidForbiddenResponse } from './utils/verification.js' @@ -133,7 +134,7 @@ export async function gatewayGet (request, env, ctx) { } = await getFromGatewayRacer(cid, pathname, search, getHeaders(request), env, ctx) // Validation layer - resource CID - const resourceCid = pathname !== '/' ? getCidFromEtag(winnerGwResponse.headers.get('etag') || cid) : cid + const resourceCid = pathname !== '/' ? getCidFromEtag(winnerGwResponse.headers.get('etag') || `"${cid}"`) : cid if (winnerGwResponse && pathname !== '/' && resourceCid) { const resourceCidForbiddenResponse = await getCidForbiddenResponse(resourceCid, env) if (resourceCidForbiddenResponse) { @@ -206,7 +207,7 @@ async function getFromCdn (request, env, cache) { /** * @param {Request} request * @param {Env} env - * @param {string} cid + * @param {import('multiformats').UnknownLink} cid * @param {{ pathname?: string, search?: string }} [options] * @return {Promise} */ @@ -249,9 +250,14 @@ async function getFromDotstorage (request, env, cid, options = {}) { }), ...env.cdnGateways.map(async (host) => { const gwResponse = await gatewayFetch(host, cid, pathname, { + method: request.method, timeout: env.CDN_REQUEST_TIMEOUT, headers: request.headers, - search + search, + // Cloudflare's IdentityTransformStream provides a zero copy + // passthrough alternative to TransformStream. + // https://developers.cloudflare.com/workers/runtime-apis/streams/transformstream/#identitytransformstream + IdentityTransformStream: IdentityTransformStream }) // @ts-ignore 'response' does not exist on type 'GatewayResponseFailure' @@ -274,7 +280,7 @@ async function getFromDotstorage (request, env, cid, options = {}) { /** * - * @param {string} cid + * @param {import('multiformats').UnknownLink} cid * @param {string} pathname * @param {string} search * @param {Headers} headers @@ -308,7 +314,11 @@ async function getFromGatewayRacer (cid, pathname, search, headers, env, ctx) { reportRaceResults(env, gatewayResponsePromises, undefined, gatewayControllers) ) } - } + }, + // Cloudflare's IdentityTransformStream provides a zero copy + // passthrough alternative to TransformStream. + // https://developers.cloudflare.com/workers/runtime-apis/streams/transformstream/#identitytransformstream + IdentityTransformStream: IdentityTransformStream }) if (!layerOneIsWinner) { throw new Error('no winner in the first race') @@ -443,28 +453,6 @@ function getResponseWithCustomHeaders ( return clonedResponse } -/** - * Extracting resource CID from etag based on - * https://github.com/ipfs/specs/blob/main/http-gateways/PATH_GATEWAY.md#etag-response-header - * - * @param {string} etag - */ -function getCidFromEtag (etag) { - let resourceCid = decodeURIComponent(etag) - - // Handle weak etag - resourceCid = resourceCid.replace('W/', '') - resourceCid = resourceCid.replaceAll('"', '') - - // Handle directory index generated - if (etag.includes('DirIndex')) { - const split = resourceCid.split('-') - resourceCid = split[split.length - 1] - } - - return resourceCid -} - /** * Async metrics for race. * diff --git a/packages/edge-gateway/src/utils/cid.js b/packages/edge-gateway/src/utils/cid.js index d75f9d0..3020c38 100644 --- a/packages/edge-gateway/src/utils/cid.js +++ b/packages/edge-gateway/src/utils/cid.js @@ -1,6 +1,6 @@ import { Multibases } from 'ipfs-core-utils/multibases' import { bases } from 'multiformats/basics' -import { CID } from 'multiformats/cid' +import { parse } from 'multiformats/link' import * as uint8arrays from 'uint8arrays' import { sha256 } from 'multiformats/hashes/sha2' @@ -31,14 +31,13 @@ export async function getCidFromSubdomainUrl (url) { } /** - * Parse CID and return normalized b32 v1. + * Parse CID and return normalized v1. * * @param {string} cid */ export async function normalizeCid (cid) { const baseDecoder = await getMultibaseDecoder(cid) - const c = CID.parse(cid, baseDecoder) - return c.toV1().toString() + return parse(cid, baseDecoder).toV1() } /** @@ -61,10 +60,32 @@ async function getMultibaseDecoder (cid) { /** * Get denylist anchor with badbits format. * - * @param {string} cid + * @param {import('multiformats').UnknownLink} cid */ export async function toDenyListAnchor (cid) { const multihash = await sha256.digest(uint8arrays.fromString(`${cid}/`)) const digest = multihash.bytes.subarray(2) return uint8arrays.toString(digest, 'hex') } + +/** + * Extracting resource CID from etag based on + * https://github.com/ipfs/specs/blob/main/http-gateways/PATH_GATEWAY.md#etag-response-header + * + * @param {string} etag + */ +export function getCidFromEtag (etag) { + let resourceCid = decodeURIComponent(etag) + + // Handle weak etag + resourceCid = resourceCid.replace('W/', '') + resourceCid = resourceCid.replaceAll('"', '') + + // Handle directory index generated + if (etag.includes('DirIndex')) { + const split = resourceCid.split('-') + resourceCid = split[split.length - 1] + } + + return parse(resourceCid) +} diff --git a/packages/edge-gateway/src/utils/verification.js b/packages/edge-gateway/src/utils/verification.js index 4c2fec1..541154d 100644 --- a/packages/edge-gateway/src/utils/verification.js +++ b/packages/edge-gateway/src/utils/verification.js @@ -8,7 +8,7 @@ import { /** * Checks to see if denylist or cid-verifier forbid this CID from being served. * - * @param {string} cid + * @param {import('multiformats').UnknownLink} cid * @param {import('../env').Env} env */ export async function getCidForbiddenResponse (cid, env) { diff --git a/packages/ipfs-gateway-race/lib/index.js b/packages/ipfs-gateway-race/lib/index.js index f025093..b6b5d75 100644 --- a/packages/ipfs-gateway-race/lib/index.js +++ b/packages/ipfs-gateway-race/lib/index.js @@ -4,7 +4,8 @@ import pAny, { AggregateError } from 'p-any' import { FilterError } from 'p-some' import pSettle from 'p-settle' import fetch, { Headers } from '@web-std/fetch' - +import * as UnixFSDownloader from '@storacha/unixfs-dl' +import * as raw from 'multiformats/codecs/raw' import { NotFoundError, GatewayTimeoutError, @@ -15,6 +16,7 @@ import { ABORT_CODE, DEFAULT_REQUEST_TIMEOUT } from './constants.js' +import { isAlternateFormatRequest, isRangeRequest } from './request.js' const nop = () => {} @@ -38,7 +40,7 @@ export class IpfsGatewayRacer { } /** - * @param {string} cid + * @param {import('multiformats').UnknownLink} cid * @param {IpfsGatewayRaceGetOptions} [options] * @return {Promise} */ @@ -53,13 +55,15 @@ export class IpfsGatewayRacer { /** @type {GatewayResponsePromise[]} */ const gatewayResponsePromises = this.ipfsGateways.map((gwUrl) => gatewayFetch(gwUrl, cid, pathname, { + method: options.method, headers, search, timeout: this.timeout, // Combine internal race winner controller signal with custom user signal signal: gatewaySignals[gwUrl] ? anySignal([raceControllers[gwUrl].signal, gatewaySignals[gwUrl]]) - : raceControllers[gwUrl].signal + : raceControllers[gwUrl].signal, + IdentityTransformStream: options.IdentityTransformStream }) ) @@ -128,13 +132,15 @@ export function createGatewayRacer (ipfsGateways, options = {}) { * Fetches given CID from given IPFS gateway URL. * * @param {string} gwUrl - * @param {string} cid + * @param {import('multiformats').UnknownLink} cid * @param {string} pathname * @param {Object} [options] + * @param {string} [options.method] * @param {Headers} [options.headers] * @param {string} [options.search] * @param {number} [options.timeout] * @param {AbortSignal} [options.signal] + * @param {typeof TransformStream} [options.IdentityTransformStream] */ export async function gatewayFetch ( gwUrl, @@ -142,21 +148,36 @@ export async function gatewayFetch ( pathname, options = {} ) { - const { headers, signal } = options + const method = options.method || 'GET' + const headers = options.headers || new Headers() const timeout = options.timeout || 60000 const search = options.search || '' const timeoutController = new AbortController() const timer = setTimeout(() => timeoutController.abort(), timeout) + // Combine timeout signal with done signal + const signal = options.signal + ? anySignal([timeoutController.signal, options.signal]) + : timeoutController.signal + const url = new URL(`ipfs/${cid}${pathname}${search}`, gwUrl) let response try { - response = await fetch(new URL(`ipfs/${cid}${pathname}${search}`, gwUrl), { - // Combine timeout signal with done signal - signal: signal - ? anySignal([timeoutController.signal, signal]) - : timeoutController.signal, - headers - }) + // If this is an atypical request, i.e. a HEAD request, a range request, a + // request for a different format to UnixFS, or if the root CID is for a raw + // block then just make the request to the upstream as usual. + if ( + method !== 'GET' || + cid.code === raw.code || + isRangeRequest(headers) || + isAlternateFormatRequest(headers, url.searchParams) + ) { + response = await fetch(url, { method, signal, headers }) + } else { + // Otherwise use the unixfs downloader to make byte range requests + // upstream allowing big files to be downloaded without exhausting the + // upstream worker's CPU budget. + response = await UnixFSDownloader.fetch(url, { signal, headers, IdentityTransformStream: options.IdentityTransformStream }) + } } catch (error) { if (timeoutController.signal.aborted) { return { diff --git a/packages/ipfs-gateway-race/lib/request.js b/packages/ipfs-gateway-race/lib/request.js new file mode 100644 index 0000000..3d87d19 --- /dev/null +++ b/packages/ipfs-gateway-race/lib/request.js @@ -0,0 +1,17 @@ +/** + * Determine if the request is for a specific byte range. + * @param {Headers} headers + */ +export const isRangeRequest = headers => headers.get('Range') !== null + +/** + * Determine if the request is for an alternative format, like an IPLD block or + * a CAR file. + * @param {Headers} headers + * @param {URLSearchParams} searchParams + */ +export const isAlternateFormatRequest = (headers, searchParams) => { + const format = searchParams.get('format') + const accept = headers.get('Accept') + return Boolean(format || accept?.includes('application/vnd.ipld.')) +} diff --git a/packages/ipfs-gateway-race/package.json b/packages/ipfs-gateway-race/package.json index 842c8a3..150b14d 100644 --- a/packages/ipfs-gateway-race/package.json +++ b/packages/ipfs-gateway-race/package.json @@ -30,9 +30,10 @@ "gateway" ], "dependencies": { + "@storacha/unixfs-dl": "^1.5.1", "@web-std/fetch": "^4.1.0", "any-signal": "^3.0.1", - "multiformats": "^9.6.4", + "multiformats": "^13.3.0", "p-any": "^4.0.0", "p-map": "^5.3.0", "p-retry": "^5.0.0", diff --git a/packages/ipfs-gateway-race/test/index.spec.js b/packages/ipfs-gateway-race/test/index.spec.js index 1ec73ee..e95598b 100644 --- a/packages/ipfs-gateway-race/test/index.spec.js +++ b/packages/ipfs-gateway-race/test/index.spec.js @@ -4,9 +4,10 @@ import { Headers } from '@web-std/fetch' import { GenericContainer, Wait } from 'testcontainers' import pDefer from 'p-defer' import pSettle from 'p-settle' - +import * as Link from 'multiformats/link' import { createGatewayRacer } from '../lib/index.js' import { ABORT_CODE, TIMEOUT_CODE } from '../lib/constants.js' +import { bigData } from './mocks/fixtures.js' test.before(async (t) => { const container = await new GenericContainer('ipfs/go-ipfs:v0.13.0') @@ -34,7 +35,7 @@ test.after(async (t) => { test('Gets response from cid only', async (t) => { const { gwRacer } = t.context - const cid = 'bafkreihl44bu5rqxctfvl3ahcln7gnjgmjqi7v5wfwojqwriqnq7wo4n7u' + const cid = Link.parse('bafkreihl44bu5rqxctfvl3ahcln7gnjgmjqi7v5wfwojqwriqnq7wo4n7u') const response = await gwRacer.get(cid) t.assert(response) @@ -43,10 +44,24 @@ test('Gets response from cid only', async (t) => { t.is(await response.text(), 'Hello dot.storage! 😎') }) +// Results in HEAD request, and then multiple byte-range GET requests. +// The mock ipfs.io gateway implements this for the `bigData` CID. +test('Gets response from cid only (big data)', async (t) => { + const { gwRacer } = t.context + + const cid = Link.parse(bigData.cid) + const response = await gwRacer.get(cid) + + t.assert(response) + t.is(response.status, 200) + t.is(response.headers.get('content-length'), bigData.bytes.length.toString()) + t.is(await response.text(), new TextDecoder().decode(bigData.bytes)) +}) + test('Gets response from cid and pathname', async (t) => { const { gwRacer } = t.context - const cid = 'bafybeih74zqc6kamjpruyra4e4pblnwdpickrvk4hvturisbtveghflovq' + const cid = Link.parse('bafybeih74zqc6kamjpruyra4e4pblnwdpickrvk4hvturisbtveghflovq') const pathname = '/path' const response = await gwRacer.get(cid, { pathname }) @@ -69,7 +84,7 @@ test('Gets response from cid and pathname', async (t) => { */ test('Gets 304 response from cid and valid if-none-match header', async (t) => { const { gwRacer } = t.context - const cid = 'bafkreidwgoyc2f7n5vmwbcabbckwa6ejes4ujyncyq6xec5gt5nrm5hzga' + const cid = Link.parse('bafkreidwgoyc2f7n5vmwbcabbckwa6ejes4ujyncyq6xec5gt5nrm5hzga') const headers = new Headers({ 'if-none-match': `"${cid}"` }) const response = await gwRacer.get(cid, { headers }) @@ -83,7 +98,7 @@ test('Aborts other race contestants once there is a winner', async (t) => { const defer = pDefer() t.plan(5) - const cid = 'bafkreihl44bu5rqxctfvl3ahcln7gnjgmjqi7v5wfwojqwriqnq7wo4n7u' + const cid = Link.parse('bafkreihl44bu5rqxctfvl3ahcln7gnjgmjqi7v5wfwojqwriqnq7wo4n7u') const response = await gwRacer.get(cid, { onRaceEnd: async (gwRequests, winnerGwResponse) => { t.assert(winnerGwResponse) @@ -107,13 +122,13 @@ test('Disables abort of other race contestants once there is a winner', async (t const defer = pDefer() t.plan(5) - const cid = 'bafkreihl44bu5rqxctfvl3ahcln7gnjgmjqi7v5wfwojqwriqnq7wo4n7u' + const cid = Link.parse('bafkreihl44bu5rqxctfvl3ahcln7gnjgmjqi7v5wfwojqwriqnq7wo4n7u') const response = await gwRacer.get(cid, { onRaceEnd: async (gwRequests, winnerGwResponse) => { t.assert(winnerGwResponse) const responses = await pSettle(gwRequests) - t.is(responses.filter(r => !!r.isFulfilled).length, gwRequests.length) + t.is(responses.filter(r => r.isFulfilled).length, gwRequests.length) // @ts-ignore Property 'value' does not exist on type 'PromiseRejectedResult' t.is(responses.filter(r => r.value?.response).length, gwRequests.length) // @ts-ignore Property 'value' does not exist on type 'PromiseRejectedResult' @@ -142,7 +157,7 @@ test('Can abort other race contestants only after all promises are resolved and gatewaySignals[gateway] = abortController.signal }) - const cid = 'bafkreihl44bu5rqxctfvl3ahcln7gnjgmjqi7v5wfwojqwriqnq7wo4n7u' + const cid = Link.parse('bafkreihl44bu5rqxctfvl3ahcln7gnjgmjqi7v5wfwojqwriqnq7wo4n7u') const response = await gwRacer.get(cid, { onRaceEnd: async (gwRequests, winnerGwResponse) => { t.assert(winnerGwResponse) @@ -176,14 +191,14 @@ test('A subset of gateways in the race can fail', async t => { const { gwRacer } = t.context // gateway[1] and gateway[2] will not be able to resolve this - const cid = 'bafkreifbh4or5yoti7bahifd3gwx5m2qiwmrvpxsx3nsquf7r4wwkiruve' + const cid = Link.parse('bafkreifbh4or5yoti7bahifd3gwx5m2qiwmrvpxsx3nsquf7r4wwkiruve') const response = await gwRacer.get(cid, { onRaceEnd: async (gwRequests, winnerGwResponse) => { t.assert(winnerGwResponse) const responses = await pSettle(gwRequests) - t.is(responses.filter(r => !!r.isFulfilled).length, gwRequests.length) + t.is(responses.filter(r => r.isFulfilled).length, gwRequests.length) // @ts-ignore Property 'value' does not exist on type 'PromiseRejectedResult' t.is(responses.filter(r => r.value?.response).length, gwRequests.length) // @ts-ignore Property 'value' does not exist on type 'PromiseRejectedResult' @@ -207,7 +222,7 @@ test('Can decrease race timeout to not have winner', async t => { ) // gateway[1] delays 300ms before resolving this - const cid = 'bafkreibehzafi6gdvlyue5lzxa3rfobvp452kylox6f4vwqpd4xbr53uqu' + const cid = Link.parse('bafkreibehzafi6gdvlyue5lzxa3rfobvp452kylox6f4vwqpd4xbr53uqu') const error = await t.throwsAsync(async () => { await gwRacer.get(cid, { diff --git a/packages/ipfs-gateway-race/test/mocks/fixtures.js b/packages/ipfs-gateway-race/test/mocks/fixtures.js new file mode 100644 index 0000000..2c512f1 --- /dev/null +++ b/packages/ipfs-gateway-race/test/mocks/fixtures.js @@ -0,0 +1,4 @@ +exports.bigData = { + cid: 'bafybeihctj26zx2smrvl6ev7k2h4krjey3rd2irjsqtduhlowwe75fdzo4', + bytes: new TextEncoder().encode('Hello w3s.link!!!!!!'.repeat(10_000_000)) +} diff --git a/packages/ipfs-gateway-race/test/mocks/ipfs.io/get_ipfs#@cid.js b/packages/ipfs-gateway-race/test/mocks/ipfs.io/get_ipfs#@cid.js index e8c114d..f2305c5 100644 --- a/packages/ipfs-gateway-race/test/mocks/ipfs.io/get_ipfs#@cid.js +++ b/packages/ipfs-gateway-race/test/mocks/ipfs.io/get_ipfs#@cid.js @@ -1,3 +1,5 @@ +const { bigData } = require('../fixtures.js') + /** * https://github.com/sinedied/smoke#javascript-mocks */ @@ -39,6 +41,18 @@ module.exports = async ({ params, headers }) => { } } + if (cid === bigData.cid) { + const [start, end] = headers.range.split('bytes=')[1].split('-').map(s => parseInt(s)) + responseHeaders['Content-Length'] = end - start + 1 + responseHeaders['Content-Range'] = `bytes ${start}-${end}/${bigData.bytes.length}` + return { + statusCode: 206, + headers: responseHeaders, + body: Buffer.from(bigData.bytes.slice(start, end + 1)).toString('base64'), + buffer: true + } + } + return { statusCode: 500, body: undefined, // smoke ignores statusCode if body is not present! diff --git a/packages/ipfs-gateway-race/test/mocks/ipfs.io/head_ipfs#@cid.js b/packages/ipfs-gateway-race/test/mocks/ipfs.io/head_ipfs#@cid.js new file mode 100644 index 0000000..535b47a --- /dev/null +++ b/packages/ipfs-gateway-race/test/mocks/ipfs.io/head_ipfs#@cid.js @@ -0,0 +1,28 @@ +const { bigData } = require('../fixtures.js') + +/** + * https://github.com/sinedied/smoke#javascript-mocks + */ +module.exports = async ({ params, headers }) => { + const cid = params.cid + + const responseHeaders = { + Etag: `"${cid}"`, + 'Cache-Control': 'public, max-age=29030400, immutable' + } + + if (cid === bigData.cid) { + responseHeaders['Content-Length'] = bigData.bytes.length + return { + statusCode: 200, + headers: responseHeaders, + body: undefined + } + } + + return { + statusCode: 500, + body: undefined, // smoke ignores statusCode if body is not present! + headers: responseHeaders + } +} diff --git a/packages/ipfs-gateway-race/types.d.ts b/packages/ipfs-gateway-race/types.d.ts index ac4ff13..ad7534f 100644 --- a/packages/ipfs-gateway-race/types.d.ts +++ b/packages/ipfs-gateway-race/types.d.ts @@ -3,12 +3,14 @@ export interface IpfsGatewayRacerOptions { } export interface IpfsGatewayRaceGetOptions { + method?: string pathname?: string search?: string headers?: Headers noAbortRequestsOnWinner?: boolean onRaceEnd?: (gatewayResponsePromises: GatewayResponsePromise[], winnerResponse: GatewayResponse | undefined) => void gatewaySignals?: Record + IdentityTransformStream?: typeof TransformStream } // Gateway Race Responses diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e0b7340..00f706a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -174,7 +174,7 @@ importers: version: 0.3.0-dev ipfs-core-utils: specifier: ^0.15.0 - version: 0.15.1(undici@5.20.0) + version: 0.15.1(undici@5.28.4) ipfs-gateway-race: specifier: link:../ipfs-gateway-race version: link:../ipfs-gateway-race @@ -182,8 +182,8 @@ importers: specifier: ^2.4.5 version: 2.6.1 multiformats: - specifier: ^9.6.4 - version: 9.7.0 + specifier: ^13.3.0 + version: 13.3.0 p-any: specifier: ^4.0.0 version: 4.0.0 @@ -207,7 +207,7 @@ importers: version: 3.0.0 undici: specifier: ^5.8.0 - version: 5.20.0 + version: 5.28.4 devDependencies: '@cloudflare/workers-types': specifier: ^3.7.1 @@ -241,7 +241,7 @@ importers: version: 3.0.2 ipfs-http-client: specifier: ^60.0.1 - version: 60.0.1(encoding@0.1.13)(undici@5.20.0) + version: 60.0.1(encoding@0.1.13)(undici@5.28.4) ipfs-utils: specifier: ^9.0.4 version: 9.0.7 @@ -278,6 +278,9 @@ importers: packages/ipfs-gateway-race: dependencies: + '@storacha/unixfs-dl': + specifier: ^1.5.1 + version: 1.5.1 '@web-std/fetch': specifier: ^4.1.0 version: 4.1.0 @@ -285,8 +288,8 @@ importers: specifier: ^3.0.1 version: 3.0.1 multiformats: - specifier: ^9.6.4 - version: 9.7.0 + specifier: ^13.3.0 + version: 13.3.0 p-any: specifier: ^4.0.0 version: 4.0.0 @@ -311,7 +314,7 @@ importers: version: 2.1.1 ipfs-http-client: specifier: ^60.0.1 - version: 60.0.1(encoding@0.1.13)(undici@5.20.0) + version: 60.0.1(encoding@0.1.13)(undici@5.28.4) ipfs-utils: specifier: ^9.0.4 version: 9.0.7 @@ -831,6 +834,9 @@ packages: resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} engines: {node: '>=6'} + '@storacha/unixfs-dl@1.5.1': + resolution: {integrity: sha512-8ezzSSRpn4slMFvlovzcGbKlcv9bcfcgseswRVBi3HdQeFQjSu/r8lBfbzo0B6BrnUYU9M5YEJ5gbI3blePxLA==} + '@szmarczak/http-timer@1.1.2': resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==} engines: {node: '>=6'} @@ -4790,6 +4796,18 @@ snapshots: - supports-color - undici + '@libp2p/interfaces@2.0.4(undici@5.28.4)': + dependencies: + '@multiformats/multiaddr': 10.3.3(undici@5.28.4) + err-code: 3.0.1 + interface-datastore: 6.1.1 + it-pushable: 2.0.2 + it-stream-types: 1.0.4 + multiformats: 9.7.0 + transitivePeerDependencies: + - supports-color + - undici + '@libp2p/interfaces@3.3.2': {} '@libp2p/logger@1.1.6(undici@5.20.0)': @@ -4802,6 +4820,16 @@ snapshots: - supports-color - undici + '@libp2p/logger@1.1.6(undici@5.28.4)': + dependencies: + '@libp2p/interfaces': 2.0.4(undici@5.28.4) + debug: 4.3.4(supports-color@9.2.2) + interface-datastore: 6.1.1 + multiformats: 9.7.0 + transitivePeerDependencies: + - supports-color + - undici + '@libp2p/logger@2.1.1': dependencies: '@libp2p/interface-peer-id': 2.0.2 @@ -4956,6 +4984,13 @@ snapshots: - supports-color - undici + '@multiformats/multiaddr-to-uri@9.0.1(undici@5.28.4)': + dependencies: + '@multiformats/multiaddr': 10.3.3(undici@5.28.4) + transitivePeerDependencies: + - supports-color + - undici + '@multiformats/multiaddr@10.3.3(undici@5.20.0)': dependencies: dns-over-http-resolver: 2.1.0(undici@5.20.0) @@ -4968,10 +5003,22 @@ snapshots: - supports-color - undici - '@multiformats/multiaddr@11.6.1(undici@5.20.0)': + '@multiformats/multiaddr@10.3.3(undici@5.28.4)': + dependencies: + dns-over-http-resolver: 2.1.0(undici@5.28.4) + err-code: 3.0.1 + is-ip: 4.0.0 + multiformats: 9.7.0 + uint8arrays: 3.0.0 + varint: 6.0.0 + transitivePeerDependencies: + - supports-color + - undici + + '@multiformats/multiaddr@11.6.1(undici@5.28.4)': dependencies: '@chainsafe/is-ip': 2.0.2 - dns-over-http-resolver: 2.1.0(undici@5.20.0) + dns-over-http-resolver: 2.1.0(undici@5.28.4) err-code: 3.0.1 multiformats: 11.0.2 uint8arrays: 4.0.10 @@ -5079,6 +5126,8 @@ snapshots: '@sindresorhus/is@0.14.0': {} + '@storacha/unixfs-dl@1.5.1': {} + '@szmarczak/http-timer@1.1.2': dependencies: defer-to-connect: 1.1.3 @@ -5929,6 +5978,15 @@ snapshots: - supports-color - undici + dns-over-http-resolver@2.1.0(undici@5.28.4): + dependencies: + debug: 4.3.4(supports-color@9.2.2) + native-fetch: 4.0.2(undici@5.28.4) + receptacle: 1.3.2 + transitivePeerDependencies: + - supports-color + - undici + dns-packet@5.6.1: dependencies: '@leichtgewicht/ip-codec': 2.0.5 @@ -6859,14 +6917,25 @@ snapshots: - supports-color - undici - ipfs-core-types@0.14.1(undici@5.20.0): + ipfs-core-types@0.11.1(undici@5.28.4): + dependencies: + '@ipld/dag-pb': 2.1.17 + '@multiformats/multiaddr': 10.3.3(undici@5.28.4) + interface-datastore: 6.1.1 + ipfs-unixfs: 6.0.9 + multiformats: 9.7.0 + transitivePeerDependencies: + - supports-color + - undici + + ipfs-core-types@0.14.1(undici@5.28.4): dependencies: '@ipld/dag-pb': 4.1.2 '@libp2p/interface-keychain': 2.0.5 '@libp2p/interface-peer-id': 2.0.2 '@libp2p/interface-peer-info': 1.0.10 '@libp2p/interface-pubsub': 3.0.7 - '@multiformats/multiaddr': 11.6.1(undici@5.20.0) + '@multiformats/multiaddr': 11.6.1(undici@5.28.4) '@types/node': 18.0.1 interface-datastore: 7.0.4 ipfs-unixfs: 9.0.1 @@ -6901,16 +6970,42 @@ snapshots: - supports-color - undici - ipfs-core-utils@0.18.1(encoding@0.1.13)(undici@5.20.0): + ipfs-core-utils@0.15.1(undici@5.28.4): + dependencies: + '@libp2p/logger': 1.1.6(undici@5.28.4) + '@multiformats/multiaddr': 10.3.3(undici@5.28.4) + '@multiformats/multiaddr-to-uri': 9.0.1(undici@5.28.4) + any-signal: 3.0.1 + blob-to-it: 1.0.4 + browser-readablestream-to-it: 1.0.3 + err-code: 3.0.1 + ipfs-core-types: 0.11.1(undici@5.28.4) + ipfs-unixfs: 6.0.9 + ipfs-utils: 9.0.7 + it-all: 1.0.6 + it-map: 1.0.6 + it-peekable: 1.0.3 + it-to-stream: 1.0.0 + merge-options: 3.0.4 + multiformats: 9.7.0 + nanoid: 3.3.4 + parse-duration: 1.0.2 + timeout-abort-controller: 3.0.0 + uint8arrays: 3.0.0 + transitivePeerDependencies: + - supports-color + - undici + + ipfs-core-utils@0.18.1(encoding@0.1.13)(undici@5.28.4): dependencies: '@libp2p/logger': 2.1.1 - '@multiformats/multiaddr': 11.6.1(undici@5.20.0) - '@multiformats/multiaddr-to-uri': 9.0.1(undici@5.20.0) + '@multiformats/multiaddr': 11.6.1(undici@5.28.4) + '@multiformats/multiaddr-to-uri': 9.0.1(undici@5.28.4) any-signal: 3.0.1 blob-to-it: 2.0.7 browser-readablestream-to-it: 2.0.7 err-code: 3.0.1 - ipfs-core-types: 0.14.1(undici@5.20.0) + ipfs-core-types: 0.14.1(undici@5.28.4) ipfs-unixfs: 9.0.1 ipfs-utils: 9.0.14(encoding@0.1.13) it-all: 2.0.1 @@ -6928,19 +7023,19 @@ snapshots: - supports-color - undici - ipfs-http-client@60.0.1(encoding@0.1.13)(undici@5.20.0): + ipfs-http-client@60.0.1(encoding@0.1.13)(undici@5.28.4): dependencies: '@ipld/dag-cbor': 9.2.1 '@ipld/dag-json': 10.2.2 '@ipld/dag-pb': 4.1.2 '@libp2p/logger': 2.1.1 '@libp2p/peer-id': 2.0.4 - '@multiformats/multiaddr': 11.6.1(undici@5.20.0) + '@multiformats/multiaddr': 11.6.1(undici@5.28.4) any-signal: 3.0.1 dag-jose: 4.0.0 err-code: 3.0.1 - ipfs-core-types: 0.14.1(undici@5.20.0) - ipfs-core-utils: 0.18.1(encoding@0.1.13)(undici@5.20.0) + ipfs-core-types: 0.14.1(undici@5.28.4) + ipfs-core-utils: 0.18.1(encoding@0.1.13)(undici@5.28.4) ipfs-utils: 9.0.14(encoding@0.1.13) it-first: 2.0.1 it-last: 2.0.1 @@ -7720,6 +7815,10 @@ snapshots: dependencies: undici: 5.20.0 + native-fetch@4.0.2(undici@5.28.4): + dependencies: + undici: 5.28.4 + natural-compare@1.4.0: {} negotiator@0.6.3: {}