diff --git a/README.md b/README.md index 348c6c0..9fcdaae 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ $ docker run -it -p 8080:8080 -e DEBUG="helia-http-gateway" helia | Variable | Description | Default | | --- | --- | --- | | `DEBUG` | Debug level | `''`| +| `FASTIFY_DEBUG` | Debug level for fastify's logger | `''`| | `PORT` | Port to listen on | `8080` | | `HOST` | Host to listen on | `0.0.0.0` | | `USE_SUBDOMAINS` | Whether to use [origin isolation](https://docs.ipfs.tech/how-to/gateway-best-practices/#use-subdomain-gateway-resolution-for-origin-isolation) | `false` | diff --git a/debugging/README.md b/debugging/README.md new file mode 100644 index 0000000..a854b40 --- /dev/null +++ b/debugging/README.md @@ -0,0 +1,50 @@ +This file documents some methods used for debugging and testing of helia-http-gateway. + +Any files in this directory should be considered temporary and may be deleted at any time. + +You should also run any of these scripts from the repository root. + +# Scripts + +## test-gateways.sh +This script is used to test the gateways. It assumes you have booted up the `helia-http-gateway` via docker or otherwise and will query the gateway for the same websites listed at https://probelab.io/websites/#time-to-first-byte-using-kubo, outputting HTTP status codes and response times. + +*Example* +```sh +./debugging/test-gateways.sh +``` + +## until-death.sh +This script will start up the gateway and run the `test-gateway.sh` script until the gateway dies. This is useful for load-testing helia-http-gateway in a similar manner to how it will be used by https://github.com/plprobelab/tiros + +*Example* +```sh +./debugging/until-death.sh +``` + +# Profiling in chrome/chromium devtools + +## Setup + +1. Start the process + +```sh +# in terminal 1 +npm run start:inspect +``` + +2. Open `chrome://inspect` and click the 'inspect' link for the process you just started + * it should say something like `dist/src/index.js file:///Users/sgtpooki/code/work/protocol.ai/ipfs/helia-http-gateway/dist/src/index.js` with an 'inspect' link below it. + +3. In the inspector, click the `performance` or `memory` tab and start recording. + +## Execute the operations that will be profiled: + +1. In another terminal, run + +```sh +# in terminal 2 +npm run debug:test-gateways # or npm run debug:until-death +``` + +2. Stop recording in the inspector and analyze the results. diff --git a/debugging/test-gateways.sh b/debugging/test-gateways.sh new file mode 100755 index 0000000..e93087d --- /dev/null +++ b/debugging/test-gateways.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash + +# Query all endpoints until failure +# This script is intended to be run from the root of the helia-http-gateway repository + +PORT=${PORT:-8080} +# If localhost:$PORT is not listening, then exit with non-zero error code +if ! nc -z localhost $PORT; then + echo "localhost:$PORT is not listening" + exit 1 +fi + +ensure_gateway_running() { + npx wait-on "tcp:$PORT" -t 1000 || exit 1 +} + +max_timeout=60 +test_website() { + ensure_gateway_running + local website=$1 + echo "Requesting $website" + curl -m $max_timeout -s --no-progress-meter -o /dev/null -w "%{url}: HTTP_%{http_code} in %{time_total} seconds (TTFB: %{time_starttransfer}, rediect: %{time_redirect})\n" -L $website + echo "running GC" + curl -X POST -m $max_timeout -s --no-progress-meter -o /dev/null -w "%{url}: HTTP_%{http_code} in %{time_total} seconds\n" http://localhost:$PORT/api/v0/repo/gc +} + +test_website http://localhost:$PORT/ipns/blog.ipfs.tech + +test_website http://localhost:$PORT/ipns/blog.libp2p.io + +test_website http://localhost:$PORT/ipns/consensuslab.world + +test_website http://localhost:$PORT/ipns/docs.ipfs.tech + +test_website http://localhost:$PORT/ipns/docs.libp2p.io + +test_website http://localhost:$PORT/ipns/drand.love + +test_website http://localhost:$PORT/ipns/fil.org + +test_website http://localhost:$PORT/ipns/filecoin.io + +test_website http://localhost:$PORT/ipns/green.filecoin.io + +test_website http://localhost:$PORT/ipns/ipfs.tech + +test_website http://localhost:$PORT/ipns/ipld.io + +test_website http://localhost:$PORT/ipns/libp2p.io + +test_website http://localhost:$PORT/ipns/n0.computer + +test_website http://localhost:$PORT/ipns/probelab.io + +test_website http://localhost:$PORT/ipns/protocol.ai + +test_website http://localhost:$PORT/ipns/research.protocol.ai + +test_website http://localhost:$PORT/ipns/singularity.storage + +test_website http://localhost:$PORT/ipns/specs.ipfs.tech + +# test_website http://localhost:$PORT/ipns/strn.network +test_website http://localhost:$PORT/ipns/saturn.tech + +test_website http://localhost:$PORT/ipns/web3.storage diff --git a/debugging/until-death.sh b/debugging/until-death.sh new file mode 100755 index 0000000..a4a9632 --- /dev/null +++ b/debugging/until-death.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash + +# If this is not executed from the root of the helia-http-gateway repository, then exit with non-zero error code +if [ ! -f "package.json" ]; then + echo "This script must be executed from the root of the helia-http-gateway repository" + exit 1 +fi + +# You have to pass `DEBUG=" " to disable debugging when using this script` +export DEBUG=${DEBUG:-"helia-http-gateway,helia-http-gateway:server,helia-http-gateway:*:helia-fetch"} +export PORT=${PORT:-8080} + +gateway_already_running=false +if nc -z localhost $PORT; then + echo "gateway is already running" + gateway_already_running=true +fi + +start_gateway() { + if [ "$gateway_already_running" = true ]; then + echo "gateway is already running" + return + fi + npm run build + + # npx clinic doctor --open=false -- node dist/src/index.js & + node dist/src/index.js & + # echo "process id: $!" +} +start_gateway & process_pid=$! + +ensure_gateway_running() { + npx wait-on "tcp:$PORT" -t 1000 || exit 1 +} + + +cleanup_called=false +cleanup() { + if [ "$cleanup_called" = true ]; then + echo "cleanup already called" + return + fi + # kill $process_pid + # when we're done, ensure the process is killed by sending a SIGTEM + # kill -s SIGTERM $process_pid + # kill any process listening on $PORT + # fuser -k $PORT/tcp + # kill any process listening on $PORT with SIGTERM + + if [ "$gateway_already_running" = true ]; then + echo "gateway was already running" + return + fi + + kill -s SIGINT $(lsof -i :$PORT -t) + # exit 1 +} + +trap cleanup SIGINT +trap cleanup SIGTERM + +# if we get a non-zero exit code, we know the server is no longer listening +# we should also exit early after 4 loops +# iterations=0 +# max_loops=1 +while [ $? -ne 1 ]; do +# # iterations=$((iterations+1)) +# if [ $iterations -gt $max_loops ]; then +# echo "exiting after $max_loops loops" +# break +# fi + ensure_gateway_running + ./debugging/test-gateways.sh 2>&1 | tee -a debugging/test-gateways.log +done + +cleanup diff --git a/e2e-tests/compare-to-ipfs-io.spec.ts b/e2e-tests/compare-to-ipfs-io.spec.ts deleted file mode 100644 index 8b6b81e..0000000 --- a/e2e-tests/compare-to-ipfs-io.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { test, expect } from '@playwright/test' -import { PORT } from '../src/constants.js' - -// test all the same pages listed at https://probelab.io/websites/ -const pages = [ - // '/ipns/blog.ipfs.tech', // currently timing out for Helia. - '/ipns/blog.libp2p.io', - '/ipns/consensuslab.world', - '/ipns/docs.ipfs.tech', - '/ipns/docs.libp2p.io', - '/ipns/drand.love', - // '/ipns/fil.org', // currently timing out for Helia. - // '/ipns/filecoin.io', // currently timing out for Helia. - '/ipns/green.filecoin.io', - // '/ipns/ipfs.tech', // currently timing out for Helia. - '/ipns/ipld.io', - '/ipns/libp2p.io', - // '/ipns/n0.computer', // currently timing out for Helia. - '/ipns/probelab.io', - '/ipns/protocol.ai', // very slow, but can pass. - '/ipns/research.protocol.ai', // slow-ish, but can pass. - '/ipns/singularity.storage', - '/ipns/specs.ipfs.tech', - '/ipns/strn.network' - // '/ipns/web3.storage' // currently timing out for Helia -] - -// increase default test timeout to 2 minutes -test.setTimeout(120000) - -// now for each page, make sure we can request the website, the content is not empty, and status code is 200 -pages.forEach((pagePath) => { - // afterEach, we should request /api/v0/repo/gc to clear the cache - test(`helia-http-gateway matches ipfs.io for path '${pagePath}'`, async ({ page }) => { - const ipfsIoResponse = await page.goto(`http://ipfs.io${pagePath}`) - expect(ipfsIoResponse?.status()).toBe(200) - const ipfsIoContent = await ipfsIoResponse?.text() - // TODO: enable screenshot testing? maybe not needed if we're confirming text content matches. - // await page.screenshot({ path: `screenshots${pagePath}.png`, fullPage: true }); - const heliaGatewayResponse = await page.goto(`http://localhost:${PORT}${pagePath}`) - expect(heliaGatewayResponse?.status()).toBe(200) - // expect(page).toHaveScreenshot(`screenshots${pagePath}.png`, { fullPage: true, maxDiffPixelRatio: 0 }); - - // expect the response text content to be the same - const heliaGatewayContent = await heliaGatewayResponse?.text() - expect(heliaGatewayContent).toEqual(ipfsIoContent) - }) -}) diff --git a/e2e-tests/smoketest.spec.ts b/e2e-tests/smoketest.spec.ts new file mode 100644 index 0000000..727f296 --- /dev/null +++ b/e2e-tests/smoketest.spec.ts @@ -0,0 +1,59 @@ +import { test, expect } from '@playwright/test' +import { PORT } from '../src/constants.js' + +// test all the same pages listed at https://probelab.io/websites/ +const pages = [ + // 'blog.ipfs.tech', // timing out + 'blog.libp2p.io', + 'consensuslab.world', + 'docs.ipfs.tech', + 'docs.libp2p.io', + 'drand.love', + 'fil.org', + // 'filecoin.io', // timing out + // 'green.filecoin.io', // timing out + 'ipfs.tech', + 'ipld.io', + 'libp2p.io', + 'n0.computer', + 'probelab.io', + 'protocol.ai', + 'research.protocol.ai', + 'singularity.storage', + 'specs.ipfs.tech', + // 'strn.network' // redirects to saturn.tech + 'saturn.tech' + // 'web3.storage' // timing out +] + +// increase default test timeout to 2 minutes +test.setTimeout(120000) + +// now for each page, make sure we can request the website, the content is not empty, and status code is 200 +test.beforeEach(async ({ context }) => { + // Block any asset requests for tests in this file. + await context.route(/.(css|js|svg|png|jpg|woff2|otf|webp|ttf|json)(?:\?.*)?$/, async route => route.abort()) +}) + +test.afterEach(async ({ page }) => { + test.setTimeout(30000) + const result = await page.request.post(`http://localhost:${PORT}/api/v0/repo/gc`) + expect(result?.status()).toBe(200) + + const maybeContent = await result?.text() + expect(maybeContent).toEqual('OK') +}) + +pages.forEach((pagePath) => { + const url = `http://${pagePath}.ipns.localhost:${PORT}` + test(`helia-http-gateway can load path '${url}'`, async ({ page }) => { + // only wait for 'commit' because we don't want to wait for all the assets to load, we just want to make sure that they *would* load (e.g. the html is valid) + const heliaGatewayResponse = await page.goto(`${url}`, { waitUntil: 'commit' }) + expect(heliaGatewayResponse?.status()).toBe(200) + // await page.waitForSelector('body') + expect(await heliaGatewayResponse?.text()).not.toEqual('') + const headers = heliaGatewayResponse?.headers() + expect(headers).not.toBeNull() + expect(headers?.['content-type']).toContain('text/') + }) +}) diff --git a/package-lock.json b/package-lock.json index b7a6ff3..2b623b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,9 @@ "@helia/unixfs": "1.x", "blockstore-level": "^1.1.4", "datastore-level": "^10.1.4", - "dns-over-http-resolver": "2.1.3", + "dns-over-http-resolver": "3.0.0", "fastify": "4.24.3", - "fastify-metrics": "10.3.2", + "fastify-metrics": "10.3.3", "file-type": "18.x", "helia": "next", "lru-cache": "10.x", @@ -26,13 +26,13 @@ "@playwright/test": "^1.39.0", "@types/mime-types": "2.x", "@types/node": "20.x", - "aegir": "40.x", + "aegir": "41.x", "clinic": "^13.0.0", "concurrently": "^8.2.2", "cross-env": "^7.0.3", "debug": "4.3.4", "typescript": "5.x", - "wait-on": "^7.0.1" + "wait-on": "^7.1.0" }, "engines": { "node": ">=20", @@ -2145,18 +2145,18 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", - "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -2193,9 +2193,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.22.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", - "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2256,9 +2256,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2335,6 +2335,27 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@helia/delegated-routing-v1-http-api-client": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@helia/delegated-routing-v1-http-api-client/-/delegated-routing-v1-http-api-client-1.1.0.tgz", + "integrity": "sha512-bw2qeHabNNaK2pZFKZqCYXyz5kK3022rA+D6v2HtQclZX8WmkYD2OHwqnuVZ7ZRUN2lNTwt3Mq2ESEcKY/3Gjg==", + "dependencies": { + "@libp2p/interface": "^0.1.2", + "@libp2p/logger": "^3.0.2", + "@libp2p/peer-id": "^3.0.3", + "@multiformats/multiaddr": "^12.1.3", + "any-signal": "^4.1.1", + "browser-readablestream-to-it": "^2.0.3", + "ipns": "^7.0.1", + "it-first": "^3.0.3", + "it-map": "^3.0.4", + "it-ndjson": "^1.0.4", + "multiformats": "^12.1.1", + "p-defer": "^4.0.0", + "p-queue": "^7.3.4", + "uint8arrays": "^4.0.6" + } + }, "node_modules/@helia/interface": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-2.0.0.tgz", @@ -2374,12 +2395,12 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -2423,9 +2444,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "node_modules/@ipld/dag-cbor": { @@ -2729,9 +2750,9 @@ } }, "node_modules/@libp2p/interface": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-0.1.2.tgz", - "integrity": "sha512-Q5t27434Mvn+R6AUJlRH+q/jSXarDpP+KXVkyGY7S1fKPI2berqoFPqT61bRRBYsCH2OPZiKBB53VUzxL9uEvg==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-0.1.6.tgz", + "integrity": "sha512-Lzc5cS/hXuoXhuAbVIxJIHLCYmfPcbU0vVgrpMoiP1Qb2Q3ETU4A46GB8s8mWXgSU6tr9RcqerUqzFYD6+OAag==", "dependencies": { "@multiformats/multiaddr": "^12.1.5", "abortable-iterator": "^5.0.1", @@ -2739,6 +2760,7 @@ "it-stream-types": "^2.0.1", "multiformats": "^12.0.1", "p-defer": "^4.0.0", + "race-signal": "^1.0.0", "uint8arraylist": "^2.4.3" } }, @@ -2753,27 +2775,6 @@ "uint8arraylist": "^2.4.3" } }, - "node_modules/@libp2p/ipni-content-routing": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@libp2p/ipni-content-routing/-/ipni-content-routing-2.0.0.tgz", - "integrity": "sha512-6YY7FUJ4KJ8UcrBosEcaRAaB1n/6iJuQ+etdx/8ON49oYIRoqXfTEUGNDORZweStEoktgS8oT8DLKUuAystm0g==", - "dependencies": { - "@libp2p/interface": "^0.1.1", - "@libp2p/logger": "^3.0.1", - "@libp2p/peer-id": "^3.0.1", - "@multiformats/multiaddr": "^12.1.2", - "any-signal": "^4.1.1", - "browser-readablestream-to-it": "^2.0.2", - "iterable-ndjson": "^1.1.0", - "multiformats": "^12.0.1", - "p-defer": "^4.0.0", - "p-queue": "^7.3.4" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, "node_modules/@libp2p/kad-dht": { "version": "10.0.8", "resolved": "https://registry.npmjs.org/@libp2p/kad-dht/-/kad-dht-10.0.8.tgz", @@ -2906,11 +2907,11 @@ } }, "node_modules/@libp2p/peer-id": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-3.0.2.tgz", - "integrity": "sha512-133qGXu9UBiqsYm7nBDJaAh4eiKe79DPLKF+/aRu0Z7gKcX7I0+LewEky4kBt3olhYQSF1CAnJIzD8Dmsn40Yw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-3.0.6.tgz", + "integrity": "sha512-iN1Ia5gH2U1V/GOVRmLHmVY6fblxzrOPUoZrMYjHl/K4s+AiI7ym/527WDeQvhQpD7j3TfDwcAYforD2dLGpLw==", "dependencies": { - "@libp2p/interface": "^0.1.2", + "@libp2p/interface": "^0.1.6", "multiformats": "^12.0.1", "uint8arrays": "^4.0.6" } @@ -3137,6 +3138,17 @@ "npm": ">=7.0.0" } }, + "node_modules/@multiformats/multiaddr/node_modules/dns-over-http-resolver": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-2.1.3.tgz", + "integrity": "sha512-zjRYFhq+CsxPAouQWzOsxNMvEN+SHisjzhX8EMxd2Y0EG3thvn6wXQgMJLnTDImkhe4jhLbOQpXtL10nALBOSA==", + "dependencies": { + "debug": "^4.3.1", + "native-fetch": "^4.0.2", + "receptacle": "^1.3.2", + "undici": "^5.12.0" + } + }, "node_modules/@multiformats/murmur3": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-2.1.7.tgz", @@ -4145,9 +4157,9 @@ "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", - "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/json5": { @@ -4263,9 +4275,9 @@ "dev": true }, "node_modules/@types/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", "dev": true }, "node_modules/@types/sinon": { @@ -4293,12 +4305,6 @@ "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", "dev": true }, - "node_modules/@types/ungap__structured-clone": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@types/ungap__structured-clone/-/ungap__structured-clone-0.3.0.tgz", - "integrity": "sha512-eBWREUhVUGPze+bUW22AgUr05k8u+vETzuYdLYSvWqGTUe0KOf+zVnOB1qER5wMcw8V6D9Ar4DfJmVvD1yu0kQ==", - "dev": true - }, "node_modules/@types/unist": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", @@ -4320,18 +4326,18 @@ } }, "node_modules/@types/yargs": { - "version": "17.0.26", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz", - "integrity": "sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw==", + "version": "17.0.30", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.30.tgz", + "integrity": "sha512-3SJLzYk3yz3EgI9I8OLoH06B3PdXIoU2imrBZzaGqUtUXf5iUNDtmAfCGuQrny1bnmyjh/GM/YNts6WK5jR5Rw==", "dev": true, "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==", + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.2.tgz", + "integrity": "sha512-5qcvofLPbfjmBfKaLfj/+f+Sbd6pN4zl7w7VSVI5uz7m9QZTuB2aZAa2uo1wHFBNN2x6g/SoTkXmd8mQnQF2Cw==", "dev": true }, "node_modules/@types/yauzl": { @@ -4934,9 +4940,9 @@ } }, "node_modules/aegir": { - "version": "40.0.13", - "resolved": "https://registry.npmjs.org/aegir/-/aegir-40.0.13.tgz", - "integrity": "sha512-Z/rVx9RpSd0Bmj/0t56E5rcm23lss6WRqMVxO5bAfDPoEnb4Zrk+/JEjGM3Z9/8U7TP6KV3ioNJXy0N6rKi8Lg==", + "version": "41.1.9", + "resolved": "https://registry.npmjs.org/aegir/-/aegir-41.1.9.tgz", + "integrity": "sha512-8tu7a0XxgXOCvLTn64rZ0LemW3ZhRCP4fWM/5DONDOVbROE3PlnfRMTBgUX0UMQVMsHTu7ElXSLaakkDQk0oDQ==", "dev": true, "dependencies": { "@electron/get": "^2.0.0", @@ -4971,7 +4977,7 @@ "env-paths": "^3.0.0", "esbuild": "^0.19.2", "eslint": "^8.31.0", - "eslint-config-ipfs": "^5.0.0", + "eslint-config-ipfs": "^6.0.0", "eslint-plugin-etc": "^2.0.2", "eslint-plugin-import": "^2.18.0", "eslint-plugin-jsdoc": "^46.4.3", @@ -4992,7 +4998,6 @@ "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", - "mdast-util-toc": "^7.0.0", "merge-options": "^3.0.4", "micromark-extension-gfm": "^3.0.0", "micromark-extension-gfm-footnote": "^2.0.0", @@ -5036,10 +5041,6 @@ }, "bin": { "aegir": "src/index.js" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.6.0" } }, "node_modules/agent-base": { @@ -6598,13 +6599,14 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9351,9 +9353,9 @@ } }, "node_modules/define-data-property": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz", - "integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", "dev": true, "dependencies": { "get-intrinsic": "^1.2.1", @@ -9807,14 +9809,12 @@ } }, "node_modules/dns-over-http-resolver": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-2.1.3.tgz", - "integrity": "sha512-zjRYFhq+CsxPAouQWzOsxNMvEN+SHisjzhX8EMxd2Y0EG3thvn6wXQgMJLnTDImkhe4jhLbOQpXtL10nALBOSA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-3.0.0.tgz", + "integrity": "sha512-5+BI+B7n8LKhNaEZBYErr+CBd9t5nYtjunByLhrLGtZ+i3TRgiU8yE87pCjEBu2KOwNsD9ljpSXEbZ4S8xih5g==", "dependencies": { - "debug": "^4.3.1", - "native-fetch": "^4.0.2", - "receptacle": "^1.3.2", - "undici": "^5.12.0" + "debug": "^4.3.4", + "receptacle": "^1.3.2" } }, "node_modules/dns-packet": { @@ -10500,26 +10500,26 @@ } }, "node_modules/es-abstract": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.2.tgz", - "integrity": "sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA==", + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", + "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", "arraybuffer.prototype.slice": "^1.0.2", "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "call-bind": "^1.0.5", "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.1", + "get-intrinsic": "^1.2.2", "get-symbol-description": "^1.0.0", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has": "^1.0.3", "has-property-descriptors": "^1.0.0", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", + "hasown": "^2.0.0", "internal-slot": "^1.0.5", "is-array-buffer": "^3.0.2", "is-callable": "^1.2.7", @@ -10529,7 +10529,7 @@ "is-string": "^1.0.7", "is-typed-array": "^1.1.12", "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", + "object-inspect": "^1.13.1", "object-keys": "^1.1.1", "object.assign": "^4.1.4", "regexp.prototype.flags": "^1.5.1", @@ -10543,7 +10543,7 @@ "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -10553,26 +10553,26 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", "dev": true, "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", "dev": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" } }, "node_modules/es-to-primitive": { @@ -10825,18 +10825,19 @@ } }, "node_modules/eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -10879,9 +10880,9 @@ } }, "node_modules/eslint-config-ipfs": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-ipfs/-/eslint-config-ipfs-5.0.1.tgz", - "integrity": "sha512-rzMz44vDw4noJzFAz1gWT3/9VLFISGZsqg1o+svjiOQHzhee5GUK4yXXx89xNon77vQ/2tGvb6Qn/akIiNC1+g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-ipfs/-/eslint-config-ipfs-6.0.0.tgz", + "integrity": "sha512-8ZzsQFicC6Yu64KR923MmP7JHjGfQ2xvnQF3yC5WPGWVmGEOR+qNr53ak5D37wtc0/xNz5IdqdGJ9ZB6Qnso9w==", "dev": true, "dependencies": { "@typescript-eslint/eslint-plugin": "^5.23.0", @@ -11033,9 +11034,9 @@ } }, "node_modules/eslint-plugin-es-x": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.2.0.tgz", - "integrity": "sha512-9dvv5CcvNjSJPqnS5uZkqb3xmbeqRLnvXKK7iI5+oK/yTusyc46zbBZKENGsOfojm/mKfszyZb+wNqNPAPeGXA==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.3.0.tgz", + "integrity": "sha512-W9zIs+k00I/I13+Bdkl/zG1MEO07G97XjUSQuH117w620SJ6bHtLUmoMvkGA2oYnI/gNdr+G7BONLyYnFaLLEQ==", "dev": true, "peer": true, "dependencies": { @@ -11071,26 +11072,26 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.28.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz", - "integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==", + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", + "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", "dev": true, "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.findlastindex": "^1.2.2", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", + "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.8.0", - "has": "^1.0.3", - "is-core-module": "^2.13.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.fromentries": "^2.0.6", - "object.groupby": "^1.0.0", - "object.values": "^1.1.6", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", "semver": "^6.3.1", "tsconfig-paths": "^3.14.2" }, @@ -11189,9 +11190,9 @@ } }, "node_modules/eslint-plugin-n": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.1.0.tgz", - "integrity": "sha512-3wv/TooBst0N4ND+pnvffHuz9gNPmk/NkLwAxOt2JykTl/hcuECe6yhTtLJcZjIxtZwN+GX92ACp/QTLpHA3Hg==", + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.3.0.tgz", + "integrity": "sha512-/XZLH5CUXGK3laz3xYFNza8ZxLCq8ZNW6MsVw5z3d5hc2AwZzi0fPiySFZHQTdVDOHGs2cGv91aqzWmgBdq2gQ==", "dev": true, "peer": true, "dependencies": { @@ -11463,9 +11464,9 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "13.22.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", - "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -11975,9 +11976,9 @@ } }, "node_modules/fastify-metrics": { - "version": "10.3.2", - "resolved": "https://registry.npmjs.org/fastify-metrics/-/fastify-metrics-10.3.2.tgz", - "integrity": "sha512-02SEIGH02zfguqRMho0LB8L7YVAj5cIgWM0iqZslIErqaUWc1iHVAOC+YXYG3S2DZU6VHdFaMyuxjEOCQHAETA==", + "version": "10.3.3", + "resolved": "https://registry.npmjs.org/fastify-metrics/-/fastify-metrics-10.3.3.tgz", + "integrity": "sha512-TmMcfrMWBSbA7yk31tFtJnWKtNXLSO7jmTRIjPX9HKC4pLmyd0JnOQ3r9XCYnev6NL9/eVRXxNfrsqQdKTLZkw==", "dependencies": { "fastify-plugin": "^4.3.0", "prom-client": "^14.2.0" @@ -12200,12 +12201,12 @@ } }, "node_modules/flat-cache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", - "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", + "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", "dev": true, "dependencies": { - "flatted": "^3.2.7", + "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" }, @@ -12410,10 +12411,13 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { "version": "1.1.6", @@ -12494,15 +12498,15 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -12654,12 +12658,6 @@ "through2": "~2.0.0" } }, - "node_modules/github-slugger": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", - "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", - "dev": true - }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -13171,6 +13169,18 @@ "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==" }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hdr-histogram-js": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-3.0.0.tgz", @@ -13207,20 +13217,20 @@ } }, "node_modules/helia": { - "version": "2.0.3-43932a5", - "resolved": "https://registry.npmjs.org/helia/-/helia-2.0.3-43932a5.tgz", - "integrity": "sha512-B6f1+fOlKh4UW1OGuAvT447lCI2KJn8oq8jYsD2ADinQMPBa1e352Pvt6RVZ6RJiBQGqEVCl7h4EIqgQMjrLCw==", + "version": "2.0.3-343725", + "resolved": "https://registry.npmjs.org/helia/-/helia-2.0.3-343725.tgz", + "integrity": "sha512-kXHPXbi7Y/eYhFcKBFGzD73WFxgL6Efp6vIUbvww6Ur6XPFUmkX/jDe9fhBKQCn9trHz1c2tj4J+T9JEUw8KpQ==", "dependencies": { "@chainsafe/libp2p-gossipsub": "^10.0.0", "@chainsafe/libp2p-noise": "^13.0.0", "@chainsafe/libp2p-yamux": "^5.0.0", - "@helia/interface": "2.0.0-43932a5", + "@helia/delegated-routing-v1-http-api-client": "^1.1.0", + "@helia/interface": "2.0.0-0343725", "@ipld/dag-cbor": "^9.0.0", "@ipld/dag-json": "^10.0.1", "@ipld/dag-pb": "^4.0.3", "@libp2p/bootstrap": "^9.0.2", "@libp2p/interface": "^0.1.1", - "@libp2p/ipni-content-routing": "^2.0.0", "@libp2p/kad-dht": "^10.0.2", "@libp2p/logger": "^3.0.1", "@libp2p/mdns": "^9.0.2", @@ -13252,9 +13262,9 @@ } }, "node_modules/helia/node_modules/@helia/interface": { - "version": "2.0.0-43932a5", - "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-2.0.0-43932a5.tgz", - "integrity": "sha512-P17jg5QVBHqsn6ySg49txURjtGS1xG2MeTrhaRwt9SuQKmkj258CSGsgt/p6L2bXYKEPVa2aKSG27cQ2N+oLHw==", + "version": "2.0.0-343725", + "resolved": "https://registry.npmjs.org/@helia/interface/-/interface-2.0.0-343725.tgz", + "integrity": "sha512-1Yuf7tzEEfrJePGtdxleGtwFlidIRwQjJGFsQ7TVisLnIdG+KzgUJn9o6XSJ/O0BG28xZE/rzZh4rWiGjWqwcw==", "dependencies": { "@libp2p/interface": "^0.1.1", "interface-blockstore": "^5.0.0", @@ -13937,13 +13947,13 @@ "integrity": "sha512-SI2co5IAxAybBc9egRM2bXvHOa1RPh5SQQkO6di6t/aX92RbtzP4t8raB0l3GTzQmJADaBbzz8Tfa1QLgfMdGQ==" }, "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", "side-channel": "^1.0.4" }, "engines": { @@ -14258,12 +14268,12 @@ } }, "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -15037,6 +15047,11 @@ "it-pushable": "^3.2.0" } }, + "node_modules/it-ndjson": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/it-ndjson/-/it-ndjson-1.0.4.tgz", + "integrity": "sha512-tUDdN0N1oA3TahtTuN+0M7J1gOOw1KERE/dCaKt7PevI/ky3n2aPYv49yiuXB2LJZvY3N3rOAd0f5hVANtpKhA==" + }, "node_modules/it-pair": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/it-pair/-/it-pair-2.0.6.tgz", @@ -15168,14 +15183,6 @@ "npm": ">=7.0.0" } }, - "node_modules/iterable-ndjson": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/iterable-ndjson/-/iterable-ndjson-1.1.0.tgz", - "integrity": "sha512-OOp1Lb0o3k5MkXHx1YaIY5Z0ELosZfTnBaas9f8opJVcZGBIONA2zY/6CYE+LKkqrSDooIneZbrBGgOZnHPkrg==", - "dependencies": { - "string_decoder": "^1.2.0" - } - }, "node_modules/jackspeak": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", @@ -16477,25 +16484,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-toc": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-7.0.0.tgz", - "integrity": "sha512-C28UcSqjmnWuvgT8d97qpaItHKvySqVPAECUzqQ51xuMyNFFJwcFoKW77KoMjtXrclTidLQFDzLUmTmrshRweA==", - "dev": true, - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/ungap__structured-clone": "^0.3.0", - "@ungap/structured-clone": "^1.0.0", - "github-slugger": "^2.0.0", - "mdast-util-to-string": "^4.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/meow": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", @@ -22957,9 +22945,9 @@ } }, "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -25339,6 +25327,11 @@ "node": ">= 6" } }, + "node_modules/race-signal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/race-signal/-/race-signal-1.0.1.tgz", + "integrity": "sha512-a5un4dInIWoB7+76DieVE+Xv+wmyochKJ3P2GVs9dUKIzGuPyFR5iU3gEWJvztde/15fSOGkslbIsPxi+Loosw==" + }, "node_modules/ramda": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", @@ -27722,6 +27715,21 @@ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/set-function-name": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", @@ -30547,16 +30555,16 @@ "dev": true }, "node_modules/wait-on": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.0.1.tgz", - "integrity": "sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.1.0.tgz", + "integrity": "sha512-U7TF/OYYzAg+OoiT/B8opvN48UHt0QYMi4aD3PjRFpybQ+o6czQF8Ig3SKCCMJdxpBrCalIJ4O00FBof27Fu9Q==", "dev": true, "dependencies": { "axios": "^0.27.2", - "joi": "^17.7.0", + "joi": "^17.11.0", "lodash": "^4.17.21", - "minimist": "^1.2.7", - "rxjs": "^7.8.0" + "minimist": "^1.2.8", + "rxjs": "^7.8.1" }, "bin": { "wait-on": "bin/wait-on" @@ -30652,13 +30660,13 @@ "dev": true }, "node_modules/which-typed-array": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", - "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "call-bind": "^1.0.4", "for-each": "^0.3.3", "gopd": "^1.0.1", "has-tostringtag": "^1.0.0" diff --git a/package.json b/package.json index 037d8c2..8dd0837 100644 --- a/package.json +++ b/package.json @@ -4,18 +4,22 @@ "description": "Helia in Docker Container", "main": "src/index.ts", "scripts": { + "lint": "aegir lint", "build": "aegir build --bundle false", "start": "node dist/src/index.js", "start:dev": "npm run build && node dist/src/index.js", "start:dev-trace": "npm run build && node --trace-warnings dist/src/index.js", "start:dev-doctor": "npm run build && npx clinic doctor --name playwright -- node dist/src/index.js", "start:dev-flame": "npm run build && npx clinic flame --name playwright -- node dist/src/index.js", + "start:inspect": "npm run build && node --inspect dist/src/index.js", "test": "echo \"Error: no test specified\" && exit 1", "test:e2e": "playwright test", "test:http-e2e": "cross-env USE_BITSWAP=false USE_LIBP2P=false playwright test", - "test:e2e-flame": "concurrently -k --ks SIGINT -s all -n \"gateway,playwright\" -c \"magenta,blue\" \"npm run start:dev-flame\" \"wait-on 'tcp:$PORT' && npm run test:e2e\"", - "test:e2e-doctor": "concurrently -k --ks SIGINT -s all -n \"gateway,playwright\" -c \"magenta,blue\" \"npm run start:dev-doctor\" \"wait-on 'tcp:$PORT' && npm run test:e2e\"", - "healthcheck": "node dist/src/healthcheck.js" + "test:e2e-flame": "concurrently -k -s all -n \"gateway,playwright\" -c \"magenta,blue\" \"npm run start:dev-flame\" \"wait-on 'tcp:$PORT' && npm run test:e2e\"", + "test:e2e-doctor": "concurrently -k -s all -n \"gateway,playwright\" -c \"magenta,blue\" \"npm run start:dev-doctor\" \"wait-on 'tcp:$PORT' && npm run test:e2e\"", + "healthcheck": "node dist/src/healthcheck.js", + "debug:until-death": "./debugging/until-death.sh", + "debug:test-gateways": "./debugging/test-gateways.sh" }, "type": "module", "repository": { @@ -48,21 +52,21 @@ "@playwright/test": "^1.39.0", "@types/mime-types": "2.x", "@types/node": "20.x", - "aegir": "40.x", + "aegir": "41.x", "clinic": "^13.0.0", "concurrently": "^8.2.2", "cross-env": "^7.0.3", "debug": "4.3.4", "typescript": "5.x", - "wait-on": "^7.0.1" + "wait-on": "^7.1.0" }, "dependencies": { "@helia/unixfs": "1.x", "blockstore-level": "^1.1.4", "datastore-level": "^10.1.4", - "dns-over-http-resolver": "2.1.3", + "dns-over-http-resolver": "3.0.0", "fastify": "4.24.3", - "fastify-metrics": "10.3.2", + "fastify-metrics": "10.3.3", "file-type": "18.x", "helia": "next", "lru-cache": "10.x", diff --git a/playwright.config.ts b/playwright.config.ts index 6e91a49..69b6f75 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -53,6 +53,9 @@ export default defineConfig({ command: (process.env.DOCTOR != null) ? 'npm run start:dev-doctor' : 'npm run start:dev', port: PORT, // Tiros does not re-use the existing server. - reuseExistingServer: process.env.CI == null + reuseExistingServer: process.env.CI == null, + env: { + DEBUG: process.env.DEBUG ?? ' ' + } } }) diff --git a/src/constants.ts b/src/constants.ts index ea99dee..8538162 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -3,6 +3,7 @@ export const PORT = Number(process.env.PORT ?? 8080) export const HOST = process.env.HOST ?? '0.0.0.0' export const DEBUG = process.env.DEBUG ?? '' +export const FASTIFY_DEBUG = process.env.FASTIFY_DEBUG ?? '' export const USE_SUBDOMAINS = process.env.USE_SUBDOMAINS === 'true' diff --git a/src/contentType.ts b/src/contentType.ts index 133b243..7a55f79 100644 --- a/src/contentType.ts +++ b/src/contentType.ts @@ -23,7 +23,7 @@ const tests: Array<(input: testInput) => testOutput> = [ // testing file-type from buffer async ({ bytes }): testOutput => (await fileTypeFromBuffer(bytes))?.mime, // testing file-type from path - // ts-expect-error because the type definitions are misleading + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions async ({ path }): testOutput => mime.lookup(path) || undefined, // default async (): Promise => DEFAULT_MIME_TYPE diff --git a/src/heliaFetch.ts b/src/heliaFetch.ts index 374f5f3..6f349b9 100644 --- a/src/heliaFetch.ts +++ b/src/heliaFetch.ts @@ -6,7 +6,7 @@ import DOHResolver from 'dns-over-http-resolver' import { createHelia, type Helia } from 'helia' import { LRUCache } from 'lru-cache' import { CID } from 'multiformats/cid' -import pTryEach from 'p-try-each' +import type { UnixFSEntry } from 'ipfs-unixfs-exporter' const ROOT_FILE_PATTERNS = [ 'index.html', @@ -20,6 +20,10 @@ interface HeliaPathParts { relativePath: string } +interface HeliaFetchOptions extends HeliaPathParts { + signal?: AbortSignal +} + interface HeliaFetchConfig { resolveRedirects: boolean } @@ -111,15 +115,15 @@ export class HeliaFetch { /** * fetch a path from a given namespace and address. */ - public async fetch ({ namespace, address, relativePath }: HeliaPathParts): Promise> { + public async fetch ({ namespace, address, relativePath, signal }: HeliaFetchOptions): Promise> { try { await this.ready this.log('Processing Fetch:', { namespace, address, relativePath }) switch (namespace) { case 'ipfs': - return await this.fetchIpfs(CID.parse(address), { path: relativePath }) + return await this.fetchIpfs(CID.parse(address), { path: relativePath, signal }) case 'ipns': - return await this.fetchIpns(address, { path: relativePath }) + return await this.fetchIpns(address, { path: relativePath, signal }) default: throw new Error('Namespace is not valid, provide path as /ipfs/ or /ipns/') } @@ -231,26 +235,21 @@ export class HeliaFetch { */ private async getDirectoryResponse (...[cid, options]: Parameters): Promise> { this.log('Getting directory response:', { cid, options }) - const rootFile = await pTryEach(this.rootFilePatterns.map(file => { - const directoryPath = options?.path ?? '' - return async (): Promise<{ name: string, cid: CID }> => { - try { - const path = this.sanitizeUrlPath(`${directoryPath}/${file}`) - this.log('Trying to get root file:', { file, directoryPath }) - const stats = await this.fs.stat(cid, { path }) - this.log('Got root file:', { file, directoryPath, stats }) - return { - name: file, - cid: stats.cid - } - } catch (error) { - return Promise.reject(error) - } + let rootFile: UnixFSEntry | null = null + for await (const file of this.fs.ls(cid, { signal: options?.signal })) { + if (this.rootFilePatterns.includes(file.name)) { + this.log(`Found root file '${file.name}': `, file) + rootFile = file + break + } else { + this.log(`Skipping ${file.type} '${file.name}' in root CID because the filename is not in rootFilePatterns: ${this.rootFilePatterns}`) } - })) + } + if (rootFile == null) { + throw new Error('No root file found') + } - // no options needed, because we already have the CID for the rootFile - return this.getFileResponse(rootFile.cid) + return this.getFileResponse(rootFile.cid, { ...options }) } /** diff --git a/src/heliaServer.ts b/src/heliaServer.ts index 9677d97..c6a691d 100644 --- a/src/heliaServer.ts +++ b/src/heliaServer.ts @@ -11,7 +11,7 @@ const HELIA_RELEASE_INFO_API = (version: string): string => `https://api.github. export interface RouteEntry { path: string type: 'GET' | 'POST' - handler: (request: FastifyRequest, reply: FastifyReply) => Promise + handler(request: FastifyRequest, reply: FastifyReply): Promise } interface RouteHandler { @@ -41,7 +41,7 @@ export class HeliaServer { public routes: RouteEntry[] constructor (logger: debug.Debugger) { - this.log = logger.extend('fastify') + this.log = logger.extend('server') this.isReady = this.init() this.routes = [] this.log('Initialized') @@ -85,7 +85,13 @@ export class HeliaServer { path: '/api/v0/repo/gc', type: 'POST', handler: async (request, reply): Promise => this.gc({ request, reply }) - }, { + }, + { + path: '/api/v0/repo/gc', + type: 'GET', + handler: async (request, reply): Promise => this.gc({ request, reply }) + }, + { path: '/*', type: 'GET', handler: async (request, reply): Promise => this.fetch({ request, reply }) @@ -94,6 +100,10 @@ export class HeliaServer { path: '/', type: 'GET', handler: async (request, reply): Promise => { + if (request.url.includes('/api/v0')) { + await reply.code(400).send('API + Method not supported') + return + } if (USE_SUBDOMAINS && request.hostname.split('.').length > 1) { return this.fetch({ request, reply }) } @@ -111,6 +121,10 @@ export class HeliaServer { return this.fetchWithoutSubdomain({ request, reply }) } const { ns: namespace, address, '*': relativePath } = request.params as EntryParams + if (address.includes('wikipedia')) { + await reply.code(500).send('Wikipedia is not yet supported. Follow https://github.com/ipfs/helia-http-gateway/issues/35 for more information.') + return + } let cidv1Address: string | null = null if (this.HAS_UPPERCASE_REGEX.test(address)) { cidv1Address = CID.parse(address).toV1().toString() @@ -133,44 +147,69 @@ export class HeliaServer { } async fetchWithoutSubdomain ({ request, reply }: RouteHandler): Promise { + const opController = new AbortController() + request.raw.on('close', () => { + if (request.raw.aborted) { + this.log('Request aborted by client') + opController.abort() + } + }) const { ns: namespace, address, '*': relativePath } = request.params as EntryParams try { await this.isReady - await this._fetch({ request, reply, address, namespace, relativePath }) + await this._fetch({ request, reply, address, namespace, relativePath, signal: opController.signal }) } catch (error) { this.log('Error requesting content from helia:', error) await reply.code(500).send(error) } } - async _fetch ({ reply, address, namespace, relativePath }: RouteHandler & ParsedEntryParams): Promise { + async _fetch ({ reply, address, namespace, relativePath, signal }: RouteHandler & ParsedEntryParams & { signal: AbortSignal }): Promise { this.log('Fetching from Helia:', { address, namespace, relativePath }) let type: string | undefined // raw response is needed to respond with the correct content type. - for await (const chunk of await this.heliaFetch.fetch({ address, namespace, relativePath })) { - if (type === undefined) { - type = await parseContentType({ bytes: chunk, path: relativePath }) - // this needs to happen first. - reply.raw.writeHead(200, { - 'Content-Type': type ?? DEFAULT_MIME_TYPE, - 'Cache-Control': 'public, max-age=31536000, immutable' - }) + try { + for await (const chunk of await this.heliaFetch.fetch({ address, namespace, relativePath, signal })) { + if (type === undefined) { + type = await parseContentType({ bytes: chunk, path: relativePath }) + // this needs to happen first. + reply.raw.writeHead(200, { + 'Content-Type': type ?? DEFAULT_MIME_TYPE, + 'Cache-Control': 'public, max-age=31536000, immutable' + }) + } + reply.raw.write(Buffer.from(chunk)) } - reply.raw.write(Buffer.from(chunk)) + } catch (err) { + this.log('Error fetching from Helia:', err) + // TODO: If we failed here and we already wrote the headers, we need to handle that. + // await reply.code(500) + } finally { + reply.raw.end() } - reply.raw.end() } /** * Fetches a content for a subdomain, which basically queries delegated routing API and then fetches the path from helia. */ async fetch ({ request, reply }: RouteHandler): Promise { + const opController = new AbortController() + request.raw.on('close', () => { + if (request.raw.aborted) { + this.log('Request aborted by client') + opController.abort() + } + }) try { await this.isReady this.log('Requesting content from helia:', request.url) const { address, namespace } = this.parseHostParts(request.hostname) + if (address.includes('wikipedia')) { + await reply.code(500).send('Wikipedia is not yet supported. Follow https://github.com/ipfs/helia-http-gateway/issues/35 for more information.') + return + } const { url: relativePath } = request - await this._fetch({ request, reply, address, namespace, relativePath }) + await this._fetch({ request, reply, address, namespace, relativePath, signal: opController.signal }) } catch (error) { this.log('Error requesting content from helia:', error) await reply.code(500).send(error) @@ -222,7 +261,7 @@ export class HeliaServer { async gc ({ reply }: RouteHandler): Promise { await this.isReady this.log('GCing node') - await this.heliaFetch.node?.gc() + await this.heliaFetch.node?.gc({ signal: AbortSignal.timeout(20000) }) await reply.code(200).send('OK') } diff --git a/src/index.ts b/src/index.ts index b8ab026..69c3867 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import debug from 'debug' import Fastify from 'fastify' import metricsPlugin from 'fastify-metrics' -import { DEBUG, HOST, PORT, METRICS } from './constants.js' +import { FASTIFY_DEBUG, HOST, PORT, METRICS } from './constants.js' import { HeliaServer, type RouteEntry } from './heliaServer.js' const logger = debug('helia-http-gateway') @@ -12,8 +12,8 @@ await heliaServer.isReady // Add the prometheus middleware const app = Fastify({ logger: { - enabled: DEBUG !== '', - msgPrefix: 'helia-http-gateway:fastify', + enabled: FASTIFY_DEBUG !== '', + msgPrefix: 'helia-http-gateway:fastify ', level: 'info', transport: { target: 'pino-pretty'