From 560329a0e93ddfe106b5d3c42c1edaafa29c4a6b Mon Sep 17 00:00:00 2001 From: Travis Vachon Date: Thu, 19 Sep 2024 18:56:40 +0200 Subject: [PATCH 1/6] fix: disable UCAN log expiration (#416) it looks like we set this bucket up to expire items - in retrospect I think we should have created a separate bucket for the UCAN log because this stream log store bucket was probably originally meant to store streaming items temporarily disable expiration for now, but uncomment with a note --- stacks/firehose-stack.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/stacks/firehose-stack.js b/stacks/firehose-stack.js index 7234487f..cfccb7a0 100644 --- a/stacks/firehose-stack.js +++ b/stacks/firehose-stack.js @@ -34,10 +34,13 @@ export function UcanFirehoseStack ({ stack, app }) { cdk: { bucket: { ...getBucketConfig('stream-log-store', app.stage), - lifecycleRules: [{ - enabled: true, - expiration: Duration.days(90) - }] + lifecycleRules: [ + // disable this for now - maybe we do want lifecycle rules eventually but we DEFINITELY don't want to expire these logs! + // { + // enabled: true, + // expiration: Duration.days(90) + // } + ] } } }) From 53cf7cf60ca0f80555ba77cb8b41a19334ce8693 Mon Sep 17 00:00:00 2001 From: Hannah Howard Date: Thu, 19 Sep 2024 12:17:44 -0700 Subject: [PATCH 2/6] Disable flaky filecoin test for now (#419) # Goals currently, seed.run PRs fail 90%+ of the time because of the Filecoin test. until we have a more reliable way to test the filecoin flow, the PR disables it. the inserted comment suggests a few solutions -- either spinning up custom infra for w3filecoin pipeline on each pr to get more reliable results, or mocking out the pipeline either way, this is the temporary fix -- I don't want to put in extra retries or anything -- we just need to find a better way to test, or investigate if there are in fact underlying issues, but in the meantime, other PRs need to get merged. --------- Co-authored-by: Petra Jaros --- test/filecoin.test.js | 166 ++++++++++++++++++++++-------------------- 1 file changed, 88 insertions(+), 78 deletions(-) diff --git a/test/filecoin.test.js b/test/filecoin.test.js index 4f37bfbb..fb104325 100644 --- a/test/filecoin.test.js +++ b/test/filecoin.test.js @@ -1,16 +1,13 @@ import { testFilecoin as test } from './helpers/context.js' import { fetch } from '@web-std/fetch' import pWaitFor from 'p-wait-for' -import pRetry from 'p-retry' import * as CAR from '@ucanto/transport/car' import { Storefront } from '@web3-storage/filecoin-client' -import { DynamoDBClient } from '@aws-sdk/client-dynamodb' import * as Link from 'multiformats/link' import * as raw from 'multiformats/codecs/raw' import { base58btc } from 'multiformats/bases/base58' import * as AgentStore from '../upload-api/stores/agent.js' -import { useReceiptStore } from '../filecoin/store/receipt.js' import { getApiEndpoint, @@ -24,7 +21,7 @@ import { import { createMailSlurpInbox, setupNewClient } from './helpers/up-client.js' import { getClientConfig } from './helpers/fil-client.js' import { randomFile } from './helpers/random.js' -import { putTableItem, pollQueryTable } from './helpers/table.js' +import { pollQueryTable } from './helpers/table.js' import { waitForStoreOperationOkResult } from './helpers/store.js' /** @@ -42,7 +39,7 @@ test.before(t => { test('w3filecoin integration flow', async t => { const stage = getStage() const s3Client = getAwsBucketClient() - const s3ClientFilecoin = getAwsBucketClient('us-east-2') + // const s3ClientFilecoin = getAwsBucketClient('us-east-2') const inbox = await createMailSlurpInbox() const endpoint = t.context.apiEndpoint @@ -147,6 +144,7 @@ test('w3filecoin integration flow', async t => { const workflowWithReceiptResponseAfterRedirect = await fetch(workflowLocation) // Get receipt from Message Archive const agentMessageBytes = new Uint8Array((await workflowWithReceiptResponseAfterRedirect.arrayBuffer())) + console.log(agentMessageBytes.length) const agentMessage = await CAR.request.decode({ body: agentMessageBytes, headers: {}, @@ -175,7 +173,7 @@ test('w3filecoin integration flow', async t => { name: '', }, }) - const receiptStoreFilecoin = useReceiptStore(s3ClientFilecoin, 'invocation-store-staging-0', 'workflow-store-staging-0') + // const receiptStoreFilecoin = useReceiptStore(s3ClientFilecoin, 'invocation-store-staging-0', 'workflow-store-staging-0') // Await for `filecoin/submit` receipt console.log(`wait for filecoin/submit receipt ${filecoinSubmitReceiptCid.toString()} ...`) @@ -189,62 +187,74 @@ test('w3filecoin integration flow', async t => { if (!pieceOfferReceiptCid) { throw new Error('filecoin/submit receipt has no effect for piece/offer') } - console.log(`wait for piece/offer receipt ${pieceOfferReceiptCid.toString()} ...`) - const receiptPieceOfferRes = await waitForStoreOperationOkResult( - () => receiptStoreFilecoin.get(pieceOfferReceiptCid), - (res) => Boolean(res.ok) - ) - // Await for `piece/accept` receipt - const pieceAcceptReceiptCid = receiptPieceOfferRes.ok?.fx.join?.link() - if (!pieceAcceptReceiptCid) { - throw new Error('piece/offer receipt has no effect for piece/accept') - } - console.log(`wait for piece/accept receipt ${pieceAcceptReceiptCid.toString()} ...`) - const receiptPieceAcceptRes = await waitForStoreOperationOkResult( - () => receiptStoreFilecoin.get(pieceAcceptReceiptCid), - (res) => Boolean(res.ok) - ) - // Await for `aggregate/offer` receipt - const aggregateOfferReceiptCid = receiptPieceAcceptRes.ok?.fx.join?.link() - if (!aggregateOfferReceiptCid) { - throw new Error('piece/accept receipt has no effect for aggregate/offer') - } - console.log(`wait for aggregate/offer receipt ${aggregateOfferReceiptCid.toString()} ...`) - const receiptAggregateOfferRes = await waitForStoreOperationOkResult( - () => receiptStoreFilecoin.get(aggregateOfferReceiptCid), - (res) => Boolean(res.ok) - ) + // TODO: This code is disabled as it tests running, real, shared infrastructure + // that is maintained outside of this repo, and could fail for reasons unrelated + // to what's happening in this repo + // To the extent we want a kitchen-sink test, it should happen with + // infra deployed specifically for the test, with predictable performance + // Alternatively, if we're simply testing interactions with expected behavior + // for w3-filecoininfra, we should use a mocked version of the service with + // predictable responses. + // This rest of this test is disabled until one of these solutions is put + // in place - // @ts-ignore no type for aggregate - const aggregate = receiptAggregateOfferRes.ok?.out.ok?.aggregate + // console.log(`wait for piece/offer receipt ${pieceOfferReceiptCid.toString()} ...`) + // await waitForStoreOperationOkResult( + // () => receiptStoreFilecoin.get(pieceOfferReceiptCid), + // (res) => Boolean(res.ok) + // ) + // // Await for `piece/accept` receipt + // const pieceAcceptReceiptCid = receiptPieceOfferRes.ok?.fx.join?.link() + // if (!pieceAcceptReceiptCid) { + // throw new Error('piece/offer receipt has no effect for piece/accept') + // } + // console.log(`wait for piece/accept receipt ${pieceAcceptReceiptCid.toString()} ...`) + // const receiptPieceAcceptRes = await waitForStoreOperationOkResult( + // () => receiptStoreFilecoin.get(pieceAcceptReceiptCid), + // (res) => Boolean(res.ok) + // ) - // Put FAKE value in table to issue final receipt via cron? - const dealId = 1111 - console.log(`put deal on deal tracker for aggregate ${aggregate}`) - await putDealToDealTracker(aggregate.toString(), dealId) + // // Await for `aggregate/offer` receipt + // const aggregateOfferReceiptCid = receiptPieceAcceptRes.ok?.fx.join?.link() + // if (!aggregateOfferReceiptCid) { + // throw new Error('piece/accept receipt has no effect for aggregate/offer') + // } + // console.log(`wait for aggregate/offer receipt ${aggregateOfferReceiptCid.toString()} ...`) + // const receiptAggregateOfferRes = await waitForStoreOperationOkResult( + // () => receiptStoreFilecoin.get(aggregateOfferReceiptCid), + // (res) => Boolean(res.ok) + // ) - // Await for `aggregate/accept` receipt - const aggregateAcceptReceiptCid = receiptAggregateOfferRes.ok?.fx.join?.link() - if (!aggregateAcceptReceiptCid) { - throw new Error('aggregate/offer receipt has no effect for aggregate/accept') - } - console.log(`wait for aggregate/accept receipt ${aggregateAcceptReceiptCid.toString()} ...`) - await waitForStoreOperationOkResult( - async () => { - // Trigger cron to update and issue receipts based on deals - await pRetry(async () => { - const url = 'https://staging.dealer.web3.storage/cron' - const res = await fetch(url) - if (!res.ok) throw new Error(`failed request to ${url}: ${res.status}`) - }, { onFailedAttempt: console.warn }) + // // @ts-ignore no type for aggregate + // const aggregate = receiptAggregateOfferRes.ok?.out.ok?.aggregate - return receiptStoreFilecoin.get(aggregateAcceptReceiptCid) - // return agentStoreFilecoin.receipts.get(aggregateAcceptReceiptCid) - }, - (res) => Boolean(res.ok) - ) + // // Put FAKE value in table to issue final receipt via cron? + // const dealId = 1111 + // console.log(`put deal on deal tracker for aggregate ${aggregate}`) + // await putDealToDealTracker(aggregate.toString(), dealId) + + // // Await for `aggregate/accept` receipt + // const aggregateAcceptReceiptCid = receiptAggregateOfferRes.ok?.fx.join?.link() + // if (!aggregateAcceptReceiptCid) { + // throw new Error('aggregate/offer receipt has no effect for aggregate/accept') + // } + // console.log(`wait for aggregate/accept receipt ${aggregateAcceptReceiptCid.toString()} ...`) + // await waitForStoreOperationOkResult( + // async () => { + // // Trigger cron to update and issue receipts based on deals + // await pRetry(async () => { + // const url = 'https://staging.dealer.web3.storage/cron' + // const res = await fetch(url) + // if (!res.ok) throw new Error(`failed request to ${url}: ${res.status}`) + // }, { onFailedAttempt: console.warn }) + + // return receiptStoreFilecoin.get(aggregateAcceptReceiptCid) + // // return agentStoreFilecoin.receipts.get(aggregateAcceptReceiptCid) + // }, + // (res) => Boolean(res.ok) + // ) })) }) @@ -270,25 +280,25 @@ async function getPiecesByContent (t, content) { return item } -/** - * @param {string} piece - * @param {number} dealId - */ -async function putDealToDealTracker (piece, dealId) { - const region = 'us-east-2' - const endpoint = `https://dynamodb.${region}.amazonaws.com` - const tableName = 'staging-w3filecoin-deal-tracker-deal-store-v1' - const client = new DynamoDBClient({ - region, - endpoint - }) - const record = { - piece, - provider: 'f0001', - dealId, - expirationEpoch: Date.now() + 10e9, - insertedAt: (new Date()).toISOString(), - source: 'testing' - } - await putTableItem(client, tableName, record) -} +// /** +// * @param {string} piece +// * @param {number} dealId +// */ +// async function putDealToDealTracker (piece, dealId) { +// const region = 'us-east-2' +// const endpoint = `https://dynamodb.${region}.amazonaws.com` +// const tableName = 'staging-w3filecoin-deal-tracker-deal-store-v1' +// const client = new DynamoDBClient({ +// region, +// endpoint +// }) +// const record = { +// piece, +// provider: 'f0001', +// dealId, +// expirationEpoch: Date.now() + 10e9, +// insertedAt: (new Date()).toISOString(), +// source: 'testing' +// } +// await putTableItem(client, tableName, record) +// } From 374951532f8fac0c2fc663cd28b4c4a030c5aa1c Mon Sep 17 00:00:00 2001 From: Petra Jaros Date: Thu, 19 Sep 2024 15:20:12 -0400 Subject: [PATCH 3/6] Storacha-branded validation flow (#417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Before Merge:** * [ ] Set `HOSTED_ZONES` (plural) on `prod`. * [ ] Merge https://github.com/storacha-network/w3infra/pull/419 (which this currently includes to get tests to pass). --- With `NEXT_PUBLIC_W3UP_SERVICE_URL=https://petra.up.storacha.network` in my local `console`: ![CleanShot 2024-08-19 at 17 33 15](https://github.com/user-attachments/assets/66aa54da-dbf6-47f1-9b1d-a151e9546b9f) Then… ![CleanShot 2024-08-19 at 17 34 12](https://github.com/user-attachments/assets/03141163-ec6f-447b-bdee-48e3d0ab8abc) And success! ![CleanShot 2024-08-19 at 17 33 55](https://github.com/user-attachments/assets/fe88b106-daa3-43dd-86ec-a71516bf1c0d) Or, failure. ![CleanShot 2024-08-19 at 17 34 19](https://github.com/user-attachments/assets/5a8dd394-0f90-429d-a3de-dc6ca9ea3819) Note that that error message is a dummy message in the test harness, not something real. Also, I see to have bumped something that made the Stripe section always appear. Previously, staging/prod was set not to show it by not providing the right env vars. I'm not sure why that changed, and whether we want it to. Closes https://github.com/storacha-network/project-tracking/issues/119 --------- Co-authored-by: hannahhoward --- .env.tpl | 7 +- README.md | 4 +- package-lock.json | 1004 ++++++++++++++++- package.json | 12 +- stacks/config.js | 40 +- stacks/filecoin-stack.js | 2 +- stacks/upload-api-stack.js | 240 ++-- test/helpers/deployment.js | 18 +- tsconfig.json | 19 +- upload-api/email.js | 44 +- .../functions/ucan-invocation-router.js | 4 +- upload-api/functions/validate-email.jsx | 142 ++- upload-api/html-storacha/index.jsx | 273 +++++ upload-api/html-storacha/preact-jsx.d.ts | 25 + upload-api/html-storacha/storacha-logo.svg | 1 + upload-api/html-storacha/svg.d.ts | 5 + upload-api/{html.jsx => html-w3s/index.jsx} | 5 +- upload-api/package.json | 9 +- .../test/helpers/validate-email-server.js | 126 ++- 19 files changed, 1706 insertions(+), 274 deletions(-) create mode 100644 upload-api/html-storacha/index.jsx create mode 100644 upload-api/html-storacha/preact-jsx.d.ts create mode 100644 upload-api/html-storacha/storacha-logo.svg create mode 100644 upload-api/html-storacha/svg.d.ts rename upload-api/{html.jsx => html-w3s/index.jsx} (98%) diff --git a/.env.tpl b/.env.tpl index 89d5168c..1b4c160f 100644 --- a/.env.tpl +++ b/.env.tpl @@ -1,8 +1,9 @@ # These variables are only available in your SST code. -# uncomment to try out deploying the w3up api under a custom domain. -# the value should match a hosted zone configured in route53 that your aws account has access to. -# HOSTED_ZONE=up.dag.haus +# uncomment to try out deploying the w3up api under a custom domain (or more +# than one). the value should match a hosted zone configured in route53 that +# your aws account has access to. +# HOSTED_ZONES=up.web3.storage,up.storacha.network # uncomment to try out deploying the roundabout api under a custom domain. # the value should match a hosted zone configured in route53 that your aws account has access to. diff --git a/README.md b/README.md index 069f539e..0369b22f 100644 --- a/README.md +++ b/README.md @@ -112,9 +112,9 @@ Please notice that appropriate environment variables must be set for the develop Ensure the following variables are set in the env when deploying -#### `HOSTED_ZONE` +#### `HOSTED_ZONES` -The root domain to deploy the w3up API to. e.g `up.web3.storage`. The value should match a hosted zone configured in route53 that your aws account has access to. +The root domain(s) to deploy the w3up API to. e.g `up.web3.storage`. The value should match a hosted zone configured in route53 that your aws account has access to. Multiple zones can be specified, in which case they are seperated by a comma, and this will cause deployment to each specified zone. #### `ROUNDABOUT_HOSTED_ZONE` diff --git a/package-lock.json b/package-lock.json index 313544b5..d0b277ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,7 +65,7 @@ "multiformats": "^13.1.0", "p-retry": "^6.2.0", "p-wait-for": "^5.0.0", - "typescript": "^4.9.3" + "typescript": "^5.5.4" } }, "billing": { @@ -5780,7 +5780,6 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz", "integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==", "dev": true, - "peer": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -5792,8 +5791,7 @@ "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@babel/template": { "version": "7.24.6", @@ -5999,6 +5997,22 @@ "node": "^14 || ^16 || ^17 || ^18 || ^19" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.18.13", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.13.tgz", @@ -6254,6 +6268,22 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { "version": "0.18.13", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.13.tgz", @@ -10537,6 +10567,16 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, + "node_modules/@types/reload": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@types/reload/-/reload-3.2.3.tgz", + "integrity": "sha512-mu4a71e0MJbQlw+cPjH+sZoEihq+QWR9cpM7z6tmM/ZX2rM85y0kji5Hq+szFLEKMRlIfhm+VsbE33WCCP5TpA==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/ws": "*" + } + }, "node_modules/@types/retry": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", @@ -14570,6 +14610,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-color": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.4.tgz", + "integrity": "sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.64", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.15", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -14965,6 +15021,48 @@ "node": ">=10" } }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/conf": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/conf/-/conf-11.0.2.tgz", @@ -15256,6 +15354,19 @@ "node": ">=0.10.0" } }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dev": true, + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/data-uri-to-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", @@ -15336,6 +15447,22 @@ "it-take": "^3.0.4" } }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/date-time": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz", @@ -15479,6 +15606,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/define-properties": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", @@ -16194,12 +16330,64 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, "node_modules/es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", "dev": true }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "dev": true, + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, "node_modules/esbuild": { "version": "0.18.13", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.13.tgz", @@ -16987,6 +17175,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -17067,6 +17270,16 @@ "node": ">= 0.6" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/event-iterator": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/event-iterator/-/event-iterator-2.0.0.tgz", @@ -17236,6 +17449,15 @@ "node": ">= 0.8" } }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dev": true, + "dependencies": { + "type": "^2.7.2" + } + }, "node_modules/fast-decode-uri-component": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", @@ -18086,6 +18308,19 @@ "node": ">=14" } }, + "node_modules/hd-scripts/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/helia": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/helia/-/helia-4.2.3.tgz", @@ -18826,7 +19061,6 @@ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, - "peer": true, "bin": { "is-docker": "cli.js" }, @@ -19228,7 +19462,6 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, - "peer": true, "dependencies": { "is-docker": "^2.0.0" }, @@ -20816,6 +21049,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "dev": true, + "dependencies": { + "es5-ext": "~0.10.2" + } + }, "node_modules/mailslurp-client": { "version": "15.20.2", "resolved": "https://registry.npmjs.org/mailslurp-client/-/mailslurp-client-15.20.2.tgz", @@ -20970,6 +21212,31 @@ "dev": true, "peer": true }, + "node_modules/memoizee": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", + "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==", + "dev": true, + "dependencies": { + "d": "^1.0.2", + "es5-ext": "^0.10.64", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/memoizee/node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -21872,6 +22139,12 @@ "node": ">= 0.4.0" } }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true + }, "node_modules/nocache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", @@ -22015,6 +22288,73 @@ "url": "https://github.com/sponsors/antelle" } }, + "node_modules/nodemon": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", + "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/nodemon/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/nofilter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", @@ -23516,6 +23856,12 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -24266,46 +24612,145 @@ "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz", "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==" }, - "node_modules/remeda": { - "version": "1.61.0", - "resolved": "https://registry.npmjs.org/remeda/-/remeda-1.61.0.tgz", - "integrity": "sha512-caKfSz9rDeSKBQQnlJnVW3mbVdFgxgGWQKq1XlFokqjf+hQD5gxutLGTTY2A/x24UxVyJe9gH5fAkFI63ULw4A==" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" + "node_modules/reload": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/reload/-/reload-3.3.0.tgz", + "integrity": "sha512-TObPRTy5dPlw9DS8n8ROd2BLnxI+RvVn4r0WBARVAfJ493jjcN70NI5TdkcrJmex2aQh5bfQJbFbr1NapU7Lnw==", + "dev": true, + "dependencies": { + "cli-color": "~2.0.0", + "commander": "~12.1.0", + "finalhandler": "~1.2.0", + "minimist": "~1.2.0", + "nodemon": "~3.1.4", + "open": "^8.0.0", + "serve-static": "~1.15.0", + "ws": "~8.18.0" + }, + "bin": { + "reload": "bin/reload" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "node_modules/reload/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "node_modules/reload/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "peer": true + "dependencies": { + "ms": "2.0.0" + } }, - "node_modules/requireindex": { + "node_modules/reload/node_modules/finalhandler": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", - "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dev": true, - "engines": { - "node": ">=0.10.5" - } - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/reload/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/reload/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/reload/node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reload/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/remeda": { + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-1.61.0.tgz", + "integrity": "sha512-caKfSz9rDeSKBQQnlJnVW3mbVdFgxgGWQKq1XlFokqjf+hQD5gxutLGTTY2A/x24UxVyJe9gH5fAkFI63ULw4A==" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true, + "peer": true + }, + "node_modules/requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true, + "engines": { + "node": ">=0.10.5" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "dependencies": { @@ -24449,6 +24894,15 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", @@ -24731,7 +25185,6 @@ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", "devOptional": true, - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -24830,6 +25283,30 @@ "simple-git-hooks": "cli.js" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -24898,6 +25375,12 @@ "integrity": "sha512-ZT711fePGn3+kQyLuv1fpd3rNSkNF8vd5Kv2D+qnOANeyKs3fx6bUMGWRPvgTTcYV64QMqZKZwcuaQSP3AZ0tg==", "dev": true }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -26600,6 +27083,19 @@ "node": ">=4" } }, + "node_modules/timers-ext": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", + "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", + "dev": true, + "dependencies": { + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/timestamp-nano": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/timestamp-nano/-/timestamp-nano-1.0.1.tgz", @@ -26656,6 +27152,15 @@ "resolved": "tools", "link": true }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -26765,6 +27270,12 @@ "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "dev": true }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "dev": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -26889,16 +27400,16 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/uint8-varint": { @@ -26973,6 +27484,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "node_modules/undici": { "version": "5.28.4", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", @@ -27632,9 +28149,9 @@ } }, "node_modules/ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "engines": { "node": ">=10.0.0" }, @@ -28081,18 +28598,415 @@ "@babel/preset-react": "^7.23.3", "@ipld/car": "^5.2.6", "@types/aws-lambda": "^8.10.108", + "@types/reload": "^3.2.3", "@web3-storage/content-claims-infra": "^1.2.1", "@web3-storage/sigv4": "^1.0.2", "ava": "^4.3.3", "aws-lambda-test-utils": "^1.3.0", + "concurrently": "^8.2.2", "constructs": "*", "dotenv": "^16.3.2", + "esbuild": "^0.23.1", + "express": "^4.19.2", + "nodemon": "^3.1.4", + "reload": "^3.2.2", "testcontainers": "^10.7.1" }, "engines": { "node": ">=16.15" } }, + "upload-api/node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "upload-api/node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, "upload-api/node_modules/nanoid": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz", diff --git a/package.json b/package.json index 9bfd7056..17b25523 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "w3infra", "version": "0.0.0", "private": true, + "packageManager": "npm@10.8.2+sha256.c8c61ba0fa0ab3b5120efd5ba97fdaf0e0b495eef647a97c4413919eda0a878b", "type": "module", "scripts": { "start": "sst start", @@ -58,7 +59,7 @@ "multiformats": "^13.1.0", "p-retry": "^6.2.0", "p-wait-for": "^5.0.0", - "typescript": "^4.9.3" + "typescript": "^5.5.4" }, "dependencies": { "@ipld/dag-json": "^10.1.5", @@ -109,6 +110,15 @@ "no-loop-func": "off", "no-warning-comments": "off", "jsdoc/check-indentation": "off", + "jsdoc/check-tag-names": [ + "error", + { + "definedTags": [ + "import", + "overload" + ] + } + ], "jsdoc/require-hyphen-before-param-description": "off", "react-hooks/rules-of-hooks": "off", "react/no-danger": "off" diff --git a/stacks/config.js b/stacks/config.js index 3bad2884..5c64f3ad 100644 --- a/stacks/config.js +++ b/stacks/config.js @@ -1,3 +1,4 @@ +/** @import { ApiDomainProps, App, Stack } from 'sst/constructs' */ import { Duration, RemovalPolicy } from 'aws-cdk-lib' import { createRequire } from 'module' import { StartingPosition } from 'aws-cdk-lib/aws-lambda' @@ -55,10 +56,33 @@ export function getBucketConfig(name, stage, version = 0){ /** * Return the custom domain config for http api - * + * + * @overload + * @param {string} stage + * @param {string} hostedZone + * @returns {ApiDomainProps} + */ +/** + * Return `undefined`, because {@link hostedZone} is not present. + * + * @overload + * @param {string} stage + * @param {undefined} hostedZone + * @returns {undefined} + */ +/** + * Return the custom domain config for http api, or `undefined` if + * {@link hostedZone} is not present. + * + * @overload + * @param {string} stage + * @param {string | undefined} hostedZone + * @returns {ApiDomainProps | undefined} + */ +/** * @param {string} stage * @param {string | undefined} hostedZone - * @returns {{domainName: string, hostedZone: string} | undefined} + * @returns {ApiDomainProps | undefined} */ export function getCustomDomain (stage, hostedZone) { // return no custom domain config if hostedZone not set @@ -74,7 +98,7 @@ export function getCustomDomain (stage, hostedZone) { /** - * @param {import('sst/constructs').Stack} stack + * @param {Stack} stack */ export function getEventSourceConfig (stack) { if (stack.stage !== 'prod') { @@ -103,7 +127,7 @@ export function getEventSourceConfig (stack) { } /** - * @param {import('sst/constructs').Stack} stack + * @param {Stack} stack */ export function getKinesisStreamConfig (stack) { if (stack.stage !== 'prod' && stack.stage !== 'staging') { @@ -133,8 +157,8 @@ export function getGitInfo () { } /** - * @param {import('sst/constructs').App} app - * @param {import('sst/constructs').Stack} stack + * @param {App} app + * @param {Stack} stack */ export function setupSentry (app, stack) { // Skip when locally @@ -150,8 +174,8 @@ export function setupSentry (app, stack) { } /** - * @param {import('sst/constructs').Stack} stack - * @param {{domainName: string, hostedZone: string} | undefined} customDomain + * @param {Stack} stack + * @param {ApiDomainProps | undefined} customDomain */ export function getServiceURL (stack, customDomain) { // in production we use the top level subdomain diff --git a/stacks/filecoin-stack.js b/stacks/filecoin-stack.js index 0b2c9e4e..c65eb838 100644 --- a/stacks/filecoin-stack.js +++ b/stacks/filecoin-stack.js @@ -31,7 +31,7 @@ export function FilecoinStack({ stack, app }) { STOREFRONT_PROOF, START_FILECOIN_METRICS_EPOCH_MS } = getEnv() - const storefrontCustomDomain = getCustomDomain(stack.stage, process.env.HOSTED_ZONE) + const storefrontCustomDomain = getCustomDomain(stack.stage, process.env.HOSTED_ZONES?.split(",")[0]) // Setup app monitoring with Sentry setupSentry(app, stack) diff --git a/stacks/upload-api-stack.js b/stacks/upload-api-stack.js index ab03d7ad..3e0a98a4 100644 --- a/stacks/upload-api-stack.js +++ b/stacks/upload-api-stack.js @@ -20,6 +20,17 @@ import { getCustomDomain, getApiPackageJson, getGitInfo, setupSentry, getEnv, ge * @param {import('sst/constructs').StackContext} properties */ export function UploadApiStack({ stack, app }) { + // For loading the Storacha logo + stack.setDefaultFunctionProps({ + nodejs: { + esbuild: { + loader: { + '.svg': 'text', + } + } + } + }); + const { AGGREGATOR_DID, CONTENT_CLAIMS_DID, @@ -39,119 +50,126 @@ export function UploadApiStack({ stack, app }) { const { blockAdvertPublisherQueue, blockIndexWriterQueue } = use(IndexerStack) // Setup API - const customDomain = getCustomDomain(stack.stage, process.env.HOSTED_ZONE) + const customDomains = process.env.HOSTED_ZONES?.split(',').map(zone => getCustomDomain(stack.stage, zone)) const pkg = getApiPackageJson() const git = getGitInfo() const ucanInvocationPostbasicAuth = new Config.Secret(stack, 'UCAN_INVOCATION_POST_BASIC_AUTH') - const api = new Api(stack, 'http-gateway', { - customDomain, - defaults: { - function: { - timeout: '60 seconds', - permissions: [ - allocationTable, - storeTable, - uploadTable, - customerTable, - delegationTable, - revocationTable, - delegationBucket, - consumerTable, - subscriptionTable, - rateLimitTable, - adminMetricsTable, - spaceMetricsTable, - pieceTable, - spaceDiffTable, - spaceSnapshotTable, - carparkBucket, - invocationBucket, - taskBucket, - workflowBucket, - ucanStream, - pieceOfferQueue, - filecoinSubmitQueue, - blockAdvertPublisherQueue, - blockIndexWriterQueue, - ], - environment: { - DID: process.env.UPLOAD_API_DID ?? '', - AGGREGATOR_DID, - ALLOCATION_TABLE_NAME: allocationTable.tableName, - STORE_TABLE_NAME: storeTable.tableName, - STORE_BUCKET_NAME: carparkBucket.bucketName, - UPLOAD_TABLE_NAME: uploadTable.tableName, - CONSUMER_TABLE_NAME: consumerTable.tableName, - CUSTOMER_TABLE_NAME: customerTable.tableName, - SUBSCRIPTION_TABLE_NAME: subscriptionTable.tableName, - SPACE_METRICS_TABLE_NAME: spaceMetricsTable.tableName, - RATE_LIMIT_TABLE_NAME: rateLimitTable.tableName, - DELEGATION_TABLE_NAME: delegationTable.tableName, - REVOCATION_TABLE_NAME: revocationTable.tableName, - SPACE_DIFF_TABLE_NAME: spaceDiffTable.tableName, - SPACE_SNAPSHOT_TABLE_NAME: spaceSnapshotTable.tableName, - DELEGATION_BUCKET_NAME: delegationBucket.bucketName, - INVOCATION_BUCKET_NAME: invocationBucket.bucketName, - TASK_BUCKET_NAME: taskBucket.bucketName, - WORKFLOW_BUCKET_NAME: workflowBucket.bucketName, - UCAN_LOG_STREAM_NAME: ucanStream.streamName, - ADMIN_METRICS_TABLE_NAME: adminMetricsTable.tableName, - PIECE_TABLE_NAME: pieceTable.tableName, - PIECE_OFFER_QUEUE_URL: pieceOfferQueue.queueUrl, - FILECOIN_SUBMIT_QUEUE_URL: filecoinSubmitQueue.queueUrl, - BLOCK_ADVERT_PUBLISHER_QUEUE_URL: blockAdvertPublisherQueue.queueUrl, - BLOCK_INDEX_WRITER_QUEUE_URL: blockIndexWriterQueue.queueUrl, - NAME: pkg.name, - VERSION: pkg.version, - COMMIT: git.commmit, - STAGE: stack.stage, - ACCESS_SERVICE_URL: getServiceURL(stack, customDomain) ?? '', - UPLOAD_SERVICE_URL: getServiceURL(stack, customDomain) ?? '', - POSTMARK_TOKEN: process.env.POSTMARK_TOKEN ?? '', - PROVIDERS: process.env.PROVIDERS ?? '', - R2_ACCESS_KEY_ID: process.env.R2_ACCESS_KEY_ID ?? '', - R2_SECRET_ACCESS_KEY: process.env.R2_SECRET_ACCESS_KEY ?? '', - R2_REGION: process.env.R2_REGION ?? '', - R2_CARPARK_BUCKET_NAME: process.env.R2_CARPARK_BUCKET_NAME ?? '', - R2_DELEGATION_BUCKET_NAME: process.env.R2_DELEGATION_BUCKET_NAME ?? '', - R2_ENDPOINT: process.env.R2_ENDPOINT ?? '', - REQUIRE_PAYMENT_PLAN: process.env.REQUIRE_PAYMENT_PLAN ?? '', - UPLOAD_API_DID: process.env.UPLOAD_API_DID ?? '', - STRIPE_PRICING_TABLE_ID: process.env.STRIPE_PRICING_TABLE_ID ?? '', - STRIPE_PUBLISHABLE_KEY: process.env.STRIPE_PUBLISHABLE_KEY ?? '', - DEAL_TRACKER_DID: process.env.DEAL_TRACKER_DID ?? '', - DEAL_TRACKER_URL: process.env.DEAL_TRACKER_URL ?? '', - CONTENT_CLAIMS_DID, - CONTENT_CLAIMS_URL, - CONTENT_CLAIMS_PROOF - }, - bind: [ - privateKey, - ucanInvocationPostbasicAuth, - stripeSecretKey, - contentClaimsPrivateKey - ] + const apis = (customDomains ?? [undefined]).map((customDomain) => { + const hostedZone = customDomain?.hostedZone + const apiId = [`http-gateway`, hostedZone?.replaceAll('.', '_')] + .filter(Boolean) + .join('-') + return new Api(stack, apiId, { + customDomain, + defaults: { + function: { + timeout: '60 seconds', + permissions: [ + allocationTable, + storeTable, + uploadTable, + customerTable, + delegationTable, + revocationTable, + delegationBucket, + consumerTable, + subscriptionTable, + rateLimitTable, + adminMetricsTable, + spaceMetricsTable, + pieceTable, + spaceDiffTable, + spaceSnapshotTable, + carparkBucket, + invocationBucket, + taskBucket, + workflowBucket, + ucanStream, + pieceOfferQueue, + filecoinSubmitQueue, + blockAdvertPublisherQueue, + blockIndexWriterQueue, + ], + environment: { + DID: process.env.UPLOAD_API_DID ?? '', + AGGREGATOR_DID, + ALLOCATION_TABLE_NAME: allocationTable.tableName, + STORE_TABLE_NAME: storeTable.tableName, + STORE_BUCKET_NAME: carparkBucket.bucketName, + UPLOAD_TABLE_NAME: uploadTable.tableName, + CONSUMER_TABLE_NAME: consumerTable.tableName, + CUSTOMER_TABLE_NAME: customerTable.tableName, + SUBSCRIPTION_TABLE_NAME: subscriptionTable.tableName, + SPACE_METRICS_TABLE_NAME: spaceMetricsTable.tableName, + RATE_LIMIT_TABLE_NAME: rateLimitTable.tableName, + DELEGATION_TABLE_NAME: delegationTable.tableName, + REVOCATION_TABLE_NAME: revocationTable.tableName, + SPACE_DIFF_TABLE_NAME: spaceDiffTable.tableName, + SPACE_SNAPSHOT_TABLE_NAME: spaceSnapshotTable.tableName, + DELEGATION_BUCKET_NAME: delegationBucket.bucketName, + INVOCATION_BUCKET_NAME: invocationBucket.bucketName, + TASK_BUCKET_NAME: taskBucket.bucketName, + WORKFLOW_BUCKET_NAME: workflowBucket.bucketName, + UCAN_LOG_STREAM_NAME: ucanStream.streamName, + ADMIN_METRICS_TABLE_NAME: adminMetricsTable.tableName, + PIECE_TABLE_NAME: pieceTable.tableName, + PIECE_OFFER_QUEUE_URL: pieceOfferQueue.queueUrl, + FILECOIN_SUBMIT_QUEUE_URL: filecoinSubmitQueue.queueUrl, + BLOCK_ADVERT_PUBLISHER_QUEUE_URL: blockAdvertPublisherQueue.queueUrl, + BLOCK_INDEX_WRITER_QUEUE_URL: blockIndexWriterQueue.queueUrl, + NAME: pkg.name, + VERSION: pkg.version, + COMMIT: git.commmit, + STAGE: stack.stage, + ACCESS_SERVICE_URL: getServiceURL(stack, customDomain) ?? '', + UPLOAD_SERVICE_URL: getServiceURL(stack, customDomain) ?? '', + POSTMARK_TOKEN: process.env.POSTMARK_TOKEN ?? '', + PROVIDERS: process.env.PROVIDERS ?? '', + R2_ACCESS_KEY_ID: process.env.R2_ACCESS_KEY_ID ?? '', + R2_SECRET_ACCESS_KEY: process.env.R2_SECRET_ACCESS_KEY ?? '', + R2_REGION: process.env.R2_REGION ?? '', + R2_CARPARK_BUCKET_NAME: process.env.R2_CARPARK_BUCKET_NAME ?? '', + R2_DELEGATION_BUCKET_NAME: process.env.R2_DELEGATION_BUCKET_NAME ?? '', + R2_ENDPOINT: process.env.R2_ENDPOINT ?? '', + REQUIRE_PAYMENT_PLAN: process.env.REQUIRE_PAYMENT_PLAN ?? '', + UPLOAD_API_DID: process.env.UPLOAD_API_DID ?? '', + STRIPE_PRICING_TABLE_ID: process.env.STRIPE_PRICING_TABLE_ID ?? '', + STRIPE_PUBLISHABLE_KEY: process.env.STRIPE_PUBLISHABLE_KEY ?? '', + DEAL_TRACKER_DID: process.env.DEAL_TRACKER_DID ?? '', + DEAL_TRACKER_URL: process.env.DEAL_TRACKER_URL ?? '', + CONTENT_CLAIMS_DID, + CONTENT_CLAIMS_URL, + CONTENT_CLAIMS_PROOF, + HOSTED_ZONE: hostedZone ?? '', + }, + bind: [ + privateKey, + ucanInvocationPostbasicAuth, + stripeSecretKey, + contentClaimsPrivateKey + ] + } + }, + routes: { + 'POST /': 'upload-api/functions/ucan-invocation-router.handler', + 'POST /ucan': 'upload-api/functions/ucan.handler', + 'POST /bridge': 'upload-api/functions/bridge.handler', + 'GET /': 'upload-api/functions/get.home', + 'GET /validate-email': 'upload-api/functions/validate-email.preValidateEmail', + 'POST /validate-email': 'upload-api/functions/validate-email.validateEmail', + 'GET /error': 'upload-api/functions/get.error', + 'GET /version': 'upload-api/functions/get.version', + 'GET /metrics': 'upload-api/functions/metrics.handler', + 'GET /receipt/{taskCid}': 'upload-api/functions/receipt.handler', + 'GET /storefront-cron': 'upload-api/functions/storefront-cron.handler', + // AWS API Gateway does not know trailing slash... and Grafana Agent puts trailing slash + 'GET /metrics/{proxy+}': 'upload-api/functions/metrics.handler', + }, + accessLog: { + format:'{"requestTime":"$context.requestTime","requestId":"$context.requestId","httpMethod":"$context.httpMethod","path":"$context.path","routeKey":"$context.routeKey","status":$context.status,"responseLatency":$context.responseLatency,"integrationRequestId":"$context.integration.requestId","integrationStatus":"$context.integration.status","integrationLatency":"$context.integration.latency","integrationServiceStatus":"$context.integration.integrationStatus","ip":"$context.identity.sourceIp","userAgent":"$context.identity.userAgent"}' } - }, - routes: { - 'POST /': 'upload-api/functions/ucan-invocation-router.handler', - 'POST /ucan': 'upload-api/functions/ucan.handler', - 'POST /bridge': 'upload-api/functions/bridge.handler', - 'GET /': 'upload-api/functions/get.home', - 'GET /validate-email': 'upload-api/functions/validate-email.preValidateEmail', - 'POST /validate-email': 'upload-api/functions/validate-email.validateEmail', - 'GET /error': 'upload-api/functions/get.error', - 'GET /version': 'upload-api/functions/get.version', - 'GET /metrics': 'upload-api/functions/metrics.handler', - 'GET /receipt/{taskCid}': 'upload-api/functions/receipt.handler', - 'GET /storefront-cron': 'upload-api/functions/storefront-cron.handler', - // AWS API Gateway does not know trailing slash... and Grafana Agent puts trailing slash - 'GET /metrics/{proxy+}': 'upload-api/functions/metrics.handler', - }, - accessLog: { - format:'{"requestTime":"$context.requestTime","requestId":"$context.requestId","httpMethod":"$context.httpMethod","path":"$context.path","routeKey":"$context.routeKey","status":$context.status,"responseLatency":$context.responseLatency,"integrationRequestId":"$context.integration.requestId","integrationStatus":"$context.integration.status","integrationLatency":"$context.integration.latency","integrationServiceStatus":"$context.integration.integrationStatus","ip":"$context.identity.sourceIp","userAgent":"$context.identity.userAgent"}' - } + }); }) // UCAN stream metrics for admin and space @@ -219,7 +237,9 @@ export function UploadApiStack({ stack, app }) { }) stack.addOutputs({ - ApiEndpoint: api.url, - CustomDomain: customDomain ? `https://${customDomain.domainName}` : 'Set HOSTED_ZONE in env to deploy to a custom domain' + ApiEndpoints: JSON.stringify(apis.map(api => api.url)), + CustomDomains: customDomains + ? JSON.stringify(customDomains.map(customDomain => `https://${customDomain.domainName}`)) + : 'Set HOSTED_ZONES in env to deploy to a custom domain', }) } diff --git a/test/helpers/deployment.js b/test/helpers/deployment.js index fc8dce4c..0e5095d4 100644 --- a/test/helpers/deployment.js +++ b/test/helpers/deployment.js @@ -49,7 +49,7 @@ export const getApiEndpoint = () => { // Get Upload API endpoint const id = 'UploadApiStack' - return testEnv[`${getStackName()}-${id}`].ApiEndpoint + return JSON.parse(testEnv[`${getStackName()}-${id}`].ApiEndpoints)[0] } export const getRoundaboutEndpoint = () => { @@ -71,21 +71,7 @@ export const getRoundaboutEndpoint = () => { } export const getReceiptsEndpoint = () => { - // CI/CD deployment - if (process.env.SEED_APP_NAME) { - const stage = getStage() - return `https://${stage}.up.web3.storage/receipt/` - } - - const require = createRequire(import.meta.url) - const testEnv = require(path.join( - process.cwd(), - '.sst/outputs.json' - )) - - // Get Upload API endpoint - const id = 'UploadApiStack' - return `${testEnv[`${getStackName()}-${id}`].ApiEndpoint}/receipt/` + return `${getApiEndpoint()}/receipt/` } export const getCarparkBucketInfo = () => { diff --git a/tsconfig.json b/tsconfig.json index 05cd9117..fe4a6e27 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,8 +33,19 @@ "forceConsistentCasingInFileNames": true, "skipLibCheck": true, "stripInternal": true, - "resolveJsonModule": true, + "resolveJsonModule": true }, - "include": [ "billing", "indexer", "upload-api", "stacks", "carpark", "replicator", "ucan-invocation", "filecoin", "tools", "lib" ], - "exclude": [ "billing/coverage" ] -} \ No newline at end of file + "include": [ + "billing", + "indexer", + "upload-api", + "stacks", + "carpark", + "replicator", + "ucan-invocation", + "filecoin", + "tools", + "lib" + ], + "exclude": ["billing/coverage", "upload-api/dist"] +} diff --git a/upload-api/email.js b/upload-api/email.js index dd34d6f7..785251fd 100644 --- a/upload-api/email.js +++ b/upload-api/email.js @@ -12,16 +12,21 @@ export const configure = (opts) => new Email(opts) export class Email { /** * @param {object} opts - * @param {string} opts.token - * @param {string} [opts.sender] + * @param {string} opts.token - Postmark server token + * @param {string} [opts.sender] - Sender email mailbox (`Display Name + * `) + * @param {string} [opts.environment] - Environment/stage name to display in + * the email subject. Omit to show none + * (ie, production). */ constructor(opts) { - this.sender = opts.sender || 'web3.storage ' + this.sender = opts.sender this.headers = { Accept: 'text/json', 'Content-Type': 'text/json', 'X-Postmark-Server-Token': opts.token, } + this.environment = opts.environment } /** @@ -29,20 +34,35 @@ export class Email { * * @param {ValidationEmailSend} opts */ - async sendValidation (opts) { + async sendValidation(opts) { + const { hostname } = new URL(opts.url) + const emailParams = hostname.endsWith('web3.storage') + ? { + From: this.sender || 'web3.storage ', + TemplateAlias: 'welcome', + TemplateModel: { + product_url: 'https://web3.storage', + product_name: 'Web3 Storage', + email: opts.to, + action_url: opts.url, + }, + } + : { + From: this.sender || 'Storacha ', + TemplateAlias: 'welcome-storacha', + TemplateModel: { + email: opts.to, + action_url: opts.url, + environment_name: this.environment, + }, + } + const rsp = await fetch('https://api.postmarkapp.com/email/withTemplate', { method: 'POST', headers: this.headers, body: JSON.stringify({ - From: this.sender, To: opts.to, - TemplateAlias: 'welcome', - TemplateModel: { - product_url: 'https://web3.storage', - product_name: 'Web3 Storage', - email: opts.to, - action_url: opts.url, - }, + ...emailParams, }), }) diff --git a/upload-api/functions/ucan-invocation-router.js b/upload-api/functions/ucan-invocation-router.js index ec3651f5..16a3a6f6 100644 --- a/upload-api/functions/ucan-invocation-router.js +++ b/upload-api/functions/ucan-invocation-router.js @@ -130,6 +130,7 @@ export async function ucanInvocationRouter(request) { carparkBucketSecretAccessKey, blockAdvertisementPublisherQueueConfig, blockIndexWriterQueueConfig, + sstStage } = getLambdaEnv() if (request.body === undefined) { @@ -265,7 +266,7 @@ export async function ucanInvocationRouter(request) { signer: serviceSigner, // TODO: we should set URL from a different env var, doing this for now to avoid that refactor - tracking in https://github.com/web3-storage/w3infra/issues/209 url: new URL(accessServiceURL), - email: new Email({ token: postmarkToken }), + email: new Email({ token: postmarkToken, environment: sstStage === 'prod' ? undefined : sstStage, }), agentStore, provisionsStorage, subscriptionsStorage, @@ -375,6 +376,7 @@ function getLambdaEnv () { url: new URL(mustGetEnv('BLOCK_INDEX_WRITER_QUEUE_URL')), region: AWS_REGION }, + sstStage: mustGetEnv('SST_STAGE'), // set for testing dbEndpoint: process.env.DYNAMO_DB_ENDPOINT, } diff --git a/upload-api/functions/validate-email.jsx b/upload-api/functions/validate-email.jsx index bc9866dd..2a4066d1 100644 --- a/upload-api/functions/validate-email.jsx +++ b/upload-api/functions/validate-email.jsx @@ -13,16 +13,14 @@ import { createConsumerTable } from '../tables/consumer.js' import { createRevocationsTable } from '../stores/revocations.js' import * as AgentStore from '../stores/agent.js' import { useProvisionStore } from '../stores/provisions.js' -import { - HtmlResponse, - ValidateEmail, - ValidateEmailError, - PendingValidateEmail, -} from '../html.jsx' +import * as htmlStoracha from '../html-storacha' +import * as htmlW3s from '../html-w3s' import { createRateLimitTable } from '../tables/rate-limit.js' import { createSpaceMetricsTable } from '../tables/space-metrics.js' import { createCustomerStore } from '@web3-storage/w3infra-billing/tables/customer' +const html = process.env.HOSTED_ZONE === 'up.web3.storage' ? htmlW3s : htmlStoracha + Sentry.AWSLambda.init({ environment: process.env.SST_STAGE, dsn: process.env.SENTRY_DSN, @@ -30,9 +28,9 @@ Sentry.AWSLambda.init({ }) /** - * @param {HtmlResponse} response + * @param {htmlStoracha.HtmlResponse} response */ -export function toLambdaResponse (response) { +export function toLambdaResponse(response) { const { status = 200, headers: responseHeaders, body } = response // translate headers from Response format to Lambda format /** @type {Record} */ @@ -44,7 +42,7 @@ export function toLambdaResponse (response) { statusCode: status, headers, body: body && Buffer.from(response.getStringBody()).toString('base64'), - isBase64Encoded: true + isBase64Encoded: true, } } @@ -53,19 +51,24 @@ export function toLambdaResponse (response) { * * @param {import('aws-lambda').APIGatewayProxyEventV2} request */ -export async function validateEmailGet (request) { +export async function validateEmailGet(request) { + request.requestContext.domainName if (!request.queryStringParameters?.ucan) { - return toLambdaResponse(new HtmlResponse( - - )) + return toLambdaResponse( + new html.HtmlResponse( + + ) + ) } - return toLambdaResponse(new HtmlResponse()) + return toLambdaResponse( + new html.HtmlResponse() + ) } export const preValidateEmail = Sentry.AWSLambda.wrapHandler(validateEmailGet) -function createAuthorizeContext () { +function createAuthorizeContext() { const { ACCESS_SERVICE_URL = '', AWS_REGION = '', @@ -88,6 +91,7 @@ function createAuthorizeContext () { STRIPE_PRICING_TABLE_ID = '', STRIPE_PUBLISHABLE_KEY = '', UCAN_LOG_STREAM_NAME = '', + SST_STAGE = '', // set for testing DYNAMO_DB_ENDPOINT: dbEndpoint, } = process.env @@ -97,21 +101,36 @@ function createAuthorizeContext () { INVOCATION_BUCKET_NAME ) const workflowBucket = createWorkflowStore(AWS_REGION, WORKFLOW_BUCKET_NAME) - const delegationBucket = createDelegationsStore(R2_ENDPOINT, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_DELEGATION_BUCKET_NAME) - const subscriptionTable = createSubscriptionTable(AWS_REGION, SUBSCRIPTION_TABLE_NAME, { - endpoint: dbEndpoint - }); + const delegationBucket = createDelegationsStore( + R2_ENDPOINT, + R2_ACCESS_KEY_ID, + R2_SECRET_ACCESS_KEY, + R2_DELEGATION_BUCKET_NAME + ) + const subscriptionTable = createSubscriptionTable( + AWS_REGION, + SUBSCRIPTION_TABLE_NAME, + { + endpoint: dbEndpoint, + } + ) const consumerTable = createConsumerTable(AWS_REGION, CONSUMER_TABLE_NAME, { - endpoint: dbEndpoint - }); - const customerStore = createCustomerStore({ region: AWS_REGION }, { tableName: CUSTOMER_TABLE_NAME }) - const spaceMetricsTable = createSpaceMetricsTable(AWS_REGION, SPACE_METRICS_TABLE_NAME) + endpoint: dbEndpoint, + }) + const customerStore = createCustomerStore( + { region: AWS_REGION }, + { tableName: CUSTOMER_TABLE_NAME } + ) + const spaceMetricsTable = createSpaceMetricsTable( + AWS_REGION, + SPACE_METRICS_TABLE_NAME + ) const agentStore = AgentStore.open({ store: { connection: { address: { - region: AWS_REGION + region: AWS_REGION, }, }, region: AWS_REGION, @@ -129,11 +148,26 @@ function createAuthorizeContext () { return { // TODO: we should set URL from a different env var, doing this for now to avoid that refactor url: new URL(ACCESS_SERVICE_URL), - email: new Email({ token: POSTMARK_TOKEN }), + email: new Email({ + token: POSTMARK_TOKEN, + environment: SST_STAGE === 'prod' ? undefined : SST_STAGE, + }), signer: getServiceSigner({ did: UPLOAD_API_DID, privateKey: PRIVATE_KEY }), - delegationsStorage: createDelegationsTable(AWS_REGION, DELEGATION_TABLE_NAME, { bucket: delegationBucket, invocationBucket, workflowBucket }), - revocationsStorage: createRevocationsTable(AWS_REGION, REVOCATION_TABLE_NAME), - provisionsStorage: useProvisionStore(subscriptionTable, consumerTable, spaceMetricsTable, parseServiceDids(PROVIDERS)), + delegationsStorage: createDelegationsTable( + AWS_REGION, + DELEGATION_TABLE_NAME, + { bucket: delegationBucket, invocationBucket, workflowBucket } + ), + revocationsStorage: createRevocationsTable( + AWS_REGION, + REVOCATION_TABLE_NAME + ), + provisionsStorage: useProvisionStore( + subscriptionTable, + consumerTable, + spaceMetricsTable, + parseServiceDids(PROVIDERS) + ), rateLimitsStorage: createRateLimitTable(AWS_REGION, RATE_LIMIT_TABLE_NAME), customerStore, agentStore, @@ -147,42 +181,56 @@ function createAuthorizeContext () { * * @param {import('aws-lambda').APIGatewayProxyEventV2} request */ -export async function validateEmailPost (request) { +export async function validateEmailPost(request) { const encodedUcan = request.queryStringParameters?.ucan if (!encodedUcan) { - return toLambdaResponse(new HtmlResponse( - - )) + return toLambdaResponse( + new html.HtmlResponse( + + ) + ) } const context = createAuthorizeContext() const authorizeResult = await authorize(encodedUcan, context) if (authorizeResult.error) { console.error(authorizeResult.error) - return toLambdaResponse(new HtmlResponse( - , - { status: 500 } - )) + return toLambdaResponse( + new html.HtmlResponse( + ( + + ), + { status: 500 } + ) + ) } const { email, audience, ucan } = authorizeResult.ok - const planCheckResult = await context.customerStore.get({ customer: DidMailto.fromEmail(email) }) + const planCheckResult = await context.customerStore.get({ + customer: DidMailto.fromEmail(email), + }) let stripePricingTableId let stripePublishableKey - if (!planCheckResult.ok?.product){ + if (!planCheckResult.ok?.product) { stripePricingTableId = context.stripePricingTableId stripePublishableKey = context.stripePublishableKey } - return toLambdaResponse(new HtmlResponse( - - )) + return toLambdaResponse( + new html.HtmlResponse( + ( + + ) + ) + ) } export const validateEmail = Sentry.AWSLambda.wrapHandler(validateEmailPost) diff --git a/upload-api/html-storacha/index.jsx b/upload-api/html-storacha/index.jsx new file mode 100644 index 00000000..dd72ae1c --- /dev/null +++ b/upload-api/html-storacha/index.jsx @@ -0,0 +1,273 @@ +// @jsxImportSource preact +import { render } from 'preact-render-to-string' +import { Response } from '@web-std/fetch' +import storachaLogoSvg from './storacha-logo.svg' + +/** + * Dev changes quickly without deploying to AWS! + * Use test/helpers/validate-email-server.js + */ + +/** + * Build HTML document + * + * @param {string} body + */ +export function buildDocument(body) { + return /* html */ ` + + + + + Storacha Email Validation + + + + + + + + + ${body} + +` +} + +export class HtmlResponse extends Response { + /** + * + * @param {import('preact').VNode<{}>} body + * @param {ResponseInit} [init] + */ + constructor(body, init = {}) { + const headers = { + headers: { + 'content-type': 'text/html; charset=utf-8', + }, + } + const stringBody = buildDocument(render(body)) + super(stringBody, { ...init, ...headers }) + this.stringBody = stringBody + } + + getStringBody() { + return this.stringBody + } + + /** + * @param {import('preact').VNode<{}>} body + * @param {ResponseInit} [init] + */ + static respond(body, init = {}) { + return new HtmlResponse(body, init) + } +} + +/** + * + * @param {object} props + * @param {boolean} [props.autoApprove] + */ +export const PendingValidateEmail = ({ autoApprove }) => ( + <> +
+
+
+

Validating Email

+
+ +
+ {autoApprove ? ( + ' + ) +} + +const app = express() +app.set('port', process.env.PORT || 9000) + +const indexPage = /* html */ ` + + + + ${Object.entries(HTMLS) + .map( + ([htmlName, _html]) => /* html */ ` +

${htmlName}:

+
    + ${COMPONENTS.map( + (componentName) => + /* html */ `
  • ${componentName}
  • ` + ).join('')} +
+ ` + ) + .join('')} + + +` +app.get('/', function (req, res) { + res.write(insertReloadScript(indexPage)) res.end() -}).listen(9000) +}) + +/** + * Generate a random string + * + * @param {number} n + */ +const randomString = (n) => + [...Array(n)].map(() => Math.random().toString(36)[2]).join('') + +Object.entries(HTMLS).forEach(([htmlName, html]) => { + COMPONENTS.forEach((componentName) => { + app.get(`/${htmlName}/${componentName}`, function (req, res) { + const vnode = html[componentName]({ + ucan: randomString(1000), + email: 'test@example.org', + audience: `did:key:${randomString(400)}`, + stripePricingTableId: process.env.STRIPE_PRICING_TABLE_ID, + stripePublishableKey: process.env.STRIPE_PUBLISHABLE_KEY, + msg: 'Missing delegation in the URL, or some such problem.', + autoApprove: false, + qrcode: '
QR code goes here
', + }) + res.write( + html + .buildDocument(render(vnode)) + .replace( + /<\/body>/, + '' + ) + ) + res.end() + }) + }) +}) + +const server = http.createServer(app) + +try { + await reload(app) -console.log('http://127.0.0.1:9000') + // Reload started, start web server + server.listen(app.get('port'), function () { + console.log(`Dev server listening at: http://localhost:${app.get('port')}/`) + }) +} catch (err) { + console.error( + 'Reload could not start, could not start server/sample app', + err + ) +} From 64ddde38298854044401b63953809e249361476d Mon Sep 17 00:00:00 2001 From: Hannah Howard Date: Thu, 19 Sep 2024 14:56:59 -0700 Subject: [PATCH 4/6] fix(upload-api-stack): don't add domain to apiID on first zone (#420) # Goals resolve https://console.seed.run/dag-house/w3infra/activity/stages/staging/builds/386/services/upload-api Essentially cause the up.web3.storage domain apiID changes, it tries to recreate the domain record, and it can't. So the solution is don't change the API ID for the web3.storage domain. # Implementation In order to match the existing deployment, this PR will not add the domain to the first API ID for the first hosted zone. --------- Co-authored-by: Travis Vachon --- stacks/upload-api-stack.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/stacks/upload-api-stack.js b/stacks/upload-api-stack.js index 3e0a98a4..796fe407 100644 --- a/stacks/upload-api-stack.js +++ b/stacks/upload-api-stack.js @@ -55,9 +55,10 @@ export function UploadApiStack({ stack, app }) { const git = getGitInfo() const ucanInvocationPostbasicAuth = new Config.Secret(stack, 'UCAN_INVOCATION_POST_BASIC_AUTH') - const apis = (customDomains ?? [undefined]).map((customDomain) => { + const apis = (customDomains ?? [undefined]).map((customDomain, idx) => { const hostedZone = customDomain?.hostedZone - const apiId = [`http-gateway`, hostedZone?.replaceAll('.', '_')] + // the first customDomain will be web3.storage, and we don't want the apiId for that domain to have a second part, see PR of this change for context + const apiId = [`http-gateway`, idx > 0 ? hostedZone?.replaceAll('.', '_') : ''] .filter(Boolean) .join('-') return new Api(stack, apiId, { From 8bd90cfc11ef6f21f3c915fb61e29c512a69cfbf Mon Sep 17 00:00:00 2001 From: Hannah Howard Date: Thu, 19 Sep 2024 17:44:09 -0700 Subject: [PATCH 5/6] fix(upload-api): make text color explicit (#421) # Goals My storacha branded email validation page is rendering body text white and unreadable, cause it's just the browser default. Fix this # Implementation set text color for body text on storacha branded email validation page to black, rather than relying on browser default color --- upload-api/html-storacha/index.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/upload-api/html-storacha/index.jsx b/upload-api/html-storacha/index.jsx index dd72ae1c..24e141ef 100644 --- a/upload-api/html-storacha/index.jsx +++ b/upload-api/html-storacha/index.jsx @@ -36,6 +36,7 @@ export function buildDocument(body) { } body { + color: black; background-color: var(--hot-red-light); font-family: 'Epilogue', sans-serif; max-width: 70rem; From 12fdbe9f4bd56f086c46f49bbc31cc0e2a1189e5 Mon Sep 17 00:00:00 2001 From: Hannah Howard Date: Fri, 20 Sep 2024 09:45:25 -0700 Subject: [PATCH 6/6] fix(config): don't force access service url (#423) # Goals use access service url from custom domain, even in prod # Implementation Now that we have two domains for prod, we need to use the custom domain rather than just forcing `up.web3.storage` This issue only applies to prod which is why it didn't come up in previous testing. --- stacks/config.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/stacks/config.js b/stacks/config.js index 5c64f3ad..aab1e390 100644 --- a/stacks/config.js +++ b/stacks/config.js @@ -178,16 +178,7 @@ export function setupSentry (app, stack) { * @param {ApiDomainProps | undefined} customDomain */ export function getServiceURL (stack, customDomain) { - // in production we use the top level subdomain - if (stack.stage === 'prod') { - return 'https://up.web3.storage' - // Derive from custom domain if there is one, which is used in staging, PR envs and dev envs - } else if (customDomain) { - return `https://${customDomain.domainName}` - // everywhere else we use something more estoteric - usually an AWS Lambda URL - } else { - return process.env.ACCESS_SERVICE_URL - } + return customDomain ? `https://${customDomain.domainName}` : process.env.ACCESS_SERVICE_URL } /**