From 09a59c6d0590493b660b5a6c83ebc6829d52e733 Mon Sep 17 00:00:00 2001 From: Benjamin Goering <171782+gobengo@users.noreply.github.com> Date: Mon, 13 Nov 2023 09:31:36 -0800 Subject: [PATCH] feat: can disable new user registration at /user/login API around w3up launch by setting `NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START` (#2324) Motivation: * https://github.com/web3-storage/secrets/issues/22 What * now that some stuff is needed as common between the two packages for api and website, I moved the `w3up-launch` module into a new package that both can depend on `@web3-storage/w3up-launch` * `@web3-storage/api` `loginOrRegister` reads env.`NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START` and if its after that, will throw an error when it would otherwise create a new user. * website 'callback' page from magic link detects the error from POST /user/login indicating this and redirects user to `/login/signups-closed/try-w3up/` --- .github/workflows/api.yml | 8 + .github/workflows/website.yml | 2 + package-lock.json | 741 +++++++++++++----- package.json | 1 + packages/api/package.json | 1 + packages/api/src/env.js | 3 + packages/api/src/errors.js | 24 + packages/api/src/magic.link.js | 31 +- packages/api/src/user.js | 41 +- packages/api/test/hooks.js | 61 +- packages/api/test/user.spec.js | 43 + packages/api/tsconfig.json | 3 + packages/w3up-launch/.gitignore | 1 + packages/w3up-launch/package.json | 23 + packages/w3up-launch/src/index.js | 92 +++ packages/w3up-launch/tsconfig.json | 32 + .../components/contexts/w3upLaunchContext.js | 15 + packages/website/components/w3up-launch.js | 57 +- packages/website/lib/magic.js | 22 +- packages/website/modules/docs-theme/index.js | 11 +- packages/website/package.json | 3 +- packages/website/pages/_app.js | 9 +- packages/website/pages/_document.js | 16 +- packages/website/pages/account/index.js | 12 +- packages/website/pages/account/payment.js | 8 +- packages/website/pages/callback/index.js | 19 +- packages/website/pages/index.js | 12 +- .../pages/login/signups-closed/try-w3up.js | 44 ++ packages/website/tsconfig.json | 9 + 29 files changed, 1071 insertions(+), 273 deletions(-) create mode 100644 packages/w3up-launch/.gitignore create mode 100644 packages/w3up-launch/package.json create mode 100644 packages/w3up-launch/src/index.js create mode 100644 packages/w3up-launch/tsconfig.json create mode 100644 packages/website/components/contexts/w3upLaunchContext.js create mode 100644 packages/website/pages/login/signups-closed/try-w3up.js diff --git a/.github/workflows/api.yml b/.github/workflows/api.yml index e3dad45e7e..c146fdb1b9 100644 --- a/.github/workflows/api.yml +++ b/.github/workflows/api.yml @@ -55,6 +55,8 @@ jobs: if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest needs: test + environment: + name: staging steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 @@ -67,6 +69,9 @@ jobs: ENV: 'staging' # inform the build process what the env is SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN}} SENTRY_UPLOAD: ${{ secrets.SENTRY_UPLOAD}} + NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_ANNOUNCEMENT_START: ${{ vars.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_ANNOUNCEMENT_START }} + NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START: ${{ vars.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START }} + NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START: ${{ vars.NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START }} with: apiToken: ${{ secrets.CF_TOKEN }} workingDirectory: 'packages/api' @@ -100,6 +105,9 @@ jobs: ENV: 'production' # inform the build process what the env is SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN}} SENTRY_UPLOAD: ${{ secrets.SENTRY_UPLOAD}} + NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_ANNOUNCEMENT_START: ${{ vars.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_ANNOUNCEMENT_START }} + NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START: ${{ vars.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START }} + NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START: ${{ vars.NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START }} with: apiToken: ${{ secrets.CF_TOKEN }} workingDirectory: 'packages/api' diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 19a1e47dcf..f558c3c926 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -201,6 +201,7 @@ jobs: NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: ${{ secrets.TESTING_STRIPE_PUBLISHABLE_KEY }} NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_ANNOUNCEMENT_START: ${{ vars.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_ANNOUNCEMENT_START }} NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START: ${{ vars.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START }} + NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START: ${{ vars.NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START }} DID_DOCUMENT_ID: ${{ secrets.STAGING_DID_DOCUMENT_ID }} DID_DOCUMENT_PRIMARY_DID_KEY: ${{ secrets.STAGING_DID_DOCUMENT_PRIMARY_DID_KEY }} - name: Add to web3.storage @@ -298,6 +299,7 @@ jobs: NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: ${{ secrets.STRIPE_PUBLISHABLE_KEY }} NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_ANNOUNCEMENT_START: ${{ vars.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_ANNOUNCEMENT_START }} NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START: ${{ vars.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START }} + NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START: ${{ vars.NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START }} DID_DOCUMENT_ID: ${{ secrets.PRODUCTION_DID_DOCUMENT_ID }} DID_DOCUMENT_PRIMARY_DID_KEY: ${{ secrets.PRODUCTION_DID_DOCUMENT_PRIMARY_DID_KEY }} - name: Add to web3.storage diff --git a/package-lock.json b/package-lock.json index d4b5bdfc97..b769105abd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "packages/cron", "packages/db", "packages/w3", + "packages/w3up-launch", "packages/website", "packages/tools" ], @@ -29,6 +30,14 @@ "npm": ">=7.x" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@algolia/autocomplete-core": { "version": "1.5.2", "license": "MIT", @@ -5579,6 +5588,10 @@ "resolved": "packages/w3", "link": true }, + "node_modules/@web3-storage/w3up-launch": { + "resolved": "packages/w3up-launch", + "link": true + }, "node_modules/@web3-storage/website": { "resolved": "packages/website", "link": true @@ -6051,18 +6064,31 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "license": "MIT" }, "node_modules/array-includes": { - "version": "3.1.4", - "license": "MIT", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", "is-string": "^1.0.7" }, "engines": { @@ -6097,12 +6123,14 @@ } }, "node_modules/array.prototype.flat": { - "version": "1.2.5", - "license": "MIT", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -6112,12 +6140,34 @@ } }, "node_modules/array.prototype.flatmap": { - "version": "1.2.5", - "license": "MIT", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6892,11 +6942,13 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "license": "MIT", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", "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" @@ -8566,6 +8618,19 @@ "clone": "^1.0.2" } }, + "node_modules/define-data-property": { + "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==", + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/define-lazy-prop": { "version": "2.0.0", "license": "MIT", @@ -8574,13 +8639,19 @@ } }, "node_modules/define-properties": { - "version": "1.1.3", - "license": "MIT", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dependencies": { - "object-keys": "^1.0.12" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/defined": { @@ -9239,29 +9310,49 @@ } }, "node_modules/es-abstract": { - "version": "1.19.1", - "license": "MIT", + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", + "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", "dependencies": { - "call-bind": "^1.0.2", + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.5", + "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.2", "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "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", + "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", + "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", + "is-typed-array": "^1.1.12", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -9293,6 +9384,27 @@ "license": "MIT", "peer": true }, + "node_modules/es-set-tostringtag": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "dependencies": { + "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.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dependencies": { + "hasown": "^2.0.0" + } + }, "node_modules/es-to-primitive": { "version": "1.2.1", "license": "MIT", @@ -9525,11 +9637,13 @@ } }, "node_modules/eslint-import-resolver-node": { - "version": "0.3.6", - "license": "MIT", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dependencies": { "debug": "^3.2.7", - "resolve": "^1.20.0" + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" } }, "node_modules/eslint-import-resolver-node/node_modules/debug": { @@ -9539,15 +9653,36 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-import-resolver-node/node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/eslint-module-utils": { - "version": "2.7.3", - "license": "MIT", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", "dependencies": { - "debug": "^3.2.7", - "find-up": "^2.1.0" + "debug": "^3.2.7" }, "engines": { "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, "node_modules/eslint-module-utils/node_modules/debug": { @@ -9906,8 +10041,9 @@ } }, "node_modules/esquery": { - "version": "1.4.0", - "license": "BSD-3-Clause", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dependencies": { "estraverse": "^5.1.0" }, @@ -10693,16 +10829,11 @@ }, "node_modules/for-each": { "version": "0.3.3", - "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.1.3" } }, - "node_modules/foreach": { - "version": "2.0.5", - "license": "MIT" - }, "node_modules/foreground-child": { "version": "2.0.0", "dev": true, @@ -10911,13 +11042,42 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "license": "MIT" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "license": "MIT" }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gauge": { "version": "2.7.4", "dev": true, @@ -10992,12 +11152,14 @@ } }, "node_modules/get-intrinsic": { - "version": "1.1.1", - "license": "MIT", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11110,6 +11272,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globalyzer": { "version": "0.1.0", "dev": true, @@ -11145,6 +11321,17 @@ "dev": true, "license": "MIT" }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.1.15", "license": "ISC" @@ -11200,8 +11387,9 @@ } }, "node_modules/has-bigints": { - "version": "1.0.1", - "license": "MIT", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11225,6 +11413,28 @@ "node": ">=4" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbol-support-x": { "version": "1.4.2", "license": "MIT", @@ -11233,8 +11443,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.2", - "license": "MIT", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "engines": { "node": ">= 0.4" }, @@ -11304,6 +11515,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hast-util-parse-selector": { "version": "2.2.5", "license": "MIT", @@ -11678,11 +11900,12 @@ "license": "(Apache-2.0 OR MIT)" }, "node_modules/internal-slot": { - "version": "1.0.3", - "license": "MIT", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", "side-channel": "^1.0.4" }, "engines": { @@ -12253,6 +12476,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "license": "MIT" @@ -12314,8 +12550,9 @@ } }, "node_modules/is-callable": { - "version": "1.2.4", - "license": "MIT", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "engines": { "node": ">= 0.4" }, @@ -12324,10 +12561,11 @@ } }, "node_modules/is-core-module": { - "version": "2.8.1", - "license": "MIT", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -12604,8 +12842,12 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.1", - "license": "MIT", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dependencies": { + "call-bind": "^1.0.2" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -12648,14 +12890,11 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.8", - "license": "MIT", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.18.5", - "foreach": "^2.0.5", - "has-tostringtag": "^1.0.0" + "which-typed-array": "^1.1.11" }, "engines": { "node": ">= 0.4" @@ -12728,7 +12967,6 @@ }, "node_modules/isarray": { "version": "2.0.5", - "dev": true, "license": "MIT" }, "node_modules/isexe": { @@ -13150,8 +13388,9 @@ "license": "MIT" }, "node_modules/json5": { - "version": "1.0.1", - "license": "MIT", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dependencies": { "minimist": "^1.2.0" }, @@ -15749,8 +15988,12 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "license": "MIT" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/minimist-options": { "version": "4.1.0", @@ -17350,8 +17593,9 @@ } }, "node_modules/object-inspect": { - "version": "1.12.2", - "license": "MIT", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -17379,12 +17623,13 @@ } }, "node_modules/object.assign": { - "version": "4.1.2", - "license": "MIT", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", "object-keys": "^1.1.1" }, "engines": { @@ -17395,24 +17640,26 @@ } }, "node_modules/object.entries": { - "version": "1.1.5", - "license": "MIT", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", + "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/object.fromentries": { - "version": "2.0.5", - "license": "MIT", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" @@ -17422,23 +17669,25 @@ } }, "node_modules/object.hasown": { - "version": "1.1.0", - "license": "MIT", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", + "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object.values": { - "version": "1.1.5", - "license": "MIT", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" @@ -17852,15 +18101,16 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "license": "MIT", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -19598,11 +19848,13 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.4.1", - "license": "MIT", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" }, "engines": { "node": ">= 0.4" @@ -20252,6 +20504,23 @@ "version": "3.0.0", "license": "Apache-2.0" }, + "node_modules/safe-array-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", + "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "funding": [ @@ -20270,6 +20539,19 @@ ], "license": "MIT" }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "license": "MIT" @@ -20518,6 +20800,33 @@ "dev": true, "license": "MIT" }, + "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==", + "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", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "license": "MIT" @@ -21106,6 +21415,8 @@ }, "node_modules/standard": { "version": "16.0.4", + "resolved": "https://registry.npmjs.org/standard/-/standard-16.0.4.tgz", + "integrity": "sha512-2AGI874RNClW4xUdM+bg1LRXVlYLzTNEkHmTG5mhyn45OhbgwA+6znowkOGYy+WMb5HRyELvtNy39kcdMQMcYQ==", "dev": true, "funding": [ { @@ -21121,7 +21432,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "eslint": "~7.18.0", "eslint-config-standard": "16.0.3", @@ -21262,16 +21572,18 @@ } }, "node_modules/string.prototype.matchall": { - "version": "4.0.6", - "license": "MIT", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", + "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.3.1", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "regexp.prototype.flags": "^1.5.0", + "set-function-name": "^2.0.0", "side-channel": "^1.0.4" }, "funding": { @@ -21295,13 +21607,13 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.5", - "dev": true, - "license": "MIT", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" @@ -21311,22 +21623,26 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.4", - "license": "MIT", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "license": "MIT", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -22434,12 +22750,13 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.12.0", - "license": "MIT", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", "dependencies": { "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.0", + "json5": "^1.0.2", + "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, @@ -22512,6 +22829,67 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typedarray": { "version": "0.0.6", "license": "MIT" @@ -22605,12 +22983,13 @@ } }, "node_modules/unbox-primitive": { - "version": "1.0.1", - "license": "MIT", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" }, "funding": { @@ -23384,15 +23763,15 @@ "license": "ISC" }, "node_modules/which-typed-array": { - "version": "1.1.7", - "license": "MIT", + "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==", "dependencies": { "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.18.5", - "foreach": "^2.0.5", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.7" + "call-bind": "^1.0.4", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -23420,13 +23799,6 @@ "node": ">= 0.10.0" } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/workerpool": { "version": "6.1.5", "dev": true, @@ -23847,6 +24219,7 @@ "@web3-storage/content-claims": "^3.0.1", "@web3-storage/db": "^4.0.0", "@web3-storage/multipart-parser": "^1.0.0", + "@web3-storage/w3up-launch": "^1.0.0", "cardex": "^2.3.1", "carstream": "^1.1.0", "cborg": "^1.6.0", @@ -26648,9 +27021,34 @@ "node": ">=10" } }, + "packages/w3up-launch": { + "name": "@web3-storage/w3up-launch", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "devDependencies": { + "typescript": "^4.6.0" + } + }, + "packages/w3up-launch/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" + } + }, "packages/website": { "name": "@web3-storage/website", - "version": "2.36.3", + "version": "2.37.0", "dependencies": { "@docsearch/react": "^3.0.0", "@fortawesome/free-brands-svg-icons": "^6.1.2", @@ -26660,6 +27058,7 @@ "@stripe/react-stripe-js": "^1.10.0", "@stripe/stripe-js": "^1.35.0", "@web3-storage/parse-link-header": "^3.1.0", + "@web3-storage/w3up-launch": "^1.0.0", "algoliasearch": "^4.13.0", "clsx": "^1.1.1", "countly-sdk-web": "^20.11.2", @@ -31963,28 +32362,6 @@ "node": ">=0.10.0" } }, - "packages/website/node_modules/tsconfig-paths": { - "version": "3.14.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" - } - }, - "packages/website/node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, "packages/website/node_modules/tty-browserify": { "version": "0.0.0", "license": "MIT", diff --git a/package.json b/package.json index 02e64013f9..ad2d478481 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "packages/cron", "packages/db", "packages/w3", + "packages/w3up-launch", "packages/website", "packages/tools" ], diff --git a/packages/api/package.json b/packages/api/package.json index d0398bb317..574c25c06b 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -75,6 +75,7 @@ "@web3-storage/car-block-validator": "^1.0.0", "@web3-storage/content-claims": "^3.0.1", "@web3-storage/db": "^4.0.0", + "@web3-storage/w3up-launch": "^1.0.0", "@web3-storage/multipart-parser": "^1.0.0", "cardex": "^2.3.1", "carstream": "^1.1.0", diff --git a/packages/api/src/env.js b/packages/api/src/env.js index 1b85f7c096..0b123300aa 100644 --- a/packages/api/src/env.js +++ b/packages/api/src/env.js @@ -54,6 +54,9 @@ import { Factory as ClaimFactory } from './utils/content-claims.js' * @property {string} [CONTENT_CLAIMS_PROOF] Proof of delegation. * @property {string} [CONTENT_CLAIMS_SERVICE_DID] DID of the content claims service. * @property {string} [CONTENT_CLAIMS_SERVICE_URL] URL of the content claims service. + * @property {string|undefined} [NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START] - when limited availability starts, e.g. no new user signups, cant change plan + * @property {string|undefined} [NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_ANNOUNCEMENT_START] - when announcement starts + * @property {string|undefined} [NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START] - when the product is actually sunset (after announcement) * // Derived values and class dependencies * @property {Cluster} cluster * @property {DBClient} db diff --git a/packages/api/src/errors.js b/packages/api/src/errors.js index 92308da69f..234418d88a 100644 --- a/packages/api/src/errors.js +++ b/packages/api/src/errors.js @@ -117,6 +117,30 @@ export class MagicTokenRequiredError extends HTTPError { } MagicTokenRequiredError.CODE = 'ERROR_MAGIC_TOKEN_REQUIRED' +/** + * Error indicating a new user signup was denied and probably will be indefinitely, + * and the user should try a new product instead. + */ +export class NewUserDeniedTryOtherProductError extends HTTPError { + /** + * @param {string} message + * @param {URL} otherProduct + */ + constructor (message, otherProduct) { + super(message, 403) + this.code = 'NEW_USER_DENIED_TRY_OTHER_PRODUCT' + this.otherProduct = otherProduct + } + + toJSON () { + return { + message: this.message, + code: this.code, + otherProduct: this.otherProduct.toString() + } + } +} + export class AgreementsRequiredError extends HTTPError { /** * @param {import("./utils/billing-types").Agreement[]} agreements diff --git a/packages/api/src/magic.link.js b/packages/api/src/magic.link.js index a0fdd4404d..9e58150718 100644 --- a/packages/api/src/magic.link.js +++ b/packages/api/src/magic.link.js @@ -46,9 +46,10 @@ function createMagicTestmodeBypasss () { return isMagicTestModeToken(token) }, authenticateMagicToken (env, token) { - const [, magicClaims] = env.magic.token.decode(token) + const [publicAddress, magicClaims] = env.magic.token.decode(token) return { - issuer: magicClaims.iss + issuer: magicClaims.iss, + publicAddress } } } @@ -68,3 +69,29 @@ function isMagicTestModeToken (token) { const isTestModeToken = [isTestModeAud(claims), isTestModeSub(claims)].every(Boolean) return isTestModeToken } + +/** + * create a token parseable as a magic.link test mode token + * @param {BigInt} publicAddress + * @param {*} claims + * @returns + */ +export function createMagicTestModeToken ( + publicAddress = BigInt(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)), + claims +) { + const tokenClaims = { + sub: 'TEST_MODE_USER_ID', + aud: 'TEST_MODE_CLIENT_ID', + iat: Date.now(), + ext: '?', + iss: '@web3-storage/api/test', + nbf: 1, + tid: 1, + add: 1, + ...claims + } + const tokenJson = JSON.stringify([`0x${publicAddress.toString(16)}`, JSON.stringify(tokenClaims)]) + const authToken = globalThis.btoa(tokenJson) + return authToken +} diff --git a/packages/api/src/user.js b/packages/api/src/user.js index 8a0ca2d734..178d8478fc 100644 --- a/packages/api/src/user.js +++ b/packages/api/src/user.js @@ -1,7 +1,7 @@ import * as JWT from './utils/jwt.js' import { JSONResponse, notFound } from './utils/json-response.js' import { JWT_ISSUER } from './constants.js' -import { HTTPError, PSAErrorInvalidData, PSAErrorRequiredData, PSAErrorResourceNotFound, RangeNotSatisfiableError } from './errors.js' +import { HTTPError, PSAErrorInvalidData, PSAErrorRequiredData, PSAErrorResourceNotFound, RangeNotSatisfiableError, NewUserDeniedTryOtherProductError } from './errors.js' import { getTagValue, hasPendingTagProposal, hasTag } from './utils/tags.js' import { NO_READ_OR_WRITE, @@ -14,6 +14,7 @@ import { toPinStatusResponse } from './pins.js' import { INVALID_REQUEST_ID, REQUIRED_REQUEST_ID, validateSearchParams } from './utils/psa.js' import { magicLinkBypassForE2ETestingInTestmode } from './magic.link.js' import { CustomerNotFound, getPaymentSettings, isStoragePriceName, savePaymentSettings } from './utils/billing.js' +import * as w3upLaunch from '@web3-storage/w3up-launch' /** * @typedef {string} UserId @@ -45,7 +46,9 @@ import { CustomerNotFound, getPaymentSettings, isStoragePriceName, savePaymentSe * @property {object} db * @property {import('./env').Env['db']['upsertUser']} db.upsertUser * @property {import('./env').Env['db']['getUser']} db.getUser - * @property {import('./env').Env['DANGEROUSLY_BYPASS_MAGIC_AUTH']} [DANGEROUSLY_BYPASS_MAGIC_AUTH] + * @property {import('./env').Env['NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START']} [NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START] + * @property {import('./env').Env['NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_ANNOUNCEMENT_START']} [NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_ANNOUNCEMENT_START] + * @property {import('./env').Env['NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START']} [NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START] * @property {RequestAuthenticator} authenticateRequest * @property {import('../src/utils/billing-types').CustomersService} customers * @property {import('../src/utils/billing-types').SubscriptionsService} subscriptions @@ -66,11 +69,11 @@ export async function userLoginPost (request, env) { */ function createMagicLoginController (env, testModeBypass = magicLinkBypassForE2ETestingInTestmode) { const createTestmodeMetadata = (token) => { - const { issuer } = testModeBypass.authenticateMagicToken(env, token) + const { issuer, publicAddress, email } = testModeBypass.authenticateMagicToken(env, token) return { issuer, - email: 'testMode@magic.link', - publicAddress: issuer + email: email || 'testMode@magic.link', + publicAddress: publicAddress || issuer } } /** @@ -114,7 +117,6 @@ const createMagicLinkRequestAuthenticator = (env) => async (request) => { */ async function loginOrRegister (request, env) { const data = await request.json() - const authenticateRequest = env.authenticateRequest || createMagicLinkRequestAuthenticator(env) const metadata = await authenticateRequest(request) if (!metadata) { @@ -130,19 +132,36 @@ async function loginOrRegister (request, env) { ? parseGitHub(data.data, metadata) : parseMagic(metadata) + const newUserRegistrationIsClosed = w3upLaunch.shouldBlockNewUserSignupsBecauseProductSunset(w3upLaunch.W3upLaunch.fromEnv(env)) + let user + + // will be true once w3up is launched and this product is sunset + if (newUserRegistrationIsClosed) { + const user = await env.db.getUser(parsed.issuer, {}) + if (!user) { + const otherProduct = new URL('https://console.web3.storage/') + throw new NewUserDeniedTryOtherProductError(`new user registration is closed. Try creating an account at ${otherProduct.toString()}`, otherProduct) + } + } + // check if maintenance mode if (env.MODE === NO_READ_OR_WRITE) { return maintenanceHandler() } else if (env.MODE === READ_WRITE) { - user = await env.db.upsertUser(parsed) - if (!user.inserted) { - await updateUserCustomerContact(env, user, parsed) + if (newUserRegistrationIsClosed) { + // dont upsert even though MODE allows, because we dont want to insert new users + user = user || await env.db.getUser(parsed.issuer, {}) + } else { + user = await env.db.upsertUser(parsed) + if (!user.inserted) { + await updateUserCustomerContact(env, user, parsed) + } } } else if (env.MODE === READ_ONLY) { - user = await env.db.getUser(parsed.issuer, {}) + user = user || await env.db.getUser(parsed.issuer, {}) } else { - throw new Error('Unknown maintenance mode') + throw new Error(`Unknown maintenance mode ${env.MODE}`) } return user diff --git a/packages/api/test/hooks.js b/packages/api/test/hooks.js index 1a0564f1ca..09ea2176a4 100644 --- a/packages/api/test/hooks.js +++ b/packages/api/test/hooks.js @@ -12,8 +12,10 @@ import { AuthorizationTestContext } from './contexts/authorization.js' import { Response } from '@web-std/fetch' import * as Claims from './scripts/content-claims.js' -// @ts-ignore -global.crypto = webcrypto +if (typeof globalThis.crypto === 'undefined') { + // @ts-expect-error webcrypto not a perfect match + globalThis.crypto = webcrypto +} // @ts-ignore globalThis.Response = Response @@ -126,3 +128,58 @@ export const mochaHooks = () => { } } } + +/** + * create a miniflare instance to run the api cf worker + */ +export function createApiMiniflare ({ initialBindings = workerGlobals, bindings = {}, port = 0 } = {}) { + return new Miniflare({ + // Autoload configuration from `.env`, `package.json` and `wrangler.toml` + envPath: true, + scriptPath: 'dist/worker.js', + packagePath: true, + wranglerConfigPath: true, + wranglerConfigEnv: 'test', + modules: true, + port, + bindings: { + ...initialBindings, + ...bindings + } + }) +} + +/** + * @param {import('http').Server} serverPromise + * @param {(server: import('http').Server) => Promise} withServerCb + */ +export function useServer (serverPromise, withServerCb) { + const use = async () => { + const server = await serverPromise + try { + await withServerCb(server) + } finally { + await closeServer(server) + } + } + return use() +} + +/** + * @param {import('http').Server} server + */ +export async function closeServer (server) { + return new Promise((resolve, reject) => { + server.close(error => error ? reject(error) : resolve(undefined)) + }) +} + +/** + * @param {import('http').Server} server + */ +export function getServerUrl (server) { + const address = server.address() + if (!address) { throw new Error('no address') } + if (typeof address !== 'object') { throw new Error(`unexpected address type ${address}`) } + return `http://localhost:${address.port}` +} diff --git a/packages/api/test/user.spec.js b/packages/api/test/user.spec.js index 28d351c38b..fe55764dd7 100644 --- a/packages/api/test/user.spec.js +++ b/packages/api/test/user.spec.js @@ -8,6 +8,8 @@ import { AuthorizationTestContext } from './contexts/authorization.js' import { userLoginPost } from '../src/user.js' import { Magic } from '@magic-sdk/admin' import { createMockCustomerService, createMockSubscriptionsService, createMockUserCustomerService } from '../src/utils/billing.js' +import { createMagicTestModeToken } from '../src/magic.link.js' +import { createApiMiniflare, useServer, getServerUrl } from './hooks.js' describe('GET /user/account', () => { it('error if not authenticated with magic.link', async () => { @@ -566,4 +568,45 @@ describe('userLoginPost', function () { assert.deepEqual(contact2.email, user1Authentication2.email, 'customer contact has email from authentication after second login') assert.deepEqual(contact2.name, githubUserOauth2.userInfo.name, 'customer contact has name from userLoginPost request body after second login') }) + + it('should not create new users once date is after NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START', async () => { + /** + * we're going to create a server with the appropriate configuration using miniflare, + * boot the server, then request POST /user/login and assert about the response. + */ + const server = await createApiMiniflare({ + bindings: { + NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START: (new Date(0)).toISOString(), + NEXT_PUBLIC_MAGIC_TESTMODE_ENABLED: 'true' + } + }).startServer() + await useServer(server, async (server) => { + const loginEndpoint = new URL('/user/login', getServerUrl(server)) + const user = { + publicAddress: BigInt(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)), + claims: {} + } + const authToken = createMagicTestModeToken(user.publicAddress, user.claims) + const userPostLoginResponse = await fetch(loginEndpoint, { + headers: { + accept: 'application/json', + authorization: `Bearer ${authToken}`, + contentType: 'application/json' + }, + method: 'post', + body: JSON.stringify({ + email: `test-${Math.random().toString().slice(2)}@example.com`, + issuer: 'test' + }) + }) + const responseText = await userPostLoginResponse.text() + assert.equal(userPostLoginResponse.status, 403, 'response status code is 403 forbidden because new user registration is forbidden') + assert.ok(responseText.includes('new user registration is closed'), 'response body indicates new user registration is closed') + + const responseJson = JSON.parse(responseText) + assert.equal(typeof responseJson, 'object', 'response can be parsed as json') + assert.ok(responseJson.message.includes('new user registration is closed'), 'response object message indicates new user registration is closed') + assert.equal(responseJson.code, 'NEW_USER_DENIED_TRY_OTHER_PRODUCT') + }) + }) }) diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 3f27685f17..f60b3c6582 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -30,6 +30,9 @@ "references": [ { "path": "../client" + }, + { + "path": "../w3up-launch" } ] } diff --git a/packages/w3up-launch/.gitignore b/packages/w3up-launch/.gitignore new file mode 100644 index 0000000000..9b1c8b133c --- /dev/null +++ b/packages/w3up-launch/.gitignore @@ -0,0 +1 @@ +/dist diff --git a/packages/w3up-launch/package.json b/packages/w3up-launch/package.json new file mode 100644 index 0000000000..8ca179b649 --- /dev/null +++ b/packages/w3up-launch/package.json @@ -0,0 +1,23 @@ +{ + "name": "@web3-storage/w3up-launch", + "private": true, + "version": "1.0.0", + "description": "", + "main": "src/index.js", + "types": "dist/src/index.d.ts", + "type": "module", + "scripts": { + "build": "tsc --build", + "prepare": "npm run build", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "devDependencies": { + "typescript": "^4.6.0" + }, + "author": "", + "license": "ISC" +} diff --git a/packages/w3up-launch/src/index.js b/packages/w3up-launch/src/index.js new file mode 100644 index 0000000000..4b900d1f1a --- /dev/null +++ b/packages/w3up-launch/src/index.js @@ -0,0 +1,92 @@ +export const DEFAULT_SUNSET_START_DATE = new Date(Date.parse('2024-01-10T00:00:00Z')) + +/** + * @typedef {object} W3upLaunchEnv + * @property {string|undefined} [NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START] - when limited availability starts, e.g. no new user signups, cant change plan + * @property {string|undefined} [NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_ANNOUNCEMENT_START] - when announcement starts + * @property {string|undefined} [NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START] - when the product is actually sunset (after announcement) + */ + +export class W3upLaunch { + /** + * @param {W3upLaunchEnv} env + */ + static fromEnv (env) { + const limitedAvailabilityStartEnv = env.NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START + const limitedAvailabilityStartDate = limitedAvailabilityStartEnv ? new Date(Date.parse(limitedAvailabilityStartEnv)) : undefined + + const sunsetStartEnv = env.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START + const sunsetStartDate = sunsetStartEnv ? new Date(Date.parse(sunsetStartEnv)) : undefined + + const sunsetAnnouncementStartEnv = env.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_ANNOUNCEMENT_START + const sunsetAnnouncementStartDate = sunsetAnnouncementStartEnv ? new Date(Date.parse(sunsetAnnouncementStartEnv)) : limitedAvailabilityStartDate + + const preventUserRegistrationStartDate = limitedAvailabilityStartDate + const preventPlanSwitchingStartDate = limitedAvailabilityStartDate + return new W3upLaunch({ + sunsetStartDate, + sunsetAnnouncementStartDate, + preventUserRegistrationStartDate, + preventPlanSwitchingStartDate + }) + } + + constructor ({ sunsetStartDate = DEFAULT_SUNSET_START_DATE, sunsetAnnouncementStartDate, preventUserRegistrationStartDate, preventPlanSwitchingStartDate }) { + /** @type {Date} */ + this.sunsetStartDate = sunsetStartDate + /** @type {Date|undefined} */ + this.sunsetAnnouncementStartDate = sunsetAnnouncementStartDate + /** @type {Date|undefined} */ + this.preventUserRegistrationStartDate = preventUserRegistrationStartDate + /** @type {Date|undefined} */ + this.preventPlanSwitchingStartDate = preventPlanSwitchingStartDate + } +} + +/** + * Return whether sunset announcements related to w3up-launch should be shown. + * An announcement date must be explicitly configured via env var, and now must be after that date. + * @param {Date} at - time at which to return whether to show the announcement + * @param {W3upLaunch} launch - configuration about the launch + */ +export const shouldShowSunsetAnnouncement = (launch, at = new Date()) => { + return launch.sunsetAnnouncementStartDate && at > launch.sunsetAnnouncementStartDate +} + +/** + * Return whether new user signups should be prevented. + * start date is configured by NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START + * @param {W3upLaunch} launch - configuration about the launch + * @param {Date} at - time at which to return whether to show the announcement + */ +export const shouldBlockNewUserSignupsBecauseProductSunset = (launch, at = new Date()) => { + return launch.preventUserRegistrationStartDate && at > launch.preventUserRegistrationStartDate +} + +/** + * return whether the website should prevent allowing customers to switch their storage subscription plan. + * @param {W3upLaunch} launch - configuration about the launch + * @param {Date} at - time at which to return whether to show the announcement + */ +export const shouldPreventPlanSwitching = (launch, at = new Date()) => { + return launch.preventPlanSwitchingStartDate && at > launch.preventPlanSwitchingStartDate +} + +/** + * @param {W3upLaunch} launch + */ +export const createW3upLaunchConfig = (launch) => { + return { + type: 'W3upLaunchConfig', + stages: { + sunsetAnnouncement: { + start: launch.sunsetAnnouncementStartDate + }, + sunset: { + start: launch.sunsetStartDate + } + }, + shouldShowSunsetAnnouncement: shouldShowSunsetAnnouncement(launch), + shouldBlockNewUserSignupsBecauseProductSunset: shouldBlockNewUserSignupsBecauseProductSunset(launch) + } +} diff --git a/packages/w3up-launch/tsconfig.json b/packages/w3up-launch/tsconfig.json new file mode 100644 index 0000000000..39d2fef725 --- /dev/null +++ b/packages/w3up-launch/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "composite": true, + "outDir": "./dist", + "allowJs": true, + "checkJs": true, + "target": "esnext", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "strict": true, + "moduleResolution": "node", + "sourceMap": true, + "esModuleInterop": true, + "noEmit": false, + "declaration": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "resolveJsonModule": true, + "isolatedModules": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "baseUrl": "./", + "incremental": true, + "jsx": "react" + }, + "include": [ + "./src", + "./test" + ], + "references": [ + ] +} diff --git a/packages/website/components/contexts/w3upLaunchContext.js b/packages/website/components/contexts/w3upLaunchContext.js new file mode 100644 index 0000000000..5823b780f4 --- /dev/null +++ b/packages/website/components/contexts/w3upLaunchContext.js @@ -0,0 +1,15 @@ +import * as React from 'react'; +import * as w3upLaunch from '@web3-storage/w3up-launch'; + +const w3upLaunchEnv = { + // pass these explicitly so nextjs bundling replaces the process.env vars + NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START: process.env.NEXT_PUBLIC_W3UP_LAUNCH_LIMITED_AVAILABILITY_START, + NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_ANNOUNCEMENT_START: process.env.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_ANNOUNCEMENT_START, + NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START: process.env.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START, +}; + +/** + * react context used for passing w3up-launch around + * @type {React.Context} + */ +export const W3upLaunchContext = React.createContext(w3upLaunch.W3upLaunch.fromEnv(w3upLaunchEnv)); diff --git a/packages/website/components/w3up-launch.js b/packages/website/components/w3up-launch.js index 6dd542e6e6..5a89b565ad 100644 --- a/packages/website/components/w3up-launch.js +++ b/packages/website/components/w3up-launch.js @@ -1,58 +1,19 @@ import * as React from 'react'; -const sunsetStartEnv = process.env.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_START || '2024-01-10T00:00:00Z'; -const sunsetStartDate = new Date(Date.parse(sunsetStartEnv)); +import { W3upLaunchContext } from './contexts/w3upLaunchContext'; +export { W3upLaunchContext } from './contexts/w3upLaunchContext'; +export * from '@web3-storage/w3up-launch'; -/** - * If this isn't set, no announcements will appear - */ -const sunsetAnnouncementStartEnv = process.env.NEXT_PUBLIC_W3UP_LAUNCH_SUNSET_ANNOUNCEMENT_START; - -/** - * after this datetime, show announcements that web3.storage is sunset - * and end-users should switch to w3up/console.web3.storage - */ -const sunsetAnnouncementStartDate = sunsetAnnouncementStartEnv - ? new Date(Date.parse(sunsetAnnouncementStartEnv)) - : undefined; - -/** - * Return whether sunset announcements related to w3up-launch should be shown. - * An announcement date must be explicitly configured via env var, and now must be after that date. - * @param {Date} at - time at which to return whether to show the announcement - * @param {Date|undefined} [announcementStartDate] - when to begin showing announcements. - * If not provided, always return false. - */ -export const shouldShowSunsetAnnouncement = (at = new Date(), announcementStartDate = sunsetAnnouncementStartDate) => { - return announcementStartDate && at > announcementStartDate; -}; - -/** - * return whether the website should prevent allowing customers to switch their storage subscription plan. - * @param {Date} at - time at which to return whether to show the announcement - * @param {Date|undefined} [startDate] - when to begin preventing - */ -export const shouldPreventPlanSwitching = (at = new Date(), startDate = sunsetStartDate) => { - return at > startDate; -}; - -export const w3upLaunchConfig = { - type: 'W3upLaunchConfig', - stages: { - sunsetAnnouncement: { - start: sunsetAnnouncementStartDate, - }, - sunset: { - start: sunsetStartDate, - }, - }, - shouldShowSunsetAnnouncement: shouldShowSunsetAnnouncement(), -}; +export function useW3upLaunch(context = W3upLaunchContext) { + return React.useContext(context); +} /** * copy for banner message across top of some web3.storage pages when w3up ships + * @param {object} props + * @param {Date} props.sunsetStartDate */ -export const W3upMigrationRecommendationCopy = () => { +export const W3upMigrationRecommendationCopy = ({ sunsetStartDate }) => { const createNewAccountHref = 'https://console.web3.storage/?intent=create-account'; const learnWhatsNewHref = 'https://console.web3.storage/?intent=learn-new-web3storage-experience'; const sunsetDateFormatter = new Intl.DateTimeFormat(undefined, { dateStyle: 'long' }); diff --git a/packages/website/lib/magic.js b/packages/website/lib/magic.js index 7d2cb40805..eabba70777 100644 --- a/packages/website/lib/magic.js +++ b/packages/website/lib/magic.js @@ -36,7 +36,27 @@ export async function login(token, type = 'magic', data = {}) { }), }); if (!res.ok) { - throw new Error(`failed to login user: ${await res.text()}`); + const resText = await res.text(); + // try to parse as json + let resJson = undefined; + try { + resJson = JSON.parse(resText); + } catch (error) { + if (error instanceof Error && error.name === 'SyntaxError') { + // expected + } else { + throw error; + } + } + if (resJson) { + switch (resJson.code) { + case 'NEW_USER_DENIED_TRY_OTHER_PRODUCT': + throw Object.assign(new Error(resJson.message || 'NEW_USER_DENIED_TRY_OTHER_PRODUCT'), resJson); + default: + // unknown code + } + } + throw new Error(`failed to login user: ${resText}`); } return res.json(); } diff --git a/packages/website/modules/docs-theme/index.js b/packages/website/modules/docs-theme/index.js index 3e3ca087e4..37b04437ff 100644 --- a/packages/website/modules/docs-theme/index.js +++ b/packages/website/modules/docs-theme/index.js @@ -13,7 +13,11 @@ import Sidebar from './sidebar/sidebar'; import Feedback from './feedback/feedback'; import Toc from './toc/toc'; import DocsPagination from './docspagination/docspagination'; -import { W3upMigrationRecommendationCopy, shouldShowSunsetAnnouncement } from '../../components/w3up-launch.js'; +import { + W3upMigrationRecommendationCopy, + shouldShowSunsetAnnouncement, + useW3upLaunch, +} from '../../components/w3up-launch.js'; import * as PageBannerPortal from '../../components/page-banner/page-banner-portal.js'; hljs.registerLanguage('javascript', javascript); @@ -90,11 +94,12 @@ export default function Docs(props) { if (route.startsWith('/docs')) { return function Layout({ children }) { + const w3upLaunch = useW3upLaunch(); return ( <> - {shouldShowSunsetAnnouncement() && ( + {shouldShowSunsetAnnouncement(w3upLaunch) && ( - + )} {sharedHead} diff --git a/packages/website/package.json b/packages/website/package.json index b705f6a688..306ae48412 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -17,7 +17,7 @@ "test:e2e": "env-cmd -f ../../.env npx playwright test", "lint": "npm run lint:ts && npm run lint:es", "lint:fix": "npm run lint:es:fix", - "lint:ts": "tsc --noEmit", + "lint:ts": "tsc --build", "lint:es": "eslint --max-warnings=0", "lint:es:fix": "npm run lint:es -- --fix", "lint-staged": "lint-staged", @@ -34,6 +34,7 @@ "@stripe/react-stripe-js": "^1.10.0", "@stripe/stripe-js": "^1.35.0", "@web3-storage/parse-link-header": "^3.1.0", + "@web3-storage/w3up-launch": "^1.0.0", "algoliasearch": "^4.13.0", "clsx": "^1.1.1", "countly-sdk-web": "^20.11.2", diff --git a/packages/website/pages/_app.js b/packages/website/pages/_app.js index 1fae4f0d74..2f60769f0e 100644 --- a/packages/website/pages/_app.js +++ b/packages/website/pages/_app.js @@ -21,7 +21,14 @@ import Footer from '../components/footer/footer.js'; */ const App = ({ Component, pageProps }) => { const { pathname } = useRouter(); - const productRoutes = ['/login', '/account', '/account/payment', '/tokens', '/callback']; + const productRoutes = [ + '/login', + '/login/signups-closed/try-w3up', + '/account', + '/account/payment', + '/tokens', + '/callback', + ]; const productApp = productRoutes.includes(pathname); const pageClass = pathname.includes('docs') ? 'docs-site' : productApp ? 'product-app' : 'marketing-site'; const [pageBannerPortalEl, setPageBannerPortalEl] = useState(); diff --git a/packages/website/pages/_document.js b/packages/website/pages/_document.js index 42b2a93952..050085e44c 100644 --- a/packages/website/pages/_document.js +++ b/packages/website/pages/_document.js @@ -1,6 +1,6 @@ import Document, { Html, Head, Main, NextScript } from 'next/document'; -import { w3upLaunchConfig } from '../components/w3up-launch.js'; +import { createW3upLaunchConfig, W3upLaunchContext } from '../components/w3up-launch.js'; class MyDocument extends Document { /** @@ -31,10 +31,16 @@ class MyDocument extends Document { {/* add this for debuggability of launch announcements that only appear when configured */} - + + {w3upLaunch => { + return ( + + ); + }} + ); diff --git a/packages/website/pages/account/index.js b/packages/website/pages/account/index.js index 07a44b0235..5236659fcd 100644 --- a/packages/website/pages/account/index.js +++ b/packages/website/pages/account/index.js @@ -10,7 +10,11 @@ import FileUploader from '../../components/account/fileUploader/fileUploader'; import GradientBackground from '../../components/gradientbackground/gradientbackground.js'; import AppData from '../../content/pages/app/account.json'; import GeneralPageData from '../../content/pages/general.json'; -import { W3upMigrationRecommendationCopy, shouldShowSunsetAnnouncement } from '../../components/w3up-launch.js'; +import { + shouldShowSunsetAnnouncement, + useW3upLaunch, + W3upMigrationRecommendationCopy, +} from '../../components/w3up-launch.js'; import * as PageBannerPortal from '../../components/page-banner/page-banner-portal.js'; export const CTACardTypes = { @@ -78,12 +82,12 @@ const Account = () => { }), [uploads.length, onFileUpload, dashboard, user] ); - + const w3upLaunch = useW3upLaunch(); return ( <> - {shouldShowSunsetAnnouncement() && ( + {shouldShowSunsetAnnouncement(w3upLaunch) && ( - + )}
diff --git a/packages/website/pages/account/payment.js b/packages/website/pages/account/payment.js index 6341c3a118..c262e37e47 100644 --- a/packages/website/pages/account/payment.js +++ b/packages/website/pages/account/payment.js @@ -22,6 +22,7 @@ import { W3upMigrationRecommendationCopy, shouldShowSunsetAnnouncement, shouldPreventPlanSwitching, + useW3upLaunch, } from '../../components/w3up-launch.js'; import * as PageBannerPortal from '../../components/page-banner/page-banner-portal.js'; @@ -129,11 +130,12 @@ const PaymentSettingsPage = props => { const savedPaymentMethod = useMemo(() => { return paymentSettings?.paymentMethod; }, [paymentSettings]); + const w3upLaunch = useW3upLaunch(); return ( <> - {shouldShowSunsetAnnouncement() && ( + {shouldShowSunsetAnnouncement(w3upLaunch) && ( - + )} <> @@ -159,7 +161,7 @@ const PaymentSettingsPage = props => { currentPlan={currentPlan} setPlanSelection={setPlanSelection} setIsPaymentPlanModalOpen={setIsPaymentPlanModalOpen} - disablePlanSwitching={shouldPreventPlanSwitching()} + disablePlanSwitching={shouldPreventPlanSwitching(w3upLaunch)} /> )} diff --git a/packages/website/pages/callback/index.js b/packages/website/pages/callback/index.js index 1d5c1491ca..678e67d909 100644 --- a/packages/website/pages/callback/index.js +++ b/packages/website/pages/callback/index.js @@ -23,15 +23,24 @@ const Callback = () => { const redirectUri = (redirectUriQuery && Array.isArray(redirectUriQuery) ? redirectUriQuery[0] : redirectUriQuery) ?? '/account'; useEffect(() => { + const handleError = async error => { + console.error(error); + await queryClient.invalidateQueries('magic-user'); + switch (error.code) { + case 'NEW_USER_DENIED_TRY_OTHER_PRODUCT': + router.push(`/login/signups-closed/try-w3up`); + break; + default: + router.push('/login'); + } + }; const finishSocialLogin = async () => { try { await redirectSocial(); await queryClient.invalidateQueries('magic-user'); router.push(redirectUri); } catch (err) { - console.error(err); - await queryClient.invalidateQueries('magic-user'); - router.push('/login'); + await handleError(err); } }; const finishEmailRedirectLogin = async () => { @@ -41,9 +50,7 @@ const Callback = () => { router.push(redirectUri); } catch (err) { - console.error(err); - await queryClient.invalidateQueries('magic-user'); - router.push('/login'); + await handleError(err); } }; if (!router.query.provider && router.query.magic_credential) { diff --git a/packages/website/pages/index.js b/packages/website/pages/index.js index 2963571fb3..7082be8712 100644 --- a/packages/website/pages/index.js +++ b/packages/website/pages/index.js @@ -4,7 +4,11 @@ import IndexPageData from '../content/pages/index.json'; import Scroll2Top from '../components/scroll2top/scroll2top.js'; import BlockBuilder from '../components/blockbuilder/blockbuilder.js'; import { initFloaterAnimations } from '../lib/floater-animations.js'; -import { W3upMigrationRecommendationCopy, shouldShowSunsetAnnouncement } from '../components/w3up-launch.js'; +import { + shouldShowSunsetAnnouncement, + useW3upLaunch, + W3upMigrationRecommendationCopy, +} from '../components/w3up-launch.js'; import * as PageBannerPortal from '../components/page-banner/page-banner-portal.js'; export default function Home() { @@ -22,12 +26,12 @@ export default function Home() { } }; }, [animations]); - + const w3upLaunch = useW3upLaunch(); return ( <> - {shouldShowSunsetAnnouncement() && ( + {shouldShowSunsetAnnouncement(w3upLaunch) && ( - + )} diff --git a/packages/website/pages/login/signups-closed/try-w3up.js b/packages/website/pages/login/signups-closed/try-w3up.js new file mode 100644 index 0000000000..e88375ff49 --- /dev/null +++ b/packages/website/pages/login/signups-closed/try-w3up.js @@ -0,0 +1,44 @@ +import Button, { ButtonVariant } from 'components/button/button'; + +/* eslint-disable react/no-unescaped-entities */ +const styles = { + lightText: { + color: 'white', + }, + mediumLineHeight: { + lineHeight: '1.5em', + }, + centeredContainer: /** @type {const} */ ({ + maxWidth: '24.6875rem', + margin: '0 auto', + zIndex: 0, + padding: '1em', + }), + textMargin: { + marginBottom: '1em', + }, +}; + +function TryW3up() { + return ( +
+
+
+

Sign up for our new stuff instead.

+
+

We're no longer accepting new signups here at old.web3.storage.

+

+ We've launched a new web3.storage experience called w3up, and you should check it out. +

+ +
+
+ ); +} + +export default TryW3up; diff --git a/packages/website/tsconfig.json b/packages/website/tsconfig.json index 77caa80ce9..3e4c7139e5 100644 --- a/packages/website/tsconfig.json +++ b/packages/website/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "composite": true, "outDir": "./dist", "allowJs": true, "checkJs": true, @@ -37,10 +38,15 @@ "incremental": true }, "include": [ + "assets/icons/*.js", + "assets/illustrations/*.js", "lib", "pages", + "pages/docs/*.json", "components", "content", + "content/pages/*/*.json", + "content/pages/*.json", "hooks", "modules", "store", @@ -52,6 +58,9 @@ "references": [ { "path": "../client" + }, + { + "path": "../w3up-launch" } ], "exclude": [