diff --git a/.cspell.json b/.cspell.json index e8582e38b0..dd57575bc2 100644 --- a/.cspell.json +++ b/.cspell.json @@ -24,9 +24,11 @@ "cipherparams", "ciphertext", "circleci", + "circom", "codecov", "codegen", "commitlint", + "cooldown", "dependabot", "dialable", "dingpu", @@ -41,9 +43,7 @@ "Encrypters", "enr", "enrs", - "unsubscription", "enrtree", - "unhandle", "ephem", "esnext", "ethersproject", @@ -62,7 +62,6 @@ "ineed", "IPAM", "ipfs", - "cooldown", "iwant", "jdev", "jswaku", @@ -122,9 +121,11 @@ "typedoc", "undialable", "unencrypted", + "unhandle", "unmarshal", "unmount", "unmounts", + "unsubscription", "untracked", "upgrader", "vacp", @@ -139,6 +140,7 @@ "weboko", "websockets", "wifi", + "WTNS", "xsalsa20", "zerokit", "Привет", diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 2049b79b78..57d53bd8c9 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -8,9 +8,6 @@ on: env: NODE_JS: "22" - EXAMPLE_TEMPLATE: "web-chat" - EXAMPLE_NAME: "example" - EXAMPLE_PORT: "8080" # Firefox in container fails due to $HOME not being owned by user running commands # more details https://github.com/microsoft/playwright/issues/6500 HOME: "/root" @@ -29,11 +26,8 @@ jobs: - uses: ./.github/actions/npm - - name: Build browser container - run: npm run build --workspace=@waku/headless-tests - - - name: Build browser test environment - run: npm run build --workspace=@waku/browser-tests + - name: Build entire monorepo + run: npm run build - name: Run Playwright tests run: npm run test --workspace=@waku/browser-tests diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 03382b0d08..0000000000 --- a/Dockerfile +++ /dev/null @@ -1,45 +0,0 @@ -FROM node:20-slim - -# Install Chrome dependencies -RUN apt-get update && apt-get install -y \ - procps \ - libglib2.0-0 \ - libnss3 \ - libnspr4 \ - libatk1.0-0 \ - libatk-bridge2.0-0 \ - libcups2 \ - libdrm2 \ - libxkbcommon0 \ - libxcomposite1 \ - libxdamage1 \ - libxfixes3 \ - libxrandr2 \ - libgbm1 \ - libasound2 \ - libpango-1.0-0 \ - libcairo2 \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /app - -# Copy package files -COPY package*.json ./ -COPY packages/browser-tests/package.json ./packages/browser-tests/ -COPY packages/headless-tests/package.json ./packages/headless-tests/ - -# Install dependencies and serve -RUN npm install && npm install -g serve - -# Copy source files -COPY tsconfig.json ./ -COPY packages/ ./packages/ - -# Build packages -RUN npm run build -w packages/headless-tests && \ - npm run build:server -w packages/browser-tests && \ - npx playwright install chromium - -EXPOSE 3000 - -CMD ["npm", "run", "start:server", "-w", "packages/browser-tests"] diff --git a/package-lock.json b/package-lock.json index a3577599e9..370aa3b75a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,6 @@ "packages/relay", "packages/tests", "packages/reliability-tests", - "packages/headless-tests", "packages/browser-tests", "packages/build-utils", "packages/react" @@ -99,9 +98,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", - "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -159,15 +158,15 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -175,9 +174,9 @@ } }, "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -237,17 +236,17 @@ "license": "ISC" }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", - "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", + "@babel/traverse": "^7.28.3", "semver": "^6.3.1" }, "engines": { @@ -293,16 +292,16 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", - "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -320,6 +319,15 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", @@ -486,12 +494,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.28.2" }, "bin": { "parser": "bin/babel-parser.js" @@ -500,90 +508,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", - "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", - "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, "node_modules/@babel/plugin-proposal-async-generator-functions": { "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", @@ -707,19 +631,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -774,38 +685,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", - "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", @@ -884,23 +763,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, "node_modules/@babel/plugin-transform-arrow-functions": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", @@ -916,62 +778,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.27.1.tgz", - "integrity": "sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.5.tgz", - "integrity": "sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz", + "integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -983,52 +793,18 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", - "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.1.tgz", - "integrity": "sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.3.tgz", + "integrity": "sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.27.1", - "globals": "^11.1.0" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -1037,15 +813,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-classes/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/plugin-transform-computed-properties": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", @@ -1063,78 +830,13 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.3.tgz", - "integrity": "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", + "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", - "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -1158,22 +860,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-flow-strip-types": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz", @@ -1223,22 +909,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", - "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-literals": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", @@ -1254,55 +924,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", - "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-modules-commonjs": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", @@ -1319,107 +940,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", - "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-object-assign": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.27.1.tgz", @@ -1435,130 +955,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.3.tgz", - "integrity": "sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.27.3", - "@babel/plugin-transform-parameters": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", - "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz", - "integrity": "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", - "dev": true, + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1635,42 +1035,9 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.5.tgz", - "integrity": "sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", - "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", - "dev": true, + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.3.tgz", + "integrity": "sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1772,22 +1139,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-typescript": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", @@ -1807,39 +1158,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", - "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-unicode-regex": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", @@ -1856,152 +1174,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", - "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.2.tgz", - "integrity": "sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.27.1", - "@babel/plugin-transform-async-to-generator": "^7.27.1", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.27.1", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.27.1", - "@babel/plugin-transform-classes": "^7.27.1", - "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.27.1", - "@babel/plugin-transform-dotall-regex": "^7.27.1", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-exponentiation-operator": "^7.27.1", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.27.1", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.27.2", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", - "@babel/plugin-transform-parameters": "^7.27.1", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.27.1", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.11.0", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.40.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", - "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -2017,36 +1189,27 @@ } }, "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/types": "^7.28.2", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -2820,16 +1983,6 @@ "node": ">=12" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", - "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.17.0" - } - }, "node_modules/@electron/get": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@electron/get/-/get-4.0.1.tgz", @@ -4205,6 +3358,16 @@ "@ethersproject/strings": "^5.8.0" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/@gerrit0/mini-shiki": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.6.0.tgz", @@ -4605,17 +3768,13 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { @@ -4637,19 +3796,11 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/source-map": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -4660,6 +3811,7 @@ "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -7440,6 +6592,7 @@ "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "*", @@ -7450,6 +6603,7 @@ "version": "3.7.7", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, "license": "MIT", "dependencies": { "@types/eslint": "*", @@ -7750,6 +6904,16 @@ "@types/node": "^18.11.18" } }, + "node_modules/@types/ssh2-streams": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@types/ssh2-streams/-/ssh2-streams-0.1.12.tgz", + "integrity": "sha512-Sy8tpEmCce4Tq0oSOYdfqaBpA3hDM8SoxoFh5vzFsu2oL+znzGz8oVWW7xb4K920yYMUY+PIG31qZnFMfPWNCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/ssh2/node_modules/@types/node": { "version": "18.19.112", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.112.tgz", @@ -8398,10 +7562,6 @@ "resolved": "packages/enr", "link": true }, - "node_modules/@waku/headless-tests": { - "resolved": "packages/headless-tests", - "link": true - }, "node_modules/@waku/interfaces": { "resolved": "packages/interfaces", "link": true @@ -8423,6 +7583,46 @@ "node": ">=20" } }, + "node_modules/@waku/message-hash/node_modules/@waku/interfaces": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.30.tgz", + "integrity": "sha512-2cR8+u0CePmUFBB4vVL1zw403Rki5hK+7rKQH0WikDT4SD4lJTdMV4j3q3+YBfPTsMJrFCVFhLcqpeBADgavAw==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@waku/proto": "^0.0.10" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@waku/message-hash/node_modules/@waku/proto": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/@waku/proto/-/proto-0.0.10.tgz", + "integrity": "sha512-dgBOjwRtduZSHxmr2IqDfrzgDnog8f/qiseLV39W1WNDkVLqpNT7K2bPDPz5/e2e7EtVtTAzbGPZPakOswn5FQ==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "protons-runtime": "^5.4.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@waku/message-hash/node_modules/@waku/utils": { + "version": "0.0.23", + "resolved": "https://registry.npmjs.org/@waku/utils/-/utils-0.0.23.tgz", + "integrity": "sha512-8abBIAI7hq1kb5WVpv0o6CCW5Go3bwxo1xovKXfTZfdERwgV7/R6VcijKaUWOHF9SYIskyJuC98TFx/1HgrUBw==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@noble/hashes": "^1.3.2", + "@waku/interfaces": "0.0.30", + "chai": "^4.3.10", + "debug": "^4.3.4", + "uint8arrays": "^5.0.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@waku/proto": { "resolved": "packages/proto", "link": true @@ -8460,15 +7660,16 @@ "link": true }, "node_modules/@waku/zerokit-rln-wasm": { - "version": "0.0.13", - "resolved": "https://registry.npmjs.org/@waku/zerokit-rln-wasm/-/zerokit-rln-wasm-0.0.13.tgz", - "integrity": "sha512-x7CRIIslmfCmTZc7yVp3dhLlKeLUs8ILIm9kv7+wVJ23H4pPw0Z+uH0ueLIYYfwODI6fDiwJj3S1vdFzM8D1zA==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@waku/zerokit-rln-wasm/-/zerokit-rln-wasm-0.2.1.tgz", + "integrity": "sha512-2Xp7e92y4qZpsiTPGBSVr4gVJ9mJTLaudlo0DQxNpxJUBtoJKpxdH5xDCQDiorbkWZC2j9EId+ohhxHO/xC1QQ==", "license": "MIT or Apache2" }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", @@ -8479,24 +7680,28 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", @@ -8508,12 +7713,14 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -8526,6 +7733,7 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" @@ -8535,6 +7743,7 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" @@ -8544,12 +7753,14 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -8566,6 +7777,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -8579,6 +7791,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -8591,6 +7804,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -8605,69 +7819,25 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, - "node_modules/@webpack-cli/configtest": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", - "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "webpack": "^5.82.0", - "webpack-cli": "6.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", - "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "webpack": "^5.82.0", - "webpack-cli": "6.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", - "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "webpack": "^5.82.0", - "webpack-cli": "6.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@yarnpkg/lockfile": { @@ -8676,12 +7846,18 @@ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", "license": "BSD-2-Clause" }, - "node_modules/@zeit/schemas": { - "version": "2.36.0", - "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", - "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==", + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } }, "node_modules/abort-error": { "version": "1.0.1", @@ -10304,6 +9480,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -10321,6 +9498,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -10337,6 +9515,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, "license": "MIT" }, "node_modules/allure-commandline": { @@ -10397,48 +9576,6 @@ "mocha": ">=6.2.x" } }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-align/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -10546,10 +9683,48 @@ "node": ">=8" } }, - "node_modules/arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, "funding": [ { @@ -10565,7 +9740,130 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver/node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } }, "node_modules/archy": { "version": "1.0.0", @@ -10806,23 +10104,6 @@ "safer-buffer": "~2.1.0" } }, - "node_modules/asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", - "license": "MIT", - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", - "license": "MIT" - }, "node_modules/assert": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", @@ -10873,6 +10154,13 @@ "node": ">= 0.4" } }, + "node_modules/async-lock": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", + "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==", + "dev": true, + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -10923,153 +10211,14 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/babel-loader": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", - "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-cache-dir": "^4.0.0", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0", - "webpack": ">=5" - } - }, - "node_modules/babel-loader/node_modules/find-cache-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", - "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "common-path-prefix": "^3.0.0", - "pkg-dir": "^7.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/babel-loader/node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/babel-loader/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/babel-loader/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/babel-loader/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/babel-loader/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/babel-loader/node_modules/pkg-dir": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^6.3.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/babel-loader/node_modules/yocto-queue": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", - "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", - "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.4", + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { @@ -11099,12 +10248,12 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", - "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.4" + "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -11349,153 +10498,6 @@ "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", "license": "MIT" }, - "node_modules/boxen": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", - "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^7.0.0", - "chalk": "^5.0.1", - "cli-boxes": "^3.0.0", - "string-width": "^5.1.2", - "type-fest": "^2.13.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.0.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/boxen/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/boxen/node_modules/camelcase": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", - "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/boxen/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/boxen/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/boxen/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -11523,144 +10525,12 @@ "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", "license": "MIT" }, - "node_modules/browser-resolve": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", - "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", - "license": "MIT", - "dependencies": { - "resolve": "^1.17.0" - } - }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "license": "ISC" }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "license": "MIT", - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "license": "MIT", - "dependencies": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "node_modules/browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "license": "MIT", - "dependencies": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/browserify-rsa": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", - "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", - "license": "MIT", - "dependencies": { - "bn.js": "^5.2.1", - "randombytes": "^2.1.0", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/browserify-sign": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", - "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", - "license": "ISC", - "dependencies": { - "bn.js": "^5.2.1", - "browserify-rsa": "^4.1.0", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.5", - "hash-base": "~3.0", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.7", - "readable-stream": "^2.3.8", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/browserify-sign/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/browserify-sign/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/browserify-sign/node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/browserify-sign/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/browserify-sign/node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "license": "MIT", - "dependencies": { - "pako": "~1.0.5" - } - }, "node_modules/browserslist": { "version": "4.25.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", @@ -11732,12 +10602,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "license": "MIT" - }, "node_modules/buildcheck": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", @@ -11747,11 +10611,15 @@ "node": ">=10.0.0" } }, - "node_modules/builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", - "license": "MIT" + "node_modules/byline": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", + "integrity": "sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, "node_modules/bytes": { "version": "3.1.2", @@ -12238,6 +11106,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0" @@ -12274,19 +11143,6 @@ "node": ">=8" } }, - "node_modules/cipher-base": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz", - "integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.4", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -12333,19 +11189,6 @@ "node": ">=8" } }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -12539,140 +11382,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/clipboardy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", - "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arch": "^2.2.0", - "execa": "^5.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/clipboardy/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/clipboardy/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/clipboardy/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/clipboardy/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/clipboardy/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/clipboardy/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/clipboardy/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/clipboardy/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/clipboardy/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -12742,34 +11451,6 @@ "node": ">=0.8" } }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clone-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -12861,13 +11542,6 @@ "node": ">= 12.0.0" } }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "dev": true, - "license": "ISC" - }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -12884,72 +11558,78 @@ "dot-prop": "^5.1.0" } }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, "license": "MIT", "dependencies": { - "mime-db": ">= 1.43.0 < 2" + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 14" } }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "node_modules/compress-commons/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/compress-commons/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", "dev": true, "license": "MIT", "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">= 0.8.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -13005,17 +11685,6 @@ "dev": true, "license": "MIT" }, - "node_modules/console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==" - }, - "node_modules/constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", - "license": "MIT" - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -13309,47 +11978,73 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "license": "MIT", - "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" } }, - "node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", - "license": "MIT" - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "dev": true, "license": "MIT", "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" } }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "node_modules/crc32-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/create-require": { @@ -13382,32 +12077,6 @@ "node": "*" } }, - "node_modules/crypto-browserify": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", - "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", - "license": "MIT", - "dependencies": { - "browserify-cipher": "^1.0.1", - "browserify-sign": "^4.2.3", - "create-ecdh": "^4.0.4", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "diffie-hellman": "^5.0.3", - "hash-base": "~3.0.4", - "inherits": "^2.0.4", - "pbkdf2": "^3.1.2", - "public-encrypt": "^4.0.3", - "randombytes": "^2.1.0", - "randomfill": "^1.0.4" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/crypto-random-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", @@ -14326,16 +12995,6 @@ "node": ">=6" } }, - "node_modules/des.js": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", - "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -14398,23 +13057,6 @@ "node": ">=0.3.1" } }, - "node_modules/diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "license": "MIT", - "dependencies": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", - "license": "MIT" - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -14449,6 +13091,19 @@ "node": ">=6" } }, + "node_modules/docker-compose": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.8.tgz", + "integrity": "sha512-plizRs/Vf15H+GCVxq2EUvyPK7ei9b/cVesHvjnX4xaXjM9spHe2Ytq0BitndFgvTJ3E3NljPNUEl7BAN43iZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "yaml": "^2.2.2" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/docker-modem": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.6.tgz", @@ -14535,18 +13190,6 @@ "void-elements": "^2.0.0" } }, - "node_modules/domain-browser": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.22.0.tgz", - "integrity": "sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, "node_modules/dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -15053,19 +13696,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/envinfo": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", - "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", - "dev": true, - "license": "MIT", - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/environment": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", @@ -15204,6 +13834,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, "license": "MIT" }, "node_modules/es-object-atoms": { @@ -16484,6 +15115,16 @@ "integrity": "sha512-KGft0ldl31BZVV//jj+IAIGCxkvvUkkON+ScH6zfoX+l+omX6001ggyRSpI0Io2Hlro0ThXotswCtfzS8UkIiQ==", "license": "MIT" }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -16500,16 +15141,6 @@ "node": ">=0.8.x" } }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "license": "MIT", - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, "node_modules/execa": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", @@ -16821,6 +15452,7 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, "funding": [ { "type": "github", @@ -16833,16 +15465,6 @@ ], "license": "BSD-3-Clause" }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -16946,7 +15568,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-2.0.2.tgz", "integrity": "sha512-lO3ttPjHZRfjMcxWKb1j1eDhTFsu4meeR3lnMcnBFhk6RuLhvEiuALu2TlfL310ph4lCYYwgF/ElIjdP739tdg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -17467,6 +16088,19 @@ "node": ">=8.0.0" } }, + "node_modules/get-port": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", + "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -17621,6 +16255,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, "license": "BSD-2-Clause" }, "node_modules/glob/node_modules/brace-expansion": { @@ -17973,19 +16608,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hash-base": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", - "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.4", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -18218,12 +16840,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", - "license": "MIT" - }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -18338,26 +16954,6 @@ "node": ">=18.20" } }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/import-meta-resolve": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", @@ -18454,16 +17050,6 @@ "node": ">= 0.4" } }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/into-stream": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", @@ -19121,19 +17707,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-port-reachable": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", - "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", @@ -19368,16 +17941,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/isomorphic-fetch": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", @@ -19389,15 +17952,6 @@ "whatwg-fetch": "^3.4.1" } }, - "node_modules/isomorphic-timers-promises": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-timers-promises/-/isomorphic-timers-promises-1.0.1.tgz", - "integrity": "sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -19852,6 +18406,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -19866,6 +18421,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -20423,6 +18979,59 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -21078,6 +19687,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.11.5" @@ -21629,17 +20239,6 @@ "is-buffer": "~1.1.6" } }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "license": "MIT", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, "node_modules/mdast-util-find-and-replace": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", @@ -22557,25 +21156,6 @@ "node": ">=8.6" } }, - "node_modules/miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "license": "MIT", - "dependencies": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "bin": { - "miller-rabin": "bin/miller-rabin" - } - }, - "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", - "license": "MIT" - }, "node_modules/mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", @@ -23267,34 +21847,6 @@ } } }, - "node_modules/node-polyfill-webpack-plugin": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/node-polyfill-webpack-plugin/-/node-polyfill-webpack-plugin-4.1.0.tgz", - "integrity": "sha512-b4ei444EKkOagG/yFqojrD3QTYM5IOU1f8tn9o6uwrG4qL+brI7oVhjPVd0ZL2xy+Z6CP5bu9w8XTvlWgiXHcw==", - "license": "MIT", - "dependencies": { - "node-stdlib-browser": "^1.3.0", - "type-fest": "^4.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "webpack": ">=5" - } - }, - "node_modules/node-polyfill-webpack-plugin/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -23313,56 +21865,6 @@ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "license": "MIT" }, - "node_modules/node-stdlib-browser": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-stdlib-browser/-/node-stdlib-browser-1.3.1.tgz", - "integrity": "sha512-X75ZN8DCLftGM5iKwoYLA3rjnrAEs97MkzvSd4q2746Tgpg8b8XWiBGiBG4ZpgcAqBgtgPHTiAc8ZMCvZuikDw==", - "license": "MIT", - "dependencies": { - "assert": "^2.0.0", - "browser-resolve": "^2.0.0", - "browserify-zlib": "^0.2.0", - "buffer": "^5.7.1", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "create-require": "^1.1.1", - "crypto-browserify": "^3.12.1", - "domain-browser": "4.22.0", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "isomorphic-timers-promises": "^1.0.1", - "os-browserify": "^0.3.0", - "path-browserify": "^1.0.1", - "pkg-dir": "^5.0.0", - "process": "^0.11.10", - "punycode": "^1.4.1", - "querystring-es3": "^0.2.1", - "readable-stream": "^3.6.0", - "stream-browserify": "^3.0.0", - "stream-http": "^3.2.0", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.1", - "url": "^0.11.4", - "util": "^0.12.4", - "vm-browserify": "^1.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-stdlib-browser/node_modules/pkg-dir": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", - "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", - "license": "MIT", - "dependencies": { - "find-up": "^5.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/normalize-package-data": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", @@ -26975,16 +25477,6 @@ "node": ">= 0.8" } }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -27132,12 +25624,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", - "license": "MIT" - }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -27444,12 +25930,6 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "license": "(MIT AND Zlib)" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -27462,23 +25942,6 @@ "node": ">=6" } }, - "node_modules/parse-asn1": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", - "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", - "license": "ISC", - "dependencies": { - "asn1.js": "^4.10.1", - "browserify-aes": "^1.2.0", - "evp_bytestokey": "^1.0.3", - "hash-base": "~3.0", - "pbkdf2": "^3.1.2", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/parse-imports-exports": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", @@ -27781,13 +26244,6 @@ "node": ">=0.10.0" } }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", - "dev": true, - "license": "(WTFPL OR MIT)" - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -27864,54 +26320,6 @@ "node": "*" } }, - "node_modules/pbkdf2": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.3.tgz", - "integrity": "sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==", - "license": "MIT", - "dependencies": { - "create-hash": "~1.1.3", - "create-hmac": "^1.1.7", - "ripemd160": "=2.0.1", - "safe-buffer": "^5.2.1", - "sha.js": "^2.4.11", - "to-buffer": "^1.2.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/pbkdf2/node_modules/create-hash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", - "integrity": "sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==", - "license": "MIT", - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "sha.js": "^2.4.0" - } - }, - "node_modules/pbkdf2/node_modules/hash-base": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", - "integrity": "sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1" - } - }, - "node_modules/pbkdf2/node_modules/ripemd160": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", - "integrity": "sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==", - "license": "MIT", - "dependencies": { - "hash-base": "^2.0.0", - "inherits": "^2.0.1" - } - }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -29246,6 +27654,36 @@ "node": ">=0.10" } }, + "node_modules/properties-reader": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/properties-reader/-/properties-reader-2.3.0.tgz", + "integrity": "sha512-z597WicA7nDZxK12kZqHr2TcvwNU1GCfA5UwfDY/HDp3hXPoPlb5rlEx9bwGTiJnc0OqbBTkU975jDToth8Gxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/properties?sponsor=1" + } + }, + "node_modules/properties-reader/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -29492,26 +27930,6 @@ "dev": true, "license": "MIT" }, - "node_modules/public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "license": "MIT", - "dependencies": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", - "license": "MIT" - }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -29526,6 +27944,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true, "license": "MIT" }, "node_modules/punycode.js": { @@ -29739,14 +28158,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -29806,16 +28217,6 @@ "safe-buffer": "^5.1.0" } }, - "node_modules/randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "license": "MIT", - "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -30593,6 +28994,29 @@ "node": ">= 6" } }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -30605,19 +29029,6 @@ "node": ">=8.10.0" } }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -30799,6 +29210,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -30859,29 +29271,6 @@ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", "license": "MIT" }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/resolve-dir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", @@ -31010,16 +29399,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "license": "MIT", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, "node_modules/roarr": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", @@ -31339,6 +29718,7 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", @@ -31358,6 +29738,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -31374,6 +29755,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" @@ -31386,6 +29768,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, "license": "MIT" }, "node_modules/scrypt-js": { @@ -32162,132 +30545,6 @@ "randombytes": "^2.1.0" } }, - "node_modules/serve": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.4.tgz", - "integrity": "sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@zeit/schemas": "2.36.0", - "ajv": "8.12.0", - "arg": "5.0.2", - "boxen": "7.0.0", - "chalk": "5.0.1", - "chalk-template": "0.4.0", - "clipboardy": "3.0.0", - "compression": "1.7.4", - "is-port-reachable": "4.0.0", - "serve-handler": "6.1.6", - "update-check": "1.5.4" - }, - "bin": { - "serve": "build/main.js" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/serve-handler": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", - "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.0.0", - "content-disposition": "0.5.2", - "mime-types": "2.1.18", - "minimatch": "3.1.2", - "path-is-inside": "1.0.2", - "path-to-regexp": "3.3.0", - "range-parser": "1.2.0" - } - }, - "node_modules/serve-handler/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/serve-handler/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-handler/node_modules/content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-handler/node_modules/mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-handler/node_modules/mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "~1.33.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-handler/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/serve-handler/node_modules/path-to-regexp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", - "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", - "dev": true, - "license": "MIT" - }, - "node_modules/serve-handler/node_modules/range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", @@ -32312,83 +30569,6 @@ "node": ">= 0.8" } }, - "node_modules/serve/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/serve/node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true, - "license": "MIT" - }, - "node_modules/serve/node_modules/chalk": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", - "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/serve/node_modules/chalk-template": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", - "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/chalk-template?sponsor=1" - } - }, - "node_modules/serve/node_modules/chalk-template/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/serve/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -32441,44 +30621,12 @@ "node": ">= 0.4" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "license": "MIT" - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "license": "(MIT AND BSD-3-Clause)", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -33156,6 +31304,28 @@ "devOptional": true, "license": "BSD-3-Clause" }, + "node_modules/ssh-remote-port-forward": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz", + "integrity": "sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ssh2": "^0.5.48", + "ssh2": "^1.4.0" + } + }, + "node_modules/ssh-remote-port-forward/node_modules/@types/ssh2": { + "version": "0.5.52", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.52.tgz", + "integrity": "sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/ssh2-streams": "*" + } + }, "node_modules/ssh2": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz", @@ -33288,18 +31458,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/stream-http": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", - "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", - "license": "MIT", - "dependencies": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "xtend": "^4.0.2" - } - }, "node_modules/streamroller": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", @@ -33858,6 +32016,7 @@ "version": "5.43.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -33876,6 +32035,7 @@ "version": "5.3.14", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", @@ -33910,6 +32070,7 @@ "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -33920,6 +32081,7 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, "license": "MIT" }, "node_modules/test-exclude": { @@ -33971,6 +32133,30 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/testcontainers": { + "version": "10.28.0", + "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.28.0.tgz", + "integrity": "sha512-1fKrRRCsgAQNkarjHCMKzBKXSJFmzNTiTbhb5E/j5hflRXChEtHvkefjaHlgkNUjfw92/Dq8LTgwQn6RDBFbMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "@types/dockerode": "^3.3.35", + "archiver": "^7.0.1", + "async-lock": "^1.4.1", + "byline": "^5.0.0", + "debug": "^4.3.5", + "docker-compose": "^0.24.8", + "dockerode": "^4.0.5", + "get-port": "^7.1.0", + "proper-lockfile": "^4.1.2", + "properties-reader": "^2.3.0", + "ssh-remote-port-forward": "^1.0.4", + "tar-fs": "^3.0.7", + "tmp": "^0.2.3", + "undici": "^5.29.0" + } + }, "node_modules/text-decoder": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", @@ -34075,18 +32261,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "license": "MIT", - "dependencies": { - "setimmediate": "^1.0.4" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", @@ -34139,20 +32313,6 @@ "node": ">=14.14" } }, - "node_modules/to-buffer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", - "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", - "license": "MIT", - "dependencies": { - "isarray": "^2.0.5", - "safe-buffer": "^5.2.1", - "typed-array-buffer": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -34405,12 +32565,6 @@ "devOptional": true, "license": "0BSD" }, - "node_modules/tty-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", - "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", - "license": "MIT" - }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -35186,6 +33340,19 @@ "dev": true, "license": "MIT" }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/undici-types": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", @@ -35411,41 +33578,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/update-check": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", - "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "registry-auth-token": "3.3.2", - "registry-url": "3.1.0" - } - }, - "node_modules/update-check/node_modules/registry-auth-token": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", - "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/update-check/node_modules/registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "rc": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -35464,19 +33596,6 @@ "node": ">=6" } }, - "node_modules/url": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", - "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", - "license": "MIT", - "dependencies": { - "punycode": "^1.4.1", - "qs": "^6.12.3" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/url-join": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", @@ -35602,12 +33721,6 @@ "node": ">= 0.8" } }, - "node_modules/vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "license": "MIT" - }, "node_modules/void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", @@ -35634,6 +33747,7 @@ "version": "2.4.4", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "dev": true, "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", @@ -35694,6 +33808,7 @@ "version": "5.99.9", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz", "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", + "dev": true, "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", @@ -35737,64 +33852,6 @@ } } }, - "node_modules/webpack-cli": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", - "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@discoveryjs/json-ext": "^0.6.1", - "@webpack-cli/configtest": "^3.0.1", - "@webpack-cli/info": "^3.0.1", - "@webpack-cli/serve": "^3.0.1", - "colorette": "^2.0.14", - "commander": "^12.1.0", - "cross-spawn": "^7.0.3", - "envinfo": "^7.14.0", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^6.0.1" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.82.0" - }, - "peerDependenciesMeta": { - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/webpack-merge": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", - "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/webpack-merge": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", @@ -35809,6 +33866,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "dev": true, "license": "MIT", "engines": { "node": ">=10.13.0" @@ -35818,6 +33876,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -35831,6 +33890,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -35973,83 +34033,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/widest-line": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", - "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^5.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/widest-line/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/widest-line/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/widest-line/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/widest-line/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true, - "license": "MIT" - }, "node_modules/winston": { "version": "2.4.7", "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.7.tgz", @@ -36459,6 +34442,63 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zip-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/zod": { "version": "3.22.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", @@ -36484,68 +34524,417 @@ "version": "0.1.0", "dependencies": { "@playwright/test": "^1.51.1", - "@waku/sdk": "^0.0.30", + "@waku/discovery": "^0.0.11", + "@waku/interfaces": "^0.0.33", + "@waku/sdk": "^0.0.34", + "@waku/utils": "0.0.27", "cors": "^2.8.5", + "dotenv-flow": "^0.4.0", "express": "^4.21.2", - "node-polyfill-webpack-plugin": "^4.1.0" + "filter-obj": "^2.0.2", + "it-first": "^3.0.9" }, "devDependencies": { "@types/cors": "^2.8.15", "@types/express": "^4.17.21", "@types/node": "^20.10.0", + "@waku/tests": "*", "axios": "^1.8.4", - "dotenv-flow": "^0.4.0", + "esbuild": "^0.21.5", "npm-run-all": "^4.1.5", - "serve": "^14.2.3", - "typescript": "5.8.3", - "webpack-cli": "^6.0.1" + "testcontainers": "^10.9.0", + "typescript": "5.8.3" } }, - "packages/browser-tests/node_modules/@chainsafe/as-sha256": { - "version": "0.4.2", - "license": "Apache-2.0" - }, - "packages/browser-tests/node_modules/@chainsafe/libp2p-noise": { - "version": "16.0.0", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@chainsafe/as-chacha20poly1305": "^0.1.0", - "@chainsafe/as-sha256": "^0.4.1", - "@libp2p/crypto": "^5.0.0", - "@libp2p/interface": "^2.0.0", - "@libp2p/peer-id": "^5.0.0", - "@noble/ciphers": "^0.6.0", - "@noble/curves": "^1.1.0", - "@noble/hashes": "^1.3.1", - "it-length-prefixed": "^9.0.1", - "it-length-prefixed-stream": "^1.0.0", - "it-pair": "^2.0.6", - "it-pipe": "^3.0.1", - "it-stream-types": "^2.0.1", - "protons-runtime": "^5.5.0", - "uint8arraylist": "^2.4.3", - "uint8arrays": "^5.0.0", - "wherearewe": "^2.0.1" - } - }, - "packages/browser-tests/node_modules/@libp2p/ping": { - "version": "2.0.1", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/crypto": "^5.0.1", - "@libp2p/interface": "^2.0.1", - "@libp2p/interface-internal": "^2.0.1", - "@multiformats/multiaddr": "^12.2.3", - "it-first": "^3.0.6", - "it-pipe": "^3.0.1", - "uint8arrays": "^5.1.0" - } - }, - "packages/browser-tests/node_modules/@noble/ciphers": { - "version": "0.6.0", + "packages/browser-tests/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/browser-tests/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, "packages/browser-tests/node_modules/@types/node": { @@ -36557,14 +34946,17 @@ } }, "packages/browser-tests/node_modules/@waku/core": { - "version": "0.0.34", + "version": "0.0.38", + "resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.38.tgz", + "integrity": "sha512-Vm7o5uA7oQDc5tL6UHWUCsNqk8U7Dw+jAzbIjfsUJjNCtWov/8e1+ysrihcJfz0YmQnXfiuGA/rJSaJwMhtiNQ==", "license": "MIT OR Apache-2.0", "dependencies": { - "@libp2p/ping": "2.0.1", - "@waku/enr": "^0.0.28", - "@waku/interfaces": "0.0.29", - "@waku/proto": "0.0.9", - "@waku/utils": "0.0.22", + "@libp2p/ping": "2.0.35", + "@noble/hashes": "^1.3.2", + "@waku/enr": "^0.0.32", + "@waku/interfaces": "0.0.33", + "@waku/proto": "0.0.13", + "@waku/utils": "0.0.26", "debug": "^4.3.4", "it-all": "^3.0.4", "it-length-prefixed": "^9.0.4", @@ -36573,11 +34965,11 @@ "uuid": "^9.0.0" }, "engines": { - "node": ">=20" + "node": ">=22" }, "peerDependencies": { "@multiformats/multiaddr": "^12.0.0", - "libp2p": "2.1.8" + "libp2p": "2.8.11" }, "peerDependenciesMeta": { "@multiformats/multiaddr": { @@ -36589,38 +34981,42 @@ } }, "packages/browser-tests/node_modules/@waku/discovery": { - "version": "0.0.7", + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@waku/discovery/-/discovery-0.0.11.tgz", + "integrity": "sha512-PeL1nECYHnkCAZlIAl72JBqnuKOABlaxU0LydhH6gyCuwfilw09aWD8JFk6oVwLyW2LhUpckkUWP8Gg18qIW8g==", "license": "MIT OR Apache-2.0", "dependencies": { - "@waku/core": "0.0.34", - "@waku/enr": "0.0.28", - "@waku/interfaces": "0.0.29", - "@waku/proto": "^0.0.9", - "@waku/utils": "0.0.22", + "@waku/core": "0.0.38", + "@waku/enr": "0.0.32", + "@waku/interfaces": "0.0.33", + "@waku/proto": "^0.0.13", + "@waku/utils": "0.0.26", "debug": "^4.3.4", "dns-over-http-resolver": "^3.0.8", "hi-base32": "^0.5.1", "uint8arrays": "^5.0.1" }, "engines": { - "node": ">=20" + "node": ">=22" } }, "packages/browser-tests/node_modules/@waku/enr": { - "version": "0.0.28", + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@waku/enr/-/enr-0.0.32.tgz", + "integrity": "sha512-bAC/uYV9L25mCjoFY0Z9dQzrZr8/OV60GhNGbbqvRi9uX/k6suiT4guWATzW01c3p1TsWEO1uJS2zDzSPvgcWg==", "license": "MIT OR Apache-2.0", "dependencies": { "@ethersproject/rlp": "^5.7.0", - "@libp2p/crypto": "^5.0.1", - "@libp2p/peer-id": "^5.0.1", + "@libp2p/crypto": "5.1.6", + "@libp2p/peer-id": "5.1.7", "@multiformats/multiaddr": "^12.0.0", "@noble/secp256k1": "^1.7.1", - "@waku/utils": "0.0.22", + "@waku/utils": "0.0.26", "debug": "^4.3.4", "js-sha3": "^0.9.2" }, "engines": { - "node": ">=20" + "node": ">=22" }, "peerDependencies": { "@multiformats/multiaddr": "^12.0.0" @@ -36632,76 +35028,52 @@ } }, "packages/browser-tests/node_modules/@waku/interfaces": { - "version": "0.0.29", + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.33.tgz", + "integrity": "sha512-+DAc6l/pxW+o8a9NQb3bjZ0auwItXGuamqJe8UmjJd5w70RVqNZgl8WNH9lAkOH2UswYBdQjKaS5VNxBMFj8ew==", "license": "MIT OR Apache-2.0", - "dependencies": { - "@waku/proto": "^0.0.9" - }, "engines": { - "node": ">=20" - } - }, - "packages/browser-tests/node_modules/@waku/message-hash": { - "version": "0.1.18", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@noble/hashes": "^1.3.2", - "@waku/utils": "0.0.22" - }, - "engines": { - "node": ">=20" + "node": ">=22" } }, "packages/browser-tests/node_modules/@waku/proto": { - "version": "0.0.9", + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@waku/proto/-/proto-0.0.13.tgz", + "integrity": "sha512-t2ZFQ4TSJWsyljxBIRhA1lOvmgqDCReiX2GaRoJEXWnglOYxWlHzQ7rkIq1aONAiRqG6JWB4nfE4FcJVSE7TkA==", "license": "MIT OR Apache-2.0", "dependencies": { "protons-runtime": "^5.4.0" }, "engines": { - "node": ">=20" + "node": ">=22" } }, "packages/browser-tests/node_modules/@waku/sdk": { - "version": "0.0.30", + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@waku/sdk/-/sdk-0.0.34.tgz", + "integrity": "sha512-1kLafimSX9QeSk2/W/V+ErcekZgMR/OmtqmtM78BhW7n+Yo9XpI1ZVsGGKIu3mXnt/mKgCt0Pg4mViEl4PIlIg==", "license": "MIT OR Apache-2.0", "dependencies": { - "@chainsafe/libp2p-noise": "16.0.0", - "@libp2p/bootstrap": "^11.0.1", - "@libp2p/identify": "^3.0.1", - "@libp2p/mplex": "^11.0.1", - "@libp2p/ping": "2.0.1", - "@libp2p/websockets": "^9.0.1", + "@chainsafe/libp2p-noise": "16.1.3", + "@libp2p/bootstrap": "11.0.42", + "@libp2p/identify": "3.0.36", + "@libp2p/mplex": "11.0.42", + "@libp2p/ping": "2.0.35", + "@libp2p/websockets": "9.2.16", "@noble/hashes": "^1.3.3", - "@waku/core": "0.0.34", - "@waku/discovery": "0.0.7", - "@waku/interfaces": "0.0.29", - "@waku/message-hash": "0.1.18", - "@waku/proto": "^0.0.9", - "@waku/utils": "0.0.22", - "libp2p": "2.1.8" + "@waku/core": "0.0.38", + "@waku/discovery": "0.0.11", + "@waku/interfaces": "0.0.33", + "@waku/proto": "^0.0.13", + "@waku/utils": "0.0.26", + "libp2p": "2.8.11" }, "engines": { - "node": ">=20" - } - }, - "packages/browser-tests/node_modules/@waku/utils": { - "version": "0.0.22", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@noble/hashes": "^1.3.2", - "@waku/interfaces": "0.0.29", - "chai": "^4.3.10", - "debug": "^4.3.4", - "uint8arrays": "^5.0.1" - }, - "engines": { - "node": ">=20" + "node": ">=22" } }, "packages/browser-tests/node_modules/dotenv": { "version": "7.0.0", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=6" @@ -36709,75 +35081,56 @@ }, "packages/browser-tests/node_modules/dotenv-flow": { "version": "0.4.0", - "dev": true, "license": "MIT", "dependencies": { "dotenv": "^7.0.0" } }, - "packages/browser-tests/node_modules/it-byte-stream": { - "version": "1.1.1", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "it-queueless-pushable": "^1.0.0", - "it-stream-types": "^2.0.2", - "uint8arraylist": "^2.4.8" - } - }, - "packages/browser-tests/node_modules/it-length-prefixed-stream": { - "version": "1.2.1", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "it-byte-stream": "^1.0.0", - "it-stream-types": "^2.0.2", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8" - } - }, - "packages/browser-tests/node_modules/it-queueless-pushable": { - "version": "1.0.2", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "p-defer": "^4.0.1", - "race-signal": "^1.1.3" + "packages/browser-tests/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "packages/browser-tests/node_modules/js-sha3": { "version": "0.9.3", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.9.3.tgz", + "integrity": "sha512-BcJPCQeLg6WjEx3FE591wVAevlli8lxsxm9/FzV4HXkV49TmBH38Yvrpce6fjbADGMKFrBMGTqrVz3qPIZ88Gg==", "license": "MIT" }, - "packages/browser-tests/node_modules/libp2p": { - "version": "2.1.8", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/crypto": "^5.0.5", - "@libp2p/interface": "^2.1.3", - "@libp2p/interface-internal": "^2.0.8", - "@libp2p/logger": "^5.1.1", - "@libp2p/multistream-select": "^6.0.6", - "@libp2p/peer-collections": "^6.0.8", - "@libp2p/peer-id": "^5.0.5", - "@libp2p/peer-store": "^11.0.8", - "@libp2p/utils": "^6.1.1", - "@multiformats/dns": "^1.0.6", - "@multiformats/multiaddr": "^12.2.3", - "@multiformats/multiaddr-matcher": "^1.2.1", - "any-signal": "^4.1.1", - "datastore-core": "^10.0.0", - "interface-datastore": "^8.3.0", - "it-byte-stream": "^1.0.12", - "it-merge": "^3.0.5", - "it-parallel": "^3.0.7", - "merge-options": "^3.0.4", - "multiformats": "^13.1.0", - "p-defer": "^4.0.1", - "p-retry": "^6.2.0", - "progress-events": "^1.0.0", - "race-event": "^1.3.0", - "race-signal": "^1.0.2", - "uint8arrays": "^5.1.0" - } - }, "packages/browser-tests/node_modules/undici-types": { "version": "6.19.8", "dev": true, @@ -36785,6 +35138,8 @@ }, "packages/browser-tests/node_modules/uuid": { "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -36953,6 +35308,7 @@ "packages/headless-tests": { "name": "@waku/headless-tests", "version": "0.1.0", + "extraneous": true, "dependencies": { "@waku/sdk": "^0.0.30" }, @@ -36969,479 +35325,6 @@ "webpack-cli": "^5.1.4" } }, - "packages/headless-tests/node_modules/@chainsafe/as-sha256": { - "version": "0.4.2", - "license": "Apache-2.0" - }, - "packages/headless-tests/node_modules/@chainsafe/libp2p-noise": { - "version": "16.0.0", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@chainsafe/as-chacha20poly1305": "^0.1.0", - "@chainsafe/as-sha256": "^0.4.1", - "@libp2p/crypto": "^5.0.0", - "@libp2p/interface": "^2.0.0", - "@libp2p/peer-id": "^5.0.0", - "@noble/ciphers": "^0.6.0", - "@noble/curves": "^1.1.0", - "@noble/hashes": "^1.3.1", - "it-length-prefixed": "^9.0.1", - "it-length-prefixed-stream": "^1.0.0", - "it-pair": "^2.0.6", - "it-pipe": "^3.0.1", - "it-stream-types": "^2.0.1", - "protons-runtime": "^5.5.0", - "uint8arraylist": "^2.4.3", - "uint8arrays": "^5.0.0", - "wherearewe": "^2.0.1" - } - }, - "packages/headless-tests/node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "packages/headless-tests/node_modules/@libp2p/ping": { - "version": "2.0.1", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/crypto": "^5.0.1", - "@libp2p/interface": "^2.0.1", - "@libp2p/interface-internal": "^2.0.1", - "@multiformats/multiaddr": "^12.2.3", - "it-first": "^3.0.6", - "it-pipe": "^3.0.1", - "uint8arrays": "^5.1.0" - } - }, - "packages/headless-tests/node_modules/@noble/ciphers": { - "version": "0.6.0", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "packages/headless-tests/node_modules/@waku/core": { - "version": "0.0.34", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@libp2p/ping": "2.0.1", - "@waku/enr": "^0.0.28", - "@waku/interfaces": "0.0.29", - "@waku/proto": "0.0.9", - "@waku/utils": "0.0.22", - "debug": "^4.3.4", - "it-all": "^3.0.4", - "it-length-prefixed": "^9.0.4", - "it-pipe": "^3.0.1", - "uint8arraylist": "^2.4.3", - "uuid": "^9.0.0" - }, - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "@multiformats/multiaddr": "^12.0.0", - "libp2p": "2.1.8" - }, - "peerDependenciesMeta": { - "@multiformats/multiaddr": { - "optional": true - }, - "libp2p": { - "optional": true - } - } - }, - "packages/headless-tests/node_modules/@waku/discovery": { - "version": "0.0.7", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@waku/core": "0.0.34", - "@waku/enr": "0.0.28", - "@waku/interfaces": "0.0.29", - "@waku/proto": "^0.0.9", - "@waku/utils": "0.0.22", - "debug": "^4.3.4", - "dns-over-http-resolver": "^3.0.8", - "hi-base32": "^0.5.1", - "uint8arrays": "^5.0.1" - }, - "engines": { - "node": ">=20" - } - }, - "packages/headless-tests/node_modules/@waku/enr": { - "version": "0.0.28", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@ethersproject/rlp": "^5.7.0", - "@libp2p/crypto": "^5.0.1", - "@libp2p/peer-id": "^5.0.1", - "@multiformats/multiaddr": "^12.0.0", - "@noble/secp256k1": "^1.7.1", - "@waku/utils": "0.0.22", - "debug": "^4.3.4", - "js-sha3": "^0.9.2" - }, - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "@multiformats/multiaddr": "^12.0.0" - }, - "peerDependenciesMeta": { - "@multiformats/multiaddr": { - "optional": true - } - } - }, - "packages/headless-tests/node_modules/@waku/interfaces": { - "version": "0.0.29", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@waku/proto": "^0.0.9" - }, - "engines": { - "node": ">=20" - } - }, - "packages/headless-tests/node_modules/@waku/message-hash": { - "version": "0.1.18", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@noble/hashes": "^1.3.2", - "@waku/utils": "0.0.22" - }, - "engines": { - "node": ">=20" - } - }, - "packages/headless-tests/node_modules/@waku/proto": { - "version": "0.0.9", - "license": "MIT OR Apache-2.0", - "dependencies": { - "protons-runtime": "^5.4.0" - }, - "engines": { - "node": ">=20" - } - }, - "packages/headless-tests/node_modules/@waku/sdk": { - "version": "0.0.30", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@chainsafe/libp2p-noise": "16.0.0", - "@libp2p/bootstrap": "^11.0.1", - "@libp2p/identify": "^3.0.1", - "@libp2p/mplex": "^11.0.1", - "@libp2p/ping": "2.0.1", - "@libp2p/websockets": "^9.0.1", - "@noble/hashes": "^1.3.3", - "@waku/core": "0.0.34", - "@waku/discovery": "0.0.7", - "@waku/interfaces": "0.0.29", - "@waku/message-hash": "0.1.18", - "@waku/proto": "^0.0.9", - "@waku/utils": "0.0.22", - "libp2p": "2.1.8" - }, - "engines": { - "node": ">=20" - } - }, - "packages/headless-tests/node_modules/@waku/utils": { - "version": "0.0.22", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@noble/hashes": "^1.3.2", - "@waku/interfaces": "0.0.29", - "chai": "^4.3.10", - "debug": "^4.3.4", - "uint8arrays": "^5.0.1" - }, - "engines": { - "node": ">=20" - } - }, - "packages/headless-tests/node_modules/@webpack-cli/configtest": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "packages/headless-tests/node_modules/@webpack-cli/info": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "packages/headless-tests/node_modules/@webpack-cli/serve": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, - "packages/headless-tests/node_modules/buffer": { - "version": "6.0.3", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "packages/headless-tests/node_modules/commander": { - "version": "10.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "packages/headless-tests/node_modules/it-byte-stream": { - "version": "1.1.1", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "it-queueless-pushable": "^1.0.0", - "it-stream-types": "^2.0.2", - "uint8arraylist": "^2.4.8" - } - }, - "packages/headless-tests/node_modules/it-length-prefixed-stream": { - "version": "1.2.1", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "it-byte-stream": "^1.0.0", - "it-stream-types": "^2.0.2", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8" - } - }, - "packages/headless-tests/node_modules/it-queueless-pushable": { - "version": "1.0.2", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "p-defer": "^4.0.1", - "race-signal": "^1.1.3" - } - }, - "packages/headless-tests/node_modules/js-sha3": { - "version": "0.9.3", - "license": "MIT" - }, - "packages/headless-tests/node_modules/libp2p": { - "version": "2.1.8", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/crypto": "^5.0.5", - "@libp2p/interface": "^2.1.3", - "@libp2p/interface-internal": "^2.0.8", - "@libp2p/logger": "^5.1.1", - "@libp2p/multistream-select": "^6.0.6", - "@libp2p/peer-collections": "^6.0.8", - "@libp2p/peer-id": "^5.0.5", - "@libp2p/peer-store": "^11.0.8", - "@libp2p/utils": "^6.1.1", - "@multiformats/dns": "^1.0.6", - "@multiformats/multiaddr": "^12.2.3", - "@multiformats/multiaddr-matcher": "^1.2.1", - "any-signal": "^4.1.1", - "datastore-core": "^10.0.0", - "interface-datastore": "^8.3.0", - "it-byte-stream": "^1.0.12", - "it-merge": "^3.0.5", - "it-parallel": "^3.0.7", - "merge-options": "^3.0.4", - "multiformats": "^13.1.0", - "p-defer": "^4.0.1", - "p-retry": "^6.2.0", - "progress-events": "^1.0.0", - "race-event": "^1.3.0", - "race-signal": "^1.0.2", - "uint8arrays": "^5.1.0" - } - }, - "packages/headless-tests/node_modules/node-polyfill-webpack-plugin": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "assert": "^2.0.0", - "browserify-zlib": "^0.2.0", - "buffer": "^6.0.3", - "console-browserify": "^1.2.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.12.0", - "domain-browser": "^4.22.0", - "events": "^3.3.0", - "filter-obj": "^2.0.2", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "^1.0.1", - "process": "^0.11.10", - "punycode": "^2.1.1", - "querystring-es3": "^0.2.1", - "readable-stream": "^4.0.0", - "stream-browserify": "^3.0.0", - "stream-http": "^3.2.0", - "string_decoder": "^1.3.0", - "timers-browserify": "^2.0.12", - "tty-browserify": "^0.0.1", - "type-fest": "^2.14.0", - "url": "^0.11.0", - "util": "^0.12.4", - "vm-browserify": "^1.1.2" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "webpack": ">=5" - } - }, - "packages/headless-tests/node_modules/punycode": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "packages/headless-tests/node_modules/readable-stream": { - "version": "4.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "packages/headless-tests/node_modules/type-fest": { - "version": "2.19.0", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "packages/headless-tests/node_modules/uuid": { - "version": "9.0.1", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "packages/headless-tests/node_modules/webpack-cli": { - "version": "5.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "packages/headless-tests/node_modules/webpack-merge": { - "version": "5.10.0", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, "packages/interfaces": { "name": "@waku/interfaces", "version": "0.0.34", @@ -37959,7 +35842,7 @@ "@noble/hashes": "^1.2.0", "@waku/core": "^0.0.39", "@waku/utils": "^0.0.27", - "@waku/zerokit-rln-wasm": "^0.0.13", + "@waku/zerokit-rln-wasm": "^0.2.1", "chai": "^5.1.2", "chai-as-promised": "^8.0.1", "chai-spies": "^1.1.0", diff --git a/package.json b/package.json index 36a199dc3c..bd29d5bc00 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "packages/relay", "packages/tests", "packages/reliability-tests", - "packages/headless-tests", "packages/browser-tests", "packages/build-utils", "packages/react" diff --git a/packages/browser-tests/.dockerignore b/packages/browser-tests/.dockerignore index 5da2ccb7c4..ddabfcd06a 100644 --- a/packages/browser-tests/.dockerignore +++ b/packages/browser-tests/.dockerignore @@ -1,5 +1,4 @@ node_modules -dist build .DS_Store *.log diff --git a/packages/browser-tests/.eslintrc.cjs b/packages/browser-tests/.eslintrc.cjs index 2e2c5c6384..5bbd70f5ff 100644 --- a/packages/browser-tests/.eslintrc.cjs +++ b/packages/browser-tests/.eslintrc.cjs @@ -12,7 +12,7 @@ module.exports = { plugins: ["import"], extends: ["eslint:recommended"], rules: { - "no-console": "off" + "no-unused-vars": ["error", { "argsIgnorePattern": "^_", "ignoreRestSiblings": true }] }, globals: { process: true diff --git a/packages/browser-tests/Dockerfile b/packages/browser-tests/Dockerfile new file mode 100644 index 0000000000..76dfc9a741 --- /dev/null +++ b/packages/browser-tests/Dockerfile @@ -0,0 +1,72 @@ +# syntax=docker/dockerfile:1 + +# Build stage - install all dependencies and build +FROM node:22-bullseye AS builder + +WORKDIR /app + +# Copy package.json and temporarily remove workspace dependencies that can't be resolved +COPY package.json package.json.orig +RUN sed '/"@waku\/tests": "\*",/d' package.json.orig > package.json +RUN npm install --no-audit --no-fund + +COPY src ./src +COPY types ./types +COPY tsconfig.json ./ +COPY web ./web + +RUN npm run build + +# Production stage - only runtime dependencies +FROM node:22-bullseye + +# Install required system deps for Playwright Chromium +RUN apt-get update && apt-get install -y \ + wget \ + gnupg \ + ca-certificates \ + fonts-liberation \ + libatk-bridge2.0-0 \ + libatk1.0-0 \ + libatspi2.0-0 \ + libcups2 \ + libdbus-1-3 \ + libdrm2 \ + libgtk-3-0 \ + libnspr4 \ + libnss3 \ + libx11-xcb1 \ + libxcomposite1 \ + libxdamage1 \ + libxfixes3 \ + libxkbcommon0 \ + libxrandr2 \ + xdg-utils \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Copy package files and install only production dependencies +COPY package.json package.json.orig +RUN sed '/"@waku\/tests": "\*",/d' package.json.orig > package.json +RUN npm install --only=production --no-audit --no-fund + +# Copy built application from builder stage +COPY --from=builder /app/dist ./dist + +# Install Playwright browsers (Chromium only) at runtime layer +RUN npx playwright install --with-deps chromium + +ENV PORT=8080 \ + NODE_ENV=production + +EXPOSE 8080 + +# Use a script to handle CLI arguments and environment variables +COPY scripts/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +RUN chmod +x /usr/local/bin/docker-entrypoint.sh + +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] +CMD ["npm", "run", "start:server"] + + diff --git a/packages/browser-tests/README.md b/packages/browser-tests/README.md index d3ca0a5908..9b75f3ca8d 100644 --- a/packages/browser-tests/README.md +++ b/packages/browser-tests/README.md @@ -1,182 +1,174 @@ # Waku Browser Tests -This project provides a system for testing the Waku SDK in a browser environment. +This package provides a containerized Waku light node simulation server for testing and development. The server runs a headless browser using Playwright and exposes a REST API similar to the nwaku REST API. A Dockerfile is provided to allow programmatic simulation and "deployment" of js-waku nodes in any Waku orchestration environment that uses Docker (e.g. [10ksim](https://github.com/vacp2p/10ksim) ). -## Architecture +## Quick Start -The system consists of: - -1. **Headless Web App**: A simple web application (in the `@waku/headless-tests` package) that loads the Waku SDK and exposes shared API functions. -2. **Express Server**: A server that communicates with the headless app using Playwright. -3. **Shared API**: TypeScript functions shared between the server and web app. - -## Setup - -1. Install dependencies: - -```bash -# Install main dependencies -npm install - -# Install headless app dependencies -cd ../headless-tests -npm install -cd ../browser-tests -``` - -2. Build the application: +### Build and Run ```bash +# Build the application npm run build + +# Start the server (port 8080) +npm run start:server + +# Build and run Docker container +npm run docker:build +docker run -p 8080:8080 waku-browser-tests:local ``` -This will: -- Build the headless web app using webpack -- Compile the TypeScript server code +## Configuration -## Running +Configure the Waku node using environment variables: -Start the server with: +### Network Configuration +- `WAKU_CLUSTER_ID`: Cluster ID (default: 1) +- `WAKU_SHARD`: Specific shard number - enables static sharding mode (optional) + +**Sharding Behavior:** +- **Auto-sharding** (default): Uses `numShardsInCluster: 8` across cluster 1 +- **Static sharding**: When `WAKU_SHARD` is set, uses only that specific shard + +### Bootstrap Configuration +- `WAKU_ENR_BOOTSTRAP`: Enable ENR bootstrap mode with custom bootstrap peers (comma-separated) +- `WAKU_LIGHTPUSH_NODE`: Preferred lightpush node multiaddr (Docker only) + +### ENR Bootstrap Mode + +When `WAKU_ENR_BOOTSTRAP` is set: +- Disables default bootstrap (`defaultBootstrap: false`) +- Enables DNS discovery using production ENR trees +- Enables peer exchange and peer cache +- Uses the specified ENR for additional bootstrap peers ```bash -npm run start:server +# Example: ENR bootstrap mode +WAKU_ENR_BOOTSTRAP="enr:-QEnuEBEAyErHEfhiQxAVQoWowGTCuEF9fKZtXSd7H_PymHFhGJA3rGAYDVSHKCyJDGRLBGsloNbS8AZF33IVuefjOO6BIJpZIJ2NIJpcIQS39tkim11bHRpYWRkcnO4lgAvNihub2RlLTAxLmRvLWFtczMud2FrdXYyLnRlc3Quc3RhdHVzaW0ubmV0BgG73gMAODcxbm9kZS0wMS5hYy1jbi1ob25na29uZy1jLndha3V2Mi50ZXN0LnN0YXR1c2ltLm5ldAYBu94DACm9A62t7AQL4Ef5ZYZosRpQTzFVAB8jGjf1TER2wH-0zBOe1-MDBNLeA4lzZWNwMjU2azGhAzfsxbxyCkgCqq8WwYsVWH7YkpMLnU2Bw5xJSimxKav-g3VkcIIjKA" npm run start:server ``` -This will: -1. Serve the headless app on port 8080 -2. Start a headless browser to load the app -3. Expose API endpoints to interact with Waku - ## API Endpoints -- `GET /info`: Get information about the Waku node -- `GET /debug/v1/info`: Get debug information from the Waku node -- `POST /push`: Push a message to the Waku network (legacy) -- `POST /lightpush/v1/message`: Push a message to the Waku network (Waku REST API compatible) -- `POST /admin/v1/create-node`: Create a new Waku node (requires networkConfig) -- `POST /admin/v1/start-node`: Start the Waku node -- `POST /admin/v1/stop-node`: Stop the Waku node -- `POST /admin/v1/peers`: Dial to specified peers (Waku REST API compatible) -- `GET /filter/v2/messages/:contentTopic`: Subscribe to messages on a specific content topic using Server-Sent Events (Waku REST API compatible) -- `GET /filter/v1/messages/:contentTopic`: Retrieve stored messages from a content topic (Waku REST API compatible) +The server exposes the following HTTP endpoints: -### Example: Pushing a message with the legacy endpoint +### Node Management +- `GET /`: Health check - returns server status +- `GET /waku/v1/peer-info`: Get node peer information +- `POST /waku/v1/wait-for-peers`: Wait for peers with specific protocols +### Messaging +- `POST /lightpush/v3/message`: Send message via lightpush + +### Static Files +- `GET /app/index.html`: Web application entry point +- `GET /app/*`: Static web application files + +### Examples + +#### Send a Message (Auto-sharding) ```bash -curl -X POST http://localhost:3000/push \ - -H "Content-Type: application/json" \ - -d '{"contentTopic": "/toy-chat/2/huilong/proto", "payload": [1, 2, 3]}' -``` - -### Example: Pushing a message with the Waku REST API compatible endpoint - -```bash -curl -X POST http://localhost:3000/lightpush/v1/message \ +curl -X POST http://localhost:8080/lightpush/v3/message \ -H "Content-Type: application/json" \ -d '{ - "pubsubTopic": "/waku/2/rs/0/0", + "pubsubTopic": "", "message": { - "payload": "SGVsbG8sIFdha3Uh", - "contentTopic": "/toy-chat/2/huilong/proto", - "timestamp": 1712135330213797632 + "contentTopic": "/test/1/example/proto", + "payload": "SGVsbG8gV2FrdQ==", + "version": 1 } }' ``` -### Example: Executing a function - +#### Send a Message (Explicit pubsub topic) ```bash -curl -X POST http://localhost:3000/execute \ - -H "Content-Type: application/json" \ - -d '{"functionName": "getPeerInfo", "params": []}' -``` - -### Example: Creating a Waku node - -```bash -curl -X POST http://localhost:3000/admin/v1/create-node \ +curl -X POST http://localhost:8080/lightpush/v3/message \ -H "Content-Type: application/json" \ -d '{ - "defaultBootstrap": true, - "networkConfig": { - "clusterId": 1, - "shards": [0, 1] + "pubsubTopic": "/waku/2/rs/1/4", + "message": { + "contentTopic": "/test/1/example/proto", + "payload": "SGVsbG8gV2FrdQ==", + "version": 1 } }' ``` -### Example: Starting and stopping a Waku node - +#### Wait for Peers ```bash -# Start the node -curl -X POST http://localhost:3000/admin/v1/start-node - -# Stop the node -curl -X POST http://localhost:3000/admin/v1/stop-node -``` - -### Example: Dialing to specific peers with the Waku REST API compatible endpoint - -```bash -curl -X POST http://localhost:3000/admin/v1/peers \ +curl -X POST http://localhost:8080/waku/v1/wait-for-peers \ -H "Content-Type: application/json" \ -d '{ - "peerMultiaddrs": [ - "/ip4/127.0.0.1/tcp/8000/p2p/16Uiu2HAm4v8KuHUH6Cwz3upPeQbkyxQJsFGPdt7kHtkN8F79QiE6"] - ] + "timeoutMs": 30000, + "protocols": ["lightpush", "filter"] }' ``` -### Example: Dialing to specific peers with the execute endpoint - +#### Get Peer Info ```bash -curl -X POST http://localhost:3000/execute \ - -H "Content-Type: application/json" \ - -d '{ - "functionName": "dialPeers", - "params": [ - ["/ip4/127.0.0.1/tcp/8000/p2p/16Uiu2HAm4v8KuHUH6Cwz3upPeQbkyxQJsFGPdt7kHtkN8F79QiE6"] - ] - }' +curl -X GET http://localhost:8080/waku/v1/peer-info ``` -### Example: Subscribing to a content topic with the filter endpoint +## CLI Usage + +Run with CLI arguments: ```bash -# Open a persistent connection to receive messages as Server-Sent Events -curl -N http://localhost:3000/filter/v2/messages/%2Ftoy-chat%2F2%2Fhuilong%2Fproto - -# You can also specify clustering options -curl -N "http://localhost:3000/filter/v2/messages/%2Ftoy-chat%2F2%2Fhuilong%2Fproto?clusterId=0&shard=0" +# Custom cluster and shard +node dist/src/server.js --cluster-id=2 --shard=0 ``` -### Example: Retrieving stored messages from a content topic +## Testing + +The package includes several test suites: ```bash -# Get the most recent 20 messages -curl http://localhost:3000/filter/v1/messages/%2Ftoy-chat%2F2%2Fhuilong%2Fproto +# Basic server functionality tests (default) +npm test -# Get messages with pagination and time filtering -curl "http://localhost:3000/filter/v1/messages/%2Ftoy-chat%2F2%2Fhuilong%2Fproto?pageSize=10&startTime=1712000000000&endTime=1713000000000&ascending=true" +# Docker testing workflow +npm run docker:build +npm run test:integration + +# All tests +npm run test:all + +# Individual test suites: +npm run test:server # Server-only tests +npm run test:e2e # End-to-end tests ``` -## Extending +**Test Types:** +- `server.spec.ts` - Tests basic server functionality and static file serving +- `integration.spec.ts` - Tests Docker container integration with external services +- `e2e.spec.ts` - Full end-to-end tests using nwaku nodes -To add new functionality: +## Docker Usage -1. Add your function to `src/api/shared.ts` -2. Add your function to the `API` object in `src/api/shared.ts` -3. Use it via the server endpoints - -### Example: Dialing to specific peers +The package includes Docker support for containerized testing: ```bash -curl -X POST http://localhost:3000/execute \ - -H "Content-Type: application/json" \ - -d '{ - "functionName": "dialPeers", - "params": [ - ["/ip4/127.0.0.1/tcp/8000/p2p/16Uiu2HAm4v8KuHUH6Cwz3upPeQbkyxQJsFGPdt7kHtkN8F79QiE6"] - ] - }' +# Build image +docker build -t waku-browser-tests:local . + +# Run with ENR bootstrap +docker run -p 8080:8080 \ + -e WAKU_ENR_BOOTSTRAP="enr:-QEnuE..." \ + -e WAKU_CLUSTER_ID="1" \ + waku-browser-tests:local + +# Run with specific configuration +docker run -p 8080:8080 \ + -e WAKU_CLUSTER_ID="2" \ + -e WAKU_SHARD="0" \ + waku-browser-tests:local ``` + +## Development + +The server automatically: +- Creates a Waku light node on startup +- Configures network settings from environment variables +- Enables appropriate protocols (lightpush, filter) +- Handles peer discovery and connection management + +All endpoints are CORS-enabled for cross-origin requests. diff --git a/packages/browser-tests/package.json b/packages/browser-tests/package.json index 6cb3cdc230..56e2e58a71 100644 --- a/packages/browser-tests/package.json +++ b/packages/browser-tests/package.json @@ -5,27 +5,38 @@ "type": "module", "scripts": { "start": "npm run start:server", - "start:server": "node ./dist/server.js", - "test": "npx playwright test", + "start:server": "PORT=8080 node ./dist/src/server.js", + "test": "npx playwright test tests/server.spec.ts --reporter=line", + "test:all": "npx playwright test --reporter=line", + "test:server": "npx playwright test tests/server.spec.ts --reporter=line", + "test:integration": "npx playwright test tests/integration.spec.ts --reporter=line", + "test:e2e": "npx playwright test tests/e2e.spec.ts --reporter=line", "build:server": "tsc -p tsconfig.json", - "build": "npm run build:server" + "build:web": "esbuild web/index.ts --bundle --format=esm --platform=browser --outdir=dist/web && cp web/index.html dist/web/index.html", + "build": "npm-run-all -s build:server build:web", + "docker:build": "docker build -t waku-browser-tests:local . && docker tag waku-browser-tests:local waku-browser-tests:latest" + }, + "dependencies": { + "@playwright/test": "^1.51.1", + "@waku/discovery": "^0.0.11", + "@waku/interfaces": "^0.0.33", + "@waku/sdk": "^0.0.34", + "@waku/utils": "0.0.27", + "cors": "^2.8.5", + "dotenv-flow": "^0.4.0", + "express": "^4.21.2", + "filter-obj": "^2.0.2", + "it-first": "^3.0.9" }, "devDependencies": { "@types/cors": "^2.8.15", "@types/express": "^4.17.21", "@types/node": "^20.10.0", + "@waku/tests": "*", "axios": "^1.8.4", - "dotenv-flow": "^0.4.0", + "esbuild": "^0.21.5", "npm-run-all": "^4.1.5", - "serve": "^14.2.3", - "typescript": "5.8.3", - "webpack-cli": "^6.0.1" - }, - "dependencies": { - "@playwright/test": "^1.51.1", - "@waku/sdk": "^0.0.30", - "cors": "^2.8.5", - "express": "^4.21.2", - "node-polyfill-webpack-plugin": "^4.1.0" + "testcontainers": "^10.9.0", + "typescript": "5.8.3" } } diff --git a/packages/browser-tests/playwright.config.ts b/packages/browser-tests/playwright.config.ts index 2c90278bf2..572b2268ec 100644 --- a/packages/browser-tests/playwright.config.ts +++ b/packages/browser-tests/playwright.config.ts @@ -1,57 +1,39 @@ -// For dynamic import of dotenv-flow import { defineConfig, devices } from "@playwright/test"; +import { Logger } from "@waku/utils"; + +const log = new Logger("playwright-config"); -// Only load dotenv-flow in non-CI environments if (!process.env.CI) { - // Need to use .js extension for ES modules - // eslint-disable-next-line import/extensions - await import("dotenv-flow/config.js"); + try { + await import("dotenv-flow/config.js"); + } catch (e) { + log.warn("dotenv-flow not found; skipping env loading"); + } } const EXAMPLE_PORT = process.env.EXAMPLE_PORT || "8080"; -// web-chat specific thingy -const EXAMPLE_TEMPLATE = process.env.EXAMPLE_TEMPLATE || ""; -const BASE_URL = `http://127.0.0.1:${EXAMPLE_PORT}/${EXAMPLE_TEMPLATE}`; +const BASE_URL = `http://127.0.0.1:${EXAMPLE_PORT}`; +const TEST_IGNORE = process.env.CI ? ["tests/e2e.spec.ts"] : []; -/** - * See https://playwright.dev/docs/test-configuration. - */ export default defineConfig({ testDir: "./tests", - /* Run tests in files in parallel */ + testIgnore: TEST_IGNORE, fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, - /* Retry on CI only */ retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 2 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: "html", - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { - /* Base URL to use in actions like `await page.goto('/')`. */ baseURL: BASE_URL, - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry" }, - /* Configure projects for major browsers */ projects: [ { name: "chromium", use: { ...devices["Desktop Chrome"] } } - ], + ] - /* Run your local dev server before starting the tests */ - webServer: { - url: BASE_URL, - stdout: "pipe", - stderr: "pipe", - command: "npm run start:server", - reuseExistingServer: !process.env.CI, - timeout: 5 * 60 * 1000 // five minutes for bootstrapping an example - } }); diff --git a/packages/browser-tests/scripts/docker-entrypoint.sh b/packages/browser-tests/scripts/docker-entrypoint.sh new file mode 100644 index 0000000000..7a09d78c1c --- /dev/null +++ b/packages/browser-tests/scripts/docker-entrypoint.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# Docker entrypoint script for waku-browser-tests +# Handles CLI arguments and converts them to environment variables +# Supports reading discovered addresses from /etc/addrs/addrs.env (10k sim pattern) +echo "docker-entrypoint.sh" +echo "Using address: $addrs1" +# Only set WAKU_LIGHTPUSH_NODE if it's not already set and addrs1 is available +if [ -z "$WAKU_LIGHTPUSH_NODE" ] && [ -n "$addrs1" ]; then + export WAKU_LIGHTPUSH_NODE="$addrs1" +fi +echo "Num Args: $#" +echo "Args: $@" + +echo "WAKU_LIGHTPUSH_NODE=$WAKU_LIGHTPUSH_NODE" + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --cluster-id=*) + export WAKU_CLUSTER_ID="${1#*=}" + echo "Setting WAKU_CLUSTER_ID=${WAKU_CLUSTER_ID}" + shift + ;; + --shard=*) + export WAKU_SHARD="${1#*=}" + echo "Setting WAKU_SHARD=${WAKU_SHARD}" + shift + ;; + --lightpushnode=*) + export WAKU_LIGHTPUSH_NODE="${1#*=}" + echo "Setting WAKU_LIGHTPUSH_NODE=${WAKU_LIGHTPUSH_NODE}" + shift + ;; + --enr-bootstrap=*) + export WAKU_ENR_BOOTSTRAP="${1#*=}" + echo "Setting WAKU_ENR_BOOTSTRAP=${WAKU_ENR_BOOTSTRAP}" + shift + ;; + *) + # Unknown argument, notify user and keep it for the main command + echo "Warning: Unknown argument '$1' will be passed to the main command" + break + ;; + esac +done + +# If no specific command is provided, use the default CMD +if [ $# -eq 0 ]; then + set -- "npm" "run" "start:server" +fi + +# Execute the main command +exec "$@" diff --git a/packages/browser-tests/src/api/common.d.ts b/packages/browser-tests/src/api/common.d.ts deleted file mode 100644 index 292d30430c..0000000000 --- a/packages/browser-tests/src/api/common.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Shared utilities for working with Waku nodes - * This file contains functions used by both browser tests and server - */ - -/** - * Type definition for a minimal Waku node interface - * This allows us to use the same code in different contexts - */ -export interface IWakuNode { - libp2p: { - peerId: { toString(): string }; - getMultiaddrs(): Array<{ toString(): string }>; - getProtocols(): any; - peerStore: { - all(): Promise>; - }; - }; - lightPush: { - send: (encoder: any, message: { payload: Uint8Array }) => Promise<{ successes: any[] }>; - }; -} diff --git a/packages/browser-tests/src/api/debug.ts b/packages/browser-tests/src/api/debug.ts deleted file mode 100644 index 1d4bf4a143..0000000000 --- a/packages/browser-tests/src/api/debug.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { IWakuNode } from "./common.js"; - -/** - * Gets peer information from a Waku node - * Used in both server API endpoints and headless tests - */ -export async function getPeerInfo(waku: IWakuNode): Promise<{ - peerId: string; - multiaddrs: string[]; - peers: string[]; -}> { - const multiaddrs = waku.libp2p.getMultiaddrs(); - const peers = await waku.libp2p.peerStore.all(); - - return { - peerId: waku.libp2p.peerId.toString(), - multiaddrs: multiaddrs.map((addr) => addr.toString()), - peers: peers.map((peer) => peer.id.toString()) - }; -} - -/** - * Gets debug information from a Waku node - * Used in both server API endpoints and tests - */ -export async function getDebugInfo(waku: IWakuNode): Promise<{ - listenAddresses: string[]; - peerId: string; - protocols: string[]; -}> { - return { - listenAddresses: waku.libp2p.getMultiaddrs().map((addr) => addr.toString()), - peerId: waku.libp2p.peerId.toString(), - protocols: Array.from(waku.libp2p.getProtocols()) - }; -} diff --git a/packages/browser-tests/src/api/push.ts b/packages/browser-tests/src/api/push.ts deleted file mode 100644 index 298a3ece8d..0000000000 --- a/packages/browser-tests/src/api/push.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createEncoder, LightNode, SDKProtocolResult } from "@waku/sdk"; - -export async function pushMessage( - waku: LightNode, - contentTopic: string, - payload?: Uint8Array -): Promise { - const enc = createEncoder({ - contentTopic - }); - - const result = await waku.lightPush.send(enc, { - payload: payload ?? new Uint8Array() - }); - return result; -} diff --git a/packages/browser-tests/src/api/shared.ts b/packages/browser-tests/src/api/shared.ts deleted file mode 100644 index 1eb7950155..0000000000 --- a/packages/browser-tests/src/api/shared.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { - createDecoder, - createEncoder, - createLightNode, - CreateNodeOptions, - DecodedMessage, - LightNode, - SDKProtocolResult, - SubscribeResult -} from "@waku/sdk"; - -import { IWakuNode } from "./common.js"; - -/** - * Gets peer information from a Waku node - */ -export async function getPeerInfo(waku: IWakuNode): Promise<{ - peerId: string; - multiaddrs: string[]; - peers: string[]; -}> { - const multiaddrs = waku.libp2p.getMultiaddrs(); - const peers = await waku.libp2p.peerStore.all(); - - return { - peerId: waku.libp2p.peerId.toString(), - multiaddrs: multiaddrs.map((addr) => addr.toString()), - peers: peers.map((peer) => peer.id.toString()) - }; -} - -/** - * Gets debug information from a Waku node - */ -export async function getDebugInfo(waku: IWakuNode): Promise<{ - listenAddresses: string[]; - peerId: string; - protocols: string[]; -}> { - return { - listenAddresses: waku.libp2p.getMultiaddrs().map((addr) => addr.toString()), - peerId: waku.libp2p.peerId.toString(), - protocols: Array.from(waku.libp2p.getProtocols()) - }; -} - -/** - * Pushes a message to the network - */ -export async function pushMessage( - waku: LightNode, - contentTopic: string, - payload?: Uint8Array, - options?: { - clusterId?: number; - shard?: number; - } -): Promise { - if (!waku) { - throw new Error("Waku node not found"); - } - - const encoder = createEncoder({ - contentTopic, - pubsubTopicShardInfo: { - clusterId: options?.clusterId ?? 1, - shard: options?.shard ?? 1 - } - }); - - const result = await waku.lightPush.send(encoder, { - payload: payload ?? new Uint8Array() - }); - return result; -} - -/** - * Creates and initializes a Waku node - * Checks if a node is already running in window and stops it if it exists - */ -export async function createWakuNode( - options: CreateNodeOptions -): Promise<{ success: boolean; error?: string }> { - // Check if we're in a browser environment and a node already exists - if (typeof window === "undefined") { - return { success: false, error: "No window found" }; - } - - try { - if ((window as any).waku) { - await (window as any).waku.stop(); - } - (window as any).waku = await createLightNode(options); - return { success: true }; - } catch (error: any) { - return { success: false, error: error.message }; - } -} - -export async function startNode(): Promise<{ - success: boolean; - error?: string; -}> { - if (typeof window !== "undefined" && (window as any).waku) { - try { - await (window as any).waku.start(); - return { success: true }; - } catch (error: any) { - // Silently continue if there's an error starting the node - return { success: false, error: error.message }; - } - } - return { success: false, error: "Waku node not found in window" }; -} - -export async function stopNode(): Promise<{ - success: boolean; - error?: string; -}> { - if (typeof window !== "undefined" && (window as any).waku) { - await (window as any).waku.stop(); - return { success: true }; - } - return { success: false, error: "Waku node not found in window" }; -} - -export async function dialPeers( - waku: LightNode, - peers: string[] -): Promise<{ - total: number; - errors: string[]; -}> { - const total = peers.length; - const errors: string[] = []; - - await Promise.allSettled( - peers.map((peer) => - waku.dial(peer).catch((error: any) => { - errors.push(error.message); - }) - ) - ); - - return { total, errors }; -} - -export async function subscribe( - waku: LightNode, - contentTopic: string, - options?: { - clusterId?: number; - shard?: number; - }, - // eslint-disable-next-line no-unused-vars - callback?: (message: DecodedMessage) => void -): Promise { - const clusterId = options?.clusterId ?? 42; - const shard = options?.shard ?? 0; - - console.log( - `Creating decoder for content topic ${contentTopic} with clusterId=${clusterId}, shard=${shard}` - ); - - const pubsubTopic = `/waku/2/rs/${clusterId}/${shard}`; - - let configuredTopics: string[] = []; - - try { - const protocols = waku.libp2p.getProtocols(); - console.log(`Available protocols: ${Array.from(protocols).join(", ")}`); - - const metadataMethod = (waku.libp2p as any)._services?.metadata?.getInfo; - if (metadataMethod) { - const metadata = metadataMethod(); - console.log(`Node metadata: ${JSON.stringify(metadata)}`); - - if (metadata?.pubsubTopics && Array.isArray(metadata.pubsubTopics)) { - configuredTopics = metadata.pubsubTopics; - console.log( - `Found configured pubsub topics: ${configuredTopics.join(", ")}` - ); - } - } - - if ( - configuredTopics.length > 0 && - !configuredTopics.includes(pubsubTopic) - ) { - console.warn( - `Pubsub topic ${pubsubTopic} is not configured. Configured topics: ${configuredTopics.join(", ")}` - ); - - for (const topic of configuredTopics) { - const parts = topic.split("/"); - if (parts.length === 6 && parts[1] === "waku" && parts[3] === "rs") { - console.log(`Found potential matching pubsub topic: ${topic}`); - - // Use the first topic as a fallback if no exact match is found - // This isn't ideal but allows tests to continue - const topicClusterId = parseInt(parts[4]); - const topicShard = parseInt(parts[5]); - - if (!isNaN(topicClusterId) && !isNaN(topicShard)) { - console.log( - `Using pubsub topic with clusterId=${topicClusterId}, shard=${topicShard} instead` - ); - - const decoder = createDecoder(contentTopic, { - clusterId: topicClusterId, - shard: topicShard - }); - - try { - const subscription = await waku.filter.subscribe( - decoder, - callback ?? - ((_message) => { - console.log(_message); - }) - ); - return subscription; - } catch (innerErr: any) { - console.error( - `Error with alternative pubsub topic: ${innerErr.message}` - ); - } - } - } - } - } - } catch (err) { - console.error(`Error checking node protocols: ${String(err)}`); - } - - const decoder = createDecoder(contentTopic, { - clusterId, - shard - }); - - try { - const subscription = await waku.filter.subscribe( - decoder, - callback ?? - ((_message) => { - console.log(_message); - }) - ); - return subscription; - } catch (err: any) { - if (err.message && err.message.includes("Pubsub topic")) { - console.error(`Pubsub topic error: ${err.message}`); - console.log("Subscription failed, but continuing with empty result"); - - return { - unsubscribe: async () => { - console.log("No-op unsubscribe from failed subscription"); - } - } as unknown as SubscribeResult; - } - throw err; - } -} - -export const API = { - getPeerInfo, - getDebugInfo, - pushMessage, - createWakuNode, - startNode, - stopNode, - dialPeers, - subscribe -}; diff --git a/packages/browser-tests/src/browser/index.ts b/packages/browser-tests/src/browser/index.ts index c073c84bc5..11f7319088 100644 --- a/packages/browser-tests/src/browser/index.ts +++ b/packages/browser-tests/src/browser/index.ts @@ -1,43 +1,63 @@ import { Browser, chromium, Page } from "@playwright/test"; +import { Logger } from "@waku/utils"; + +const log = new Logger("browser-test"); -// Global variable to store the browser and page let browser: Browser | undefined; let page: Page | undefined; -/** - * Initialize browser and load headless page - */ -export async function initBrowser(): Promise { - browser = await chromium.launch({ - headless: true - }); +export async function initBrowser(appPort: number): Promise { + try { + const launchArgs = ["--no-sandbox", "--disable-setuid-sandbox"]; - if (!browser) { - throw new Error("Failed to initialize browser"); + browser = await chromium.launch({ + headless: true, + args: launchArgs + }); + + if (!browser) { + throw new Error("Failed to initialize browser"); + } + + page = await browser.newPage(); + + // Forward browser console to server logs + page.on('console', msg => { + const type = msg.type(); + const text = msg.text(); + log.info(`[Browser Console ${type.toUpperCase()}] ${text}`); + }); + + page.on('pageerror', error => { + log.error('[Browser Page Error]', error.message); + }); + + await page.goto(`http://localhost:${appPort}/app/index.html`, { + waitUntil: "networkidle", + }); + + await page.waitForFunction( + () => { + return window.wakuApi && typeof window.wakuApi.createWakuNode === "function"; + }, + { timeout: 30000 } + ); + + log.info("Browser initialized successfully with wakuApi"); + } catch (error) { + log.error("Error initializing browser:", error); + throw error; } - - page = await browser.newPage(); - - await page.goto("http://localhost:8080"); } -/** - * Get the current page instance - */ export function getPage(): Page | undefined { return page; } -/** - * Set the page instance (for use by server.ts) - */ export function setPage(pageInstance: Page | undefined): void { page = pageInstance; } -/** - * Closes the browser instance - */ export async function closeBrowser(): Promise { if (browser) { await browser.close(); diff --git a/packages/browser-tests/src/queue/index.ts b/packages/browser-tests/src/queue/index.ts deleted file mode 100644 index 4162093a4a..0000000000 --- a/packages/browser-tests/src/queue/index.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Message queue to store received messages by content topic -export interface QueuedMessage { - payload: number[] | undefined; - contentTopic: string; - timestamp: number; - receivedAt: number; -} - -export interface MessageQueue { - [contentTopic: string]: QueuedMessage[]; -} - -// Global message queue storage -const messageQueue: MessageQueue = {}; - -/** - * Store a message in the queue - */ -export function storeMessage(message: QueuedMessage): void { - const { contentTopic } = message; - - if (!messageQueue[contentTopic]) { - messageQueue[contentTopic] = []; - } - - messageQueue[contentTopic].push(message); -} - -/** - * Get messages for a specific content topic - */ -export function getMessages( - contentTopic: string, - options?: { - startTime?: number; - endTime?: number; - pageSize?: number; - ascending?: boolean; - } -): QueuedMessage[] { - if (!messageQueue[contentTopic]) { - return []; - } - - let messages = [...messageQueue[contentTopic]]; - - // Filter by time if specified - if (options?.startTime || options?.endTime) { - messages = messages.filter((msg) => { - const afterStart = options.startTime - ? msg.timestamp >= options.startTime - : true; - const beforeEnd = options.endTime - ? msg.timestamp <= options.endTime - : true; - return afterStart && beforeEnd; - }); - } - - // Sort by timestamp - messages.sort((a, b) => { - return options?.ascending - ? a.timestamp - b.timestamp - : b.timestamp - a.timestamp; - }); - - // Limit result size - if (options?.pageSize && options.pageSize > 0) { - messages = messages.slice(0, options.pageSize); - } - - return messages; -} - -/** - * Clear all messages from the queue - */ -export function clearQueue(): void { - Object.keys(messageQueue).forEach((topic) => { - delete messageQueue[topic]; - }); -} - -/** - * Get all content topics in the queue - */ -export function getContentTopics(): string[] { - return Object.keys(messageQueue); -} diff --git a/packages/browser-tests/src/routes/admin.ts b/packages/browser-tests/src/routes/admin.ts deleted file mode 100644 index bb06ad82ea..0000000000 --- a/packages/browser-tests/src/routes/admin.ts +++ /dev/null @@ -1,223 +0,0 @@ -import express, { Request, Response, Router } from "express"; - -import { getPage } from "../browser/index.js"; - -const router = Router(); - -router.head("/admin/v1/create-node", (_req: Request, res: Response) => { - res.status(200).end(); -}); - -router.head("/admin/v1/start-node", (_req: Request, res: Response) => { - res.status(200).end(); -}); - -router.head("/admin/v1/stop-node", (_req: Request, res: Response) => { - res.status(200).end(); -}); - -router.post("/admin/v1/create-node", (async (req: Request, res: Response) => { - try { - const { - defaultBootstrap = true, - networkConfig - } = req.body; - - // Validate that networkConfig is provided - if (!networkConfig) { - return res.status(400).json({ - code: 400, - message: "networkConfig is required" - }); - } - - // Validate that networkConfig has required properties - if (networkConfig.clusterId === undefined) { - return res.status(400).json({ - code: 400, - message: "networkConfig.clusterId is required" - }); - } - - const page = getPage(); - - if (!page) { - return res.status(503).json({ - code: 503, - message: "Browser not initialized" - }); - } - - const result = await page.evaluate( - ({ defaultBootstrap, networkConfig }) => { - const nodeOptions: any = { - defaultBootstrap, - relay: { - advertise: true, - gossipsubOptions: { - allowPublishToZeroPeers: true - } - }, - filter: true, - peers: [], - networkConfig: { - clusterId: networkConfig.clusterId, - shards: networkConfig.shards || [0] - } - }; - - return window.wakuAPI.createWakuNode(nodeOptions); - }, - { defaultBootstrap, networkConfig } - ); - - if (result && result.success) { - res.status(200).json({ - success: true, - message: "Waku node created successfully" - }); - } else { - res.status(500).json({ - code: 500, - message: "Failed to create Waku node", - details: result?.error || "Unknown error" - }); - } - } catch (error: any) { - res.status(500).json({ - code: 500, - message: `Could not create Waku node: ${error.message}` - }); - } -}) as express.RequestHandler); - -// Start Waku node endpoint -router.post("/admin/v1/start-node", (async (_req: Request, res: Response) => { - try { - const page = getPage(); - - if (!page) { - return res.status(503).json({ - code: 503, - message: "Browser not initialized" - }); - } - - const result = await page.evaluate(() => { - return window.wakuAPI.startNode - ? window.wakuAPI.startNode() - : { error: "startNode function not available" }; - }); - - if (result && !result.error) { - res.status(200).json({ - success: true, - message: "Waku node started successfully" - }); - } else { - res.status(500).json({ - code: 500, - message: "Failed to start Waku node", - details: result?.error || "Unknown error" - }); - } - } catch (error: any) { - res.status(500).json({ - code: 500, - message: `Could not start Waku node: ${error.message}` - }); - } -}) as express.RequestHandler); - -// Stop Waku node endpoint -router.post("/admin/v1/stop-node", (async (_req: Request, res: Response) => { - try { - const page = getPage(); - - if (!page) { - return res.status(503).json({ - code: 503, - message: "Browser not initialized" - }); - } - - const result = await page.evaluate(() => { - return window.wakuAPI.stopNode - ? window.wakuAPI.stopNode() - : { error: "stopNode function not available" }; - }); - - if (result && !result.error) { - res.status(200).json({ - success: true, - message: "Waku node stopped successfully" - }); - } else { - res.status(500).json({ - code: 500, - message: "Failed to stop Waku node", - details: result?.error || "Unknown error" - }); - } - } catch (error: any) { - res.status(500).json({ - code: 500, - message: `Could not stop Waku node: ${error.message}` - }); - } -}) as express.RequestHandler); - -// Dial to peers endpoint -router.post("/admin/v1/peers", (async (req: Request, res: Response) => { - try { - const { peerMultiaddrs } = req.body; - - if (!peerMultiaddrs || !Array.isArray(peerMultiaddrs)) { - return res.status(400).json({ - code: 400, - message: "Invalid request. peerMultiaddrs array is required." - }); - } - - const page = getPage(); - - if (!page) { - return res.status(503).json({ - code: 503, - message: "Browser not initialized" - }); - } - - const result = await page.evaluate( - ({ peerAddrs }) => { - return window.wakuAPI.dialPeers(window.waku, peerAddrs); - }, - { peerAddrs: peerMultiaddrs } - ); - - if (result) { - res.status(200).json({ - peersAdded: peerMultiaddrs.length - (result.errors?.length || 0), - peerErrors: - result.errors?.map((error: string, index: number) => { - return { - peerMultiaddr: peerMultiaddrs[index], - error - }; - }) || [] - }); - } else { - res.status(500).json({ - code: 500, - message: "Failed to dial peers" - }); - } - } catch (error: any) { - res.status(500).json({ - code: 500, - message: `Could not dial peers: ${error.message}` - }); - } -}) as express.RequestHandler); - -export default router; diff --git a/packages/browser-tests/src/routes/info.ts b/packages/browser-tests/src/routes/info.ts deleted file mode 100644 index 9bfd12f820..0000000000 --- a/packages/browser-tests/src/routes/info.ts +++ /dev/null @@ -1,51 +0,0 @@ -import express, { Request, Response, Router } from "express"; - -import { getPage } from "../browser/index.js"; - -const router = Router(); - -// Get node info endpoint -router.get("/info", (async (_req: Request, res: Response) => { - try { - const page = getPage(); - if (!page) { - return res.status(503).json({ - code: 503, - message: "Browser not initialized" - }); - } - - const result = await page.evaluate(() => { - return window.wakuAPI.getPeerInfo(window.waku); - }); - - res.json(result); - } catch (error: any) { - console.error("Error getting info:", error); - res.status(500).json({ error: error.message }); - } -}) as express.RequestHandler); - -// Get node debug info endpoint -router.get("/debug/v1/info", (async (_req: Request, res: Response) => { - try { - const page = getPage(); - if (!page) { - return res.status(503).json({ - code: 503, - message: "Browser not initialized" - }); - } - - const result = await page.evaluate(() => { - return window.wakuAPI.getDebugInfo(window.waku); - }); - - res.json(result); - } catch (error: any) { - console.error("Error getting debug info:", error); - res.status(500).json({ error: error.message }); - } -}) as express.RequestHandler); - -export default router; diff --git a/packages/browser-tests/src/routes/push.ts b/packages/browser-tests/src/routes/push.ts deleted file mode 100644 index 37e533986a..0000000000 --- a/packages/browser-tests/src/routes/push.ts +++ /dev/null @@ -1,131 +0,0 @@ -import express, { Request, Response, Router } from "express"; - -import { getPage } from "../browser/index.js"; - -const router = Router(); - -// Legacy push message endpoint -router.post("/push", (async (req: Request, res: Response) => { - try { - const { contentTopic, payload } = req.body; - - if (!contentTopic) { - return res.status(400).json({ - code: 400, - message: "Invalid request. contentTopic is required." - }); - } - - const page = getPage(); - if (!page) { - return res.status(503).json({ - code: 503, - message: "Browser not initialized" - }); - } - - const result = await page.evaluate( - ({ topic, data }) => { - return window.wakuAPI.pushMessage(window.waku, topic, data); - }, - { - topic: contentTopic, - data: payload - } - ); - - if (result) { - res.status(200).json({ - messageId: - "0x" + - Buffer.from(contentTopic + Date.now().toString()).toString("hex") - }); - } else { - res.status(503).json({ - code: 503, - message: "Could not publish message: no suitable peers" - }); - } - } catch (error: any) { - if ( - error.message.includes("size exceeds") || - error.message.includes("stream reset") - ) { - res.status(503).json({ - code: 503, - message: - "Could not publish message: message size exceeds gossipsub max message size" - }); - } else { - res.status(500).json({ - code: 500, - message: `Could not publish message: ${error.message}` - }); - } - } -}) as express.RequestHandler); - -// Waku REST API compatible push endpoint -router.post("/lightpush/v1/message", (async (req: Request, res: Response) => { - try { - const { message } = req.body; - - if (!message || !message.contentTopic) { - return res.status(400).json({ - code: 400, - message: "Invalid request. contentTopic is required." - }); - } - - const page = getPage(); - if (!page) { - return res.status(503).json({ - code: 503, - message: "Browser not initialized" - }); - } - - const result = await page.evaluate( - ({ contentTopic, payload }) => { - return window.wakuAPI.pushMessage(window.waku, contentTopic, payload); - }, - { - contentTopic: message.contentTopic, - payload: message.payload - } - ); - - if (result) { - res.status(200).json({ - messageId: - "0x" + - Buffer.from(message.contentTopic + Date.now().toString()).toString( - "hex" - ) - }); - } else { - res.status(503).json({ - code: 503, - message: "Could not publish message: no suitable peers" - }); - } - } catch (error: any) { - if ( - error.message.includes("size exceeds") || - error.message.includes("stream reset") - ) { - res.status(503).json({ - code: 503, - message: - "Could not publish message: message size exceeds gossipsub max message size" - }); - } else { - res.status(500).json({ - code: 500, - message: `Could not publish message: ${error.message}` - }); - } - } -}) as express.RequestHandler); - -export default router; diff --git a/packages/browser-tests/src/routes/waku.ts b/packages/browser-tests/src/routes/waku.ts new file mode 100644 index 0000000000..61a9be9ee5 --- /dev/null +++ b/packages/browser-tests/src/routes/waku.ts @@ -0,0 +1,87 @@ +import { Router } from "express"; +import { Logger } from "@waku/utils"; +import { + createEndpointHandler, + validators, + errorHandlers, +} from "../utils/endpoint-handler.js"; + +interface LightPushResult { + successes: string[]; + failures: Array<{ error: string; peerId?: string }>; +} + +const log = new Logger("routes:waku"); +const router = Router(); + +const corsEndpoints = [ + "/waku/v1/wait-for-peers", + "/waku/v1/peer-info", + "/lightpush/v3/message", +]; + +corsEndpoints.forEach((endpoint) => { + router.head(endpoint, (_req, res) => { + res.status(200).end(); + }); +}); + +router.post( + "/waku/v1/wait-for-peers", + createEndpointHandler({ + methodName: "waitForPeers", + validateInput: (body: unknown) => { + const bodyObj = body as { timeoutMs?: number; protocols?: string[] }; + return [ + bodyObj.timeoutMs || 10000, + bodyObj.protocols || ["lightpush", "filter"], + ]; + }, + transformResult: () => ({ + success: true, + message: "Successfully connected to peers", + }), + }), +); + +router.get( + "/waku/v1/peer-info", + createEndpointHandler({ + methodName: "getPeerInfo", + validateInput: validators.noInput, + }), +); + +router.post( + "/lightpush/v3/message", + createEndpointHandler({ + methodName: "pushMessageV3", + validateInput: (body: unknown): [string, string, string] => { + const validatedRequest = validators.requireLightpushV3(body); + + return [ + validatedRequest.message.contentTopic, + validatedRequest.message.payload, + validatedRequest.pubsubTopic, + ]; + }, + handleError: errorHandlers.lightpushError, + transformResult: (result: unknown) => { + const lightPushResult = result as LightPushResult; + if (lightPushResult && lightPushResult.successes && lightPushResult.successes.length > 0) { + log.info("[Server] Message successfully sent via v3 lightpush!"); + return { + success: true, + result: lightPushResult, + }; + } else { + return { + success: false, + error: "Could not publish message: no suitable peers", + }; + } + }, + }), +); + +export default router; diff --git a/packages/browser-tests/src/server.ts b/packages/browser-tests/src/server.ts index 269de9d161..55bb640407 100644 --- a/packages/browser-tests/src/server.ts +++ b/packages/browser-tests/src/server.ts @@ -1,507 +1,244 @@ -import { ChildProcess, exec } from "child_process"; -import * as net from "net"; -import { dirname, join } from "path"; import { fileURLToPath } from "url"; +import * as path from "path"; -import { chromium } from "@playwright/test"; import cors from "cors"; import express, { Request, Response } from "express"; +import { Logger } from "@waku/utils"; -import adminRouter from "./routes/admin.js"; -import { setPage, getPage, closeBrowser } from "./browser/index.js"; +import wakuRouter from "./routes/waku.js"; +import { initBrowser, getPage, closeBrowser } from "./browser/index.js"; +import { + DEFAULT_CLUSTER_ID, + DEFAULT_NUM_SHARDS, + Protocols, + AutoSharding, + StaticSharding, +} from "@waku/interfaces"; +import { CreateNodeOptions } from "@waku/sdk"; +import type { WindowNetworkConfig } from "../types/global.js"; -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); +interface NodeError extends Error { + code?: string; +} +const log = new Logger("server"); const app = express(); app.use(cors()); app.use(express.json()); -app.use(adminRouter); -let headlessServerProcess: ChildProcess | undefined; +import * as fs from "fs"; -interface MessageQueue { - [contentTopic: string]: Array<{ - payload: number[] | undefined; - contentTopic: string; - timestamp: number; - receivedAt: number; - }>; -} +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const distRoot = path.resolve(__dirname, ".."); +const webDir = path.resolve(distRoot, "web"); -const messageQueue: MessageQueue = {}; - -async function startHeadlessServer(): Promise { - return new Promise((resolve, reject) => { - try { - headlessServerProcess = exec( - `serve ${join(__dirname, "../../headless-tests")} -p 8080 -s`, - (error) => { - if (error) { - console.error(`Error starting serve: ${error}`); - return; - } - } - ); - - setTimeout(resolve, 2000); - } catch (error) { - console.error("Failed to start headless server:", error); - reject(error); - } - }); -} - -async function initBrowser(): Promise { +app.get("/app/index.html", (_req: Request, res: Response) => { try { - const browser = await chromium.launch({ - headless: true - }); + const htmlPath = path.join(webDir, "index.html"); + let htmlContent = fs.readFileSync(htmlPath, "utf8"); - if (!browser) { - throw new Error("Failed to initialize browser"); + const networkConfig: WindowNetworkConfig = {}; + if (process.env.WAKU_CLUSTER_ID) { + networkConfig.clusterId = parseInt(process.env.WAKU_CLUSTER_ID, 10); + } + if (process.env.WAKU_SHARD) { + networkConfig.shards = [parseInt(process.env.WAKU_SHARD, 10)]; + log.info("Using static shard:", networkConfig.shards); } - const page = await browser.newPage(); + const lightpushNode = process.env.WAKU_LIGHTPUSH_NODE || null; + const enrBootstrap = process.env.WAKU_ENR_BOOTSTRAP || null; - try { - await checkServerAvailability("http://localhost:8080", 3); - await page.goto("http://localhost:8080"); - } catch (error) { - console.error( - "Error loading headless app, continuing without it:", - error - ); - await page.setContent(` - - Waku Test Environment - -

Waku Test Environment (No headless app available)

- - - - `); - } + log.info("Network config on server start, pre headless:", networkConfig); - setPage(page); + const configScript = ` `; + const originalPattern = + ' '; + const replacement = `${configScript}\n `; + + htmlContent = htmlContent.replace(originalPattern, replacement); + + res.setHeader("Content-Type", "text/html"); + res.send(htmlContent); } catch (error) { - console.error("Error initializing browser:", error); - throw error; + log.error("Error serving dynamic index.html:", error); + res.status(500).send("Error loading page"); } -} +}); -async function checkServerAvailability( - url: string, - retries = 3 -): Promise { - for (let i = 0; i < retries; i++) { - try { - const response = await fetch(url, { method: "HEAD" }); - if (response.ok) return true; - } catch (e) { - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - } - throw new Error(`Server at ${url} not available after ${retries} retries`); -} +app.use("/app", express.static(webDir, { index: false })); -async function findAvailablePort( - startPort: number, - maxAttempts = 10 -): Promise { - for (let attempt = 0; attempt < maxAttempts; attempt++) { - const port = startPort + attempt; - try { - // Try to create a server on the port - await new Promise((resolve, reject) => { - const server = net - .createServer() - .once("error", (err: any) => { - reject(err); - }) - .once("listening", () => { - // If we can listen, the port is available - server.close(); - resolve(); - }) - .listen(port); - }); +app.use(wakuRouter); - // If we get here, the port is available - return port; - } catch (err) { - // Port is not available, continue to next port - } - } - - // If we tried all ports and none are available, throw an error - throw new Error( - `Unable to find an available port after ${maxAttempts} attempts` - ); -} - -async function startServer(port: number = 3000): Promise { - try { - await startHeadlessServer(); - - await initBrowser(); - - await startAPI(port); - } catch (error: any) { - console.error("Error starting server:", error); - } -} - -async function startAPI(requestedPort: number): Promise { +async function startAPI(requestedPort: number): Promise { try { app.get("/", (_req: Request, res: Response) => { res.json({ status: "Waku simulation server is running" }); }); - app.get("/info", (async (_req: Request, res: Response) => { - try { - const result = await getPage()?.evaluate(() => { - return window.wakuAPI.getPeerInfo(window.waku); - }); - - res.json(result); - } catch (error: any) { - console.error("Error getting info:", error); - res.status(500).json({ error: error.message }); - } - }) as express.RequestHandler); - - app.get("/debug/v1/info", (async (_req: Request, res: Response) => { - try { - const result = await getPage()?.evaluate(() => { - return window.wakuAPI.getDebugInfo(window.waku); - }); - - res.json(result); - } catch (error: any) { - console.error("Error getting debug info:", error); - res.status(500).json({ error: error.message }); - } - }) as express.RequestHandler); - - app.post("/lightpush/v1/message", (async (req: Request, res: Response) => { - try { - const { message } = req.body; - - if (!message || !message.contentTopic) { - return res.status(400).json({ - code: 400, - message: "Invalid request. contentTopic is required." - }); - } - - const result = await getPage()?.evaluate( - ({ contentTopic, payload }) => { - return window.wakuAPI.pushMessage( - window.waku, - contentTopic, - payload - ); - }, - { - contentTopic: message.contentTopic, - payload: message.payload - } - ); - - if (result) { - res.status(200).json({ - messageId: - "0x" + - Buffer.from( - message.contentTopic + Date.now().toString() - ).toString("hex") - }); - } else { - res.status(503).json({ - code: 503, - message: "Could not publish message: no suitable peers" - }); - } - } catch (error: any) { - - if ( - error.message.includes("size exceeds") || - error.message.includes("stream reset") - ) { - res.status(503).json({ - code: 503, - - message: - "Could not publish message: message size exceeds gossipsub max message size" - }); - } else { - res.status(500).json({ - code: 500, - message: `Could not publish message: ${error.message}` - }); - } - } - }) as express.RequestHandler); - - app.get("/filter/v2/messages/:contentTopic", (async ( - req: Request, - res: Response - ) => { - try { - const { contentTopic } = req.params; - const { clusterId, shard } = req.query; - - const options = { - clusterId: clusterId ? parseInt(clusterId as string, 10) : 42, // Default to match node creation - shard: shard ? parseInt(shard as string, 10) : 0 // Default to match node creation - }; - - - // Set up SSE (Server-Sent Events) - res.setHeader("Content-Type", "text/event-stream"); - res.setHeader("Cache-Control", "no-cache"); - res.setHeader("Connection", "keep-alive"); - - // Function to send SSE - const sendSSE = (data: any): void => { - res.write(`data: ${JSON.stringify(data)}\n\n`); - }; - - // Subscribe to messages - await getPage()?.evaluate( - ({ contentTopic, options }) => { - // Message handler that will send messages back to the client - const callback = (message: any): void => { - // Post message to the browser context - window.postMessage( - { - type: "WAKU_MESSAGE", - payload: { - payload: message.payload - ? Array.from(message.payload) - : undefined, - contentTopic: message.contentTopic, - timestamp: message.timestamp - } - }, - "*" - ); - }; - - return window.wakuAPI.subscribe( - window.waku, - contentTopic, - options, - callback - ); - }, - { contentTopic, options } - ); - - // Set up event listener for messages from the page - await getPage()?.exposeFunction("sendMessageToServer", (message: any) => { - // Send the message as SSE - sendSSE(message); - - const topic = message.contentTopic; - if (!messageQueue[topic]) { - messageQueue[topic] = []; - } - - messageQueue[topic].push({ - ...message, - receivedAt: Date.now() - }); - - if (messageQueue[topic].length > 1000) { - messageQueue[topic].shift(); - } - }); - - // Add event listener in the browser context to forward messages to the server - await getPage()?.evaluate(() => { - window.addEventListener("message", (event) => { - if (event.data.type === "WAKU_MESSAGE") { - (window as any).sendMessageToServer(event.data.payload); - } - }); - }); - - req.on("close", () => { - }); - } catch (error: any) { - console.error("Error in filter subscription:", error); - res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`); - res.end(); - } - }) as express.RequestHandler); - - app.get("/filter/v1/messages/:contentTopic", (async ( - req: Request, - res: Response - ) => { - try { - const { contentTopic } = req.params; - const { - pageSize = "20", - startTime, - endTime, - ascending = "false" - } = req.query; - - if (!messageQueue[contentTopic]) { - return res.status(200).json({ messages: [] }); - } - - const limit = parseInt(pageSize as string, 10); - const isAscending = (ascending as string).toLowerCase() === "true"; - const timeStart = startTime ? parseInt(startTime as string, 10) : 0; - const timeEnd = endTime ? parseInt(endTime as string, 10) : Date.now(); - - const filteredMessages = messageQueue[contentTopic] - .filter((msg) => { - const msgTime = msg.timestamp || msg.receivedAt; - return msgTime >= timeStart && msgTime <= timeEnd; - }) - .sort((a, b) => { - const timeA = a.timestamp || a.receivedAt; - const timeB = b.timestamp || b.receivedAt; - return isAscending ? timeA - timeB : timeB - timeA; - }) - .slice(0, limit); - - - // Format response to match Waku REST API format - const response = { - messages: filteredMessages.map((msg) => ({ - payload: msg.payload - ? Buffer.from(msg.payload).toString("base64") - : "", - contentTopic: msg.contentTopic, - timestamp: msg.timestamp, - version: 0 // Default version - })) - }; - - res.status(200).json(response); - } catch (error: any) { - console.error("Error retrieving messages:", error); - res.status(500).json({ - code: 500, - message: `Failed to retrieve messages: ${error.message}` - }); - } - }) as express.RequestHandler); - - // Helper endpoint for executing functions (useful for testing) - app.post("/execute", (async (req: Request, res: Response) => { - try { - const { functionName, params = [] } = req.body; - - if (functionName === "simulateMessages") { - const [contentTopic, messages] = params; - - if (!messageQueue[contentTopic]) { - messageQueue[contentTopic] = []; - } - - // Add messages to the queue - for (const msg of messages) { - messageQueue[contentTopic].push({ - ...msg, - contentTopic, - receivedAt: Date.now() - }); - } - - return res.status(200).json({ - success: true, - messagesAdded: messages.length - }); - } - - const result = await getPage()?.evaluate( - ({ fnName, fnParams }) => { - if (!window.wakuAPI[fnName]) { - return { error: `Function ${fnName} not found` }; - } - return window.wakuAPI[fnName](...fnParams); - }, - { fnName: functionName, fnParams: params } - ); - - res.status(200).json(result); - } catch (error: any) { - console.error( - `Error executing function ${req.body.functionName}:`, - error - ); - res.status(500).json({ - error: error.message - }); - } - }) as express.RequestHandler); - - - let actualPort: number; - try { - actualPort = await findAvailablePort(requestedPort); - } catch (error) { - console.error("Failed to find an available port:", error); - throw error; - } - app - .listen(actualPort, () => { + .listen(requestedPort, () => { + log.info(`API server running on http://localhost:${requestedPort}`); }) - .on("error", (error: any) => { + .on("error", (error: NodeError) => { if (error.code === "EADDRINUSE") { - console.error( - `Port ${actualPort} is already in use. Please close the application using this port and try again.` + log.error( + `Port ${requestedPort} is already in use. Please close the application using this port and try again.`, ); } else { - console.error("Error starting server:", error); + log.error("Error starting server:", error); } + throw error; }); - return Promise.resolve(); - } catch (error: any) { - console.error("Error starting server:", error); - return Promise.reject(error); + return requestedPort; + } catch (error) { + log.error("Error starting server:", error); + throw error; } } -process.on("SIGINT", (async () => { - await closeBrowser(); +async function startServer(port: number = 3000): Promise { + try { + const actualPort = await startAPI(port); + await initBrowser(actualPort); - if (headlessServerProcess && headlessServerProcess.pid) { try { - process.kill(headlessServerProcess.pid); + log.info("Auto-starting node with CLI configuration..."); + + const hasEnrBootstrap = Boolean(process.env.WAKU_ENR_BOOTSTRAP); + + const networkConfig: AutoSharding | StaticSharding = process.env.WAKU_SHARD + ? ({ + clusterId: process.env.WAKU_CLUSTER_ID + ? parseInt(process.env.WAKU_CLUSTER_ID, 10) + : DEFAULT_CLUSTER_ID, + shards: [parseInt(process.env.WAKU_SHARD, 10)], + } as StaticSharding) + : ({ + clusterId: process.env.WAKU_CLUSTER_ID + ? parseInt(process.env.WAKU_CLUSTER_ID, 10) + : DEFAULT_CLUSTER_ID, + numShardsInCluster: DEFAULT_NUM_SHARDS, + } as AutoSharding); + + const createOptions: CreateNodeOptions = { + defaultBootstrap: false, + ...(hasEnrBootstrap && { + discovery: { + dns: true, + peerExchange: true, + peerCache: true, + }, + }), + networkConfig, + }; + + log.info( + `Bootstrap mode: ${hasEnrBootstrap ? "ENR-only (defaultBootstrap=false)" : "default bootstrap (defaultBootstrap=true)"}`, + ); + if (hasEnrBootstrap) { + log.info(`ENR bootstrap peers: ${process.env.WAKU_ENR_BOOTSTRAP}`); + } + + log.info( + `Network config: ${JSON.stringify(networkConfig)}`, + ); + + await getPage()?.evaluate((config) => { + return window.wakuApi.createWakuNode(config); + }, createOptions); + await getPage()?.evaluate(() => window.wakuApi.startNode()); + + try { + await getPage()?.evaluate(() => + window.wakuApi.waitForPeers?.(5000, [Protocols.LightPush]), + ); + log.info("Auto-start completed with bootstrap peers"); + } catch (peerError) { + log.info( + "Auto-start completed (no bootstrap peers found - may be expected with test ENRs)", + ); + } } catch (e) { - // Process already stopped + log.warn("Auto-start failed:", e); + } + } catch (error) { + log.error("Error starting server:", error); + } +} + +process.on("uncaughtException", (error) => { + log.error("Uncaught Exception:", error); + if (process.env.NODE_ENV !== "production") { + process.exit(1); + } +}); + +process.on("unhandledRejection", (reason, promise) => { + log.error("Unhandled Rejection at:", promise, "reason:", reason); + if (process.env.NODE_ENV !== "production") { + process.exit(1); + } +}); + +const gracefulShutdown = async (signal: string) => { + log.info(`Received ${signal}, gracefully shutting down...`); + try { + await closeBrowser(); + } catch (e) { + log.warn("Error closing browser:", e); + } + process.exit(0); +}; + +process.on("SIGINT", () => gracefulShutdown("SIGINT")); +process.on("SIGTERM", () => gracefulShutdown("SIGTERM")); + +function parseCliArgs() { + const args = process.argv.slice(2); + let clusterId: number | undefined; + let shard: number | undefined; + + for (const arg of args) { + if (arg.startsWith("--cluster-id=")) { + clusterId = parseInt(arg.split("=")[1], 10); + if (isNaN(clusterId)) { + log.error("Invalid cluster-id value. Must be a number."); + process.exit(1); + } + } else if (arg.startsWith("--shard=")) { + shard = parseInt(arg.split("=")[1], 10); + if (isNaN(shard)) { + log.error("Invalid shard value. Must be a number."); + process.exit(1); + } } } - process.exit(0); -}) as any); + return { clusterId, shard }; +} const isMainModule = process.argv[1] === fileURLToPath(import.meta.url); if (isMainModule) { const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000; + const cliArgs = parseCliArgs(); + + if (cliArgs.clusterId !== undefined) { + process.env.WAKU_CLUSTER_ID = cliArgs.clusterId.toString(); + log.info(`Using CLI cluster ID: ${cliArgs.clusterId}`); + } + if (cliArgs.shard !== undefined) { + process.env.WAKU_SHARD = cliArgs.shard.toString(); + log.info(`Using CLI shard: ${cliArgs.shard}`); + } + void startServer(port); } diff --git a/packages/browser-tests/src/utils.js b/packages/browser-tests/src/utils.js deleted file mode 100644 index 0122958d04..0000000000 --- a/packages/browser-tests/src/utils.js +++ /dev/null @@ -1,8 +0,0 @@ -import { readFileSync } from "fs"; -import { dirname } from "path"; -import { fileURLToPath } from "url"; - -const __filename = fileURLToPath(import.meta.url); -export const __dirname = dirname(__filename); - -export const readJSON = (path) => JSON.parse(readFileSync(path, "utf-8")); diff --git a/packages/browser-tests/src/utils/endpoint-handler.ts b/packages/browser-tests/src/utils/endpoint-handler.ts new file mode 100644 index 0000000000..5888520946 --- /dev/null +++ b/packages/browser-tests/src/utils/endpoint-handler.ts @@ -0,0 +1,197 @@ +import { Request, Response } from "express"; +import { Logger } from "@waku/utils"; +import { getPage } from "../browser/index.js"; +import type { ITestBrowser } from "../../types/global.js"; + +const log = new Logger("endpoint-handler"); + +export interface LightpushV3Request { + pubsubTopic: string; + message: { + payload: string; + contentTopic: string; + version: number; + }; +} + +export interface LightpushV3Response { + success?: boolean; + error?: string; + result?: { + successes: string[]; + failures: Array<{ + error: string; + peerId?: string; + }>; + }; +} + +export interface EndpointConfig { + methodName: string; + validateInput?: (_requestBody: unknown) => TInput; + transformResult?: (_sdkResult: unknown) => TOutput; + handleError?: (_caughtError: Error) => { code: number; message: string }; + preCheck?: () => Promise | void; + logResult?: boolean; +} + +export function createEndpointHandler( + config: EndpointConfig, +) { + return async (req: Request, res: Response) => { + try { + let input: TInput; + try { + input = config.validateInput + ? config.validateInput(req.body) + : req.body; + } catch (validationError) { + return res.status(400).json({ + code: 400, + message: `Invalid input: ${validationError instanceof Error ? validationError.message : String(validationError)}`, + }); + } + + if (config.preCheck) { + try { + await config.preCheck(); + } catch (checkError) { + return res.status(503).json({ + code: 503, + message: checkError instanceof Error ? checkError.message : String(checkError), + }); + } + } + + const page = getPage(); + if (!page) { + return res.status(503).json({ + code: 503, + message: "Browser not initialized", + }); + } + + const result = await page.evaluate( + ({ methodName, params }) => { + const testWindow = window as ITestBrowser; + if (!testWindow.wakuApi) { + throw new Error("window.wakuApi is not available"); + } + + const wakuApi = testWindow.wakuApi as unknown as Record; + const method = wakuApi[methodName]; + if (typeof method !== "function") { + throw new Error(`window.wakuApi.${methodName} is not a function`); + } + + if (params === null || params === undefined) { + return method.call(testWindow.wakuApi); + } else if (Array.isArray(params)) { + return method.apply(testWindow.wakuApi, params); + } else { + return method.call(testWindow.wakuApi, params); + } + }, + { methodName: config.methodName, params: input }, + ); + + if (config.logResult !== false) { + log.info( + `[${config.methodName}] Result:`, + JSON.stringify(result, null, 2), + ); + } + + const finalResult = config.transformResult + ? config.transformResult(result) + : result; + + res.status(200).json(finalResult); + } catch (error) { + if (config.handleError) { + const errorResponse = config.handleError(error as Error); + return res.status(errorResponse.code).json({ + code: errorResponse.code, + message: errorResponse.message, + }); + } + + log.error(`[${config.methodName}] Error:`, error); + res.status(500).json({ + code: 500, + message: `Could not execute ${config.methodName}: ${error instanceof Error ? error.message : String(error)}`, + }); + } + }; +} + +export const validators = { + requireLightpushV3: (body: unknown): LightpushV3Request => { + // Type guard to check if body is an object + if (!body || typeof body !== "object") { + throw new Error("Request body must be an object"); + } + + const bodyObj = body as Record; + + if ( + bodyObj.pubsubTopic !== undefined && + typeof bodyObj.pubsubTopic !== "string" + ) { + throw new Error("pubsubTopic must be a string if provided"); + } + if (!bodyObj.message || typeof bodyObj.message !== "object") { + throw new Error("message is required and must be an object"); + } + + const message = bodyObj.message as Record; + + if ( + !message.contentTopic || + typeof message.contentTopic !== "string" + ) { + throw new Error("message.contentTopic is required and must be a string"); + } + if (!message.payload || typeof message.payload !== "string") { + throw new Error( + "message.payload is required and must be a string (base64 encoded)", + ); + } + if ( + message.version !== undefined && + typeof message.version !== "number" + ) { + throw new Error("message.version must be a number if provided"); + } + + return { + pubsubTopic: (bodyObj.pubsubTopic as string) || "", + message: { + payload: message.payload as string, + contentTopic: message.contentTopic as string, + version: (message.version as number) || 1, + }, + }; + }, + + noInput: () => null, +}; + +export const errorHandlers = { + lightpushError: (error: Error) => { + if ( + error.message.includes("size exceeds") || + error.message.includes("stream reset") + ) { + return { + code: 503, + message: + "Could not publish message: message size exceeds gossipsub max message size", + }; + } + return { + code: 500, + message: `Could not publish message: ${error.message}`, + }; + }, +}; diff --git a/packages/browser-tests/tests/e2e.spec.ts b/packages/browser-tests/tests/e2e.spec.ts new file mode 100644 index 0000000000..5cf2c2505e --- /dev/null +++ b/packages/browser-tests/tests/e2e.spec.ts @@ -0,0 +1,117 @@ +import { test, expect } from "@playwright/test"; +import axios from "axios"; +import { StartedTestContainer } from "testcontainers"; +import { DefaultTestRoutingInfo } from "@waku/tests"; +import { + startBrowserTestsContainer, + stopContainer +} from "./utils/container-helpers.js"; +import { + createTwoNodeNetwork, + getDockerAccessibleMultiaddr, + stopNwakuNodes, + TwoNodeNetwork +} from "./utils/nwaku-helpers.js"; +import { + ENV_BUILDERS, + TEST_CONFIG, + ASSERTIONS +} from "./utils/test-config.js"; + +test.describe.configure({ mode: "serial" }); + +let container: StartedTestContainer; +let nwakuNodes: TwoNodeNetwork; +let baseUrl: string; + +test.beforeAll(async () => { + nwakuNodes = await createTwoNodeNetwork(); + + const lightPushPeerAddr = await getDockerAccessibleMultiaddr(nwakuNodes.nodes[0]); + + const result = await startBrowserTestsContainer({ + environment: { + ...ENV_BUILDERS.withLocalLightPush(lightPushPeerAddr), + DEBUG: "waku:*", + WAKU_LIGHTPUSH_NODE: lightPushPeerAddr, + }, + networkMode: "waku", + }); + + container = result.container; + baseUrl = result.baseUrl; +}); + +test.afterAll(async () => { + await Promise.all([ + stopContainer(container), + stopNwakuNodes(nwakuNodes?.nodes || []), + ]); +}); + +test("WakuHeadless can discover nwaku peer and use it for light push", async () => { + test.setTimeout(TEST_CONFIG.DEFAULT_TEST_TIMEOUT); + + const contentTopic = TEST_CONFIG.DEFAULT_CONTENT_TOPIC; + const testMessage = TEST_CONFIG.DEFAULT_TEST_MESSAGE; + + await new Promise((r) => setTimeout(r, TEST_CONFIG.WAKU_INIT_DELAY)); + + const healthResponse = await axios.get(`${baseUrl}/`, { timeout: 5000 }); + ASSERTIONS.serverHealth(healthResponse); + + try { + await axios.post(`${baseUrl}/waku/v1/wait-for-peers`, { + timeoutMs: 10000, + protocols: ["lightpush"], + }, { timeout: 15000 }); + } catch { + // Ignore errors + } + + const peerInfoResponse = await axios.get(`${baseUrl}/waku/v1/peer-info`); + ASSERTIONS.peerInfo(peerInfoResponse); + + const routingInfo = DefaultTestRoutingInfo; + + const subscriptionResults = await Promise.all([ + nwakuNodes.nodes[0].ensureSubscriptions([routingInfo.pubsubTopic]), + nwakuNodes.nodes[1].ensureSubscriptions([routingInfo.pubsubTopic]) + ]); + + expect(subscriptionResults[0]).toBe(true); + expect(subscriptionResults[1]).toBe(true); + + await new Promise((r) => setTimeout(r, TEST_CONFIG.SUBSCRIPTION_DELAY)); + + const base64Payload = btoa(testMessage); + + const pushResponse = await axios.post(`${baseUrl}/lightpush/v3/message`, { + pubsubTopic: routingInfo.pubsubTopic, + message: { + contentTopic, + payload: base64Payload, + version: 1, + }, + }); + + ASSERTIONS.lightPushV3Success(pushResponse); + + await new Promise((r) => setTimeout(r, TEST_CONFIG.MESSAGE_PROPAGATION_DELAY)); + + const [node1Messages, node2Messages] = await Promise.all([ + nwakuNodes.nodes[0].messages(contentTopic), + nwakuNodes.nodes[1].messages(contentTopic) + ]); + + + const totalMessages = node1Messages.length + node2Messages.length; + expect(totalMessages).toBeGreaterThanOrEqual(1); + + const receivedMessages = [...node1Messages, ...node2Messages]; + expect(receivedMessages.length).toBeGreaterThan(0); + + const receivedMessage = receivedMessages[0]; + ASSERTIONS.messageContent(receivedMessage, testMessage, contentTopic); + +}); diff --git a/packages/browser-tests/tests/headless.spec.ts b/packages/browser-tests/tests/headless.spec.ts deleted file mode 100644 index 0817d1c1cf..0000000000 --- a/packages/browser-tests/tests/headless.spec.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { expect, test } from "@playwright/test"; -import { LightNode } from "@waku/sdk"; - -import { API } from "../src/api/shared.js"; -import { NETWORK_CONFIG, ACTIVE_PEERS } from "./test-config.js"; - -// Define the window interface for TypeScript -declare global { - // eslint-disable-next-line no-unused-vars - interface Window { - waku: LightNode; - wakuAPI: typeof API; - } -} - -test.describe("waku", () => { - test.beforeEach(async ({ page }) => { - await page.goto(""); - await page.waitForTimeout(5000); - - // Create and initialize a fresh Waku node for each test - const setupResult = await page.evaluate(async (config) => { - try { - await window.wakuAPI.createWakuNode({ - ...config.defaultNodeConfig, - networkConfig: config.networkConfig - }); - await window.wakuAPI.startNode(); - return { success: true }; - } catch (error) { - console.error("Failed to initialize Waku node:", error); - return { success: false, error: String(error) }; - } - }, NETWORK_CONFIG); - - expect(setupResult.success).toBe(true); - }); - - test("can get peer id", async ({ page }) => { - const peerId = await page.evaluate(() => { - return window.waku.libp2p.peerId.toString(); - }); - - expect(peerId).toBeDefined(); - console.log("Peer ID:", peerId); - }); - - test("can get info", async ({ page }) => { - const info = await page.evaluate(() => { - return window.wakuAPI.getPeerInfo(window.waku); - }); - - expect(info).toBeDefined(); - expect(info.peerId).toBeDefined(); - expect(info.multiaddrs).toBeDefined(); - expect(info.peers).toBeDefined(); - console.log("Info:", info); - }); - - test("can get debug info", async ({ page }) => { - const debug = await page.evaluate(() => { - return window.wakuAPI.getDebugInfo(window.waku); - }); - - expect(debug).toBeDefined(); - expect(debug.listenAddresses).toBeDefined(); - expect(debug.peerId).toBeDefined(); - expect(debug.protocols).toBeDefined(); - console.log("Debug:", debug); - }); - - test("can dial peers", async ({ page }) => { - const result = await page.evaluate((peerAddrs) => { - return window.wakuAPI.dialPeers(window.waku, peerAddrs); - }, ACTIVE_PEERS); - - expect(result).toBeDefined(); - expect(result.total).toBe(ACTIVE_PEERS.length); - expect(result.errors.length >= result.total).toBe(false); - console.log("Dial result:", result); - }); - - test("can push a message", async ({ page }) => { - // First dial to peers - await page.evaluate((peersToDial) => { - return window.wakuAPI.dialPeers(window.waku, peersToDial); - }, ACTIVE_PEERS); - - // Create a test message - const contentTopic = NETWORK_CONFIG.testMessage.contentTopic; - const payload = new TextEncoder().encode(NETWORK_CONFIG.testMessage.payload); - const arrayPayload = Array.from(payload); - - // Push the message - const result = await page.evaluate( - ({ topic, data }) => { - return window.wakuAPI.pushMessage( - window.waku, - topic, - new Uint8Array(data) - ); - }, - { topic: contentTopic, data: arrayPayload } - ); - - expect(result).toBeDefined(); - console.log("Push result:", result); - }); - - test("can recreate Waku node", async ({ page }) => { - // Get the current node's peer ID - const initialPeerId = await page.evaluate(() => { - return window.waku.libp2p.peerId.toString(); - }); - - // Create a new node with different parameters - const result = await page.evaluate(() => { - return window.wakuAPI.createWakuNode({ - defaultBootstrap: true // Different from beforeEach - }); - }); - - expect(result.success).toBe(true); - - // Start the new node - await page.evaluate(() => window.wakuAPI.startNode()); - - // Get the new peer ID - const newPeerId = await page.evaluate(() => { - return window.waku.libp2p.peerId.toString(); - }); - - expect(newPeerId).not.toBe(initialPeerId); - console.log("Initial:", initialPeerId, "New:", newPeerId); - }); -}); diff --git a/packages/browser-tests/tests/integration.spec.ts b/packages/browser-tests/tests/integration.spec.ts new file mode 100644 index 0000000000..cfceafd5e1 --- /dev/null +++ b/packages/browser-tests/tests/integration.spec.ts @@ -0,0 +1,134 @@ +import { test, expect } from "@playwright/test"; +import axios from "axios"; +import { StartedTestContainer } from "testcontainers"; +import { + createLightNode, + LightNode, + Protocols, + IDecodedMessage, +} from "@waku/sdk"; +import { DEFAULT_CLUSTER_ID, DEFAULT_NUM_SHARDS } from "@waku/interfaces"; +import { startBrowserTestsContainer, stopContainer } from "./utils/container-helpers.js"; +import { ENV_BUILDERS, TEST_CONFIG } from "./utils/test-config.js"; + +test.describe.configure({ mode: "serial" }); + +let container: StartedTestContainer; +let baseUrl: string; +let wakuNode: LightNode; + +test.beforeAll(async () => { + const result = await startBrowserTestsContainer({ + environment: { + ...ENV_BUILDERS.withProductionEnr(), + DEBUG: "waku:*", + }, + }); + + container = result.container; + baseUrl = result.baseUrl; +}); + +test.afterAll(async () => { + if (wakuNode) { + try { + await wakuNode.stop(); + } catch { + // Ignore errors + } + } + + await stopContainer(container); +}); + +test("cross-network message delivery: SDK light node receives server lightpush", async () => { + test.setTimeout(TEST_CONFIG.DEFAULT_TEST_TIMEOUT); + + const contentTopic = TEST_CONFIG.DEFAULT_CONTENT_TOPIC; + const testMessage = TEST_CONFIG.DEFAULT_TEST_MESSAGE; + + wakuNode = await createLightNode({ + defaultBootstrap: true, + discovery: { + dns: true, + peerExchange: true, + peerCache: true, + }, + networkConfig: { + clusterId: DEFAULT_CLUSTER_ID, + numShardsInCluster: DEFAULT_NUM_SHARDS, + }, + libp2p: { + filterMultiaddrs: false, + }, + }); + + await wakuNode.start(); + + await wakuNode.waitForPeers( + [Protocols.Filter, Protocols.LightPush], + 30000, + ); + + const messages: IDecodedMessage[] = []; + const decoder = wakuNode.createDecoder({ contentTopic }); + + if ( + !(await wakuNode.filter.subscribe([decoder], (message) => { + messages.push(message); + })) + ) { + throw new Error("Failed to subscribe to Filter"); + } + + await new Promise((r) => setTimeout(r, 2000)); + + const messagePromise = new Promise((resolve) => { + const originalLength = messages.length; + const checkForMessage = () => { + if (messages.length > originalLength) { + resolve(); + } else { + setTimeout(checkForMessage, 100); + } + }; + checkForMessage(); + }); + + await axios.post(`${baseUrl}/waku/v1/wait-for-peers`, { + timeoutMs: 30000, // Increased timeout + protocols: ["lightpush", "filter"], + }); + + await new Promise((r) => setTimeout(r, 10000)); + + const base64Payload = btoa(testMessage); + + const pushResponse = await axios.post(`${baseUrl}/lightpush/v3/message`, { + pubsubTopic: decoder.pubsubTopic, + message: { + contentTopic, + payload: base64Payload, + version: 1, + }, + }); + + expect(pushResponse.status).toBe(200); + expect(pushResponse.data.success).toBe(true); + + await Promise.race([ + messagePromise, + new Promise((_, reject) => + setTimeout(() => { + reject(new Error("Timeout waiting for message")); + }, 45000), + ), + ]); + + expect(messages).toHaveLength(1); + const receivedMessage = messages[0]; + expect(receivedMessage.contentTopic).toBe(contentTopic); + + const receivedPayload = new TextDecoder().decode(receivedMessage.payload); + expect(receivedPayload).toBe(testMessage); +}); diff --git a/packages/browser-tests/tests/server.spec.ts b/packages/browser-tests/tests/server.spec.ts index 85c8098c05..c20d77b64f 100644 --- a/packages/browser-tests/tests/server.spec.ts +++ b/packages/browser-tests/tests/server.spec.ts @@ -1,722 +1,82 @@ -import { ChildProcess, exec, spawn } from "child_process"; -import * as http from "http"; -import * as net from "net"; -import { join } from "path"; - -import { expect, test } from "@playwright/test"; +import { test, expect } from "@playwright/test"; import axios from "axios"; +import { spawn, ChildProcess } from "child_process"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; -// The default URL, but we'll update this if we detect a different port -let API_URL = "http://localhost:3000"; -// Need this for basic node initialization that doesn't rely on /execute -const PEERS = [ - "/dns4/waku-test.bloxy.one/tcp/8095/wss/p2p/16Uiu2HAmSZbDB7CusdRhgkD81VssRjQV5ZH13FbzCGcdnbbh6VwZ", - "/dns4/waku.fryorcraken.xyz/tcp/8000/wss/p2p/16Uiu2HAmMRvhDHrtiHft1FTUYnn6cVA8AWVrTyLUayJJ3MWpUZDB" -]; +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); -let serverProcess: ChildProcess; - -// Force tests to run sequentially to avoid port conflicts test.describe.configure({ mode: "serial" }); -// Helper function to check if a port is in use -async function isPortInUse(port: number): Promise { - return new Promise((resolve) => { - const server = net - .createServer() - .once("error", () => { - // Port is in use - resolve(true); - }) - .once("listening", () => { - // Port is free, close server - server.close(); - resolve(false); - }) - .listen(port); - }); -} +test.describe("Server Tests", () => { + let serverProcess: ChildProcess; + let baseUrl = "http://localhost:3000"; -// Helper function to kill processes on port 3000 -async function killProcessOnPort(): Promise { - return new Promise((resolve) => { - // Different commands for different platforms - const cmd = - process.platform === "win32" - ? `netstat -ano | findstr :3000 | findstr LISTENING` - : `lsof -i:3000 -t`; + test.beforeAll(async () => { + const serverPath = join(__dirname, "..", "dist", "src", "server.js"); - exec(cmd, (err, stdout) => { - if (err || !stdout.trim()) { - console.log("No process running on port 3000"); - resolve(); - return; - } - - console.log(`Found processes on port 3000: ${stdout.trim()}`); - - // Kill the process - const killCmd = - process.platform === "win32" - ? `FOR /F "tokens=5" %P IN ('netstat -ano ^| findstr :3000 ^| findstr LISTENING') DO taskkill /F /PID %P` - : `kill -9 ${stdout.trim()}`; - - exec(killCmd, (killErr) => { - if (killErr) { - console.error(`Error killing process: ${killErr.message}`); - } else { - console.log("Killed process on port 3000"); - } - - // Wait a moment for OS to release the port - setTimeout(resolve, 500); - }); + serverProcess = spawn("node", [serverPath], { + stdio: "pipe", + env: { ...process.env, PORT: "3000" } }); - }); -} -// Helper function to wait for the API server to be available -async function waitForApiServer( - maxRetries = 10, - interval = 1000 -): Promise { - for (let i = 0; i < maxRetries; i++) { - try { - const response = await axios.get(API_URL, { timeout: 2000 }); - if (response.status === 200) { - console.log(`API server is available at ${API_URL}`); - return true; - } - } catch (e) { - console.log( - `API server not available at ${API_URL}, retrying (${i + 1}/${maxRetries})...` - ); - await new Promise((resolve) => setTimeout(resolve, interval)); - } - } - console.warn( - `API server at ${API_URL} not available after ${maxRetries} attempts` - ); - return false; -} + serverProcess.stdout?.on("data", (_data: Buffer) => { + }); -// Setup and teardown for the whole test suite -test.beforeAll(async () => { - // First check if port 3000 is already in use - if so, try to kill it - const portInUse = await isPortInUse(3000); - if (portInUse) { - console.log( - "Port 3000 is already in use. Attempting to kill the process..." - ); - await killProcessOnPort(); + serverProcess.stderr?.on("data", (_data: Buffer) => { + }); - // Check again - const stillInUse = await isPortInUse(3000); - if (stillInUse) { - console.log("Failed to free port 3000. Waiting for it to be released..."); - await new Promise((resolve) => setTimeout(resolve, 5000)); - } - } + await new Promise((resolve) => setTimeout(resolve, 3000)); - // Start the server - console.log("Starting server for tests..."); - serverProcess = spawn("node", [join(process.cwd(), "dist/server.js")], { - stdio: "pipe", - detached: true - }); - - // Log server output for debugging and capture the actual port - serverProcess.stdout?.on("data", (data) => { - const output = data.toString(); - console.log(`Server: ${output}`); - - // Check if the output contains the port information - const portMatch = output.match( - /API server running on http:\/\/localhost:(\d+)/ - ); - if (portMatch && portMatch[1]) { - const detectedPort = parseInt(portMatch[1], 10); - if (detectedPort !== 3000) { - console.log( - `Server is running on port ${detectedPort} instead of 3000` - ); - API_URL = `http://localhost:${detectedPort}`; - } - } - }); - - serverProcess.stderr?.on("data", (data) => { - console.error(`Server Error: ${data}`); - }); - - // Wait for server to start and API to be available - console.log("Waiting for server to start..."); - await new Promise((resolve) => setTimeout(resolve, 5000)); - - const apiAvailable = await waitForApiServer(); - if (!apiAvailable) { - console.warn("API server is not available, tests may fail"); - } - - if (apiAvailable) { - // Create a node for the tests - try { - console.log("Creating node for tests..."); - const createNodeResponse = await axios.post( - `${API_URL}/admin/v1/create-node`, - { - defaultBootstrap: false, - networkConfig: { - clusterId: 42, - shards: [0] - }, - pubsubTopics: ["/waku/2/rs/42/0"] // Explicitly configure the pubsub topic - }, - { timeout: 10000 } - ); - - if (createNodeResponse.status === 200) { - console.log("Node creation response:", createNodeResponse.data); - - // Start the node - const startNodeResponse = await axios.post( - `${API_URL}/admin/v1/start-node`, - {}, - { timeout: 5000 } - ); - - if (startNodeResponse.status === 200) { - console.log("Node started successfully"); - } - } - } catch (error) { - console.warn( - "Failed to create/start node through API, some tests may fail:", - error - ); - } - } else { - console.warn( - "Skipping node creation as server doesn't appear to be running" - ); - } -}); - -test.afterAll(async () => { - // Stop the server - console.log("Stopping server..."); - if (serverProcess && serverProcess.pid) { - if (process.platform === "win32") { - spawn("taskkill", ["/pid", serverProcess.pid.toString(), "/f", "/t"]); - } else { - // Ensure the process and all its children are terminated + let serverReady = false; + for (let i = 0; i < 30; i++) { try { - process.kill(-serverProcess.pid, "SIGINT"); - } catch (e) { - console.log("Server process already terminated"); - } - } - } - - // Verify no processes running on port 3000 - await killProcessOnPort(); - - // Give time for all processes to terminate - await new Promise((resolve) => setTimeout(resolve, 1000)); -}); - -test.describe("Waku Server API", () => { - // Direct test of filter endpoint - this runs first - test("can directly access filter/v1/messages endpoint", async () => { - // Try with different content topic formats - const testTopics = [ - "test-topic", - "/test/topic", - "%2Ftest%2Ftopic", // Pre-encoded - "%2Ftest%2Ftopic" // Pre-encoded - ]; - - for (const topic of testTopics) { - console.log(`Testing direct access with topic: ${topic}`); - try { - const response = await axios.get( - `${API_URL}/filter/v1/messages/${topic}`, - { - timeout: 5000, - validateStatus: () => true - } - ); - - console.log(` Status: ${response.status}`); - console.log(` Content-Type: ${response.headers["content-type"]}`); - console.log(` Data: ${JSON.stringify(response.data)}`); - - // If this succeeds, we'll use this topic format for our tests - if (response.status === 200) { - console.log(` Found working topic format: ${topic}`); + const res = await axios.get(`${baseUrl}/`, { timeout: 2000 }); + if (res.status === 200) { + serverReady = true; break; } - } catch (error: any) { - console.error(` Error with topic ${topic}:`, error.message); - if (error.response) { - console.error(` Response status: ${error.response.status}`); - } + } catch { + // Ignore errors } + await new Promise((r) => setTimeout(r, 1000)); + } + + expect(serverReady).toBe(true); + }); + + test.afterAll(async () => { + if (serverProcess) { + serverProcess.kill("SIGTERM"); + await new Promise((resolve) => setTimeout(resolve, 1000)); } }); - // This test checks if the server is running and can serve the basic endpoints - test("can get server status and verify endpoints", async () => { - // Get initial server status with retry mechanism - let initialResponse; - for (let attempt = 0; attempt < 5; attempt++) { - try { - initialResponse = await axios.get(`${API_URL}/`, { - timeout: 5000, - validateStatus: () => true // Accept any status code - }); - if (initialResponse.status === 200) { - break; - } - } catch (e) { - console.log( - `Server not responding on attempt ${attempt + 1}/5, retrying...` - ); - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - } + test("server health endpoint", async () => { + const res = await axios.get(`${baseUrl}/`); + expect(res.status).toBe(200); + expect(res.data.status).toBe("Waku simulation server is running"); + }); - // If we still couldn't connect, skip this test - if (!initialResponse || initialResponse.status !== 200) { - console.warn("Server is not responding, skipping endpoint checks"); - test.skip(); - return; - } + test("static files are served", async () => { + const htmlRes = await axios.get(`${baseUrl}/app/index.html`); + expect(htmlRes.status).toBe(200); + expect(htmlRes.data).toContain("Waku Test Environment"); - expect(initialResponse.status).toBe(200); - expect(initialResponse.data.status).toBe( - "Waku simulation server is running" - ); - - // Check if key endpoints are available - console.log("Checking if server endpoints are properly registered..."); + const jsRes = await axios.get(`${baseUrl}/app/index.js`); + expect(jsRes.status).toBe(200); + expect(jsRes.data).toContain("WakuHeadless"); + }); + test("Waku node auto-started", async () => { try { - // Try to access the various endpoints with simple HEAD requests - const endpoints = [ - "/info", - "/debug/v1/info", - "/admin/v1/create-node", - "/admin/v1/start-node", - "/admin/v1/stop-node", - "/filter/v1/messages/test-topic", - "/filter/v2/messages/test-topic" - ]; - - for (const endpoint of endpoints) { - try { - const response = await axios.head(`${API_URL}${endpoint}`, { - validateStatus: () => true, // Accept any status code - timeout: 3000 // Short timeout to avoid hanging - }); - - // Some endpoints may return 404 or 405 if they only support specific methods, - // but at least we should get a response if the route is registered - console.log(`Endpoint ${endpoint}: Status ${response.status}`); - - // If we get a 404, the route is not registered - expect(response.status).not.toBe(404); - } catch (error) { - console.warn(`Error checking endpoint ${endpoint}:`, error.message); - // Continue checking other endpoints even if one fails - } - } - } catch (error: any) { - console.error("Error checking endpoints:", error.message); - throw error; - } - }); - - // Test node lifecycle operations using the dedicated endpoints - test("can create, start, and stop a node", async () => { - // 1. Create a new node - const createResponse = await axios.post(`${API_URL}/admin/v1/create-node`, { - defaultBootstrap: true, - networkConfig: { - clusterId: 42, - shards: [0] - }, - pubsubTopics: ["/waku/2/rs/42/0"] // Explicitly configure the pubsub topic - }); - expect(createResponse.status).toBe(200); - expect(createResponse.data.success).toBe(true); - - // 2. Start the node - const startResponse = await axios.post(`${API_URL}/admin/v1/start-node`); - expect(startResponse.status).toBe(200); - expect(startResponse.data.success).toBe(true); - - // 3. Get info to verify it's running - const infoResponse = await axios.get(`${API_URL}/info`); - expect(infoResponse.status).toBe(200); - expect(infoResponse.data.peerId).toBeDefined(); - console.log("Node peer ID:", infoResponse.data.peerId); - - // 4. Stop the node - const stopResponse = await axios.post(`${API_URL}/admin/v1/stop-node`); - expect(stopResponse.status).toBe(200); - expect(stopResponse.data.success).toBe(true); - - // 5. Start it again - const restartResponse = await axios.post(`${API_URL}/admin/v1/start-node`); - expect(restartResponse.status).toBe(200); - expect(restartResponse.data.success).toBe(true); - - // 6. Verify it's running again - const finalInfoResponse = await axios.get(`${API_URL}/info`); - expect(finalInfoResponse.status).toBe(200); - expect(finalInfoResponse.data.peerId).toBeDefined(); - }); - - // This test requires a running node, which we now can properly initialize with our new endpoints - test("can connect to peers and get node info", async () => { - // Create and start a fresh node - await axios.post(`${API_URL}/admin/v1/create-node`, { - defaultBootstrap: false, - networkConfig: { - clusterId: 42, - shards: [0] - }, - pubsubTopics: ["/waku/2/rs/42/0"] // Explicitly configure the pubsub topic - }); - await axios.post(`${API_URL}/admin/v1/start-node`); - - // FilterConnect to peers - const dialResponse = await axios.post(`${API_URL}/admin/v1/peers`, { - peerMultiaddrs: PEERS - }); - - expect(dialResponse.status).toBe(200); - console.log("Peer connection response:", dialResponse.data); - - // Get debug info now that we have a properly initialized node - const debugResponse = await axios.get(`${API_URL}/debug/v1/info`); - expect(debugResponse.status).toBe(200); - expect(debugResponse.data).toBeDefined(); - - // Log protocols available - if (debugResponse.data.protocols) { - const wakuProtocols = debugResponse.data.protocols.filter((p: string) => - p.includes("/waku/") - ); - console.log("Waku protocols:", wakuProtocols); - } - }); - - test("can push messages", async () => { - // Create and start a fresh node - await axios.post(`${API_URL}/admin/v1/create-node`, { - defaultBootstrap: true, - networkConfig: { - clusterId: 42, - shards: [0] - }, - pubsubTopics: ["/waku/2/rs/42/0"] // Explicitly configure the pubsub topic - }); - await axios.post(`${API_URL}/admin/v1/start-node`); - - // FilterConnect to peers - await axios.post(`${API_URL}/admin/v1/peers`, { - peerMultiaddrs: PEERS - }); - - // Test the REST API format push endpoint - try { - const restPushResponse = await axios.post( - `${API_URL}/lightpush/v1/message`, - { - pubsubTopic: "/waku/2/default-waku/proto", - message: { - contentTopic: "/test/1/message/proto", - payload: Array.from( - new TextEncoder().encode("Test message via REST endpoint") - ) - } - } - ); - - expect(restPushResponse.status).toBe(200); - expect(restPushResponse.data.messageId).toBeDefined(); - console.log("Message ID:", restPushResponse.data.messageId); + const infoRes = await axios.get(`${baseUrl}/waku/v1/peer-info`); + expect(infoRes.status).toBe(200); + expect(infoRes.data.peerId).toBeDefined(); + expect(infoRes.data.multiaddrs).toBeDefined(); } catch (error) { - console.log("REST push might fail if no peers connected:", error); - } - }); - - test("can retrieve messages from the queue", async () => { - // Create and start a fresh node - await axios.post(`${API_URL}/admin/v1/create-node`, { - defaultBootstrap: true, - networkConfig: { - clusterId: 42, - shards: [0] - }, - pubsubTopics: ["/waku/2/rs/42/0"] // Explicitly configure the pubsub topic - }); - await axios.post(`${API_URL}/admin/v1/start-node`); - - // FilterConnect to peers - await axios.post(`${API_URL}/admin/v1/peers`, { - peerMultiaddrs: PEERS - }); - - // Use a simple content topic to avoid encoding issues - const contentTopic = "test-queue"; - - try { - // Check endpoint existence by checking available routes - console.log("Checking server routes and status..."); - const rootResponse = await axios.get(`${API_URL}/`); - console.log( - "Server root response:", - rootResponse.status, - rootResponse.data - ); - - // First ensure the queue is empty - console.log(`Attempting to get messages from ${contentTopic}...`); - const emptyQueueResponse = await axios.get( - `${API_URL}/filter/v1/messages/${contentTopic}` - ); - expect(emptyQueueResponse.status).toBe(200); - expect(emptyQueueResponse.data.messages).toEqual([]); - } catch (error: any) { - console.error("Error accessing filter endpoint:", error.message); - if (error.response) { - console.error("Response status:", error.response.status); - console.error("Response data:", error.response.data); - } - throw error; - } - - // Simulate adding messages to the queue - const messages = [ - { - payload: Array.from(new TextEncoder().encode("Message 1")), - timestamp: Date.now() - 2000, - contentTopic - }, - { - payload: Array.from(new TextEncoder().encode("Message 2")), - timestamp: Date.now() - 1000, - contentTopic - }, - { - payload: Array.from(new TextEncoder().encode("Message 3")), - timestamp: Date.now(), - contentTopic - } - ]; - - const testMessages = await axios.post(`${API_URL}/execute`, { - functionName: "simulateMessages", - params: [contentTopic, messages] - }); - expect(testMessages.status).toBe(200); - - // Now check if we can retrieve messages - const messagesResponse = await axios.get( - `${API_URL}/filter/v1/messages/${contentTopic}` - ); - expect(messagesResponse.status).toBe(200); - expect(messagesResponse.data.messages.length).toBe(3); - - // Verify message format - const message = messagesResponse.data.messages[0]; - expect(message).toHaveProperty("payload"); - expect(message).toHaveProperty("contentTopic"); - expect(message).toHaveProperty("timestamp"); - expect(message).toHaveProperty("version"); - - // Test pagination - const paginatedResponse = await axios.get( - `${API_URL}/filter/v1/messages/${contentTopic}?pageSize=2` - ); - expect(paginatedResponse.status).toBe(200); - expect(paginatedResponse.data.messages.length).toBe(2); - - // Test sorting order - const ascendingResponse = await axios.get( - `${API_URL}/filter/v1/messages/${contentTopic}?ascending=true` - ); - expect(ascendingResponse.status).toBe(200); - expect(ascendingResponse.data.messages.length).toBe(3); - const timestamps = ascendingResponse.data.messages.map( - (msg: any) => msg.timestamp - ); - expect(timestamps[0]).toBeLessThan(timestamps[1]); - expect(timestamps[1]).toBeLessThan(timestamps[2]); - }); - - test("can access filter endpoint for SSE", async () => { - // Create and start a fresh node - only if API is accessible - try { - // Quick check if server is running - await axios.get(API_URL, { timeout: 2000 }); - - // Create node - await axios.post(`${API_URL}/admin/v1/create-node`, { - defaultBootstrap: true, - networkConfig: { - clusterId: 42, - shards: [0] - }, - pubsubTopics: ["/waku/2/rs/42/0"] // Explicitly configure the pubsub topic - }); - - // Start node - await axios.post(`${API_URL}/admin/v1/start-node`); - - // FilterConnect to peers - await axios.post(`${API_URL}/admin/v1/peers`, { - peerMultiaddrs: PEERS - }); - } catch (error) { - console.warn("Server appears to be unreachable, skipping test"); - test.skip(); - return; - } - - const contentTopic = "test-sse"; - - // Verify filter endpoint is accessible - // Instead of implementing a full SSE client, we'll make sure the endpoint - // returns the correct headers and status code which indicates SSE readiness - try { - const sseResponse = await axios - .get( - `${API_URL}/filter/v2/messages/${contentTopic}?clusterId=42&shard=0`, - { - // Set a timeout to avoid hanging the test - timeout: 2000, - // Expecting the request to timeout as SSE keeps connection open - validateStatus: () => true, - // We can't use responseType: 'stream' directly with axios, - // but we can check the response headers - headers: { - Accept: "text/event-stream" - } - } - ) - .catch((e) => { - // We expect a timeout error since SSE keeps connection open - if (e.code === "ECONNABORTED") { - return e.response; - } - throw e; - }); - - // If response exists and has expected SSE headers, the test passes - if (sseResponse) { - expect(sseResponse.headers["content-type"]).toBe("text/event-stream"); - expect(sseResponse.headers["cache-control"]).toBe("no-cache"); - expect(sseResponse.headers["connection"]).toBe("keep-alive"); - } else { - // If no response, we manually make an HTTP request to check the headers - const headers = await new Promise>((resolve) => { - const requestUrl = new URL( - `${API_URL}/filter/v2/messages/${contentTopic}?clusterId=42&shard=0` - ); - const req = http.get(requestUrl, (res) => { - // Only interested in headers - req.destroy(); - if (res.headers) { - resolve(res.headers as Record); - } else { - resolve({}); - } - }); - req.on("error", () => resolve({})); - }); - - if (Object.keys(headers).length === 0) { - console.warn( - "No headers received, SSE endpoint may not be accessible" - ); - test.skip(); - return; - } - - expect(headers["content-type"]).toBe("text/event-stream"); - } - } catch (error) { - console.error("Error during SSE endpoint test:", error); - test.fail(); - return; - } - - console.log("SSE endpoint is accessible with correct headers"); - }); - - // Add a specific test just for the filter/v1/messages endpoint - test("can access filter/v1/messages endpoint directly", async () => { - // Check if server is available first - try { - await axios.get(API_URL, { timeout: 2000 }); - } catch (error) { - console.warn("Server appears to be unreachable, skipping test"); - test.skip(); - return; - } - - // Create a random content topic just for this test - const contentTopic = `direct-filter-${Date.now()}`; - - try { - // Try different approaches to access the endpoint - console.log( - `Testing direct access to filter/v1/messages/${contentTopic}` - ); - - // Method 1: GET request with encoded content topic - const getResponse = await axios({ - method: "get", - url: `${API_URL}/filter/v1/messages/${contentTopic}`, - validateStatus: function () { - // Allow any status code to check what's coming back - return true; - }, - timeout: 5000 - }); - - console.log("Response status:", getResponse.status); - console.log("Response headers:", getResponse.headers); - - if (getResponse.status === 404) { - throw new Error( - `Endpoint not found (404): /filter/v1/messages/${contentTopic}` - ); - } - - // If we got here, the endpoint exists even if it returns empty results - expect(getResponse.status).toBe(200); - expect(getResponse.data).toHaveProperty("messages"); - expect(Array.isArray(getResponse.data.messages)).toBe(true); - } catch (error: any) { - console.error("Error during filter/v1 endpoint test:", error.message); - - if (error.response) { - console.error("Response status:", error.response.status); - console.error("Response headers:", error.response.headers); - console.error("Response data:", error.response.data); - } else if (error.request) { - console.error("No response received:", error.request); - // If no response, we'll skip the test rather than fail it - test.skip(); - return; - } - - throw error; + expect(error.response?.status).toBe(400); } }); }); diff --git a/packages/browser-tests/tests/test-config.ts b/packages/browser-tests/tests/test-config.ts deleted file mode 100644 index a20295a056..0000000000 --- a/packages/browser-tests/tests/test-config.ts +++ /dev/null @@ -1,35 +0,0 @@ -export const NETWORK_CONFIG = { - "waku.sandbox": { - peers: [ - "/dns4/node-01.do-ams3.waku.sandbox.status.im/tcp/30303/p2p/16Uiu2HAmNaeL4p3WEYzC9mgXBmBWSgWjPHRvatZTXnp8Jgv3iKsb", - "/dns4/node-01.gc-us-central1-a.waku.sandbox.status.im/tcp/30303/p2p/16Uiu2HAmRv1iQ3NoMMcjbtRmKxPuYBbF9nLYz2SDv9MTN8WhGuUU", - "/dns4/node-01.ac-cn-hongkong-c.waku.sandbox.status.im/tcp/30303/p2p/16Uiu2HAmQYiojgZ8APsh9wqbWNyCstVhnp9gbeNrxSEQnLJchC92" - ] - }, - - "waku.test": { - peers: [ - "/dns4/node-01.do-ams3.waku.test.status.im/tcp/8000/wss/p2p/16Uiu2HAkykgaECHswi3YKJ5dMLbq2kPVCo89fcyTd38UcQD6ej5W", - "/dns4/node-01.gc-us-central1-a.waku.test.status.im/tcp/8000/wss/p2p/16Uiu2HAmDCp8XJ9z1ev18zuv8NHekAsjNyezAvmMfFEJkiharitG", - "/dns4/node-01.ac-cn-hongkong-c.waku.test.status.im/tcp/8000/wss/p2p/16Uiu2HAkzHaTP5JsUwfR9NR8Rj9HC24puS6ocaU8wze4QrXr9iXp" - ] - }, - - networkConfig: { - clusterId: 1, - shards: [0] - }, - - // Default node configuration - defaultNodeConfig: { - defaultBootstrap: false - }, - - // Test message configuration - testMessage: { - contentTopic: "/test/1/message/proto", - payload: "Hello, Waku!" - } -}; - -export const ACTIVE_PEERS = NETWORK_CONFIG["waku.test"].peers; \ No newline at end of file diff --git a/packages/browser-tests/tests/utils/container-helpers.ts b/packages/browser-tests/tests/utils/container-helpers.ts new file mode 100644 index 0000000000..0ff6108e06 --- /dev/null +++ b/packages/browser-tests/tests/utils/container-helpers.ts @@ -0,0 +1,128 @@ +import axios from "axios"; +import { GenericContainer, StartedTestContainer } from "testcontainers"; +import { Logger } from "@waku/utils"; + +const log = new Logger("container-helpers"); + +export interface ContainerSetupOptions { + environment?: Record; + networkMode?: string; + timeout?: number; + maxAttempts?: number; +} + +export interface ContainerSetupResult { + container: StartedTestContainer; + baseUrl: string; +} + +/** + * Starts a waku-browser-tests Docker container with proper health checking. + * Follows patterns from @waku/tests package for retry logic and cleanup. + */ +export async function startBrowserTestsContainer( + options: ContainerSetupOptions = {} +): Promise { + const { + environment = {}, + networkMode = "bridge", + timeout = 2000, + maxAttempts = 60 + } = options; + + log.info("Starting waku-browser-tests container..."); + + let generic = new GenericContainer("waku-browser-tests:local") + .withExposedPorts(8080) + .withNetworkMode(networkMode); + + // Apply environment variables + for (const [key, value] of Object.entries(environment)) { + generic = generic.withEnvironment({ [key]: value }); + } + + const container = await generic.start(); + + // Set up container logging - stream all output from the start + const logs = await container.logs(); + logs.on("data", (b) => process.stdout.write("[container] " + b.toString())); + logs.on("error", (err) => log.error("[container log error]", err)); + + // Give container time to initialize + await new Promise((r) => setTimeout(r, 5000)); + + const mappedPort = container.getMappedPort(8080); + const baseUrl = `http://127.0.0.1:${mappedPort}`; + + // Wait for server readiness with retry logic (following waku/tests patterns) + const serverReady = await waitForServerReady(baseUrl, maxAttempts, timeout); + + if (!serverReady) { + await logFinalContainerState(container); + throw new Error("Container failed to become ready"); + } + + log.info("✅ Browser tests container ready"); + await new Promise((r) => setTimeout(r, 500)); // Final settling time + + return { container, baseUrl }; +} + +/** + * Waits for server to become ready with exponential backoff and detailed logging. + * Follows retry patterns from @waku/tests ServiceNode. + */ +async function waitForServerReady( + baseUrl: string, + maxAttempts: number, + timeout: number +): Promise { + for (let i = 0; i < maxAttempts; i++) { + try { + const res = await axios.get(`${baseUrl}/`, { timeout }); + if (res.status === 200) { + log.info(`Server is ready after ${i + 1} attempts`); + return true; + } + } catch (error) { + if (i % 10 === 0) { + log.info(`Attempt ${i + 1}/${maxAttempts} failed:`, error.code || error.message); + } + } + await new Promise((r) => setTimeout(r, 1000)); + } + return false; +} + +/** + * Logs final container state for debugging, following waku/tests error handling patterns. + */ +async function logFinalContainerState(container: StartedTestContainer): Promise { + try { + const finalLogs = await container.logs({ tail: 50 }); + log.info("=== Final Container Logs ==="); + finalLogs.on("data", (b) => log.info(b.toString())); + await new Promise((r) => setTimeout(r, 1000)); + } catch (logError) { + log.error("Failed to get container logs:", logError); + } +} + +/** + * Gracefully stops containers with retry logic, following teardown patterns from waku/tests. + */ +export async function stopContainer(container: StartedTestContainer): Promise { + if (!container) return; + + log.info("Stopping container gracefully..."); + try { + await container.stop({ timeout: 10000 }); + log.info("Container stopped successfully"); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + log.warn( + "Container stop had issues (expected):", + message + ); + } +} diff --git a/packages/browser-tests/tests/utils/index.ts b/packages/browser-tests/tests/utils/index.ts new file mode 100644 index 0000000000..8965565ef9 --- /dev/null +++ b/packages/browser-tests/tests/utils/index.ts @@ -0,0 +1,8 @@ +/** + * Shared test utilities for browser-tests package. + * Follows patterns established in @waku/tests package. + */ + +export * from "./container-helpers.js"; +export * from "./nwaku-helpers.js"; +export * from "./test-config.js"; \ No newline at end of file diff --git a/packages/browser-tests/tests/utils/nwaku-helpers.ts b/packages/browser-tests/tests/utils/nwaku-helpers.ts new file mode 100644 index 0000000000..e51d56bcf0 --- /dev/null +++ b/packages/browser-tests/tests/utils/nwaku-helpers.ts @@ -0,0 +1,141 @@ +import { ServiceNode } from "@waku/tests"; +import { DefaultTestRoutingInfo } from "@waku/tests"; +import { Logger } from "@waku/utils"; + +const log = new Logger("nwaku-helpers"); + +export interface TwoNodeNetwork { + nodes: ServiceNode[]; +} + +/** + * Creates a two-node nwaku network following waku/tests patterns. + * Node 1: Relay + Light Push (service provider) + * Node 2: Relay only (network peer) + */ +export async function createTwoNodeNetwork(): Promise { + log.info("Creating nwaku node 1 (Relay + Light Push)..."); + const lightPushNode = new ServiceNode( + "lightpush-node-" + Math.random().toString(36).substring(7), + ); + + const lightPushArgs = { + relay: true, + lightpush: true, + filter: false, + store: false, + clusterId: DefaultTestRoutingInfo.clusterId, + numShardsInNetwork: DefaultTestRoutingInfo.networkConfig.numShardsInCluster, + contentTopic: [DefaultTestRoutingInfo.contentTopic], + }; + + await lightPushNode.start(lightPushArgs, { retries: 3 }); + + log.info("Creating nwaku node 2 (Relay only)..."); + const relayNode = new ServiceNode( + "relay-node-" + Math.random().toString(36).substring(7), + ); + + // Connect second node to first node (following ServiceNodesFleet pattern) + const firstNodeAddr = await lightPushNode.getExternalMultiaddr(); + const relayArgs = { + relay: true, + lightpush: false, + filter: false, + store: false, + staticnode: firstNodeAddr, + clusterId: DefaultTestRoutingInfo.clusterId, + numShardsInNetwork: DefaultTestRoutingInfo.networkConfig.numShardsInCluster, + contentTopic: [DefaultTestRoutingInfo.contentTopic], + }; + + await relayNode.start(relayArgs, { retries: 3 }); + + // Wait for network formation (following waku/tests timing patterns) + log.info("Waiting for nwaku network formation..."); + await new Promise((r) => setTimeout(r, 5000)); + + // Verify connectivity (optional, for debugging) + await verifyNetworkFormation([lightPushNode, relayNode]); + + return { + nodes: [lightPushNode, relayNode], + }; +} + +/** + * Verifies that nwaku nodes have formed connections. + * Follows error handling patterns from waku/tests. + */ +async function verifyNetworkFormation(nodes: ServiceNode[]): Promise { + try { + const peerCounts = await Promise.all( + nodes.map(async (node, index) => { + const peers = await node.peers(); + log.info(`Node ${index + 1} has ${peers.length} peer(s)`); + return peers.length; + }), + ); + + if (peerCounts.every((count) => count === 0)) { + log.warn("⚠️ Nodes may not be properly connected yet"); + } + } catch (error) { + log.warn("Could not verify peer connections:", error); + } +} + +/** + * Extracts Docker-accessible multiaddr from nwaku node. + * Returns multiaddr using container's internal IP for Docker network communication. + */ +export async function getDockerAccessibleMultiaddr( + node: ServiceNode, +): Promise { + // Get multiaddr with localhost and extract components + const localhostMultiaddr = await node.getMultiaddrWithId(); + const peerId = await node.getPeerId(); + + // Extract port from multiaddr string + const multiaddrStr = localhostMultiaddr.toString(); + const portMatch = multiaddrStr.match(/\/tcp\/(\d+)/); + const port = portMatch ? portMatch[1] : null; + + if (!port) { + throw new Error("Could not extract port from multiaddr: " + multiaddrStr); + } + + // Get Docker container IP (accessing internal field) + // Note: This accesses an internal implementation detail of ServiceNode + const nodeWithDocker = node as ServiceNode & { + docker?: { containerIp?: string }; + }; + const containerIp = nodeWithDocker.docker?.containerIp; + if (!containerIp) { + throw new Error("Could not get container IP from node"); + } + + // Build Docker network accessible multiaddr + const dockerMultiaddr = `/ip4/${containerIp}/tcp/${port}/ws/p2p/${peerId}`; + + log.info("Original multiaddr:", multiaddrStr); + log.info("Docker accessible multiaddr:", dockerMultiaddr); + + return dockerMultiaddr; +} + +/** + * Stops nwaku nodes with retry logic, following teardown patterns from waku/tests. + */ +export async function stopNwakuNodes(nodes: ServiceNode[]): Promise { + if (!nodes || nodes.length === 0) return; + + log.info("Stopping nwaku nodes..."); + try { + await Promise.all(nodes.map((node) => node.stop())); + log.info("Nwaku nodes stopped successfully"); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + log.warn("Nwaku nodes stop had issues:", message); + } +} diff --git a/packages/browser-tests/tests/utils/test-config.ts b/packages/browser-tests/tests/utils/test-config.ts new file mode 100644 index 0000000000..2b80a3a74f --- /dev/null +++ b/packages/browser-tests/tests/utils/test-config.ts @@ -0,0 +1,127 @@ +import { expect } from "@playwright/test"; +import { DefaultTestRoutingInfo } from "@waku/tests"; +import { AxiosResponse } from "axios"; + +/** + * Response type definitions for API endpoints + */ +interface ServerHealthResponse { + status: string; +} + +interface PeerInfoResponse { + peerId: string; + multiaddrs: string[]; + peers: string[]; +} + +interface LightPushV3Result { + successes: string[]; + failures: Array<{ error: string; peerId?: string }>; +} + +interface LightPushV3Response { + success: boolean; + result: LightPushV3Result; + error?: string; +} + +interface MessageResponse { + contentTopic: string; + payload: string; + version: number; + timestamp?: bigint | number; +} + +/** + * Common test configuration constants following waku/tests patterns. + */ +export const TEST_CONFIG = { + // Test timeouts (following waku/tests timeout patterns) + DEFAULT_TEST_TIMEOUT: 120000, // 2 minutes + CONTAINER_READY_TIMEOUT: 60000, // 1 minute + NETWORK_FORMATION_DELAY: 5000, // 5 seconds + SUBSCRIPTION_DELAY: 3000, // 3 seconds + MESSAGE_PROPAGATION_DELAY: 5000, // 5 seconds + WAKU_INIT_DELAY: 8000, // 8 seconds + + // Network configuration + DEFAULT_CLUSTER_ID: DefaultTestRoutingInfo.clusterId.toString(), + DEFAULT_CONTENT_TOPIC: "/test/1/browser-tests/proto", + + // Test messages + DEFAULT_TEST_MESSAGE: "Hello from browser tests", +} as const; + +/** + * Environment variable builders for different test scenarios. + */ +export const ENV_BUILDERS = { + /** + * Environment for production ENR bootstrap (integration test pattern). + */ + withProductionEnr: () => ({ + WAKU_ENR_BOOTSTRAP: "enr:-QEnuEBEAyErHEfhiQxAVQoWowGTCuEF9fKZtXSd7H_PymHFhGJA3rGAYDVSHKCyJDGRLBGsloNbS8AZF33IVuefjOO6BIJpZIJ2NIJpcIQS39tkim11bHRpYWRkcnO4lgAvNihub2RlLTAxLmRvLWFtczMud2FrdXYyLnRlc3Quc3RhdHVzaW0ubmV0BgG73gMAODcxbm9kZS0wMS5hYy1jbi1ob25na29uZy1jLndha3V2Mi50ZXN0LnN0YXR1c2ltLm5ldAYBu94DACm9A62t7AQL4Ef5ZYZosRpQTzFVAB8jGjf1TER2wH-0zBOe1-MDBNLeA4lzZWNwMjU2azGhAzfsxbxyCkgCqq8WwYsVWH7YkpMLnU2Bw5xJSimxKav-g3VkcIIjKA", + WAKU_CLUSTER_ID: "1", + }), + + /** + * Environment for local nwaku node connection (e2e test pattern). + */ + withLocalLightPush: (lightpushMultiaddr: string) => ({ + WAKU_LIGHTPUSH_NODE: lightpushMultiaddr, + WAKU_CLUSTER_ID: TEST_CONFIG.DEFAULT_CLUSTER_ID, + }), +}; + +/** + * Test assertion helpers following waku/tests verification patterns. + */ +export const ASSERTIONS = { + /** + * Verifies server health response structure. + */ + serverHealth: (response: AxiosResponse) => { + expect(response.status).toBe(200); + expect(response.data.status).toBe("Waku simulation server is running"); + }, + + /** + * Verifies peer info response structure. + */ + peerInfo: (response: AxiosResponse) => { + expect(response.status).toBe(200); + expect(response.data.peerId).toBeDefined(); + expect(typeof response.data.peerId).toBe("string"); + }, + + /** + * Verifies lightpush response structure (v3 format). + */ + lightPushV3Success: (response: AxiosResponse) => { + expect(response.status).toBe(200); + expect(response.data).toHaveProperty('success', true); + expect(response.data).toHaveProperty('result'); + expect(response.data.result).toHaveProperty('successes'); + expect(Array.isArray(response.data.result.successes)).toBe(true); + expect(response.data.result.successes.length).toBeGreaterThan(0); + }, + + /** + * Verifies message content and structure. + */ + messageContent: (message: MessageResponse, expectedContent: string, expectedTopic: string) => { + expect(message).toHaveProperty('contentTopic', expectedTopic); + expect(message).toHaveProperty('payload'); + expect(typeof message.payload).toBe('string'); + + const receivedPayload = Buffer.from(message.payload, 'base64').toString(); + expect(receivedPayload).toBe(expectedContent); + + // Optional fields + expect(message).toHaveProperty('version'); + if (message.timestamp) { + expect(['bigint', 'number']).toContain(typeof message.timestamp); + } + }, +}; \ No newline at end of file diff --git a/packages/browser-tests/tsconfig.json b/packages/browser-tests/tsconfig.json index abf2b61f89..6903a40abb 100644 --- a/packages/browser-tests/tsconfig.json +++ b/packages/browser-tests/tsconfig.json @@ -15,5 +15,5 @@ "typeRoots": ["./node_modules/@types", "./types"] }, "include": ["src/server.ts", "types/**/*.d.ts"], - "exclude": ["node_modules", "dist"] + "exclude": ["node_modules", "dist", "web"] } diff --git a/packages/browser-tests/types/global.d.ts b/packages/browser-tests/types/global.d.ts index 5e0b68cbce..e2993f48dc 100644 --- a/packages/browser-tests/types/global.d.ts +++ b/packages/browser-tests/types/global.d.ts @@ -1,27 +1,19 @@ -import { LightNode } from "@waku/sdk"; -import { IWakuNode } from "../src/api/common.js"; -import { - createWakuNode, - dialPeers, - getDebugInfo, - getPeerInfo, - pushMessage, - subscribe -} from "../src/api/shared.js"; +import type { WakuHeadless } from "../web/index.js"; + +export interface WindowNetworkConfig { + clusterId?: number; + shards?: number[]; +} + +export interface ITestBrowser extends Window { + wakuApi: WakuHeadless; + __WAKU_NETWORK_CONFIG?: WindowNetworkConfig; + __WAKU_LIGHTPUSH_NODE?: string | null; + __WAKU_ENR_BOOTSTRAP?: string | null; +} -// Define types for the Waku node and window declare global { - // eslint-disable-next-line no-unused-vars interface Window { - waku: IWakuNode & LightNode; - wakuAPI: { - getPeerInfo: typeof getPeerInfo; - getDebugInfo: typeof getDebugInfo; - pushMessage: typeof pushMessage; - dialPeers: typeof dialPeers; - createWakuNode: typeof createWakuNode; - subscribe: typeof subscribe; - [key: string]: any; - }; + wakuApi: WakuHeadless; } } diff --git a/packages/browser-tests/types/serve.d.ts b/packages/browser-tests/types/serve.d.ts index f35d627c51..048a1cc932 100644 --- a/packages/browser-tests/types/serve.d.ts +++ b/packages/browser-tests/types/serve.d.ts @@ -1,7 +1,9 @@ declare module "serve" { + import type { Server } from "http"; + function serve( folder: string, - options: { port: number; single: boolean; listen: boolean } - ): any; + options: { port: number; single: boolean; listen: boolean }, + ): Promise; export default serve; } diff --git a/packages/browser-tests/web/index.html b/packages/browser-tests/web/index.html new file mode 100644 index 0000000000..8f8d0aa647 --- /dev/null +++ b/packages/browser-tests/web/index.html @@ -0,0 +1,14 @@ + + + + + + Waku Test Environment + + +

Waku Test Environment

+ + + + + diff --git a/packages/browser-tests/web/index.ts b/packages/browser-tests/web/index.ts new file mode 100644 index 0000000000..df0e750a27 --- /dev/null +++ b/packages/browser-tests/web/index.ts @@ -0,0 +1,431 @@ +import { + createLightNode, + LightNode, + Protocols, + NetworkConfig, + CreateNodeOptions, +} from "@waku/sdk"; +import { + AutoSharding, + DEFAULT_CLUSTER_ID, + DEFAULT_NUM_SHARDS, + ShardId, + StaticSharding, + ShardInfo, + CreateLibp2pOptions, + IEncoder, + ILightPush, + SDKProtocolResult, + Failure, +} from "@waku/interfaces"; +import { bootstrap } from "@libp2p/bootstrap"; +import { EnrDecoder, TransportProtocol } from "@waku/enr"; +import type { Multiaddr } from "@multiformats/multiaddr"; +import type { ITestBrowser } from "../types/global.js"; +import { Logger, StaticShardingRoutingInfo } from "@waku/utils"; +import type { PeerId } from "@libp2p/interface"; + +const log = new Logger("waku-headless"); + +export interface SerializableSDKProtocolResult { + successes: string[]; + failures: Array<{ + error: string; + peerId?: string; + }>; + myPeerId?: string; +} + +function makeSerializable(result: SDKProtocolResult): SerializableSDKProtocolResult { + return { + ...result, + successes: result.successes.map((peerId: PeerId) => peerId.toString()), + failures: result.failures.map((failure: Failure) => ({ + error: failure.error || failure.toString(), + peerId: failure.peerId ? failure.peerId.toString() : undefined, + })), + }; +} + +async function convertEnrToMultiaddrs(enrString: string): Promise { + try { + const enr = await EnrDecoder.fromString(enrString); + const allMultiaddrs = enr.getAllLocationMultiaddrs(); + const multiaddrs: string[] = []; + + for (const multiaddr of allMultiaddrs) { + const maStr = multiaddr.toString(); + multiaddrs.push(maStr); + } + if (multiaddrs.length === 0) { + const tcpMultiaddr = enr.getFullMultiaddr(TransportProtocol.TCP); + if (tcpMultiaddr) { + const tcpStr = tcpMultiaddr.toString(); + multiaddrs.push(tcpStr); + } + const udpMultiaddr = enr.getFullMultiaddr(TransportProtocol.UDP); + if (udpMultiaddr) { + const udpStr = udpMultiaddr.toString(); + multiaddrs.push(udpStr); + } + } + + return multiaddrs; + } catch (error) { + return []; + } +} + +export class WakuHeadless { + waku: LightNode | null; + networkConfig: NetworkConfig; + lightpushNode: string | null; + enrBootstrap: string | null; + constructor( + networkConfig?: Partial, + lightpushNode?: string | null, + enrBootstrap?: string | null, + ) { + this.waku = null; + this.networkConfig = this.buildNetworkConfig(networkConfig); + log.info("Network config on construction:", this.networkConfig); + this.lightpushNode = lightpushNode || null; + this.enrBootstrap = enrBootstrap || null; + + if (this.lightpushNode) { + log.info(`Configured preferred lightpush node: ${this.lightpushNode}`); + } + if (this.enrBootstrap) { + log.info(`Configured ENR bootstrap: ${this.enrBootstrap}`); + } + } + + private shouldUseCustomBootstrap(options: CreateNodeOptions): boolean { + const hasEnr = Boolean(this.enrBootstrap); + const isDefaultBootstrap = Boolean(options.defaultBootstrap); + + return hasEnr && !isDefaultBootstrap; + } + + private async getBootstrapMultiaddrs(): Promise { + if (!this.enrBootstrap) { + return []; + } + + const enrList = this.enrBootstrap.split(",").map((enr) => enr.trim()); + const allMultiaddrs: string[] = []; + + for (const enr of enrList) { + const multiaddrs = await convertEnrToMultiaddrs(enr); + if (multiaddrs.length > 0) { + allMultiaddrs.push(...multiaddrs); + } + } + + return allMultiaddrs; + } + + private buildNetworkConfig( + providedConfig?: Partial | Partial, + ): NetworkConfig { + const clusterId = providedConfig?.clusterId ?? DEFAULT_CLUSTER_ID; + + const staticShards = (providedConfig as Partial)?.shards; + if ( + staticShards && + Array.isArray(staticShards) && + staticShards.length > 0 + ) { + log.info("Using static sharding with shards:", staticShards); + return { + clusterId, + } as StaticSharding; + } + + const numShardsInCluster = + (providedConfig as Partial)?.numShardsInCluster ?? DEFAULT_NUM_SHARDS; + log.info( + "Using auto sharding with num shards in cluster:", + numShardsInCluster, + ); + return { + clusterId, + numShardsInCluster, + } as AutoSharding; + } + + private async send( + lightPush: ILightPush, + encoder: IEncoder, + payload: Uint8Array, + ): Promise { + return lightPush.send(encoder, { + payload, + timestamp: new Date(), + }); + } + + async pushMessageV3( + contentTopic: string, + payload: string, + pubsubTopic: string, + ): Promise { + if (!this.waku) { + throw new Error("Waku node not started"); + } + log.info( + "Pushing message via v3 lightpush:", + contentTopic, + payload, + pubsubTopic, + ); + log.info("Waku node:", this.waku); + log.info("Network config:", this.networkConfig); + + let processedPayload: Uint8Array; + try { + const binaryString = atob(payload); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + processedPayload = bytes; + } catch (e) { + processedPayload = new TextEncoder().encode(payload); + } + + try { + const lightPush = this.waku.lightPush; + if (!lightPush) { + throw new Error("Lightpush service not available"); + } + + let shardId: ShardId | undefined; + if (pubsubTopic) { + const staticShardingRoutingInfo = + StaticShardingRoutingInfo.fromPubsubTopic( + pubsubTopic, + this.networkConfig as StaticSharding, + ); + shardId = staticShardingRoutingInfo?.shardId; + } + + const encoder = this.waku.createEncoder({ + contentTopic, + shardId, + }); + log.info("Encoder:", encoder); + log.info("Pubsub topic:", pubsubTopic); + log.info("Encoder pubsub topic:", encoder.pubsubTopic); + + if (pubsubTopic && pubsubTopic !== encoder.pubsubTopic) { + log.warn( + `Explicit pubsubTopic ${pubsubTopic} provided, but auto-sharding determined ${encoder.pubsubTopic}. Using auto-sharding.`, + ); + } + + let result; + if (this.lightpushNode) { + try { + const preferredPeerId = this.getPeerIdFromMultiaddr( + this.lightpushNode, + ); + if (preferredPeerId) { + result = await this.send(lightPush, encoder, processedPayload); + log.info("✅ Message sent via preferred lightpush node"); + } else { + throw new Error( + "Could not extract peer ID from preferred node address", + ); + } + } catch (error) { + log.error( + "Couldn't send message via preferred lightpush node:", + error, + ); + result = await this.send(lightPush, encoder, processedPayload); + } + } else { + result = await this.send(lightPush, encoder, processedPayload); + } + + const serializableResult = makeSerializable(result); + + return serializableResult; + } catch (error) { + log.error("Error sending message via v3 lightpush:", error); + throw new Error( + `Failed to send v3 message: ${error instanceof Error ? error.message : String(error)}`, + ); + } + } + + async waitForPeers( + timeoutMs: number = 10000, + protocols: Protocols[] = [Protocols.LightPush, Protocols.Filter], + ) { + if (!this.waku) { + throw new Error("Waku node not started"); + } + + const startTime = Date.now(); + + try { + await this.waku.waitForPeers(protocols, timeoutMs); + const elapsed = Date.now() - startTime; + + const peers = this.waku.libp2p.getPeers(); + + return { + success: true, + peersFound: peers.length, + protocolsRequested: protocols, + timeElapsed: elapsed, + }; + } catch (error) { + const elapsed = Date.now() - startTime; + log.error(`Failed to find peers after ${elapsed}ms:`, error); + throw error; + } + } + + async createWakuNode(options: CreateNodeOptions) { + try { + if (this.waku) { + await this.waku.stop(); + } + } catch (e) { + log.warn("ignore previous waku stop error"); + } + + let libp2pConfig: CreateLibp2pOptions = { + ...options.libp2p, + filterMultiaddrs: false, + }; + + if (this.enrBootstrap) { + const multiaddrs = await this.getBootstrapMultiaddrs(); + + if (multiaddrs.length > 0) { + libp2pConfig.peerDiscovery = [ + bootstrap({ list: multiaddrs }), + ...(options.libp2p?.peerDiscovery || []), + ]; + } + } + + const createOptions = { + ...options, + networkConfig: this.networkConfig, + libp2p: libp2pConfig, + }; + + this.waku = await createLightNode(createOptions); + return { success: true }; + } + + async startNode() { + if (!this.waku) { + throw new Error("Waku node not created"); + } + await this.waku.start(); + + if (this.lightpushNode) { + await this.dialPreferredLightpushNode(); + } + + return { success: true }; + } + + private async dialPreferredLightpushNode() { + if (!this.waku || !this.lightpushNode) { + log.info("Skipping dial: waku or lightpushNode not set"); + return; + } + + try { + log.info("Attempting to dial preferred lightpush node:", this.lightpushNode); + await this.waku.dial(this.lightpushNode); + log.info("Successfully dialed preferred lightpush node:", this.lightpushNode); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + log.error( + "Failed to dial preferred lightpush node:", + this.lightpushNode, + message + ); + } + } + + private getPeerIdFromMultiaddr(multiaddr: string): string | null { + const parts = multiaddr.split("/"); + const p2pIndex = parts.indexOf("p2p"); + return p2pIndex !== -1 && p2pIndex + 1 < parts.length + ? parts[p2pIndex + 1] + : null; + } + + async stopNode() { + if (!this.waku) { + throw new Error("Waku node not created"); + } + await this.waku.stop(); + return { success: true }; + } + + getPeerInfo() { + if (!this.waku) { + throw new Error("Waku node not started"); + } + + const addrs = this.waku.libp2p.getMultiaddrs(); + return { + peerId: this.waku.libp2p.peerId.toString(), + multiaddrs: addrs.map((a: Multiaddr) => a.toString()), + peers: [], + }; + } +} + +(() => { + try { + log.info("Initializing WakuHeadless..."); + + const testWindow = window as ITestBrowser; + const globalNetworkConfig = testWindow.__WAKU_NETWORK_CONFIG; + const globalLightpushNode = testWindow.__WAKU_LIGHTPUSH_NODE; + const globalEnrBootstrap = testWindow.__WAKU_ENR_BOOTSTRAP; + + log.info("Global config from window:", { + networkConfig: globalNetworkConfig, + lightpushNode: globalLightpushNode, + enrBootstrap: globalEnrBootstrap + }); + + const instance = new WakuHeadless( + globalNetworkConfig, + globalLightpushNode, + globalEnrBootstrap, + ); + + testWindow.wakuApi = instance; + log.info("WakuHeadless initialized successfully:", !!testWindow.wakuApi); + } catch (error) { + log.error("Error initializing WakuHeadless:", error); + const testWindow = window as ITestBrowser; + // Create a stub wakuApi that will reject all method calls + testWindow.wakuApi = { + waku: null, + networkConfig: { clusterId: 0, numShardsInCluster: 0 }, + lightpushNode: null, + enrBootstrap: null, + error, + createWakuNode: () => Promise.reject(new Error("WakuHeadless failed to initialize")), + startNode: () => Promise.reject(new Error("WakuHeadless failed to initialize")), + stopNode: () => Promise.reject(new Error("WakuHeadless failed to initialize")), + pushMessageV3: () => Promise.reject(new Error("WakuHeadless failed to initialize")), + waitForPeers: () => Promise.reject(new Error("WakuHeadless failed to initialize")), + getPeerInfo: () => { throw new Error("WakuHeadless failed to initialize"); }, + } as unknown as WakuHeadless; + } +})(); diff --git a/packages/core/src/lib/connection_manager/connection_limiter.ts b/packages/core/src/lib/connection_manager/connection_limiter.ts index 83d85714c7..fae83b2828 100644 --- a/packages/core/src/lib/connection_manager/connection_limiter.ts +++ b/packages/core/src/lib/connection_manager/connection_limiter.ts @@ -9,7 +9,6 @@ import { WakuEvent } from "@waku/interfaces"; import { Logger } from "@waku/utils"; -import { numberToBytes } from "@waku/utils/bytes"; import { Dialer } from "./dialer.js"; import { NetworkMonitor } from "./network_monitor.js"; @@ -125,7 +124,6 @@ export class ConnectionLimiter implements IConnectionLimiter { private async maintainConnections(): Promise { await this.maintainConnectionsCount(); await this.maintainBootstrapConnections(); - await this.maintainTTLConnectedPeers(); } private async onDisconnectedEvent(): Promise { @@ -215,28 +213,6 @@ export class ConnectionLimiter implements IConnectionLimiter { } } - private async maintainTTLConnectedPeers(): Promise { - log.info(`Maintaining TTL connected peers`); - - const promises = this.libp2p.getConnections().map(async (c) => { - try { - await this.libp2p.peerStore.merge(c.remotePeer, { - metadata: { - ttl: numberToBytes(Date.now()) - } - }); - log.info(`TTL updated for connected peer ${c.remotePeer.toString()}`); - } catch (error) { - log.error( - `Unexpected error while maintaining TTL connected peer`, - error - ); - } - }); - - await Promise.all(promises); - } - private async dialPeersFromStore(): Promise { log.info(`Dialing peers from store`); @@ -268,6 +244,9 @@ export class ConnectionLimiter implements IConnectionLimiter { private async getPrioritizedPeers(): Promise { const allPeers = await this.libp2p.peerStore.all(); const allConnections = this.libp2p.getConnections(); + const allConnectionsSet = new Set( + allConnections.map((c) => c.remotePeer.toString()) + ); log.info( `Found ${allPeers.length} peers in store, and found ${allConnections.length} connections` @@ -275,7 +254,7 @@ export class ConnectionLimiter implements IConnectionLimiter { const notConnectedPeers = allPeers.filter( (p) => - !allConnections.some((c) => c.remotePeer.equals(p.id)) && + !allConnectionsSet.has(p.id.toString()) && isAddressesSupported( this.libp2p, p.addresses.map((a) => a.multiaddr) diff --git a/packages/headless-tests/.eslintrc.cjs b/packages/headless-tests/.eslintrc.cjs deleted file mode 100644 index d4383bc136..0000000000 --- a/packages/headless-tests/.eslintrc.cjs +++ /dev/null @@ -1,34 +0,0 @@ -module.exports = { - root: true, - env: { - browser: true, - node: true, - es2021: true - }, - plugins: ["import"], - extends: ["eslint:recommended"], - parserOptions: { - ecmaVersion: 2022, - sourceType: "module" - }, - rules: { - // Disable rules that might cause issues with this package - "no-undef": "off" - }, - ignorePatterns: [ - "node_modules", - "build", - "coverage" - ], - overrides: [ - { - files: ["*.spec.ts", "**/test_utils/*.ts", "*.js", "*.cjs"], - rules: { - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-explicit-any": "off", - "no-console": "off", - "import/no-extraneous-dependencies": ["error", { "devDependencies": true }] - } - } - ] -}; diff --git a/packages/headless-tests/README.md b/packages/headless-tests/README.md deleted file mode 100644 index 1ee1b340f8..0000000000 --- a/packages/headless-tests/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Waku Headless Tests - -This package contains a minimal browser application used for testing the Waku SDK in a browser environment. It is used by the browser-tests package to run end-to-end tests on the SDK. - -## Usage - -### Build the app - -```bash -npm run build -``` - -### Start the app - -```bash -npm start -``` - -This will start a server on port 8080 by default. - -## Integration with browser-tests - -This package is designed to be used with the browser-tests package to run end-to-end tests on the SDK. It exposes the Waku API via a global object in the browser. diff --git a/packages/headless-tests/favicon.ico b/packages/headless-tests/favicon.ico deleted file mode 100644 index 74cf5df7f8..0000000000 Binary files a/packages/headless-tests/favicon.ico and /dev/null differ diff --git a/packages/headless-tests/favicon.png b/packages/headless-tests/favicon.png deleted file mode 100644 index f4cab92fbe..0000000000 Binary files a/packages/headless-tests/favicon.png and /dev/null differ diff --git a/packages/headless-tests/index.html b/packages/headless-tests/index.html deleted file mode 100644 index 44d2db8c07..0000000000 --- a/packages/headless-tests/index.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - Headless - - - - - - - -
-
-
-

Status:

- -
- Peer's information - -

Content topic

-

- -

Local Peer Id

-

- -

Remote Peer Id

-

-
-
- -
- - -
- - - - diff --git a/packages/headless-tests/index.js b/packages/headless-tests/index.js deleted file mode 100644 index 6349f639d8..0000000000 --- a/packages/headless-tests/index.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable */ -import { API } from "../browser-tests/src/api/shared.ts"; - -runApp().catch((err) => { - console.error(err); -}); - -async function runApp() { - if (typeof window !== "undefined") { - // Expose shared API functions for browser communication - window.wakuAPI = API; - window.subscriptions = []; - } -} diff --git a/packages/headless-tests/manifest.json b/packages/headless-tests/manifest.json deleted file mode 100644 index 6fafb2c8d0..0000000000 --- a/packages/headless-tests/manifest.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "Light Chat", - "description": "Send messages between several users (or just one) using light client targeted protocols.", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "favicon.png", - "type": "image/png", - "sizes": "192x192" - } - ], - "display": "standalone", - "theme_color": "#ffffff", - "background_color": "#ffffff" -} diff --git a/packages/headless-tests/package.json b/packages/headless-tests/package.json deleted file mode 100644 index 7dd56fd72a..0000000000 --- a/packages/headless-tests/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "@waku/headless-tests", - "version": "0.1.0", - "private": true, - "homepage": "/headless", - "type": "module", - "devDependencies": { - "@babel/core": "^7.24.0", - "@babel/preset-env": "^7.24.0", - "@babel/preset-typescript": "^7.23.3", - "babel-loader": "^9.1.3", - "filter-obj": "^2.0.2", - "it-first": "^3.0.9", - "node-polyfill-webpack-plugin": "^2.0.1", - "serve": "^14.1.2", - "webpack": "^5.99.5", - "webpack-cli": "^5.1.4" - }, - "dependencies": { - "@waku/sdk": "^0.0.30" - }, - "scripts": { - "start": "serve .", - "build": "webpack", - "format": "eslint --fix webpack.config.js" - } -} diff --git a/packages/headless-tests/style.css b/packages/headless-tests/style.css deleted file mode 100644 index ff54ed113e..0000000000 --- a/packages/headless-tests/style.css +++ /dev/null @@ -1,153 +0,0 @@ -* { - margin: 0; - padding: 0; - word-wrap: break-word; - box-sizing: border-box; -} - -html, body { - width: 100%; - height: 100%; - max-width: 100%; - max-height: 100%; -} - -html { - font-size: 16px; - overflow: hidden; -} - -body { - display: flex; - align-items: center; - padding: 10px; - justify-content: center; -} - -details { - margin-bottom: 15px; -} - -details p { - margin-bottom: 10px; -} - -summary { - cursor: pointer; - max-width: 100%; - margin-bottom: 5px; -} - -span { - font-weight: 300; -} - -input, textarea { - line-height: 1rem; - padding: 5px; -} - -textarea { - min-height: 3rem; -} - -h3 { - margin-bottom: 5px; -} - -.content { - width: 800px; - min-width: 300px; - max-width: 800px; - height: 100%; - display: flex; - flex-direction: column; - align-content: space-between; -} - -#messages { - overflow-y: scroll; - overflow-x: hidden; -} - -.message + .message { - margin-top: 15px; -} - -.message :first-child { - font-weight: bold; -} - -.message p + p { - margin-top: 5px; -} - -.message span { - font-size: 0.8rem; -} - -.inputArea { - display: flex; - gap: 10px; - flex-direction: column; - margin-top: 20px; -} - -.controls { - margin-top: 10px; - display: flex; - gap: 10px; -} - -.controls button { - flex-grow: 1; - cursor: pointer; - padding: 10px; -} - -#send { - background-color: #32d1a0; - border: none; - color: white; -} -#send:hover { - background-color: #3abd96; -} -#send:active { - background-color: #3ba183; -} - -#exit { - color: white; - border: none; - background-color: #ff3a31; -} -#exit:hover { - background-color: #e4423a; -} -#exit:active { - background-color: #c84740; -} - -.success { - color: #3ba183; -} - -.progress { - color: #9ea13b; -} - -.terminated { - color: black; -} - -.error { - color: #c84740; -} - -.footer { - display: flex; - width: 100%; - flex-direction: column; - align-self: flex-end; -} diff --git a/packages/headless-tests/tsconfig.json b/packages/headless-tests/tsconfig.json deleted file mode 100644 index a015324b34..0000000000 --- a/packages/headless-tests/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "target": "es2020", - "module": "commonjs", - "allowJs": true, - "checkJs": false, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true - }, - "include": [ - "**/*.js" - ] -} diff --git a/packages/headless-tests/webpack.config.js b/packages/headless-tests/webpack.config.js deleted file mode 100644 index 9b85739a5e..0000000000 --- a/packages/headless-tests/webpack.config.js +++ /dev/null @@ -1,47 +0,0 @@ -/* eslint-disable */ -/** - * This webpack configuration file uses ES Module syntax. - */ -import path from 'path'; -import { fileURLToPath } from 'url'; -import NodePolyfillPlugin from 'node-polyfill-webpack-plugin'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -export default { - entry: "./index.js", - output: { - filename: "bundle.js", - path: path.resolve(__dirname, "build") - }, - mode: "production", - target: "web", - plugins: [new NodePolyfillPlugin()], - resolve: { - extensions: [".js", ".ts", ".tsx", ".jsx"], - fallback: { - fs: false, - net: false, - tls: false - }, - alias: { - // Create an alias to easily import from src - "@src": path.resolve(__dirname, "../src") - } - }, - module: { - rules: [ - { - test: /\.(js|ts|tsx)$/, - exclude: /node_modules/, - use: { - loader: "babel-loader", - options: { - presets: ["@babel/preset-env", "@babel/preset-typescript"] - } - } - } - ] - } -}; diff --git a/packages/proto/src/generated/sds_message.ts b/packages/proto/src/generated/sds_message.ts index 20ef8746ae..eba12d4acd 100644 --- a/packages/proto/src/generated/sds_message.ts +++ b/packages/proto/src/generated/sds_message.ts @@ -84,7 +84,7 @@ export interface SdsMessage { senderId: string messageId: string channelId: string - lamportTimestamp?: number + lamportTimestamp?: bigint causalHistory: HistoryEntry[] bloomFilter?: Uint8Array content?: Uint8Array @@ -117,7 +117,7 @@ export namespace SdsMessage { if (obj.lamportTimestamp != null) { w.uint32(80) - w.int32(obj.lamportTimestamp) + w.uint64(obj.lamportTimestamp) } if (obj.causalHistory != null) { @@ -167,7 +167,7 @@ export namespace SdsMessage { break } case 10: { - obj.lamportTimestamp = reader.int32() + obj.lamportTimestamp = reader.uint64() break } case 11: { diff --git a/packages/proto/src/lib/sds_message.proto b/packages/proto/src/lib/sds_message.proto index 5344a0d33a..c38e99b084 100644 --- a/packages/proto/src/lib/sds_message.proto +++ b/packages/proto/src/lib/sds_message.proto @@ -9,7 +9,7 @@ message SdsMessage { string sender_id = 1; // Participant ID of the message sender string message_id = 2; // Unique identifier of the message string channel_id = 3; // Identifier of the channel to which the message belongs - optional int32 lamport_timestamp = 10; // Logical timestamp for causal ordering in channel + 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 optional bytes content = 20; // Actual content of the message diff --git a/packages/rln/package.json b/packages/rln/package.json index bb34607c96..4ea0199115 100644 --- a/packages/rln/package.json +++ b/packages/rln/package.json @@ -79,7 +79,7 @@ "@waku/core": "^0.0.39", "@waku/utils": "^0.0.27", "@noble/hashes": "^1.2.0", - "@waku/zerokit-rln-wasm": "^0.0.13", + "@waku/zerokit-rln-wasm": "^0.2.1", "ethereum-cryptography": "^3.1.0", "ethers": "^5.7.2", "lodash": "^4.17.21", diff --git a/packages/rln/src/codec.spec.ts b/packages/rln/src/codec.spec.ts deleted file mode 100644 index dfb6c80f1b..0000000000 --- a/packages/rln/src/codec.spec.ts +++ /dev/null @@ -1,363 +0,0 @@ -import { createDecoder, createEncoder } from "@waku/core/lib/message/version_0"; -import { IDecodedMessage } from "@waku/interfaces"; -import { - generatePrivateKey, - generateSymmetricKey, - getPublicKey -} from "@waku/message-encryption"; -import { - createDecoder as createAsymDecoder, - createEncoder as createAsymEncoder -} from "@waku/message-encryption/ecies"; -import { - createDecoder as createSymDecoder, - createEncoder as createSymEncoder -} from "@waku/message-encryption/symmetric"; -import { expect } from "chai"; - -import { - createRLNDecoder, - createRLNEncoder, - RLNDecoder, - RLNEncoder -} from "./codec.js"; -import { - createTestMetaSetter, - createTestRLNCodecSetup, - EMPTY_PROTO_MESSAGE, - TEST_CONSTANTS, - verifyRLNMessage -} from "./codec.test-utils.js"; -import { RlnMessage } from "./message.js"; -import { epochBytesToInt } from "./utils/index.js"; - -describe("RLN codec with version 0", () => { - it("toWire", async function () { - const { rlnInstance, credential, index, payload } = - await createTestRLNCodecSetup(); - - const rlnEncoder = createRLNEncoder({ - encoder: createEncoder({ - contentTopic: TEST_CONSTANTS.contentTopic, - routingInfo: TEST_CONSTANTS.routingInfo - }), - rlnInstance, - index, - credential - }); - const rlnDecoder = createRLNDecoder({ - rlnInstance, - decoder: createDecoder( - TEST_CONSTANTS.contentTopic, - TEST_CONSTANTS.routingInfo - ) - }); - - const bytes = await rlnEncoder.toWire({ payload }); - - expect(bytes).to.not.be.undefined; - const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!); - expect(protoResult).to.not.be.undefined; - const msg = (await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, - protoResult! - ))!; - - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance); - }); - - it("toProtoObj", async function () { - const { rlnInstance, credential, index, payload } = - await createTestRLNCodecSetup(); - - const rlnEncoder = new RLNEncoder( - createEncoder({ - contentTopic: TEST_CONSTANTS.contentTopic, - routingInfo: TEST_CONSTANTS.routingInfo - }), - rlnInstance, - index, - credential - ); - const rlnDecoder = new RLNDecoder( - rlnInstance, - createDecoder(TEST_CONSTANTS.contentTopic, TEST_CONSTANTS.routingInfo) - ); - - const proto = await rlnEncoder.toProtoObj({ payload }); - - expect(proto).to.not.be.undefined; - const msg = (await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, - proto! - )) as RlnMessage; - - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance); - }); -}); - -describe("RLN codec with version 1", () => { - it("Symmetric, toWire", async function () { - const { rlnInstance, credential, index, payload } = - await createTestRLNCodecSetup(); - const symKey = generateSymmetricKey(); - - const rlnEncoder = new RLNEncoder( - createSymEncoder({ - contentTopic: TEST_CONSTANTS.contentTopic, - routingInfo: TEST_CONSTANTS.routingInfo, - symKey - }), - rlnInstance, - index, - credential - ); - const rlnDecoder = new RLNDecoder( - rlnInstance, - createSymDecoder( - TEST_CONSTANTS.contentTopic, - TEST_CONSTANTS.routingInfo, - symKey - ) - ); - - const bytes = await rlnEncoder.toWire({ payload }); - - expect(bytes).to.not.be.undefined; - const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!); - expect(protoResult).to.not.be.undefined; - const msg = (await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, - protoResult! - ))!; - - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 1, rlnInstance); - }); - - it("Symmetric, toProtoObj", async function () { - const { rlnInstance, credential, index, payload } = - await createTestRLNCodecSetup(); - const symKey = generateSymmetricKey(); - - const rlnEncoder = new RLNEncoder( - createSymEncoder({ - contentTopic: TEST_CONSTANTS.contentTopic, - routingInfo: TEST_CONSTANTS.routingInfo, - symKey - }), - rlnInstance, - index, - credential - ); - const rlnDecoder = new RLNDecoder( - rlnInstance, - createSymDecoder( - TEST_CONSTANTS.contentTopic, - TEST_CONSTANTS.routingInfo, - symKey - ) - ); - - const proto = await rlnEncoder.toProtoObj({ payload }); - - expect(proto).to.not.be.undefined; - const msg = await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, - proto! - ); - - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 1, rlnInstance); - }); - - it("Asymmetric, toWire", async function () { - const { rlnInstance, credential, index, payload } = - await createTestRLNCodecSetup(); - const privateKey = generatePrivateKey(); - const publicKey = getPublicKey(privateKey); - - const rlnEncoder = new RLNEncoder( - createAsymEncoder({ - contentTopic: TEST_CONSTANTS.contentTopic, - routingInfo: TEST_CONSTANTS.routingInfo, - publicKey - }), - rlnInstance, - index, - credential - ); - const rlnDecoder = new RLNDecoder( - rlnInstance, - createAsymDecoder( - TEST_CONSTANTS.contentTopic, - TEST_CONSTANTS.routingInfo, - privateKey - ) - ); - - const bytes = await rlnEncoder.toWire({ payload }); - - expect(bytes).to.not.be.undefined; - const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!); - expect(protoResult).to.not.be.undefined; - const msg = (await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, - protoResult! - ))!; - - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 1, rlnInstance); - }); - - it("Asymmetric, toProtoObj", async function () { - const { rlnInstance, credential, index, payload } = - await createTestRLNCodecSetup(); - const privateKey = generatePrivateKey(); - const publicKey = getPublicKey(privateKey); - - const rlnEncoder = new RLNEncoder( - createAsymEncoder({ - contentTopic: TEST_CONSTANTS.contentTopic, - routingInfo: TEST_CONSTANTS.routingInfo, - publicKey - }), - rlnInstance, - index, - credential - ); - const rlnDecoder = new RLNDecoder( - rlnInstance, - createAsymDecoder( - TEST_CONSTANTS.contentTopic, - TEST_CONSTANTS.routingInfo, - privateKey - ) - ); - - const proto = await rlnEncoder.toProtoObj({ payload }); - - expect(proto).to.not.be.undefined; - const msg = await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, - proto! - ); - - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 1, rlnInstance); - }); -}); - -describe("RLN Codec - epoch", () => { - it("toProtoObj", async function () { - const { rlnInstance, credential, index, payload } = - await createTestRLNCodecSetup(); - - const rlnEncoder = new RLNEncoder( - createEncoder({ - contentTopic: TEST_CONSTANTS.contentTopic, - routingInfo: TEST_CONSTANTS.routingInfo - }), - rlnInstance, - index, - credential - ); - const rlnDecoder = new RLNDecoder( - rlnInstance, - createDecoder(TEST_CONSTANTS.contentTopic, TEST_CONSTANTS.routingInfo) - ); - - const proto = await rlnEncoder.toProtoObj({ payload }); - - expect(proto).to.not.be.undefined; - const msg = (await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, - proto! - )) as RlnMessage; - - const epochBytes = proto!.rateLimitProof!.epoch; - const epoch = epochBytesToInt(epochBytes); - - expect(msg.epoch!.toString(10).length).to.eq(9); - expect(msg.epoch).to.eq(epoch); - - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance); - }); -}); - -describe("RLN codec with version 0 and meta setter", () => { - it("toWire", async function () { - const { rlnInstance, credential, index, payload } = - await createTestRLNCodecSetup(); - const metaSetter = createTestMetaSetter(); - - const rlnEncoder = createRLNEncoder({ - encoder: createEncoder({ - contentTopic: TEST_CONSTANTS.contentTopic, - routingInfo: TEST_CONSTANTS.routingInfo, - metaSetter - }), - rlnInstance, - index, - credential - }); - const rlnDecoder = createRLNDecoder({ - rlnInstance, - decoder: createDecoder( - TEST_CONSTANTS.contentTopic, - TEST_CONSTANTS.routingInfo - ) - }); - - const bytes = await rlnEncoder.toWire({ payload }); - - expect(bytes).to.not.be.undefined; - const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!); - expect(protoResult).to.not.be.undefined; - const msg = (await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, - protoResult! - ))!; - - const expectedMeta = metaSetter({ - ...EMPTY_PROTO_MESSAGE, - payload: protoResult!.payload - }); - - expect(msg!.meta).to.deep.eq(expectedMeta); - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance); - }); - - it("toProtoObj", async function () { - const { rlnInstance, credential, index, payload } = - await createTestRLNCodecSetup(); - const metaSetter = createTestMetaSetter(); - - const rlnEncoder = new RLNEncoder( - createEncoder({ - contentTopic: TEST_CONSTANTS.contentTopic, - routingInfo: TEST_CONSTANTS.routingInfo, - metaSetter - }), - rlnInstance, - index, - credential - ); - const rlnDecoder = new RLNDecoder( - rlnInstance, - createDecoder(TEST_CONSTANTS.contentTopic, TEST_CONSTANTS.routingInfo) - ); - - const proto = await rlnEncoder.toProtoObj({ payload }); - - expect(proto).to.not.be.undefined; - const msg = (await rlnDecoder.fromProtoObj( - TEST_CONSTANTS.emptyPubsubTopic, - proto! - )) as RlnMessage; - - const expectedMeta = metaSetter({ - ...EMPTY_PROTO_MESSAGE, - payload: msg!.payload - }); - - expect(msg!.meta).to.deep.eq(expectedMeta); - verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance); - }); -}); diff --git a/packages/rln/src/codec.test-utils.ts b/packages/rln/src/codec.test-utils.ts deleted file mode 100644 index 4b16ae7131..0000000000 --- a/packages/rln/src/codec.test-utils.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { IProtoMessage } from "@waku/interfaces"; -import { createRoutingInfo } from "@waku/utils"; -import { expect } from "chai"; - -import { createRLN } from "./create.js"; -import type { IdentityCredential } from "./identity.js"; - -export interface TestRLNCodecSetup { - rlnInstance: any; - credential: IdentityCredential; - index: number; - payload: Uint8Array; -} - -export const TEST_CONSTANTS = { - contentTopic: "/test/1/waku-message/utf8", - emptyPubsubTopic: "", - defaultIndex: 0, - defaultPayload: new Uint8Array([1, 2, 3, 4, 5]), - routingInfo: createRoutingInfo( - { - clusterId: 0, - numShardsInCluster: 2 - }, - { contentTopic: "/test/1/waku-message/utf8" } - ) -} as const; - -export const EMPTY_PROTO_MESSAGE = { - timestamp: undefined, - contentTopic: "", - ephemeral: undefined, - meta: undefined, - rateLimitProof: undefined, - version: undefined -} as const; - -/** - * Creates a basic RLN setup for codec tests - */ -export async function createTestRLNCodecSetup(): Promise { - const rlnInstance = await createRLN(); - const credential = rlnInstance.zerokit.generateIdentityCredentials(); - rlnInstance.zerokit.insertMember(credential.IDCommitment); - - return { - rlnInstance, - credential, - index: TEST_CONSTANTS.defaultIndex, - payload: TEST_CONSTANTS.defaultPayload - }; -} - -/** - * Creates a meta setter function for testing - */ -export function createTestMetaSetter(): ( - msg: IProtoMessage & { meta: undefined } -) => Uint8Array { - return (msg: IProtoMessage & { meta: undefined }): Uint8Array => { - const buffer = new ArrayBuffer(4); - const view = new DataView(buffer); - view.setUint32(0, msg.payload.length, false); - return new Uint8Array(buffer); - }; -} - -/** - * Verifies common RLN message properties - */ -export function verifyRLNMessage( - msg: any, - payload: Uint8Array, - contentTopic: string, - version: number, - rlnInstance: any -): void { - expect(msg.rateLimitProof).to.not.be.undefined; - expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true; - expect(msg.verifyNoRoot()).to.be.true; - expect(msg.epoch).to.not.be.undefined; - expect(msg.epoch).to.be.gt(0); - - expect(msg.contentTopic).to.eq(contentTopic); - expect(msg.msg.version).to.eq(version); - expect(msg.payload).to.deep.eq(payload); - expect(msg.timestamp).to.not.be.undefined; -} diff --git a/packages/rln/src/codec.ts b/packages/rln/src/codec.ts deleted file mode 100644 index 067e2f382a..0000000000 --- a/packages/rln/src/codec.ts +++ /dev/null @@ -1,138 +0,0 @@ -import type { - IDecodedMessage, - IDecoder, - IEncoder, - IMessage, - IProtoMessage, - IRateLimitProof, - IRoutingInfo -} from "@waku/interfaces"; -import { Logger } from "@waku/utils"; - -import type { IdentityCredential } from "./identity.js"; -import { RlnMessage, toRLNSignal } from "./message.js"; -import { RLNInstance } from "./rln.js"; - -const log = new Logger("rln:encoder"); - -export class RLNEncoder implements IEncoder { - private readonly idSecretHash: Uint8Array; - - public constructor( - private readonly encoder: IEncoder, - private readonly rlnInstance: RLNInstance, - private readonly index: number, - identityCredential: IdentityCredential - ) { - if (index < 0) throw new Error("Invalid membership index"); - this.idSecretHash = identityCredential.IDSecretHash; - } - - public async toWire(message: IMessage): Promise { - message.rateLimitProof = await this.generateProof(message); - log.info("Proof generated", message.rateLimitProof); - return this.encoder.toWire(message); - } - - public async toProtoObj( - message: IMessage - ): Promise { - const protoMessage = await this.encoder.toProtoObj(message); - if (!protoMessage) return; - - protoMessage.contentTopic = this.contentTopic; - protoMessage.rateLimitProof = await this.generateProof(message); - log.info("Proof generated", protoMessage.rateLimitProof); - return protoMessage; - } - - private async generateProof(message: IMessage): Promise { - const signal = toRLNSignal(this.contentTopic, message); - return this.rlnInstance.zerokit.generateRLNProof( - signal, - this.index, - message.timestamp, - this.idSecretHash - ); - } - - public get pubsubTopic(): string { - return this.encoder.pubsubTopic; - } - - public get routingInfo(): IRoutingInfo { - return this.encoder.routingInfo; - } - - public get contentTopic(): string { - return this.encoder.contentTopic; - } - - public get ephemeral(): boolean { - return this.encoder.ephemeral; - } -} - -type RLNEncoderOptions = { - encoder: IEncoder; - rlnInstance: RLNInstance; - index: number; - credential: IdentityCredential; -}; - -export const createRLNEncoder = (options: RLNEncoderOptions): RLNEncoder => { - return new RLNEncoder( - options.encoder, - options.rlnInstance, - options.index, - options.credential - ); -}; - -export class RLNDecoder - implements IDecoder> -{ - public constructor( - private readonly rlnInstance: RLNInstance, - private readonly decoder: IDecoder - ) {} - - public get pubsubTopic(): string { - return this.decoder.pubsubTopic; - } - - public get contentTopic(): string { - return this.decoder.contentTopic; - } - - public fromWireToProtoObj( - bytes: Uint8Array - ): Promise { - const protoMessage = this.decoder.fromWireToProtoObj(bytes); - log.info("Message decoded", protoMessage); - return Promise.resolve(protoMessage); - } - - public async fromProtoObj( - pubsubTopic: string, - proto: IProtoMessage - ): Promise | undefined> { - const msg: T | undefined = await this.decoder.fromProtoObj( - pubsubTopic, - proto - ); - if (!msg) return; - return new RlnMessage(this.rlnInstance, msg, proto.rateLimitProof); - } -} - -type RLNDecoderOptions = { - decoder: IDecoder; - rlnInstance: RLNInstance; -}; - -export const createRLNDecoder = ( - options: RLNDecoderOptions -): RLNDecoder => { - return new RLNDecoder(options.rlnInstance, options.decoder); -}; diff --git a/packages/rln/src/contract/constants.ts b/packages/rln/src/contract/constants.ts index 4808a88a14..7869e65bc6 100644 --- a/packages/rln/src/contract/constants.ts +++ b/packages/rln/src/contract/constants.ts @@ -19,26 +19,16 @@ export const PRICE_CALCULATOR_CONTRACT = { * @see https://github.com/waku-org/specs/blob/master/standards/core/rln-contract.md#implementation-suggestions */ export const RATE_LIMIT_TIERS = { - LOW: 20, // Suggested minimum rate - 20 messages per epoch - MEDIUM: 200, - HIGH: 600 // Suggested maximum rate - 600 messages per epoch + STANDARD: 300, + MAX: 600 } as const; // Global rate limit parameters export const RATE_LIMIT_PARAMS = { - MIN_RATE: RATE_LIMIT_TIERS.LOW, - MAX_RATE: RATE_LIMIT_TIERS.HIGH, - MAX_TOTAL_RATE: 160_000, // Maximum total rate limit across all memberships - EPOCH_LENGTH: 600 // Epoch length in seconds (10 minutes) + MIN_RATE: RATE_LIMIT_TIERS.STANDARD, + MAX_RATE: RATE_LIMIT_TIERS.MAX, + MAX_TOTAL_RATE: 160_000, + EPOCH_LENGTH: 600 } as const; -/** - * Default Q value for the RLN contract - * This is the upper bound for the ID commitment - * @see https://github.com/waku-org/specs/blob/master/standards/core/rln-contract.md#implementation-suggestions - */ -export const RLN_Q = BigInt( - "21888242871839275222246405745257275088548364400416034343698204186575808495617" -); - export const DEFAULT_RATE_LIMIT = RATE_LIMIT_PARAMS.MAX_RATE; diff --git a/packages/rln/src/contract/index.ts b/packages/rln/src/contract/index.ts index 7f96557dc9..5d4d612733 100644 --- a/packages/rln/src/contract/index.ts +++ b/packages/rln/src/contract/index.ts @@ -1,3 +1,2 @@ -export { RLNContract } from "./rln_contract.js"; export * from "./constants.js"; export * from "./types.js"; diff --git a/packages/rln/src/contract/rln_base_contract.ts b/packages/rln/src/contract/rln_base_contract.ts index 7546596cd0..9934360be3 100644 --- a/packages/rln/src/contract/rln_base_contract.ts +++ b/packages/rln/src/contract/rln_base_contract.ts @@ -3,7 +3,6 @@ import { ethers } from "ethers"; import { IdentityCredential } from "../identity.js"; import { DecryptedCredentials } from "../keystore/types.js"; -import { BytesUtils } from "../utils/bytes.js"; import { RLN_ABI } from "./abi/rln.js"; import { @@ -632,7 +631,7 @@ export class RLNBaseContract { permit.v, permit.r, permit.s, - BytesUtils.buildBigIntFromUint8ArrayBE(identity.IDCommitment), + identity.IDCommitmentBigInt, this.rateLimit, idCommitmentsToErase.map((id) => ethers.BigNumber.from(id)) ); diff --git a/packages/rln/src/contract/rln_contract.spec.ts b/packages/rln/src/contract/rln_contract.spec.ts deleted file mode 100644 index 7603e7cc2e..0000000000 --- a/packages/rln/src/contract/rln_contract.spec.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { hexToBytes } from "@waku/utils/bytes"; -import { expect, use } from "chai"; -import chaiAsPromised from "chai-as-promised"; -import * as ethers from "ethers"; -import sinon, { SinonSandbox } from "sinon"; - -import { createTestRLNInstance, initializeRLNContract } from "./test_setup.js"; -import { - createMockRegistryContract, - createRegisterStub, - mockRLNRegisteredEvent, - verifyRegistration -} from "./test_utils.js"; - -use(chaiAsPromised); - -describe("RLN Contract abstraction - RLN", () => { - let sandbox: SinonSandbox; - - beforeEach(async () => { - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe("Member Registration", () => { - it("should fetch members from events and store them in the RLN instance", async () => { - const { rlnInstance, insertMemberSpy } = await createTestRLNInstance(); - const membershipRegisteredEvent = mockRLNRegisteredEvent(); - const queryFilterStub = sinon.stub().returns([membershipRegisteredEvent]); - - const mockedRegistryContract = createMockRegistryContract({ - queryFilter: queryFilterStub - }); - - const rlnContract = await initializeRLNContract( - rlnInstance, - mockedRegistryContract - ); - - await rlnContract.fetchMembers({ - fromBlock: 0, - fetchRange: 1000, - fetchChunks: 2 - }); - - expect( - insertMemberSpy.calledWith( - ethers.utils.zeroPad( - hexToBytes(membershipRegisteredEvent.args!.idCommitment), - 32 - ) - ) - ).to.be.true; - expect(queryFilterStub.called).to.be.true; - }); - - it("should register a member", async () => { - const { rlnInstance, identity, insertMemberSpy } = - await createTestRLNInstance(); - - const registerStub = createRegisterStub(identity); - const mockedRegistryContract = createMockRegistryContract({ - register: registerStub, - queryFilter: () => [] - }); - - const rlnContract = await initializeRLNContract( - rlnInstance, - mockedRegistryContract - ); - - const decryptedCredentials = - await rlnContract.registerWithIdentity(identity); - - if (!decryptedCredentials) { - throw new Error("Failed to retrieve credentials"); - } - - verifyRegistration( - decryptedCredentials, - identity, - registerStub, - insertMemberSpy - ); - }); - }); -}); diff --git a/packages/rln/src/contract/rln_contract.ts b/packages/rln/src/contract/rln_contract.ts deleted file mode 100644 index eae91b323e..0000000000 --- a/packages/rln/src/contract/rln_contract.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { Logger } from "@waku/utils"; -import { hexToBytes } from "@waku/utils/bytes"; -import { ethers } from "ethers"; - -import type { RLNInstance } from "../rln.js"; -import { MerkleRootTracker } from "../root_tracker.js"; -import { BytesUtils } from "../utils/bytes.js"; - -import { RLNBaseContract } from "./rln_base_contract.js"; -import { RLNContractInitOptions } from "./types.js"; - -const log = new Logger("rln:contract"); - -export class RLNContract extends RLNBaseContract { - private instance: RLNInstance; - private merkleRootTracker: MerkleRootTracker; - - /** - * Asynchronous initializer for RLNContract. - * Allows injecting a mocked contract for testing purposes. - */ - public static async init( - rlnInstance: RLNInstance, - options: RLNContractInitOptions - ): Promise { - const rlnContract = new RLNContract(rlnInstance, options); - - return rlnContract; - } - - private constructor( - rlnInstance: RLNInstance, - options: RLNContractInitOptions - ) { - super(options); - - this.instance = rlnInstance; - - const initialRoot = rlnInstance.zerokit.getMerkleRoot(); - this.merkleRootTracker = new MerkleRootTracker(5, initialRoot); - } - - public override processEvents(events: ethers.Event[]): void { - const toRemoveTable = new Map(); - const toInsertTable = new Map(); - - events.forEach((evt) => { - if (!evt.args) { - return; - } - - if ( - evt.event === "MembershipErased" || - evt.event === "MembershipExpired" - ) { - let index = evt.args.index; - - if (!index) { - return; - } - - if (typeof index === "number" || typeof index === "string") { - index = ethers.BigNumber.from(index); - } else { - log.error("Index is not a number or string", { - index, - event: evt - }); - return; - } - - const toRemoveVal = toRemoveTable.get(evt.blockNumber); - if (toRemoveVal != undefined) { - toRemoveVal.push(index.toNumber()); - toRemoveTable.set(evt.blockNumber, toRemoveVal); - } else { - toRemoveTable.set(evt.blockNumber, [index.toNumber()]); - } - } else if (evt.event === "MembershipRegistered") { - let eventsPerBlock = toInsertTable.get(evt.blockNumber); - if (eventsPerBlock == undefined) { - eventsPerBlock = []; - } - - eventsPerBlock.push(evt); - toInsertTable.set(evt.blockNumber, eventsPerBlock); - } - }); - - this.removeMembers(this.instance, toRemoveTable); - this.insertMembers(this.instance, toInsertTable); - } - - private insertMembers( - rlnInstance: RLNInstance, - toInsert: Map - ): void { - toInsert.forEach((events: ethers.Event[], blockNumber: number) => { - events.forEach((evt) => { - if (!evt.args) return; - - const _idCommitment = evt.args.idCommitment as string; - let index = evt.args.index; - - if (!_idCommitment || !index) { - return; - } - - if (typeof index === "number" || typeof index === "string") { - index = ethers.BigNumber.from(index); - } - - const idCommitment = BytesUtils.zeroPadLE( - hexToBytes(_idCommitment), - 32 - ); - rlnInstance.zerokit.insertMember(idCommitment); - - const numericIndex = index.toNumber(); - this._members.set(numericIndex, { - index, - idCommitment: _idCommitment - }); - }); - - const currentRoot = rlnInstance.zerokit.getMerkleRoot(); - this.merkleRootTracker.pushRoot(blockNumber, currentRoot); - }); - } - - private removeMembers( - rlnInstance: RLNInstance, - toRemove: Map - ): void { - const removeDescending = new Map([...toRemove].reverse()); - removeDescending.forEach((indexes: number[], blockNumber: number) => { - indexes.forEach((index) => { - if (this._members.has(index)) { - this._members.delete(index); - rlnInstance.zerokit.deleteMember(index); - } - }); - - this.merkleRootTracker.backFill(blockNumber); - }); - } -} diff --git a/packages/rln/src/contract/test_setup.ts b/packages/rln/src/contract/test_setup.ts deleted file mode 100644 index b5da3f6af6..0000000000 --- a/packages/rln/src/contract/test_setup.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { hexToBytes } from "@waku/utils/bytes"; -import { ethers } from "ethers"; -import sinon from "sinon"; - -import { createRLN } from "../create.js"; -import type { IdentityCredential } from "../identity.js"; - -import { DEFAULT_RATE_LIMIT, RLN_CONTRACT } from "./constants.js"; -import { RLNContract } from "./rln_contract.js"; - -export interface TestRLNInstance { - rlnInstance: any; - identity: IdentityCredential; - insertMemberSpy: sinon.SinonStub; -} - -/** - * Creates a test RLN instance with basic setup - */ -export async function createTestRLNInstance(): Promise { - const rlnInstance = await createRLN(); - const insertMemberSpy = sinon.stub(); - rlnInstance.zerokit.insertMember = insertMemberSpy; - - const mockSignature = - "0xdeb8a6b00a8e404deb1f52d3aa72ed7f60a2ff4484c737eedaef18a0aacb2dfb4d5d74ac39bb71fa358cf2eb390565a35b026cc6272f2010d4351e17670311c21c"; - const identity = - rlnInstance.zerokit.generateSeededIdentityCredential(mockSignature); - - return { - rlnInstance, - identity, - insertMemberSpy - }; -} - -/** - * Initializes an RLN contract with the given registry contract - */ -export async function initializeRLNContract( - rlnInstance: any, - mockedRegistryContract: ethers.Contract -): Promise { - const provider = new ethers.providers.JsonRpcProvider(); - const voidSigner = new ethers.VoidSigner(RLN_CONTRACT.address, provider); - - const originalRegister = mockedRegistryContract.register; - (mockedRegistryContract as any).register = function (...args: any[]) { - const result = originalRegister.apply(this, args); - - if (args[0] && rlnInstance.zerokit) { - const idCommitmentBigInt = args[0]; - const idCommitmentHex = - "0x" + idCommitmentBigInt.toString(16).padStart(64, "0"); - const idCommitment = ethers.utils.zeroPad( - hexToBytes(idCommitmentHex), - 32 - ); - rlnInstance.zerokit.insertMember(idCommitment); - } - - return result; - }; - - const contract = await RLNContract.init(rlnInstance, { - address: RLN_CONTRACT.address, - signer: voidSigner, - rateLimit: DEFAULT_RATE_LIMIT, - contract: mockedRegistryContract - }); - - return contract; -} - -/** - * Common test message data - */ -export const TEST_DATA = { - contentTopic: "/test/1/waku-message/utf8", - emptyPubsubTopic: "", - testMessage: Uint8Array.from( - "Hello World".split("").map((x) => x.charCodeAt(0)) - ), - mockSignature: - "0xdeb8a6b00a8e404deb1f52d3aa72ed7f60a2ff4484c737eedaef18a0aacb2dfb4d5d74ac39bb71fa358cf2eb390565a35b026cc6272f2010d4351e17670311c21c" -}; diff --git a/packages/rln/src/contract/test_utils.ts b/packages/rln/src/contract/test_utils.ts deleted file mode 100644 index a2ac8bc403..0000000000 --- a/packages/rln/src/contract/test_utils.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { hexToBytes } from "@waku/utils/bytes"; -import { expect } from "chai"; -import * as ethers from "ethers"; -import sinon from "sinon"; - -import type { IdentityCredential } from "../identity.js"; - -import { DEFAULT_RATE_LIMIT, RLN_CONTRACT } from "./constants.js"; - -export const mockRateLimits = { - minRate: 20, - maxRate: 600, - maxTotalRate: 1200, - currentTotalRate: 500 -}; - -type MockProvider = { - getLogs: () => never[]; - getBlockNumber: () => Promise; - getNetwork: () => Promise<{ chainId: number }>; -}; - -type MockFilters = { - MembershipRegistered: () => { address: string }; - MembershipErased: () => { address: string }; - MembershipExpired: () => { address: string }; -}; - -export function createMockProvider(): MockProvider { - return { - getLogs: () => [], - getBlockNumber: () => Promise.resolve(1000), - getNetwork: () => Promise.resolve({ chainId: 11155111 }) - }; -} - -export function createMockFilters(): MockFilters { - return { - MembershipRegistered: () => ({ address: RLN_CONTRACT.address }), - MembershipErased: () => ({ address: RLN_CONTRACT.address }), - MembershipExpired: () => ({ address: RLN_CONTRACT.address }) - }; -} - -type ContractOverrides = Partial<{ - filters: Record; - [key: string]: unknown; -}>; - -export function createMockRegistryContract( - overrides: ContractOverrides = {} -): ethers.Contract { - const filters = { - MembershipRegistered: () => ({ address: RLN_CONTRACT.address }), - MembershipErased: () => ({ address: RLN_CONTRACT.address }), - MembershipExpired: () => ({ address: RLN_CONTRACT.address }) - }; - - const baseContract = { - minMembershipRateLimit: () => - Promise.resolve(ethers.BigNumber.from(mockRateLimits.minRate)), - maxMembershipRateLimit: () => - Promise.resolve(ethers.BigNumber.from(mockRateLimits.maxRate)), - maxTotalRateLimit: () => - Promise.resolve(ethers.BigNumber.from(mockRateLimits.maxTotalRate)), - currentTotalRateLimit: () => - Promise.resolve(ethers.BigNumber.from(mockRateLimits.currentTotalRate)), - queryFilter: () => [], - provider: createMockProvider(), - filters, - on: () => ({}), - removeAllListeners: () => ({}), - register: () => ({ - wait: () => - Promise.resolve({ - events: [mockRLNRegisteredEvent()] - }) - }), - estimateGas: { - register: () => Promise.resolve(ethers.BigNumber.from(100000)) - }, - functions: { - register: () => Promise.resolve() - }, - getMemberIndex: () => Promise.resolve(null), - interface: { - getEvent: (eventName: string) => ({ - name: eventName, - format: () => {} - }) - }, - address: RLN_CONTRACT.address - }; - - // Merge overrides while preserving filters - const merged = { - ...baseContract, - ...overrides, - filters: { ...filters, ...(overrides.filters || {}) } - }; - - return merged as unknown as ethers.Contract; -} - -export function mockRLNRegisteredEvent(idCommitment?: string): ethers.Event { - return { - args: { - idCommitment: - idCommitment || - "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - membershipRateLimit: ethers.BigNumber.from(DEFAULT_RATE_LIMIT), - index: ethers.BigNumber.from(1) - }, - event: "MembershipRegistered" - } as unknown as ethers.Event; -} - -export function formatIdCommitment(idCommitmentBigInt: bigint): string { - return "0x" + idCommitmentBigInt.toString(16).padStart(64, "0"); -} - -export function createRegisterStub( - identity: IdentityCredential -): sinon.SinonStub { - return sinon.stub().callsFake(() => ({ - wait: () => - Promise.resolve({ - events: [ - { - event: "MembershipRegistered", - args: { - idCommitment: formatIdCommitment(identity.IDCommitmentBigInt), - membershipRateLimit: ethers.BigNumber.from(DEFAULT_RATE_LIMIT), - index: ethers.BigNumber.from(1) - } - } - ] - }) - })); -} - -export function verifyRegistration( - decryptedCredentials: any, - identity: IdentityCredential, - registerStub: sinon.SinonStub, - insertMemberSpy: sinon.SinonStub -): void { - if (!decryptedCredentials) { - throw new Error("Decrypted credentials should not be undefined"); - } - - // Verify registration call - expect( - registerStub.calledWith( - sinon.match.same(identity.IDCommitmentBigInt), - sinon.match.same(DEFAULT_RATE_LIMIT), - sinon.match.array, - sinon.match.object - ) - ).to.be.true; - - // Verify credential properties - expect(decryptedCredentials).to.have.property("identity"); - expect(decryptedCredentials).to.have.property("membership"); - expect(decryptedCredentials.membership).to.include({ - address: RLN_CONTRACT.address, - treeIndex: 1 - }); - - // Verify member insertion - const expectedIdCommitment = ethers.utils.zeroPad( - hexToBytes(formatIdCommitment(identity.IDCommitmentBigInt)), - 32 - ); - expect(insertMemberSpy.callCount).to.equal(1); - expect(insertMemberSpy.getCall(0).args[0]).to.deep.equal( - expectedIdCommitment - ); -} diff --git a/packages/rln/src/create.spec.ts b/packages/rln/src/create.spec.ts deleted file mode 100644 index 7dc2e4fd5e..0000000000 --- a/packages/rln/src/create.spec.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { assert, expect } from "chai"; - -import { createRLN } from "./create.js"; - -describe("js-rln", () => { - it("should verify a proof", async function () { - const rlnInstance = await createRLN(); - - const credential = rlnInstance.zerokit.generateIdentityCredentials(); - - //peer's index in the Merkle Tree - const index = 5; - - // Create a Merkle tree with random members - for (let i = 0; i < 10; i++) { - if (i == index) { - // insert the current peer's pk - rlnInstance.zerokit.insertMember(credential.IDCommitment); - } else { - // create a new key pair - rlnInstance.zerokit.insertMember( - rlnInstance.zerokit.generateIdentityCredentials().IDCommitment - ); - } - } - - // prepare the message - const uint8Msg = Uint8Array.from( - "Hello World".split("").map((x) => x.charCodeAt(0)) - ); - - // setting up the epoch - const epoch = new Date(); - - // generating proof - const proof = await rlnInstance.zerokit.generateRLNProof( - uint8Msg, - index, - epoch, - credential.IDSecretHash - ); - - try { - // verify the proof - const verifResult = rlnInstance.zerokit.verifyRLNProof(proof, uint8Msg); - expect(verifResult).to.be.true; - } catch (err) { - assert.fail(0, 1, "should not have failed proof verification"); - } - - try { - // Modifying the signal so it's invalid - uint8Msg[4] = 4; - // verify the proof - const verifResult = rlnInstance.zerokit.verifyRLNProof(proof, uint8Msg); - expect(verifResult).to.be.false; - } catch (err) { - console.log(err); - } - }); - it("should verify a proof with a seeded membership key generation", async function () { - const rlnInstance = await createRLN(); - const seed = "This is a test seed"; - const credential = - rlnInstance.zerokit.generateSeededIdentityCredential(seed); - - //peer's index in the Merkle Tree - const index = 5; - - // Create a Merkle tree with random members - for (let i = 0; i < 10; i++) { - if (i == index) { - // insert the current peer's pk - rlnInstance.zerokit.insertMember(credential.IDCommitment); - } else { - // create a new key pair - rlnInstance.zerokit.insertMember( - rlnInstance.zerokit.generateIdentityCredentials().IDCommitment - ); - } - } - - // prepare the message - const uint8Msg = Uint8Array.from( - "Hello World".split("").map((x) => x.charCodeAt(0)) - ); - - // setting up the epoch - const epoch = new Date(); - - // generating proof - const proof = await rlnInstance.zerokit.generateRLNProof( - uint8Msg, - index, - epoch, - credential.IDSecretHash - ); - - try { - // verify the proof - const verifResult = rlnInstance.zerokit.verifyRLNProof(proof, uint8Msg); - expect(verifResult).to.be.true; - } catch (err) { - assert.fail(0, 1, "should not have failed proof verification"); - } - - try { - // Modifying the signal so it's invalid - uint8Msg[4] = 4; - // verify the proof - const verifResult = rlnInstance.zerokit.verifyRLNProof(proof, uint8Msg); - expect(verifResult).to.be.false; - } catch (err) { - console.log(err); - } - }); - - it("should generate the same membership key if the same seed is provided", async function () { - const rlnInstance = await createRLN(); - const seed = "This is a test seed"; - const memKeys1 = rlnInstance.zerokit.generateSeededIdentityCredential(seed); - const memKeys2 = rlnInstance.zerokit.generateSeededIdentityCredential(seed); - - memKeys1.IDCommitment.forEach((element, index) => { - expect(element).to.equal(memKeys2.IDCommitment[index]); - }); - memKeys1.IDNullifier.forEach((element, index) => { - expect(element).to.equal(memKeys2.IDNullifier[index]); - }); - memKeys1.IDSecretHash.forEach((element, index) => { - expect(element).to.equal(memKeys2.IDSecretHash[index]); - }); - memKeys1.IDTrapdoor.forEach((element, index) => { - expect(element).to.equal(memKeys2.IDTrapdoor[index]); - }); - }); -}); diff --git a/packages/rln/src/credentials_manager.ts b/packages/rln/src/credentials_manager.ts index c4fdceec70..b82d9421f1 100644 --- a/packages/rln/src/credentials_manager.ts +++ b/packages/rln/src/credentials_manager.ts @@ -1,11 +1,8 @@ -import { hmac } from "@noble/hashes/hmac"; -import { sha256 } from "@noble/hashes/sha2"; import { Logger } from "@waku/utils"; import { ethers } from "ethers"; -import { RLN_CONTRACT, RLN_Q } from "./contract/constants.js"; +import { RLN_CONTRACT } from "./contract/constants.js"; import { RLNBaseContract } from "./contract/rln_base_contract.js"; -import { IdentityCredential } from "./identity.js"; import { Keystore } from "./keystore/index.js"; import type { DecryptedCredentials, @@ -13,7 +10,6 @@ import type { } from "./keystore/index.js"; import { KeystoreEntity, Password } from "./keystore/types.js"; import { RegisterMembershipOptions, StartRLNOptions } from "./types.js"; -import { BytesUtils } from "./utils/bytes.js"; import { extractMetaMaskSigner } from "./utils/index.js"; import { Zerokit } from "./zerokit.js"; @@ -21,7 +17,6 @@ const log = new Logger("rln:credentials"); /** * Manages credentials for RLN - * This is a lightweight implementation of the RLN contract that doesn't require Zerokit * It is used to register membership and generate identity credentials */ export class RLNCredentialsManager { @@ -34,9 +29,9 @@ export class RLNCredentialsManager { protected keystore = Keystore.create(); public credentials: undefined | DecryptedCredentials; - public zerokit: undefined | Zerokit; + public zerokit: Zerokit; - public constructor(zerokit?: Zerokit) { + public constructor(zerokit: Zerokit) { log.info("RLNCredentialsManager initialized"); this.zerokit = zerokit; } @@ -81,7 +76,7 @@ export class RLNCredentialsManager { this.contract = await RLNBaseContract.create({ address: address!, signer: signer!, - rateLimit: rateLimit ?? this.zerokit?.rateLimit + rateLimit: rateLimit ?? this.zerokit.rateLimit }); log.info("RLNCredentialsManager successfully started"); @@ -106,18 +101,10 @@ export class RLNCredentialsManager { let identity = "identity" in options && options.identity; if ("signature" in options) { - log.info("Generating identity from signature"); - if (this.zerokit) { - log.info("Using Zerokit to generate identity"); - identity = this.zerokit.generateSeededIdentityCredential( - options.signature - ); - } else { - log.info("Using local implementation to generate identity"); - identity = await this.generateSeededIdentityCredential( - options.signature - ); - } + log.info("Using Zerokit to generate identity"); + identity = this.zerokit.generateSeededIdentityCredential( + options.signature + ); } if (!identity) { @@ -242,55 +229,4 @@ export class RLNCredentialsManager { ); } } - - /** - * Generates an identity credential from a seed string - * This is a pure implementation that doesn't rely on Zerokit - * @param seed A string seed to generate the identity from - * @returns IdentityCredential - */ - private async generateSeededIdentityCredential( - seed: string - ): Promise { - log.info("Generating seeded identity credential"); - // Convert the seed to bytes - const encoder = new TextEncoder(); - const seedBytes = encoder.encode(seed); - - // Generate deterministic values using HMAC-SHA256 - // We use different context strings for each component to ensure they're different - const idTrapdoorBE = hmac(sha256, seedBytes, encoder.encode("IDTrapdoor")); - const idNullifierBE = hmac( - sha256, - seedBytes, - encoder.encode("IDNullifier") - ); - - const combinedBytes = new Uint8Array([...idTrapdoorBE, ...idNullifierBE]); - const idSecretHashBE = sha256(combinedBytes); - - const idCommitmentRawBE = sha256(idSecretHashBE); - const idCommitmentBE = this.reduceIdCommitment(idCommitmentRawBE); - - log.info( - "Successfully generated identity credential, storing in Big Endian format" - ); - return new IdentityCredential( - idTrapdoorBE, - idNullifierBE, - idSecretHashBE, - idCommitmentBE - ); - } - - /** - * Helper: take 32-byte BE, reduce mod Q, return 32-byte BE - */ - private reduceIdCommitment( - bytesBE: Uint8Array, - limit: bigint = RLN_Q - ): Uint8Array { - const nBE = BytesUtils.buildBigIntFromUint8ArrayBE(bytesBE); - return BytesUtils.bigIntToUint8Array32BE(nBE % limit); - } } diff --git a/packages/rln/src/identity.ts b/packages/rln/src/identity.ts index 87167165ba..873463ea6e 100644 --- a/packages/rln/src/identity.ts +++ b/packages/rln/src/identity.ts @@ -11,8 +11,7 @@ export class IdentityCredential { public readonly IDSecretHash: Uint8Array, public readonly IDCommitment: Uint8Array ) { - this.IDCommitmentBigInt = - BytesUtils.buildBigIntFromUint8ArrayBE(IDCommitment); + this.IDCommitmentBigInt = BytesUtils.toBigInt(IDCommitment); } public static fromBytes(memKeys: Uint8Array): IdentityCredential { diff --git a/packages/rln/src/index.ts b/packages/rln/src/index.ts index 0a07db7810..3348e370f7 100644 --- a/packages/rln/src/index.ts +++ b/packages/rln/src/index.ts @@ -1,28 +1,18 @@ -import { RLNDecoder, RLNEncoder } from "./codec.js"; import { RLN_ABI } from "./contract/abi/rln.js"; -import { RLN_CONTRACT, RLNContract } from "./contract/index.js"; +import { RLN_CONTRACT } from "./contract/index.js"; import { RLNBaseContract } from "./contract/rln_base_contract.js"; import { createRLN } from "./create.js"; -import { RLNCredentialsManager } from "./credentials_manager.js"; import { IdentityCredential } from "./identity.js"; import { Keystore } from "./keystore/index.js"; -import { Proof } from "./proof.js"; import { RLNInstance } from "./rln.js"; -import { MerkleRootTracker } from "./root_tracker.js"; import { extractMetaMaskSigner } from "./utils/index.js"; export { - RLNCredentialsManager, RLNBaseContract, createRLN, Keystore, RLNInstance, IdentityCredential, - Proof, - RLNEncoder, - RLNDecoder, - MerkleRootTracker, - RLNContract, RLN_CONTRACT, extractMetaMaskSigner, RLN_ABI diff --git a/packages/rln/src/keystore/keystore.spec.ts b/packages/rln/src/keystore/keystore.spec.ts index 02f2f20bd3..95543e8535 100644 --- a/packages/rln/src/keystore/keystore.spec.ts +++ b/packages/rln/src/keystore/keystore.spec.ts @@ -222,9 +222,7 @@ describe("Keystore", () => { ]) } as unknown as IdentityCredential; // Add the missing property for test correctness - identity.IDCommitmentBigInt = BytesUtils.buildBigIntFromUint8ArrayBE( - identity.IDCommitment - ); + identity.IDCommitmentBigInt = BytesUtils.toBigInt(identity.IDCommitment); const membership = { chainId: "0xAA36A7", treeIndex: 8, @@ -276,9 +274,7 @@ describe("Keystore", () => { 58, 94, 20, 246, 8, 33, 65, 238, 37, 112, 97, 65, 241, 255, 93, 171, 15 ] } as unknown as IdentityCredential; - identity.IDCommitmentBigInt = BytesUtils.buildBigIntFromUint8ArrayBE( - identity.IDCommitment - ); + identity.IDCommitmentBigInt = BytesUtils.toBigInt(identity.IDCommitment); const membership = { chainId: "0xAA36A7", treeIndex: 8, diff --git a/packages/rln/src/keystore/keystore.ts b/packages/rln/src/keystore/keystore.ts index 880d707bf3..f001f2938d 100644 --- a/packages/rln/src/keystore/keystore.ts +++ b/packages/rln/src/keystore/keystore.ts @@ -264,20 +264,14 @@ export class Keystore { _.get(obj, "identityCredential.idSecretHash", []) ); - // Big Endian - const idCommitmentBE = BytesUtils.switchEndianness(idCommitmentLE); - const idTrapdoorBE = BytesUtils.switchEndianness(idTrapdoorLE); - const idNullifierBE = BytesUtils.switchEndianness(idNullifierLE); - const idSecretHashBE = BytesUtils.switchEndianness(idSecretHashLE); - const idCommitmentBigInt = - BytesUtils.buildBigIntFromUint8ArrayBE(idCommitmentBE); + const idCommitmentBigInt = BytesUtils.toBigInt(idCommitmentLE); return { identity: { - IDCommitment: idCommitmentBE, - IDTrapdoor: idTrapdoorBE, - IDNullifier: idNullifierBE, - IDSecretHash: idSecretHashBE, + IDCommitment: idCommitmentLE, + IDTrapdoor: idTrapdoorLE, + IDNullifier: idNullifierLE, + IDSecretHash: idSecretHashLE, IDCommitmentBigInt: idCommitmentBigInt }, membership: { @@ -329,35 +323,18 @@ export class Keystore { // follows nwaku implementation // https://github.com/waku-org/nwaku/blob/f05528d4be3d3c876a8b07f9bb7dfaae8aa8ec6e/waku/waku_keystore/protocol_types.nim#L98 - // IdentityCredential is stored in Big Endian format => switch to Little Endian private static fromIdentityToBytes(options: KeystoreEntity): Uint8Array { const { IDCommitment, IDNullifier, IDSecretHash, IDTrapdoor } = options.identity; - const idCommitmentLE = BytesUtils.switchEndianness(IDCommitment); - const idNullifierLE = BytesUtils.switchEndianness(IDNullifier); - const idSecretHashLE = BytesUtils.switchEndianness(IDSecretHash); - const idTrapdoorLE = BytesUtils.switchEndianness(IDTrapdoor); - - // eslint-disable-next-line no-console - console.log({ - idCommitmentBE: IDCommitment, - idCommitmentLE, - idNullifierBE: IDNullifier, - idNullifierLE, - idSecretHashBE: IDSecretHash, - idSecretHashLE, - idTrapdoorBE: IDTrapdoor, - idTrapdoorLE - }); return utf8ToBytes( JSON.stringify({ treeIndex: options.membership.treeIndex, identityCredential: { - idCommitment: Array.from(idCommitmentLE), - idNullifier: Array.from(idNullifierLE), - idSecretHash: Array.from(idSecretHashLE), - idTrapdoor: Array.from(idTrapdoorLE) + idCommitment: Array.from(IDCommitment), + idNullifier: Array.from(IDNullifier), + idSecretHash: Array.from(IDSecretHash), + idTrapdoor: Array.from(IDTrapdoor) }, membershipContract: { chainId: options.membership.chainId, diff --git a/packages/rln/src/message.ts b/packages/rln/src/message.ts deleted file mode 100644 index 1cca8a4ed2..0000000000 --- a/packages/rln/src/message.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { message } from "@waku/core"; -import type { - IDecodedMessage, - IMessage, - IRateLimitProof, - IRlnMessage -} from "@waku/interfaces"; -import * as utils from "@waku/utils/bytes"; - -import { RLNInstance } from "./rln.js"; -import { epochBytesToInt } from "./utils/index.js"; - -export function toRLNSignal(contentTopic: string, msg: IMessage): Uint8Array { - const contentTopicBytes = utils.utf8ToBytes(contentTopic ?? ""); - return new Uint8Array([...(msg.payload ?? []), ...contentTopicBytes]); -} - -export class RlnMessage implements IRlnMessage { - public pubsubTopic = ""; - public version = message.version_0.Version; - - public constructor( - private rlnInstance: RLNInstance, - private msg: T, - public rateLimitProof: IRateLimitProof | undefined - ) {} - - public verify(roots: Uint8Array[]): boolean | undefined { - return this.rateLimitProof - ? this.rlnInstance.zerokit.verifyWithRoots( - this.rateLimitProof, - toRLNSignal(this.msg.contentTopic, this.msg), - roots - ) // this.rlnInstance.verifyRLNProof once issue status-im/nwaku#1248 is fixed - : undefined; - } - - public verifyNoRoot(): boolean | undefined { - return this.rateLimitProof - ? this.rlnInstance.zerokit.verifyWithNoRoot( - this.rateLimitProof, - toRLNSignal(this.msg.contentTopic, this.msg) - ) // this.rlnInstance.verifyRLNProof once issue status-im/nwaku#1248 is fixed - : undefined; - } - - public get payload(): Uint8Array { - return this.msg.payload; - } - - public get hash(): Uint8Array { - return this.msg.hash; - } - - public get hashStr(): string { - return this.msg.hashStr; - } - - public get contentTopic(): string { - return this.msg.contentTopic; - } - - public get timestamp(): Date | undefined { - return this.msg.timestamp; - } - - public get ephemeral(): boolean | undefined { - return this.msg.ephemeral; - } - - public get meta(): Uint8Array | undefined { - return this.msg.meta; - } - - public get epoch(): number | undefined { - const bytes = this.rateLimitProof?.epoch; - if (!bytes) return undefined; - - return epochBytesToInt(bytes); - } -} diff --git a/packages/rln/src/proof.ts b/packages/rln/src/proof.ts deleted file mode 100644 index ce3ad6f26d..0000000000 --- a/packages/rln/src/proof.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { IRateLimitProof } from "@waku/interfaces"; - -import { BytesUtils, poseidonHash } from "./utils/index.js"; - -const proofOffset = 128; -const rootOffset = proofOffset + 32; -const epochOffset = rootOffset + 32; -const shareXOffset = epochOffset + 32; -const shareYOffset = shareXOffset + 32; -const nullifierOffset = shareYOffset + 32; -const rlnIdentifierOffset = nullifierOffset + 32; - -class ProofMetadata { - public constructor( - public readonly nullifier: Uint8Array, - public readonly shareX: Uint8Array, - public readonly shareY: Uint8Array, - public readonly externalNullifier: Uint8Array - ) {} -} - -export class Proof implements IRateLimitProof { - public readonly proof: Uint8Array; - public readonly merkleRoot: Uint8Array; - public readonly epoch: Uint8Array; - public readonly shareX: Uint8Array; - public readonly shareY: Uint8Array; - public readonly nullifier: Uint8Array; - public readonly rlnIdentifier: Uint8Array; - - public constructor(proofBytes: Uint8Array) { - if (proofBytes.length < rlnIdentifierOffset) { - throw new Error("invalid proof"); - } - // parse the proof as proof<128> | share_y<32> | nullifier<32> | root<32> | epoch<32> | share_x<32> | rln_identifier<32> - this.proof = proofBytes.subarray(0, proofOffset); - this.merkleRoot = proofBytes.subarray(proofOffset, rootOffset); - this.epoch = proofBytes.subarray(rootOffset, epochOffset); - this.shareX = proofBytes.subarray(epochOffset, shareXOffset); - this.shareY = proofBytes.subarray(shareXOffset, shareYOffset); - this.nullifier = proofBytes.subarray(shareYOffset, nullifierOffset); - this.rlnIdentifier = proofBytes.subarray( - nullifierOffset, - rlnIdentifierOffset - ); - } - - public extractMetadata(): ProofMetadata { - const externalNullifier = poseidonHash(this.epoch, this.rlnIdentifier); - return new ProofMetadata( - this.nullifier, - this.shareX, - this.shareY, - externalNullifier - ); - } -} - -export function proofToBytes(p: IRateLimitProof): Uint8Array { - return BytesUtils.concatenate( - p.proof, - p.merkleRoot, - p.epoch, - p.shareX, - p.shareY, - p.nullifier, - p.rlnIdentifier - ); -} diff --git a/packages/rln/src/resources/rln.wasm b/packages/rln/src/resources/rln.wasm index 04aaeef783..ddca70ce2e 100644 Binary files a/packages/rln/src/resources/rln.wasm and b/packages/rln/src/resources/rln.wasm differ diff --git a/packages/rln/src/resources/rln_final.zkey b/packages/rln/src/resources/rln_final.zkey index c6cc7d491a..46489a11c0 100644 Binary files a/packages/rln/src/resources/rln_final.zkey and b/packages/rln/src/resources/rln_final.zkey differ diff --git a/packages/rln/src/resources/verification_key.d.ts b/packages/rln/src/resources/verification_key.d.ts deleted file mode 100644 index 99ec4f67d1..0000000000 --- a/packages/rln/src/resources/verification_key.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -declare const verificationKey: { - protocol: string; - curve: string; - nPublic: number; - vk_alpha_1: string[]; - vk_beta_2: string[][]; - vk_gamma_2: string[][]; - vk_delta_2: string[][]; - vk_alphabeta_12: string[][][]; - IC: string[][]; -}; - -export default verificationKey; diff --git a/packages/rln/src/resources/verification_key.js b/packages/rln/src/resources/verification_key.js deleted file mode 100644 index 15425bef8c..0000000000 --- a/packages/rln/src/resources/verification_key.js +++ /dev/null @@ -1,112 +0,0 @@ -const verificationKey = { - protocol: "groth16", - curve: "bn128", - nPublic: 6, - vk_alpha_1: [ - "20124996762962216725442980738609010303800849578410091356605067053491763969391", - "9118593021526896828671519912099489027245924097793322973632351264852174143923", - "1" - ], - vk_beta_2: [ - [ - "4693952934005375501364248788849686435240706020501681709396105298107971354382", - "14346958885444710485362620645446987998958218205939139994511461437152241966681" - ], - [ - "16851772916911573982706166384196538392731905827088356034885868448550849804972", - "823612331030938060799959717749043047845343400798220427319188951998582076532" - ], - ["1", "0"] - ], - vk_gamma_2: [ - [ - "10857046999023057135944570762232829481370756359578518086990519993285655852781", - "11559732032986387107991004021392285783925812861821192530917403151452391805634" - ], - [ - "8495653923123431417604973247489272438418190587263600148770280649306958101930", - "4082367875863433681332203403145435568316851327593401208105741076214120093531" - ], - ["1", "0"] - ], - vk_delta_2: [ - [ - "8353516066399360694538747105302262515182301251524941126222712285088022964076", - "9329524012539638256356482961742014315122377605267454801030953882967973561832" - ], - [ - "16805391589556134376869247619848130874761233086443465978238468412168162326401", - "10111259694977636294287802909665108497237922060047080343914303287629927847739" - ], - ["1", "0"] - ], - vk_alphabeta_12: [ - [ - [ - "12608968655665301215455851857466367636344427685631271961542642719683786103711", - "9849575605876329747382930567422916152871921500826003490242628251047652318086" - ], - [ - "6322029441245076030714726551623552073612922718416871603535535085523083939021", - "8700115492541474338049149013125102281865518624059015445617546140629435818912" - ], - [ - "10674973475340072635573101639867487770811074181475255667220644196793546640210", - "2926286967251299230490668407790788696102889214647256022788211245826267484824" - ] - ], - [ - [ - "9660441540778523475944706619139394922744328902833875392144658911530830074820", - "19548113127774514328631808547691096362144426239827206966690021428110281506546" - ], - [ - "1870837942477655969123169532603615788122896469891695773961478956740992497097", - "12536105729661705698805725105036536744930776470051238187456307227425796690780" - ], - [ - "21811903352654147452884857281720047789720483752548991551595462057142824037334", - "19021616763967199151052893283384285352200445499680068407023236283004353578353" - ] - ] - ], - IC: [ - [ - "11992897507809711711025355300535923222599547639134311050809253678876341466909", - "17181525095924075896332561978747020491074338784673526378866503154966799128110", - "1" - ], - [ - "17018665030246167677911144513385572506766200776123272044534328594850561667818", - "18601114175490465275436712413925513066546725461375425769709566180981674884464", - "1" - ], - [ - "18799470100699658367834559797874857804183288553462108031963980039244731716542", - "13064227487174191981628537974951887429496059857753101852163607049188825592007", - "1" - ], - [ - "17432501889058124609368103715904104425610382063762621017593209214189134571156", - "13406815149699834788256141097399354592751313348962590382887503595131085938635", - "1" - ], - [ - "10320964835612716439094703312987075811498239445882526576970512041988148264481", - "9024164961646353611176283204118089412001502110138072989569118393359029324867", - "1" - ], - [ - "718355081067365548229685160476620267257521491773976402837645005858953849298", - "14635482993933988261008156660773180150752190597753512086153001683711587601974", - "1" - ], - [ - "11777720285956632126519898515392071627539405001940313098390150593689568177535", - "8483603647274280691250972408211651407952870456587066148445913156086740744515", - "1" - ] - ] -}; - -export default verificationKey; diff --git a/packages/rln/src/resources/witness_calculator.d.ts b/packages/rln/src/resources/witness_calculator.d.ts index eb6f86aab9..a54b1d62c8 100644 --- a/packages/rln/src/resources/witness_calculator.d.ts +++ b/packages/rln/src/resources/witness_calculator.d.ts @@ -1,11 +1,25 @@ -export async function builder( +export const builder: ( code: Uint8Array, - sanityCheck: boolean -): Promise; + sanityCheck?: boolean +) => Promise; export class WitnessCalculator { - public calculateWitness( - input: unknown, - sanityCheck: boolean - ): Promise>; + constructor(instance: any, sanityCheck?: boolean); + + circom_version(): number; + + calculateWitness( + input: Record, + sanityCheck?: boolean + ): Promise; + + calculateBinWitness( + input: Record, + sanityCheck?: boolean + ): Promise; + + calculateWTNSBin( + input: Record, + sanityCheck?: boolean + ): Promise; } diff --git a/packages/rln/src/resources/witness_calculator.js b/packages/rln/src/resources/witness_calculator.js index 47b218d881..1befc32e98 100644 --- a/packages/rln/src/resources/witness_calculator.js +++ b/packages/rln/src/resources/witness_calculator.js @@ -1,6 +1,6 @@ // File generated with https://github.com/iden3/circom // following the instructions from: -// https://github.com/vacp2p/zerokit/tree/master/rln#compiling-circuits +// https://github.com/vacp2p/zerokit/tree/master/rln#advanced-custom-circuit-compilation export async function builder(code, options) { options = options || {}; diff --git a/packages/rln/src/rln.ts b/packages/rln/src/rln.ts index 00e7eaa249..491fd14d44 100644 --- a/packages/rln/src/rln.ts +++ b/packages/rln/src/rln.ts @@ -1,37 +1,14 @@ -import { createDecoder, createEncoder } from "@waku/core"; -import type { - ContentTopic, - IDecodedMessage, - IRoutingInfo, - EncoderOptions as WakuEncoderOptions -} from "@waku/interfaces"; import { Logger } from "@waku/utils"; -import init from "@waku/zerokit-rln-wasm"; -import * as zerokitRLN from "@waku/zerokit-rln-wasm"; +import init, * as zerokitRLN from "@waku/zerokit-rln-wasm"; -import { - createRLNDecoder, - createRLNEncoder, - type RLNDecoder, - type RLNEncoder -} from "./codec.js"; import { DEFAULT_RATE_LIMIT } from "./contract/constants.js"; import { RLNCredentialsManager } from "./credentials_manager.js"; -import type { - DecryptedCredentials, - EncryptedCredentials -} from "./keystore/index.js"; -import verificationKey from "./resources/verification_key"; import * as wc from "./resources/witness_calculator"; import { WitnessCalculator } from "./resources/witness_calculator"; import { Zerokit } from "./zerokit.js"; const log = new Logger("rln"); -type WakuRLNEncoderOptions = WakuEncoderOptions & { - credentials: EncryptedCredentials | DecryptedCredentials; -}; - export class RLNInstance extends RLNCredentialsManager { /** * Create an instance of RLN @@ -39,18 +16,13 @@ export class RLNInstance extends RLNCredentialsManager { */ public static async create(): Promise { try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - await (init as any)?.(); - zerokitRLN.init_panic_hook(); + await init(); + zerokitRLN.initPanicHook(); const witnessCalculator = await RLNInstance.loadWitnessCalculator(); const zkey = await RLNInstance.loadZkey(); - const stringEncoder = new TextEncoder(); - const vkey = stringEncoder.encode(JSON.stringify(verificationKey)); - - const DEPTH = 20; - const zkRLN = zerokitRLN.newRLN(DEPTH, zkey, vkey); + const zkRLN = zerokitRLN.newRLN(zkey); const zerokit = new Zerokit(zkRLN, witnessCalculator, DEFAULT_RATE_LIMIT); return new RLNInstance(zerokit); @@ -64,39 +36,6 @@ export class RLNInstance extends RLNCredentialsManager { super(zerokit); } - public async createEncoder( - options: WakuRLNEncoderOptions - ): Promise { - const { credentials: decryptedCredentials } = - await RLNInstance.decryptCredentialsIfNeeded(options.credentials); - const credentials = decryptedCredentials || this.credentials; - - if (!credentials) { - throw Error( - "Failed to create Encoder: missing RLN credentials. Use createRLNEncoder directly." - ); - } - - await this.verifyCredentialsAgainstContract(credentials); - - return createRLNEncoder({ - encoder: createEncoder(options), - rlnInstance: this, - index: credentials.membership.treeIndex, - credential: credentials.identity - }); - } - - public createDecoder( - contentTopic: ContentTopic, - routingInfo: IRoutingInfo - ): RLNDecoder { - return createRLNDecoder({ - rlnInstance: this, - decoder: createDecoder(contentTopic, routingInfo) - }); - } - public static async loadWitnessCalculator(): Promise { try { const url = new URL("./resources/rln.wasm", import.meta.url); diff --git a/packages/rln/src/root_tracker.spec.ts b/packages/rln/src/root_tracker.spec.ts deleted file mode 100644 index 8e3c03efe0..0000000000 --- a/packages/rln/src/root_tracker.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { assert, expect } from "chai"; - -import { MerkleRootTracker } from "./root_tracker.js"; - -describe("js-rln", () => { - it("should track merkle roots and backfill from block number", async function () { - const acceptableRootWindow = 3; - - const tracker = new MerkleRootTracker( - acceptableRootWindow, - new Uint8Array([0, 0, 0, 0]) - ); - expect(tracker.roots()).to.have.length(1); - expect(tracker.buffer()).to.have.length(0); - expect(tracker.roots()[0]).to.deep.equal(new Uint8Array([0, 0, 0, 0])); - - for (let i = 1; i <= 30; i++) { - tracker.pushRoot(i, new Uint8Array([0, 0, 0, i])); - } - - expect(tracker.roots()).to.have.length(acceptableRootWindow); - expect(tracker.buffer()).to.have.length(20); - assert.sameDeepMembers(tracker.roots(), [ - new Uint8Array([0, 0, 0, 30]), - new Uint8Array([0, 0, 0, 29]), - new Uint8Array([0, 0, 0, 28]) - ]); - - // Buffer should keep track of 20 blocks previous to the current valid merkle root window - expect(tracker.buffer()[0]).to.be.eql(new Uint8Array([0, 0, 0, 8])); - expect(tracker.buffer()[19]).to.be.eql(new Uint8Array([0, 0, 0, 27])); - - // Remove roots 29 and 30 - tracker.backFill(29); - assert.sameDeepMembers(tracker.roots(), [ - new Uint8Array([0, 0, 0, 28]), - new Uint8Array([0, 0, 0, 27]), - new Uint8Array([0, 0, 0, 26]) - ]); - - expect(tracker.buffer()).to.have.length(18); - expect(tracker.buffer()[0]).to.be.eql(new Uint8Array([0, 0, 0, 8])); - expect(tracker.buffer()[17]).to.be.eql(new Uint8Array([0, 0, 0, 25])); - - // Remove roots from block 15 onwards. These blocks exists within the buffer - tracker.backFill(15); - assert.sameDeepMembers(tracker.roots(), [ - new Uint8Array([0, 0, 0, 14]), - new Uint8Array([0, 0, 0, 13]), - new Uint8Array([0, 0, 0, 12]) - ]); - expect(tracker.buffer()).to.have.length(4); - expect(tracker.buffer()[0]).to.be.eql(new Uint8Array([0, 0, 0, 8])); - expect(tracker.buffer()[3]).to.be.eql(new Uint8Array([0, 0, 0, 11])); - }); -}); diff --git a/packages/rln/src/root_tracker.ts b/packages/rln/src/root_tracker.ts deleted file mode 100644 index 79b71845c1..0000000000 --- a/packages/rln/src/root_tracker.ts +++ /dev/null @@ -1,92 +0,0 @@ -class RootPerBlock { - public constructor( - public root: Uint8Array, - public blockNumber: number - ) {} -} - -const maxBufferSize = 20; - -export class MerkleRootTracker { - private validMerkleRoots: Array = new Array(); - private merkleRootBuffer: Array = new Array(); - - public constructor( - private acceptableRootWindowSize: number, - initialRoot: Uint8Array - ) { - this.pushRoot(0, initialRoot); - } - - public backFill(fromBlockNumber: number): void { - if (this.validMerkleRoots.length == 0) return; - - let numBlocks = 0; - for (let i = this.validMerkleRoots.length - 1; i >= 0; i--) { - if (this.validMerkleRoots[i].blockNumber >= fromBlockNumber) { - numBlocks++; - } - } - - if (numBlocks == 0) return; - - const olderBlock = fromBlockNumber < this.validMerkleRoots[0].blockNumber; - - // Remove last roots - let rootsToPop = numBlocks; - if (this.validMerkleRoots.length < rootsToPop) { - rootsToPop = this.validMerkleRoots.length; - } - - this.validMerkleRoots = this.validMerkleRoots.slice( - 0, - this.validMerkleRoots.length - rootsToPop - ); - - if (this.merkleRootBuffer.length == 0) return; - - if (olderBlock) { - const idx = this.merkleRootBuffer.findIndex( - (x) => x.blockNumber == fromBlockNumber - ); - if (idx > -1) { - this.merkleRootBuffer = this.merkleRootBuffer.slice(0, idx); - } - } - - // Backfill the tree's acceptable roots - let rootsToRestore = - this.acceptableRootWindowSize - this.validMerkleRoots.length; - if (this.merkleRootBuffer.length < rootsToRestore) { - rootsToRestore = this.merkleRootBuffer.length; - } - - for (let i = 0; i < rootsToRestore; i++) { - const x = this.merkleRootBuffer.pop(); - if (x) this.validMerkleRoots.unshift(x); - } - } - - public pushRoot(blockNumber: number, root: Uint8Array): void { - this.validMerkleRoots.push(new RootPerBlock(root, blockNumber)); - - // Maintain valid merkle root window - if (this.validMerkleRoots.length > this.acceptableRootWindowSize) { - const x = this.validMerkleRoots.shift(); - if (x) this.merkleRootBuffer.push(x); - } - - // Maintain merkle root buffer - if (this.merkleRootBuffer.length > maxBufferSize) { - this.merkleRootBuffer.shift(); - } - } - - public roots(): Array { - return this.validMerkleRoots.map((x) => x.root); - } - - public buffer(): Array { - return this.merkleRootBuffer.map((x) => x.root); - } -} diff --git a/packages/rln/src/utils/bytes.ts b/packages/rln/src/utils/bytes.ts index d871e8f564..4df17bd380 100644 --- a/packages/rln/src/utils/bytes.ts +++ b/packages/rln/src/utils/bytes.ts @@ -1,56 +1,52 @@ export class BytesUtils { /** - * Switches endianness of a byte array + * Concatenate Uint8Arrays + * @param input + * @returns concatenation of all Uint8Array received as input */ - public static switchEndianness(bytes: Uint8Array): Uint8Array { - return new Uint8Array([...bytes].reverse()); - } - - /** - * Builds a BigInt from a big-endian Uint8Array - * @param bytes The big-endian bytes to convert - * @returns The resulting BigInt in big-endian format - */ - public static buildBigIntFromUint8ArrayBE(bytes: Uint8Array): bigint { - let result = 0n; - for (let i = 0; i < bytes.length; i++) { - result = (result << 8n) + BigInt(bytes[i]); + public static concatenate(...input: Uint8Array[]): Uint8Array { + let totalLength = 0; + for (const arr of input) { + totalLength += arr.length; + } + const result = new Uint8Array(totalLength); + let offset = 0; + for (const arr of input) { + result.set(arr, offset); + offset += arr.length; } return result; } /** - * Switches endianness of a bigint value - * @param value The bigint value to switch endianness for - * @returns The bigint value with reversed endianness + * Convert a Uint8Array to a BigInt with configurable input endianness + * @param bytes - The byte array to convert + * @param inputEndianness - Endianness of the input bytes ('big' or 'little') + * @returns BigInt representation of the bytes */ - public static switchEndiannessBigInt(value: bigint): bigint { - // Convert bigint to byte array - const bytes = []; - let tempValue = value; - while (tempValue > 0n) { - bytes.push(Number(tempValue & 0xffn)); - tempValue >>= 8n; + public static toBigInt( + bytes: Uint8Array, + inputEndianness: "big" | "little" = "little" + ): bigint { + if (bytes.length === 0) { + return 0n; } - // Reverse bytes and convert back to bigint - return bytes - .reverse() - .reduce((acc, byte) => (acc << 8n) + BigInt(byte), 0n); - } + // Create a copy to avoid modifying the original array + const workingBytes = new Uint8Array(bytes); - /** - * Converts a big-endian bigint to a 32-byte big-endian Uint8Array - * @param value The big-endian bigint to convert - * @returns A 32-byte big-endian Uint8Array - */ - public static bigIntToUint8Array32BE(value: bigint): Uint8Array { - const bytes = new Uint8Array(32); - for (let i = 31; i >= 0; i--) { - bytes[i] = Number(value & 0xffn); - value >>= 8n; + // Reverse bytes if input is little-endian to work with big-endian internally + if (inputEndianness === "little") { + workingBytes.reverse(); } - return bytes; + + // Convert to BigInt + let result = 0n; + for (let i = 0; i < workingBytes.length; i++) { + result = (result << 8n) | BigInt(workingBytes[i]); + } + + return result; } /** @@ -81,20 +77,6 @@ export class BytesUtils { return buf; } - /** - * Fills with zeros to set length - * @param array little endian Uint8Array - * @param length amount to pad - * @returns little endian Uint8Array padded with zeros to set length - */ - public static zeroPadLE(array: Uint8Array, length: number): Uint8Array { - const result = new Uint8Array(length); - for (let i = 0; i < length; i++) { - result[i] = array[i] || 0; - } - return result; - } - // Adapted from https://github.com/feross/buffer public static checkInt( buf: Uint8Array, @@ -108,23 +90,4 @@ export class BytesUtils { throw new RangeError('"value" argument is out of bounds'); if (offset + ext > buf.length) throw new RangeError("Index out of range"); } - - /** - * Concatenate Uint8Arrays - * @param input - * @returns concatenation of all Uint8Array received as input - */ - public static concatenate(...input: Uint8Array[]): Uint8Array { - let totalLength = 0; - for (const arr of input) { - totalLength += arr.length; - } - const result = new Uint8Array(totalLength); - let offset = 0; - for (const arr of input) { - result.set(arr, offset); - offset += arr.length; - } - return result; - } } diff --git a/packages/rln/src/zerokit.spec.ts b/packages/rln/src/zerokit.spec.ts new file mode 100644 index 0000000000..6126c7c6ac --- /dev/null +++ b/packages/rln/src/zerokit.spec.ts @@ -0,0 +1,26 @@ +import { expect } from "chai"; + +import { RLNInstance } from "./rln.js"; + +describe("@waku/rln", () => { + it("should generate the same membership key if the same seed is provided", async function () { + const rlnInstance = await RLNInstance.create(); + + const seed = "This is a test seed"; + const memKeys1 = rlnInstance.zerokit.generateSeededIdentityCredential(seed); + const memKeys2 = rlnInstance.zerokit.generateSeededIdentityCredential(seed); + + memKeys1.IDCommitment.forEach((element, index) => { + expect(element).to.equal(memKeys2.IDCommitment[index]); + }); + memKeys1.IDNullifier.forEach((element, index) => { + expect(element).to.equal(memKeys2.IDNullifier[index]); + }); + memKeys1.IDSecretHash.forEach((element, index) => { + expect(element).to.equal(memKeys2.IDSecretHash[index]); + }); + memKeys1.IDTrapdoor.forEach((element, index) => { + expect(element).to.equal(memKeys2.IDTrapdoor[index]); + }); + }); +}); diff --git a/packages/rln/src/zerokit.ts b/packages/rln/src/zerokit.ts index a7e7e628f4..47df182f00 100644 --- a/packages/rln/src/zerokit.ts +++ b/packages/rln/src/zerokit.ts @@ -1,11 +1,8 @@ -import type { IRateLimitProof } from "@waku/interfaces"; import * as zerokitRLN from "@waku/zerokit-rln-wasm"; -import { DEFAULT_RATE_LIMIT, RATE_LIMIT_PARAMS } from "./contract/constants.js"; +import { DEFAULT_RATE_LIMIT } from "./contract/constants.js"; import { IdentityCredential } from "./identity.js"; -import { Proof, proofToBytes } from "./proof.js"; import { WitnessCalculator } from "./resources/witness_calculator"; -import { BytesUtils, dateToEpoch, epochIntToBytes } from "./utils/index.js"; export class Zerokit { public constructor( @@ -26,226 +23,13 @@ export class Zerokit { return this._rateLimit; } - public generateIdentityCredentials(): IdentityCredential { - const memKeys = zerokitRLN.generateExtendedMembershipKey(this.zkRLN); // TODO: rename this function in zerokit rln-wasm - return IdentityCredential.fromBytes(memKeys); - } - public generateSeededIdentityCredential(seed: string): IdentityCredential { const stringEncoder = new TextEncoder(); const seedBytes = stringEncoder.encode(seed); - // TODO: rename this function in zerokit rln-wasm const memKeys = zerokitRLN.generateSeededExtendedMembershipKey( this.zkRLN, seedBytes ); return IdentityCredential.fromBytes(memKeys); } - - public insertMember(idCommitment: Uint8Array): void { - zerokitRLN.insertMember(this.zkRLN, idCommitment); - } - - public insertMembers( - index: number, - ...idCommitments: Array - ): void { - // serializes a seq of IDCommitments to a byte seq - // the order of serialization is |id_commitment_len<8>|id_commitment| - const idCommitmentLen = BytesUtils.writeUIntLE( - new Uint8Array(8), - idCommitments.length, - 0, - 8 - ); - const idCommitmentBytes = BytesUtils.concatenate( - idCommitmentLen, - ...idCommitments - ); - zerokitRLN.setLeavesFrom(this.zkRLN, index, idCommitmentBytes); - } - - public deleteMember(index: number): void { - zerokitRLN.deleteLeaf(this.zkRLN, index); - } - - public getMerkleRoot(): Uint8Array { - return zerokitRLN.getRoot(this.zkRLN); - } - - public serializeMessage( - uint8Msg: Uint8Array, - memIndex: number, - epoch: Uint8Array, - idKey: Uint8Array, - rateLimit?: number - ): Uint8Array { - // calculate message length - const msgLen = BytesUtils.writeUIntLE( - new Uint8Array(8), - uint8Msg.length, - 0, - 8 - ); - const memIndexBytes = BytesUtils.writeUIntLE( - new Uint8Array(8), - memIndex, - 0, - 8 - ); - const rateLimitBytes = BytesUtils.writeUIntLE( - new Uint8Array(8), - rateLimit ?? this.rateLimit, - 0, - 8 - ); - - // [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal | rate_limit<8> ] - return BytesUtils.concatenate( - idKey, - memIndexBytes, - epoch, - msgLen, - uint8Msg, - rateLimitBytes - ); - } - - public async generateRLNProof( - msg: Uint8Array, - index: number, - epoch: Uint8Array | Date | undefined, - idSecretHash: Uint8Array, - rateLimit?: number - ): Promise { - if (epoch === undefined) { - epoch = epochIntToBytes(dateToEpoch(new Date())); - } else if (epoch instanceof Date) { - epoch = epochIntToBytes(dateToEpoch(epoch)); - } - - const effectiveRateLimit = rateLimit ?? this.rateLimit; - - if (epoch.length !== 32) throw new Error("invalid epoch"); - if (idSecretHash.length !== 32) throw new Error("invalid id secret hash"); - if (index < 0) throw new Error("index must be >= 0"); - if ( - effectiveRateLimit < RATE_LIMIT_PARAMS.MIN_RATE || - effectiveRateLimit > RATE_LIMIT_PARAMS.MAX_RATE - ) { - throw new Error( - `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}` - ); - } - - const serialized_msg = this.serializeMessage( - msg, - index, - epoch, - idSecretHash, - effectiveRateLimit - ); - const rlnWitness = zerokitRLN.getSerializedRLNWitness( - this.zkRLN, - serialized_msg - ); - const inputs = zerokitRLN.RLNWitnessToJson(this.zkRLN, rlnWitness); - const calculatedWitness = await this.witnessCalculator.calculateWitness( - inputs, - false - ); - - const proofBytes = zerokitRLN.generate_rln_proof_with_witness( - this.zkRLN, - calculatedWitness, - rlnWitness - ); - - return new Proof(proofBytes); - } - - public verifyRLNProof( - proof: IRateLimitProof | Uint8Array, - msg: Uint8Array, - rateLimit?: number - ): boolean { - let pBytes: Uint8Array; - if (proof instanceof Uint8Array) { - pBytes = proof; - } else { - pBytes = proofToBytes(proof); - } - - // calculate message length - const msgLen = BytesUtils.writeUIntLE(new Uint8Array(8), msg.length, 0, 8); - const rateLimitBytes = BytesUtils.writeUIntLE( - new Uint8Array(8), - rateLimit ?? this.rateLimit, - 0, - 8 - ); - - return zerokitRLN.verifyRLNProof( - this.zkRLN, - BytesUtils.concatenate(pBytes, msgLen, msg, rateLimitBytes) - ); - } - - public verifyWithRoots( - proof: IRateLimitProof | Uint8Array, - msg: Uint8Array, - roots: Array, - rateLimit?: number - ): boolean { - let pBytes: Uint8Array; - if (proof instanceof Uint8Array) { - pBytes = proof; - } else { - pBytes = proofToBytes(proof); - } - // calculate message length - const msgLen = BytesUtils.writeUIntLE(new Uint8Array(8), msg.length, 0, 8); - const rateLimitBytes = BytesUtils.writeUIntLE( - new Uint8Array(8), - rateLimit ?? this.rateLimit, - 0, - 8 - ); - - const rootsBytes = BytesUtils.concatenate(...roots); - - return zerokitRLN.verifyWithRoots( - this.zkRLN, - BytesUtils.concatenate(pBytes, msgLen, msg, rateLimitBytes), - rootsBytes - ); - } - - public verifyWithNoRoot( - proof: IRateLimitProof | Uint8Array, - msg: Uint8Array, - rateLimit?: number - ): boolean { - let pBytes: Uint8Array; - if (proof instanceof Uint8Array) { - pBytes = proof; - } else { - pBytes = proofToBytes(proof); - } - - // calculate message length - const msgLen = BytesUtils.writeUIntLE(new Uint8Array(8), msg.length, 0, 8); - const rateLimitBytes = BytesUtils.writeUIntLE( - new Uint8Array(8), - rateLimit ?? this.rateLimit, - 0, - 8 - ); - - return zerokitRLN.verifyWithRoots( - this.zkRLN, - BytesUtils.concatenate(pBytes, msgLen, msg, rateLimitBytes), - new Uint8Array() - ); - } } diff --git a/packages/sdk/src/query_on_connect/query_on_connect.spec.ts b/packages/sdk/src/query_on_connect/query_on_connect.spec.ts index 9006239d33..b87caa5ce7 100644 --- a/packages/sdk/src/query_on_connect/query_on_connect.spec.ts +++ b/packages/sdk/src/query_on_connect/query_on_connect.spec.ts @@ -95,6 +95,7 @@ describe("QueryOnConnect", () => { it("should create QueryOnConnect instance with all required parameters", () => { queryOnConnect = new QueryOnConnect( mockDecoders, + () => false, mockPeerManagerEventEmitter, mockWakuEventEmitter, mockQueryGenerator, @@ -108,6 +109,7 @@ describe("QueryOnConnect", () => { it("should create QueryOnConnect instance without options", () => { queryOnConnect = new QueryOnConnect( mockDecoders, + () => false, mockPeerManagerEventEmitter, mockWakuEventEmitter, mockQueryGenerator @@ -120,6 +122,7 @@ describe("QueryOnConnect", () => { it("should accept empty decoders array", () => { queryOnConnect = new QueryOnConnect( [], + () => false, mockPeerManagerEventEmitter, mockWakuEventEmitter, mockQueryGenerator, @@ -134,6 +137,7 @@ describe("QueryOnConnect", () => { beforeEach(() => { queryOnConnect = new QueryOnConnect( mockDecoders, + () => false, mockPeerManagerEventEmitter, mockWakuEventEmitter, mockQueryGenerator, @@ -173,6 +177,7 @@ describe("QueryOnConnect", () => { beforeEach(() => { queryOnConnect = new QueryOnConnect( mockDecoders, + () => false, mockPeerManagerEventEmitter, mockWakuEventEmitter, mockQueryGenerator, @@ -224,6 +229,7 @@ describe("QueryOnConnect", () => { queryOnConnect = new QueryOnConnect( mockDecoders, + () => false, mockPeerManagerEventEmitter, mockWakuEventEmitter, mockQueryGenerator, @@ -276,6 +282,7 @@ describe("QueryOnConnect", () => { queryOnConnect = new QueryOnConnect( mockDecoders, + () => false, mockPeerManagerEventEmitter, mockWakuEventEmitter, mockQueryGenerator, @@ -298,6 +305,7 @@ describe("QueryOnConnect", () => { queryOnConnect = new QueryOnConnect( mockDecoders, + () => false, mockPeerManagerEventEmitter, mockWakuEventEmitter, mockQueryGenerator, @@ -320,6 +328,7 @@ describe("QueryOnConnect", () => { queryOnConnect = new QueryOnConnect( mockDecoders, + () => false, mockPeerManagerEventEmitter, mockWakuEventEmitter, mockQueryGenerator, @@ -391,6 +400,7 @@ describe("QueryOnConnect", () => { const queryOnConnect = new QueryOnConnect( mockDecoders, + () => false, mockPeerManagerEventEmitter, mockWakuEventEmitter, mockQueryGenerator, @@ -418,6 +428,7 @@ describe("QueryOnConnect", () => { queryOnConnect = new QueryOnConnect( mockDecoders, + () => false, mockPeerManagerEventEmitter, mockWakuEventEmitter, mockQueryGenerator, @@ -473,6 +484,7 @@ describe("QueryOnConnect", () => { queryOnConnect = new QueryOnConnect( mockDecoders, + () => false, mockPeerManagerEventEmitter, mockWakuEventEmitter, mockQueryGenerator, @@ -605,6 +617,7 @@ describe("QueryOnConnect", () => { queryOnConnect = new QueryOnConnect( mockDecoders, + () => false, mockPeerManagerEventEmitter, mockWakuEventEmitter, mockQueryGenerator, @@ -750,6 +763,248 @@ describe("QueryOnConnect", () => { expect(mockQueryGenerator.calledTwice).to.be.true; }); }); + + describe("stopIfTrue predicate", () => { + beforeEach(() => { + mockPeerManagerEventEmitter.addEventListener = sinon.stub(); + mockWakuEventEmitter.addEventListener = sinon.stub(); + }); + + it("should stop query iteration when stopIfTrue returns true", async () => { + const messages = [ + { + hash: new Uint8Array(), + hashStr: "msg1", + version: 1, + timestamp: new Date(), + contentTopic: "/test/1/content", + pubsubTopic: "/waku/2/default-waku/proto", + payload: new Uint8Array([1]), + rateLimitProof: undefined, + ephemeral: false, + meta: undefined + }, + { + hash: new Uint8Array(), + hashStr: "stop-hash", + version: 1, + timestamp: new Date(), + contentTopic: "/test/1/content", + pubsubTopic: "/waku/2/default-waku/proto", + payload: new Uint8Array([2]), + rateLimitProof: undefined, + ephemeral: false, + meta: undefined + }, + { + hash: new Uint8Array(), + hashStr: "msg3", + version: 1, + timestamp: new Date(), + contentTopic: "/test/1/content", + pubsubTopic: "/waku/2/default-waku/proto", + payload: new Uint8Array([3]), + rateLimitProof: undefined, + ephemeral: false, + meta: undefined + } + ]; + + // Setup generator to yield 3 pages, stop should occur on page 2 + const mockAsyncGenerator = async function* (): AsyncGenerator< + Promise[] + > { + yield [Promise.resolve(messages[0])]; + yield [Promise.resolve(messages[1])]; + yield [Promise.resolve(messages[2])]; + }; + mockQueryGenerator.returns(mockAsyncGenerator()); + + const stopPredicate = (msg: IDecodedMessage): boolean => + msg.hashStr === "stop-hash"; + + queryOnConnect = new QueryOnConnect( + mockDecoders, + stopPredicate, + mockPeerManagerEventEmitter, + mockWakuEventEmitter, + mockQueryGenerator, + options + ); + + const receivedMessages: IDecodedMessage[] = []; + queryOnConnect.addEventListener( + QueryOnConnectEvent.MessagesRetrieved, + (event: CustomEvent) => { + receivedMessages.push(...event.detail); + } + ); + + queryOnConnect.start(); + await queryOnConnect["maybeQuery"](mockPeerId); + + // Should have received messages from first 2 pages only + expect(receivedMessages).to.have.length(2); + expect(receivedMessages[0].hashStr).to.equal("msg1"); + expect(receivedMessages[1].hashStr).to.equal("stop-hash"); + }); + + it("should process all pages when stopIfTrue never returns true", async () => { + const messages = [ + { + hash: new Uint8Array(), + hashStr: "msg1", + version: 1, + timestamp: new Date(), + contentTopic: "/test/1/content", + pubsubTopic: "/waku/2/default-waku/proto", + payload: new Uint8Array([1]), + rateLimitProof: undefined, + ephemeral: false, + meta: undefined + }, + { + hash: new Uint8Array(), + hashStr: "msg2", + version: 1, + timestamp: new Date(), + contentTopic: "/test/1/content", + pubsubTopic: "/waku/2/default-waku/proto", + payload: new Uint8Array([2]), + rateLimitProof: undefined, + ephemeral: false, + meta: undefined + }, + { + hash: new Uint8Array(), + hashStr: "msg3", + version: 1, + timestamp: new Date(), + contentTopic: "/test/1/content", + pubsubTopic: "/waku/2/default-waku/proto", + payload: new Uint8Array([3]), + rateLimitProof: undefined, + ephemeral: false, + meta: undefined + } + ]; + + const mockAsyncGenerator = async function* (): AsyncGenerator< + Promise[] + > { + yield [Promise.resolve(messages[0])]; + yield [Promise.resolve(messages[1])]; + yield [Promise.resolve(messages[2])]; + }; + mockQueryGenerator.returns(mockAsyncGenerator()); + + const stopPredicate = (): boolean => false; + + queryOnConnect = new QueryOnConnect( + mockDecoders, + stopPredicate, + mockPeerManagerEventEmitter, + mockWakuEventEmitter, + mockQueryGenerator, + options + ); + + const receivedMessages: IDecodedMessage[] = []; + queryOnConnect.addEventListener( + QueryOnConnectEvent.MessagesRetrieved, + (event: CustomEvent) => { + receivedMessages.push(...event.detail); + } + ); + + queryOnConnect.start(); + await queryOnConnect["maybeQuery"](mockPeerId); + + // Should have received all 3 messages + expect(receivedMessages).to.have.length(3); + }); + + it("should stop on first message of a page if stopIfTrue matches", async () => { + const messages = [ + { + hash: new Uint8Array(), + hashStr: "stop-hash", + version: 1, + timestamp: new Date(), + contentTopic: "/test/1/content", + pubsubTopic: "/waku/2/default-waku/proto", + payload: new Uint8Array([1]), + rateLimitProof: undefined, + ephemeral: false, + meta: undefined + }, + { + hash: new Uint8Array(), + hashStr: "msg2", + version: 1, + timestamp: new Date(), + contentTopic: "/test/1/content", + pubsubTopic: "/waku/2/default-waku/proto", + payload: new Uint8Array([2]), + rateLimitProof: undefined, + ephemeral: false, + meta: undefined + }, + { + hash: new Uint8Array(), + hashStr: "msg3", + version: 1, + timestamp: new Date(), + contentTopic: "/test/1/content", + pubsubTopic: "/waku/2/default-waku/proto", + payload: new Uint8Array([3]), + rateLimitProof: undefined, + ephemeral: false, + meta: undefined + } + ]; + + const mockAsyncGenerator = async function* (): AsyncGenerator< + Promise[] + > { + yield [ + Promise.resolve(messages[0]), + Promise.resolve(messages[1]), + Promise.resolve(messages[2]) + ]; + }; + mockQueryGenerator.returns(mockAsyncGenerator()); + + const stopPredicate = (msg: IDecodedMessage): boolean => + msg.hashStr === "stop-hash"; + + queryOnConnect = new QueryOnConnect( + mockDecoders, + stopPredicate, + mockPeerManagerEventEmitter, + mockWakuEventEmitter, + mockQueryGenerator, + options + ); + + const receivedMessages: IDecodedMessage[] = []; + queryOnConnect.addEventListener( + QueryOnConnectEvent.MessagesRetrieved, + (event: CustomEvent) => { + receivedMessages.push(...event.detail); + } + ); + + queryOnConnect.start(); + await queryOnConnect["maybeQuery"](mockPeerId); + + // Should have received all 3 messages from the page, even though first matched + expect(receivedMessages).to.have.length(3); + expect(receivedMessages[0].hashStr).to.equal("stop-hash"); + expect(receivedMessages[1].hashStr).to.equal("msg2"); + expect(receivedMessages[2].hashStr).to.equal("msg3"); + }); + }); }); describe("calculateTimeRange", () => { diff --git a/packages/sdk/src/query_on_connect/query_on_connect.ts b/packages/sdk/src/query_on_connect/query_on_connect.ts index f42c2ada91..da9e78a763 100644 --- a/packages/sdk/src/query_on_connect/query_on_connect.ts +++ b/packages/sdk/src/query_on_connect/query_on_connect.ts @@ -17,7 +17,7 @@ import { const log = new Logger("sdk:query-on-connect"); export const DEFAULT_FORCE_QUERY_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes -export const MAX_TIME_RANGE_QUERY_MS = 24 * 60 * 60 * 1000; // 24 hours +export const MAX_TIME_RANGE_QUERY_MS = 30 * 24 * 60 * 60 * 1000; // 30 days (queries are split) export interface QueryOnConnectOptions { /** @@ -54,6 +54,7 @@ export class QueryOnConnect< public constructor( public decoders: IDecoder[], + public stopIfTrue: (msg: T) => boolean, private readonly peerManagerEventEmitter: TypedEventEmitter, private readonly wakuEventEmitter: IWakuEventEmitter, private readonly _queryGenerator: ( @@ -125,8 +126,13 @@ export class QueryOnConnect< const messages = (await Promise.all(page)).filter( (m) => m !== undefined ); + const stop = messages.some((msg: T) => this.stopIfTrue(msg)); // Bundle the messages to help batch process by sds this.dispatchMessages(messages); + + if (stop) { + break; + } } // Didn't throw, so it didn't fail diff --git a/packages/sdk/src/reliable_channel/reliable_channel.spec.ts b/packages/sdk/src/reliable_channel/reliable_channel.spec.ts index b6d83123e6..ad69d35009 100644 --- a/packages/sdk/src/reliable_channel/reliable_channel.spec.ts +++ b/packages/sdk/src/reliable_channel/reliable_channel.spec.ts @@ -13,7 +13,7 @@ import { LightPushSDKResult, QueryRequestParams } from "@waku/interfaces"; -import { ContentMessage } from "@waku/sds"; +import { ContentMessage, SyncMessage } from "@waku/sds"; import { createRoutingInfo, delay, @@ -176,7 +176,8 @@ describe("Reliable Channel", () => { expect(messageAcknowledged).to.be.false; }); - it("Outgoing message is possibly acknowledged", async () => { + // TODO: https://github.com/waku-org/js-waku/issues/2648 + it.skip("Outgoing message is possibly acknowledged", async () => { const commonEventEmitter = new TypedEventEmitter(); const mockWakuNodeAlice = new MockWakuNode(commonEventEmitter); const mockWakuNodeBob = new MockWakuNode(commonEventEmitter); @@ -418,7 +419,7 @@ describe("Reliable Channel", () => { "MyChannel", "alice", [], - 1, + 1n, undefined, message ); @@ -531,7 +532,7 @@ describe("Reliable Channel", () => { "testChannel", "testSender", [], - 1, + 1n, undefined, messagePayload ); @@ -599,7 +600,7 @@ describe("Reliable Channel", () => { "testChannel", "testSender", [], - 1, + 1n, undefined, message1Payload ); @@ -609,7 +610,7 @@ describe("Reliable Channel", () => { "testChannel", "testSender", [], - 2, + 2n, undefined, message2Payload ); @@ -677,4 +678,456 @@ describe("Reliable Channel", () => { expect(queryGeneratorStub.called).to.be.true; }); }); + + describe("stopIfTrue Integration with QueryOnConnect", () => { + let mockWakuNode: MockWakuNode; + let encoder: IEncoder; + let decoder: IDecoder; + let mockPeerManagerEvents: TypedEventEmitter; + let queryGeneratorStub: sinon.SinonStub; + let mockPeerId: PeerId; + + beforeEach(async () => { + mockWakuNode = new MockWakuNode(); + mockPeerManagerEvents = new TypedEventEmitter(); + (mockWakuNode as any).peerManager = { + events: mockPeerManagerEvents + }; + + encoder = createEncoder({ + contentTopic: TEST_CONTENT_TOPIC, + routingInfo: TEST_ROUTING_INFO + }); + + decoder = createDecoder(TEST_CONTENT_TOPIC, TEST_ROUTING_INFO); + + queryGeneratorStub = sinon.stub(); + mockWakuNode.store = { + queryGenerator: queryGeneratorStub + } as any; + + mockPeerId = { + toString: () => "QmTestPeerId" + } as unknown as PeerId; + }); + + it("should stop query when sync message from same channel is found", async () => { + const channelId = "testChannel"; + const senderId = "testSender"; + + // Create messages: one from different channel, one sync from same channel, one more + const sdsMessageDifferentChannel = new ContentMessage( + "msg1", + "differentChannel", + senderId, + [], + 1n, + undefined, + utf8ToBytes("different channel") + ); + + const sdsSyncMessage = new SyncMessage( + "sync-msg-id", + channelId, + senderId, + [], + 2n, + undefined, + undefined + ); + + const sdsMessageAfterSync = new ContentMessage( + "msg3", + channelId, + senderId, + [], + 3n, + undefined, + utf8ToBytes("after sync") + ); + + const messages: IDecodedMessage[] = [ + { + hash: hexToBytes("1111"), + hashStr: "1111", + version: 1, + timestamp: new Date(), + contentTopic: TEST_CONTENT_TOPIC, + pubsubTopic: decoder.pubsubTopic, + payload: sdsMessageDifferentChannel.encode(), + rateLimitProof: undefined, + ephemeral: false, + meta: undefined + }, + { + hash: hexToBytes("2222"), + hashStr: "2222", + version: 1, + timestamp: new Date(), + contentTopic: TEST_CONTENT_TOPIC, + pubsubTopic: decoder.pubsubTopic, + payload: sdsSyncMessage.encode(), + rateLimitProof: undefined, + ephemeral: false, + meta: undefined + }, + { + hash: hexToBytes("3333"), + hashStr: "3333", + version: 1, + timestamp: new Date(), + contentTopic: TEST_CONTENT_TOPIC, + pubsubTopic: decoder.pubsubTopic, + payload: sdsMessageAfterSync.encode(), + rateLimitProof: undefined, + ephemeral: false, + meta: undefined + } + ]; + + // Setup generator to yield 3 messages, but should stop after 2nd + queryGeneratorStub.callsFake(async function* () { + yield [Promise.resolve(messages[0])]; + yield [Promise.resolve(messages[1])]; + yield [Promise.resolve(messages[2])]; + }); + + const reliableChannel = await ReliableChannel.create( + mockWakuNode, + channelId, + senderId, + encoder, + decoder + ); + + await delay(50); + + // Trigger query on connect + mockPeerManagerEvents.dispatchEvent( + new CustomEvent("store:connect", { detail: mockPeerId }) + ); + + await delay(200); + + // queryGenerator should have been called + expect(queryGeneratorStub.called).to.be.true; + // The query should have stopped after finding sync message from same channel + expect(reliableChannel).to.not.be.undefined; + }); + + it("should stop query on content message from same channel", async () => { + const channelId = "testChannel"; + const senderId = "testSender"; + + const sdsContentMessage = new ContentMessage( + "msg1", + channelId, + senderId, + [{ messageId: "previous-msg-id" }], + 1n, + undefined, + utf8ToBytes("content message") + ); + + const sdsMessageAfter = new ContentMessage( + "msg2", + channelId, + senderId, + [], + 2n, + undefined, + utf8ToBytes("after content") + ); + + const messages: IDecodedMessage[] = [ + { + hash: hexToBytes("1111"), + hashStr: "1111", + version: 1, + timestamp: new Date(), + contentTopic: TEST_CONTENT_TOPIC, + pubsubTopic: decoder.pubsubTopic, + payload: sdsContentMessage.encode(), + rateLimitProof: undefined, + ephemeral: false, + meta: undefined + }, + { + hash: hexToBytes("2222"), + hashStr: "2222", + version: 1, + timestamp: new Date(), + contentTopic: TEST_CONTENT_TOPIC, + pubsubTopic: decoder.pubsubTopic, + payload: sdsMessageAfter.encode(), + rateLimitProof: undefined, + ephemeral: false, + meta: undefined + } + ]; + + let pagesYielded = 0; + queryGeneratorStub.callsFake(async function* () { + pagesYielded++; + yield [Promise.resolve(messages[0])]; + pagesYielded++; + yield [Promise.resolve(messages[1])]; + }); + + const reliableChannel = await ReliableChannel.create( + mockWakuNode, + channelId, + senderId, + encoder, + decoder + ); + + await delay(50); + + mockPeerManagerEvents.dispatchEvent( + new CustomEvent("store:connect", { detail: mockPeerId }) + ); + + await delay(200); + + expect(queryGeneratorStub.called).to.be.true; + expect(reliableChannel).to.not.be.undefined; + // Should have stopped after first page with content message + expect(pagesYielded).to.equal(1); + }); + + it("should continue query when messages are from different channels", async () => { + const channelId = "testChannel"; + const senderId = "testSender"; + + const sdsMessageDifferent1 = new ContentMessage( + "msg1", + "differentChannel1", + senderId, + [], + 1n, + undefined, + utf8ToBytes("different 1") + ); + + const sdsMessageDifferent2 = new ContentMessage( + "msg2", + "differentChannel2", + senderId, + [], + 2n, + undefined, + utf8ToBytes("different 2") + ); + + const sdsMessageDifferent3 = new ContentMessage( + "msg3", + "differentChannel3", + senderId, + [], + 3n, + undefined, + utf8ToBytes("different 3") + ); + + const messages: IDecodedMessage[] = [ + { + hash: hexToBytes("1111"), + hashStr: "1111", + version: 1, + timestamp: new Date(), + contentTopic: TEST_CONTENT_TOPIC, + pubsubTopic: decoder.pubsubTopic, + payload: sdsMessageDifferent1.encode(), + rateLimitProof: undefined, + ephemeral: false, + meta: undefined + }, + { + hash: hexToBytes("2222"), + hashStr: "2222", + version: 1, + timestamp: new Date(), + contentTopic: TEST_CONTENT_TOPIC, + pubsubTopic: decoder.pubsubTopic, + payload: sdsMessageDifferent2.encode(), + rateLimitProof: undefined, + ephemeral: false, + meta: undefined + }, + { + hash: hexToBytes("3333"), + hashStr: "3333", + version: 1, + timestamp: new Date(), + contentTopic: TEST_CONTENT_TOPIC, + pubsubTopic: decoder.pubsubTopic, + payload: sdsMessageDifferent3.encode(), + rateLimitProof: undefined, + ephemeral: false, + meta: undefined + } + ]; + + let pagesYielded = 0; + queryGeneratorStub.callsFake(async function* () { + pagesYielded++; + yield [Promise.resolve(messages[0])]; + pagesYielded++; + yield [Promise.resolve(messages[1])]; + pagesYielded++; + yield [Promise.resolve(messages[2])]; + }); + + const reliableChannel = await ReliableChannel.create( + mockWakuNode, + channelId, + senderId, + encoder, + decoder + ); + + await delay(50); + + mockPeerManagerEvents.dispatchEvent( + new CustomEvent("store:connect", { detail: mockPeerId }) + ); + + await delay(200); + + expect(queryGeneratorStub.called).to.be.true; + expect(reliableChannel).to.not.be.undefined; + // Should have processed all pages since no matching channel + expect(pagesYielded).to.equal(3); + }); + }); + + describe("isChannelMessageWithCausalHistory predicate", () => { + let mockWakuNode: MockWakuNode; + let reliableChannel: ReliableChannel; + let encoder: IEncoder; + let decoder: IDecoder; + + beforeEach(async () => { + mockWakuNode = new MockWakuNode(); + encoder = createEncoder({ + contentTopic: TEST_CONTENT_TOPIC, + routingInfo: TEST_ROUTING_INFO + }); + decoder = createDecoder(TEST_CONTENT_TOPIC, TEST_ROUTING_INFO); + + reliableChannel = await ReliableChannel.create( + mockWakuNode, + "testChannel", + "testSender", + encoder, + decoder, + { queryOnConnect: false } + ); + }); + + it("should return false for malformed SDS messages", () => { + const msg = { + payload: new Uint8Array([1, 2, 3]) + } as IDecodedMessage; + + const result = reliableChannel["isChannelMessageWithCausalHistory"](msg); + expect(result).to.be.false; + }); + + it("should return false for different channelId", () => { + const sdsMsg = new ContentMessage( + "msg1", + "differentChannel", + "sender", + [], + 1n, + undefined, + utf8ToBytes("content") + ); + + const msg = { + payload: sdsMsg.encode() + } as IDecodedMessage; + + const result = reliableChannel["isChannelMessageWithCausalHistory"](msg); + expect(result).to.be.false; + }); + + it("should return false for sync message without causal history", () => { + const syncMsg = new SyncMessage( + "sync-msg-id", + "testChannel", + "sender", + [], + 1n, + undefined, + undefined + ); + + const msg = { + payload: syncMsg.encode() + } as IDecodedMessage; + + const result = reliableChannel["isChannelMessageWithCausalHistory"](msg); + expect(result).to.be.false; + }); + + it("should return false for content message without causal history", () => { + const contentMsg = new ContentMessage( + "msg1", + "testChannel", + "sender", + [], + 1n, + undefined, + utf8ToBytes("content") + ); + + const msg = { + payload: contentMsg.encode() + } as IDecodedMessage; + + const result = reliableChannel["isChannelMessageWithCausalHistory"](msg); + expect(result).to.be.false; + }); + + it("should return true for message with causal history", () => { + const contentMsg = new ContentMessage( + "msg1", + "testChannel", + "sender", + [{ messageId: "previous-msg-id" }], + 1n, + undefined, + utf8ToBytes("content") + ); + + const msg = { + payload: contentMsg.encode() + } as IDecodedMessage; + + const result = reliableChannel["isChannelMessageWithCausalHistory"](msg); + expect(result).to.be.true; + }); + + it("should return true for sync message with causal history", () => { + const syncMsg = new SyncMessage( + "sync-msg-id", + "testChannel", + "sender", + [{ messageId: "previous-msg-id" }], + 1n, + undefined, + undefined + ); + + const msg = { + payload: syncMsg.encode() + } as IDecodedMessage; + + const result = reliableChannel["isChannelMessageWithCausalHistory"](msg); + expect(result).to.be.true; + }); + }); }); diff --git a/packages/sdk/src/reliable_channel/reliable_channel.ts b/packages/sdk/src/reliable_channel/reliable_channel.ts index 713309b90f..49b55aa495 100644 --- a/packages/sdk/src/reliable_channel/reliable_channel.ts +++ b/packages/sdk/src/reliable_channel/reliable_channel.ts @@ -185,9 +185,9 @@ export class ReliableChannel< peerManagerEvents !== undefined && (options?.queryOnConnect ?? true) ) { - log.info("auto-query enabled"); this.queryOnConnect = new QueryOnConnect( [this.decoder], + this.isChannelMessageWithCausalHistory.bind(this), peerManagerEvents, node.events, this._retrieve.bind(this) @@ -580,6 +580,21 @@ export class ReliableChannel< this.messageChannel.sweepOutgoingBuffer(); } + private isChannelMessageWithCausalHistory(msg: T): boolean { + // TODO: we do end-up decoding messages twice as this is used to stop store queries. + const sdsMessage = SdsMessage.decode(msg.payload); + + if (!sdsMessage) { + return false; + } + + if (sdsMessage.channelId !== this.messageChannel.channelId) { + return false; + } + + return sdsMessage.causalHistory && sdsMessage.causalHistory.length > 0; + } + private setupEventListeners(): void { this.messageChannel.addEventListener( MessageChannelEvent.OutMessageSent, diff --git a/packages/sdk/src/reliable_channel/reliable_channel_encryption.spec.ts b/packages/sdk/src/reliable_channel/reliable_channel_encryption.spec.ts index 978d357ec6..628c99bfaa 100644 --- a/packages/sdk/src/reliable_channel/reliable_channel_encryption.spec.ts +++ b/packages/sdk/src/reliable_channel/reliable_channel_encryption.spec.ts @@ -187,7 +187,8 @@ describe("Reliable Channel: Encryption", () => { expect(messageAcknowledged).to.be.false; }); - it("Outgoing message is possibly acknowledged", async () => { + // TODO: https://github.com/waku-org/js-waku/issues/2648 + it.skip("Outgoing message is possibly acknowledged", async () => { const commonEventEmitter = new TypedEventEmitter(); const mockWakuNodeAlice = new MockWakuNode(commonEventEmitter); const mockWakuNodeBob = new MockWakuNode(commonEventEmitter); diff --git a/packages/sdk/src/reliable_channel/reliable_channel_sync.spec.ts b/packages/sdk/src/reliable_channel/reliable_channel_sync.spec.ts index 75dbb2eda0..226d5b8c6a 100644 --- a/packages/sdk/src/reliable_channel/reliable_channel_sync.spec.ts +++ b/packages/sdk/src/reliable_channel/reliable_channel_sync.spec.ts @@ -56,6 +56,19 @@ describe("Reliable Channel: Sync", () => { } ); + // Send a message to have a history + const sentMsgId = reliableChannel.send(utf8ToBytes("some message")); + let messageSent = false; + reliableChannel.addEventListener("message-sent", (event) => { + if (event.detail === sentMsgId) { + messageSent = true; + } + }); + + while (!messageSent) { + await delay(50); + } + let syncMessageSent = false; reliableChannel.messageChannel.addEventListener( MessageChannelEvent.OutSyncSent, @@ -131,6 +144,19 @@ describe("Reliable Channel: Sync", () => { return 1; }; // will wait a full second + // Send a message to have a history + const sentMsgId = reliableChannelAlice.send(utf8ToBytes("some message")); + let messageSent = false; + reliableChannelAlice.addEventListener("message-sent", (event) => { + if (event.detail === sentMsgId) { + messageSent = true; + } + }); + + while (!messageSent) { + await delay(50); + } + let syncMessageSent = false; reliableChannelBob.messageChannel.addEventListener( MessageChannelEvent.OutSyncSent, @@ -191,6 +217,19 @@ describe("Reliable Channel: Sync", () => { return 1; }; // will wait a full second + // Send a message to have a history + const sentMsgId = reliableChannelAlice.send(utf8ToBytes("some message")); + let messageSent = false; + reliableChannelAlice.addEventListener("message-sent", (event) => { + if (event.detail === sentMsgId) { + messageSent = true; + } + }); + + while (!messageSent) { + await delay(50); + } + let syncMessageSent = false; reliableChannelBob.messageChannel.addEventListener( MessageChannelEvent.OutSyncSent, @@ -232,6 +271,19 @@ describe("Reliable Channel: Sync", () => { return 1; }; // will wait a full second + // Send a message to have a history + const sentMsgId = reliableChannel.send(utf8ToBytes("some message")); + let messageSent = false; + reliableChannel.addEventListener("message-sent", (event) => { + if (event.detail === sentMsgId) { + messageSent = true; + } + }); + + while (!messageSent) { + await delay(50); + } + let syncMessageSent = false; reliableChannel.messageChannel.addEventListener( MessageChannelEvent.OutSyncSent, @@ -273,6 +325,19 @@ describe("Reliable Channel: Sync", () => { return 1; }; // will wait a full second + // Send a message to have a history + const sentMsgId = reliableChannel.send(utf8ToBytes("some message")); + let messageSent = false; + reliableChannel.addEventListener("message-sent", (event) => { + if (event.detail === sentMsgId) { + messageSent = true; + } + }); + + while (!messageSent) { + await delay(50); + } + let syncMessageSent = false; reliableChannel.messageChannel.addEventListener( MessageChannelEvent.OutSyncSent, diff --git a/packages/sds/src/message_channel/lamport_timestamp.spec.ts b/packages/sds/src/message_channel/lamport_timestamp.spec.ts new file mode 100644 index 0000000000..57aec2666e --- /dev/null +++ b/packages/sds/src/message_channel/lamport_timestamp.spec.ts @@ -0,0 +1,56 @@ +import { expect } from "chai"; + +import { lamportTimestampIncrement } from "./message_channel.js"; + +describe("lamportTimestampIncrement", () => { + it("should increment timestamp by 1 when current time is not greater", () => { + const futureTimestamp = BigInt(Date.now()) + 1000n; + const result = lamportTimestampIncrement(futureTimestamp); + expect(result).to.equal(futureTimestamp + 1n); + }); + + it("should use current time when it's greater than incremented timestamp", () => { + const pastTimestamp = BigInt(Date.now()) - 1000n; + const result = lamportTimestampIncrement(pastTimestamp); + const now = BigInt(Date.now()); + // Result should be at least as large as now (within small tolerance for test execution time) + expect(result >= now - 10n).to.be.true; + expect(result <= now + 10n).to.be.true; + }); + + it("should handle timestamp equal to current time", () => { + const currentTimestamp = BigInt(Date.now()); + const result = lamportTimestampIncrement(currentTimestamp); + // Should increment by 1 since now is likely not greater than current + 1 + expect(result >= currentTimestamp + 1n).to.be.true; + }); + + it("should ensure monotonic increase", () => { + let timestamp = BigInt(Date.now()) + 5000n; + const results: bigint[] = []; + + for (let i = 0; i < 5; i++) { + timestamp = lamportTimestampIncrement(timestamp); + results.push(timestamp); + } + + // Verify all timestamps are strictly increasing + for (let i = 1; i < results.length; i++) { + expect(results[i] > results[i - 1]).to.be.true; + } + }); + + it("should handle very large timestamps", () => { + const largeTimestamp = BigInt(Number.MAX_SAFE_INTEGER) * 1000n; + const result = lamportTimestampIncrement(largeTimestamp); + expect(result).to.equal(largeTimestamp + 1n); + }); + + it("should jump to current time when timestamp is far in the past", () => { + const veryOldTimestamp = 1000n; // Very old timestamp (1 second after epoch) + const result = lamportTimestampIncrement(veryOldTimestamp); + const now = BigInt(Date.now()); + expect(result >= now - 10n).to.be.true; + expect(result <= now + 10n).to.be.true; + }); +}); diff --git a/packages/sds/src/message_channel/message.spec.ts b/packages/sds/src/message_channel/message.spec.ts index 11bb9b3735..680bf5cdb5 100644 --- a/packages/sds/src/message_channel/message.spec.ts +++ b/packages/sds/src/message_channel/message.spec.ts @@ -18,7 +18,7 @@ describe("Message serialization", () => { "my-channel", "me", [], - 0, + 0n, bloomFilter.toBytes(), undefined ); @@ -42,7 +42,7 @@ describe("Message serialization", () => { "my-channel", "me", [{ messageId: depMessageId, retrievalHint: depRetrievalHint }], - 0, + 0n, undefined, undefined ); @@ -63,7 +63,7 @@ describe("ContentMessage comparison with < operator", () => { "channel", "sender", [], - 100, // Lower timestamp + 100n, // Lower timestamp undefined, new Uint8Array([1]) ); @@ -73,7 +73,7 @@ describe("ContentMessage comparison with < operator", () => { "channel", "sender", [], - 200, // Higher timestamp + 200n, // Higher timestamp undefined, new Uint8Array([2]) ); @@ -89,7 +89,7 @@ describe("ContentMessage comparison with < operator", () => { "channel", "sender", [], - 100, // Same timestamp + 100n, // Same timestamp undefined, new Uint8Array([1]) ); @@ -99,7 +99,7 @@ describe("ContentMessage comparison with < operator", () => { "channel", "sender", [], - 100, // Same timestamp + 100n, // Same timestamp undefined, new Uint8Array([2]) ); diff --git a/packages/sds/src/message_channel/message.ts b/packages/sds/src/message_channel/message.ts index e186124750..78b99f9006 100644 --- a/packages/sds/src/message_channel/message.ts +++ b/packages/sds/src/message_channel/message.ts @@ -14,7 +14,7 @@ export class Message implements proto_sds_message.SdsMessage { public channelId: string, public senderId: string, public causalHistory: proto_sds_message.HistoryEntry[], - public lamportTimestamp?: number | undefined, + public lamportTimestamp?: bigint | undefined, public bloomFilter?: Uint8Array | undefined, public content?: Uint8Array | undefined, /** @@ -30,56 +30,60 @@ export class Message implements proto_sds_message.SdsMessage { public static decode( data: Uint8Array ): undefined | ContentMessage | SyncMessage | EphemeralMessage { - const { - messageId, - channelId, - senderId, - causalHistory, - lamportTimestamp, - bloomFilter, - content - } = proto_sds_message.SdsMessage.decode(data); - - if (testContentMessage({ lamportTimestamp, content })) { - return new ContentMessage( + try { + const { messageId, channelId, senderId, causalHistory, - lamportTimestamp!, + lamportTimestamp, bloomFilter, - content! - ); - } + content + } = proto_sds_message.SdsMessage.decode(data); - if (testEphemeralMessage({ lamportTimestamp, content })) { - return new EphemeralMessage( - messageId, - channelId, - senderId, - causalHistory, - undefined, - bloomFilter, - content! - ); - } + if (testContentMessage({ lamportTimestamp, content })) { + return new ContentMessage( + messageId, + channelId, + senderId, + causalHistory, + lamportTimestamp!, + bloomFilter, + content! + ); + } - if (testSyncMessage({ lamportTimestamp, content })) { - return new SyncMessage( - messageId, - channelId, - senderId, - causalHistory, - lamportTimestamp!, - bloomFilter, - undefined + if (testEphemeralMessage({ lamportTimestamp, content })) { + return new EphemeralMessage( + messageId, + channelId, + senderId, + causalHistory, + undefined, + bloomFilter, + content! + ); + } + + if (testSyncMessage({ lamportTimestamp, content })) { + return new SyncMessage( + messageId, + channelId, + senderId, + causalHistory, + lamportTimestamp!, + bloomFilter, + undefined + ); + } + log.error( + "message received was of unknown type", + lamportTimestamp, + content ); + } catch (err) { + log.error("failed to decode sds message", err); } - log.error( - "message received was of unknown type", - lamportTimestamp, - content - ); return undefined; } } @@ -90,7 +94,7 @@ export class SyncMessage extends Message { public channelId: string, public senderId: string, public causalHistory: proto_sds_message.HistoryEntry[], - public lamportTimestamp: number, + public lamportTimestamp: bigint, public bloomFilter: Uint8Array | undefined, public content: undefined, /** @@ -112,12 +116,12 @@ export class SyncMessage extends Message { } function testSyncMessage(message: { - lamportTimestamp?: number; + lamportTimestamp?: bigint; content?: Uint8Array; }): boolean { return Boolean( "lamportTimestamp" in message && - typeof message.lamportTimestamp === "number" && + typeof message.lamportTimestamp === "bigint" && (message.content === undefined || message.content.length === 0) ); } @@ -165,7 +169,7 @@ export function isEphemeralMessage( } function testEphemeralMessage(message: { - lamportTimestamp?: number; + lamportTimestamp?: bigint; content?: Uint8Array; }): boolean { return Boolean( @@ -182,7 +186,7 @@ export class ContentMessage extends Message { public channelId: string, public senderId: string, public causalHistory: proto_sds_message.HistoryEntry[], - public lamportTimestamp: number, + public lamportTimestamp: bigint, public bloomFilter: Uint8Array | undefined, public content: Uint8Array, /** @@ -222,12 +226,12 @@ export function isContentMessage( } function testContentMessage(message: { - lamportTimestamp?: number; + lamportTimestamp?: bigint; content?: Uint8Array; -}): message is { lamportTimestamp: number; content: Uint8Array } { +}): message is { lamportTimestamp: bigint; content: Uint8Array } { return Boolean( "lamportTimestamp" in message && - typeof message.lamportTimestamp === "number" && + typeof message.lamportTimestamp === "bigint" && message.content && message.content.length ); diff --git a/packages/sds/src/message_channel/message_channel.spec.ts b/packages/sds/src/message_channel/message_channel.spec.ts index 604994c0f8..91184f04d8 100644 --- a/packages/sds/src/message_channel/message_channel.spec.ts +++ b/packages/sds/src/message_channel/message_channel.spec.ts @@ -75,7 +75,7 @@ describe("MessageChannel", function () { const timestampBefore = channelA["lamportTimestamp"]; await sendMessage(channelA, utf8ToBytes("message"), callback); const timestampAfter = channelA["lamportTimestamp"]; - expect(timestampAfter).to.equal(timestampBefore + 1); + expect(timestampAfter).to.equal(timestampBefore + 1n); }); it("should push the message to the outgoing buffer", async () => { @@ -95,7 +95,7 @@ describe("MessageChannel", function () { it("should insert message id into causal history", async () => { const payload = utf8ToBytes("message"); - const expectedTimestamp = channelA["lamportTimestamp"] + 1; + const expectedTimestamp = channelA["lamportTimestamp"] + 1n; const messageId = MessageChannel.getMessageId(payload); await sendMessage(channelA, payload, callback); const messageIdLog = channelA["localHistory"] as ILocalHistory; @@ -181,7 +181,7 @@ describe("MessageChannel", function () { return { success: true }; }); const timestampAfter = channelA["lamportTimestamp"]; - expect(timestampAfter).to.equal(timestampBefore + 1); + expect(timestampAfter).to.equal(timestampBefore + 1n); }); // TODO: test is failing in CI, investigate in https://github.com/waku-org/js-waku/issues/2648 @@ -201,7 +201,9 @@ describe("MessageChannel", function () { }); } const timestampAfter = testChannelA["lamportTimestamp"]; - expect(timestampAfter - timestampBefore).to.equal(messagesB.length); + expect(timestampAfter - timestampBefore).to.equal( + BigInt(messagesB.length) + ); }); // TODO: test is failing in CI, investigate in https://github.com/waku-org/js-waku/issues/2648 @@ -228,7 +230,7 @@ describe("MessageChannel", function () { const expectedLength = messagesA.length + messagesB.length; expect(channelA["lamportTimestamp"]).to.equal( - aTimestampBefore + expectedLength + aTimestampBefore + BigInt(expectedLength) ); expect(channelA["lamportTimestamp"]).to.equal( channelB["lamportTimestamp"] @@ -293,7 +295,7 @@ describe("MessageChannel", function () { channelA.channelId, "not-alice", [], - 1, + 1n, undefined, payload, testRetrievalHint @@ -335,7 +337,7 @@ describe("MessageChannel", function () { channelA.channelId, "bob", [], - startTimestamp + 3, // Higher timestamp + startTimestamp + 3n, // Higher timestamp undefined, message3Payload ) @@ -349,7 +351,7 @@ describe("MessageChannel", function () { channelA.channelId, "carol", [], - startTimestamp + 2, // Middle timestamp + startTimestamp + 2n, // Middle timestamp undefined, message2Payload ) @@ -363,7 +365,7 @@ describe("MessageChannel", function () { const first = localHistory.findIndex( ({ messageId, lamportTimestamp }) => { return ( - messageId === message1Id && lamportTimestamp === startTimestamp + 1 + messageId === message1Id && lamportTimestamp === startTimestamp + 1n ); } ); @@ -372,7 +374,7 @@ describe("MessageChannel", function () { const second = localHistory.findIndex( ({ messageId, lamportTimestamp }) => { return ( - messageId === message2Id && lamportTimestamp === startTimestamp + 2 + messageId === message2Id && lamportTimestamp === startTimestamp + 2n ); } ); @@ -381,7 +383,7 @@ describe("MessageChannel", function () { const third = localHistory.findIndex( ({ messageId, lamportTimestamp }) => { return ( - messageId === message3Id && lamportTimestamp === startTimestamp + 3 + messageId === message3Id && lamportTimestamp === startTimestamp + 3n ); } ); @@ -404,7 +406,7 @@ describe("MessageChannel", function () { channelA.channelId, "bob", [], - 5, // Same timestamp + 5n, // Same timestamp undefined, message2Payload ) @@ -417,7 +419,7 @@ describe("MessageChannel", function () { channelA.channelId, "carol", [], - 5, // Same timestamp + 5n, // Same timestamp undefined, message1Payload ) @@ -432,14 +434,14 @@ describe("MessageChannel", function () { const first = localHistory.findIndex( ({ messageId, lamportTimestamp }) => { - return messageId === expectedOrder[0] && lamportTimestamp == 5; + return messageId === expectedOrder[0] && lamportTimestamp == 5n; } ); expect(first).to.eq(0); const second = localHistory.findIndex( ({ messageId, lamportTimestamp }) => { - return messageId === expectedOrder[1] && lamportTimestamp == 5; + return messageId === expectedOrder[1] && lamportTimestamp == 5n; } ); expect(second).to.eq(1); @@ -645,11 +647,12 @@ describe("MessageChannel", function () { }); // And be sends a sync message - await channelB.pushOutgoingSyncMessage(async (message) => { + const res = await channelB.pushOutgoingSyncMessage(async (message) => { await receiveMessage(channelA, message); return true; }); + expect(res).to.be.true; expect(messageAcked).to.be.true; }); }); @@ -1087,17 +1090,41 @@ describe("MessageChannel", function () { causalHistorySize: 2 }); channelB = new MessageChannel(channelId, "bob", { causalHistorySize: 2 }); + const message = utf8ToBytes("first message in channel"); + channelA["localHistory"].push( + new ContentMessage( + MessageChannel.getMessageId(message), + "MyChannel", + "alice", + [], + 1n, + undefined, + message + ) + ); }); it("should be sent with empty content", async () => { - await channelA.pushOutgoingSyncMessage(async (message) => { + const res = await channelA.pushOutgoingSyncMessage(async (message) => { expect(message.content).to.be.undefined; return true; }); + expect(res).to.be.true; + }); + + it("should not be sent when there is no history", async () => { + const channelC = new MessageChannel(channelId, "carol", { + causalHistorySize: 2 + }); + const res = await channelC.pushOutgoingSyncMessage(async (_msg) => { + throw "callback was called when it's not expected"; + }); + expect(res).to.be.false; }); it("should not be added to outgoing buffer, bloom filter, or local log", async () => { - await channelA.pushOutgoingSyncMessage(); + const res = await channelA.pushOutgoingSyncMessage(); + expect(res).to.be.true; const outgoingBuffer = channelA["outgoingBuffer"] as Message[]; expect(outgoingBuffer.length).to.equal(0); @@ -1108,15 +1135,16 @@ describe("MessageChannel", function () { ).to.equal(false); const localLog = channelA["localHistory"]; - expect(localLog.length).to.equal(0); + expect(localLog.length).to.equal(1); // beforeEach adds one message }); it("should not be delivered", async () => { const timestampBefore = channelB["lamportTimestamp"]; - await channelA.pushOutgoingSyncMessage(async (message) => { + const res = await channelA.pushOutgoingSyncMessage(async (message) => { await receiveMessage(channelB, message); return true; }); + expect(res).to.be.true; const timestampAfter = channelB["lamportTimestamp"]; expect(timestampAfter).to.equal(timestampBefore); @@ -1130,20 +1158,23 @@ describe("MessageChannel", function () { }); it("should update ack status of messages in outgoing buffer", async () => { + const channelC = new MessageChannel(channelId, "carol", { + causalHistorySize: 2 + }); for (const m of messagesA) { - await sendMessage(channelA, utf8ToBytes(m), async (message) => { + await sendMessage(channelC, utf8ToBytes(m), async (message) => { await receiveMessage(channelB, message); return { success: true }; }); } await sendSyncMessage(channelB, async (message) => { - await receiveMessage(channelA, message); + await receiveMessage(channelC, message); return true; }); - const causalHistorySize = channelA["causalHistorySize"]; - const outgoingBuffer = channelA["outgoingBuffer"] as Message[]; + const causalHistorySize = channelC["causalHistorySize"]; + const outgoingBuffer = channelC["outgoingBuffer"] as Message[]; expect(outgoingBuffer.length).to.equal( messagesA.length - causalHistorySize ); diff --git a/packages/sds/src/message_channel/message_channel.ts b/packages/sds/src/message_channel/message_channel.ts index 6375e819f7..3df21f160a 100644 --- a/packages/sds/src/message_channel/message_channel.ts +++ b/packages/sds/src/message_channel/message_channel.ts @@ -56,7 +56,7 @@ export type ILocalHistory = Pick< export class MessageChannel extends TypedEventEmitter { public readonly channelId: ChannelId; public readonly senderId: SenderId; - private lamportTimestamp: number; + private lamportTimestamp: bigint; private filter: DefaultBloomFilter; private outgoingBuffer: ContentMessage[]; private possibleAcks: Map; @@ -95,9 +95,8 @@ export class MessageChannel extends TypedEventEmitter { super(); this.channelId = channelId; this.senderId = senderId; - // SDS RFC says to use nanoseconds, but current time in nanosecond is > Number.MAX_SAFE_INTEGER - // So instead we are using milliseconds and proposing a spec change (TODO) - this.lamportTimestamp = Date.now(); + // Initialize channel lamport timestamp to current time in milliseconds. + this.lamportTimestamp = BigInt(Date.now()); this.filter = new DefaultBloomFilter(DEFAULT_BLOOM_FILTER_OPTIONS); this.outgoingBuffer = []; this.possibleAcks = new Map(); @@ -369,7 +368,7 @@ export class MessageChannel extends TypedEventEmitter { public async pushOutgoingSyncMessage( callback?: (message: SyncMessage) => Promise ): Promise { - this.lamportTimestamp++; + this.lamportTimestamp = lamportTimestampIncrement(this.lamportTimestamp); const message = new SyncMessage( // does not need to be secure randomness `sync-${Math.random().toString(36).substring(2)}`, @@ -385,6 +384,14 @@ export class MessageChannel extends TypedEventEmitter { undefined ); + if (!message.causalHistory || message.causalHistory.length === 0) { + log.info( + this.senderId, + "no causal history in sync message, aborting sending" + ); + return false; + } + if (callback) { try { await callback(message); @@ -401,7 +408,8 @@ export class MessageChannel extends TypedEventEmitter { throw error; } } - return false; + // No problem encountered so returning true + return true; } private _pushIncomingMessage(message: Message): void { @@ -526,7 +534,7 @@ export class MessageChannel extends TypedEventEmitter { retrievalHint?: Uint8Array; }> ): Promise { - this.lamportTimestamp++; + this.lamportTimestamp = lamportTimestampIncrement(this.lamportTimestamp); const messageId = MessageChannel.getMessageId(payload); @@ -724,3 +732,12 @@ export class MessageChannel extends TypedEventEmitter { }); } } + +export function lamportTimestampIncrement(lamportTimestamp: bigint): bigint { + const now = BigInt(Date.now()); + lamportTimestamp++; + if (now > lamportTimestamp) { + return now; + } + return lamportTimestamp; +}