From 37c6c1e529d2577116e8340907bb76efa9219190 Mon Sep 17 00:00:00 2001 From: Sasha <118575614+weboko@users.noreply.github.com> Date: Thu, 9 Oct 2025 00:38:54 +0200 Subject: [PATCH 1/8] chore: expose sdk from waku/react (#2676) --- packages/react/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index cf41fa9f5f..e4a7896147 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,2 +1,4 @@ +export * from "@waku/sdk"; + export type { CreateNodeOptions } from "./types.js"; export { WakuProvider, useWaku } from "./WakuProvider.js"; From b8a9d132c189bd7a1cedb21dbd6d1317067e6949 Mon Sep 17 00:00:00 2001 From: fryorcraken <110212804+fryorcraken@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:35:01 +1100 Subject: [PATCH 2/8] chore: npm publication (#2688) * chore: npm publication Fixing npm publication and warnings * Upgrade workflow to use trusted publishing https://docs.npmjs.com/trusted-publishers * bump node js to 24 To avoid having to reinstall npm in pre-release for npmjs trusted publishers --- .github/workflows/ci.yml | 2 +- .github/workflows/pre-release.yml | 22 ++++++++++++---------- .github/workflows/test-node.yml | 8 ++++---- .github/workflows/test-reliability.yml | 6 +++--- package.json | 3 ++- packages/core/package.json | 2 +- packages/discovery/package.json | 2 +- packages/enr/package.json | 2 +- packages/interfaces/package.json | 2 +- packages/message-encryption/package.json | 2 +- packages/proto/package.json | 2 +- packages/react/package.json | 2 +- packages/relay/package.json | 2 +- packages/reliability-tests/package.json | 2 +- packages/rln/package.json | 2 +- packages/sdk/package.json | 2 +- packages/sds/package.json | 2 +- packages/tests/package.json | 2 +- packages/utils/package.json | 2 +- 19 files changed, 36 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28595b9022..8a04e6a560 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ on: type: string env: - NODE_JS: "22" + NODE_JS: "24" jobs: check: diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 659d6c62b2..6170343f17 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -2,7 +2,11 @@ on: workflow_dispatch: env: - NODE_JS: "22" + NODE_JS: "24" + +permissions: + id-token: write # Required for npmjs' OIDC + contents: read jobs: pre-release: @@ -10,19 +14,17 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'workflow_dispatch' steps: - - uses: actions/checkout@v3 - with: + - uses: actions/checkout@v4 + with: repository: waku-org/js-waku - - - uses: actions/setup-node@v3 + + - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_JS }} registry-url: "https://registry.npmjs.org" - + - run: npm install - + - run: npm run build - + - run: npm run publish -- --tag next - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_JS_WAKU_PUBLISH }} diff --git a/.github/workflows/test-node.yml b/.github/workflows/test-node.yml index a42f1afb92..0022695fd7 100644 --- a/.github/workflows/test-node.yml +++ b/.github/workflows/test-node.yml @@ -24,7 +24,7 @@ on: default: false env: - NODE_JS: "22" + NODE_JS: "24" # Ensure test type conditions remain consistent. WAKU_SERVICE_NODE_PARAMS: ${{ (inputs.test_type == 'go-waku-master') && '--min-relay-peers-to-publish=0' || '' }} DEBUG: ${{ inputs.debug }} @@ -42,7 +42,7 @@ jobs: checks: write steps: - uses: actions/checkout@v3 - with: + with: repository: waku-org/js-waku - name: Remove unwanted software @@ -62,7 +62,7 @@ jobs: - name: Merge allure reports if: always() && env.ALLURE_REPORTS == 'true' - run: node ci/mergeAllureResults.cjs + run: node ci/mergeAllureResults.cjs - name: Get allure history if: always() && env.ALLURE_REPORTS == 'true' @@ -125,4 +125,4 @@ jobs: echo "## Run Information" >> $GITHUB_STEP_SUMMARY echo "- **NWAKU**: ${{ env.WAKUNODE_IMAGE }}" >> $GITHUB_STEP_SUMMARY echo "## Test Results" >> $GITHUB_STEP_SUMMARY - echo "Allure report will be available at: https://waku-org.github.io/allure-jswaku/${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY \ No newline at end of file + echo "Allure report will be available at: https://waku-org.github.io/allure-jswaku/${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/test-reliability.yml b/.github/workflows/test-reliability.yml index a25f5a6be3..ba6c221e95 100644 --- a/.github/workflows/test-reliability.yml +++ b/.github/workflows/test-reliability.yml @@ -18,7 +18,7 @@ on: - all env: - NODE_JS: "22" + NODE_JS: "24" jobs: test: @@ -34,7 +34,7 @@ jobs: if: ${{ github.event.inputs.test_type == 'all' }} steps: - uses: actions/checkout@v3 - with: + with: repository: waku-org/js-waku - name: Remove unwanted software @@ -74,7 +74,7 @@ jobs: if: ${{ github.event.inputs.test_type != 'all' }} steps: - uses: actions/checkout@v3 - with: + with: repository: waku-org/js-waku - name: Remove unwanted software diff --git a/package.json b/package.json index bd29d5bc00..f406982678 100644 --- a/package.json +++ b/package.json @@ -78,5 +78,6 @@ "*.{ts,js}": [ "eslint --fix" ] - } + }, + "version": "" } diff --git a/packages/core/package.json b/packages/core/package.json index ff2fb1899d..d0af073272 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -28,7 +28,7 @@ "homepage": "https://github.com/waku-org/js-waku/tree/master/packages/core#readme", "repository": { "type": "git", - "url": "https://github.com/waku-org/js-waku.git" + "url": "git+https://github.com/waku-org/js-waku.git" }, "bugs": { "url": "https://github.com/waku-org/js-waku/issues" diff --git a/packages/discovery/package.json b/packages/discovery/package.json index 6082f191c2..d3fd24fe77 100644 --- a/packages/discovery/package.json +++ b/packages/discovery/package.json @@ -15,7 +15,7 @@ "homepage": "https://github.com/waku-org/js-waku/tree/master/packages/discovery#readme", "repository": { "type": "git", - "url": "https://github.com/waku-org/js-waku.git" + "url": "git+https://github.com/waku-org/js-waku.git" }, "bugs": { "url": "https://github.com/waku-org/js-waku/issues" diff --git a/packages/enr/package.json b/packages/enr/package.json index 80144ddc32..e73b05f5e3 100644 --- a/packages/enr/package.json +++ b/packages/enr/package.json @@ -15,7 +15,7 @@ "homepage": "https://github.com/waku-org/js-waku/tree/master/packages/enr#readme", "repository": { "type": "git", - "url": "https://github.com/waku-org/js-waku.git" + "url": "git+https://github.com/waku-org/js-waku.git" }, "bugs": { "url": "https://github.com/waku-org/js-waku/issues" diff --git a/packages/interfaces/package.json b/packages/interfaces/package.json index 9387df9f1b..4c8fabbb53 100644 --- a/packages/interfaces/package.json +++ b/packages/interfaces/package.json @@ -15,7 +15,7 @@ "homepage": "https://github.com/waku-org/js-waku/tree/master/packages/interfaces#readme", "repository": { "type": "git", - "url": "https://github.com/waku-org/js-waku.git" + "url": "git+https://github.com/waku-org/js-waku.git" }, "bugs": { "url": "https://github.com/waku-org/js-waku/issues" diff --git a/packages/message-encryption/package.json b/packages/message-encryption/package.json index b9a4901875..f21d8ec29d 100644 --- a/packages/message-encryption/package.json +++ b/packages/message-encryption/package.json @@ -36,7 +36,7 @@ "homepage": "https://github.com/waku-org/js-waku/tree/master/packages/message-encryption#readme", "repository": { "type": "git", - "url": "https://github.com/waku-org/js-waku.git" + "url": "git+https://github.com/waku-org/js-waku.git" }, "bugs": { "url": "https://github.com/waku-org/js-waku/issues" diff --git a/packages/proto/package.json b/packages/proto/package.json index eb46635413..5eeb717b83 100644 --- a/packages/proto/package.json +++ b/packages/proto/package.json @@ -15,7 +15,7 @@ "homepage": "https://github.com/waku-org/js-waku/tree/master/packages/proto#readme", "repository": { "type": "git", - "url": "https://github.com/waku-org/js-waku.git" + "url": "git+https://github.com/waku-org/js-waku.git" }, "bugs": { "url": "https://github.com/waku-org/js-waku/issues" diff --git a/packages/react/package.json b/packages/react/package.json index ca0da6f168..3491a6ce85 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -21,7 +21,7 @@ "homepage": "https://github.com/waku-org/js-waku/tree/master/packages/react#readme", "repository": { "type": "git", - "url": "https://github.com/waku-org/js-waku.git" + "url": "git+https://github.com/waku-org/js-waku.git" }, "bugs": { "url": "https://github.com/waku-org/js-waku/issues" diff --git a/packages/relay/package.json b/packages/relay/package.json index a3f9bec26a..70f05a3077 100644 --- a/packages/relay/package.json +++ b/packages/relay/package.json @@ -14,7 +14,7 @@ "homepage": "https://github.com/waku-org/js-waku/tree/master/packages/relay#readme", "repository": { "type": "git", - "url": "https://github.com/waku-org/js-waku.git" + "url": "git+https://github.com/waku-org/js-waku.git" }, "bugs": { "url": "https://github.com/waku-org/js-waku/issues" diff --git a/packages/reliability-tests/package.json b/packages/reliability-tests/package.json index 97dfac7ba1..42de782ff8 100644 --- a/packages/reliability-tests/package.json +++ b/packages/reliability-tests/package.json @@ -16,7 +16,7 @@ "homepage": "https://github.com/waku-org/js-waku/tree/master/packages/reliability-tests#readme", "repository": { "type": "git", - "url": "https://github.com/waku-org/js-waku.git" + "url": "git+https://github.com/waku-org/js-waku.git" }, "bugs": { "url": "https://github.com/waku-org/js-waku/issues" diff --git a/packages/rln/package.json b/packages/rln/package.json index 4ea0199115..e8affc320d 100644 --- a/packages/rln/package.json +++ b/packages/rln/package.json @@ -14,7 +14,7 @@ "homepage": "https://github.com/waku-org/js-waku/tree/master/packages/rln#readme", "repository": { "type": "git", - "url": "https://github.com/waku-org/js-waku.git" + "url": "git+https://github.com/waku-org/js-waku.git" }, "bugs": { "url": "https://github.com/waku-org/js-waku/issues" diff --git a/packages/sdk/package.json b/packages/sdk/package.json index a72b4025d7..dd313a6d8d 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -24,7 +24,7 @@ "homepage": "https://github.com/waku-org/js-waku/tree/master/packages/sdk#readme", "repository": { "type": "git", - "url": "https://github.com/waku-org/js-waku.git" + "url": "git+https://github.com/waku-org/js-waku.git" }, "bugs": { "url": "https://github.com/waku-org/js-waku/issues" diff --git a/packages/sds/package.json b/packages/sds/package.json index 0e59d4fa41..08cac5c0a8 100644 --- a/packages/sds/package.json +++ b/packages/sds/package.json @@ -24,7 +24,7 @@ "homepage": "https://github.com/waku-org/js-waku/tree/master/packages/scalable-data-sync#readme", "repository": { "type": "git", - "url": "https://github.com/waku-org/js-waku.git" + "url": "git+https://github.com/waku-org/js-waku.git" }, "bugs": { "url": "https://github.com/waku-org/js-waku/issues" diff --git a/packages/tests/package.json b/packages/tests/package.json index b059f4a909..9bfdb28f52 100644 --- a/packages/tests/package.json +++ b/packages/tests/package.json @@ -16,7 +16,7 @@ "homepage": "https://github.com/waku-org/js-waku/tree/master/packages/tests#readme", "repository": { "type": "git", - "url": "https://github.com/waku-org/js-waku.git" + "url": "git+https://github.com/waku-org/js-waku.git" }, "bugs": { "url": "https://github.com/waku-org/js-waku/issues" diff --git a/packages/utils/package.json b/packages/utils/package.json index be791ef342..5450aeb6d0 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -28,7 +28,7 @@ "homepage": "https://github.com/waku-org/js-waku/tree/master/packages/utils#readme", "repository": { "type": "git", - "url": "https://github.com/waku-org/js-waku.git" + "url": "git+https://github.com/waku-org/js-waku.git" }, "bugs": { "url": "https://github.com/waku-org/js-waku/issues" From ff9c43038e3a0a4c080b28de8cc94f4a334fd173 Mon Sep 17 00:00:00 2001 From: Sasha <118575614+weboko@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:26:13 +0200 Subject: [PATCH 3/8] chore: use npm token (#2693) --- .github/workflows/pre-release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 6170343f17..659676f47e 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -5,7 +5,7 @@ env: NODE_JS: "24" permissions: - id-token: write # Required for npmjs' OIDC + id-token: write contents: read jobs: @@ -28,3 +28,5 @@ jobs: - run: npm run build - run: npm run publish -- --tag next + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_JS_WAKU_PUBLISH }} From 0df18b2a75f558f86f03c7f1c1e4b5e89d92f009 Mon Sep 17 00:00:00 2001 From: Arseniy Klempner Date: Wed, 22 Oct 2025 21:38:28 -0700 Subject: [PATCH 4/8] feat: create @waku/run package for local dev env (#2678) * feat: create @waku/run package for local dev env * chore: add @waku/run to release please config * feat: test @waku/run with playwright * fix: don't run waku/run tests in CI * fix: cache images so docker-compose can work offline * feat: set nodekey and staticnode flags for each nwaku node * fix: use constants for node ids * chore: set directories for running via npx * fix: remove .env, support env vars for nwaku ports * fix: use separate db (same instance) for each node * feat: add command to test dev env * chore: use package version in container name * fix: replace hardcoded WS/REST ports with constants/env vars * chore: clean up README * fix: refactor config printing into own function * fix: add run package to release please manifest * fix: defer to root folder gitignore/cspell * fix: update node version and remove tsx * fix: remove browser tests and express dep * fix: replace magic values with constants * fix: move to root .gitignore * fix: move cspell to root --- .cspell.json | 3 + .gitignore | 4 +- .release-please-manifest.json | 3 +- package-lock.json | 32 ++++++ package.json | 1 + packages/run/.eslintrc.cjs | 20 ++++ packages/run/.mocharc.cjs | 11 ++ packages/run/README.md | 148 ++++++++++++++++++++++++++ packages/run/docker-compose.yml | 142 +++++++++++++++++++++++++ packages/run/init-db.sh | 8 ++ packages/run/package.json | 68 ++++++++++++ packages/run/scripts/info.ts | 83 +++++++++++++++ packages/run/scripts/logs.ts | 26 +++++ packages/run/scripts/start.ts | 172 +++++++++++++++++++++++++++++++ packages/run/scripts/stop.ts | 28 +++++ packages/run/scripts/test.ts | 122 ++++++++++++++++++++++ packages/run/src/cli.ts | 40 +++++++ packages/run/src/constants.ts | 40 +++++++ packages/run/src/run-tests.js | 30 ++++++ packages/run/src/test-client.ts | 126 ++++++++++++++++++++++ packages/run/src/utils.ts | 63 +++++++++++ packages/run/tests/basic.spec.ts | 120 +++++++++++++++++++++ packages/run/tsconfig.dev.json | 7 ++ packages/run/tsconfig.json | 10 ++ release-please-config.json | 3 +- 25 files changed, 1307 insertions(+), 3 deletions(-) create mode 100644 packages/run/.eslintrc.cjs create mode 100644 packages/run/.mocharc.cjs create mode 100644 packages/run/README.md create mode 100644 packages/run/docker-compose.yml create mode 100755 packages/run/init-db.sh create mode 100644 packages/run/package.json create mode 100755 packages/run/scripts/info.ts create mode 100644 packages/run/scripts/logs.ts create mode 100755 packages/run/scripts/start.ts create mode 100644 packages/run/scripts/stop.ts create mode 100644 packages/run/scripts/test.ts create mode 100644 packages/run/src/cli.ts create mode 100644 packages/run/src/constants.ts create mode 100644 packages/run/src/run-tests.js create mode 100644 packages/run/src/test-client.ts create mode 100644 packages/run/src/utils.ts create mode 100644 packages/run/tests/basic.spec.ts create mode 100644 packages/run/tsconfig.dev.json create mode 100644 packages/run/tsconfig.json diff --git a/.cspell.json b/.cspell.json index dd57575bc2..e6c6f1fa0a 100644 --- a/.cspell.json +++ b/.cspell.json @@ -55,6 +55,7 @@ "fontsource", "globby", "gossipsub", + "hackathons", "huilong", "iasked", "ihave", @@ -62,6 +63,7 @@ "ineed", "IPAM", "ipfs", + "isready", "iwant", "jdev", "jswaku", @@ -165,6 +167,7 @@ "gen", "proto", "*.spec.ts", + "*.log", "CHANGELOG.md" ], "patterns": [ diff --git a/.gitignore b/.gitignore index 80d8fb41fb..1e6952cf2f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,6 @@ packages/discovery/mock_local_storage .giga .cursor .DS_Store -CLAUDE.md \ No newline at end of file +CLAUDE.md +.env +postgres-data/ diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 288bdb7fbc..210d52b6a6 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -10,5 +10,6 @@ "packages/discovery": "0.0.12", "packages/sds": "0.0.7", "packages/rln": "0.1.9", - "packages/react": "0.0.7" + "packages/react": "0.0.7", + "packages/run": "0.0.1" } diff --git a/package-lock.json b/package-lock.json index 370aa3b75a..aec015a416 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "packages/rln", "packages/sdk", "packages/relay", + "packages/run", "packages/tests", "packages/reliability-tests", "packages/browser-tests", @@ -7643,6 +7644,10 @@ "resolved": "packages/rln", "link": true }, + "node_modules/@waku/run": { + "resolved": "packages/run", + "link": true + }, "node_modules/@waku/sdk": { "resolved": "packages/sdk", "link": true @@ -35978,6 +35983,33 @@ "uuid": "dist/esm/bin/uuid" } }, + "packages/run": { + "name": "@waku/run", + "version": "0.0.1", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@waku/core": "*", + "@waku/interfaces": "*", + "@waku/sdk": "*", + "@waku/utils": "*" + }, + "bin": { + "waku-run": "dist/src/cli.js" + }, + "devDependencies": { + "@types/chai": "^4.3.11", + "@types/mocha": "^10.0.6", + "chai": "^4.3.10", + "cspell": "^8.6.1", + "mocha": "^10.3.0", + "npm-run-all": "^4.1.5", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + }, + "engines": { + "node": ">=22" + } + }, "packages/sdk": { "name": "@waku/sdk", "version": "0.0.35", diff --git a/package.json b/package.json index f406982678..02a8034760 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "packages/rln", "packages/sdk", "packages/relay", + "packages/run", "packages/tests", "packages/reliability-tests", "packages/browser-tests", diff --git a/packages/run/.eslintrc.cjs b/packages/run/.eslintrc.cjs new file mode 100644 index 0000000000..c353a0323b --- /dev/null +++ b/packages/run/.eslintrc.cjs @@ -0,0 +1,20 @@ +module.exports = { + parserOptions: { + tsconfigRootDir: __dirname, + project: "./tsconfig.dev.json" + }, + rules: { + "@typescript-eslint/no-non-null-assertion": "off" + }, + globals: { + process: true + }, + overrides: [ + { + files: ["*.js"], + rules: { + "no-console": "error" + } + } + ] +}; diff --git a/packages/run/.mocharc.cjs b/packages/run/.mocharc.cjs new file mode 100644 index 0000000000..424cc14a01 --- /dev/null +++ b/packages/run/.mocharc.cjs @@ -0,0 +1,11 @@ +module.exports = { + extension: ['ts'], + require: ['ts-node/register'], + loader: 'ts-node/esm', + 'node-option': [ + 'experimental-specifier-resolution=node', + 'loader=ts-node/esm' + ], + timeout: 90000, + exit: true +}; diff --git a/packages/run/README.md b/packages/run/README.md new file mode 100644 index 0000000000..a05a2bb0b5 --- /dev/null +++ b/packages/run/README.md @@ -0,0 +1,148 @@ +# @waku/run + +> **Spin up a local Waku network for development without relying on external infrastructure** + +Perfect for hackathons, offline development, or when you need a controlled testing environment for your js-waku application. + +## What's Included + +- **2 nwaku nodes** connected to each other with all protocols enabled: +- **PostgreSQL database** for message persistence +- **Isolated network** - nodes only connect to each other + +## Requirements + +- [Docker Desktop](https://www.docker.com/products/docker-desktop/) or Docker Engine with Compose plugin + +## Quick Start + +### 1. Start the Network + +```bash +npx @waku/run start +``` + +This will: +- Start 2 nwaku nodes and a PostgreSQL database +- Run in the background (detached mode) +- Display connection information you need for your app + +**Example output:** +```typescript +import { createLightNode } from "@waku/sdk"; + +const waku = await createLightNode({ + defaultBootstrap: false, + bootstrapPeers: [ + "/ip4/127.0.0.1/tcp/60000/ws/p2p/16Uiu2HAmF6oAsd23RMAnZb3NJgxXrExxBTPMdEoih232iAZkviU2", + "/ip4/127.0.0.1/tcp/60001/ws/p2p/16Uiu2HAm5aZU47YkiUoARqivbCXwuFPzFFXXiURAorySqAQbL6EQ" + ], + numPeersToUse: 2, + libp2p: { + filterMultiaddrs: false + }, + networkConfig: { + clusterId: 0, + numShardsInCluster: 8 + } +}); +``` + +### 2. Connect Your js-waku App + +Copy the configuration from the output above and paste it into your application. Then start your node: + +```typescript +await waku.start(); + +// Your app is now connected to your local Waku network! +``` + +### 3. Stop When Done + +```bash +npx @waku/run stop +``` + +## Available Commands + +### Using npx (published package) + +| Command | Description | +|---------|-------------| +| `npx @waku/run start` | Start the network (detached) and show connection info | +| `npx @waku/run stop` | Stop the network and clean up | +| `npx @waku/run info` | Show connection info for running network | +| `npx @waku/run logs` | View and follow logs from all nodes | +| `npx @waku/run test` | Test the network by sending a message | + +## Configuration + +All configuration is done via environment variables passed to the command. + +### Custom Ports + +If the default ports are in use, specify custom ports: + +```bash +NODE1_WS_PORT=50000 NODE2_WS_PORT=50001 npx @waku/run start +``` + +Available port configuration: +- `NODE1_WS_PORT` (default: 60000) +- `NODE2_WS_PORT` (default: 60001) +- `NODE1_REST_PORT` (default: 8646) +- `NODE2_REST_PORT` (default: 8647) + +### Cluster Configuration + +The default configuration uses: +- Cluster ID: 0 +- Number of shards: 8 + +To test with a different cluster: + +```bash +CLUSTER_ID=16 npx @waku/run start +``` + +### Custom nwaku Version + +To use a different nwaku image version: + +```bash +NWAKU_IMAGE=wakuorg/nwaku:v0.35.0 npx @waku/run start +``` + +## Debugging + +### View Node Logs + +```bash +npx @waku/run logs +``` + +### Check Node Health + +```bash +# Node 1 +curl http://127.0.0.1:8646/health + +# Node 2 +curl http://127.0.0.1:8647/health +``` + +### Check Peer Connections + +```bash +# Node 1 debug info +curl http://127.0.0.1:8646/debug/v1/info + +# Node 2 debug info +curl http://127.0.0.1:8647/debug/v1/info +``` + + +## License + +MIT OR Apache-2.0 diff --git a/packages/run/docker-compose.yml b/packages/run/docker-compose.yml new file mode 100644 index 0000000000..4634db9627 --- /dev/null +++ b/packages/run/docker-compose.yml @@ -0,0 +1,142 @@ +# Environment variable definitions +x-pg-pass: &pg_pass ${POSTGRES_PASSWORD:-test123} +x-pg-user: &pg_user ${POSTGRES_USER:-postgres} + +x-pg-environment: &pg_env + POSTGRES_USER: *pg_user + POSTGRES_PASSWORD: *pg_pass + +# Shared nwaku configuration +x-nwaku-base: &nwaku-base + image: ${NWAKU_IMAGE:-wakuorg/nwaku:v0.36.0} + pull_policy: if_not_present + restart: on-failure + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + +services: + postgres: + image: postgres:15.4-alpine3.18 + pull_policy: if_not_present + restart: on-failure + environment: + <<: *pg_env + POSTGRES_DB: postgres + volumes: + - postgres-data:/var/lib/postgresql/data + - ./init-db.sh:/docker-entrypoint-initdb.d/init-db.sh + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + + nwaku-1: + <<: *nwaku-base + container_name: ${COMPOSE_PROJECT_NAME:-waku-run-0-0-1}-node-1 + networks: + default: + ipv4_address: 172.20.0.10 + ports: + - "${NODE1_TCP_PORT:-30303}:30303/tcp" + - "${NODE1_WS_PORT:-60000}:60000/tcp" + - "${NODE1_REST_PORT:-8646}:8646/tcp" + environment: + <<: *pg_env + depends_on: + postgres: + condition: service_healthy + command: + - --nodekey=e419c3cf4f09ac3babdf61856e6faa0e0c6a7d97674d5401a0114616549c7632 + - --staticnode=/ip4/172.20.0.11/tcp/60001/ws/p2p/16Uiu2HAm5aZU47YkiUoARqivbCXwuFPzFFXXiURAorySqAQbL6EQ + - --relay=true + - --filter=true + - --lightpush=true + - --store=true + - --peer-exchange=true + - --discv5-discovery=true + - --cluster-id=0 + - --shard=0 + - --shard=1 + - --shard=2 + - --shard=3 + - --shard=4 + - --shard=5 + - --shard=6 + - --shard=7 + - --listen-address=0.0.0.0 + - --tcp-port=30303 + - --websocket-support=true + - --websocket-port=60000 + - --ext-multiaddr=/dns4/nwaku-1/tcp/60000/ws + - --ext-multiaddr=/ip4/127.0.0.1/tcp/60000/ws + - --rest=true + - --rest-address=0.0.0.0 + - --rest-port=8646 + - --rest-admin=true + - --store-message-db-url=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-test123}@postgres:5432/nwaku1 + - --log-level=${LOG_LEVEL:-INFO} + - --max-connections=150 + + nwaku-2: + <<: *nwaku-base + container_name: ${COMPOSE_PROJECT_NAME:-waku-run-0-0-1}-node-2 + networks: + default: + ipv4_address: 172.20.0.11 + ports: + - "${NODE2_TCP_PORT:-30304}:30304/tcp" + - "${NODE2_WS_PORT:-60001}:60001/tcp" + - "${NODE2_REST_PORT:-8647}:8647/tcp" + environment: + <<: *pg_env + depends_on: + postgres: + condition: service_healthy + nwaku-1: + condition: service_started + command: + - --nodekey=50632ab0efd313bfb4aa842de716f03dacd181c863770abd145e3409290fdaa7 + - --staticnode=/ip4/172.20.0.10/tcp/60000/ws/p2p/16Uiu2HAmF6oAsd23RMAnZb3NJgxXrExxBTPMdEoih232iAZkviU2 + - --relay=true + - --filter=true + - --lightpush=true + - --store=true + - --peer-exchange=true + - --discv5-discovery=true + - --cluster-id=0 + - --shard=0 + - --shard=1 + - --shard=2 + - --shard=3 + - --shard=4 + - --shard=5 + - --shard=6 + - --shard=7 + - --listen-address=0.0.0.0 + - --tcp-port=30304 + - --websocket-support=true + - --websocket-port=60001 + - --ext-multiaddr=/dns4/nwaku-2/tcp/60001/ws + - --ext-multiaddr=/ip4/127.0.0.1/tcp/60001/ws + - --rest=true + - --rest-address=0.0.0.0 + - --rest-port=8647 + - --rest-admin=true + - --store-message-db-url=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-test123}@postgres:5432/nwaku2 + - --log-level=${LOG_LEVEL:-INFO} + - --max-connections=150 + +volumes: + postgres-data: + +networks: + default: + name: ${COMPOSE_PROJECT_NAME:-waku-run-0-0-1}-network + ipam: + config: + - subnet: 172.20.0.0/16 diff --git a/packages/run/init-db.sh b/packages/run/init-db.sh new file mode 100755 index 0000000000..35a95a7560 --- /dev/null +++ b/packages/run/init-db.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +# Create separate databases for each nwaku node +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL + CREATE DATABASE nwaku1; + CREATE DATABASE nwaku2; +EOSQL diff --git a/packages/run/package.json b/packages/run/package.json new file mode 100644 index 0000000000..7c72459fa3 --- /dev/null +++ b/packages/run/package.json @@ -0,0 +1,68 @@ +{ + "name": "@waku/run", + "version": "0.0.1", + "description": "Run a local Waku network for development and testing", + "type": "module", + "author": "Waku Team", + "homepage": "https://github.com/waku-org/js-waku/tree/master/packages/run#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/waku-org/js-waku.git" + }, + "bugs": { + "url": "https://github.com/waku-org/js-waku/issues" + }, + "license": "MIT OR Apache-2.0", + "keywords": [ + "waku", + "decentralized", + "communication", + "web3", + "testing", + "development" + ], + "bin": { + "waku-run": "./dist/src/cli.js" + }, + "files": [ + "dist", + "docker-compose.yml", + "init-db.sh", + "README.md" + ], + "scripts": { + "build": "tsc", + "prepublishOnly": "npm run build", + "start": "node dist/scripts/start.js", + "stop": "node dist/scripts/stop.js", + "restart": "npm run stop && npm run start", + "logs": "node dist/scripts/logs.js", + "info": "node dist/scripts/info.js", + "test": "if [ \"$CI\" = \"true\" ]; then echo 'Skipping tests in CI'; exit 0; fi && NODE_ENV=test node ./src/run-tests.js \"tests/basic.spec.ts\"", + "fix": "run-s fix:*", + "fix:lint": "eslint src scripts tests --fix", + "check": "run-s check:*", + "check:tsc": "tsc -p tsconfig.dev.json", + "check:lint": "eslint src scripts tests", + "check:spelling": "cspell \"{README.md,src/**/*.ts,scripts/**/*.ts,tests/**/*.ts}\"" + }, + "engines": { + "node": ">=22" + }, + "dependencies": { + "@waku/core": "*", + "@waku/interfaces": "*", + "@waku/sdk": "*", + "@waku/utils": "*" + }, + "devDependencies": { + "@types/chai": "^4.3.11", + "@types/mocha": "^10.0.6", + "chai": "^4.3.10", + "cspell": "^8.6.1", + "mocha": "^10.3.0", + "npm-run-all": "^4.1.5", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + } +} diff --git a/packages/run/scripts/info.ts b/packages/run/scripts/info.ts new file mode 100755 index 0000000000..98259f21d3 --- /dev/null +++ b/packages/run/scripts/info.ts @@ -0,0 +1,83 @@ +#!/usr/bin/env node + +import { execSync } from "child_process"; +import { dirname, join } from "path"; +import { fileURLToPath } from "url"; + +import { + DEFAULT_CLUSTER_ID, + DEFAULT_NODE1_WS_PORT, + DEFAULT_NODE2_WS_PORT, + NODE1_PEER_ID, + NODE2_PEER_ID +} from "../src/constants.js"; +import { getProjectName, printWakuConfig } from "../src/utils.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const packageRoot = __dirname.includes("dist") + ? join(__dirname, "..", "..") + : join(__dirname, ".."); + +interface Colors { + reset: string; + cyan: string; + blue: string; + gray: string; + yellow: string; +} + +// ANSI color codes +const colors: Colors = { + reset: "\x1b[0m", + cyan: "\x1b[36m", + blue: "\x1b[34m", + gray: "\x1b[90m", + yellow: "\x1b[33m" +}; + +try { + // Check if containers are running + const projectName = getProjectName(packageRoot); + const output: string = execSync( + `docker compose --project-name ${projectName} ps --quiet`, + { + cwd: packageRoot, + encoding: "utf-8", + env: { ...process.env, COMPOSE_PROJECT_NAME: projectName } + } + ).trim(); + + if (!output) { + process.stdout.write( + `${colors.gray}No nodes running. Start with: ${colors.cyan}npm run start${colors.reset}\n` + ); + process.exit(0); + } + + // Get cluster config from env or defaults + const clusterId: string = process.env.CLUSTER_ID || DEFAULT_CLUSTER_ID; + const node1Port: string = process.env.NODE1_WS_PORT || DEFAULT_NODE1_WS_PORT; + const node2Port: string = process.env.NODE2_WS_PORT || DEFAULT_NODE2_WS_PORT; + + // Static peer IDs from --nodekey configuration + // cspell:ignore nodekey + const peer1: string = NODE1_PEER_ID; + const peer2: string = NODE2_PEER_ID; + + // Print TypeScript-style config + printWakuConfig(colors, node1Port, node2Port, peer1, peer2, clusterId); +} catch (error: unknown) { + const err = error as { cause?: { code?: string }; message?: string }; + if (err.cause?.code === "ECONNREFUSED") { + process.stderr.write( + `${colors.yellow}⚠${colors.reset} Nodes are still starting. Try again in a few seconds.\n` + ); + process.exit(1); + } else { + process.stderr.write( + `${colors.yellow}✗${colors.reset} Error: ${err.message || String(error)}\n` + ); + process.exit(1); + } +} diff --git a/packages/run/scripts/logs.ts b/packages/run/scripts/logs.ts new file mode 100644 index 0000000000..864a8d6422 --- /dev/null +++ b/packages/run/scripts/logs.ts @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +import { execSync } from "child_process"; +import { dirname, join } from "path"; +import { fileURLToPath } from "url"; + +import { getProjectName } from "../src/utils.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const packageRoot = __dirname.includes("dist") + ? join(__dirname, "..", "..") + : join(__dirname, ".."); + +try { + const projectName = getProjectName(packageRoot); + execSync(`docker compose --project-name ${projectName} logs -f`, { + cwd: packageRoot, + stdio: "inherit", + env: { ...process.env, COMPOSE_PROJECT_NAME: projectName } + }); +} catch (error: unknown) { + const err = error as { message?: string }; + process.stderr.write(`Error viewing logs: ${err.message || String(error)}\n`); + process.exit(1); +} diff --git a/packages/run/scripts/start.ts b/packages/run/scripts/start.ts new file mode 100755 index 0000000000..38a6945967 --- /dev/null +++ b/packages/run/scripts/start.ts @@ -0,0 +1,172 @@ +#!/usr/bin/env node + +import { execSync } from "child_process"; +import { dirname, join } from "path"; +import { fileURLToPath } from "url"; + +import { + DEFAULT_CLUSTER_ID, + DEFAULT_NODE1_WS_PORT, + DEFAULT_NODE2_WS_PORT, + DEFAULT_NWAKU_IMAGE, + NODE1_PEER_ID, + NODE2_PEER_ID, + POSTGRES_IMAGE, + STARTUP_WAIT_MS +} from "../src/constants.js"; +import { getProjectName, printWakuConfig } from "../src/utils.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const packageRoot = __dirname.includes("dist") + ? join(__dirname, "..", "..") + : join(__dirname, ".."); + +interface Colors { + reset: string; + cyan: string; + green: string; + blue: string; + gray: string; + yellow: string; +} + +// ANSI color codes +const colors: Colors = { + reset: "\x1b[0m", + cyan: "\x1b[36m", + green: "\x1b[32m", + blue: "\x1b[34m", + gray: "\x1b[90m", + yellow: "\x1b[33m" +}; + +function checkAndPullImages(): void { + const nwakuImage = process.env.NWAKU_IMAGE || DEFAULT_NWAKU_IMAGE; + const postgresImage = POSTGRES_IMAGE; + const images = [ + { name: nwakuImage, label: "nwaku" }, + { name: postgresImage, label: "postgres" } + ]; + + for (const { name, label } of images) { + try { + // Check if image exists locally + const imageId = execSync(`docker images -q ${name}`, { + encoding: "utf-8" + }).trim(); + + if (!imageId) { + // Image doesn't exist, pull it + process.stdout.write( + `${colors.cyan}Pulling ${label} image (${name})...${colors.reset}\n` + ); + execSync(`docker pull ${name}`, { stdio: "inherit" }); + process.stdout.write( + `${colors.green}✓${colors.reset} ${label} image ready\n` + ); + } + } catch (error) { + process.stderr.write( + `${colors.yellow}⚠${colors.reset} Failed to check/pull ${label} image. Continuing anyway...\n` + ); + } + } +} + +async function waitWithProgress(ms: number): Promise { + const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; + const startTime = Date.now(); + let frameIndex = 0; + + return new Promise((resolve) => { + const interval = setInterval(() => { + const elapsed = Date.now() - startTime; + + if (elapsed >= ms) { + clearInterval(interval); + process.stdout.write("\r" + " ".repeat(50) + "\r"); + resolve(); + return; + } + + const frame = frames[frameIndex % frames.length]; + process.stdout.write( + `\r${colors.cyan}${frame}${colors.reset} Waiting for nodes to start...` + ); + frameIndex++; + }, 100); + }); +} + +process.stdout.write( + `${colors.cyan}Starting local Waku development environment...${colors.reset}\n` +); + +try { + // Check and pull images if needed + checkAndPullImages(); + + // Start docker compose from package root + const projectName = getProjectName(packageRoot); + execSync(`docker compose --project-name ${projectName} up -d`, { + cwd: packageRoot, + stdio: ["ignore", "ignore", "pipe"], + encoding: "utf-8", + env: { ...process.env, COMPOSE_PROJECT_NAME: projectName } + }); + + // Wait for nodes to be ready + await waitWithProgress(STARTUP_WAIT_MS); + + // Get cluster config from env or defaults + const clusterId: string = process.env.CLUSTER_ID || DEFAULT_CLUSTER_ID; + const node1Port: string = process.env.NODE1_WS_PORT || DEFAULT_NODE1_WS_PORT; + const node2Port: string = process.env.NODE2_WS_PORT || DEFAULT_NODE2_WS_PORT; + + // Static peer IDs from --nodekey configuration + // cspell:ignore nodekey + const peer1: string = NODE1_PEER_ID; + const peer2: string = NODE2_PEER_ID; + + // Print TypeScript-style config + process.stdout.write( + `${colors.green}✓${colors.reset} Network started successfully!\n\n` + ); + process.stdout.write( + `${colors.gray}Copy this into your application:${colors.reset}\n\n` + ); + + printWakuConfig(colors, node1Port, node2Port, peer1, peer2, clusterId); + process.stdout.write(`\n`); + process.stdout.write(`${colors.gray}Management:${colors.reset}\n`); + + // Detect if running via npx (published package) or npm run (development) + const isPublished = __dirname.includes("dist"); + const cmdPrefix = isPublished ? "npx @waku/run" : "npm run"; + + process.stdout.write( + ` ${colors.cyan}${cmdPrefix} test${colors.reset} - Test network with a message\n` + ); + process.stdout.write( + ` ${colors.cyan}${cmdPrefix} logs${colors.reset} - View logs\n` + ); + process.stdout.write( + ` ${colors.cyan}${cmdPrefix} info${colors.reset} - Show config again\n` + ); + process.stdout.write( + ` ${colors.cyan}${cmdPrefix} stop${colors.reset} - Stop network\n` + ); +} catch (error: unknown) { + const err = error as { cause?: { code?: string }; message?: string }; + if (err.cause?.code === "ECONNREFUSED") { + process.stderr.write( + `${colors.yellow}⚠${colors.reset} Nodes are still starting up. Run ${colors.cyan}npm run info${colors.reset} in a few seconds.\n` + ); + } else { + process.stderr.write( + `${colors.yellow}✗${colors.reset} Error: ${err.message || String(error)}\n` + ); + } + process.exit(1); +} diff --git a/packages/run/scripts/stop.ts b/packages/run/scripts/stop.ts new file mode 100644 index 0000000000..dee6528033 --- /dev/null +++ b/packages/run/scripts/stop.ts @@ -0,0 +1,28 @@ +#!/usr/bin/env node + +import { execSync } from "child_process"; +import { dirname, join } from "path"; +import { fileURLToPath } from "url"; + +import { getProjectName } from "../src/utils.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const packageRoot = __dirname.includes("dist") + ? join(__dirname, "..", "..") + : join(__dirname, ".."); + +try { + const projectName = getProjectName(packageRoot); + execSync(`docker compose --project-name ${projectName} down`, { + cwd: packageRoot, + stdio: "inherit", + env: { ...process.env, COMPOSE_PROJECT_NAME: projectName } + }); +} catch (error: unknown) { + const err = error as { message?: string }; + process.stderr.write( + `Error stopping network: ${err.message || String(error)}\n` + ); + process.exit(1); +} diff --git a/packages/run/scripts/test.ts b/packages/run/scripts/test.ts new file mode 100644 index 0000000000..15809f8928 --- /dev/null +++ b/packages/run/scripts/test.ts @@ -0,0 +1,122 @@ +#!/usr/bin/env node + +import { execSync } from "child_process"; +import { dirname, join } from "path"; +import { fileURLToPath } from "url"; + +import { Protocols } from "@waku/sdk"; + +import { WakuTestClient } from "../src/test-client.js"; +import { getProjectName } from "../src/utils.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const packageRoot = __dirname.includes("dist") + ? join(__dirname, "..", "..") + : join(__dirname, ".."); + +interface Colors { + reset: string; + cyan: string; + green: string; + red: string; + yellow: string; +} + +// ANSI color codes +const colors: Colors = { + reset: "\x1b[0m", + cyan: "\x1b[36m", + green: "\x1b[32m", + red: "\x1b[31m", + yellow: "\x1b[33m" +}; + +async function main(): Promise { + let client: WakuTestClient | null = null; + + try { + // Check if containers are running + const projectName = getProjectName(packageRoot); + const output: string = execSync( + `docker compose --project-name ${projectName} ps --quiet`, + { + cwd: packageRoot, + encoding: "utf-8", + env: { ...process.env, COMPOSE_PROJECT_NAME: projectName } + } + ).trim(); + + if (!output) { + process.stderr.write( + `${colors.red}✗${colors.reset} No nodes running. Start with: ${colors.cyan}npx @waku/run start${colors.reset}\n` + ); + process.exit(1); + } + + process.stdout.write( + `${colors.cyan}Testing local Waku network...${colors.reset}\n\n` + ); + + // Step 1: Create client + process.stdout.write( + `${colors.cyan}→${colors.reset} Creating Waku light node...\n` + ); + client = new WakuTestClient(); + + // Step 2: Start and connect + process.stdout.write(`${colors.cyan}→${colors.reset} Starting node...\n`); + await client.start(); + + // Step 3: Wait for peers + process.stdout.write( + `${colors.cyan}→${colors.reset} Waiting for peers...\n` + ); + await client.waku!.waitForPeers([Protocols.LightPush]); + const connectedPeers = client.waku!.libp2p.getPeers().length; + process.stdout.write( + `${colors.green}✓${colors.reset} Connected to ${connectedPeers} peer(s)\n` + ); + + // Step 4: Send test message + process.stdout.write( + `${colors.cyan}→${colors.reset} Sending lightpush message...\n` + ); + const result = await client.sendTestMessage("Test from @waku/run"); + + if (result.success) { + process.stdout.write( + `${colors.green}✓${colors.reset} Message sent successfully to ${result.messagesSent} peer(s)\n` + ); + process.stdout.write( + `\n${colors.green}✓ All tests passed!${colors.reset}\n` + ); + process.stdout.write( + `${colors.cyan}The local Waku network is working correctly.${colors.reset}\n` + ); + } else { + process.stderr.write( + `${colors.red}✗${colors.reset} Failed to send message: ${result.error || "Unknown error"}\n` + ); + process.stderr.write( + ` Sent: ${result.messagesSent}, Failed: ${result.failures}\n` + ); + process.exit(1); + } + } catch (error: unknown) { + const err = error as { message?: string }; + process.stderr.write( + `${colors.red}✗${colors.reset} Test failed: ${err.message || String(error)}\n` + ); + process.exit(1); + } finally { + if (client) { + await client.stop(); + } + } +} + +main().catch((error) => { + process.stderr.write(`Unexpected error: ${String(error)}\n`); + process.exit(1); +}); diff --git a/packages/run/src/cli.ts b/packages/run/src/cli.ts new file mode 100644 index 0000000000..55fe2161c1 --- /dev/null +++ b/packages/run/src/cli.ts @@ -0,0 +1,40 @@ +#!/usr/bin/env node + +import { spawn } from "child_process"; +import { dirname, join } from "path"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const command = process.argv[2]; + +const scriptMap: Record = { + start: join(__dirname, "..", "scripts", "start.js"), + stop: join(__dirname, "..", "scripts", "stop.js"), + info: join(__dirname, "..", "scripts", "info.js"), + logs: join(__dirname, "..", "scripts", "logs.js"), + test: join(__dirname, "..", "scripts", "test.js") +}; + +if (!command || !scriptMap[command]) { + process.stderr.write("Usage: @waku/run \n"); + process.stderr.write("\n"); + process.stderr.write("Commands:\n"); + process.stderr.write(" start Start the local Waku network\n"); + process.stderr.write(" stop Stop the local Waku network\n"); + process.stderr.write(" info Show connection info for running network\n"); + process.stderr.write(" logs View logs from running network\n"); + process.stderr.write(" test Test the network by sending a message\n"); + process.exit(1); +} + +const scriptPath = scriptMap[command]; +const child = spawn("node", [scriptPath], { + stdio: "inherit", + env: process.env +}); + +child.on("exit", (code) => { + process.exit(code || 0); +}); diff --git a/packages/run/src/constants.ts b/packages/run/src/constants.ts new file mode 100644 index 0000000000..9b9721d959 --- /dev/null +++ b/packages/run/src/constants.ts @@ -0,0 +1,40 @@ +/** + * Static configuration constants for the local Waku development environment. + * These values are derived from the --nodekey configuration in docker-compose.yml + * cspell:ignore nodekey + */ + +// Node private keys (from docker-compose.yml --nodekey) +export const NODE1_PRIVATE_KEY = + "e419c3cf4f09ac3babdf61856e6faa0e0c6a7d97674d5401a0114616549c7632"; +export const NODE2_PRIVATE_KEY = + "50632ab0efd313bfb4aa842de716f03dacd181c863770abd145e3409290fdaa7"; + +// Derived peer IDs (libp2p identities from the private keys) +export const NODE1_PEER_ID = + "16Uiu2HAmF6oAsd23RMAnZb3NJgxXrExxBTPMdEoih232iAZkviU2"; +export const NODE2_PEER_ID = + "16Uiu2HAm5aZU47YkiUoARqivbCXwuFPzFFXXiURAorySqAQbL6EQ"; + +// Static IP addresses (from docker-compose.yml network configuration) +export const NODE1_IP = "172.20.0.10"; +export const NODE2_IP = "172.20.0.11"; + +// Default WebSocket ports for local nodes +export const DEFAULT_NODE1_WS_PORT = "60000"; +export const DEFAULT_NODE2_WS_PORT = "60001"; + +// Default REST API ports for local nodes +export const DEFAULT_NODE1_REST_PORT = "8646"; +export const DEFAULT_NODE2_REST_PORT = "8647"; + +// Docker images +export const DEFAULT_NWAKU_IMAGE = "wakuorg/nwaku:v0.36.0"; +export const POSTGRES_IMAGE = "postgres:15.4-alpine3.18"; + +// Timing configuration +export const STARTUP_WAIT_MS = 20000; // Time to wait for nodes to start + +// Network configuration +export const DEFAULT_CLUSTER_ID = "0"; +export const DEFAULT_NUM_SHARDS_IN_CLUSTER = 8; diff --git a/packages/run/src/run-tests.js b/packages/run/src/run-tests.js new file mode 100644 index 0000000000..0c4ff82c6e --- /dev/null +++ b/packages/run/src/run-tests.js @@ -0,0 +1,30 @@ +#!/usr/bin/env node + +import { spawn } from "child_process"; + +const mochaArgs = [ + "mocha", + "--require", + "ts-node/register", + "--project", + "./tsconfig.json", + ...process.argv.slice(2) +]; + +// Run mocha tests +const mocha = spawn("npx", mochaArgs, { + stdio: "inherit", + env: { + ...process.env, + NODE_ENV: "test" + } +}); + +mocha.on("error", (error) => { + console.log(`Error running mocha tests: ${error.message}`); // eslint-disable-line no-console + process.exit(1); +}); + +mocha.on("exit", (code) => { + process.exit(code || 0); +}); diff --git a/packages/run/src/test-client.ts b/packages/run/src/test-client.ts new file mode 100644 index 0000000000..47880fcf93 --- /dev/null +++ b/packages/run/src/test-client.ts @@ -0,0 +1,126 @@ +import { createEncoder } from "@waku/core"; +import type { LightNode } from "@waku/interfaces"; +import { createLightNode } from "@waku/sdk"; +import { createRoutingInfo } from "@waku/utils"; + +import { + DEFAULT_CLUSTER_ID, + DEFAULT_NODE1_WS_PORT, + DEFAULT_NODE2_WS_PORT, + DEFAULT_NUM_SHARDS_IN_CLUSTER, + NODE1_PEER_ID, + NODE2_PEER_ID +} from "./constants.js"; + +export interface WakuTestClientOptions { + node1Port?: string; + node2Port?: string; + clusterId?: number; + numShardsInCluster?: number; + contentTopic?: string; +} + +export interface TestResult { + success: boolean; + connectedPeers: number; + messagesSent: number; + failures: number; + error?: string; +} + +export class WakuTestClient { + public waku: LightNode | null = null; + private options: Required; + + public constructor(options: WakuTestClientOptions = {}) { + this.options = { + node1Port: + options.node1Port || process.env.NODE1_WS_PORT || DEFAULT_NODE1_WS_PORT, + node2Port: + options.node2Port || process.env.NODE2_WS_PORT || DEFAULT_NODE2_WS_PORT, + clusterId: options.clusterId ?? parseInt(DEFAULT_CLUSTER_ID), + numShardsInCluster: + options.numShardsInCluster ?? DEFAULT_NUM_SHARDS_IN_CLUSTER, + contentTopic: options.contentTopic || "/waku-run/1/test/proto" + }; + } + + /** + * Create and start the Waku light node + */ + public async start(): Promise { + const { node1Port, node2Port, clusterId, numShardsInCluster } = + this.options; + + const networkConfig = { + clusterId, + numShardsInCluster + }; + + this.waku = await createLightNode({ + defaultBootstrap: false, + bootstrapPeers: [ + `/ip4/127.0.0.1/tcp/${node1Port}/ws/p2p/${NODE1_PEER_ID}`, + `/ip4/127.0.0.1/tcp/${node2Port}/ws/p2p/${NODE2_PEER_ID}` + ], + networkConfig, + numPeersToUse: 2, + libp2p: { + filterMultiaddrs: false + } + }); + + await this.waku.start(); + } + + /** + * Send a test message via lightpush + */ + public async sendTestMessage( + payload: string = "Hello Waku!" + ): Promise { + if (!this.waku) { + throw new Error("Waku node not started. Call start() first."); + } + + try { + const { contentTopic, clusterId, numShardsInCluster } = this.options; + const networkConfig = { clusterId, numShardsInCluster }; + + const routingInfo = createRoutingInfo(networkConfig, { contentTopic }); + const encoder = createEncoder({ contentTopic, routingInfo }); + + const result = await this.waku.lightPush.send(encoder, { + payload: new TextEncoder().encode(payload) + }); + + const connectedPeers = this.waku.libp2p.getPeers().length; + + return { + success: + result.successes.length > 0 && (result.failures?.length || 0) === 0, + connectedPeers, + messagesSent: result.successes.length, + failures: result.failures?.length || 0 + }; + } catch (error) { + return { + success: false, + connectedPeers: this.waku.libp2p.getPeers().length, + messagesSent: 0, + failures: 0, + error: error instanceof Error ? error.message : String(error) + }; + } + } + + /** + * Stop the Waku node + */ + public async stop(): Promise { + if (this.waku) { + await this.waku.stop(); + this.waku = null; + } + } +} diff --git a/packages/run/src/utils.ts b/packages/run/src/utils.ts new file mode 100644 index 0000000000..a5da9e052a --- /dev/null +++ b/packages/run/src/utils.ts @@ -0,0 +1,63 @@ +import { readFileSync } from "fs"; +import { join } from "path"; + +import { DEFAULT_NUM_SHARDS_IN_CLUSTER } from "./constants.js"; + +export function getProjectName(packageRoot: string): string { + const packageJsonPath = join(packageRoot, "package.json"); + const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")); + // Docker Compose project names must consist only of lowercase alphanumeric characters, hyphens, and underscores + const name = packageJson.name.replace("@", "").replace("/", "-"); + const version = packageJson.version.replace(/\./g, "-"); + return `${name}-${version}`; +} + +interface Colors { + reset: string; + cyan: string; + blue: string; + yellow: string; +} + +export function printWakuConfig( + colors: Colors, + node1Port: string, + node2Port: string, + peer1: string, + peer2: string, + clusterId: string +): void { + process.stdout.write( + `${colors.blue}import${colors.reset} { createLightNode } ${colors.blue}from${colors.reset} ${colors.yellow}"@waku/sdk"${colors.reset};\n` + ); + process.stdout.write(`\n`); + process.stdout.write( + `${colors.blue}const${colors.reset} waku = ${colors.blue}await${colors.reset} createLightNode({\n` + ); + process.stdout.write( + ` defaultBootstrap: ${colors.cyan}false${colors.reset},\n` + ); + process.stdout.write(` bootstrapPeers: [\n`); + process.stdout.write( + ` ${colors.yellow}"/ip4/127.0.0.1/tcp/${node1Port}/ws/p2p/${peer1}"${colors.reset},\n` + ); + process.stdout.write( + ` ${colors.yellow}"/ip4/127.0.0.1/tcp/${node2Port}/ws/p2p/${peer2}"${colors.reset}\n` + ); + process.stdout.write(` ],\n`); + process.stdout.write(` numPeersToUse: ${colors.cyan}2${colors.reset},\n`); + process.stdout.write(` libp2p: {\n`); + process.stdout.write( + ` filterMultiaddrs: ${colors.cyan}false${colors.reset}\n` + ); + process.stdout.write(` },\n`); + process.stdout.write(` networkConfig: {\n`); + process.stdout.write( + ` clusterId: ${colors.cyan}${clusterId}${colors.reset},\n` + ); + process.stdout.write( + ` numShardsInCluster: ${colors.cyan}${DEFAULT_NUM_SHARDS_IN_CLUSTER}${colors.reset}\n` + ); + process.stdout.write(` }\n`); + process.stdout.write(`});\n`); +} diff --git a/packages/run/tests/basic.spec.ts b/packages/run/tests/basic.spec.ts new file mode 100644 index 0000000000..0ec9fc0441 --- /dev/null +++ b/packages/run/tests/basic.spec.ts @@ -0,0 +1,120 @@ +import { execSync } from "child_process"; +import { dirname, join } from "path"; +import { fileURLToPath } from "url"; + +import { Protocols } from "@waku/sdk"; +import { expect } from "chai"; + +import { + DEFAULT_NODE1_REST_PORT, + DEFAULT_NODE2_REST_PORT +} from "../src/constants.js"; +import { WakuTestClient } from "../src/test-client.js"; +import { getProjectName } from "../src/utils.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const packageRoot = join(__dirname, ".."); + +describe("Waku Run - Basic Test", function () { + this.timeout(90000); + + let client: WakuTestClient; + + before(async function () { + // Step 1: Start the nodes + const projectName = getProjectName(packageRoot); + execSync(`docker compose --project-name ${projectName} up -d`, { + cwd: packageRoot, + stdio: "inherit", + env: { ...process.env, COMPOSE_PROJECT_NAME: projectName } + }); + + // Wait for nodes to be ready + const maxRetries = 30; + const retryDelay = 2000; + let ready = false; + + for (let i = 0; i < maxRetries; i++) { + try { + await fetch( + `http://127.0.0.1:${DEFAULT_NODE1_REST_PORT}/debug/v1/info` + ); + await fetch( + `http://127.0.0.1:${DEFAULT_NODE2_REST_PORT}/debug/v1/info` + ); + ready = true; + break; + } catch { + await new Promise((resolve) => setTimeout(resolve, retryDelay)); + } + } + + if (!ready) { + throw new Error("Nodes failed to start within expected time"); + } + + // Nodes automatically connect via --staticnode configuration + // cspell:ignore staticnode + // Wait for nwaku nodes to connect to each other + let connected = false; + for (let i = 0; i < 15; i++) { + try { + const peers = await fetch( + `http://127.0.0.1:${DEFAULT_NODE1_REST_PORT}/admin/v1/peers` + ).then((r) => r.json()); + if (peers.length > 0 && peers[0].connected === "Connected") { + connected = true; + break; + } + } catch { + // Ignore errors + } + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + if (!connected) { + throw new Error("Nwaku nodes failed to connect to each other"); + } + }); + + after(async function () { + // Step 4: Stop the nodes + if (client) { + await client.stop(); + } + const projectName = getProjectName(packageRoot); + execSync(`docker compose --project-name ${projectName} down`, { + cwd: packageRoot, + stdio: "inherit", + env: { ...process.env, COMPOSE_PROJECT_NAME: projectName } + }); + }); + + it("should connect to both nodes and send lightpush message to both peers", async function () { + // Step 2: Connect to nodes via js-waku using WakuTestClient + client = new WakuTestClient({ + contentTopic: "/test/1/basic/proto" + }); + + await client.start(); + + // Wait for both peers to be connected + await client.waku!.waitForPeers([Protocols.LightPush]); + const connectedPeers = client.waku!.libp2p.getPeers().length; + expect(connectedPeers).to.equal( + 2, + "Should be connected to both nwaku nodes" + ); + + // Step 3: Send lightpush message - it should be sent to both peers + const result = await client.sendTestMessage("Hello Waku!"); + + expect(result.success).to.be.true; + expect(result.messagesSent).to.equal( + 2, + "Message should be sent to both peers" + ); + expect(result.failures).to.equal(0, "Should have no failures"); + }); +}); diff --git a/packages/run/tsconfig.dev.json b/packages/run/tsconfig.dev.json new file mode 100644 index 0000000000..2e8879a53e --- /dev/null +++ b/packages/run/tsconfig.dev.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.dev", + "compilerOptions": { + "rootDir": "." + }, + "include": ["src", "scripts", "tests"] +} diff --git a/packages/run/tsconfig.json b/packages/run/tsconfig.json new file mode 100644 index 0000000000..84417ad4b5 --- /dev/null +++ b/packages/run/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "dist/", + "rootDir": ".", + "tsBuildInfoFile": "dist/.tsbuildinfo" + }, + "include": ["src", "scripts"], + "exclude": ["tests", "dist", "node_modules"] +} diff --git a/release-please-config.json b/release-please-config.json index 622f9b6933..b07c32ed39 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -24,6 +24,7 @@ "packages/discovery": {}, "packages/sds": {}, "packages/rln": {}, - "packages/react": {} + "packages/react": {}, + "packages/run": {} } } \ No newline at end of file From e2c93640537f0375fd67feb954fc1c10e00cb0b8 Mon Sep 17 00:00:00 2001 From: markoburcul Date: Thu, 23 Oct 2025 12:53:04 +0200 Subject: [PATCH 5/8] ci: move it to a container Add nix flake and use it in the pipeline for reliability and reproducability. Referenced issue: * https://github.com/status-im/infra-ci/issues/188 --- README.md | 9 +++++++++ ci/Jenkinsfile | 26 +++++++++++++++++++++----- flake.lock | 26 ++++++++++++++++++++++++++ flake.nix | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/README.md b/README.md index 204d78181a..1cbf7e60ef 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,15 @@ npm install npm run doc ``` +# Using Nix shell +```shell +git clone https://github.com/waku-org/js-waku.git +cd js-waku +nix develop +npm install +npm run doc +``` + ## Bugs, Questions & Features If you encounter any bug or would like to propose new features, feel free to [open an issue](https://github.com/waku-org/js-waku/issues/new/). diff --git a/ci/Jenkinsfile b/ci/Jenkinsfile index 84740abd09..0b09d74ab0 100644 --- a/ci/Jenkinsfile +++ b/ci/Jenkinsfile @@ -1,5 +1,13 @@ pipeline { - agent { label 'linux' } + agent { + docker { + label 'linuxcontainer' + image 'harbor.status.im/infra/ci-build-containers:linux-base-1.0.0' + args '--volume=/nix:/nix ' + + '--volume=/etc/nix:/etc/nix ' + + '--user jenkins' + } + } options { disableConcurrentBuilds() @@ -21,19 +29,25 @@ pipeline { stages { stage('Deps') { steps { - sh 'npm install' + script { + nix.develop('npm install', pure: true) + } } } stage('Packages') { steps { - sh 'npm run build' + script { + nix.develop('npm run build', pure: true) + } } } stage('Build') { steps { - sh 'npm run doc' + script { + nix.develop('npm run doc', pure: true) + } } } @@ -41,7 +55,9 @@ pipeline { when { expression { GIT_BRANCH.endsWith('master') } } steps { sshagent(credentials: ['status-im-auto-ssh']) { - sh 'npm run deploy' + script { + nix.develop('npm run deploy', pure: true) + } } } } diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000..03dff37982 --- /dev/null +++ b/flake.lock @@ -0,0 +1,26 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1761016216, + "narHash": "sha256-G/iC4t/9j/52i/nm+0/4ybBmAF4hzR8CNHC75qEhjHo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "481cf557888e05d3128a76f14c76397b7d7cc869", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-25.05", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000..4b4f6e8633 --- /dev/null +++ b/flake.nix @@ -0,0 +1,33 @@ +{ + description = "Nix flake development shell."; + + inputs = { + nixpkgs.url = "nixpkgs/nixos-25.05"; + }; + + outputs = + { self, nixpkgs }: + let + supportedSystems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + forEachSystem = nixpkgs.lib.genAttrs supportedSystems; + pkgsFor = forEachSystem (system: import nixpkgs { inherit system; }); + in + rec { + formatter = forEachSystem (system: pkgsFor.${system}.nixpkgs-fmt); + + devShells = forEachSystem (system: { + default = pkgsFor.${system}.mkShellNoCC { + packages = with pkgsFor.${system}.buildPackages; [ + git # 2.44.1 + openssh # 9.7p1 + nodejs_20 # v20.15.1 + ]; + }; + }); + }; +} From 0daa81d3d723d17f614c11e1e3f63255297c4fe5 Mon Sep 17 00:00:00 2001 From: Arseniy Klempner Date: Mon, 27 Oct 2025 13:47:14 -0700 Subject: [PATCH 6/8] fix: run npm audit fix (#2696) * fix: run npm audit fix * fix: bump playwright image in CI --- .github/workflows/ci.yml | 2 +- .github/workflows/playwright.yml | 2 +- package-lock.json | 1497 ++++++++---------------------- 3 files changed, 374 insertions(+), 1127 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a04e6a560..8fa6efcb79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: browser: runs-on: ubuntu-latest container: - image: mcr.microsoft.com/playwright:v1.53.1-jammy + image: mcr.microsoft.com/playwright:v1.56.1-jammy env: HOME: "/root" steps: diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 57d53bd8c9..bcd209a1b9 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -17,7 +17,7 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-latest container: - image: mcr.microsoft.com/playwright:v1.53.1-jammy + image: mcr.microsoft.com/playwright:v1.56.1-jammy steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 diff --git a/package-lock.json b/package-lock.json index aec015a416..8c1c0099ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1976,6 +1976,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" @@ -2066,9 +2067,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", - "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", "cpu": [ "ppc64" ], @@ -2082,9 +2083,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", - "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", "cpu": [ "arm" ], @@ -2098,9 +2099,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", - "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", "cpu": [ "arm64" ], @@ -2114,9 +2115,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", - "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", "cpu": [ "x64" ], @@ -2130,9 +2131,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", - "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", "cpu": [ "arm64" ], @@ -2146,9 +2147,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", - "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", "cpu": [ "x64" ], @@ -2162,9 +2163,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", - "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", "cpu": [ "arm64" ], @@ -2178,9 +2179,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", - "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", "cpu": [ "x64" ], @@ -2194,9 +2195,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", - "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", "cpu": [ "arm" ], @@ -2210,9 +2211,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", - "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", "cpu": [ "arm64" ], @@ -2226,9 +2227,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", - "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", "cpu": [ "ia32" ], @@ -2242,9 +2243,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", - "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", "cpu": [ "loong64" ], @@ -2258,9 +2259,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", - "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", "cpu": [ "mips64el" ], @@ -2274,9 +2275,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", - "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", "cpu": [ "ppc64" ], @@ -2290,9 +2291,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", - "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", "cpu": [ "riscv64" ], @@ -2306,9 +2307,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", - "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", "cpu": [ "s390x" ], @@ -2322,9 +2323,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", - "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", "cpu": [ "x64" ], @@ -2338,9 +2339,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", - "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", "cpu": [ "arm64" ], @@ -2354,9 +2355,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", - "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", "cpu": [ "x64" ], @@ -2370,9 +2371,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", - "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", "cpu": [ "arm64" ], @@ -2386,9 +2387,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", - "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", "cpu": [ "x64" ], @@ -2401,10 +2402,26 @@ "node": ">=18" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", - "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", "cpu": [ "x64" ], @@ -2418,9 +2435,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", - "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", "cpu": [ "arm64" ], @@ -2434,9 +2451,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", - "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", "cpu": [ "ia32" ], @@ -2450,9 +2467,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", - "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", "cpu": [ "x64" ], @@ -2613,12 +2630,12 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz", - "integrity": "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.0", + "@eslint/core": "^0.15.2", "levn": "^0.4.1" }, "engines": { @@ -2626,9 +2643,9 @@ } }, "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz", - "integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -3829,6 +3846,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", @@ -4334,27 +4352,6 @@ "ws": "^8.18.2" } }, - "node_modules/@libp2p/websockets/node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/@multiformats/dns": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.6.tgz", @@ -4714,12 +4711,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.53.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.1.tgz", - "integrity": "sha512-Z4c23LHV0muZ8hfv4jw6HngPJkbbtZxTkxPNIg7cJcTc9C28N/p2q7g3JZS2SiKBBHJ3uM1dgDye66bB7LEk5w==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", + "integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==", "license": "Apache-2.0", "dependencies": { - "playwright": "1.53.1" + "playwright": "1.56.1" }, "bin": { "playwright": "cli.js" @@ -4846,18 +4843,18 @@ "license": "BSD-3-Clause" }, "node_modules/@puppeteer/browsers": { - "version": "2.10.5", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.5.tgz", - "integrity": "sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==", + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.10.tgz", + "integrity": "sha512-3ZG500+ZeLql8rE0hjfhkycJjDj0pI/btEh3L9IkWUYcOrgP0xCNRq3HbtbqOPbvDhFaAWD88pDFtlLv8ns8gA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "debug": "^4.4.1", + "debug": "^4.4.3", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.7.2", - "tar-fs": "^3.0.8", + "tar-fs": "^3.1.0", "yargs": "^17.7.2" }, "bin": { @@ -6437,24 +6434,28 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, "license": "MIT" }, "node_modules/@tybys/wasm-util": { @@ -6932,18 +6933,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", - "license": "MIT" - }, - "node_modules/@types/strip-json-comments": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", - "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", - "license": "MIT" - }, "node_modules/@types/tail": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@types/tail/-/tail-2.2.3.tgz", @@ -7930,6 +7919,7 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -7939,9 +7929,9 @@ } }, "node_modules/aegir": { - "version": "47.0.17", - "resolved": "https://registry.npmjs.org/aegir/-/aegir-47.0.17.tgz", - "integrity": "sha512-3H6SoEpWI/VeewoYBEMRbTtkK/Kq/tB5pLB3WAcAUA34CAuhJyRwApvb3MSkrcbcttFrSny1/r0L1JDY/kkafQ==", + "version": "47.0.24", + "resolved": "https://registry.npmjs.org/aegir/-/aegir-47.0.24.tgz", + "integrity": "sha512-XpCeRXWv/0DtxwyTnYFHwytxUPsAkEEEmKrhBPmgPgJ00EWw7YPFo4AdN9VbV+Zv2wDWRmTY/ujw4ROioG0MMw==", "license": "Apache-2.0 OR MIT", "dependencies": { "@electron/get": "^4.0.0", @@ -7986,7 +7976,7 @@ "fast-glob": "^3.3.2", "fs-extra": "^11.1.0", "gh-pages": "^6.0.0", - "globby": "^14.0.0", + "globby": "^15.0.0", "is-plain-obj": "^4.1.0", "kleur": "^4.1.4", "latest-version": "^9.0.0", @@ -8006,11 +7996,11 @@ "micromark-extension-gfm-task-list-item": "^2.0.1", "mocha": "^11.0.1", "neostandard": "^0.12.1", - "npm-package-json-lint": "^8.0.0", + "npm-package-json-lint": "^9.0.0", "nyc": "^17.0.0", "p-map": "^7.0.1", - "p-queue": "^8.0.1", - "p-retry": "^6.0.0", + "p-queue": "^9.0.0", + "p-retry": "^7.0.0", "pascalcase": "^2.0.0", "path": "^0.12.7", "playwright-test": "^14.0.0", @@ -8032,8 +8022,8 @@ "typedoc-plugin-mdn-links": "^5.0.2", "typedoc-plugin-mermaid": "^1.12.0", "typedoc-plugin-missing-exports": "^4.0.0", - "typescript": "^5.1.6", - "typescript-docs-verifier": "^2.5.0", + "typescript": "5.8.3", + "typescript-docs-verifier": "^3.0.1", "wherearewe": "^2.0.1", "yargs": "^17.1.1", "yargs-parser": "^21.1.1" @@ -8271,6 +8261,18 @@ "url": "https://eslint.org/donate" } }, + "node_modules/aegir/node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/aegir/node_modules/@types/node": { "version": "22.15.32", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.32.tgz", @@ -8843,6 +8845,12 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/aegir/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/aegir/node_modules/execa": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", @@ -8869,18 +8877,6 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/aegir/node_modules/execa/node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/aegir/node_modules/figures": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", @@ -8970,20 +8966,20 @@ } }, "node_modules/aegir/node_modules/globby": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", - "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-15.0.0.tgz", + "integrity": "sha512-oB4vkQGqlMl682wL1IlWd02tXCbquGWM4voPEI85QmNKCaw8zGTm1f1rubFgkg3Eli2PtKlFgrnmUqasbQWlkw==", "license": "MIT", "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", + "@sindresorhus/merge-streams": "^4.0.0", "fast-glob": "^3.3.3", - "ignore": "^7.0.3", + "ignore": "^7.0.5", "path-type": "^6.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.3.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9183,6 +9179,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/aegir/node_modules/p-queue": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.0.0.tgz", + "integrity": "sha512-KO1RyxstL9g1mK76530TExamZC/S2Glm080Nx8PE5sTd7nlduDQsAfEl4uXX+qZjLiwvDauvzXavufy3+rJ9zQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/aegir/node_modules/p-retry": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-7.1.0.tgz", + "integrity": "sha512-xL4PiFRQa/f9L9ZvR4/gUCRNus4N8YX80ku8kv9Jqz+ZokkiZLM0bcvX0gm1F3PDi9SPRsww1BDsTWgE6Y1GLQ==", + "license": "MIT", + "dependencies": { + "is-network-error": "^1.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/aegir/node_modules/p-timeout": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/aegir/node_modules/path-key": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", @@ -9658,6 +9697,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -9889,6 +9929,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, "license": "MIT" }, "node_modules/argparse": { @@ -10198,14 +10239,14 @@ } }, "node_modules/axios": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", - "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", "dev": true, "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -10418,6 +10459,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11069,6 +11111,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -11093,6 +11136,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -11118,15 +11162,14 @@ } }, "node_modules/chromium-bidi": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.17.tgz", - "integrity": "sha512-BqOuIWUgTPj8ayuBFJUYCCuwIcwjBsb3/614P7tt1bEPJ4i1M0kCdIl0Wi9xhtswBXnfO2bTpTMkHD71H8rJMg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-8.0.0.tgz", + "integrity": "sha512-d1VmE0FD7lxZQHzcDUCKZSNRtRwISXDsdg4HjdTR5+Ll5nQ/vzU12JeNmupD6VWffrPSlrnGhEWlLESKH3VO+g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "mitt": "3.0.1", - "urlpattern-polyfill": "10.0.0", - "zod": "3.22.4" + "mitt": "^3.0.1", + "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" @@ -11811,22 +11854,6 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, - "node_modules/copy-file": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/copy-file/-/copy-file-11.0.0.tgz", - "integrity": "sha512-mFsNh/DIANLqFt5VHZoGirdg7bK5+oTWlhnGu6tgRhzBlnEKWaPX2xrFaLltii/6rmhqFMJqffUgknuRdpYlHw==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.11", - "p-event": "^6.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/core-js-compat": { "version": "3.43.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz", @@ -11898,91 +11925,6 @@ "node": ">=10.0.0" } }, - "node_modules/cpy": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/cpy/-/cpy-11.1.0.tgz", - "integrity": "sha512-QGHetPSSuprVs+lJmMDcivvrBwTKASzXQ5qxFvRC2RFESjjod71bDvFvhxTjDgkNjrrb72AI6JPjfYwxrIy33A==", - "license": "MIT", - "dependencies": { - "copy-file": "^11.0.0", - "globby": "^14.0.2", - "junk": "^4.0.1", - "micromatch": "^4.0.7", - "p-filter": "^4.1.0", - "p-map": "^7.0.2" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cpy/node_modules/globby": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", - "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.3", - "ignore": "^7.0.3", - "path-type": "^6.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cpy/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/cpy/node_modules/path-type": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", - "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cpy/node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cpy/node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -12056,6 +11998,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, "license": "MIT" }, "node_modules/cross-spawn": { @@ -12481,9 +12424,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -13040,9 +12983,9 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1262051", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1262051.tgz", - "integrity": "sha512-YJe4CT5SA8on3Spa+UDtNhEqtuV6Epwz3OZ4HQVLhlRccpZ9/PAYk0/cy/oKxFKRrZPBUPyxympQci4yWNWZ9g==", + "version": "0.0.1495869", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1495869.tgz", + "integrity": "sha512-i+bkd9UYFis40RcnkW7XrOprCujXRAHg62IVh/Ah3G8MmNXpCGt1m0dTFhSdx/AVs8XEMbdOGRwdkR1Bcta8AA==", "dev": true, "license": "BSD-3-Clause" }, @@ -13057,6 +13000,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -13143,9 +13087,9 @@ } }, "node_modules/dockerode/node_modules/tar-fs": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", - "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "license": "MIT", "dependencies": { "chownr": "^1.1.1", @@ -13905,9 +13849,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", - "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -13917,31 +13861,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.5", - "@esbuild/android-arm": "0.25.5", - "@esbuild/android-arm64": "0.25.5", - "@esbuild/android-x64": "0.25.5", - "@esbuild/darwin-arm64": "0.25.5", - "@esbuild/darwin-x64": "0.25.5", - "@esbuild/freebsd-arm64": "0.25.5", - "@esbuild/freebsd-x64": "0.25.5", - "@esbuild/linux-arm": "0.25.5", - "@esbuild/linux-arm64": "0.25.5", - "@esbuild/linux-ia32": "0.25.5", - "@esbuild/linux-loong64": "0.25.5", - "@esbuild/linux-mips64el": "0.25.5", - "@esbuild/linux-ppc64": "0.25.5", - "@esbuild/linux-riscv64": "0.25.5", - "@esbuild/linux-s390x": "0.25.5", - "@esbuild/linux-x64": "0.25.5", - "@esbuild/netbsd-arm64": "0.25.5", - "@esbuild/netbsd-x64": "0.25.5", - "@esbuild/openbsd-arm64": "0.25.5", - "@esbuild/openbsd-x64": "0.25.5", - "@esbuild/sunos-x64": "0.25.5", - "@esbuild/win32-arm64": "0.25.5", - "@esbuild/win32-ia32": "0.25.5", - "@esbuild/win32-x64": "0.25.5" + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" } }, "node_modules/esbuild-plugin-wasm": { @@ -14963,17 +14908,17 @@ } }, "node_modules/estimo": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estimo/-/estimo-3.0.3.tgz", - "integrity": "sha512-qSibrDHo82yvmgeOW7onGgeOzS/nnqa8r2exQ8LyTSH8rAma10VBJE+hPSdukV1nQrqFvEz7BVe5puUK2LZJXg==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/estimo/-/estimo-3.0.5.tgz", + "integrity": "sha512-Q9asaAAM3KZc4Ckr8GMcJWYc3hNCf0KnmhkfzHuAWmqGoPssQoe5Mb8et1CYmmkeMfPTlUyeBHRi53Bedvnl1Q==", "dev": true, "license": "MIT", "dependencies": { - "@sitespeed.io/tracium": "^0.3.3", - "commander": "^12.0.0", - "find-chrome-bin": "2.0.2", - "nanoid": "5.0.7", - "puppeteer-core": "22.6.5" + "@sitespeed.io/tracium": "0.3.3", + "commander": "12.0.0", + "find-chrome-bin": "2.0.4", + "nanoid": "5.1.5", + "puppeteer-core": "24.22.0" }, "bin": { "estimo": "scripts/cli.js" @@ -14982,23 +14927,14 @@ "node": ">=18" } }, - "node_modules/estimo/node_modules/nanoid": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz", - "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==", + "node_modules/estimo/node_modules/commander": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", + "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, "engines": { - "node": "^18 || >=20" + "node": ">=18" } }, "node_modules/estraverse": { @@ -15645,13 +15581,13 @@ } }, "node_modules/find-chrome-bin": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/find-chrome-bin/-/find-chrome-bin-2.0.2.tgz", - "integrity": "sha512-KlggCilbbvgETk/WEq9NG894U8yu4erIW0SjMm1sMPm2xihCHeNoybpzGoxEzHRthwF3XrKOgHYtfqgJzpCH2w==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/find-chrome-bin/-/find-chrome-bin-2.0.4.tgz", + "integrity": "sha512-iKiqIb7FsA0hwnq0vvDay4RsmHUFLvWVquTb59XVlxfHS68XaWZfEjriF2vTZ3k/plicyKZxMJLqxKt10kSOtQ==", "dev": true, "license": "MIT", "dependencies": { - "@puppeteer/browsers": "^2.1.0" + "@puppeteer/browsers": "2.10.10" }, "engines": { "node": ">=18.0.0" @@ -15807,9 +15743,9 @@ } }, "node_modules/form-data": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", - "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, "license": "MIT", "dependencies": { @@ -15946,6 +15882,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -16164,9 +16101,9 @@ } }, "node_modules/get-uri": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", - "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", "dev": true, "license": "MIT", "dependencies": { @@ -17072,15 +17009,11 @@ } }, "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", "dev": true, "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, "engines": { "node": ">= 12" } @@ -17180,6 +17113,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -18482,13 +18416,6 @@ "xmlcreate": "^2.0.4" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true, - "license": "MIT" - }, "node_modules/jsdoc": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", @@ -18645,18 +18572,6 @@ "node": ">=4.0" } }, - "node_modules/junk": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz", - "integrity": "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==", - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/just-extend": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", @@ -20089,6 +20004,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, "license": "ISC" }, "node_modules/map-obj": { @@ -21888,6 +21804,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -22066,35 +21983,35 @@ } }, "node_modules/npm-package-json-lint": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/npm-package-json-lint/-/npm-package-json-lint-8.0.0.tgz", - "integrity": "sha512-44xqAKoV0nXnBYYLGUhMItGZb5tW3cLoW3UZxcsaCOX/YAkECrzOQA5F48oAA51vVE5CqAnsJB2CFvtolzMA3Q==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/npm-package-json-lint/-/npm-package-json-lint-9.0.0.tgz", + "integrity": "sha512-bmyraQyO9wAyFwyr+ouMrbv3mFY/UZY8nlBorgqfTw735QtVfAUBKs7GrO/rvJByoKnnwzQIlIFq9WXdfkSL0g==", "license": "MIT", "dependencies": { "ajv": "^6.12.6", "ajv-errors": "^1.0.1", "chalk": "^4.1.2", "cosmiconfig": "^8.3.6", - "debug": "^4.3.4", + "debug": "^4.3.6", "globby": "^11.1.0", - "ignore": "^5.3.1", + "ignore": "^5.3.2", "is-plain-obj": "^3.0.0", - "jsonc-parser": "^3.2.1", + "jsonc-parser": "^3.3.1", "log-symbols": "^4.1.0", "meow": "^9.0.0", "plur": "^4.0.0", - "semver": "^7.6.2", + "semver": "^7.6.3", "slash": "^3.0.0", "strip-json-comments": "^3.1.1", - "type-fest": "^4.20.0", - "validate-npm-package-name": "^5.0.1" + "type-fest": "^4.26.1", + "validate-npm-package-name": "^6.0.0" }, "bin": { "npmPkgJsonLint": "dist/cli.js" }, "engines": { - "node": ">=18.0.0", - "npm": ">=9.0.0" + "node": ">=20.0.0", + "npm": ">=10.0.0" } }, "node_modules/npm-package-json-lint/node_modules/cosmiconfig": { @@ -26604,12 +26521,12 @@ } }, "node_modules/playwright": { - "version": "1.53.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.1.tgz", - "integrity": "sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", + "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.53.1" + "playwright-core": "1.56.1" }, "bin": { "playwright": "cli.js" @@ -26622,9 +26539,9 @@ } }, "node_modules/playwright-core": { - "version": "1.53.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.1.tgz", - "integrity": "sha512-Z46Oq7tLAyT0lGoFx4DOuB1IA9D1TPj0QkYxpPVUnGDqHHvDpCftu1J2hM2PiWsNMoZh8+LQaarAWcDfPBc6zg==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", + "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -26634,9 +26551,9 @@ } }, "node_modules/playwright-test": { - "version": "14.1.9", - "resolved": "https://registry.npmjs.org/playwright-test/-/playwright-test-14.1.9.tgz", - "integrity": "sha512-L6HkKrcaW+qhaIPPOsoR1HDDR3bQdKeQCcpLkCbebZDG1ikSzMwmF3HAcEtAUXbmRwZcvB1dPoxQi6jlCF/f+A==", + "version": "14.1.12", + "resolved": "https://registry.npmjs.org/playwright-test/-/playwright-test-14.1.12.tgz", + "integrity": "sha512-tEhyXJwIU2f5nK9lNKbKBQ8cuBnux69cfm+tjwGGn3wLQkmMa8IfqCGDV5+s2aVrAPvrxlxSDP1cMr6jX8nhsg==", "license": "MIT", "dependencies": { "acorn-loose": "^8.3.0", @@ -26644,14 +26561,12 @@ "buffer": "^6.0.3", "c8": "^10.1.3", "camelcase": "^8.0.0", - "chokidar": "^3.6.0", - "cpy": "^11.0.0", - "esbuild": "0.23.0", + "chokidar": "^4.0.3", + "esbuild": "0.25.8", "esbuild-plugin-wasm": "^1.1.0", "events": "^3.3.0", "execa": "^9.3.0", "exit-hook": "^4.0.0", - "globby": "^14.0.2", "kleur": "^4.1.5", "lilconfig": "^3.1.3", "lodash": "^4.17.21", @@ -26660,7 +26575,7 @@ "ora": "^8.0.1", "p-timeout": "^6.1.4", "path-browserify": "^1.0.1", - "playwright-core": "1.50.1", + "playwright-core": "1.54.1", "polka": "^0.5.2", "premove": "^4.0.0", "process": "^0.11.10", @@ -26671,6 +26586,7 @@ "stream-browserify": "^3.0.0", "tempy": "^3.1.0", "test-exclude": "^7.0.1", + "tinyglobby": "^0.2.14", "util": "^0.12.5", "v8-to-istanbul": "^9.3.0" }, @@ -26682,390 +26598,6 @@ "node": ">=16.0.0" } }, - "node_modules/playwright-test/node_modules/@esbuild/aix-ppc64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", - "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/android-arm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", - "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/android-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", - "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/android-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", - "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/darwin-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", - "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/darwin-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", - "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/freebsd-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", - "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/freebsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", - "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/linux-arm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", - "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/linux-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", - "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/linux-ia32": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", - "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/linux-loong64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", - "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/linux-mips64el": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", - "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/linux-ppc64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", - "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/linux-riscv64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", - "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/linux-s390x": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", - "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/linux-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", - "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/netbsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", - "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/openbsd-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz", - "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/openbsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", - "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/sunos-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", - "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/win32-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", - "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/win32-ia32": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", - "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-test/node_modules/@esbuild/win32-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", - "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/playwright-test/node_modules/buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", @@ -27102,43 +26634,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/playwright-test/node_modules/esbuild": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", - "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", - "hasInstallScript": true, + "node_modules/playwright-test/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "dependencies": { + "readdirp": "^4.0.1" }, "engines": { - "node": ">=18" + "node": ">= 14.16.0" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.23.0", - "@esbuild/android-arm": "0.23.0", - "@esbuild/android-arm64": "0.23.0", - "@esbuild/android-x64": "0.23.0", - "@esbuild/darwin-arm64": "0.23.0", - "@esbuild/darwin-x64": "0.23.0", - "@esbuild/freebsd-arm64": "0.23.0", - "@esbuild/freebsd-x64": "0.23.0", - "@esbuild/linux-arm": "0.23.0", - "@esbuild/linux-arm64": "0.23.0", - "@esbuild/linux-ia32": "0.23.0", - "@esbuild/linux-loong64": "0.23.0", - "@esbuild/linux-mips64el": "0.23.0", - "@esbuild/linux-ppc64": "0.23.0", - "@esbuild/linux-riscv64": "0.23.0", - "@esbuild/linux-s390x": "0.23.0", - "@esbuild/linux-x64": "0.23.0", - "@esbuild/netbsd-x64": "0.23.0", - "@esbuild/openbsd-arm64": "0.23.0", - "@esbuild/openbsd-x64": "0.23.0", - "@esbuild/sunos-x64": "0.23.0", - "@esbuild/win32-arm64": "0.23.0", - "@esbuild/win32-ia32": "0.23.0", - "@esbuild/win32-x64": "0.23.0" + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/playwright-test/node_modules/execa": { @@ -27210,26 +26718,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/playwright-test/node_modules/globby": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", - "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.3", - "ignore": "^7.0.3", - "path-type": "^6.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/playwright-test/node_modules/human-signals": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", @@ -27239,15 +26727,6 @@ "node": ">=18.18.0" } }, - "node_modules/playwright-test/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/playwright-test/node_modules/is-stream": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", @@ -27288,22 +26767,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/playwright-test/node_modules/path-type": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", - "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/playwright-test/node_modules/playwright-core": { - "version": "1.50.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.1.tgz", - "integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==", + "version": "1.54.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz", + "integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==", "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -27312,16 +26779,17 @@ "node": ">=18" } }, - "node_modules/playwright-test/node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "node_modules/playwright-test/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "license": "MIT", "engines": { - "node": ">=14.16" + "node": ">= 14.18.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/playwright-test/node_modules/strip-final-newline": { @@ -27962,166 +27430,24 @@ } }, "node_modules/puppeteer-core": { - "version": "22.6.5", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.6.5.tgz", - "integrity": "sha512-s0/5XkAWe0/dWISiljdrybjwDCHhgN31Nu/wznOZPKeikgcJtZtbvPKBz0t802XWqfSQnQDt3L6xiAE5JLlfuw==", + "version": "24.22.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.22.0.tgz", + "integrity": "sha512-oUeWlIg0pMz8YM5pu0uqakM+cCyYyXkHBxx9di9OUELu9X9+AYrNGGRLK9tNME3WfN3JGGqQIH3b4/E9LGek/w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.2.2", - "chromium-bidi": "0.5.17", - "debug": "4.3.4", - "devtools-protocol": "0.0.1262051", - "ws": "8.16.0" + "@puppeteer/browsers": "2.10.10", + "chromium-bidi": "8.0.0", + "debug": "^4.4.3", + "devtools-protocol": "0.0.1495869", + "typed-query-selector": "^2.12.0", + "webdriver-bidi-protocol": "0.2.11", + "ws": "^8.18.3" }, "engines": { "node": ">=18" } }, - "node_modules/puppeteer-core/node_modules/@puppeteer/browsers": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.2.2.tgz", - "integrity": "sha512-hZ/JhxPIceWaGSEzUZp83/8M49CoxlkuThfTR7t4AoCu5+ZvJ3vktLm60Otww2TXeROB5igiZ8D9oPQh6ckBVg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.4.0", - "semver": "7.6.0", - "tar-fs": "3.0.5", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.2" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/puppeteer-core/node_modules/bare-fs": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", - "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-events": "^2.0.0", - "bare-path": "^2.0.0", - "bare-stream": "^2.0.0" - } - }, - "node_modules/puppeteer-core/node_modules/bare-os": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", - "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", - "dev": true, - "license": "Apache-2.0", - "optional": true - }, - "node_modules/puppeteer-core/node_modules/bare-path": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", - "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-os": "^2.1.0" - } - }, - "node_modules/puppeteer-core/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/puppeteer-core/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/puppeteer-core/node_modules/proxy-agent": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", - "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.3", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.1", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.2" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/puppeteer-core/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/puppeteer-core/node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/puppeteer-core/node_modules/tar-fs": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.5.tgz", - "integrity": "sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^2.1.1", - "bare-path": "^2.1.0" - } - }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -31136,13 +30462,13 @@ } }, "node_modules/socks": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", - "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "dev": true, "license": "MIT", "dependencies": { - "ip-address": "^9.0.5", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -31306,8 +30632,8 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "devOptional": true, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "optional": true }, "node_modules/ssh-remote-port-forward": { "version": "1.0.4", @@ -31952,9 +31278,9 @@ } }, "node_modules/tar-fs": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.10.tgz", - "integrity": "sha512-C1SwlQGNLe/jPNqapK8epDsXME7CAJR5RL3GcE6KWx1d9OUByzoHVcbu1VPI8tevg9H8Alae0AApHHFGzrD5zA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", "dev": true, "license": "MIT", "dependencies": { @@ -32309,9 +31635,9 @@ } }, "node_modules/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "dev": true, "license": "MIT", "engines": { @@ -32491,6 +31817,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -32530,18 +31857,6 @@ } } }, - "node_modules/tsconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", - "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", - "license": "MIT", - "dependencies": { - "@types/strip-bom": "^3.0.0", - "@types/strip-json-comments": "0.0.30", - "strip-bom": "^3.0.0", - "strip-json-comments": "^2.0.0" - } - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -32554,15 +31869,6 @@ "strip-bom": "^3.0.0" } }, - "node_modules/tsconfig/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -32696,6 +32002,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true, + "license": "MIT" + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -32800,27 +32113,23 @@ } }, "node_modules/typescript-docs-verifier": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/typescript-docs-verifier/-/typescript-docs-verifier-2.5.3.tgz", - "integrity": "sha512-fATV69QQZzIQWDGfUzo2USUcUTK0hPqTm7XZuyHf4QOkZUshnkwDk8TEk2IxaIlHxKjbM+5RtyDgxCtKYycjXA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/typescript-docs-verifier/-/typescript-docs-verifier-3.0.1.tgz", + "integrity": "sha512-ohQsDJ49+tfOsRoJGREUgh6Vk79KVMsk+M3263b2CHqb1eQW8vkeDhBr9euz7nITBwgRk+RujUcxjIhD3kYPpA==", "license": "Apache-2.0", "dependencies": { "chalk": "^4.1.2", - "fs-extra": "^10.0.0", "ora": "^5.4.1", - "strip-ansi": "^7.0.1", - "ts-node": "^10.8.1", - "tsconfig": "^7.0.0", "yargs": "^17.5.1" }, "bin": { "typescript-docs-verifier": "dist/bin/compile-typescript-docs.js" }, "engines": { - "node": ">=12" + "node": ">=20" }, "peerDependencies": { - "typescript": ">3.8.3" + "typescript": ">=4.7.2" } }, "node_modules/typescript-docs-verifier/node_modules/cli-cursor": { @@ -32835,20 +32144,6 @@ "node": ">=8" } }, - "node_modules/typescript-docs-verifier/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/typescript-docs-verifier/node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -32933,18 +32228,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/typescript-docs-verifier/node_modules/ora/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/typescript-docs-verifier/node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -32964,33 +32247,6 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, - "node_modules/typescript-docs-verifier/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/typescript-docs-verifier/node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/typescript-eslint": { "version": "8.34.1", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz", @@ -33327,17 +32583,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/unbzip2-stream": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^5.2.1", - "through": "^2.3.8" - } - }, "node_modules/underscore": { "version": "1.13.7", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", @@ -33610,13 +32855,6 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/urlpattern-polyfill": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", - "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", - "dev": true, - "license": "MIT" - }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -33662,6 +32900,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, "license": "MIT" }, "node_modules/v8-to-istanbul": { @@ -33709,12 +32948,12 @@ } }, "node_modules/validate-npm-package-name": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", - "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", + "integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==", "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/vary": { @@ -33802,6 +33041,13 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.2.11.tgz", + "integrity": "sha512-Y9E1/oi4XMxcR8AT0ZC4OvYntl34SPgwjmELH+owjBr0korAX4jKgZULBWILGCVGdVCQ0dodTToIETozhG8zvA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -34218,9 +33464,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -34418,6 +33664,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -34505,9 +33752,9 @@ } }, "node_modules/zod": { - "version": "3.22.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", - "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "dev": true, "license": "MIT", "funding": { From 115cdd28fe81c957f2aaa235241c0e2a6e23e63f Mon Sep 17 00:00:00 2001 From: Arseniy Klempner Date: Mon, 27 Oct 2025 17:02:48 -0700 Subject: [PATCH 7/8] chore: update hardcoded version of nwaku to 0.36.0, remove unused ci job (#2710) * fix: update hardcoded version of nwaku to 0.36.0 * fix: remove unused/outdated rln-sync-tree job --- .github/workflows/ci.yml | 51 +------------------ package.json | 3 +- packages/reliability-tests/src/run-tests.js | 2 +- packages/tests/src/lib/service_node.ts | 2 +- packages/tests/src/run-tests.js | 2 +- packages/tests/src/sync-rln-tree.js | 56 --------------------- 6 files changed, 6 insertions(+), 110 deletions(-) delete mode 100644 packages/tests/src/sync-rln-tree.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8fa6efcb79..07654a4a3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,65 +71,18 @@ jobs: - run: npm run build:esm - run: npm run test:browser - build_rln_tree: - if: false # This condition disables the job - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - repository: waku-org/js-waku - - uses: actions/setup-node@v3 - with: - node-version: ${{ env.NODE_JS }} - - name: Check for existing RLN tree artifact - id: check-artifact - uses: actions/github-script@v6 - with: - script: | - const artifact = await github.rest.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: context.runId - }); - console.log(artifact); - const foundArtifact = artifact.data.artifacts.find(art => art.name === 'rln_tree.tar.gz'); - if (foundArtifact) { - core.setOutput('artifact_id', foundArtifact.id); - core.setOutput('artifact_found', 'true'); - } else { - core.setOutput('artifact_found', 'false'); - } - - name: Download RLN tree artifact - if: steps.check-artifact.outputs.artifact_found == 'true' - uses: actions/download-artifact@v4 - with: - name: rln_tree.tar.gz - path: /tmp - - uses: ./.github/actions/npm - - name: Sync rln tree and save artifact - run: | - mkdir -p /tmp/rln_tree.db - npm run build:esm - npm run sync-rln-tree - tar -czf rln_tree.tar.gz -C /tmp/rln_tree.db . - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: rln_tree.tar.gz - path: rln_tree.tar.gz - node: uses: ./.github/workflows/test-node.yml secrets: inherit with: - nim_wakunode_image: ${{ inputs.nim_wakunode_image || 'wakuorg/nwaku:v0.35.1' }} + nim_wakunode_image: ${{ inputs.nim_wakunode_image || 'wakuorg/nwaku:v0.36.0' }} test_type: node allure_reports: true node_optional: uses: ./.github/workflows/test-node.yml with: - nim_wakunode_image: ${{ inputs.nim_wakunode_image || 'wakuorg/nwaku:v0.35.1' }} + nim_wakunode_image: ${{ inputs.nim_wakunode_image || 'wakuorg/nwaku:v0.36.0' }} test_type: node-optional node_with_nwaku_master: diff --git a/package.json b/package.json index 02a8034760..feb7dad361 100644 --- a/package.json +++ b/package.json @@ -45,8 +45,7 @@ "doc": "run-s doc:*", "doc:html": "typedoc --options typedoc.cjs", "doc:cname": "echo 'js.waku.org' > docs/CNAME", - "publish": "node ./ci/publish.js", - "sync-rln-tree": "node ./packages/tests/src/sync-rln-tree.js" + "publish": "node ./ci/publish.js" }, "devDependencies": { "@size-limit/preset-big-lib": "^11.0.2", diff --git a/packages/reliability-tests/src/run-tests.js b/packages/reliability-tests/src/run-tests.js index 7de7da2253..4086255552 100644 --- a/packages/reliability-tests/src/run-tests.js +++ b/packages/reliability-tests/src/run-tests.js @@ -3,7 +3,7 @@ import { promisify } from "util"; const execAsync = promisify(exec); -const WAKUNODE_IMAGE = process.env.WAKUNODE_IMAGE || "wakuorg/nwaku:v0.35.1"; +const WAKUNODE_IMAGE = process.env.WAKUNODE_IMAGE || "wakuorg/nwaku:v0.36.0"; async function main() { try { diff --git a/packages/tests/src/lib/service_node.ts b/packages/tests/src/lib/service_node.ts index f16fad2a19..7e089c804d 100644 --- a/packages/tests/src/lib/service_node.ts +++ b/packages/tests/src/lib/service_node.ts @@ -34,7 +34,7 @@ const WAKU_SERVICE_NODE_PARAMS = const NODE_READY_LOG_LINE = "Node setup complete"; export const DOCKER_IMAGE_NAME = - process.env.WAKUNODE_IMAGE || "wakuorg/nwaku:v0.35.1"; + process.env.WAKUNODE_IMAGE || "wakuorg/nwaku:v0.36.0"; const LOG_DIR = "./log"; diff --git a/packages/tests/src/run-tests.js b/packages/tests/src/run-tests.js index 91ec65dc7b..dd97dd36e9 100644 --- a/packages/tests/src/run-tests.js +++ b/packages/tests/src/run-tests.js @@ -3,7 +3,7 @@ import { promisify } from "util"; const execAsync = promisify(exec); -const WAKUNODE_IMAGE = process.env.WAKUNODE_IMAGE || "wakuorg/nwaku:v0.35.1"; +const WAKUNODE_IMAGE = process.env.WAKUNODE_IMAGE || "wakuorg/nwaku:v0.36.0"; async function main() { try { diff --git a/packages/tests/src/sync-rln-tree.js b/packages/tests/src/sync-rln-tree.js deleted file mode 100644 index 6939c1141e..0000000000 --- a/packages/tests/src/sync-rln-tree.js +++ /dev/null @@ -1,56 +0,0 @@ -import { exec } from "child_process"; -import { setTimeout } from "timers"; -import { promisify } from "util"; - -import { SEPOLIA_RPC_URL } from "./constants.js"; -import { ServiceNode } from "./lib/index.js"; - -const execAsync = promisify(exec); - -const WAKUNODE_IMAGE = process.env.WAKUNODE_IMAGE || "wakuorg/nwaku:v0.35.1"; -const containerName = "rln_tree"; - -async function syncRlnTree() { - try { - await execAsync(`docker inspect ${WAKUNODE_IMAGE}`); - console.log(`Using local image ${WAKUNODE_IMAGE}`); - } catch (error) { - console.log(`Pulling image ${WAKUNODE_IMAGE}`); - await execAsync(`docker pull ${WAKUNODE_IMAGE}`); - console.log("Image pulled"); - } - - const nwaku = new ServiceNode(containerName); - await nwaku.start( - { - store: false, - lightpush: false, - relay: true, - filter: false, - rest: true, - clusterId: 1, - rlnRelayEthClientAddress: SEPOLIA_RPC_URL - }, - { retries: 3 } - ); - let healthy = false; - while (!healthy) { - healthy = await nwaku.healthy(); - await new Promise((resolve) => setTimeout(resolve, 500)); - } - - await execAsync( - `docker cp ${nwaku.containerName}:/rln_tree.db /tmp/rln_tree.db` - ); - await nwaku.stop(); -} - -syncRlnTree() - .then(() => { - console.log("Synced RLN tree"); - process.exit(0); - }) - .catch((err) => { - console.error(`Error syncing RLN tree: ${err}`); - process.exit(1); - }); From 5334a7fcc91544d33294beaad9b45e641ecf404d Mon Sep 17 00:00:00 2001 From: Hanno Cornelius <68783915+jm-clius@users.noreply.github.com> Date: Tue, 28 Oct 2025 10:27:06 +0000 Subject: [PATCH 8/8] feat: add SDS-Repair (SDS-R) to the SDS implementation (#2698) * wip * feat: integrate sds-r with message channels * feat: add SDS-R events * fix: fixed buffer handling incoming and outgoing * fix: more buffer fixes * fix: remove some magic numbers * fix: buffer optimisation, backwards compatible senderId * fix: fix implementation guide, remove unrelated claude file * fix: further buffer optimisations * fix: linting errors * fix: suggestions from code review Co-authored-by: Sasha <118575614+weboko@users.noreply.github.com> Co-authored-by: fryorcraken <110212804+fryorcraken@users.noreply.github.com> * fix: remove implementation guide * fix: build errors, remove override, improve buffer * fix: consistent use of MessageId and ParticipantId * fix: switch to conditionally constructed from conditionally executed --------- Co-authored-by: fryorcraken Co-authored-by: Sasha <118575614+weboko@users.noreply.github.com> Co-authored-by: fryorcraken <110212804+fryorcraken@users.noreply.github.com> --- packages/proto/src/generated/sds_message.ts | 31 +- packages/proto/src/lib/sds_message.proto | 5 + packages/sds/src/index.ts | 8 +- packages/sds/src/message_channel/events.ts | 31 +- packages/sds/src/message_channel/index.ts | 2 +- .../sds/src/message_channel/message.spec.ts | 34 ++ packages/sds/src/message_channel/message.ts | 37 +- .../message_channel/message_channel.spec.ts | 4 +- .../src/message_channel/message_channel.ts | 168 ++++++++- .../message_channel/repair/buffers.spec.ts | 239 +++++++++++++ .../sds/src/message_channel/repair/buffers.ts | 277 +++++++++++++++ .../sds/src/message_channel/repair/repair.ts | 331 ++++++++++++++++++ .../sds/src/message_channel/repair/utils.ts | 80 +++++ 13 files changed, 1210 insertions(+), 37 deletions(-) create mode 100644 packages/sds/src/message_channel/repair/buffers.spec.ts create mode 100644 packages/sds/src/message_channel/repair/buffers.ts create mode 100644 packages/sds/src/message_channel/repair/repair.ts create mode 100644 packages/sds/src/message_channel/repair/utils.ts diff --git a/packages/proto/src/generated/sds_message.ts b/packages/proto/src/generated/sds_message.ts index eba12d4acd..86474c41a7 100644 --- a/packages/proto/src/generated/sds_message.ts +++ b/packages/proto/src/generated/sds_message.ts @@ -13,6 +13,7 @@ import type { Uint8ArrayList } from 'uint8arraylist' export interface HistoryEntry { messageId: string retrievalHint?: Uint8Array + senderId?: string } export namespace HistoryEntry { @@ -35,6 +36,11 @@ export namespace HistoryEntry { w.bytes(obj.retrievalHint) } + if (obj.senderId != null) { + w.uint32(26) + w.string(obj.senderId) + } + if (opts.lengthDelimited !== false) { w.ldelim() } @@ -57,6 +63,10 @@ export namespace HistoryEntry { obj.retrievalHint = reader.bytes() break } + case 3: { + obj.senderId = reader.string() + break + } default: { reader.skipType(tag & 7) break @@ -87,6 +97,7 @@ export interface SdsMessage { lamportTimestamp?: bigint causalHistory: HistoryEntry[] bloomFilter?: Uint8Array + repairRequest: HistoryEntry[] content?: Uint8Array } @@ -132,6 +143,13 @@ export namespace SdsMessage { w.bytes(obj.bloomFilter) } + if (obj.repairRequest != null) { + for (const value of obj.repairRequest) { + w.uint32(106) + HistoryEntry.codec().encode(value, w) + } + } + if (obj.content != null) { w.uint32(162) w.bytes(obj.content) @@ -145,7 +163,8 @@ export namespace SdsMessage { senderId: '', messageId: '', channelId: '', - causalHistory: [] + causalHistory: [], + repairRequest: [] } const end = length == null ? reader.len : reader.pos + length @@ -184,6 +203,16 @@ export namespace SdsMessage { obj.bloomFilter = reader.bytes() break } + case 13: { + if (opts.limits?.repairRequest != null && obj.repairRequest.length === opts.limits.repairRequest) { + throw new MaxLengthError('Decode error - map field "repairRequest" had too many elements') + } + + obj.repairRequest.push(HistoryEntry.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.repairRequest$ + })) + break + } case 20: { obj.content = reader.bytes() break diff --git a/packages/proto/src/lib/sds_message.proto b/packages/proto/src/lib/sds_message.proto index c38e99b084..50ca08d716 100644 --- a/packages/proto/src/lib/sds_message.proto +++ b/packages/proto/src/lib/sds_message.proto @@ -3,6 +3,8 @@ syntax = "proto3"; message HistoryEntry { string message_id = 1; // Unique identifier of the SDS message, as defined in `Message` optional bytes retrieval_hint = 2; // Optional information to help remote parties retrieve this SDS message; For example, A Waku deterministic message hash or routing payload hash + + optional string sender_id = 3; // Participant ID of original message sender. Only populated if using optional SDS Repair extension } message SdsMessage { @@ -12,5 +14,8 @@ message SdsMessage { optional uint64 lamport_timestamp = 10; // Logical timestamp for causal ordering in channel repeated HistoryEntry causal_history = 11; // List of preceding message IDs that this message causally depends on. Generally 2 or 3 message IDs are included. optional bytes bloom_filter = 12; // Bloom filter representing received message IDs in channel + + repeated HistoryEntry repair_request = 13; // Capped list of history entries missing from sender's causal history. Only populated if using the optional SDS Repair extension. + optional bytes content = 20; // Actual content of the message } diff --git a/packages/sds/src/index.ts b/packages/sds/src/index.ts index 3c1fb30cb1..14960d36b4 100644 --- a/packages/sds/src/index.ts +++ b/packages/sds/src/index.ts @@ -14,8 +14,14 @@ export { type HistoryEntry, type ChannelId, type MessageChannelEvents, - type SenderId, + type ParticipantId, type MessageId } from "./message_channel/index.js"; +/** + * @deprecated Use ParticipantId instead. SenderId has been renamed to ParticipantId + * to better reflect that it represents a channel participant, not just a message sender. + */ +export type { ParticipantId as SenderId } from "./message_channel/index.js"; + export { BloomFilter }; diff --git a/packages/sds/src/message_channel/events.ts b/packages/sds/src/message_channel/events.ts index 75318df210..ecc2a55edc 100644 --- a/packages/sds/src/message_channel/events.ts +++ b/packages/sds/src/message_channel/events.ts @@ -1,4 +1,4 @@ -import { HistoryEntry, Message, MessageId } from "./message.js"; +import { HistoryEntry, Message, MessageId, ParticipantId } from "./message.js"; export enum MessageChannelEvent { OutMessageSent = "sds:out:message-sent", @@ -10,7 +10,13 @@ export enum MessageChannelEvent { OutSyncSent = "sds:out:sync-sent", InSyncReceived = "sds:in:sync-received", InMessageLost = "sds:in:message-irretrievably-lost", - ErrorTask = "sds:error-task" + ErrorTask = "sds:error-task", + // SDS-R Repair Events + RepairRequestQueued = "sds:repair:request-queued", + RepairRequestSent = "sds:repair:request-sent", + RepairRequestReceived = "sds:repair:request-received", + RepairResponseQueued = "sds:repair:response-queued", + RepairResponseSent = "sds:repair:response-sent" } export type MessageChannelEvents = { @@ -26,5 +32,24 @@ export type MessageChannelEvents = { [MessageChannelEvent.InMessageLost]: CustomEvent; [MessageChannelEvent.OutSyncSent]: CustomEvent; [MessageChannelEvent.InSyncReceived]: CustomEvent; - [MessageChannelEvent.ErrorTask]: CustomEvent; + [MessageChannelEvent.ErrorTask]: CustomEvent; + [MessageChannelEvent.RepairRequestQueued]: CustomEvent<{ + messageId: MessageId; + tReq: number; + }>; + [MessageChannelEvent.RepairRequestSent]: CustomEvent<{ + messageIds: MessageId[]; + carrierMessageId: MessageId; + }>; + [MessageChannelEvent.RepairRequestReceived]: CustomEvent<{ + messageIds: MessageId[]; + fromSenderId?: ParticipantId; + }>; + [MessageChannelEvent.RepairResponseQueued]: CustomEvent<{ + messageId: MessageId; + tResp: number; + }>; + [MessageChannelEvent.RepairResponseSent]: CustomEvent<{ + messageId: MessageId; + }>; }; diff --git a/packages/sds/src/message_channel/index.ts b/packages/sds/src/message_channel/index.ts index 53c37ae388..7a8e279c2a 100644 --- a/packages/sds/src/message_channel/index.ts +++ b/packages/sds/src/message_channel/index.ts @@ -8,7 +8,7 @@ export { HistoryEntry, Message, MessageId, - SenderId, + ParticipantId, SyncMessage, isContentMessage, isEphemeralMessage, diff --git a/packages/sds/src/message_channel/message.spec.ts b/packages/sds/src/message_channel/message.spec.ts index 680bf5cdb5..860529a9e5 100644 --- a/packages/sds/src/message_channel/message.spec.ts +++ b/packages/sds/src/message_channel/message.spec.ts @@ -44,6 +44,7 @@ describe("Message serialization", () => { [{ messageId: depMessageId, retrievalHint: depRetrievalHint }], 0n, undefined, + undefined, undefined ); @@ -54,6 +55,39 @@ describe("Message serialization", () => { { messageId: depMessageId, retrievalHint: depRetrievalHint } ]); }); + + it("Repair Request", () => { + const repairMessageId = "missing-message"; + const repairRetrievalHint = utf8ToBytes("missing-retrieval"); + const repairSenderId = "original-sender"; + const message = new Message( + "123", + "my-channel", + "me", + [], + 0n, + undefined, + undefined, + [ + { + messageId: repairMessageId, + retrievalHint: repairRetrievalHint, + senderId: repairSenderId + } + ] + ); + + const bytes = message.encode(); + const decMessage = Message.decode(bytes); + + expect(decMessage!.repairRequest).to.deep.equal([ + { + messageId: repairMessageId, + retrievalHint: repairRetrievalHint, + senderId: repairSenderId + } + ]); + }); }); describe("ContentMessage comparison with < operator", () => { diff --git a/packages/sds/src/message_channel/message.ts b/packages/sds/src/message_channel/message.ts index 78b99f9006..6bea3e938e 100644 --- a/packages/sds/src/message_channel/message.ts +++ b/packages/sds/src/message_channel/message.ts @@ -4,19 +4,20 @@ import { Logger } from "@waku/utils"; export type MessageId = string; export type HistoryEntry = proto_sds_message.HistoryEntry; export type ChannelId = string; -export type SenderId = string; +export type ParticipantId = string; const log = new Logger("sds:message"); export class Message implements proto_sds_message.SdsMessage { public constructor( - public messageId: string, + public messageId: MessageId, public channelId: string, - public senderId: string, + public senderId: ParticipantId, public causalHistory: proto_sds_message.HistoryEntry[], public lamportTimestamp?: bigint | undefined, public bloomFilter?: Uint8Array | undefined, public content?: Uint8Array | undefined, + public repairRequest: proto_sds_message.HistoryEntry[] = [], /** * Not encoded, set after it is sent, used to include in follow-up messages */ @@ -38,7 +39,8 @@ export class Message implements proto_sds_message.SdsMessage { causalHistory, lamportTimestamp, bloomFilter, - content + content, + repairRequest } = proto_sds_message.SdsMessage.decode(data); if (testContentMessage({ lamportTimestamp, content })) { @@ -49,7 +51,8 @@ export class Message implements proto_sds_message.SdsMessage { causalHistory, lamportTimestamp!, bloomFilter, - content! + content!, + repairRequest ); } @@ -61,7 +64,8 @@ export class Message implements proto_sds_message.SdsMessage { causalHistory, undefined, bloomFilter, - content! + content!, + repairRequest ); } @@ -73,7 +77,8 @@ export class Message implements proto_sds_message.SdsMessage { causalHistory, lamportTimestamp!, bloomFilter, - undefined + undefined, + repairRequest ); } log.error( @@ -90,13 +95,14 @@ export class Message implements proto_sds_message.SdsMessage { export class SyncMessage extends Message { public constructor( - public messageId: string, + public messageId: MessageId, public channelId: string, - public senderId: string, + public senderId: ParticipantId, public causalHistory: proto_sds_message.HistoryEntry[], public lamportTimestamp: bigint, public bloomFilter: Uint8Array | undefined, public content: undefined, + public repairRequest: proto_sds_message.HistoryEntry[] = [], /** * Not encoded, set after it is sent, used to include in follow-up messages */ @@ -110,6 +116,7 @@ export class SyncMessage extends Message { lamportTimestamp, bloomFilter, content, + repairRequest, retrievalHint ); } @@ -134,13 +141,14 @@ export function isSyncMessage( export class EphemeralMessage extends Message { public constructor( - public messageId: string, + public messageId: MessageId, public channelId: string, - public senderId: string, + public senderId: ParticipantId, public causalHistory: proto_sds_message.HistoryEntry[], public lamportTimestamp: undefined, public bloomFilter: Uint8Array | undefined, public content: Uint8Array, + public repairRequest: proto_sds_message.HistoryEntry[] = [], /** * Not encoded, set after it is sent, used to include in follow-up messages */ @@ -157,6 +165,7 @@ export class EphemeralMessage extends Message { lamportTimestamp, bloomFilter, content, + repairRequest, retrievalHint ); } @@ -182,13 +191,14 @@ function testEphemeralMessage(message: { export class ContentMessage extends Message { public constructor( - public messageId: string, + public messageId: MessageId, public channelId: string, - public senderId: string, + public senderId: ParticipantId, public causalHistory: proto_sds_message.HistoryEntry[], public lamportTimestamp: bigint, public bloomFilter: Uint8Array | undefined, public content: Uint8Array, + public repairRequest: proto_sds_message.HistoryEntry[] = [], /** * Not encoded, set after it is sent, used to include in follow-up messages */ @@ -205,6 +215,7 @@ export class ContentMessage extends Message { lamportTimestamp, bloomFilter, content, + repairRequest, retrievalHint ); } diff --git a/packages/sds/src/message_channel/message_channel.spec.ts b/packages/sds/src/message_channel/message_channel.spec.ts index 91184f04d8..ea1629250c 100644 --- a/packages/sds/src/message_channel/message_channel.spec.ts +++ b/packages/sds/src/message_channel/message_channel.spec.ts @@ -162,7 +162,8 @@ describe("MessageChannel", function () { .slice(-causalHistorySize - 1, -1) .map((message) => ({ messageId: MessageChannel.getMessageId(utf8ToBytes(message)), - retrievalHint: undefined + retrievalHint: undefined, + senderId: "alice" })); expect(causalHistory).to.deep.equal(expectedCausalHistory); }); @@ -298,6 +299,7 @@ describe("MessageChannel", function () { 1n, undefined, payload, + undefined, testRetrievalHint ), testRetrievalHint diff --git a/packages/sds/src/message_channel/message_channel.ts b/packages/sds/src/message_channel/message_channel.ts index 3df21f160a..f72ba52579 100644 --- a/packages/sds/src/message_channel/message_channel.ts +++ b/packages/sds/src/message_channel/message_channel.ts @@ -18,15 +18,21 @@ import { isSyncMessage, Message, MessageId, - SenderId, + ParticipantId, SyncMessage } from "./message.js"; +import { RepairConfig, RepairManager } from "./repair/repair.js"; export const DEFAULT_BLOOM_FILTER_OPTIONS = { capacity: 10000, errorRate: 0.001 }; +/** + * Maximum number of repair requests to include in a single message + */ +const MAX_REPAIR_REQUESTS_PER_MESSAGE = 3; + const DEFAULT_CAUSAL_HISTORY_SIZE = 200; const DEFAULT_POSSIBLE_ACKS_THRESHOLD = 2; @@ -46,6 +52,15 @@ export interface MessageChannelOptions { * How many possible acks does it take to consider it a definitive ack. */ possibleAcksThreshold?: number; + /** + * Whether to enable SDS-R repair protocol. + * @default true + */ + enableRepair?: boolean; + /** + * SDS-R repair configuration. Only used if enableRepair is true. + */ + repairConfig?: RepairConfig; } export type ILocalHistory = Pick< @@ -55,7 +70,7 @@ export type ILocalHistory = Pick< export class MessageChannel extends TypedEventEmitter { public readonly channelId: ChannelId; - public readonly senderId: SenderId; + public readonly senderId: ParticipantId; private lamportTimestamp: bigint; private filter: DefaultBloomFilter; private outgoingBuffer: ContentMessage[]; @@ -66,6 +81,7 @@ export class MessageChannel extends TypedEventEmitter { private readonly causalHistorySize: number; private readonly possibleAcksThreshold: number; private readonly timeoutForLostMessagesMs?: number; + private readonly repairManager?: RepairManager; private tasks: Task[] = []; private handlers: Handlers = { @@ -88,7 +104,7 @@ export class MessageChannel extends TypedEventEmitter { public constructor( channelId: ChannelId, - senderId: SenderId, + senderId: ParticipantId, options: MessageChannelOptions = {}, localHistory: ILocalHistory = new MemLocalHistory() ) { @@ -109,6 +125,17 @@ export class MessageChannel extends TypedEventEmitter { options.possibleAcksThreshold ?? DEFAULT_POSSIBLE_ACKS_THRESHOLD; this.timeReceived = new Map(); this.timeoutForLostMessagesMs = options.timeoutForLostMessagesMs; + + // Only construct RepairManager if repair is enabled (default: true) + if (options.enableRepair ?? true) { + this.repairManager = new RepairManager( + senderId, + options.repairConfig, + (event: string, detail: unknown) => { + this.safeSendEvent(event as MessageChannelEvent, { detail }); + } + ); + } } public static getMessageId(payload: Uint8Array): MessageId { @@ -272,9 +299,7 @@ export class MessageChannel extends TypedEventEmitter { ); const missingDependencies = message.causalHistory.filter( (messageHistoryEntry) => - !this.localHistory.some( - ({ messageId }) => messageId === messageHistoryEntry.messageId - ) + !this.isMessageAvailable(messageHistoryEntry.messageId) ); if (missingDependencies.length === 0) { if (isContentMessage(message) && this.deliverMessage(message)) { @@ -355,6 +380,44 @@ export class MessageChannel extends TypedEventEmitter { ); } + /** + * Sweep repair incoming buffer and rebroadcast messages ready for repair. + * Per SDS-R spec: periodically check for repair responses that are due. + * + * @param callback - callback to rebroadcast the message + * @returns Promise that resolves when all ready repairs have been sent + */ + public async sweepRepairIncomingBuffer( + callback?: (message: Message) => Promise + ): Promise { + const repairsToSend = + this.repairManager?.sweepIncomingBuffer(this.localHistory) ?? []; + + if (callback) { + for (const message of repairsToSend) { + try { + await callback(message); + log.info( + this.senderId, + "repair message rebroadcast", + message.messageId + ); + + // Emit RepairResponseSent event + this.safeSendEvent(MessageChannelEvent.RepairResponseSent, { + detail: { + messageId: message.messageId + } + }); + } catch (error) { + log.error("Failed to rebroadcast repair message:", error); + } + } + } + + return repairsToSend; + } + /** * Send a sync message to the SDS channel. * @@ -369,6 +432,12 @@ export class MessageChannel extends TypedEventEmitter { callback?: (message: SyncMessage) => Promise ): Promise { this.lamportTimestamp = lamportTimestampIncrement(this.lamportTimestamp); + + // Get repair requests to include in sync message (SDS-R) + const repairRequests = + this.repairManager?.getRepairRequests(MAX_REPAIR_REQUESTS_PER_MESSAGE) ?? + []; + const message = new SyncMessage( // does not need to be secure randomness `sync-${Math.random().toString(36).substring(2)}`, @@ -376,18 +445,22 @@ export class MessageChannel extends TypedEventEmitter { this.senderId, this.localHistory .slice(-this.causalHistorySize) - .map(({ messageId, retrievalHint }) => { - return { messageId, retrievalHint }; + .map(({ messageId, retrievalHint, senderId }) => { + return { messageId, retrievalHint, senderId }; }), this.lamportTimestamp, this.filter.toBytes(), - undefined + undefined, + repairRequests ); - if (!message.causalHistory || message.causalHistory.length === 0) { + if ( + (!message.causalHistory || message.causalHistory.length === 0) && + repairRequests.length === 0 + ) { log.info( this.senderId, - "no causal history in sync message, aborting sending" + "no causal history and no repair requests in sync message, aborting sending" ); return false; } @@ -399,6 +472,17 @@ export class MessageChannel extends TypedEventEmitter { this.safeSendEvent(MessageChannelEvent.OutSyncSent, { detail: message }); + + // Emit RepairRequestSent event if repair requests were included + if (repairRequests.length > 0) { + this.safeSendEvent(MessageChannelEvent.RepairRequestSent, { + detail: { + messageIds: repairRequests.map((r) => r.messageId), + carrierMessageId: message.messageId + } + }); + } + return true; } catch (error) { log.error( @@ -464,6 +548,26 @@ export class MessageChannel extends TypedEventEmitter { detail: message }); } + + // SDS-R: Handle received message in repair manager + this.repairManager?.markMessageReceived(message.messageId); + + // SDS-R: Process incoming repair requests + if (message.repairRequest && message.repairRequest.length > 0) { + // Emit RepairRequestReceived event + this.safeSendEvent(MessageChannelEvent.RepairRequestReceived, { + detail: { + messageIds: message.repairRequest.map((r) => r.messageId), + fromSenderId: message.senderId + } + }); + + this.repairManager?.processIncomingRepairRequests( + message.repairRequest, + this.localHistory + ); + } + this.reviewAckStatus(message); if (isContentMessage(message)) { this.filter.insert(message.messageId); @@ -471,9 +575,7 @@ export class MessageChannel extends TypedEventEmitter { const missingDependencies = message.causalHistory.filter( (messageHistoryEntry) => - !this.localHistory.some( - ({ messageId }) => messageId === messageHistoryEntry.messageId - ) + !this.isMessageAvailable(messageHistoryEntry.messageId) ); if (missingDependencies.length > 0) { @@ -487,6 +589,9 @@ export class MessageChannel extends TypedEventEmitter { missingDependencies.map((ch) => ch.messageId) ); + // SDS-R: Track missing dependencies in repair manager + this.repairManager?.markDependenciesMissing(missingDependencies); + this.safeSendEvent(MessageChannelEvent.InMessageMissing, { detail: Array.from(missingDependencies) }); @@ -549,18 +654,26 @@ export class MessageChannel extends TypedEventEmitter { // It's a new message if (!message) { log.info(this.senderId, "sending new message", messageId); + + // Get repair requests to include in the message (SDS-R) + const repairRequests = + this.repairManager?.getRepairRequests( + MAX_REPAIR_REQUESTS_PER_MESSAGE + ) ?? []; + message = new ContentMessage( messageId, this.channelId, this.senderId, this.localHistory .slice(-this.causalHistorySize) - .map(({ messageId, retrievalHint }) => { - return { messageId, retrievalHint }; + .map(({ messageId, retrievalHint, senderId }) => { + return { messageId, retrievalHint, senderId }; }), this.lamportTimestamp, this.filter.toBytes(), - payload + payload, + repairRequests ); this.outgoingBuffer.push(message); @@ -616,6 +729,26 @@ export class MessageChannel extends TypedEventEmitter { } } + /** + * Check if a message is available (either in localHistory or incomingBuffer) + * This prevents treating messages as "missing" when they've already been received + * but are waiting in the incoming buffer for their dependencies. + * + * @param messageId - The ID of the message to check + * @private + */ + private isMessageAvailable(messageId: MessageId): boolean { + // Check if in local history + if (this.localHistory.some((m) => m.messageId === messageId)) { + return true; + } + // Check if in incoming buffer (already received, waiting for dependencies) + if (this.incomingBuffer.some((m) => m.messageId === messageId)) { + return true; + } + return false; + } + /** * Return true if the message was "delivered" * @@ -657,6 +790,7 @@ export class MessageChannel extends TypedEventEmitter { } this.localHistory.push(message); + return true; } diff --git a/packages/sds/src/message_channel/repair/buffers.spec.ts b/packages/sds/src/message_channel/repair/buffers.spec.ts new file mode 100644 index 0000000000..484d6118cf --- /dev/null +++ b/packages/sds/src/message_channel/repair/buffers.spec.ts @@ -0,0 +1,239 @@ +import { expect } from "chai"; + +import type { HistoryEntry } from "../message.js"; + +import { IncomingRepairBuffer, OutgoingRepairBuffer } from "./buffers.js"; + +describe("OutgoingRepairBuffer", () => { + let buffer: OutgoingRepairBuffer; + + beforeEach(() => { + buffer = new OutgoingRepairBuffer(3); // Small buffer for testing + }); + + it("should add entries and maintain sorted order", () => { + const entry1: HistoryEntry = { messageId: "msg1" }; + const entry2: HistoryEntry = { messageId: "msg2" }; + const entry3: HistoryEntry = { messageId: "msg3" }; + + buffer.add(entry2, 2000); + buffer.add(entry1, 1000); + buffer.add(entry3, 3000); + + const items = buffer.getItems(); + expect(items).to.have.lengthOf(3); + expect(items[0].tReq).to.equal(1000); + expect(items[1].tReq).to.equal(2000); + expect(items[2].tReq).to.equal(3000); + }); + + it("should not update T_req if message already exists", () => { + const entry: HistoryEntry = { messageId: "msg1" }; + + buffer.add(entry, 1000); + buffer.add(entry, 2000); // Try to add again with different T_req + + const items = buffer.getItems(); + expect(items).to.have.lengthOf(1); + expect(items[0].tReq).to.equal(1000); // Should keep original + }); + + it("should evict furthest entry when buffer is full", () => { + const entry1: HistoryEntry = { messageId: "msg1" }; + const entry2: HistoryEntry = { messageId: "msg2" }; + const entry3: HistoryEntry = { messageId: "msg3" }; + const entry4: HistoryEntry = { messageId: "msg4" }; + + buffer.add(entry2, 2000); + buffer.add(entry1, 1000); + buffer.add(entry3, 3000); + buffer.add(entry4, 1500); // Should evict msg3 (furthest T_req = 3000) + + const items = buffer.getItems(); + expect(items).to.have.lengthOf(3); + expect(buffer.has("msg3")).to.be.false; // msg3 should be evicted (furthest T_req) + expect(buffer.has("msg1")).to.be.true; + expect(buffer.has("msg2")).to.be.true; + expect(buffer.has("msg4")).to.be.true; + }); + + it("should get eligible entries based on current time", () => { + const entry1: HistoryEntry = { messageId: "msg1" }; + const entry2: HistoryEntry = { messageId: "msg2" }; + const entry3: HistoryEntry = { messageId: "msg3" }; + + buffer.add(entry1, 1000); + buffer.add(entry2, 2000); + buffer.add(entry3, 3000); + + const eligible = buffer.getEligible(1500, 3); + expect(eligible).to.have.lengthOf(1); + expect(eligible[0].messageId).to.equal("msg1"); + }); + + it("should get multiple eligible entries at later time", () => { + const entry1: HistoryEntry = { messageId: "msg1" }; + const entry2: HistoryEntry = { messageId: "msg2" }; + const entry3: HistoryEntry = { messageId: "msg3" }; + + // Create new buffer for second test since getEligible marks entries as requested + const buffer2 = new OutgoingRepairBuffer(3); + buffer2.add(entry1, 1000); + buffer2.add(entry2, 2000); + buffer2.add(entry3, 3000); + + const eligible = buffer2.getEligible(2500, 3); + expect(eligible).to.have.lengthOf(2); + expect(eligible[0].messageId).to.equal("msg1"); + expect(eligible[1].messageId).to.equal("msg2"); + }); + + it("should respect maxRequests limit", () => { + const entry1: HistoryEntry = { messageId: "msg1" }; + const entry2: HistoryEntry = { messageId: "msg2" }; + const entry3: HistoryEntry = { messageId: "msg3" }; + + buffer.add(entry1, 1000); + buffer.add(entry2, 2000); + buffer.add(entry3, 3000); + + const eligible = buffer.getEligible(5000, 2); // All are eligible but limit to 2 + expect(eligible).to.have.lengthOf(2); + expect(eligible[0].messageId).to.equal("msg1"); + expect(eligible[1].messageId).to.equal("msg2"); + }); + + it("should remove entries", () => { + const entry1: HistoryEntry = { messageId: "msg1" }; + const entry2: HistoryEntry = { messageId: "msg2" }; + + buffer.add(entry1, 1000); + buffer.add(entry2, 2000); + + expect(buffer.size).to.equal(2); + buffer.remove("msg1"); + expect(buffer.size).to.equal(1); + expect(buffer.has("msg1")).to.be.false; + expect(buffer.has("msg2")).to.be.true; + }); + + it("should handle retrieval hint and sender_id", () => { + const hint = new Uint8Array([1, 2, 3]); + const entry: HistoryEntry = { + messageId: "msg1", + retrievalHint: hint, + senderId: "sender1" + }; + + buffer.add(entry, 1000); + const all = buffer.getAll(); + expect(all[0].retrievalHint).to.deep.equal(hint); + expect(all[0].senderId).to.equal("sender1"); + }); +}); + +describe("IncomingRepairBuffer", () => { + let buffer: IncomingRepairBuffer; + + beforeEach(() => { + buffer = new IncomingRepairBuffer(3); // Small buffer for testing + }); + + it("should add entries and maintain sorted order", () => { + const entry1: HistoryEntry = { messageId: "msg1" }; + const entry2: HistoryEntry = { messageId: "msg2" }; + const entry3: HistoryEntry = { messageId: "msg3" }; + + buffer.add(entry2, 2000); + buffer.add(entry1, 1000); + buffer.add(entry3, 3000); + + const items = buffer.getItems(); + expect(items).to.have.lengthOf(3); + expect(items[0].tResp).to.equal(1000); + expect(items[1].tResp).to.equal(2000); + expect(items[2].tResp).to.equal(3000); + }); + + it("should ignore duplicate entries", () => { + const entry: HistoryEntry = { messageId: "msg1" }; + + buffer.add(entry, 1000); + buffer.add(entry, 500); // Try to add again with earlier T_resp + + const items = buffer.getItems(); + expect(items).to.have.lengthOf(1); + expect(items[0].tResp).to.equal(1000); // Should keep original + }); + + it("should evict furthest entry when buffer is full", () => { + const entry1: HistoryEntry = { messageId: "msg1" }; + const entry2: HistoryEntry = { messageId: "msg2" }; + const entry3: HistoryEntry = { messageId: "msg3" }; + const entry4: HistoryEntry = { messageId: "msg4" }; + + buffer.add(entry1, 1000); + buffer.add(entry2, 2000); + buffer.add(entry3, 3000); + buffer.add(entry4, 1500); // Should evict msg3 (furthest T_resp) + + const items = buffer.getItems(); + expect(items).to.have.lengthOf(3); + expect(buffer.has("msg3")).to.be.false; // msg3 should be evicted + expect(buffer.has("msg1")).to.be.true; + expect(buffer.has("msg2")).to.be.true; + expect(buffer.has("msg4")).to.be.true; + }); + + it("should get and remove ready entries", () => { + const entry1: HistoryEntry = { messageId: "msg1" }; + const entry2: HistoryEntry = { messageId: "msg2" }; + const entry3: HistoryEntry = { messageId: "msg3" }; + + buffer.add(entry1, 1000); + buffer.add(entry2, 2000); + buffer.add(entry3, 3000); + + const ready = buffer.getReady(1500); + expect(ready).to.have.lengthOf(1); + expect(ready[0].messageId).to.equal("msg1"); + + // Entry should be removed from buffer + expect(buffer.size).to.equal(2); + expect(buffer.has("msg1")).to.be.false; + + const ready2 = buffer.getReady(2500); + expect(ready2).to.have.lengthOf(1); + expect(ready2[0].messageId).to.equal("msg2"); + + expect(buffer.size).to.equal(1); + expect(buffer.has("msg2")).to.be.false; + expect(buffer.has("msg3")).to.be.true; + }); + + it("should remove entries", () => { + const entry1: HistoryEntry = { messageId: "msg1" }; + const entry2: HistoryEntry = { messageId: "msg2" }; + + buffer.add(entry1, 1000); + buffer.add(entry2, 2000); + + expect(buffer.size).to.equal(2); + buffer.remove("msg1"); + expect(buffer.size).to.equal(1); + expect(buffer.has("msg1")).to.be.false; + expect(buffer.has("msg2")).to.be.true; + }); + + it("should clear all entries", () => { + const entry1: HistoryEntry = { messageId: "msg1" }; + const entry2: HistoryEntry = { messageId: "msg2" }; + + buffer.add(entry1, 1000); + buffer.add(entry2, 2000); + + expect(buffer.size).to.equal(2); + buffer.clear(); + expect(buffer.size).to.equal(0); + }); +}); diff --git a/packages/sds/src/message_channel/repair/buffers.ts b/packages/sds/src/message_channel/repair/buffers.ts new file mode 100644 index 0000000000..518fa7acb2 --- /dev/null +++ b/packages/sds/src/message_channel/repair/buffers.ts @@ -0,0 +1,277 @@ +import { Logger } from "@waku/utils"; + +import type { HistoryEntry, MessageId } from "../message.js"; + +const log = new Logger("sds:repair:buffers"); + +/** + * Entry in the outgoing repair buffer with request timing + */ +interface OutgoingBufferEntry { + entry: HistoryEntry; + tReq: number; // Timestamp when this repair request should be sent + requested: boolean; // Whether this repair has already been requested by the local node +} + +/** + * Entry in the incoming repair buffer with response timing + */ +interface IncomingBufferEntry { + entry: HistoryEntry; + tResp: number; // Timestamp when we should respond with this repair +} + +/** + * Buffer for outgoing repair requests (messages we need) + * Maintains a sorted array by T_req for efficient retrieval of eligible entries + */ +export class OutgoingRepairBuffer { + // Sorted array by T_req (ascending - earliest first) + private items: OutgoingBufferEntry[] = []; + private readonly maxSize: number; + + public constructor(maxSize = 1000) { + this.maxSize = maxSize; + } + + /** + * Add a missing message to the outgoing repair request buffer + * If message already exists, it is not updated (keeps original T_req) + * @returns true if the entry was added, false if it already existed + */ + public add(entry: HistoryEntry, tReq: number): boolean { + const messageId = entry.messageId; + + // Check if already exists - do NOT update T_req per spec + if (this.has(messageId)) { + log.info( + `Message ${messageId} already in outgoing buffer, keeping original T_req` + ); + return false; + } + + // Check buffer size limit + if (this.items.length >= this.maxSize) { + // Evict furthest T_req entry (last in sorted array) to preserve repairs that need to be sent the soonest + const evicted = this.items.pop()!; + log.warn( + `Buffer full, evicted furthest entry ${evicted.entry.messageId} with T_req ${evicted.tReq}` + ); + } + + // Add new entry and re-sort + const newEntry: OutgoingBufferEntry = { entry, tReq, requested: false }; + const combined = [...this.items, newEntry]; + + // Sort by T_req (ascending) + combined.sort((a, b) => a.tReq - b.tReq); + + this.items = combined; + log.info(`Added ${messageId} to outgoing buffer with T_req: ${tReq}`); + return true; + } + + /** + * Remove a message from the buffer (e.g., when received) + */ + public remove(messageId: MessageId): void { + this.items = this.items.filter( + (item) => item.entry.messageId !== messageId + ); + } + + /** + * Get eligible repair requests (where T_req <= currentTime) + * Returns up to maxRequests entries from the front of the sorted array + * Marks returned entries as requested but keeps them in buffer until received + */ + public getEligible( + currentTime: number = Date.now(), + maxRequests = 3 + ): HistoryEntry[] { + const eligible: HistoryEntry[] = []; + + // Iterate from front of sorted array (earliest T_req first) + for (const item of this.items) { + // Since array is sorted, once we hit an item with tReq > currentTime, + // all remaining items also have tReq > currentTime + if (item.tReq > currentTime) { + break; + } + + // Only return items that haven't been requested yet + if (!item.requested && eligible.length < maxRequests) { + eligible.push(item.entry); + // Mark as requested so we don't request it again + item.requested = true; + log.info( + `Repair request for ${item.entry.messageId} is eligible and marked as requested` + ); + } + + // If we've found enough eligible items, exit early + if (eligible.length >= maxRequests) { + break; + } + } + + return eligible; + } + + /** + * Check if a message is in the buffer + */ + public has(messageId: MessageId): boolean { + return this.items.some((item) => item.entry.messageId === messageId); + } + + /** + * Get the current buffer size + */ + public get size(): number { + return this.items.length; + } + + /** + * Clear all entries + */ + public clear(): void { + this.items = []; + } + + /** + * Get all entries (for testing/debugging) + */ + public getAll(): HistoryEntry[] { + return this.items.map((item) => item.entry); + } + + /** + * Get items array directly (for testing) + */ + public getItems(): OutgoingBufferEntry[] { + return [...this.items]; + } +} + +/** + * Buffer for incoming repair requests (repairs we need to send) + * Maintains a sorted array by T_resp for efficient retrieval of ready entries + */ +export class IncomingRepairBuffer { + // Sorted array by T_resp (ascending - earliest first) + private items: IncomingBufferEntry[] = []; + private readonly maxSize: number; + + public constructor(maxSize = 1000) { + this.maxSize = maxSize; + } + + /** + * Add a repair request that we can fulfill + * If message already exists, it is ignored (not updated) + * @returns true if the entry was added, false if it already existed + */ + public add(entry: HistoryEntry, tResp: number): boolean { + const messageId = entry.messageId; + + // Check if already exists - ignore per spec + if (this.has(messageId)) { + log.info(`Message ${messageId} already in incoming buffer, ignoring`); + return false; + } + + // Check buffer size limit + if (this.items.length >= this.maxSize) { + // Evict furthest T_resp entry (last in sorted array) + const evicted = this.items.pop()!; + log.warn( + `Buffer full, evicted furthest entry ${evicted.entry.messageId} with T_resp ${evicted.tResp}` + ); + } + + // Add new entry and re-sort + const newEntry: IncomingBufferEntry = { entry, tResp }; + const combined = [...this.items, newEntry]; + + // Sort by T_resp (ascending) + combined.sort((a, b) => a.tResp - b.tResp); + + this.items = combined; + log.info(`Added ${messageId} to incoming buffer with T_resp: ${tResp}`); + return true; + } + + /** + * Remove a message from the buffer + */ + public remove(messageId: MessageId): void { + this.items = this.items.filter( + (item) => item.entry.messageId !== messageId + ); + } + + /** + * Get repairs ready to be sent (where T_resp <= currentTime) + * Removes and returns ready entries + */ + public getReady(currentTime: number): HistoryEntry[] { + // Find cutoff point - first item with tResp > currentTime + // Since array is sorted, all items before this are ready + let cutoff = 0; + for (let i = 0; i < this.items.length; i++) { + if (this.items[i].tResp > currentTime) { + cutoff = i; + break; + } + // If we reach the end, all items are ready + cutoff = i + 1; + } + + // Extract ready items and log them + const ready = this.items.slice(0, cutoff).map((item) => { + log.info(`Repair for ${item.entry.messageId} is ready to be sent`); + return item.entry; + }); + + // Keep only items after cutoff + this.items = this.items.slice(cutoff); + + return ready; + } + + /** + * Check if a message is in the buffer + */ + public has(messageId: MessageId): boolean { + return this.items.some((item) => item.entry.messageId === messageId); + } + + /** + * Get the current buffer size + */ + public get size(): number { + return this.items.length; + } + + /** + * Clear all entries + */ + public clear(): void { + this.items = []; + } + + /** + * Get all entries (for testing/debugging) + */ + public getAll(): HistoryEntry[] { + return this.items.map((item) => item.entry); + } + + /** + * Get items array directly (for testing) + */ + public getItems(): IncomingBufferEntry[] { + return [...this.items]; + } +} diff --git a/packages/sds/src/message_channel/repair/repair.ts b/packages/sds/src/message_channel/repair/repair.ts new file mode 100644 index 0000000000..4207483165 --- /dev/null +++ b/packages/sds/src/message_channel/repair/repair.ts @@ -0,0 +1,331 @@ +import { Logger } from "@waku/utils"; + +import type { HistoryEntry, MessageId } from "../message.js"; +import { Message } from "../message.js"; +import type { ILocalHistory } from "../message_channel.js"; + +import { IncomingRepairBuffer, OutgoingRepairBuffer } from "./buffers.js"; +import { + bigintToNumber, + calculateXorDistance, + combinedHash, + hashString, + ParticipantId +} from "./utils.js"; + +const log = new Logger("sds:repair:manager"); + +/** + * Per SDS-R spec: One response group per 128 participants + */ +const PARTICIPANTS_PER_RESPONSE_GROUP = 128; + +/** + * Event emitter callback for repair events + */ +export type RepairEventEmitter = (event: string, detail: unknown) => void; + +/** + * Configuration for SDS-R repair protocol + */ +export interface RepairConfig { + /** Minimum wait time before requesting repair (milliseconds) */ + tMin?: number; + /** Maximum wait time for repair window (milliseconds) */ + tMax?: number; + /** Number of response groups for load distribution */ + numResponseGroups?: number; + /** Maximum buffer size for repair requests */ + bufferSize?: number; +} + +/** + * Default configuration values based on spec recommendations + */ +export const DEFAULT_REPAIR_CONFIG: Required = { + tMin: 30000, // 30 seconds + tMax: 120000, // 120 seconds + numResponseGroups: 1, // Recommendation is 1 group per PARTICIPANTS_PER_RESPONSE_GROUP participants + bufferSize: 1000 +}; + +/** + * Manager for SDS-R repair protocol + * Handles repair request/response timing and coordination + */ +export class RepairManager { + private readonly participantId: ParticipantId; + private readonly config: Required; + private readonly outgoingBuffer: OutgoingRepairBuffer; + private readonly incomingBuffer: IncomingRepairBuffer; + private readonly eventEmitter?: RepairEventEmitter; + + public constructor( + participantId: ParticipantId, + config: RepairConfig = {}, + eventEmitter?: RepairEventEmitter + ) { + this.participantId = participantId; + this.config = { ...DEFAULT_REPAIR_CONFIG, ...config }; + this.eventEmitter = eventEmitter; + + this.outgoingBuffer = new OutgoingRepairBuffer(this.config.bufferSize); + this.incomingBuffer = new IncomingRepairBuffer(this.config.bufferSize); + + log.info(`RepairManager initialized for participant ${participantId}`); + } + + /** + * Calculate T_req - when to request repair for a missing message + * Per spec: T_req = current_time + hash(participant_id, message_id) % (T_max - T_min) + T_min + */ + public calculateTReq(messageId: MessageId, currentTime = Date.now()): number { + const hash = combinedHash(this.participantId, messageId); + const range = BigInt(this.config.tMax - this.config.tMin); + const offset = bigintToNumber(hash % range) + this.config.tMin; + return currentTime + offset; + } + + /** + * Calculate T_resp - when to respond with a repair + * Per spec: T_resp = current_time + (distance * hash(message_id)) % T_max + * where distance = participant_id XOR sender_id + */ + public calculateTResp( + senderId: ParticipantId, + messageId: MessageId, + currentTime = Date.now() + ): number { + const distance = calculateXorDistance(this.participantId, senderId); + const messageHash = hashString(messageId); + const product = distance * messageHash; + const offset = bigintToNumber(product % BigInt(this.config.tMax)); + return currentTime + offset; + } + + /** + * Determine if this participant is in the response group for a message + * Per spec: (hash(participant_id, message_id) % num_response_groups) == + * (hash(sender_id, message_id) % num_response_groups) + */ + public isInResponseGroup( + senderId: ParticipantId, + messageId: MessageId + ): boolean { + if (!senderId) { + // Cannot determine response group without sender_id + return false; + } + + const numGroups = BigInt(this.config.numResponseGroups); + if (numGroups <= BigInt(1)) { + // Single group, everyone is in it + return true; + } + + const participantGroup = + combinedHash(this.participantId, messageId) % numGroups; + const senderGroup = combinedHash(senderId, messageId) % numGroups; + + return participantGroup === senderGroup; + } + + /** + * Handle missing dependencies by adding them to outgoing repair buffer + * Called when causal dependencies are detected as missing + */ + public markDependenciesMissing( + missingEntries: HistoryEntry[], + currentTime = Date.now() + ): void { + for (const entry of missingEntries) { + // Calculate when to request this repair + const tReq = this.calculateTReq(entry.messageId, currentTime); + + // Add to outgoing buffer - only log and emit event if actually added + const wasAdded = this.outgoingBuffer.add(entry, tReq); + + if (wasAdded) { + log.info( + `Added missing dependency ${entry.messageId} to repair buffer with T_req=${tReq}` + ); + + // Emit event + this.eventEmitter?.("RepairRequestQueued", { + messageId: entry.messageId, + tReq + }); + } + } + } + + /** + * Handle receipt of a message - remove from repair buffers + * Called when a message is successfully received + */ + public markMessageReceived(messageId: MessageId): void { + // Remove from both buffers as we no longer need to request or respond + const wasInOutgoing = this.outgoingBuffer.has(messageId); + const wasInIncoming = this.incomingBuffer.has(messageId); + + if (wasInOutgoing) { + this.outgoingBuffer.remove(messageId); + log.info( + `Removed ${messageId} from outgoing repair buffer after receipt` + ); + } + + if (wasInIncoming) { + this.incomingBuffer.remove(messageId); + log.info( + `Removed ${messageId} from incoming repair buffer after receipt` + ); + } + } + + /** + * Get repair requests that are eligible to be sent + * Returns up to maxRequests entries where T_req <= currentTime + */ + public getRepairRequests( + maxRequests = 3, + currentTime = Date.now() + ): HistoryEntry[] { + return this.outgoingBuffer.getEligible(currentTime, maxRequests); + } + + /** + * Process incoming repair requests from other participants + * Adds to incoming buffer if we can fulfill and are in response group + */ + public processIncomingRepairRequests( + requests: HistoryEntry[], + localHistory: ILocalHistory, + currentTime = Date.now() + ): void { + for (const request of requests) { + // Remove from our own outgoing buffer (someone else is requesting it) + this.outgoingBuffer.remove(request.messageId); + + // Check if we have this message + const message = localHistory.find( + (m) => m.messageId === request.messageId + ); + if (!message) { + log.info( + `Cannot fulfill repair for ${request.messageId} - not in local history` + ); + continue; + } + + // Check if we're in the response group + if (!request.senderId) { + log.warn( + `Cannot determine response group for ${request.messageId} - missing sender_id` + ); + continue; + } + + if (!this.isInResponseGroup(request.senderId, request.messageId)) { + log.info(`Not in response group for ${request.messageId}`); + continue; + } + + // Calculate when to respond + const tResp = this.calculateTResp( + request.senderId, + request.messageId, + currentTime + ); + + // Add to incoming buffer - only log and emit event if actually added + const wasAdded = this.incomingBuffer.add(request, tResp); + + if (wasAdded) { + log.info( + `Will respond to repair request for ${request.messageId} at T_resp=${tResp}` + ); + + // Emit event + this.eventEmitter?.("RepairResponseQueued", { + messageId: request.messageId, + tResp + }); + } + } + } + + /** + * Sweep outgoing buffer for repairs that should be requested + * Returns entries where T_req <= currentTime + */ + public sweepOutgoingBuffer( + maxRequests = 3, + currentTime = Date.now() + ): HistoryEntry[] { + return this.getRepairRequests(maxRequests, currentTime); + } + + /** + * Sweep incoming buffer for repairs ready to be sent + * Returns messages that should be rebroadcast + */ + public sweepIncomingBuffer( + localHistory: ILocalHistory, + currentTime = Date.now() + ): Message[] { + const ready = this.incomingBuffer.getReady(currentTime); + const messages: Message[] = []; + + for (const entry of ready) { + const message = localHistory.find((m) => m.messageId === entry.messageId); + if (message) { + messages.push(message); + log.info(`Sending repair for ${entry.messageId}`); + } else { + log.warn(`Message ${entry.messageId} no longer in local history`); + } + } + + return messages; + } + + /** + * Clear all buffers + */ + public clear(): void { + this.outgoingBuffer.clear(); + this.incomingBuffer.clear(); + } + + /** + * Update number of response groups (e.g., when participants change) + */ + public updateResponseGroups(numParticipants: number): void { + if ( + numParticipants < 0 || + !Number.isFinite(numParticipants) || + !Number.isInteger(numParticipants) + ) { + throw new Error( + `Invalid numParticipants: ${numParticipants}. Must be a positive integer.` + ); + } + + if (numParticipants > Number.MAX_SAFE_INTEGER) { + log.warn( + `numParticipants ${numParticipants} exceeds MAX_SAFE_INTEGER, using MAX_SAFE_INTEGER` + ); + numParticipants = Number.MAX_SAFE_INTEGER; + } + + // Per spec: num_response_groups = max(1, num_participants / PARTICIPANTS_PER_RESPONSE_GROUP) + this.config.numResponseGroups = Math.max( + 1, + Math.floor(numParticipants / PARTICIPANTS_PER_RESPONSE_GROUP) + ); + log.info( + `Updated response groups to ${this.config.numResponseGroups} for ${numParticipants} participants` + ); + } +} diff --git a/packages/sds/src/message_channel/repair/utils.ts b/packages/sds/src/message_channel/repair/utils.ts new file mode 100644 index 0000000000..4206857b2d --- /dev/null +++ b/packages/sds/src/message_channel/repair/utils.ts @@ -0,0 +1,80 @@ +import { sha256 } from "@noble/hashes/sha2"; +import { bytesToHex } from "@waku/utils/bytes"; + +import type { MessageId } from "../message.js"; + +/** + * ParticipantId can be a string or converted to a numeric representation for XOR operations + */ +export type ParticipantId = string; + +/** + * Compute SHA256 hash and convert to integer for modulo operations + * Uses first 8 bytes of hash for the integer conversion + */ +export function hashToInteger(input: string): bigint { + const hashBytes = sha256(new TextEncoder().encode(input)); + // Use first 8 bytes for a 64-bit integer + const view = new DataView(hashBytes.buffer, 0, 8); + return view.getBigUint64(0, false); // big-endian +} + +/** + * Compute combined hash for (participantId, messageId) and convert to integer + * This is used for T_req calculations and response group membership + */ +export function combinedHash( + participantId: ParticipantId, + messageId: MessageId +): bigint { + const combined = `${participantId}${messageId}`; + return hashToInteger(combined); +} + +/** + * Convert ParticipantId to numeric representation for XOR operations + * TODO: Not per spec, further review needed + * The spec assumes participant IDs support XOR natively, but we're using + * SHA256 hash to ensure consistent numeric representation for string IDs + */ +export function participantIdToNumeric(participantId: ParticipantId): bigint { + return hashToInteger(participantId); +} + +/** + * Calculate XOR distance between two participant IDs + * Used for T_resp calculations where distance affects response timing + */ +export function calculateXorDistance( + participantId1: ParticipantId, + participantId2: ParticipantId +): bigint { + const numeric1 = participantIdToNumeric(participantId1); + const numeric2 = participantIdToNumeric(participantId2); + return numeric1 ^ numeric2; +} + +/** + * Helper to convert bigint to number for timing calculations + * Ensures the result fits in JavaScript's number range + */ +export function bigintToNumber(value: bigint): number { + // For timing calculations, we modulo by MAX_SAFE_INTEGER to ensure it fits + const maxSafe = BigInt(Number.MAX_SAFE_INTEGER); + return Number(value % maxSafe); +} + +/** + * Calculate hash for a single string (used for message_id in T_resp) + */ +export function hashString(input: string): bigint { + return hashToInteger(input); +} + +/** + * Convert a hash result to hex string for debugging/logging + */ +export function hashToHex(input: string): string { + const hashBytes = sha256(new TextEncoder().encode(input)); + return bytesToHex(hashBytes); +}