diff --git a/.github/workflows/backend-integration.yml b/.github/workflows/backend-integration.yml index 61e55d3c7..e3f9407a5 100644 --- a/.github/workflows/backend-integration.yml +++ b/.github/workflows/backend-integration.yml @@ -12,7 +12,7 @@ jobs: if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'" strategy: matrix: - node: ["22.14.0"] + node: ["24.13.0"] fail-fast: false runs-on: ubuntu-latest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd6611652..3799c66b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'" strategy: matrix: - node: ["22.14.0"] + node: ["24.13.0"] flavor: ["dev", "prod"] fail-fast: false runs-on: ubuntu-latest @@ -96,7 +96,7 @@ jobs: name: "Cache assets for builds" strategy: matrix: - node: ["22.14.0"] + node: ["24.13.0"] runs-on: ubuntu-latest steps: - name: Checkout @@ -202,7 +202,7 @@ jobs: if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'" strategy: matrix: - node: ["22.14.0"] + node: ["24.13.0"] flavor: ["dev", "prod"] fail-fast: false runs-on: ubuntu-latest @@ -304,6 +304,7 @@ jobs: strategy: fail-fast: false matrix: + node: ["24.13.0"] module: ["mempool", "liquid", "testnet4"] name: E2E tests for ${{ matrix.module }} @@ -316,7 +317,7 @@ jobs: - name: Setup node uses: actions/setup-node@v3 with: - node-version: 22 + node-version: ${{ matrix.node }} cache: "npm" cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json @@ -324,9 +325,9 @@ jobs: uses: actions/cache@v4 with: path: ${{ matrix.module }}/frontend/node_modules - key: ${{ runner.os }}-e2e-${{ matrix.module }}-node-22-${{ hashFiles('${{ matrix.module }}/frontend/package-lock.json') }} + key: ${{ runner.os }}-e2e-${{ matrix.module }}-node-${{ matrix.node }}-${{ hashFiles('${{ matrix.module }}/frontend/package-lock.json') }} restore-keys: | - ${{ runner.os }}-e2e-${{ matrix.module }}-node-22- + ${{ runner.os }}-e2e-${{ matrix.module }}-node-${{ matrix.node }}- ${{ runner.os }}-e2e-${{ matrix.module }}- - name: Restore cached mining pool assets diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 22a86db56..212dfa9c3 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -17,11 +17,216 @@ permissions: contents: read jobs: + test-images: + # Always run on tag pushes and all pull requests + runs-on: ubuntu-latest + timeout-minutes: 30 + name: Test built Docker images + steps: + - name: Checkout project + uses: actions/checkout@v4 + + - name: Add SHORT_SHA env property with commit short sha + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + SHA="${{ github.event.pull_request.head.sha }}" + else + SHA="${GITHUB_SHA}" + fi + echo "SHORT_SHA=${SHA:0:8}" >> $GITHUB_ENV + + - name: Set TAG from pushed tag or package.json + run: | + if [ "${{ github.event_name }}" = "push" ]; then + TAG="${GITHUB_REF/refs\/tags\//}" + else + FRONTEND_VERSION=$(jq -r '.version' frontend/package.json) + BACKEND_VERSION=$(jq -r '.version' backend/package.json) + if [ "$FRONTEND_VERSION" != "$BACKEND_VERSION" ]; then + echo "Error: Frontend version ($FRONTEND_VERSION) and backend version ($BACKEND_VERSION) do not match" + exit 1 + fi + TAG="v${FRONTEND_VERSION}-${SHORT_SHA}" + fi + echo "TAG=${TAG}" >> $GITHUB_ENV + + - name: Show set environment variables + run: | + printf " TAG: %s\n" "$TAG" + printf " SHORT_SHA: %s\n" "$SHORT_SHA" + + - name: Init repo for Dockerization + run: docker/init.sh "$TAG" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build frontend image locally + run: | + docker buildx build \ + --tag test-frontend:$TAG \ + --build-arg commitHash=$SHORT_SHA \ + --load \ + --platform linux/amd64 \ + ./frontend/ + + - name: Build backend image locally + run: | + docker buildx build \ + --tag test-backend:$TAG \ + --build-context rustgbt=./rust \ + --build-context backend=./backend \ + --build-arg commitHash=$SHORT_SHA \ + --load \ + --platform linux/amd64 \ + ./backend/ + + - name: Prepare docker-compose test file + run: | + cat > /tmp/modify_compose.py << 'SCRIPT_END' + import re + import os + import sys + + # Read the base docker-compose file + with open('docker/docker-compose.yml', 'r') as f: + content = f.read() + + # Get TAG from environment + tag = os.environ.get('TAG', '') + + # Replace image names with locally built test images + content = content.replace('image: mempool/frontend:latest', f'image: test-frontend:{tag}') + content = content.replace('image: mempool/backend:latest', f'image: test-backend:{tag}') + + # Change web port mapping from 80:8080 to 8080:8080 + content = content.replace('- 80:8080', '- 8080:8080') + + # Remove volumes from api service + content = re.sub(r' volumes:\n - \.\/data:\/backend\/cache\n', '', content) + + # For db service: remove user and volumes, add tmpfs and healthcheck + # Remove user line from db service (only the one in db service) + lines = content.split('\n') + in_db_service = False + new_lines = [] + for i, line in enumerate(lines): + if line.strip().startswith('db:'): + in_db_service = True + elif line.strip() and not line.startswith(' ') and not line.startswith('\t'): + in_db_service = False + if in_db_service and line.strip() == 'user: "1000:1000"': + continue + new_lines.append(line) + content = '\n'.join(new_lines) + + # Remove volumes section from db service + content = re.sub(r' volumes:\n - \.\/mysql\/data:\/var\/lib\/mysql\n', '', content) + + # Add tmpfs after stop_grace_period in db service (healthcheck already exists in base file) + db_stop_grace = ' stop_grace_period: 1m' + db_additions = ' stop_grace_period: 1m\n tmpfs:\n - /var/lib/mysql' + content = content.replace(db_stop_grace, db_additions, 1) + + # Add depends_on to web service after ports + web_ports = ' ports:\n - 8080:8080' + web_with_depends = ' ports:\n - 8080:8080\n depends_on:\n - api\n - db' + content = content.replace(web_ports, web_with_depends, 1) + + # Add depends_on to api service after command + api_command = ' command: "./wait-for-it.sh db:3306 --timeout=720 --strict -- ./start.sh"' + api_with_depends = ' command: "./wait-for-it.sh db:3306 --timeout=720 --strict -- ./start.sh"\n depends_on:\n - db' + content = content.replace(api_command, api_with_depends, 1) + + # Write the modified content + with open('docker-compose.test.yml', 'w') as f: + f.write(content) + + print("Generated docker-compose.test.yml") + SCRIPT_END + python3 /tmp/modify_compose.py + cat docker-compose.test.yml + + - name: Start containers + run: | + docker compose -f docker-compose.test.yml up -d + + - name: Wait for services to be ready + run: | + echo "Waiting for all services (web, api, db) to be healthy..." + timeout=120 + elapsed=0 + while [ $elapsed -lt $timeout ]; do + # Check health status for all services + PS_OUTPUT=$(docker compose -f docker-compose.test.yml ps) + HEALTHY_COUNT=$(echo "$PS_OUTPUT" | grep -c "(healthy)" || true) + if [ "$HEALTHY_COUNT" -ge 3 ]; then + echo "All services are healthy!" + echo "$PS_OUTPUT" + break + fi + echo "Waiting for services to be healthy... (${elapsed}s/${timeout}s)" + echo "$PS_OUTPUT" + sleep 2 + elapsed=$((elapsed + 2)) + done + if [ $elapsed -ge $timeout ]; then + echo "Services did not become healthy in time" + docker compose -f docker-compose.test.yml ps + docker compose -f docker-compose.test.yml logs + exit 1 + fi + + - name: Verify containers are healthy + run: | + echo "Checking container health status..." + PS_OUTPUT=$(docker compose -f docker-compose.test.yml ps) + echo "$PS_OUTPUT" + + # Check that all three services (web, api, db) are healthy + HEALTHY_COUNT=$(echo "$PS_OUTPUT" | grep -c "(healthy)" || true) + if [ "$HEALTHY_COUNT" -lt 3 ]; then + echo "Not all containers are healthy. Expected 3 healthy services, found $HEALTHY_COUNT" + docker compose -f docker-compose.test.yml logs + exit 1 + fi + + # Verify each service individually for better error messages + if ! echo "$PS_OUTPUT" | grep -q "web.*(healthy)"; then + echo "Web service is not healthy" + docker compose -f docker-compose.test.yml logs web + exit 1 + fi + if ! echo "$PS_OUTPUT" | grep -q "api.*(healthy)"; then + echo "API service is not healthy" + docker compose -f docker-compose.test.yml logs api + exit 1 + fi + if ! echo "$PS_OUTPUT" | grep -q "db.*(healthy)"; then + echo "Database service is not healthy" + docker compose -f docker-compose.test.yml logs db + exit 1 + fi + + echo "All containers are healthy!" + + - name: Show container logs + if: failure() + run: | + docker compose -f docker-compose.test.yml logs + + - name: Clean up containers + if: always() + run: | + docker compose -f docker-compose.test.yml down -v + build: - # Run on tag pushes OR on PRs that have the "docker" label + needs: test-images + # Run on tag pushes OR on PRs with "docker-push" label (after test-images passes) if: | - github.event_name == 'push' || - (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'docker')) + needs.test-images.result == 'success' && + (github.event_name == 'push' || + (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'docker-push'))) strategy: matrix: service: @@ -33,6 +238,7 @@ jobs: outputs: image-digest-frontend: ${{ matrix.service == 'frontend' && steps.docker-build.outputs.digest || '' }} image-digest-backend: ${{ matrix.service == 'backend' && steps.docker-build.outputs.digest || '' }} + tag: ${{ matrix.service == 'frontend' && (steps.set-tag-push.outputs.tag || steps.set-tag-pr.outputs.tag) || '' }} steps: - name: Replace the current swap file shell: bash @@ -65,7 +271,11 @@ jobs: # Only for tag pushes: use the Git tag as TAG - name: Set TAG from pushed tag if: github.event_name == 'push' - run: echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + id: set-tag-push + run: | + TAG="${GITHUB_REF/refs\/tags\//}" + echo "TAG=${TAG}" >> $GITHUB_ENV + echo "tag=${TAG}" >> $GITHUB_OUTPUT - name: Add SHORT_SHA env property with commit short sha run: | @@ -78,7 +288,10 @@ jobs: - name: Login to Docker for building - run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} - name: Checkout project uses: actions/checkout@v4 @@ -86,13 +299,16 @@ jobs: # For PRs: use package.json version + short sha as TAG - name: Set TAG from service package.json for pull requests if: github.event_name == 'pull_request' + id: set-tag-pr run: | if [ "${{ matrix.service }}" = "frontend" ]; then VERSION=$(jq -r '.version' frontend/package.json) else VERSION=$(jq -r '.version' backend/package.json) fi - echo "TAG=v${VERSION}-${SHORT_SHA}" >> $GITHUB_ENV + TAG="v${VERSION}-${SHORT_SHA}" + echo "TAG=${TAG}" >> $GITHUB_ENV + echo "tag=${TAG}" >> $GITHUB_OUTPUT - name: Show set environment variables run: | @@ -144,7 +360,7 @@ jobs: tag-latest: needs: build - # Only for successful *tag pushes* and only for "plain" versions (no '-') + # Only for successful tag pushes (not PRs with docker-push label) and only for "plain" versions (no '-') if: ${{ needs.build.result == 'success' && github.event_name == 'push' && !contains(github.ref_name, '-') }} runs-on: ubuntu-latest timeout-minutes: 30 @@ -171,7 +387,10 @@ jobs: network=host - name: Login to Docker Hub - run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} - name: Tag as latest for ${{ matrix.service }} run: | diff --git a/.github/workflows/e2e_parameterized.yml b/.github/workflows/e2e_parameterized.yml index 8b07ffe82..9f0cafeb8 100644 --- a/.github/workflows/e2e_parameterized.yml +++ b/.github/workflows/e2e_parameterized.yml @@ -43,7 +43,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v3 with: - node-version: 22.14.0 + node-version: 24.13.0 registry-url: "https://registry.npmjs.org" - name: Install (Prod dependencies only) @@ -151,7 +151,7 @@ jobs: - name: Setup node uses: actions/setup-node@v3 with: - node-version: 22.14.0 + node-version: 24.13.0 cache: "npm" cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json diff --git a/.nvmrc b/.nvmrc index 53d1c14db..e8416a151 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v22 +v24.13.0 diff --git a/backend/jest.config.ts b/backend/jest.config.ts index ae4a6b3b2..7989fca81 100644 --- a/backend/jest.config.ts +++ b/backend/jest.config.ts @@ -1,24 +1,24 @@ -import type { Config } from "@jest/types" +import type { Config } from '@jest/types'; const config: Config.InitialOptions = { - preset: "ts-jest", - testEnvironment: "node", + preset: 'ts-jest', + testEnvironment: 'node', verbose: true, automock: false, collectCoverage: true, - collectCoverageFrom: ["./src/**/**.ts"], - coverageProvider: "v8", + collectCoverageFrom: ['./src/**/**.ts'], + coverageProvider: 'v8', coverageThreshold: { global: { lines: 1 } }, setupFiles: [ - "./testSetup.ts", + './testSetup.ts', ], testPathIgnorePatterns: [ - "/node_modules/", - "/__integration_tests__/", + '/node_modules/', + '/__integration_tests__/', ], -} +}; export default config; diff --git a/backend/jest.integration.config.ts b/backend/jest.integration.config.ts index f395e94be..f0c159aed 100644 --- a/backend/jest.integration.config.ts +++ b/backend/jest.integration.config.ts @@ -1,21 +1,21 @@ -import type { Config } from "@jest/types" +import type { Config } from '@jest/types'; const config: Config.InitialOptions = { - preset: "ts-jest", - testEnvironment: "node", + preset: 'ts-jest', + testEnvironment: 'node', verbose: true, automock: false, collectCoverage: false, - coverageProvider: "v8", + coverageProvider: 'v8', testMatch: [ - "**/__integration_tests__/**/*.test.ts" + '**/__integration_tests__/**/*.test.ts' ], - globalSetup: "./jest.integration.setup.ts", // Start database before all tests + globalSetup: './jest.integration.setup.ts', // Start database before all tests setupFiles: [ - "./testSetup.integration.ts", + './testSetup.integration.ts', ], - globalTeardown: "./jest.integration.teardown.ts", // Stop database after all tests + globalTeardown: './jest.integration.teardown.ts', // Stop database after all tests maxWorkers: 1, // Force sequential execution -} +}; export default config; diff --git a/backend/jest.integration.setup.ts b/backend/jest.integration.setup.ts index 2156e3cdd..c5093b01a 100644 --- a/backend/jest.integration.setup.ts +++ b/backend/jest.integration.setup.ts @@ -35,18 +35,18 @@ module.exports = async () => { try { const composeFile = path.join(__dirname, 'docker-compose.test.yml'); const dockerComposeCmd = getDockerComposeCmd(); - + // Start the container - execSync(`${dockerComposeCmd} -f "${composeFile}" up -d`, { + execSync(`${dockerComposeCmd} -f "${composeFile}" up -d`, { stdio: 'inherit', cwd: __dirname }); - + // Wait for database to be ready console.log('Waiting for database to be ready...'); let attempts = 0; const maxAttempts = 30; - + while (attempts < maxAttempts) { try { execSync(`${dockerComposeCmd} -f "${composeFile}" exec -T db-test mysqladmin ping -h localhost -u mempool_test -pmempool_test --silent`, { diff --git a/backend/jest.integration.teardown.ts b/backend/jest.integration.teardown.ts index c4229a262..386e26ab3 100644 --- a/backend/jest.integration.teardown.ts +++ b/backend/jest.integration.teardown.ts @@ -43,7 +43,7 @@ module.exports = async () => { ]; await DB.query('SET FOREIGN_KEY_CHECKS = 0'); - + for (const table of tables) { try { // Use 'silent' error logging to avoid noise for optional tables that don't exist @@ -52,29 +52,29 @@ module.exports = async () => { // Table might not exist - silently ignore } } - + await DB.query('SET FOREIGN_KEY_CHECKS = 1'); - + logger.info('Integration tests cleanup completed'); - + // Close the database connection pool to prevent Jest from hanging await DB.close(); logger.info('Database connection pool closed'); - + // Clean up singleton resources that have timers or sockets mempool.destroy(); logger.info('Mempool resources cleaned up'); - + // Close logger's UDP socket last (after all logging is done) logger.close(); - + // Stop and remove the Docker test database container // Skip if SKIP_DB_TEARDOWN is set (e.g., when test-with-db.sh manages the database) if (!process.env.SKIP_DB_TEARDOWN) { try { const composeFile = path.join(__dirname, 'docker-compose.test.yml'); const dockerComposeCmd = getDockerComposeCmd(); - execSync(`${dockerComposeCmd} -f "${composeFile}" down -v`, { + execSync(`${dockerComposeCmd} -f "${composeFile}" down -v`, { stdio: 'inherit', cwd: __dirname }); diff --git a/backend/package-lock.json b/backend/package-lock.json index 5543d7312..d1bf11995 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -22,14 +22,14 @@ "rust-gbt": "file:./rust-gbt", "socks-proxy-agent": "~7.0.0", "typescript": "~4.9.3", - "ws": "~8.18.0" + "ws": "~8.19.0" }, "devDependencies": { "@types/compression": "^1.7.2", "@types/crypto-js": "^4.1.1", "@types/express": "^4.17.17", "@types/jest": "^30.0.0", - "@types/ws": "~8.5.10", + "@types/ws": "~8.18.1", "@typescript-eslint/eslint-plugin": "^5.55.0", "@typescript-eslint/parser": "^5.55.0", "eslint": "^8.36.0", @@ -1758,9 +1758,9 @@ "license": "MIT" }, "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "dependencies": { "@types/node": "*" @@ -2669,22 +2669,23 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -2699,11 +2700,40 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -2812,6 +2842,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -3685,20 +3716,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/express/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4249,6 +4266,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -6023,11 +6041,12 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -6065,19 +6084,49 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -7123,9 +7172,9 @@ } }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "engines": { "node": ">=10.0.0" }, @@ -7209,7 +7258,11 @@ } }, "rust-gbt": { - "version": "0.0.1" + "name": "gbt", + "version": "3.0.1", + "engines": { + "node": ">= 12" + } } }, "dependencies": { @@ -8499,9 +8552,9 @@ "dev": true }, "@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "requires": { "@types/node": "*" @@ -9077,22 +9130,22 @@ } }, "body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "requires": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "dependencies": { "debug": { @@ -9103,10 +9156,27 @@ "ms": "2.0.0" } }, + "http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "requires": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" } } }, @@ -9795,14 +9865,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "requires": { - "side-channel": "^1.1.0" - } } } }, @@ -11417,11 +11479,11 @@ "dev": true }, "qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "requires": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" } }, "queue-microtask": { @@ -11436,14 +11498,33 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "dependencies": { + "http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "requires": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + } + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" + } } }, "react-is": { @@ -12129,9 +12210,9 @@ } }, "ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "requires": {} }, "y18n": { diff --git a/backend/package.json b/backend/package.json index 583945e4d..e33137dc6 100644 --- a/backend/package.json +++ b/backend/package.json @@ -53,14 +53,14 @@ "redis": "^4.7.0", "socks-proxy-agent": "~7.0.0", "typescript": "~4.9.3", - "ws": "~8.18.0" + "ws": "~8.19.0" }, "devDependencies": { "@types/compression": "^1.7.2", "@types/crypto-js": "^4.1.1", "@types/express": "^4.17.17", "@types/jest": "^30.0.0", - "@types/ws": "~8.5.10", + "@types/ws": "~8.18.1", "@typescript-eslint/eslint-plugin": "^5.55.0", "@typescript-eslint/parser": "^5.55.0", "eslint": "^8.36.0", diff --git a/backend/src/__integration_tests__/blocks-repository.test.ts b/backend/src/__integration_tests__/blocks-repository.test.ts index b9f449538..91d0353a0 100644 --- a/backend/src/__integration_tests__/blocks-repository.test.ts +++ b/backend/src/__integration_tests__/blocks-repository.test.ts @@ -40,7 +40,7 @@ describe('BlocksRepository Integration Tests', () => { }); const block = await BlocksRepository.$getBlockByHeight(height); - + expect(block).toBeDefined(); expect(block!.height).toBe(height); expect(block!.id).toBe(blockHash); @@ -58,7 +58,7 @@ describe('BlocksRepository Integration Tests', () => { }); const block = await BlocksRepository.$getBlockByHash(blockHash); - + expect(block).toBeDefined(); expect(block!.id).toBe(blockHash); expect(block!.height).toBe(height); @@ -71,36 +71,36 @@ describe('BlocksRepository Integration Tests', () => { test('should check for missing blocks in range', async () => { // Insert blocks with a gap - await insertTestBlock({ - height: 800100, + await insertTestBlock({ + height: 800100, hash: '0000000000000000000100000000000000000000000000000000000000000001', poolId: defaultPoolId }); - await insertTestBlock({ - height: 800102, + await insertTestBlock({ + height: 800102, hash: '0000000000000000000100000000000000000000000000000000000000000003', poolId: defaultPoolId }); const missingBlocks = await BlocksRepository.$getMissingBlocksBetweenHeights(800100, 800102); - + expect(missingBlocks).toContain(800101); }); test('should get latest block height', async () => { - await insertTestBlock({ - height: 800200, + await insertTestBlock({ + height: 800200, hash: '0000000000000000000200000000000000000000000000000000000000000001', poolId: defaultPoolId }); - await insertTestBlock({ - height: 800201, + await insertTestBlock({ + height: 800201, hash: '0000000000000000000200000000000000000000000000000000000000000002', poolId: defaultPoolId }); const height = await BlocksRepository.$mostRecentBlockHeight(); - + expect(height).toBe(800201); }); @@ -121,7 +121,7 @@ describe('BlocksRepository Integration Tests', () => { }); const block = await BlocksRepository.$getBlockByHash(blockHash); - + expect(block).toBeDefined(); expect(block).not.toBeNull(); // The pool should be populated with the test pool's data diff --git a/backend/src/__integration_tests__/database-migration.test.ts b/backend/src/__integration_tests__/database-migration.test.ts index 7b8a5dd74..9270f9fc8 100644 --- a/backend/src/__integration_tests__/database-migration.test.ts +++ b/backend/src/__integration_tests__/database-migration.test.ts @@ -18,7 +18,7 @@ describe('Database Migration Integration Tests', () => { }); test('should have schema version in state table', async () => { - const [result] = await DB.query("SELECT number FROM state WHERE name = 'schema_version'"); + const [result] = await DB.query('SELECT number FROM state WHERE name = \'schema_version\''); expect(result).toHaveLength(1); expect(result[0].number).toBeGreaterThan(0); }); @@ -70,7 +70,7 @@ describe('Database Migration Integration Tests', () => { WHERE TABLE_SCHEMA = 'mempool_test' AND TABLE_NAME = 'blocks'` ); - + const columnNames = columns.map((col: any) => col.COLUMN_NAME); expect(columnNames).toContain('height'); expect(columnNames).toContain('hash'); @@ -87,7 +87,7 @@ describe('Database Migration Integration Tests', () => { WHERE TABLE_SCHEMA = 'mempool_test' AND TABLE_NAME = 'pools'` ); - + const columnNames = columns.map((col: any) => col.COLUMN_NAME); expect(columnNames).toContain('id'); expect(columnNames).toContain('name'); diff --git a/backend/src/__integration_tests__/pools-repository.test.ts b/backend/src/__integration_tests__/pools-repository.test.ts index 7920e7f88..55e503b26 100644 --- a/backend/src/__integration_tests__/pools-repository.test.ts +++ b/backend/src/__integration_tests__/pools-repository.test.ts @@ -43,7 +43,7 @@ describe('PoolsRepository Integration Tests', () => { }); const pool = await PoolsRepository.$getPool('antpool'); - + expect(pool).toBeDefined(); expect(pool!.name).toBe('AntPool'); expect(pool!.slug).toBe('antpool'); @@ -64,7 +64,7 @@ describe('PoolsRepository Integration Tests', () => { }); const pools = await PoolsRepository.$getPools(); - + expect(pools.length).toBeGreaterThanOrEqual(3); }); @@ -77,7 +77,7 @@ describe('PoolsRepository Integration Tests', () => { }); const pool = await PoolsRepository.$getPool('multi-address-pool', false); - + expect(pool).toBeDefined(); const poolAddresses = JSON.parse(pool!.addresses); expect(poolAddresses).toHaveLength(3); @@ -93,7 +93,7 @@ describe('PoolsRepository Integration Tests', () => { }); const pool = await PoolsRepository.$getPool('regex-pool', false); - + expect(pool).toBeDefined(); const poolRegexes = JSON.parse(pool!.regexes); expect(poolRegexes).toHaveLength(2); diff --git a/backend/src/__integration_tests__/test-helpers.ts b/backend/src/__integration_tests__/test-helpers.ts index 74752d9d2..91e47fe2e 100644 --- a/backend/src/__integration_tests__/test-helpers.ts +++ b/backend/src/__integration_tests__/test-helpers.ts @@ -45,7 +45,7 @@ export async function cleanupTestData(): Promise { try { // Disable foreign key checks temporarily for faster cleanup await DB.query('SET FOREIGN_KEY_CHECKS = 0'); - + for (const table of tables) { try { // Use 'silent' error logging to avoid noise for optional tables that don't exist @@ -55,7 +55,7 @@ export async function cleanupTestData(): Promise { // Silently ignore - no need to log since these are expected for optional features } } - + // Re-enable foreign key checks await DB.query('SET FOREIGN_KEY_CHECKS = 1'); } catch (error) { @@ -143,7 +143,7 @@ export async function insertTestBlock(blockData: { const size = blockData.size || 1000000; const weight = blockData.weight || 4000000; const txCount = blockData.tx_count || 2000; - + await DB.query( `INSERT INTO blocks ( height, hash, blockTimestamp, size, weight, tx_count, diff --git a/backend/src/__tests__/api/common.ts b/backend/src/__tests__/api/common.ts index 14ae3c78b..5380391ac 100644 --- a/backend/src/__tests__/api/common.ts +++ b/backend/src/__tests__/api/common.ts @@ -30,7 +30,7 @@ describe('Common', () => { expect(Common.isNonStandard(tx)).toEqual(true); }); }); - + test('should not misclassify as nonstandard transactions', () => { randomTransactions.forEach((tx) => { expect(Common.isNonStandard(tx)).toEqual(false); diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts index 23b3dd346..cf81a5f7f 100644 --- a/backend/src/__tests__/config.test.ts +++ b/backend/src/__tests__/config.test.ts @@ -144,7 +144,7 @@ describe('Mempool Backend Config', () => { }); expect(config.MEMPOOL_SERVICES).toStrictEqual({ - API: "", + API: '', ACCELERATIONS: false, }); diff --git a/backend/src/api/about.routes.ts b/backend/src/api/about.routes.ts index 2020d111d..8ea052d12 100644 --- a/backend/src/api/about.routes.ts +++ b/backend/src/api/about.routes.ts @@ -1,7 +1,7 @@ -import { Application } from "express"; -import config from "../config"; -import axios from "axios"; -import logger from "../logger"; +import { Application } from 'express'; +import config from '../config'; +import axios from 'axios'; +import logger from '../logger'; class AboutRoutes { public initRoutes(app: Application) { diff --git a/backend/src/api/backend-info.ts b/backend/src/api/backend-info.ts index c9a82def4..4a907a067 100644 --- a/backend/src/api/backend-info.ts +++ b/backend/src/api/backend-info.ts @@ -30,6 +30,7 @@ class BackendInfo { lightning: config.LIGHTNING.ENABLED, backend: config.MEMPOOL.BACKEND, coreVersion: '?', + osVersion: `${os.type()} ${os.release()}`, }; this.timer = setInterval(async () => { diff --git a/backend/src/api/bitcoin/bitcoin-api.interface.ts b/backend/src/api/bitcoin/bitcoin-api.interface.ts index 5d8371d27..22f800af7 100644 --- a/backend/src/api/bitcoin/bitcoin-api.interface.ts +++ b/backend/src/api/bitcoin/bitcoin-api.interface.ts @@ -165,44 +165,44 @@ export namespace IBitcoinApi { timeout: number; // (numeric) the median time past of a block at which the deployment is considered failed if not yet locked in since: number; // (numeric) height of the first block to which the status applies statistics: { // (object) numeric statistics about BIP9 signalling for a softfork (only for started status) - period: number; // (numeric) the length in blocks of the BIP9 signalling period - threshold: number; // (numeric) the number of blocks with the version bit set required to activate the feature - elapsed: number; // (numeric) the number of blocks elapsed since the beginning of the current period - count: number; // (numeric) the number of blocks with the version bit set in the current period - possible: boolean; // (boolean) returns false if there are not enough blocks left in this period to pass activation threshold + period: number; // (numeric) the length in blocks of the BIP9 signalling period + threshold: number; // (numeric) the number of blocks with the version bit set required to activate the feature + elapsed: number; // (numeric) the number of blocks elapsed since the beginning of the current period + count: number; // (numeric) the number of blocks with the version bit set in the current period + possible: boolean; // (boolean) returns false if there are not enough blocks left in this period to pass activation threshold } } export interface BlockStats { - "avgfee": number; - "avgfeerate": number; - "avgtxsize": number; - "blockhash": string; - "feerate_percentiles": [number, number, number, number, number]; - "height": number; - "ins": number; - "maxfee": number; - "maxfeerate": number; - "maxtxsize": number; - "medianfee": number; - "mediantime": number; - "mediantxsize": number; - "minfee": number; - "minfeerate": number; - "mintxsize": number; - "outs": number; - "subsidy": number; - "swtotal_size": number; - "swtotal_weight": number; - "swtxs": number; - "time": number; - "total_out": number; - "total_size": number; - "total_weight": number; - "totalfee": number; - "txs": number; - "utxo_increase": number; - "utxo_size_inc": number; + 'avgfee': number; + 'avgfeerate': number; + 'avgtxsize': number; + 'blockhash': string; + 'feerate_percentiles': [number, number, number, number, number]; + 'height': number; + 'ins': number; + 'maxfee': number; + 'maxfeerate': number; + 'maxtxsize': number; + 'medianfee': number; + 'mediantime': number; + 'mediantxsize': number; + 'minfee': number; + 'minfeerate': number; + 'mintxsize': number; + 'outs': number; + 'subsidy': number; + 'swtotal_size': number; + 'swtotal_weight': number; + 'swtxs': number; + 'time': number; + 'total_out': number; + 'total_size': number; + 'total_weight': number; + 'totalfee': number; + 'txs': number; + 'utxo_increase': number; + 'utxo_size_inc': number; } } @@ -213,26 +213,26 @@ export interface TestMempoolAcceptResult { vsize?: number, fees?: { base: number, - "effective-feerate": number, - "effective-includes": string[], + 'effective-feerate': number, + 'effective-includes': string[], }, ['reject-reason']?: string, } export interface SubmitPackageResult { package_msg: string; - "tx-results": { [wtxid: string]: TxResult }; - "replaced-transactions"?: string[]; + 'tx-results': { [wtxid: string]: TxResult }; + 'replaced-transactions'?: string[]; } export interface TxResult { txid: string; - "other-wtxid"?: string; + 'other-wtxid'?: string; vsize?: number; fees?: { base: number; - "effective-feerate"?: number; - "effective-includes"?: string[]; + 'effective-feerate'?: number; + 'effective-includes'?: string[]; }; error?: string; } diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index edd1a2a1e..90543f03f 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -130,7 +130,7 @@ class BitcoinApi implements AbstractBitcoinApi { $getRawBlock(hash: string): Promise { return this.bitcoindClient.getBlock(hash, 0) - .then((raw: string) => Buffer.from(raw, "hex")); + .then((raw: string) => Buffer.from(raw, 'hex')); } $getBlockHash(height: number): Promise { diff --git a/backend/src/api/bitcoin/electrum-api.ts b/backend/src/api/bitcoin/electrum-api.ts index ce8ad3cbb..9e8e17705 100644 --- a/backend/src/api/bitcoin/electrum-api.ts +++ b/backend/src/api/bitcoin/electrum-api.ts @@ -5,7 +5,7 @@ import { IEsploraApi } from './esplora-api.interface'; import { IElectrumApi } from './electrum-api.interface'; import BitcoinApi from './bitcoin-api'; import logger from '../../logger'; -import crypto from "crypto-js"; +import crypto from 'crypto-js'; import loadingIndicators from '../loading-indicators'; import memoryCache from '../memory-cache'; @@ -209,7 +209,7 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi { async $getScriptHashUtxos(scripthash: string): Promise { const utxos = await this.$getScriptHashUnspent(scripthash); const result: IEsploraApi.UTXO[] = []; - for(let utxo of utxos) { + for(const utxo of utxos) { if(utxo.height===0) { //Unconfirmed result.push({ diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts index fb8ee76b2..475aae8b4 100644 --- a/backend/src/api/bitcoin/esplora-api.ts +++ b/backend/src/api/bitcoin/esplora-api.ts @@ -30,6 +30,7 @@ interface FailoverHost { electrs?: string, ssr?: string, core?: string, + os?: string, lastUpdated: number, } } @@ -253,7 +254,12 @@ class FailoverRouter { private async $updateFrontendGitHash(host: FailoverHost): Promise { try { const url = `${host.publicDomain}/resources/config.js`; - const response = await this.pollConnection.get(url, { timeout: config.ESPLORA.FALLBACK_TIMEOUT }); + const response = await this.pollConnection.get( + url, { + timeout: config.ESPLORA.FALLBACK_TIMEOUT, + headers: Common.isLiquid() ? { 'Host': 'liquid.network' } : undefined + } + ); const match = response.data.match(/GIT_COMMIT_HASH\s*=\s*['"](.*?)['"]/); if (match && match[1]?.length) { host.hashes.frontend = match[1]; @@ -276,7 +282,7 @@ class FailoverRouter { path: '/en-US/resources/config.js', method: 'GET', headers: { - 'Host': 'mempool.space' + 'Host': Common.isLiquid() ? 'liquid.network' : 'mempool.space' }, timeout: config.ESPLORA.FALLBACK_TIMEOUT, }, (res) => { @@ -307,13 +313,21 @@ class FailoverRouter { private async $updateBackendVersions(host: FailoverHost): Promise { try { const url = `${host.publicDomain}/api/v1/backend-info`; - const response = await this.pollConnection.get(url, { timeout: config.ESPLORA.FALLBACK_TIMEOUT }); + const response = await this.pollConnection.get( + url, { + timeout: config.ESPLORA.FALLBACK_TIMEOUT, + headers: Common.isLiquid() ? { 'Host': 'liquid.network' } : undefined + } + ); if (response.data?.gitCommit) { host.hashes.backend = response.data.gitCommit; } if (response.data?.coreVersion) { host.hashes.core = response.data.coreVersion; } + if (response.data?.osVersion) { + host.hashes.os = response.data.osVersion; + } } catch (e) { // failed to get backend build hash - do nothing } @@ -322,7 +336,12 @@ class FailoverRouter { private async $updateSSRGitHash(host: FailoverHost): Promise { try { const url = `${host.publicDomain}/ssr/api/status`; - const response = await this.pollConnection.get(url, { timeout: config.ESPLORA.FALLBACK_TIMEOUT }); + const response = await this.pollConnection.get( + url, { + timeout: config.ESPLORA.FALLBACK_TIMEOUT, + headers: Common.isLiquid() ? { 'Host': 'liquid.network' } : undefined + } + ); if (response.data?.gitHash) { host.hashes.ssr = response.data.gitHash; } diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index d33e8fda6..cfd5fcae4 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -106,7 +106,7 @@ class Blocks { const mempool = memPool.getMempool(); let foundInMempool = 0; let totalFound = 0; - let missing = 0; + const missing = 0; // Copy existing transactions from the mempool if (!onlyCoinbase) { @@ -1365,15 +1365,15 @@ class Blocks { /** * Get 15 blocks - * + * * Internally this function uses two methods to get the blocks, and * the method is automatically selected: * - Using previous block hash links * - Using block height - * - * @param fromHeight - * @param limit - * @returns + * + * @param fromHeight + * @param limit + * @returns */ public async $getBlocks(fromHeight?: number, limit: number = 15): Promise { let currentHeight = fromHeight !== undefined ? fromHeight : this.currentBlockHeight; @@ -1405,9 +1405,9 @@ class Blocks { /** * Used for bulk block data query - * - * @param fromHeight - * @param toHeight + * + * @param fromHeight + * @param toHeight */ public async $getBlocksBetweenHeight(fromHeight: number, toHeight: number): Promise { if (!Common.indexingEnabled()) { diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 0064b7710..df1767d76 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -911,7 +911,7 @@ export class Common { if (id.indexOf('/') !== -1) { id = id.slice(0, -2); } - + if (id.indexOf('x') !== -1) { // Already a short id return id; } @@ -1081,7 +1081,7 @@ export class Common { } static getTransactionFromRequest(req: Request, form: boolean): string { - let rawTx: any = typeof req.body === 'object' && form + const rawTx: any = typeof req.body === 'object' && form ? Object.values(req.body)[0] as any : req.body; if (typeof rawTx !== 'string') { @@ -1182,7 +1182,7 @@ export class Common { } } } - }) + }); } // Pass through the input string untouched @@ -1220,14 +1220,14 @@ export class Common { /** * Class to calculate average fee rates of a list of transactions * at certain weight percentiles, in a single pass - * + * * init with: * maxWeight - the total weight to measure percentiles relative to (e.g. 4MW for a single block) * percentileBandWidth - how many weight units to average over for each percentile (as a % of maxWeight) * percentiles - an array of weight percentiles to compute, in % - * + * * then call .processNext(tx) for each transaction, in descending order - * + * * retrieve the final results with .getFeeStats() */ export class OnlineFeeStatsCalculator { diff --git a/backend/src/api/cpfp.ts b/backend/src/api/cpfp.ts index 953664fcc..ad601361c 100644 --- a/backend/src/api/cpfp.ts +++ b/backend/src/api/cpfp.ts @@ -236,7 +236,7 @@ export function calculateMempoolTxCpfp(tx: MempoolTransactionExtended, mempool: /** * Given a root transaction and a list of in-mempool ancestors, * Calculate the CPFP cluster - * + * * @param tx * @param ancestors */ diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts index 00955a758..b0e46be96 100644 --- a/backend/src/api/database-migration.ts +++ b/backend/src/api/database-migration.ts @@ -566,8 +566,8 @@ class DatabaseMigration { await this.$executeQuery('ALTER TABLE `blocks_templates` ADD INDEX `version` (`version`)'); await this.updateToSchemaVersion(67); } - - if (databaseSchemaVersion < 68 && config.MEMPOOL.NETWORK === "liquid") { + + if (databaseSchemaVersion < 68 && config.MEMPOOL.NETWORK === 'liquid') { await this.$executeQuery('TRUNCATE TABLE elements_pegs'); await this.$executeQuery('ALTER TABLE elements_pegs ADD PRIMARY KEY (txid, txindex);'); await this.$executeQuery(`UPDATE state SET number = 0 WHERE name = 'last_elements_block';`); @@ -931,24 +931,24 @@ class DatabaseMigration { // Version 34 await this.$executeQuery('ALTER TABLE `lightning_stats` ADD clearnet_tor_nodes int(11) NOT NULL DEFAULT "0"'); - + // Version 35 await this.$executeQuery('DELETE from `lightning_stats` WHERE added > "2021-09-19"'); await this.$executeQuery('ALTER TABLE `lightning_stats` ADD CONSTRAINT added_unique UNIQUE (added);'); // Version 36 await this.$executeQuery('ALTER TABLE `nodes` ADD status TINYINT NOT NULL DEFAULT "1"'); - + // Version 37 await this.$executeQuery(this.getCreateLNNodesSocketsTableQuery(), await this.$checkIfTableExists('nodes_sockets')); - + // Version 38 await this.$executeQuery(`TRUNCATE lightning_stats`); await this.$executeQuery(`TRUNCATE node_stats`); await this.$executeQuery('ALTER TABLE `lightning_stats` CHANGE `added` `added` timestamp NULL'); await this.$executeQuery('ALTER TABLE `node_stats` CHANGE `added` `added` timestamp NULL'); await this.updateToSchemaVersion(38); - + // Version 39 await this.$executeQuery('ALTER TABLE `nodes` ADD alias_search TEXT NULL DEFAULT NULL AFTER `alias`'); await this.$executeQuery('ALTER TABLE nodes ADD FULLTEXT(alias_search)'); @@ -963,7 +963,7 @@ class DatabaseMigration { // Version 42 await this.$executeQuery('ALTER TABLE `channels` ADD closing_resolved tinyint(1) DEFAULT 0'); - + // Version 43 await this.$executeQuery(this.getCreateLNNodeRecordsTableQuery(), await this.$checkIfTableExists('nodes_records')); @@ -972,7 +972,7 @@ class DatabaseMigration { // Version 45 await this.$executeQuery('ALTER TABLE `blocks_audits` ADD fresh_txs JSON DEFAULT "[]"'); - + // Version 48 await this.$executeQuery('ALTER TABLE `channels` ADD source_checked tinyint(1) DEFAULT 0'); await this.$executeQuery('ALTER TABLE `channels` ADD closing_fee bigint(20) unsigned DEFAULT 0'); @@ -1002,13 +1002,13 @@ class DatabaseMigration { // Version 62 await this.$executeQuery('ALTER TABLE `blocks_audits` ADD expected_fees BIGINT UNSIGNED DEFAULT NULL'); await this.$executeQuery('ALTER TABLE `blocks_audits` ADD expected_weight BIGINT UNSIGNED DEFAULT NULL'); - + // Version 63 await this.$executeQuery('ALTER TABLE `blocks_audits` ADD fullrbf_txs JSON DEFAULT "[]"'); - + // Version 64 await this.$executeQuery('ALTER TABLE `nodes` ADD features text NULL'); - + // Version 65 await this.$executeQuery('ALTER TABLE `blocks_audits` ADD accelerated_txs JSON DEFAULT "[]"'); @@ -1044,8 +1044,8 @@ class DatabaseMigration { ADD INDEX \`closing_reason\` (\`closing_reason\`), ADD INDEX \`closing_resolved\` (\`closing_resolved\`) `); - - // Version 86 + + // Version 86 await this.$executeQuery(` ALTER TABLE \`nodes\` ADD INDEX \`status\` (\`status\`), @@ -1058,20 +1058,20 @@ class DatabaseMigration { // Version 87 await this.$executeQuery('ALTER TABLE `nodes_sockets` ADD INDEX `type` (`type`)'); await this.updateToSchemaVersion(87); - + // Version 88 await this.$executeQuery('ALTER TABLE `lightning_stats` ADD INDEX `added` (`added`)'); - + // Version 89 await this.$executeQuery('ALTER TABLE `geo_names` ADD INDEX `names` (`names`)'); - + // Version 90 await this.$executeQuery('ALTER TABLE `hashrates` ADD INDEX `type` (`type`)'); // Version 91 await this.$executeQuery('ALTER TABLE `blocks_audits` ADD INDEX `time` (`time`)'); } - + if (config.MEMPOOL.NETWORK !== 'liquid') { // Apply all the liquid specific migrations to all other networks // Version 68 @@ -1093,7 +1093,7 @@ class DatabaseMigration { ADD INDEX \`bitcoinaddress\` (\`bitcoinaddress\`), ADD INDEX \`bitcointxid\` (\`bitcointxid\`) `); - + // Version 93 await this.$executeQuery(` ALTER TABLE \`federation_txos\` @@ -1456,7 +1456,7 @@ class DatabaseMigration { pegtxid varchar(65) NOT NULL, pegindex int(11) NOT NULL, pegblocktime int(11) unsigned NOT NULL, - PRIMARY KEY (txid, txindex), + PRIMARY KEY (txid, txindex), FOREIGN KEY (bitcoinaddress) REFERENCES federation_addresses (bitcoinaddress) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`; } diff --git a/backend/src/api/explorer/channels.api.ts b/backend/src/api/explorer/channels.api.ts index 514239d2b..943ef0a12 100644 --- a/backend/src/api/explorer/channels.api.ts +++ b/backend/src/api/explorer/channels.api.ts @@ -53,7 +53,7 @@ class ChannelsApi { GROUP BY nodes_1.public_key, nodes_2.public_key ORDER BY channels.capacity DESC LIMIT 10000 - `; + `; } const [rows]: any = await DB.query(query, params); @@ -241,10 +241,10 @@ class ChannelsApi { let [feeRates2]: any = await DB.query(query); feeRates2 = feeRates2.map(rate => rate.node2_fee_rate); - let feeRates = (feeRates1.concat(feeRates2)).sort((a, b) => a - b); + const feeRates = (feeRates1.concat(feeRates2)).sort((a, b) => a - b); let avgFeeRate = 0; for (const rate of feeRates) { - avgFeeRate += rate; + avgFeeRate += rate; } avgFeeRate /= feeRates.length; const medianFeeRate = feeRates[Math.floor(feeRates.length / 2)]; @@ -257,14 +257,14 @@ class ChannelsApi { let [baseFees2]: any = await DB.query(query); baseFees2 = baseFees2.map(rate => rate.node2_base_fee_mtokens); - let baseFees = (baseFees1.concat(baseFees2)).sort((a, b) => a - b); + const baseFees = (baseFees1.concat(baseFees2)).sort((a, b) => a - b); let avgBaseFee = 0; for (const fee of baseFees) { - avgBaseFee += fee; + avgBaseFee += fee; } avgBaseFee /= baseFees.length; const medianBaseFee = feeRates[Math.floor(baseFees.length / 2)]; - + return { avgCapacity: parseInt(avgCapacity[0].avgCapacity, 10), avgFeeRate: avgFeeRate, @@ -272,7 +272,7 @@ class ChannelsApi { medianCapacity: medianCapacity, medianFeeRate: medianFeeRate, medianBaseFee: medianBaseFee, - } + }; } catch (e) { logger.err(`Cannot calculate channels statistics. Reason: ${e instanceof Error ? e.message : e}`); @@ -456,7 +456,7 @@ class ChannelsApi { allChannels = allChannels.slice(0, 1000); } - const channels: any[] = [] + const channels: any[] = []; for (const row of allChannels) { let channel; if (index >= 0) { diff --git a/backend/src/api/explorer/nodes.api.ts b/backend/src/api/explorer/nodes.api.ts index 22c854fcc..eba6ff516 100644 --- a/backend/src/api/explorer/nodes.api.ts +++ b/backend/src/api/explorer/nodes.api.ts @@ -34,7 +34,7 @@ class NodesApi { `; const [maximums]: any[] = await DB.query(query); - + return { maxLiquidity: maximums[0].maxLiquidity, maxChannels: maximums[0].maxChannels, @@ -78,7 +78,7 @@ class NodesApi { node.city = JSON.parse(node.city); node.country = JSON.parse(node.country); - // Features + // Features node.features = JSON.parse(node.features); node.featuresBits = null; if (node.features) { @@ -87,7 +87,7 @@ class NodesApi { maxBit = Math.max(maxBit, feature.bit); } maxBit = Math.ceil(maxBit / 4) * 4 - 1; - + node.featuresBits = new Array(maxBit + 1).fill(0); for (const feature of node.features) { node.featuresBits[feature.bit] = 1; @@ -394,7 +394,7 @@ class NodesApi { try { const publicKeySearch = search.replace(/[^a-zA-Z0-9]/g, '') + '%'; const aliasSearch = search - .replace(/[-_.]/g, ' ') // Replace all -_. characters with empty space. Eg: "ln.nicehash" becomes "ln nicehash". + .replace(/[-_.]/g, ' ') // Replace all -_. characters with empty space. Eg: "ln.nicehash" becomes "ln nicehash". .replace(/[^a-zA-Z0-9 ]/g, '') // Remove all special characters and keep just A to Z, 0 to 9. .split(' ') .filter(key => key.length) @@ -455,7 +455,7 @@ class NodesApi { } else if (ispList[isp2].ids.includes(channel.isp2ID) === false) { ispList[isp2].ids.push(channel.isp2ID); } - + ispList[isp1].capacity += channel.capacity; ispList[isp1].channels += 1; ispList[isp1].nodes[channel.node1PublicKey] = true; @@ -463,7 +463,7 @@ class NodesApi { ispList[isp2].channels += 1; ispList[isp2].nodes[channel.node2PublicKey] = true; } - + const ispRanking: any[] = []; for (const isp of Object.keys(ispList)) { ispRanking.push([ @@ -494,7 +494,7 @@ class NodesApi { `; const [clearnetCapacity]: any = await DB.query(query); - // Get the total capacity of all channels which have both nodes on Tor + // Get the total capacity of all channels which have both nodes on Tor query = ` SELECT SUM(capacity) as capacity FROM ( @@ -642,11 +642,11 @@ class NodesApi { for (const country of nodesCountPerCountry) { nodesPerCountry.push({ name: JSON.parse(country.names), - iso: country.iso_code, + iso: country.iso_code, count: country.nodesCount, share: Math.floor(country.nodesCount / nodesWithAS[0].total * 10000) / 100, capacity: country.capacity, - }) + }); } return nodesPerCountry; @@ -665,7 +665,7 @@ class NodesApi { if ((node.last_update ?? 0) < 1514736061) { // January 1st 2018 node.last_update = null; } - + const uniqueAddr = [...new Set(node.addresses?.map(a => a.addr))]; const formattedSockets = (uniqueAddr.join(',')) ?? ''; diff --git a/backend/src/api/explorer/nodes.routes.ts b/backend/src/api/explorer/nodes.routes.ts index 6f9539fcb..113e29b9a 100644 --- a/backend/src/api/explorer/nodes.routes.ts +++ b/backend/src/api/explorer/nodes.routes.ts @@ -39,7 +39,7 @@ class NodesRoutes { private async $getNodeGroup(req: Request, res: Response) { try { let nodesList; - let nodes: any[] = []; + const nodes: any[] = []; switch (config.MEMPOOL.NETWORK) { case 'testnet': nodesList = [ @@ -174,7 +174,7 @@ class NodesRoutes { ]; } - for (let pubKey of nodesList) { + for (const pubKey of nodesList) { try { const node = await nodesApi.$getNode(pubKey); if (node) { diff --git a/backend/src/api/fetch-version.ts b/backend/src/api/fetch-version.ts index cb0813c35..7183007a8 100644 --- a/backend/src/api/fetch-version.ts +++ b/backend/src/api/fetch-version.ts @@ -1,5 +1,5 @@ import fs from 'fs'; -import path from "path"; +import path from 'path'; const { spawnSync } = require('child_process'); function getVersion(): string { @@ -29,9 +29,9 @@ function getGitCommit(): string { const versionInfo = { version: getVersion(), gitCommit: getGitCommit() -} +}; fs.writeFileSync( path.join(__dirname, 'version.json'), - JSON.stringify(versionInfo, null, 2) + "\n" + JSON.stringify(versionInfo, null, 2) + '\n' ); diff --git a/backend/src/api/lightning/clightning/clightning-client.ts b/backend/src/api/lightning/clightning/clightning-client.ts index d80341063..28fc8e9f3 100644 --- a/backend/src/api/lightning/clightning/clightning-client.ts +++ b/backend/src/api/lightning/clightning/clightning-client.ts @@ -116,7 +116,7 @@ class LightningError extends Error { const defaultRpcPath = path.join(homedir(), '.lightning') , fStat = (...p) => statSync(path.join(...p)) - , fExists = (...p) => existsSync(path.join(...p)) + , fExists = (...p) => existsSync(path.join(...p)); export default class CLightningClient extends EventEmitter implements AbstractLightningApi { private rpcPath: string; @@ -141,9 +141,9 @@ export default class CLightningClient extends EventEmitter implements AbstractLi // main data directory provided, default to using the bitcoin mainnet subdirectory // to be removed in v0.2.0 else if (fExists(rpcPath, 'bitcoin', 'lightning-rpc')) { - logger.warn(`${rpcPath}/lightning-rpc is missing, using the bitcoin mainnet subdirectory at ${rpcPath}/bitcoin instead.`, logger.tags.ln) - logger.warn(`specifying the main lightning data directory is deprecated, please specify the network directory explicitly.\n`, logger.tags.ln) - rpcPath = path.join(rpcPath, 'bitcoin', 'lightning-rpc') + logger.warn(`${rpcPath}/lightning-rpc is missing, using the bitcoin mainnet subdirectory at ${rpcPath}/bitcoin instead.`, logger.tags.ln); + logger.warn(`specifying the main lightning data directory is deprecated, please specify the network directory explicitly.\n`, logger.tags.ln); + rpcPath = path.join(rpcPath, 'bitcoin', 'lightning-rpc'); } } diff --git a/backend/src/api/lightning/lnd/lnd-api.ts b/backend/src/api/lightning/lnd/lnd-api.ts index f4099e82b..eb48b5f96 100644 --- a/backend/src/api/lightning/lnd/lnd-api.ts +++ b/backend/src/api/lightning/lnd/lnd-api.ts @@ -46,10 +46,10 @@ class LndApi implements AbstractLightningApi { for (const node of graph.nodes) { const nodeFeatures: ILightningApi.Feature[] = []; - for (const bit in node.features) { + for (const bit in node.features) { nodeFeatures.push({ bit: parseInt(bit, 10), - name: node.features[bit].name, + name: node.features[bit].name, is_required: node.features[bit].is_required, is_known: node.features[bit].is_known, }); diff --git a/backend/src/api/liquid/elements-parser.ts b/backend/src/api/liquid/elements-parser.ts index 727865b95..0e5818ab0 100644 --- a/backend/src/api/liquid/elements-parser.ts +++ b/backend/src/api/liquid/elements-parser.ts @@ -87,7 +87,7 @@ class ElementsParser { logger.debug(`Saved L-BTC peg from Liquid block height #${height} with TXID ${txid}.`); if (amount > 0) { // Peg-in - + // Add the address to the federation addresses table await DB.query(`INSERT IGNORE INTO federation_addresses (bitcoinaddress) VALUES (?)`, [bitcoinaddress]); @@ -95,7 +95,7 @@ class ElementsParser { const query_utxos = `INSERT IGNORE INTO federation_txos (txid, txindex, bitcoinaddress, amount, blocknumber, blocktime, unspent, lastblockupdate, lasttimeupdate, timelock, expiredAt, emergencyKey, pegtxid, pegindex, pegblocktime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; const params_utxos: (string | number)[] = [bitcointxid, bitcoinindex, bitcoinaddress, amount, bitcoinblock, bitcoinBlockTime, 1, bitcoinblock - 1, 0, 4032, 0, 0, txid, txindex, blockTime]; await DB.query(query_utxos, params_utxos); - const [minBlockUpdate] = await DB.query(`SELECT MIN(lastblockupdate) AS lastblockupdate FROM federation_txos WHERE unspent = 1`) + const [minBlockUpdate] = await DB.query(`SELECT MIN(lastblockupdate) AS lastblockupdate FROM federation_txos WHERE unspent = 1`); await this.$saveLastBlockAuditToDatabase(minBlockUpdate[0]['lastblockupdate']); logger.debug(`Saved new Federation UTXO ${bitcointxid}:${bitcoinindex} belonging to ${bitcoinaddress} to federation txos`); @@ -174,7 +174,7 @@ class ElementsParser { const runningFor = (Date.now() / 1000) - startedAt; const blockPerSeconds = indexedThisRun / elapsedSeconds; indexingSpeeds.push(blockPerSeconds); - if (indexingSpeeds.length > 100) indexingSpeeds.shift(); // Keep the length of the up to 100 last indexing speeds + if (indexingSpeeds.length > 100) {indexingSpeeds.shift();} // Keep the length of the up to 100 last indexing speeds const meanIndexingSpeed = indexingSpeeds.reduce((a, b) => a + b, 0) / indexingSpeeds.length; const eta = (auditProgress.confirmedTip - auditProgress.lastBlockAudit) / meanIndexingSpeed; logger.debug(`Scanning ${utxos.length} Federation UTXOs and ${redeemAddresses.length} Peg-Out Addresses at Bitcoin block height #${auditProgress.lastBlockAudit} / #${auditProgress.confirmedTip} | ~${meanIndexingSpeed.toFixed(2)} blocks/sec | elapsed: ${(runningFor / 60).toFixed(0)} minutes | ETA: ${(eta / 60).toFixed(0)} minutes`); @@ -189,7 +189,7 @@ class ElementsParser { await this.$parseBitcoinBlock(block, spentAsTip, unspentAsTip, auditProgress.confirmedTip, redeemAddresses); // Finally, update the lastblockupdate of the remaining UTXOs and save to the database - const [minBlockUpdate] = await DB.query(`SELECT MIN(lastblockupdate) AS lastblockupdate FROM federation_txos WHERE unspent = 1`) + const [minBlockUpdate] = await DB.query(`SELECT MIN(lastblockupdate) AS lastblockupdate FROM federation_txos WHERE unspent = 1`); await this.$saveLastBlockAuditToDatabase(minBlockUpdate[0]['lastblockupdate']); auditProgress = await this.$getAuditProgress(); @@ -201,11 +201,11 @@ class ElementsParser { } catch (e) { this.isUtxosUpdatingRunning = false; throw new Error(e instanceof Error ? e.message : 'Error'); - } + } } // Get the UTXOs that need to be scanned in block height (UTXOs that were last updated in the block height - 1) - protected async $getFederationUtxosToScan(height: number) { + protected async $getFederationUtxosToScan(height: number) { const query = `SELECT txid, txindex, bitcoinaddress, amount, blocknumber, timelock, expiredAt FROM federation_txos WHERE lastblockupdate = ? AND unspent = 1`; const [rows] = await DB.query(query, [height - 1]); return rows as any[]; @@ -220,7 +220,7 @@ class ElementsParser { const result = await bitcoinSecondClient.getTxOut(utxo.txid, utxo.txindex, false); result ? unspentAsTip.push(utxo) : spentAsTip.push(utxo); } - + return {spentAsTip, unspentAsTip}; } @@ -296,7 +296,7 @@ class ElementsParser { } } - for (const utxo of spentAsTip) { + for (const utxo of spentAsTip) { if (utxo.expiredAt === 0 && block.height >= utxo.blocknumber + utxo.timelock) { // The UTXO is expiring in this block await DB.query(`UPDATE federation_txos SET lastblockupdate = ?, expiredAt = ? WHERE txid = ? AND txindex = ?`, [block.height, block.time, utxo.txid, utxo.txindex]); } else { @@ -308,7 +308,7 @@ class ElementsParser { if (utxo.expiredAt === 0 && block.height >= utxo.blocknumber + utxo.timelock) { // The UTXO is expiring in this block await DB.query(`UPDATE federation_txos SET unspent = 0, lastblockupdate = ?, expiredAt = ? WHERE txid = ? AND txindex = ?`, [confirmedTip, block.time, utxo.txid, utxo.txindex]); } else if (utxo.expiredAt === 0 && confirmedTip >= utxo.blocknumber + utxo.timelock) { // The UTXO is expiring before the tip: we need to keep track of it - await DB.query(`UPDATE federation_txos SET lastblockupdate = ? WHERE txid = ? AND txindex = ?`, [utxo.blocknumber + utxo.timelock - 1, utxo.txid, utxo.txindex]); + await DB.query(`UPDATE federation_txos SET lastblockupdate = ? WHERE txid = ? AND txindex = ?`, [utxo.blocknumber + utxo.timelock - 1, utxo.txid, utxo.txindex]); } else { await DB.query(`UPDATE federation_txos SET lastblockupdate = ? WHERE txid = ? AND txindex = ?`, [confirmedTip, utxo.txid, utxo.txindex]); } @@ -336,7 +336,7 @@ class ElementsParser { return { bitcoinBlocks: result.blocks, bitcoinHeaders: result.headers, - } + }; } protected async $getLastBlockAudit(): Promise { @@ -384,7 +384,7 @@ class ElementsParser { AND (expiredAt = 0 OR expiredAt > UNIX_TIMESTAMP(LAST_DAY(FROM_UNIXTIME(blocktime)) + INTERVAL 1 DAY)) GROUP BY - date;`; + date;`; const [rows] = await DB.query(query); return rows; } @@ -444,7 +444,7 @@ class ElementsParser { const [rows] = await DB.query(query); return rows; } - + // Get the total number of federation addresses public async $getFederationAddressesNumber(): Promise { const query = `SELECT COUNT(DISTINCT bitcoinaddress) AS address_count FROM federation_txos WHERE unspent = 1 AND expiredAt = 0;`; diff --git a/backend/src/api/liquid/liquid.routes.ts b/backend/src/api/liquid/liquid.routes.ts index 563cbaced..3c99900b3 100644 --- a/backend/src/api/liquid/liquid.routes.ts +++ b/backend/src/api/liquid/liquid.routes.ts @@ -14,7 +14,7 @@ class LiquidRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'asset/:assetId/icon', this.getLiquidIcon) .get(config.MEMPOOL.API_URL_PREFIX + 'assets/group/:id', this.$getAssetGroup) ; - + if (config.DATABASE.ENABLED) { app .get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs', this.$getElementsPegs) diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts index 34a27f510..ac9b4ba52 100644 --- a/backend/src/api/mempool.ts +++ b/backend/src/api/mempool.ts @@ -38,7 +38,7 @@ class Mempool { private mempoolProtection = 0; private latestTransactions: any[] = []; - private ESPLORA_MISSING_TX_WARNING_THRESHOLD = 100; + private ESPLORA_MISSING_TX_WARNING_THRESHOLD = 100; private SAMPLE_TIME = 10000; // In ms private timer = new Date().getTime(); private missingTxCount = 0; @@ -51,15 +51,15 @@ class Mempool { // Initialize mempoolInfo here to avoid circular dependency issues // Use config directly instead of Common.isLiquid() to break circular dependency const isLiquid = config.MEMPOOL.NETWORK === 'liquid' || config.MEMPOOL.NETWORK === 'liquidtestnet'; - this.mempoolInfo = { - loaded: false, - size: 0, - bytes: 0, - usage: 0, + this.mempoolInfo = { + loaded: false, + size: 0, + bytes: 0, + usage: 0, total_fee: 0, - maxmempool: 300000000, - mempoolminfee: isLiquid ? 0.00000100 : 0.00001000, - minrelaytxfee: isLiquid ? 0.00000100 : 0.00001000 + maxmempool: 300000000, + mempoolminfee: isLiquid ? 0.00000100 : 0.00001000, + minrelaytxfee: isLiquid ? 0.00000100 : 0.00001000 }; this.txPerSecondInterval = setInterval(this.updateTxPerSecond.bind(this), 1000); } diff --git a/backend/src/api/mining/mining-routes.ts b/backend/src/api/mining/mining-routes.ts index 806b113f1..d63b13a08 100644 --- a/backend/src/api/mining/mining-routes.ts +++ b/backend/src/api/mining/mining-routes.ts @@ -1,12 +1,12 @@ import { Application, Request, Response } from 'express'; -import config from "../../config"; +import config from '../../config'; import logger from '../../logger'; import BlocksAuditsRepository from '../../repositories/BlocksAuditsRepository'; import BlocksRepository from '../../repositories/BlocksRepository'; import DifficultyAdjustmentsRepository from '../../repositories/DifficultyAdjustmentsRepository'; import HashratesRepository from '../../repositories/HashratesRepository'; import bitcoinClient from '../bitcoin/bitcoin-client'; -import mining from "./mining"; +import mining from './mining'; import PricesRepository from '../../repositories/PricesRepository'; import AccelerationRepository from '../../repositories/AccelerationRepository'; import accelerationApi from '../services/acceleration'; diff --git a/backend/src/api/mining/mining.ts b/backend/src/api/mining/mining.ts index 2b007e9c5..ef58b6cd9 100644 --- a/backend/src/api/mining/mining.ts +++ b/backend/src/api/mining/mining.ts @@ -26,7 +26,7 @@ class Mining { private blocksPriceIndexingRunning = false; public lastHashrateIndexingDate: number | null = null; public lastWeeklyHashrateIndexingDate: number | null = null; - + public reindexHashrateRequested = false; public reindexDifficultyAdjustmentRequested = false; @@ -66,7 +66,7 @@ class Mining { {from, to} ); } - + /** * Get historical block rewards */ @@ -175,8 +175,8 @@ class Mining { const blockCount1w: number = await BlocksRepository.$blockCount(pool.id, '1w'); const totalBlock1w: number = await BlocksRepository.$blockCount(null, '1w'); - const avgHealth = await BlocksRepository.$getAvgBlockHealthPerPoolId(pool.id); - const totalReward = await BlocksRepository.$getTotalRewardForPoolId(pool.id); + const avgHealth = await BlocksRepository.$getAvgBlockHealthPerPoolId(pool.id); + const totalReward = await BlocksRepository.$getTotalRewardForPoolId(pool.id); let currentEstimatedHashrate = 0; try { @@ -235,7 +235,7 @@ class Mining { const indexedTimestamp = await HashratesRepository.$getWeeklyHashrateTimestamps(); const hashrates: any[] = []; - + const lastMonday = new Date(now.setDate(now.getDate() - (now.getDay() + 6) % 7)); const lastMondayMidnight = this.getDateMidnight(lastMonday); let toTimestamp = lastMondayMidnight.getTime(); @@ -537,7 +537,7 @@ class Mining { let totalInserted = 0; try { - const prices: any[] = await PricesRepository.$getPricesTimesAndId(); + const prices: any[] = await PricesRepository.$getPricesTimesAndId(); const blocksWithoutPrices: any[] = await BlocksRepository.$getBlocksWithoutPrice(); const blocksPrices: BlockPrice[] = []; @@ -609,11 +609,11 @@ class Mining { while (currentBlockHeight > 0) { const indexedBlocks = await BlocksRepository.$getBlocksMissingCoinStatsIndex( currentBlockHeight, currentBlockHeight - 10000); - + for (const block of indexedBlocks) { const txoutset = await bitcoinClient.getTxoutSetinfo('none', block.height); await BlocksRepository.$updateCoinStatsIndexData(block.hash, txoutset.txouts, - Math.round(txoutset.block_info.prevout_spent * 100000000)); + Math.round(txoutset.block_info.prevout_spent * 100000000)); ++totalIndexed; const elapsedSeconds = Math.max(1, new Date().getTime() / 1000 - timer); @@ -688,7 +688,7 @@ class Mining { default: return 1 * scale; } } - + // Finds the oldest block in a consecutive chain back from the tip // assumes `blocks` is sorted in ascending height order diff --git a/backend/src/api/rbf-cache.ts b/backend/src/api/rbf-cache.ts index edd22a582..31c5c5e9c 100644 --- a/backend/src/api/rbf-cache.ts +++ b/backend/src/api/rbf-cache.ts @@ -1,10 +1,10 @@ -import config from "../config"; -import logger from "../logger"; -import { MempoolTransactionExtended, TransactionStripped } from "../mempool.interfaces"; +import config from '../config'; +import logger from '../logger'; +import { MempoolTransactionExtended, TransactionStripped } from '../mempool.interfaces'; import bitcoinApi from './bitcoin/bitcoin-api-factory'; -import { IEsploraApi } from "./bitcoin/esplora-api.interface"; -import { Common } from "./common"; -import redisCache from "./redis-cache"; +import { IEsploraApi } from './bitcoin/esplora-api.interface'; +import { Common } from './common'; +import redisCache from './redis-cache'; export interface RbfTransaction extends TransactionStripped { rbf?: boolean; diff --git a/backend/src/api/statistics/statistics-api.ts b/backend/src/api/statistics/statistics-api.ts index fa13b60b9..5a3222e82 100644 --- a/backend/src/api/statistics/statistics-api.ts +++ b/backend/src/api/statistics/statistics-api.ts @@ -514,7 +514,7 @@ class StatisticsApi { vsize_1600: completeVsizes[36], vsize_1800: completeVsizes[37], vsize_2000: completeVsizes[38], - } + }; }); } } diff --git a/backend/src/api/transaction-utils.ts b/backend/src/api/transaction-utils.ts index da9e908e8..bc5b65607 100644 --- a/backend/src/api/transaction-utils.ts +++ b/backend/src/api/transaction-utils.ts @@ -116,7 +116,7 @@ class TransactionUtils { public extendMempoolTransaction(transaction: IEsploraApi.Transaction): MempoolTransactionExtended { const vsize = Math.ceil(transaction.weight / 4); const fractionalVsize = (transaction.weight / 4); - let sigops = Common.isLiquid() ? 0 : (transaction.sigops != null ? transaction.sigops : this.countSigops(transaction)); + const sigops = Common.isLiquid() ? 0 : (transaction.sigops != null ? transaction.sigops : this.countSigops(transaction)); // https://github.com/bitcoin/bitcoin/blob/e9262ea32a6e1d364fb7974844fadc36f931f8c6/src/policy/policy.cpp#L295-L298 const adjustedVsize = Math.max(fractionalVsize, sigops * 5); // adjusted vsize = Max(weight, sigops * bytes_per_sigop) / witness_scale_factor const feePerVbytes = (transaction.fee || 0) / fractionalVsize; @@ -365,7 +365,7 @@ class TransactionUtils { * the script item if it is a script spend. */ public witnessToP2TRScript(witness: string[]): string | null { - if (witness.length < 2) return null; + if (witness.length < 2) {return null;} // Note: see BIP341 for parsing details of witness stack // If there are at least two witness elements, and the first byte of the @@ -375,7 +375,7 @@ class TransactionUtils { // If there are at least two witness elements left, script path spending is used. // Call the second-to-last stack element s, the script. // (Note: this phrasing from BIP341 assumes we've *removed* the annex from the stack) - if (hasAnnex && witness.length < 3) return null; + if (hasAnnex && witness.length < 3) {return null;} const positionOfScript = hasAnnex ? witness.length - 3 : witness.length - 2; return witness[positionOfScript]; } @@ -482,7 +482,7 @@ class TransactionUtils { return 'unknown'; } } - + } export default new TransactionUtils(); diff --git a/backend/src/api/tx-selection-worker.ts b/backend/src/api/tx-selection-worker.ts index 8ac7328fe..98dd42909 100644 --- a/backend/src/api/tx-selection-worker.ts +++ b/backend/src/api/tx-selection-worker.ts @@ -18,7 +18,7 @@ if (parentPort) { mempool.delete(uid); }); } - + const { blocks, rates, clusters } = makeBlockTemplates(mempool); // return the result to main thread. @@ -38,7 +38,7 @@ function makeBlockTemplates(mempool: Map) const auditPool: Map = new Map(); const mempoolArray: AuditTransaction[] = []; const cpfpClusters: Map = new Map(); - + mempool.forEach(tx => { tx.dirty = false; // initializing everything up front helps V8 optimize property access later @@ -85,7 +85,7 @@ function makeBlockTemplates(mempool: Map) // (i.e. the package rooted in the transaction with the best ancestor score) const blocks: number[][] = []; let blockWeight = 4000; - let blockSigops = 0; + const blockSigops = 0; let transactions: AuditTransaction[] = []; const modified: PairingHeap = new PairingHeap((a, b): boolean => { if (a.score === b.score) { diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 532d6d4a1..6186932b2 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -1002,7 +1002,7 @@ class WebsocketHandler { }); } } - + async handleNewBlock(block: BlockExtended, txIds: string[], transactions: MempoolTransactionExtended[]): Promise { if (!this.webSocketServers.length) { throw new Error('No WebSocket.Server have been set'); @@ -1518,7 +1518,7 @@ class WebsocketHandler { if (client['track-rbf']) { numRbfSubs++; } - }) + }); } let count = 0; diff --git a/backend/src/config.ts b/backend/src/config.ts index 3fe3db2ee..f7f8b371b 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -398,7 +398,7 @@ class Config implements IConfig { }); return next; }); - } + }; } export default new Config(); diff --git a/backend/src/indexer.ts b/backend/src/indexer.ts index ca0b2303c..d4d2197e1 100644 --- a/backend/src/indexer.ts +++ b/backend/src/indexer.ts @@ -46,7 +46,7 @@ class Indexer { synced: indexes[indexName].synced, best_block_height: indexes[indexName].best_block_height, }; - logger.info(`Core index '${indexName}' is ${indexes[indexName].synced ? 'synced' : 'not synced'}. Best block height is ${indexes[indexName].best_block_height}`); + logger.info(`Core index '${indexName}' is ${indexes[indexName].synced ? 'synced' : 'not synced'}. Best block height is ${indexes[indexName].best_block_height}`); updatedCoreIndexes.push(newState); if (indexName === 'coinstatsindex' && newState.synced === true) { @@ -62,9 +62,9 @@ class Indexer { /** * Return the best block height if a core index is available, or 0 if not - * - * @param name - * @returns + * + * @param name + * @returns */ public isCoreIndexReady(name: string): CoreIndex | null { for (const index of this.coreIndexes) { diff --git a/backend/src/logger.ts b/backend/src/logger.ts index 27aa942e6..879dbab5b 100644 --- a/backend/src/logger.ts +++ b/backend/src/logger.ts @@ -36,7 +36,7 @@ class Logger { mining: 'Mining', ln: 'Lightning', goggles: 'Goggles', - }; + }; // @ts-ignore public emerg: ((msg: string, tag?: string) => void); @@ -86,7 +86,7 @@ class Logger { private getNetwork(): string { if (config.LIGHTNING.ENABLED) { - return config.MEMPOOL.NETWORK === 'mainnet' ? 'lightning' : `${config.MEMPOOL.NETWORK}-lightning`; + return config.MEMPOOL.NETWORK === 'mainnet' ? 'lightning' : `${config.MEMPOOL.NETWORK}-lightning`; } if (config.MEMPOOL.NETWORK && config.MEMPOOL.NETWORK !== 'mainnet') { return config.MEMPOOL.NETWORK; diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 1f7ffdd1f..a0888bb50 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -505,6 +505,7 @@ export interface IBackendInfo { version: string; lightning: boolean; coreVersion: string; + osVersion: string; backend: 'esplora' | 'electrum' | 'none'; } diff --git a/backend/src/replication/StatisticsReplication.ts b/backend/src/replication/StatisticsReplication.ts index 49259b458..59ecf202f 100644 --- a/backend/src/replication/StatisticsReplication.ts +++ b/backend/src/replication/StatisticsReplication.ts @@ -51,12 +51,12 @@ class StatisticsReplication { logger.info(`Statistics table is complete, no replication needed`, 'Replication'); return; } - + for (const interval of missingIntervals) { logger.debug(`Missing ${missingStatistics[interval].size} statistics rows in '${interval}' timespan`, 'Replication'); } logger.debug(`Fetching ${missingIntervals.join(', ')} statistics endpoints from trusted servers to fill ${totalMissing} rows missing in statistics`, 'Replication'); - + let totalSynced = 0; let totalMissed = 0; @@ -75,15 +75,15 @@ class StatisticsReplication { } private async $syncStatistics(interval: string, missingTimes: Set): Promise { - + let success = false; let synced = 0; - let missed = new Set(missingTimes); + const missed = new Set(missingTimes); const syncResult = await $sync(`/api/v1/statistics/${interval}`); if (syncResult && syncResult.data?.length) { success = true; - logger.info(`Fetched /api/v1/statistics/${interval} from ${syncResult.server}`); - + logger.info(`Fetched /api/v1/statistics/${interval} from ${syncResult.server}`); + for (const stat of syncResult.data) { const time = this.roundToNearestStep(stat.added, steps[interval]); if (missingTimes.has(time)) { @@ -129,7 +129,7 @@ class StatisticsReplication { startTime < now - day * 30 ? [now - day * 90, now - day * 30, '3m' ] : null, // from 3 months ago to 1 month ago = 2 hours granularity startTime < now - day * 90 ? [now - day * 180, now - day * 90, '6m' ] : null, // from 6 months ago to 3 months ago = 3 hours granularity startTime < now - day * 180 ? [now - day * 365 * 2, now - day * 180, '2y' ] : null, // from 2 years ago to 6 months ago = 8 hours granularity - startTime < now - day * 365 * 2 ? [startTime, now - day * 365 * 2, 'all'] : null, // from start of statistics to 2 years ago = 12 hours granularity + startTime < now - day * 365 * 2 ? [startTime, now - day * 365 * 2, 'all'] : null, // from start of statistics to 2 years ago = 12 hours granularity ]; for (const interval of intervals) { @@ -138,7 +138,7 @@ class StatisticsReplication { } missingStatistics[interval[2] as string] = await this.$getMissingStatisticsInterval(interval, startTime); } - + return missingStatistics; } catch (e: any) { logger.err(`Cannot fetch missing statistics times from db. Reason: ` + (e instanceof Error ? e.message : e)); @@ -169,17 +169,17 @@ class StatisticsReplication { if (timeSteps.length === 0) { return new Set(); } - + const roundedTimesAlreadyHere: number[] = Array.from(new Set(rows.map(row => this.roundToNearestStep(row.added, step)))); const missingTimes = timeSteps.filter(time => !roundedTimesAlreadyHere.includes(time)).filter((time, i, arr) => { // Remove outsiders if (i === 0) { - return arr[i + 1] === time + step + return arr[i + 1] === time + step; } else if (i === arr.length - 1) { return arr[i - 1] === time - step; } - return (arr[i + 1] === time + step) && (arr[i - 1] === time - step) + return (arr[i + 1] === time + step) && (arr[i - 1] === time - step); }); // Don't bother fetching if very few rows are missing diff --git a/backend/src/replication/replicator.ts b/backend/src/replication/replicator.ts index ac204efcc..df90a7d05 100644 --- a/backend/src/replication/replicator.ts +++ b/backend/src/replication/replicator.ts @@ -14,7 +14,7 @@ export async function $sync(path): Promise<{ data?: any, exists: boolean, server if (server === backendInfo.getBackendInfo().hostname) { continue; } - + try { const result = await query(`https://${server}${path}`); if (result) { diff --git a/backend/src/repositories/AccelerationRepository.ts b/backend/src/repositories/AccelerationRepository.ts index aa39c8929..76926839f 100644 --- a/backend/src/repositories/AccelerationRepository.ts +++ b/backend/src/repositories/AccelerationRepository.ts @@ -100,7 +100,7 @@ class AccelerationRepository { SELECT *, UNIX_TIMESTAMP(requested) as requested_timestamp, UNIX_TIMESTAMP(added) as block_timestamp FROM accelerations JOIN pools on pools.unique_id = accelerations.pool `; - let params: any[] = []; + const params: any[] = []; let hasFilter = false; if (interval && height === null) { @@ -163,7 +163,7 @@ class AccelerationRepository { SELECT SUM(boost_cost) as total_cost, COUNT(txid) as count FROM accelerations JOIN pools on pools.unique_id = accelerations.pool `; - let params: any[] = []; + const params: any[] = []; let hasFilter = false; if (interval) { @@ -346,7 +346,7 @@ class AccelerationRepository { const accelerationSummaries = accelerations.map(acc => ({ ...acc, pools: acc.pools, - })) + })); for (const acc of accelerations) { if (blockTxs[acc.txid] && acc.pools.includes(block.extras.pool.id)) { const tx = blockTxs[acc.txid]; diff --git a/backend/src/repositories/BlocksAuditsRepository.ts b/backend/src/repositories/BlocksAuditsRepository.ts index 3b3f79ce0..7250304c1 100644 --- a/backend/src/repositories/BlocksAuditsRepository.ts +++ b/backend/src/repositories/BlocksAuditsRepository.ts @@ -94,7 +94,7 @@ class BlocksAuditRepositories { JOIN blocks_templates ON blocks_templates.id = blocks_audits.hash WHERE blocks_audits.hash = ? `, [hash]); - + if (rows.length) { rows[0].unseenTxs = JSON.parse(rows[0].unseenTxs); rows[0].missingTxs = JSON.parse(rows[0].missingTxs); diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 2994dd8f4..8f2579b48 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -217,9 +217,9 @@ class BlocksRepository { /** * Save newly indexed data from core coinstatsindex - * - * @param utxoSetSize - * @param totalInputAmt + * + * @param utxoSetSize + * @param totalInputAmt */ public async $updateCoinStatsIndexData(blockHash: string, utxoSetSize: number, totalInputAmt: number @@ -245,9 +245,9 @@ class BlocksRepository { /** * Update missing fee amounts fields * - * @param blockHash - * @param feeAmtPercentiles - * @param medianFeeAmt + * @param blockHash + * @param feeAmtPercentiles + * @param medianFeeAmt */ public async $updateFeeAmounts(blockHash: string, feeAmtPercentiles, medianFeeAmt) : Promise { try { @@ -275,7 +275,7 @@ class BlocksRepository { // Ensure startHeight is the lower value and endHeight is the higher value const minHeight = Math.min(startHeight, endHeight); const maxHeight = Math.max(startHeight, endHeight); - + if (minHeight === maxHeight) { return []; } @@ -410,7 +410,7 @@ class BlocksRepository { */ public async $blockCountBetweenHeight(startHeight: number, endHeight: number): Promise { const params: any[] = []; - let query = `SELECT count(height) as blockCount + const query = `SELECT count(height) as blockCount FROM blocks WHERE height <= ${startHeight} AND height >= ${endHeight} AND stale = 0`; @@ -1028,9 +1028,9 @@ class BlocksRepository { /** * Save indexed median fee to avoid recomputing it later - * - * @param id - * @param feePercentiles + * + * @param id + * @param feePercentiles */ public async $saveFeePercentilesForBlockId(id: string, feePercentiles: number[]): Promise { try { @@ -1047,9 +1047,9 @@ class BlocksRepository { /** * Save indexed effective fee statistics - * - * @param id - * @param feeStats + * + * @param id + * @param feeStats */ public async $saveEffectiveFeeStats(id: string, feeStats: EffectiveFeeStats): Promise { try { @@ -1066,7 +1066,7 @@ class BlocksRepository { /** * Save coinbase addresses - * + * * @param id * @param addresses */ @@ -1085,7 +1085,7 @@ class BlocksRepository { /** * Save pool - * + * * @param id * @param poolId */ @@ -1104,8 +1104,8 @@ class BlocksRepository { /** * Save block first seen time - * - * @param id + * + * @param id */ public async $saveFirstSeenTime(id: string, firstSeen: number): Promise { try { @@ -1122,7 +1122,7 @@ class BlocksRepository { /** * Change which block at a height belongs to the canonical chain - * + * * @param hash * @param height */ @@ -1151,8 +1151,8 @@ class BlocksRepository { /** * Convert a mysql row block into a BlockExtended. Note that you * must provide the correct field into dbBlk object param - * - * @param dbBlk + * + * @param dbBlk */ private async formatDbBlockIntoExtendedBlock(dbBlk: DatabaseBlock): Promise { const blk: Partial = {}; diff --git a/backend/src/repositories/BlocksSummariesRepository.ts b/backend/src/repositories/BlocksSummariesRepository.ts index d0e3db848..4ab26d2dd 100644 --- a/backend/src/repositories/BlocksSummariesRepository.ts +++ b/backend/src/repositories/BlocksSummariesRepository.ts @@ -154,8 +154,8 @@ class BlocksSummariesRepository { /** * Get the fee percentiles if the block has already been indexed, [] otherwise - * - * @param id + * + * @param id */ public async $getFeePercentilesByBlockId(id: string): Promise { try { diff --git a/backend/src/repositories/HashratesRepository.ts b/backend/src/repositories/HashratesRepository.ts index 93aa2d53f..b1297bfda 100644 --- a/backend/src/repositories/HashratesRepository.ts +++ b/backend/src/repositories/HashratesRepository.ts @@ -215,7 +215,7 @@ class HashratesRepository { logger.err('Cannot delete latest hashrates data points. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining); } } - + /** * Delete hashrates from the database from timestamp */ diff --git a/backend/src/repositories/PoolsRepository.ts b/backend/src/repositories/PoolsRepository.ts index c6625775d..b97fa951d 100644 --- a/backend/src/repositories/PoolsRepository.ts +++ b/backend/src/repositories/PoolsRepository.ts @@ -149,8 +149,8 @@ class PoolsRepository { /** * Insert a new mining pool in the database - * - * @param pool + * + * @param pool */ public async $insertNewMiningPool(pool: any, slug: string): Promise { try { @@ -166,10 +166,10 @@ class PoolsRepository { /** * Rename an existing mining pool - * + * * @param dbId * @param newSlug - * @param newName + * @param newName */ public async $renameMiningPool(dbId: number, newSlug: string, newName: string): Promise { try { @@ -186,9 +186,9 @@ class PoolsRepository { /** * Update an exisiting mining pool link - * - * @param dbId - * @param newLink + * + * @param dbId + * @param newLink */ public async $updateMiningPoolLink(dbId: number, newLink: string): Promise { try { @@ -206,10 +206,10 @@ class PoolsRepository { /** * Update an existing mining pool addresses or coinbase tags - * - * @param dbId - * @param addresses - * @param regexes + * + * @param dbId + * @param addresses + * @param regexes */ public async $updateMiningPoolTags(dbId: number, addresses: string, regexes: string): Promise { try { diff --git a/backend/src/repositories/PricesRepository.ts b/backend/src/repositories/PricesRepository.ts index e12027a74..236519420 100644 --- a/backend/src/repositories/PricesRepository.ts +++ b/backend/src/repositories/PricesRepository.ts @@ -179,7 +179,7 @@ class PricesRepository { prices[currency] = 0; } } - + try { if (!config.FIAT_PRICE.API_KEY) { // Store only the 7 main currencies await DB.query(` @@ -191,8 +191,8 @@ class PricesRepository { await DB.query(` INSERT INTO prices(time, USD, EUR, GBP, CAD, CHF, AUD, JPY, BGN, BRL, CNY, CZK, DKK, HKD, HRK, HUF, IDR, ILS, INR, ISK, KRW, MXN, MYR, NOK, NZD, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, ZAR) VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ? , ?, ?, ?, ?, ?, ?, ?, ? , ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? , ? )`, - [time, prices.USD, prices.EUR, prices.GBP, prices.CAD, prices.CHF, prices.AUD, prices.JPY, prices.BGN, prices.BRL, prices.CNY, prices.CZK, prices.DKK, - prices.HKD, prices.HRK, prices.HUF, prices.IDR, prices.ILS, prices.INR, prices.ISK, prices.KRW, prices.MXN, prices.MYR, prices.NOK, prices.NZD, + [time, prices.USD, prices.EUR, prices.GBP, prices.CAD, prices.CHF, prices.AUD, prices.JPY, prices.BGN, prices.BRL, prices.CNY, prices.CZK, prices.DKK, + prices.HKD, prices.HRK, prices.HUF, prices.IDR, prices.ILS, prices.INR, prices.ISK, prices.KRW, prices.MXN, prices.MYR, prices.NOK, prices.NZD, prices.PHP, prices.PLN, prices.RON, prices.RUB, prices.SEK, prices.SGD, prices.THB, prices.TRY, prices.ZAR] ); } @@ -341,8 +341,8 @@ class PricesRepository { `); if (!Array.isArray(latestPrices)) { throw Error(`Cannot get single historical price from the database`); - } - + } + // Compute fiat exchange rates let latestPrice = latestPrices[0] as ApiPrice; if (!latestPrice || latestPrice.USD === -1) { @@ -350,8 +350,8 @@ class PricesRepository { } const computeFx = (usd: number, other: number): number => usd <= 0.05 ? 0 : Math.round(Math.max(other, 0) / usd * 100) / 100; - - const exchangeRates: ExchangeRates = config.FIAT_PRICE.API_KEY ? + + const exchangeRates: ExchangeRates = config.FIAT_PRICE.API_KEY ? { USDEUR: computeFx(latestPrice.USD, latestPrice.EUR), USDGBP: computeFx(latestPrice.USD, latestPrice.GBP), @@ -446,10 +446,10 @@ class PricesRepository { latestPrice = priceUpdater.getEmptyPricesObj(); } - const computeFx = (usd: number, other: number): number => + const computeFx = (usd: number, other: number): number => usd <= 0 ? 0 : Math.round(Math.max(other, 0) / usd * 100) / 100; - - const exchangeRates: ExchangeRates = config.FIAT_PRICE.API_KEY ? + + const exchangeRates: ExchangeRates = config.FIAT_PRICE.API_KEY ? { USDEUR: computeFx(latestPrice.USD, latestPrice.EUR), USDGBP: computeFx(latestPrice.USD, latestPrice.GBP), diff --git a/backend/src/rpc-api/index.ts b/backend/src/rpc-api/index.ts index 131e1a048..37f4e3e99 100644 --- a/backend/src/rpc-api/index.ts +++ b/backend/src/rpc-api/index.ts @@ -1,61 +1,61 @@ -var commands = require('./commands') -var rpc = require('./jsonrpc') +const commands = require('./commands'); +const rpc = require('./jsonrpc'); // ===----------------------------------------------------------------------===// // JsonRPC // ===----------------------------------------------------------------------===// function Client (opts) { // @ts-ignore - this.rpc = new rpc.JsonRPC(opts) + this.rpc = new rpc.JsonRPC(opts); } // ===----------------------------------------------------------------------===// // cmd // ===----------------------------------------------------------------------===// Client.prototype.cmd = function () { - var args = [].slice.call(arguments) - var cmd = args.shift() + const args = [].slice.call(arguments); + const cmd = args.shift(); - callRpc(cmd, args, this.rpc) -} + callRpc(cmd, args, this.rpc); +}; // ===----------------------------------------------------------------------===// // callRpc // ===----------------------------------------------------------------------===// function callRpc (cmd, args, rpc) { - var fn = args[args.length - 1] + let fn = args[args.length - 1]; // If the last argument is a callback, pop it from the args list if (typeof fn === 'function') { - args.pop() + args.pop(); } else { - fn = function () {} + fn = function () {}; } return rpc.call(cmd, args, function () { - var args = [].slice.call(arguments) + const args = [].slice.call(arguments); // @ts-ignore - args.unshift(null) + args.unshift(null); // @ts-ignore - fn.apply(this, args) + fn.apply(this, args); }, function (err) { - fn(err) - }) + fn(err); + }); } // ===----------------------------------------------------------------------===// // Initialize wrappers // ===----------------------------------------------------------------------===// (function () { - for (var protoFn in commands) { + for (const protoFn in commands) { (function (protoFn) { Client.prototype[protoFn] = function () { - var args = [].slice.call(arguments) - return callRpc(commands[protoFn], args, this.rpc) - } - })(protoFn) + const args = [].slice.call(arguments); + return callRpc(commands[protoFn], args, this.rpc); + }; + })(protoFn); } -})() +})(); // Export! module.exports.Client = Client; diff --git a/backend/src/rpc-api/jsonrpc.ts b/backend/src/rpc-api/jsonrpc.ts index 0bcbdc16c..c810cac0e 100644 --- a/backend/src/rpc-api/jsonrpc.ts +++ b/backend/src/rpc-api/jsonrpc.ts @@ -1,43 +1,43 @@ -var http = require('http') -var https = require('https') +const http = require('http'); +const https = require('https'); import { readFileSync } from 'fs'; -var JsonRPC = function (opts) { +const JsonRPC = function (opts) { // @ts-ignore - this.opts = opts || {} + this.opts = opts || {}; // @ts-ignore - this.http = this.opts.ssl ? https : http -} + this.http = this.opts.ssl ? https : http; +}; JsonRPC.prototype.call = function (method, params) { return new Promise((resolve, reject) => { - var time = Date.now() - var requestJSON + const time = Date.now(); + let requestJSON; if (Array.isArray(method)) { // multiple rpc batch call - requestJSON = [] + requestJSON = []; method.forEach(function (batchCall, i) { requestJSON.push({ id: time + '-' + i, method: batchCall.method, params: batchCall.params - }) - }) + }); + }); } else { // single rpc call requestJSON = { id: time, method: method, params: params - } + }; } // First we encode the request into JSON - requestJSON = JSON.stringify(requestJSON) + requestJSON = JSON.stringify(requestJSON); // prepare request options - var requestOptions = { + const requestOptions = { host: this.opts.host || 'localhost', port: this.opts.port || 8332, method: 'POST', @@ -48,11 +48,11 @@ JsonRPC.prototype.call = function (method, params) { }, agent: false, rejectUnauthorized: this.opts.ssl && this.opts.sslStrict !== false - } + }; if (this.opts.ssl && this.opts.sslCa) { - // @ts-ignore - requestOptions.ca = this.opts.sslCa + // @ts-ignore + requestOptions.ca = this.opts.sslCa; } // use HTTP auth if user and password set @@ -64,61 +64,61 @@ JsonRPC.prototype.call = function (method, params) { requestOptions.auth = this.cachedCookie; } else if (this.opts.user && this.opts.pass) { // @ts-ignore - requestOptions.auth = this.opts.user + ':' + this.opts.pass + requestOptions.auth = this.opts.user + ':' + this.opts.pass; } // Now we'll make a request to the server - var cbCalled = false - var request = this.http.request(requestOptions) + let cbCalled = false; + const request = this.http.request(requestOptions); // start request timeout timer - var reqTimeout = setTimeout(function () { - if (cbCalled) return - cbCalled = true - request.abort() - var err = new Error('ETIMEDOUT') + const reqTimeout = setTimeout(function () { + if (cbCalled) {return;} + cbCalled = true; + request.abort(); + const err = new Error('ETIMEDOUT'); // @ts-ignore - err.code = 'ETIMEDOUT' - reject(err) - }, this.opts.timeout || 30000) + err.code = 'ETIMEDOUT'; + reject(err); + }, this.opts.timeout || 30000); // set additional timeout on socket in case of remote freeze after sending headers request.setTimeout(this.opts.timeout || 30000, function () { - if (cbCalled) return - cbCalled = true - request.abort() - var err = new Error('ESOCKETTIMEDOUT') + if (cbCalled) {return;} + cbCalled = true; + request.abort(); + const err = new Error('ESOCKETTIMEDOUT'); // @ts-ignore - err.code = 'ESOCKETTIMEDOUT' - reject(err) - }) + err.code = 'ESOCKETTIMEDOUT'; + reject(err); + }); request.on('error', function (err) { - if (cbCalled) return - cbCalled = true - clearTimeout(reqTimeout) - reject(err) - }) + if (cbCalled) {return;} + cbCalled = true; + clearTimeout(reqTimeout); + reject(err); + }); request.on('response', (response) => { - clearTimeout(reqTimeout) + clearTimeout(reqTimeout); // We need to buffer the response chunks in a nonblocking way. - var buffer = '' + let buffer = ''; response.on('data', function (chunk) { - buffer = buffer + chunk - }) + buffer = buffer + chunk; + }); // When all the responses are finished, we decode the JSON and // depending on whether it's got a result or an error, we call // emitSuccess or emitError on the promise. response.on('end', () => { - var err + let err; - if (cbCalled) return - cbCalled = true + if (cbCalled) {return;} + cbCalled = true; try { - var decoded = JSON.parse(buffer) + var decoded = JSON.parse(buffer); } catch (e) { // if we authenticated using a cookie and it failed, read the cookie file again if ( @@ -129,19 +129,19 @@ JsonRPC.prototype.call = function (method, params) { } if (response.statusCode !== 200) { - err = new Error('Invalid params, response status code: ' + response.statusCode) - err.code = -32602 - reject(err) + err = new Error('Invalid params, response status code: ' + response.statusCode); + err.code = -32602; + reject(err); } else { - err = new Error('Problem parsing JSON response from server') - err.code = -32603 - reject(err) + err = new Error('Problem parsing JSON response from server'); + err.code = -32603; + reject(err); } - return + return; } if (!Array.isArray(decoded)) { - decoded = [decoded] + decoded = [decoded]; } // iterate over each response, normally there will be just one @@ -149,29 +149,29 @@ JsonRPC.prototype.call = function (method, params) { decoded.forEach(function (decodedResponse, i) { if (decodedResponse.hasOwnProperty('error') && decodedResponse.error != null) { if (reject) { - err = new Error(decodedResponse.error.message || '') + err = new Error(decodedResponse.error.message || ''); if (decodedResponse.error.code) { - err.code = decodedResponse.error.code + err.code = decodedResponse.error.code; } - reject(err) + reject(err); } } else if (decodedResponse.hasOwnProperty('result')) { // @ts-ignore - resolve(decodedResponse.result, response.headers) + resolve(decodedResponse.result, response.headers); } else { if (reject) { - err = new Error(decodedResponse.error.message || '') + err = new Error(decodedResponse.error.message || ''); if (decodedResponse.error.code) { - err.code = decodedResponse.error.code + err.code = decodedResponse.error.code; } - reject(err) + reject(err); } } - }) - }) - }) + }); + }); + }); request.end(requestJSON); }); -} +}; -module.exports.JsonRPC = JsonRPC +module.exports.JsonRPC = JsonRPC; diff --git a/backend/src/tasks/lightning/forensics.service.ts b/backend/src/tasks/lightning/forensics.service.ts index aa88f5bb4..47b7d6aa5 100644 --- a/backend/src/tasks/lightning/forensics.service.ts +++ b/backend/src/tasks/lightning/forensics.service.ts @@ -449,7 +449,7 @@ class ForensicsService { const initiatorSide = initiator === 'remote' ? prevRemote : prevLocal; prevChannel.closed_by = prevChannel[`node${initiatorSide}_public_key`]; } - + // save changes to the closing channel await channelsApi.$updateClosingInfo(prevChannel); } else { diff --git a/backend/src/tasks/lightning/network-sync.service.ts b/backend/src/tasks/lightning/network-sync.service.ts index da4eba170..f3a20cd22 100644 --- a/backend/src/tasks/lightning/network-sync.service.ts +++ b/backend/src/tasks/lightning/network-sync.service.ts @@ -47,7 +47,7 @@ class NetworkSyncService { await this.$lookUpCreationDateFromChain(); await this.$updateNodeFirstSeen(); await this.$scanForClosedChannels(); - + if (config.MEMPOOL.BACKEND === 'esplora') { // run forensics on new channels only await forensicsService.$runClosedChannelsForensics(true); @@ -226,7 +226,7 @@ class NetworkSyncService { if (channels.length > 0) { logger.debug(`Updated ${channels.length} channels' creation date`, logger.tags.ln); - } + } } catch (e) { logger.err(`$lookUpCreationDateFromChain() error: ${e instanceof Error ? e.message : e}`, logger.tags.ln); } diff --git a/backend/src/tasks/lightning/sync-tasks/funding-tx-fetcher.ts b/backend/src/tasks/lightning/sync-tasks/funding-tx-fetcher.ts index c279cfb60..a6c8b9a66 100644 --- a/backend/src/tasks/lightning/sync-tasks/funding-tx-fetcher.ts +++ b/backend/src/tasks/lightning/sync-tasks/funding-tx-fetcher.ts @@ -6,7 +6,7 @@ import logger from '../../../logger'; const fsPromises = promises; -const BLOCKS_CACHE_MAX_SIZE = 100; +const BLOCKS_CACHE_MAX_SIZE = 100; const CACHE_FILE_NAME = config.MEMPOOL.CACHE_DIR + '/ln-funding-txs-cache.json'; class FundingTxFetcher { @@ -33,7 +33,7 @@ class FundingTxFetcher { return; } this.running = true; - + const globalTimer = new Date().getTime() / 1000; let cacheTimer = new Date().getTime() / 1000; let loggerTimer = new Date().getTime() / 1000; @@ -70,7 +70,7 @@ class FundingTxFetcher { this.running = false; } - + public async $fetchChannelOpenTx(channelId: string): Promise<{timestamp: number, txid: string, value: number} | null> { channelId = Common.channelIntegerIdToShortId(channelId); diff --git a/backend/src/tasks/lightning/sync-tasks/node-locations.ts b/backend/src/tasks/lightning/sync-tasks/node-locations.ts index 8e791859f..4eff70ab3 100644 --- a/backend/src/tasks/lightning/sync-tasks/node-locations.ts +++ b/backend/src/tasks/lightning/sync-tasks/node-locations.ts @@ -60,13 +60,13 @@ export async function $lookupNodeLocation(): Promise { if (city && (asn || isp)) { const query = ` - UPDATE nodes SET - as_number = ?, - city_id = ?, - country_id = ?, - subdivision_id = ?, - longitude = ?, - latitude = ?, + UPDATE nodes SET + as_number = ?, + city_id = ?, + country_id = ?, + subdivision_id = ?, + longitude = ?, + latitude = ?, accuracy_radius = ? WHERE public_key = ? `; diff --git a/backend/src/tasks/lightning/sync-tasks/stats-importer.ts b/backend/src/tasks/lightning/sync-tasks/stats-importer.ts index 86f891d59..8891bfc3a 100644 --- a/backend/src/tasks/lightning/sync-tasks/stats-importer.ts +++ b/backend/src/tasks/lightning/sync-tasks/stats-importer.ts @@ -99,7 +99,7 @@ class LightningStatsImporter { const feeRates: number[] = []; const baseFees: number[] = []; const alreadyCountedChannels = {}; - + const [channelsInDbRaw]: any[] = await DB.query(`SELECT short_id FROM channels`); const channelsInDb = {}; for (const channel of channelsInDbRaw) { @@ -145,7 +145,7 @@ class LightningStatsImporter { channels: 0, }; } - + if (!alreadyCountedChannels[short_id]) { capacity += Math.round(tx.value * 100000000); capacities.push(Math.round(tx.value * 100000000)); @@ -162,7 +162,7 @@ class LightningStatsImporter { if (policy && parseInt(policy.fee_rate_milli_msat, 10) < 5000) { avgFeeRate += parseInt(policy.fee_rate_milli_msat, 10); feeRates.push(parseInt(policy.fee_rate_milli_msat, 10)); - } + } if (policy && parseInt(policy.fee_base_msat, 10) < 5000) { avgBaseFee += parseInt(policy.fee_base_msat, 10); baseFees.push(parseInt(policy.fee_base_msat, 10)); @@ -388,7 +388,7 @@ class LightningStatsImporter { totalProcessed++; continue; } - + if (this.isIncorrectSnapshot(timestamp, graph)) { logger.debug(`Ignoring ${this.topologiesFolder}/${filename}, because we defined it as an incorrect snapshot`); ++totalProcessed; @@ -399,7 +399,7 @@ class LightningStatsImporter { logger.info(`Founds a topology file that we did not import. Importing historical lightning stats now.`, logger.tags.ln); logStarted = true; } - + const datestr = `${new Date(timestamp * 1000).toUTCString()} (${timestamp})`; logger.debug(`${datestr}: Found ${graph.nodes.length} nodes and ${graph.edges.length} channels`, logger.tags.ln); @@ -475,7 +475,7 @@ class LightningStatsImporter { fee_rate_milli_msat: edge.fee_proportional_millionths, max_htlc_msat: edge.htlc_maximum_msat, last_update: edge.timestamp, - disabled: false, + disabled: false, }, node2_policy: null, }); @@ -545,7 +545,7 @@ class LightningStatsImporter { // UNIX_TIMESTAMP(added) >= 1591142400 AND UNIX_TIMESTAMP(added) <= 1592006400 OR // UNIX_TIMESTAMP(added) >= 1632787200 AND UNIX_TIMESTAMP(added) <= 1633564800 OR // UNIX_TIMESTAMP(added) >= 1634256000 AND UNIX_TIMESTAMP(added) <= 1645401600 OR - // UNIX_TIMESTAMP(added) >= 1654992000 AND UNIX_TIMESTAMP(added) <= 1661472000 + // UNIX_TIMESTAMP(added) >= 1654992000 AND UNIX_TIMESTAMP(added) <= 1661472000 // ) } } diff --git a/backend/src/tasks/price-feeds/bitfinex-api.ts b/backend/src/tasks/price-feeds/bitfinex-api.ts index 30b70e9eb..d9f348b63 100644 --- a/backend/src/tasks/price-feeds/bitfinex-api.ts +++ b/backend/src/tasks/price-feeds/bitfinex-api.ts @@ -3,7 +3,7 @@ import priceUpdater, { PriceFeed, PriceHistory } from '../price-updater'; class BitfinexApi implements PriceFeed { public name: string = 'Bitfinex'; - public currencies: string[] = ['USD', 'EUR', 'GPB', 'JPY']; + public currencies: string[] = ['USD', 'EUR', 'GBP']; public url: string = 'https://api.bitfinex.com/v1/pubticker/BTC'; public urlHist: string = 'https://api-pub.bitfinex.com/v2/candles/trade:{GRANULARITY}:tBTC{CURRENCY}/hist'; diff --git a/backend/src/tasks/price-feeds/kraken-api.ts b/backend/src/tasks/price-feeds/kraken-api.ts index ebc784c6f..0c69ecf00 100644 --- a/backend/src/tasks/price-feeds/kraken-api.ts +++ b/backend/src/tasks/price-feeds/kraken-api.ts @@ -69,7 +69,7 @@ class KrakenApi implements PriceFeed { // CHF weekly price history goes back to timestamp 1575504000 (December 5, 2019) // AUD weekly price history goes back to timestamp 1591833600 (June 11, 2020) - let priceHistory: any = {}; // map: timestamp -> Prices + const priceHistory: any = {}; // map: timestamp -> Prices for (const currency of this.currencies) { const response = await query(this.urlHist.replace('{GRANULARITY}', '10080') + currency); diff --git a/backend/src/tasks/price-updater.ts b/backend/src/tasks/price-updater.ts index 16a33cfb6..f40fc7281 100644 --- a/backend/src/tasks/price-updater.ts +++ b/backend/src/tasks/price-updater.ts @@ -432,7 +432,7 @@ class PriceUpdater { this.additionalCurrenciesHistoryRunning = true; logger.debug(`Inserting missing historical conversion rates using conversions API to fill ${priceTimesToFill.length} rows`, logger.tags.mining); - let conversionRates: { [timestamp: number]: ConversionRates } = {}; + const conversionRates: { [timestamp: number]: ConversionRates } = {}; let totalInserted = 0; for (let i = 0; i < priceTimesToFill.length; i++) { @@ -464,7 +464,7 @@ class PriceUpdater { } const prices: ApiPrice = this.getEmptyPricesObj(); - + let willInsert = false; for (const conversionCurrency of this.newCurrencies.concat(missingLegacyCurrencies)) { if (conversionRates[yearMonthTimestamp][conversionCurrency] > 0 && priceTime.USD * conversionRates[yearMonthTimestamp][conversionCurrency] < MAX_PRICES[conversionCurrency]) { @@ -474,7 +474,7 @@ class PriceUpdater { prices[conversionCurrency] = 0; } } - + if (willInsert) { await PricesRepository.$saveAdditionalCurrencyPrices(priceTime.time, prices, missingLegacyCurrencies); ++totalInserted; diff --git a/backend/src/utils/bitcoin-script.ts b/backend/src/utils/bitcoin-script.ts index f463d8f76..117fa61a7 100644 --- a/backend/src/utils/bitcoin-script.ts +++ b/backend/src/utils/bitcoin-script.ts @@ -204,7 +204,7 @@ export function getVarIntLength(n: number): number { /** Extracts miner names from a DATUM coinbase transaction */ export function parseDATUMTemplateCreator(coinbaseRaw: string): string[] | null { - let bytes: number[] = []; + const bytes: number[] = []; for (let c = 0; c < coinbaseRaw.length; c += 2) { bytes.push(parseInt(coinbaseRaw.slice(c, c + 2), 16)); } diff --git a/backend/src/utils/format.ts b/backend/src/utils/format.ts index 63dc07ae4..ce23bc369 100644 --- a/backend/src/utils/format.ts +++ b/backend/src/utils/format.ts @@ -4,7 +4,7 @@ export function getBytesUnit(bytes: number): string { if (isNaN(bytes) || !isFinite(bytes)) { return 'B'; } - + let unitIndex = 0; while (unitIndex < byteUnits.length && bytes > 1024) { unitIndex++; @@ -18,7 +18,7 @@ export function formatBytes(bytes: number, toUnit: string, skipUnit = false): st if (isNaN(bytes) || !isFinite(bytes)) { return `${bytes}`; } - + let unitIndex = 0; while (unitIndex < byteUnits.length && (toUnit && byteUnits[unitIndex] !== toUnit || (!toUnit && bytes > 1024))) { unitIndex++; diff --git a/backend/src/utils/secp256k1.ts b/backend/src/utils/secp256k1.ts index 9e0f6dc3b..b95e1493b 100644 --- a/backend/src/utils/secp256k1.ts +++ b/backend/src/utils/secp256k1.ts @@ -49,7 +49,7 @@ export function isPoint(pointHex: string): boolean { } // Function modified slightly from noble-curves - + // Now we know that pointHex is a 33 or 65 byte hex string. const isCompressed = pointHex.length === 66; diff --git a/backend/testSetup.integration.ts b/backend/testSetup.integration.ts index 74efe871b..4ae0347cb 100644 --- a/backend/testSetup.integration.ts +++ b/backend/testSetup.integration.ts @@ -1,5 +1,5 @@ // Integration test setup - uses real implementations, not mocks -// +// // Note: We don't mock ./mempool-config.json here because: // 1. The MEMPOOL_CONFIG_FILE env var points to mempool-config.test.json // 2. config.ts will load that file via require() if env var is set diff --git a/backend/testSetup.ts b/backend/testSetup.ts index ca51bbbe6..24d42dd3c 100644 --- a/backend/testSetup.ts +++ b/backend/testSetup.ts @@ -1,5 +1,20 @@ jest.mock('./mempool-config.json', () => ({}), { virtual: true }); -jest.mock('./src/logger.ts', () => ({}), { virtual: true }); +jest.mock('./src/logger.ts', () => ({ + emerg: jest.fn(), + alert: jest.fn(), + crit: jest.fn(), + err: jest.fn(), + warn: jest.fn(), + notice: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + updateNetwork: jest.fn(), + tags: { + mining: 'mining', + ln: 'ln', + goggles: 'goggles', + }, +}), { virtual: true }); jest.mock('./src/api/rbf-cache.ts', () => ({}), { virtual: true }); jest.mock('./src/api/mempool.ts', () => ({}), { virtual: true }); jest.mock('./src/api/memory-cache.ts', () => ({}), { virtual: true }); diff --git a/docker/backend/Dockerfile b/docker/backend/Dockerfile index d6846f1dc..5e36f6035 100644 --- a/docker/backend/Dockerfile +++ b/docker/backend/Dockerfile @@ -7,8 +7,8 @@ WORKDIR /build RUN apt-get update && \ apt-get install -y curl ca-certificates && \ - curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ - apt-get install -y nodejs=22.14.0-1nodesource1 build-essential python3 pkg-config && \ + curl -fsSL https://deb.nodesource.com/setup_24.x | bash - && \ + apt-get install -y nodejs=24.13.0-1nodesource1 build-essential python3 pkg-config && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -28,8 +28,8 @@ FROM rust:1.92-bookworm AS runtime RUN apt-get update && \ apt-get install -y curl ca-certificates && \ - curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ - apt-get install -y nodejs=22.14.0-1nodesource1 && \ + curl -fsSL https://deb.nodesource.com/setup_24.x | bash - && \ + apt-get install -y nodejs=24.13.0-1nodesource1 && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index b2ff2c95e..ea18199d8 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -12,6 +12,12 @@ services: command: "./wait-for db:3306 --timeout=720 -- nginx -g 'daemon off;'" ports: - 80:8080 + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:8080/ | grep -q ' { }); it('loads the graphs page - mobile', () => { - cy.visit(`${basePath}`) + cy.visit(`${basePath}`); cy.waitForSkeletonGone(); cy.get('#btn-graphs').click().then(() => { cy.viewport('iphone-6'); diff --git a/frontend/cypress/e2e/liquidtestnet/liquidtestnet.spec.ts b/frontend/cypress/e2e/liquidtestnet/liquidtestnet.spec.ts index b5038f89b..aef6dc51d 100644 --- a/frontend/cypress/e2e/liquidtestnet/liquidtestnet.spec.ts +++ b/frontend/cypress/e2e/liquidtestnet/liquidtestnet.spec.ts @@ -40,7 +40,7 @@ describe('Liquid Testnet', () => { }); it('loads the blocks page', () => { - cy.visit(`${basePath}`) + cy.visit(`${basePath}`); cy.get('#btn-blocks'); cy.waitForSkeletonGone(); }); @@ -58,7 +58,7 @@ describe('Liquid Testnet', () => { }); it('loads the graphs page - mobile', () => { - cy.visit(`${basePath}`) + cy.visit(`${basePath}`); cy.waitForSkeletonGone(); cy.viewport('iphone-6'); cy.get('.tv-only').should('not.exist'); diff --git a/frontend/cypress/e2e/mainnet/mainnet.spec.ts b/frontend/cypress/e2e/mainnet/mainnet.spec.ts index a664f333c..a04803ca0 100644 --- a/frontend/cypress/e2e/mainnet/mainnet.spec.ts +++ b/frontend/cypress/e2e/mainnet/mainnet.spec.ts @@ -14,17 +14,17 @@ const baseModule = Cypress.env('BASE_MODULE'); const areOverlapping = (rect1, rect2) => { // if one rectangle is on the left side of the other if (rect1.right < rect2.left || rect2.right < rect1.left) { - return false + return false; } // if one rectangle is above the other if (rect1.bottom < rect2.top || rect2.bottom < rect1.top) { - return false + return false; } // the rectangles must overlap - return true -} + return true; +}; /** * Returns the bounding rectangle of the first DOM @@ -134,7 +134,7 @@ describe('Mainnet', () => { cy.get('.search-box-container > .form-control').type('A').then(() => { cy.wait('@search-1wizSA'); - cy.get('app-search-results button.dropdown-item').should('have.length', 1) + cy.get('app-search-results button.dropdown-item').should('have.length', 1); }); cy.get('app-search-results button.dropdown-item.active').click().then(() => { diff --git a/frontend/cypress/support/PageIdleDetector.ts b/frontend/cypress/support/PageIdleDetector.ts index ba0cd222f..44a83a73a 100644 --- a/frontend/cypress/support/PageIdleDetector.ts +++ b/frontend/cypress/support/PageIdleDetector.ts @@ -1,6 +1,6 @@ // source: chrisp_68 @ https://stackoverflow.com/questions/50525143/how-do-you-reliably-wait-for-page-idle-in-cypress-io-test export class PageIdleDetector -{ +{ defaultOptions: object = { timeout: 60000 }; public WaitForPageToBeIdle(): void @@ -15,7 +15,7 @@ export class PageIdleDetector { cy.document(options).should((myDocument: any) => { - expect(myDocument.readyState, "WaitForPageToLoad").to.be.oneOf(["interactive", "complete"]); + expect(myDocument.readyState, 'WaitForPageToLoad').to.be.oneOf(['interactive', 'complete']); }); } @@ -23,9 +23,9 @@ export class PageIdleDetector { cy.window(options).should((myWindow: any) => { - if (!!myWindow.angular) + if (myWindow.angular) { - expect(this.NumberOfPendingAngularRequests(myWindow), "WaitForAngularRequestsToComplete").to.have.length(0); + expect(this.NumberOfPendingAngularRequests(myWindow), 'WaitForAngularRequestsToComplete').to.have.length(0); } }); } @@ -34,16 +34,16 @@ export class PageIdleDetector { cy.window(options).should((myWindow: any) => { - if (!!myWindow.angular) + if (myWindow.angular) { - expect(this.AngularRootScopePhase(myWindow), "WaitForAngularDigestCycleToComplete").to.be.null; + expect(this.AngularRootScopePhase(myWindow), 'WaitForAngularDigestCycleToComplete').to.be.null; } }); } public WaitForAnimationsToStop(options: object = this.defaultOptions): void { - cy.get(":animated", options).should("not.exist"); + cy.get(':animated', options).should('not.exist'); } private getInjector(myWindow: any) @@ -58,6 +58,6 @@ export class PageIdleDetector private AngularRootScopePhase(myWindow: any) { - return this.getInjector(myWindow).get("$rootScope").$$phase; + return this.getInjector(myWindow).get('$rootScope').$$phase; } } diff --git a/frontend/cypress/support/commands.ts b/frontend/cypress/support/commands.ts index 2ce198241..99bc5c4d2 100644 --- a/frontend/cypress/support/commands.ts +++ b/frontend/cypress/support/commands.ts @@ -52,18 +52,18 @@ const codes = { ArrowUp: 38, ArrowRight: 39, ArrowDown: 40 -} +}; Cypress.Commands.add('waitForSkeletonGone', () => { cy.waitUntil(() => { return Cypress.$('.skeleton-loader').length === 0; - }, { verbose: true, description: "waitForSkeletonGone", errorMsg: "skeleton loaders never went away", timeout: 15000, interval: 50 }); + }, { verbose: true, description: 'waitForSkeletonGone', errorMsg: 'skeleton loaders never went away', timeout: 15000, interval: 50 }); }); Cypress.Commands.add( - "waitForPageIdle", + 'waitForPageIdle', () => { - console.warn("Waiting for page idle state"); + console.warn('Waiting for page idle state'); const pageIdleDetector = new PageIdleDetector(); pageIdleDetector.WaitForPageToBeIdle(); } @@ -77,7 +77,7 @@ Cypress.Commands.add('mockMempoolSocketV2', () => { mockWebSocketV2(); }); -Cypress.Commands.add('changeNetwork', (network: "testnet" | "testnet4" | "signet" | "liquid" | "mainnet") => { +Cypress.Commands.add('changeNetwork', (network: 'testnet' | 'testnet4' | 'signet' | 'liquid' | 'mainnet') => { cy.get('.dropdown-toggle').click().then(() => { cy.get(`a.${network}`).click().then(() => { cy.waitForPageIdle(); @@ -88,60 +88,60 @@ Cypress.Commands.add('changeNetwork', (network: "testnet" | "testnet4" | "signet // https://github.com/bahmutov/cypress-arrows/blob/8f0303842a343550fbeaf01528d01d1ff213b70c/src/index.js function keydownCommand($el, key) { - const message = `sending the "${key}" keydown event` + const message = `sending the "${key}" keydown event`; const log = Cypress.log({ name: `keydown: ${key}`, message: message, consoleProps: function () { return { Subject: $el - } + }; } - }) + }); - const e = $el.createEvent('KeyboardEvent') + const e = $el.createEvent('KeyboardEvent'); Object.defineProperty(e, 'key', { get: function () { - return key + return key; } - }) + }); Object.defineProperty(e, 'keyCode', { get: function () { - return this.keyCodeVal + return this.keyCodeVal; } - }) + }); Object.defineProperty(e, 'which', { get: function () { - return this.keyCodeVal + return this.keyCodeVal; } - }) - var metaKey = false + }); + const metaKey = false; Object.defineProperty(e, 'metaKey', { get: function () { - return metaKey + return metaKey; } - }) + }); Object.defineProperty(e, 'shiftKey', { get: function () { - return false + return false; } - }) - e.keyCodeVal = codes[key] + }); + e.keyCodeVal = codes[key]; e.initKeyboardEvent('keydown', true, true, - $el.defaultView, false, false, false, false, e.keyCodeVal, e.keyCodeVal) + $el.defaultView, false, false, false, false, e.keyCodeVal, e.keyCodeVal); - $el.dispatchEvent(e) - log.snapshot().end() - return $el + $el.dispatchEvent(e); + log.snapshot().end(); + return $el; } -Cypress.Commands.add('keydown', { prevSubject: "dom" }, keydownCommand) -Cypress.Commands.add('left', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowLeft')) -Cypress.Commands.add('right', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowRight')) -Cypress.Commands.add('up', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowUp')) -Cypress.Commands.add('down', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowDown')) +Cypress.Commands.add('keydown', { prevSubject: 'dom' }, keydownCommand); +Cypress.Commands.add('left', { prevSubject: 'dom' }, $el => keydownCommand($el, 'ArrowLeft')); +Cypress.Commands.add('right', { prevSubject: 'dom' }, $el => keydownCommand($el, 'ArrowRight')); +Cypress.Commands.add('up', { prevSubject: 'dom' }, $el => keydownCommand($el, 'ArrowUp')); +Cypress.Commands.add('down', { prevSubject: 'dom' }, $el => keydownCommand($el, 'ArrowDown')); diff --git a/frontend/cypress/support/index.d.ts b/frontend/cypress/support/index.d.ts index 21ffe6a2d..122ce5ed5 100644 --- a/frontend/cypress/support/index.d.ts +++ b/frontend/cypress/support/index.d.ts @@ -6,6 +6,6 @@ declare namespace Cypress { waitForPageIdle(): Chainable mockMempoolSocket(): Chainable mockMempoolSocketV2(): Chainable - changeNetwork(network: "testnet"|"testnet4"|"signet"|"liquid"|"mainnet"): Chainable + changeNetwork(network: 'testnet'|'testnet4'|'signet'|'liquid'|'mainnet'): Chainable } } \ No newline at end of file diff --git a/frontend/cypress/support/websocket.ts b/frontend/cypress/support/websocket.ts index b067cc6e8..7bdaff725 100644 --- a/frontend/cypress/support/websocket.ts +++ b/frontend/cypress/support/websocket.ts @@ -124,15 +124,15 @@ export const emitMempoolInfo = ({ //TODO: Refactor to take into account different parameterized mocking scenarios switch (params.network) { //TODO: Use network specific mocks - case "signet": - case "testnet": - case "mainnet": + case 'signet': + case 'testnet': + case 'mainnet': default: break; } switch (params.command) { - case "init": { + case 'init': { win.mockSocket.send('{"conversions":{"USD":32365.338815782445}}'); cy.readFile('cypress/fixtures/mainnet_live2hchart.json', 'utf-8').then((fixture) => { win.mockSocket.send(JSON.stringify(fixture)); @@ -142,7 +142,7 @@ export const emitMempoolInfo = ({ }); break; } - case "rbfTransaction": { + case 'rbfTransaction': { cy.readFile('cypress/fixtures/mainnet_rbf.json', 'utf-8').then((fixture) => { win.mockSocket.send(JSON.stringify(fixture)); }); @@ -164,7 +164,7 @@ export const emitMempoolInfo = ({ export const dropWebSocket = (() => { cy.window().then((win) => { - win.mockServer.simulate("error"); + win.mockServer.simulate('error'); }); return cy.wait(500); }); diff --git a/frontend/generate-config.js b/frontend/generate-config.js index 3ae3870e5..4485c2a95 100644 --- a/frontend/generate-config.js +++ b/frontend/generate-config.js @@ -46,6 +46,22 @@ try { throw new Error(e); } +// Inject theme manifest if it exists (created by generate-themes.js) +const THEME_MANIFEST_FILE = 'theme-manifest.json'; +try { + const themeManifest = fs.readFileSync(THEME_MANIFEST_FILE, 'utf-8'); + const themeFiles = JSON.parse(themeManifest); + let indexHtml = fs.readFileSync('src/index.html', 'utf-8'); + const script = ` `; + indexHtml = indexHtml.replace('', `${script}\n`); + fs.writeFileSync('src/index.html', indexHtml); + console.log('Injected theme manifest into src/index.html:', themeFiles); +} catch (e) { + if (e.code !== 'ENOENT') { + console.log('Warning: Could not inject theme manifest:', e.message); + } +} + try { const packageJson = fs.readFileSync('package.json'); packetJsonVersion = JSON.parse(packageJson).version; diff --git a/frontend/generate-themes.js b/frontend/generate-themes.js new file mode 100644 index 000000000..da7f06c20 --- /dev/null +++ b/frontend/generate-themes.js @@ -0,0 +1,57 @@ +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +const THEMES = ['contrast', 'softsimon', 'bukele']; +const STAGING_DIR = path.join(__dirname, '.theme-build'); +const DIST_DIR = path.join(__dirname, 'dist/mempool/browser'); +const MANIFEST_FILE = path.join(__dirname, 'theme-manifest.json'); + +const command = process.argv[2]; + +if (command === 'copy') { + const themeFiles = fs.readdirSync(STAGING_DIR).filter(f => f.endsWith('.css')); + for (const dir of fs.readdirSync(DIST_DIR, { withFileTypes: true })) { + if (dir.isDirectory()) { + for (const file of themeFiles) { + fs.copyFileSync(path.join(STAGING_DIR, file), path.join(DIST_DIR, dir.name, file)); + } + } + } + console.log(`Copied ${themeFiles.length} theme files to all locale directories`); +} else { + fs.rmSync(STAGING_DIR, { recursive: true, force: true }); + fs.mkdirSync(STAGING_DIR, { recursive: true }); + + const manifest = {}; + + for (const theme of THEMES) { + const inputFile = path.join(__dirname, `src/theme-${theme}.scss`); + const tempOutput = path.join(STAGING_DIR, `${theme}.tmp.css`); + + try { + execSync(`npx sass --style=compressed --no-source-map "${inputFile}" "${tempOutput}"`, { + stdio: 'pipe' + }); + } catch (e) { + console.error(`Failed to compile theme-${theme}.scss:`, e.message); + process.exit(1); + } + + const css = fs.readFileSync(tempOutput); + const hash = crypto.createHash('md5').update(css).digest('hex').slice(0, 16); + + const nonHashedFilename = `${theme}.css`; + fs.copyFileSync(tempOutput, path.join(STAGING_DIR, nonHashedFilename)); + + const hashedFilename = `${theme}.${hash}.css`; + fs.renameSync(tempOutput, path.join(STAGING_DIR, hashedFilename)); + + manifest[theme] = hashedFilename; + console.log(`Built ${nonHashedFilename} and ${hashedFilename}`); + } + + fs.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2)); + console.log('Theme manifest written to theme-manifest.json'); +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5f7228340..d62242b32 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,19 +9,19 @@ "version": "3.3-dev", "license": "GNU Affero General Public License v3.0", "dependencies": { - "@angular-devkit/build-angular": "^20.3.13", - "@angular/animations": "^20.3.15", - "@angular/cli": "^20.3.13", - "@angular/common": "^20.3.15", - "@angular/compiler": "^20.3.15", - "@angular/core": "^20.3.15", - "@angular/forms": "^20.3.15", - "@angular/localize": "^20.3.15", - "@angular/platform-browser": "^20.3.15", - "@angular/platform-browser-dynamic": "^20.3.15", - "@angular/platform-server": "^20.3.15", - "@angular/router": "^20.3.15", - "@angular/ssr": "^20.3.13", + "@angular-devkit/build-angular": "^20.3.14", + "@angular/animations": "^20.3.16", + "@angular/cli": "^20.3.14", + "@angular/common": "^20.3.16", + "@angular/compiler": "^20.3.16", + "@angular/core": "^20.3.16", + "@angular/forms": "^20.3.16", + "@angular/localize": "^20.3.16", + "@angular/platform-browser": "^20.3.16", + "@angular/platform-browser-dynamic": "^20.3.16", + "@angular/platform-server": "^20.3.16", + "@angular/router": "^20.3.16", + "@angular/ssr": "^20.3.14", "@fortawesome/angular-fontawesome": "^3.0.0", "@fortawesome/fontawesome-common-types": "~6.7.2", "@fortawesome/fontawesome-svg-core": "~6.7.2", @@ -41,8 +41,8 @@ "zone.js": "~0.15.1" }, "devDependencies": { - "@angular/compiler-cli": "^20.3.15", - "@angular/language-service": "^20.3.15", + "@angular/compiler-cli": "^20.3.16", + "@angular/language-service": "^20.3.16", "@types/node": "^24.9.2", "@typescript-eslint/eslint-plugin": "^8.46.2", "@typescript-eslint/parser": "^8.46.2", @@ -56,7 +56,7 @@ }, "optionalDependencies": { "@cypress/schematic": "^2.5.0", - "cypress": "^15.8.1", + "cypress": "^15.9.0", "cypress-fail-on-console-error": "~5.1.1", "cypress-wait-until": "^3.0.1", "mock-socket": "~9.3.1", @@ -280,12 +280,12 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.2003.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.13.tgz", - "integrity": "sha512-JyH6Af6PNC1IHJToColFk1RaXDU87mpPjz7M5sWDfn8bC+KBipw6dSdRkCEuw0D9HY1lZkC9EBV9k9GhpvHjCQ==", + "version": "0.2003.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.14.tgz", + "integrity": "sha512-dVlWqaYu0PIgHTBu16uYUS6lJOIpXCpOYhPWuYwqdo7a4x2HcagPQ+omUZJTA6kukh7ROpKcRoiy/DsO/DgvUA==", "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.3.13", + "@angular-devkit/core": "20.3.14", "rxjs": "7.8.2" }, "engines": { @@ -295,9 +295,9 @@ } }, "node_modules/@angular-devkit/architect/node_modules/@angular-devkit/core": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.13.tgz", - "integrity": "sha512-/D84T1Caxll3I2sRihPDR9UaWBhF50M+tAX15PdP6uSh/TxwAlLl9p7Rm1bD0mPjPercqaEKA+h9a9qLP16hug==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.14.tgz", + "integrity": "sha512-hWQVi73aGdIRInJqNia79Yi6SzqEThkfLug3AdZiNuNvYMaxAI347yPQz4f3Dr/i0QuiqRq/T8zfqbr46tfCqg==", "license": "MIT", "dependencies": { "ajv": "8.17.1", @@ -414,16 +414,16 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.3.13.tgz", - "integrity": "sha512-wEM5UHc37XGtH9FFVXZPwlZooccveL1VnFUbd2ArECGi4ylW+YgjeVSe0m6uJDvWOXULNVAoHlabXTXvmqV09A==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.3.14.tgz", + "integrity": "sha512-L3saxbGXUgSSXfCrWHTl9eBAxzcA1oSrb0ojL+NBiJ82Zhx1a3XIGSTNg7YkCrXHaLx+fvuoeyT7xyBH18zYZw==", "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2003.13", - "@angular-devkit/build-webpack": "0.2003.13", - "@angular-devkit/core": "20.3.13", - "@angular/build": "20.3.13", + "@angular-devkit/architect": "0.2003.14", + "@angular-devkit/build-webpack": "0.2003.14", + "@angular-devkit/core": "20.3.14", + "@angular/build": "20.3.14", "@babel/core": "7.28.3", "@babel/generator": "7.28.3", "@babel/helper-annotate-as-pure": "7.27.3", @@ -434,7 +434,7 @@ "@babel/preset-env": "7.28.3", "@babel/runtime": "7.28.3", "@discoveryjs/json-ext": "0.6.3", - "@ngtools/webpack": "20.3.13", + "@ngtools/webpack": "20.3.14", "ansi-colors": "4.1.3", "autoprefixer": "10.4.21", "babel-loader": "10.0.0", @@ -489,7 +489,7 @@ "@angular/platform-browser": "^20.0.0", "@angular/platform-server": "^20.0.0", "@angular/service-worker": "^20.0.0", - "@angular/ssr": "^20.3.13", + "@angular/ssr": "^20.3.14", "@web/test-runner": "^0.20.0", "browser-sync": "^3.0.2", "jest": "^29.5.0 || ^30.2.0", @@ -546,9 +546,9 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.13.tgz", - "integrity": "sha512-/D84T1Caxll3I2sRihPDR9UaWBhF50M+tAX15PdP6uSh/TxwAlLl9p7Rm1bD0mPjPercqaEKA+h9a9qLP16hug==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.14.tgz", + "integrity": "sha512-hWQVi73aGdIRInJqNia79Yi6SzqEThkfLug3AdZiNuNvYMaxAI347yPQz4f3Dr/i0QuiqRq/T8zfqbr46tfCqg==", "license": "MIT", "dependencies": { "ajv": "8.17.1", @@ -753,12 +753,12 @@ } }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.2003.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2003.13.tgz", - "integrity": "sha512-k57PdWOB64/u2MQYPylQNCKDSAHGsV0T2bvvZid2wfPJ7anvSUCU15OQMCMU1JMR0JENZEyIsLw9teShAO9w0Q==", + "version": "0.2003.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2003.14.tgz", + "integrity": "sha512-kE5bCHnmkWRhCxTlZj1E75UECArc1tq3RC1LMMeJL0Wj6tFO5Y33u16zucXbGqYHZETuz4svR0/u800bjMaQyg==", "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.2003.13", + "@angular-devkit/architect": "0.2003.14", "rxjs": "7.8.2" }, "engines": { @@ -772,12 +772,12 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.13.tgz", - "integrity": "sha512-hdMKY4rUTko8xqeWYGnwwDYDomkeOoLsYsP6SdaHWK7hpGvzWsT6Q/aIv8J8NrCYkLu+M+5nLiKOooweUZu3GQ==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.14.tgz", + "integrity": "sha512-+Al9QojzTucccSUnJI+9x64Nnuev82eIgIlb1Ov9hLR572SNtjhV7zIXIalphFghEy+SPvynRuvOSc69Otp3Fg==", "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.3.13", + "@angular-devkit/core": "20.3.14", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", "ora": "8.2.0", @@ -790,9 +790,9 @@ } }, "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.13.tgz", - "integrity": "sha512-/D84T1Caxll3I2sRihPDR9UaWBhF50M+tAX15PdP6uSh/TxwAlLl9p7Rm1bD0mPjPercqaEKA+h9a9qLP16hug==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.14.tgz", + "integrity": "sha512-hWQVi73aGdIRInJqNia79Yi6SzqEThkfLug3AdZiNuNvYMaxAI347yPQz4f3Dr/i0QuiqRq/T8zfqbr46tfCqg==", "license": "MIT", "dependencies": { "ajv": "8.17.1", @@ -909,9 +909,9 @@ } }, "node_modules/@angular/animations": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.15.tgz", - "integrity": "sha512-ikyKfhkxoqQA6JcBN0B9RaN6369sM1XYX81Id0lI58dmWCe7gYfrTp8ejqxxKftl514psQO3pkW8Gn1nJ131Gw==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.16.tgz", + "integrity": "sha512-N83/GFY5lKNyWgPV3xHHy2rb3/eP1ZLzSVI+dmMVbf3jbqwY1YPQcMiAG8UDzaILY1Dkus91kWLF8Qdr3nHAzg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -920,17 +920,17 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "20.3.15" + "@angular/core": "20.3.16" } }, "node_modules/@angular/build": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.13.tgz", - "integrity": "sha512-/5pM3ZS+lLkZgA+n6TMmNV8I6t9Ow1C6Vkj6bXqWeOgFDH5LwnIEZFAKzEDBkCGos0m2gPKPcREcDD5tfp9h4g==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.14.tgz", + "integrity": "sha512-ajFJqTqyI2N9PYcWVxUfb6YEUQsZ13jsBzI/kDpeEZZCGadLJGSMZVNwkX7n9Csw7gzertpenGBXsSTxUjd8TA==", "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2003.13", + "@angular-devkit/architect": "0.2003.14", "@babel/core": "7.28.3", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", @@ -972,7 +972,7 @@ "@angular/platform-browser": "^20.0.0", "@angular/platform-server": "^20.0.0", "@angular/service-worker": "^20.0.0", - "@angular/ssr": "^20.3.13", + "@angular/ssr": "^20.3.14", "karma": "^6.4.0", "less": "^4.2.0", "ng-packagr": "^20.0.0", @@ -1315,18 +1315,18 @@ } }, "node_modules/@angular/cli": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.13.tgz", - "integrity": "sha512-G78I/HDJULloS2LSqfUfbmBlhDCbcWujIRWfuMnGsRf82TyGA2OEPe3IA/F8MrJfeOzPQim2fMyn24MqHL40Vg==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.14.tgz", + "integrity": "sha512-vlvnxyUtPnETl5az+creSPOrcnrZC5mhD5hSGl2WoqhYeyWdyUwsC9KLSy8/5gCH/4TNwtjqeX3Pw0KaAJUoCQ==", "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.2003.13", - "@angular-devkit/core": "20.3.13", - "@angular-devkit/schematics": "20.3.13", + "@angular-devkit/architect": "0.2003.14", + "@angular-devkit/core": "20.3.14", + "@angular-devkit/schematics": "20.3.14", "@inquirer/prompts": "7.8.2", "@listr2/prompt-adapter-inquirer": "3.0.1", - "@modelcontextprotocol/sdk": "1.24.0", - "@schematics/angular": "20.3.13", + "@modelcontextprotocol/sdk": "1.25.2", + "@schematics/angular": "20.3.14", "@yarnpkg/lockfile": "1.1.0", "algoliasearch": "5.35.0", "ini": "5.0.0", @@ -1349,9 +1349,9 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.13.tgz", - "integrity": "sha512-/D84T1Caxll3I2sRihPDR9UaWBhF50M+tAX15PdP6uSh/TxwAlLl9p7Rm1bD0mPjPercqaEKA+h9a9qLP16hug==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.14.tgz", + "integrity": "sha512-hWQVi73aGdIRInJqNia79Yi6SzqEThkfLug3AdZiNuNvYMaxAI347yPQz4f3Dr/i0QuiqRq/T8zfqbr46tfCqg==", "license": "MIT", "dependencies": { "ajv": "8.17.1", @@ -1814,9 +1814,9 @@ } }, "node_modules/@angular/common": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.15.tgz", - "integrity": "sha512-k4mCXWRFiOHK3bUKfWkRQQ8KBPxW8TAJuKLYCsSHPCpMz6u0eA1F0VlrnOkZVKWPI792fOaEAWH2Y4PTaXlUHw==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.16.tgz", + "integrity": "sha512-GRAziNlntwdnJy3F+8zCOvDdy7id0gITjDnM6P9+n2lXvtDuBLGJKU3DWBbvxcCjtD6JK/g/rEX5fbCxbUHkQQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1825,14 +1825,14 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "20.3.15", + "@angular/core": "20.3.16", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.15.tgz", - "integrity": "sha512-lMicIAFAKZXa+BCZWs3soTjNQPZZXrF/WMVDinm8dQcggNarnDj4UmXgKSyXkkyqK5SLfnLsXVzrX6ndVT6z7A==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.16.tgz", + "integrity": "sha512-Pt9Ms9GwTThgzdxWBwMfN8cH1JEtQ2DK5dc2yxYtPSaD+WKmG9AVL1PrzIYQEbaKcWk2jxASUHpEWSlNiwo8uw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1842,9 +1842,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.15.tgz", - "integrity": "sha512-8sJoxodxsfyZ8eJ5r6Bx7BCbazXYgsZ1+dE8t5u5rTQ6jNggwNtYEzkyReoD5xvP+MMtRkos3xpwq4rtFnpI6A==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.16.tgz", + "integrity": "sha512-l3xF/fXfJAl/UrNnH9Ufkr79myjMgXdHq1mmmph2UnpeqilRB1b8lC9sLBV9MipQHVn3dwocxMIvtrcryfOaXw==", "license": "MIT", "dependencies": { "@babel/core": "7.28.3", @@ -1864,7 +1864,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.3.15", + "@angular/compiler": "20.3.16", "typescript": ">=5.8 <6.0" }, "peerDependenciesMeta": { @@ -2030,9 +2030,9 @@ } }, "node_modules/@angular/core": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.15.tgz", - "integrity": "sha512-NMbX71SlTZIY9+rh/SPhRYFJU0pMJYW7z/TBD4lqiO+b0DTOIg1k7Pg9ydJGqSjFO1Z4dQaA6TteNuF99TJCNw==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.16.tgz", + "integrity": "sha512-KSFPKvOmWWLCJBbEO+CuRUXfecX2FRuO0jNi9c54ptXMOPHlK1lIojUnyXmMNzjdHgRug8ci9qDuftvC2B7MKg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2041,7 +2041,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.3.15", + "@angular/compiler": "20.3.16", "rxjs": "^6.5.3 || ^7.4.0", "zone.js": "~0.15.0" }, @@ -2055,9 +2055,9 @@ } }, "node_modules/@angular/forms": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.15.tgz", - "integrity": "sha512-gS5hQkinq52pm/7mxz4yHPCzEcmRWjtUkOVddPH0V1BW/HMni/p4Y6k2KqKBeGb9p8S5EAp6PDxDVLOPukp3mg==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.16.tgz", + "integrity": "sha512-1yzbXpExTqATpVcqA3wGrq4ACFIP3mRxA4pbso5KoJU+/4JfzNFwLsDaFXKpm5uxwchVnj8KM2vPaDOkvtp7NA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2066,16 +2066,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.3.15", - "@angular/core": "20.3.15", - "@angular/platform-browser": "20.3.15", + "@angular/common": "20.3.16", + "@angular/core": "20.3.16", + "@angular/platform-browser": "20.3.16", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/language-service": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.15.tgz", - "integrity": "sha512-oD5rvAsZYzNqdJqMTYYp6T9yITG6axTI/j64v3qxHe+Y/PlHKfNHXcjENpA+LcR5wq0wtIE+s96APykCq9ouEQ==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.16.tgz", + "integrity": "sha512-0A/tSQPq5geIz2mMcZA5fzzbzT39v+ADQksnfPr8htNxtkYWy+EI5+d0+++k59NuvjLY4uTBqhRTRB9b1PKrjw==", "dev": true, "license": "MIT", "engines": { @@ -2083,9 +2083,9 @@ } }, "node_modules/@angular/localize": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.15.tgz", - "integrity": "sha512-vJDLXzQgLE+zpzwT2n85yWmWHuk9BsnPByOHyF63K178GFs0TG49UqZOFKohGbq5Vlo1GI9NIvXkcegkoMJCMQ==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.16.tgz", + "integrity": "sha512-7S2ACDZC1Ag1+rc991BMvW6gDyBCH7ykGWpZL1aHOmnq4K70Sf8p2VyQMtYhaz7XfWeXxwBQjCncVYv6D7RO5A==", "license": "MIT", "dependencies": { "@babel/core": "7.28.3", @@ -2102,8 +2102,8 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.3.15", - "@angular/compiler-cli": "20.3.15" + "@angular/compiler": "20.3.16", + "@angular/compiler-cli": "20.3.16" } }, "node_modules/@angular/localize/node_modules/ansi-regex": { @@ -2235,9 +2235,9 @@ } }, "node_modules/@angular/platform-browser": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.15.tgz", - "integrity": "sha512-TxRM/wTW/oGXv/3/Iohn58yWoiYXOaeEnxSasiGNS1qhbkcKtR70xzxW6NjChBUYAixz2ERkLURkpx3pI8Q6Dw==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.16.tgz", + "integrity": "sha512-YsrLS6vyS77i4pVHg4gdSBW74qvzHjpQRTVQ5Lv/OxIjJdYYYkMmjNalCNgy1ZuyY6CaLIB11ccxhrNnxfKGOQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2246,9 +2246,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/animations": "20.3.15", - "@angular/common": "20.3.15", - "@angular/core": "20.3.15" + "@angular/animations": "20.3.16", + "@angular/common": "20.3.16", + "@angular/core": "20.3.16" }, "peerDependenciesMeta": { "@angular/animations": { @@ -2257,9 +2257,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.15.tgz", - "integrity": "sha512-RizuRdBt0d6ongQ2y8cr8YsXFyjF8f91vFfpSNw+cFj+oiEmRC1txcWUlH5bPLD9qSDied8qazUi0Tb8VPQDGw==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.16.tgz", + "integrity": "sha512-5mECCV9YeKH6ue239GXRTGeDSd/eTbM1j8dDejhm5cGnPBhTxRw4o+GgSrWTYtb6VmIYdwUGBTC+wCBphiaQ2A==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2268,16 +2268,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.3.15", - "@angular/compiler": "20.3.15", - "@angular/core": "20.3.15", - "@angular/platform-browser": "20.3.15" + "@angular/common": "20.3.16", + "@angular/compiler": "20.3.16", + "@angular/core": "20.3.16", + "@angular/platform-browser": "20.3.16" } }, "node_modules/@angular/platform-server": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.15.tgz", - "integrity": "sha512-OB3/ztCREeZ0pe2P+43Nah9Xq2Y79fN6mbsOY1JwwYxkM8ZN1WkSP11xlHHwAcoquHP7uFPhXwJqgTHBqGqkcw==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.16.tgz", + "integrity": "sha512-LxQscYd3UCWV8H3sdlnM05UB60MZVuVsdsHvXdkJ9+WOQjVDN1l1rYhj2aDL/5KkaRd/nqo0yFRnVjwceXDJhQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0", @@ -2287,17 +2287,17 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.3.15", - "@angular/compiler": "20.3.15", - "@angular/core": "20.3.15", - "@angular/platform-browser": "20.3.15", + "@angular/common": "20.3.16", + "@angular/compiler": "20.3.16", + "@angular/core": "20.3.16", + "@angular/platform-browser": "20.3.16", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/router": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.15.tgz", - "integrity": "sha512-6+qgk8swGSoAu7ISSY//GatAyCP36hEvvUgvjbZgkXLLH9yUQxdo77ij05aJ5s0OyB25q/JkqS8VTY0z1yE9NQ==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.16.tgz", + "integrity": "sha512-e1LiQFZaajKqc00cY5FboIrWJZSMnZ64GDp5R0UejritYrqorQQQNOqP1W85BMuY2owibMmxVfX+dJg/Mc8PuQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2306,16 +2306,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.3.15", - "@angular/core": "20.3.15", - "@angular/platform-browser": "20.3.15", + "@angular/common": "20.3.16", + "@angular/core": "20.3.16", + "@angular/platform-browser": "20.3.16", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/ssr": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-20.3.13.tgz", - "integrity": "sha512-ef3yQG9wVIYzHXKNqAXAgI9YBJ5r4F8KbDtNMA6TDDxuAtTdoXYT1kmlgj5OcTVDDXGNFb12ZALRjB7yKu+zSw==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-20.3.14.tgz", + "integrity": "sha512-pKUH0IpoUuMpQ29MuZuix2jdhvlOm8nFbhZnCHSr7ozB21DE+XC5EXro5wobGGvP+Z5uKrRrYgrdRYgoHHvmYw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -3953,9 +3953,9 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", - "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.10.tgz", + "integrity": "sha512-hauBrOdvu08vOsagkZ/Aju5XuiZx6ldsLfByg1htFeldhex+PeMrYauANzFsMJeAA0+dyPLbDoX2OYuvVoLDkQ==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -3972,7 +3972,7 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "6.14.0", + "qs": "~6.14.1", "safe-buffer": "^5.1.2", "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", @@ -3982,22 +3982,6 @@ "node": ">= 6" } }, - "node_modules/@cypress/request/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/@cypress/schematic": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-2.5.0.tgz", @@ -4736,6 +4720,18 @@ "@hapi/hoek": "^11.0.2" } }, + "node_modules/@hono/node-server": { + "version": "1.19.8", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.8.tgz", + "integrity": "sha512-0/g2lIOPzX8f3vzW1ggQgvG5mjtFBDBHFAzI5SFAi2DzSqS9luJwqg9T6O/gKYLi+inS7eNxBeIFkkghIPvrMA==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -5551,11 +5547,12 @@ ] }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.24.0.tgz", - "integrity": "sha512-D8h5KXY2vHFW8zTuxn2vuZGN0HGrQ5No6LkHwlEA9trVgNdPL3TF1dSqKA7Dny6BbBYKSW/rOBDXdC8KJAjUCg==", + "version": "1.25.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz", + "integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==", "license": "MIT", "dependencies": { + "@hono/node-server": "^1.19.7", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", @@ -5566,6 +5563,7 @@ "express": "^5.0.1", "express-rate-limit": "^7.5.0", "jose": "^6.1.1", + "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", @@ -6058,9 +6056,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.3.13.tgz", - "integrity": "sha512-7GyH55pOy8XUwo1lVWHzjZoAmSLtRT/vQbMn43x7WDl8pymAbi5zfwE/cnIX+5xgUOvkmT8sW9gJAD19rkASag==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.3.14.tgz", + "integrity": "sha512-2rI9naKQBtoyQmddCnPp94o0lQVl5VPsCefntMg2T0XCCdyNj2OK4X4QCVRrtAcTwlYMysDSTpPKE/VkPcjIQA==", "license": "MIT", "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0", @@ -6976,13 +6974,13 @@ ] }, "node_modules/@schematics/angular": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.13.tgz", - "integrity": "sha512-ETJ1budKmrkdxojo5QP6TPr6zQZYGxtWWf8NrX1cBIS851zPCmFkKyhSFLZsoksariYF/LP8ljvm8tlcIzt/XA==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.14.tgz", + "integrity": "sha512-JO37puMXFWN8YWqZZJ/URs8vPJNszZXcIyBnYdKDWTGaAnbOZMu0nzQlOC+h5NM7R5cPQtOpJv0wxEnY6EYI4A==", "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.3.13", - "@angular-devkit/schematics": "20.3.13", + "@angular-devkit/core": "20.3.14", + "@angular-devkit/schematics": "20.3.14", "jsonc-parser": "3.3.1" }, "engines": { @@ -6992,9 +6990,9 @@ } }, "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.13.tgz", - "integrity": "sha512-/D84T1Caxll3I2sRihPDR9UaWBhF50M+tAX15PdP6uSh/TxwAlLl9p7Rm1bD0mPjPercqaEKA+h9a9qLP16hug==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.14.tgz", + "integrity": "sha512-hWQVi73aGdIRInJqNia79Yi6SzqEThkfLug3AdZiNuNvYMaxAI347yPQz4f3Dr/i0QuiqRq/T8zfqbr46tfCqg==", "license": "MIT", "dependencies": { "ajv": "8.17.1", @@ -8658,22 +8656,23 @@ "optional": true }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -8688,6 +8687,26 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -8704,6 +8723,15 @@ "node": ">= 0.8" } }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/bonjour-service": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", @@ -9074,15 +9102,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/cacache/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/cacache/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -9101,31 +9120,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cacache/node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/cacache/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/cachedir": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", @@ -9303,12 +9297,12 @@ } }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/chrome-trace-event": { @@ -9916,13 +9910,13 @@ "peer": true }, "node_modules/cypress": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.8.1.tgz", - "integrity": "sha512-ogc62stTQGh1395ipKxfCE5hQuSApTzeH5e0d9U6m7wYO9HQeCpgnkYtBtd0MbkN2Fnch5Od2mX9u4hoTlrH4Q==", + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.9.0.tgz", + "integrity": "sha512-Ks6Bdilz3TtkLZtTQyqYaqtL/WT3X3APKaSLhTV96TmTyudzSjc6EJsJCHmBb7DxO+3R12q3Jkbjgm/iPgmwfg==", "hasInstallScript": true, "optional": true, "dependencies": { - "@cypress/request": "^3.0.9", + "@cypress/request": "^3.0.10", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", @@ -10258,10 +10252,11 @@ "peer": true }, "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -11382,21 +11377,6 @@ "node": ">= 0.8" } }, - "node_modules/express/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/express/node_modules/raw-body": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", @@ -12235,6 +12215,15 @@ "node": ">= 0.4" } }, + "node_modules/hono": { + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz", + "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/hosted-git-info": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", @@ -13068,6 +13057,12 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -14333,15 +14328,6 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, - "node_modules/node-gyp/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/node-gyp/node_modules/isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", @@ -14351,22 +14337,6 @@ "node": ">=16" } }, - "node_modules/node-gyp/node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/node-gyp/node_modules/which": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", @@ -14382,15 +14352,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/node-gyp/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -15718,11 +15679,12 @@ } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -15767,19 +15729,49 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -16725,9 +16717,10 @@ } }, "node_modules/sinon/node_modules/diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", + "license": "BSD-3-Clause", "optional": true, "engines": { "node": ">=0.3.1" @@ -17283,90 +17276,28 @@ } }, "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "license": "ISC", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz", + "integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==", + "license": "BlueOak-1.0.0", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/minipass": { + "node_modules/tar/node_modules/yallist": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "license": "ISC", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tar/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/terser": { @@ -18352,15 +18283,6 @@ "node": ">= 0.6" } }, - "node_modules/webpack-dev-server/node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/webpack-dev-server/node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -18490,20 +18412,6 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, - "node_modules/webpack-dev-server/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/webpack-dev-server/node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -19091,18 +18999,18 @@ } }, "@angular-devkit/architect": { - "version": "0.2003.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.13.tgz", - "integrity": "sha512-JyH6Af6PNC1IHJToColFk1RaXDU87mpPjz7M5sWDfn8bC+KBipw6dSdRkCEuw0D9HY1lZkC9EBV9k9GhpvHjCQ==", + "version": "0.2003.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.14.tgz", + "integrity": "sha512-dVlWqaYu0PIgHTBu16uYUS6lJOIpXCpOYhPWuYwqdo7a4x2HcagPQ+omUZJTA6kukh7ROpKcRoiy/DsO/DgvUA==", "requires": { - "@angular-devkit/core": "20.3.13", + "@angular-devkit/core": "20.3.14", "rxjs": "7.8.2" }, "dependencies": { "@angular-devkit/core": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.13.tgz", - "integrity": "sha512-/D84T1Caxll3I2sRihPDR9UaWBhF50M+tAX15PdP6uSh/TxwAlLl9p7Rm1bD0mPjPercqaEKA+h9a9qLP16hug==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.14.tgz", + "integrity": "sha512-hWQVi73aGdIRInJqNia79Yi6SzqEThkfLug3AdZiNuNvYMaxAI347yPQz4f3Dr/i0QuiqRq/T8zfqbr46tfCqg==", "requires": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -19166,15 +19074,15 @@ } }, "@angular-devkit/build-angular": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.3.13.tgz", - "integrity": "sha512-wEM5UHc37XGtH9FFVXZPwlZooccveL1VnFUbd2ArECGi4ylW+YgjeVSe0m6uJDvWOXULNVAoHlabXTXvmqV09A==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.3.14.tgz", + "integrity": "sha512-L3saxbGXUgSSXfCrWHTl9eBAxzcA1oSrb0ojL+NBiJ82Zhx1a3XIGSTNg7YkCrXHaLx+fvuoeyT7xyBH18zYZw==", "requires": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2003.13", - "@angular-devkit/build-webpack": "0.2003.13", - "@angular-devkit/core": "20.3.13", - "@angular/build": "20.3.13", + "@angular-devkit/architect": "0.2003.14", + "@angular-devkit/build-webpack": "0.2003.14", + "@angular-devkit/core": "20.3.14", + "@angular/build": "20.3.14", "@babel/core": "7.28.3", "@babel/generator": "7.28.3", "@babel/helper-annotate-as-pure": "7.27.3", @@ -19185,7 +19093,7 @@ "@babel/preset-env": "7.28.3", "@babel/runtime": "7.28.3", "@discoveryjs/json-ext": "0.6.3", - "@ngtools/webpack": "20.3.13", + "@ngtools/webpack": "20.3.14", "ansi-colors": "4.1.3", "autoprefixer": "10.4.21", "babel-loader": "10.0.0", @@ -19228,9 +19136,9 @@ }, "dependencies": { "@angular-devkit/core": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.13.tgz", - "integrity": "sha512-/D84T1Caxll3I2sRihPDR9UaWBhF50M+tAX15PdP6uSh/TxwAlLl9p7Rm1bD0mPjPercqaEKA+h9a9qLP16hug==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.14.tgz", + "integrity": "sha512-hWQVi73aGdIRInJqNia79Yi6SzqEThkfLug3AdZiNuNvYMaxAI347yPQz4f3Dr/i0QuiqRq/T8zfqbr46tfCqg==", "requires": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -19346,20 +19254,20 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.2003.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2003.13.tgz", - "integrity": "sha512-k57PdWOB64/u2MQYPylQNCKDSAHGsV0T2bvvZid2wfPJ7anvSUCU15OQMCMU1JMR0JENZEyIsLw9teShAO9w0Q==", + "version": "0.2003.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2003.14.tgz", + "integrity": "sha512-kE5bCHnmkWRhCxTlZj1E75UECArc1tq3RC1LMMeJL0Wj6tFO5Y33u16zucXbGqYHZETuz4svR0/u800bjMaQyg==", "requires": { - "@angular-devkit/architect": "0.2003.13", + "@angular-devkit/architect": "0.2003.14", "rxjs": "7.8.2" } }, "@angular-devkit/schematics": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.13.tgz", - "integrity": "sha512-hdMKY4rUTko8xqeWYGnwwDYDomkeOoLsYsP6SdaHWK7hpGvzWsT6Q/aIv8J8NrCYkLu+M+5nLiKOooweUZu3GQ==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.14.tgz", + "integrity": "sha512-+Al9QojzTucccSUnJI+9x64Nnuev82eIgIlb1Ov9hLR572SNtjhV7zIXIalphFghEy+SPvynRuvOSc69Otp3Fg==", "requires": { - "@angular-devkit/core": "20.3.13", + "@angular-devkit/core": "20.3.14", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", "ora": "8.2.0", @@ -19367,9 +19275,9 @@ }, "dependencies": { "@angular-devkit/core": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.13.tgz", - "integrity": "sha512-/D84T1Caxll3I2sRihPDR9UaWBhF50M+tAX15PdP6uSh/TxwAlLl9p7Rm1bD0mPjPercqaEKA+h9a9qLP16hug==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.14.tgz", + "integrity": "sha512-hWQVi73aGdIRInJqNia79Yi6SzqEThkfLug3AdZiNuNvYMaxAI347yPQz4f3Dr/i0QuiqRq/T8zfqbr46tfCqg==", "requires": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -19433,20 +19341,20 @@ } }, "@angular/animations": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.15.tgz", - "integrity": "sha512-ikyKfhkxoqQA6JcBN0B9RaN6369sM1XYX81Id0lI58dmWCe7gYfrTp8ejqxxKftl514psQO3pkW8Gn1nJ131Gw==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.16.tgz", + "integrity": "sha512-N83/GFY5lKNyWgPV3xHHy2rb3/eP1ZLzSVI+dmMVbf3jbqwY1YPQcMiAG8UDzaILY1Dkus91kWLF8Qdr3nHAzg==", "requires": { "tslib": "^2.3.0" } }, "@angular/build": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.13.tgz", - "integrity": "sha512-/5pM3ZS+lLkZgA+n6TMmNV8I6t9Ow1C6Vkj6bXqWeOgFDH5LwnIEZFAKzEDBkCGos0m2gPKPcREcDD5tfp9h4g==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.14.tgz", + "integrity": "sha512-ajFJqTqyI2N9PYcWVxUfb6YEUQsZ13jsBzI/kDpeEZZCGadLJGSMZVNwkX7n9Csw7gzertpenGBXsSTxUjd8TA==", "requires": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2003.13", + "@angular-devkit/architect": "0.2003.14", "@babel/core": "7.28.3", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", @@ -19640,17 +19548,17 @@ } }, "@angular/cli": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.13.tgz", - "integrity": "sha512-G78I/HDJULloS2LSqfUfbmBlhDCbcWujIRWfuMnGsRf82TyGA2OEPe3IA/F8MrJfeOzPQim2fMyn24MqHL40Vg==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.14.tgz", + "integrity": "sha512-vlvnxyUtPnETl5az+creSPOrcnrZC5mhD5hSGl2WoqhYeyWdyUwsC9KLSy8/5gCH/4TNwtjqeX3Pw0KaAJUoCQ==", "requires": { - "@angular-devkit/architect": "0.2003.13", - "@angular-devkit/core": "20.3.13", - "@angular-devkit/schematics": "20.3.13", + "@angular-devkit/architect": "0.2003.14", + "@angular-devkit/core": "20.3.14", + "@angular-devkit/schematics": "20.3.14", "@inquirer/prompts": "7.8.2", "@listr2/prompt-adapter-inquirer": "3.0.1", - "@modelcontextprotocol/sdk": "1.24.0", - "@schematics/angular": "20.3.13", + "@modelcontextprotocol/sdk": "1.25.2", + "@schematics/angular": "20.3.14", "@yarnpkg/lockfile": "1.1.0", "algoliasearch": "5.35.0", "ini": "5.0.0", @@ -19665,9 +19573,9 @@ }, "dependencies": { "@angular-devkit/core": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.13.tgz", - "integrity": "sha512-/D84T1Caxll3I2sRihPDR9UaWBhF50M+tAX15PdP6uSh/TxwAlLl9p7Rm1bD0mPjPercqaEKA+h9a9qLP16hug==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.14.tgz", + "integrity": "sha512-hWQVi73aGdIRInJqNia79Yi6SzqEThkfLug3AdZiNuNvYMaxAI347yPQz4f3Dr/i0QuiqRq/T8zfqbr46tfCqg==", "requires": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -19930,25 +19838,25 @@ } }, "@angular/common": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.15.tgz", - "integrity": "sha512-k4mCXWRFiOHK3bUKfWkRQQ8KBPxW8TAJuKLYCsSHPCpMz6u0eA1F0VlrnOkZVKWPI792fOaEAWH2Y4PTaXlUHw==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.16.tgz", + "integrity": "sha512-GRAziNlntwdnJy3F+8zCOvDdy7id0gITjDnM6P9+n2lXvtDuBLGJKU3DWBbvxcCjtD6JK/g/rEX5fbCxbUHkQQ==", "requires": { "tslib": "^2.3.0" } }, "@angular/compiler": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.15.tgz", - "integrity": "sha512-lMicIAFAKZXa+BCZWs3soTjNQPZZXrF/WMVDinm8dQcggNarnDj4UmXgKSyXkkyqK5SLfnLsXVzrX6ndVT6z7A==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.16.tgz", + "integrity": "sha512-Pt9Ms9GwTThgzdxWBwMfN8cH1JEtQ2DK5dc2yxYtPSaD+WKmG9AVL1PrzIYQEbaKcWk2jxASUHpEWSlNiwo8uw==", "requires": { "tslib": "^2.3.0" } }, "@angular/compiler-cli": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.15.tgz", - "integrity": "sha512-8sJoxodxsfyZ8eJ5r6Bx7BCbazXYgsZ1+dE8t5u5rTQ6jNggwNtYEzkyReoD5xvP+MMtRkos3xpwq4rtFnpI6A==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.16.tgz", + "integrity": "sha512-l3xF/fXfJAl/UrNnH9Ufkr79myjMgXdHq1mmmph2UnpeqilRB1b8lC9sLBV9MipQHVn3dwocxMIvtrcryfOaXw==", "requires": { "@babel/core": "7.28.3", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -20052,31 +19960,31 @@ } }, "@angular/core": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.15.tgz", - "integrity": "sha512-NMbX71SlTZIY9+rh/SPhRYFJU0pMJYW7z/TBD4lqiO+b0DTOIg1k7Pg9ydJGqSjFO1Z4dQaA6TteNuF99TJCNw==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.16.tgz", + "integrity": "sha512-KSFPKvOmWWLCJBbEO+CuRUXfecX2FRuO0jNi9c54ptXMOPHlK1lIojUnyXmMNzjdHgRug8ci9qDuftvC2B7MKg==", "requires": { "tslib": "^2.3.0" } }, "@angular/forms": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.15.tgz", - "integrity": "sha512-gS5hQkinq52pm/7mxz4yHPCzEcmRWjtUkOVddPH0V1BW/HMni/p4Y6k2KqKBeGb9p8S5EAp6PDxDVLOPukp3mg==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.16.tgz", + "integrity": "sha512-1yzbXpExTqATpVcqA3wGrq4ACFIP3mRxA4pbso5KoJU+/4JfzNFwLsDaFXKpm5uxwchVnj8KM2vPaDOkvtp7NA==", "requires": { "tslib": "^2.3.0" } }, "@angular/language-service": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.15.tgz", - "integrity": "sha512-oD5rvAsZYzNqdJqMTYYp6T9yITG6axTI/j64v3qxHe+Y/PlHKfNHXcjENpA+LcR5wq0wtIE+s96APykCq9ouEQ==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.16.tgz", + "integrity": "sha512-0A/tSQPq5geIz2mMcZA5fzzbzT39v+ADQksnfPr8htNxtkYWy+EI5+d0+++k59NuvjLY4uTBqhRTRB9b1PKrjw==", "dev": true }, "@angular/localize": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.15.tgz", - "integrity": "sha512-vJDLXzQgLE+zpzwT2n85yWmWHuk9BsnPByOHyF63K178GFs0TG49UqZOFKohGbq5Vlo1GI9NIvXkcegkoMJCMQ==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.16.tgz", + "integrity": "sha512-7S2ACDZC1Ag1+rc991BMvW6gDyBCH7ykGWpZL1aHOmnq4K70Sf8p2VyQMtYhaz7XfWeXxwBQjCncVYv6D7RO5A==", "requires": { "@babel/core": "7.28.3", "@types/babel__core": "7.20.5", @@ -20163,42 +20071,42 @@ } }, "@angular/platform-browser": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.15.tgz", - "integrity": "sha512-TxRM/wTW/oGXv/3/Iohn58yWoiYXOaeEnxSasiGNS1qhbkcKtR70xzxW6NjChBUYAixz2ERkLURkpx3pI8Q6Dw==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.16.tgz", + "integrity": "sha512-YsrLS6vyS77i4pVHg4gdSBW74qvzHjpQRTVQ5Lv/OxIjJdYYYkMmjNalCNgy1ZuyY6CaLIB11ccxhrNnxfKGOQ==", "requires": { "tslib": "^2.3.0" } }, "@angular/platform-browser-dynamic": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.15.tgz", - "integrity": "sha512-RizuRdBt0d6ongQ2y8cr8YsXFyjF8f91vFfpSNw+cFj+oiEmRC1txcWUlH5bPLD9qSDied8qazUi0Tb8VPQDGw==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.16.tgz", + "integrity": "sha512-5mECCV9YeKH6ue239GXRTGeDSd/eTbM1j8dDejhm5cGnPBhTxRw4o+GgSrWTYtb6VmIYdwUGBTC+wCBphiaQ2A==", "requires": { "tslib": "^2.3.0" } }, "@angular/platform-server": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.15.tgz", - "integrity": "sha512-OB3/ztCREeZ0pe2P+43Nah9Xq2Y79fN6mbsOY1JwwYxkM8ZN1WkSP11xlHHwAcoquHP7uFPhXwJqgTHBqGqkcw==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.16.tgz", + "integrity": "sha512-LxQscYd3UCWV8H3sdlnM05UB60MZVuVsdsHvXdkJ9+WOQjVDN1l1rYhj2aDL/5KkaRd/nqo0yFRnVjwceXDJhQ==", "requires": { "tslib": "^2.3.0", "xhr2": "^0.2.0" } }, "@angular/router": { - "version": "20.3.15", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.15.tgz", - "integrity": "sha512-6+qgk8swGSoAu7ISSY//GatAyCP36hEvvUgvjbZgkXLLH9yUQxdo77ij05aJ5s0OyB25q/JkqS8VTY0z1yE9NQ==", + "version": "20.3.16", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.16.tgz", + "integrity": "sha512-e1LiQFZaajKqc00cY5FboIrWJZSMnZ64GDp5R0UejritYrqorQQQNOqP1W85BMuY2owibMmxVfX+dJg/Mc8PuQ==", "requires": { "tslib": "^2.3.0" } }, "@angular/ssr": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-20.3.13.tgz", - "integrity": "sha512-ef3yQG9wVIYzHXKNqAXAgI9YBJ5r4F8KbDtNMA6TDDxuAtTdoXYT1kmlgj5OcTVDDXGNFb12ZALRjB7yKu+zSw==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-20.3.14.tgz", + "integrity": "sha512-pKUH0IpoUuMpQ29MuZuix2jdhvlOm8nFbhZnCHSr7ozB21DE+XC5EXro5wobGGvP+Z5uKrRrYgrdRYgoHHvmYw==", "requires": { "tslib": "^2.3.0" } @@ -21220,9 +21128,9 @@ } }, "@cypress/request": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", - "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.10.tgz", + "integrity": "sha512-hauBrOdvu08vOsagkZ/Aju5XuiZx6ldsLfByg1htFeldhex+PeMrYauANzFsMJeAA0+dyPLbDoX2OYuvVoLDkQ==", "optional": true, "requires": { "aws-sign2": "~0.7.0", @@ -21238,22 +21146,11 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "6.14.0", + "qs": "~6.14.1", "safe-buffer": "^5.1.2", "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" - }, - "dependencies": { - "qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "optional": true, - "requires": { - "side-channel": "^1.1.0" - } - } } }, "@cypress/schematic": { @@ -21643,6 +21540,12 @@ "@hapi/hoek": "^11.0.2" } }, + "@hono/node-server": { + "version": "1.19.8", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.8.tgz", + "integrity": "sha512-0/g2lIOPzX8f3vzW1ggQgvG5mjtFBDBHFAzI5SFAi2DzSqS9luJwqg9T6O/gKYLi+inS7eNxBeIFkkghIPvrMA==", + "requires": {} + }, "@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -22078,10 +21981,11 @@ "optional": true }, "@modelcontextprotocol/sdk": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.24.0.tgz", - "integrity": "sha512-D8h5KXY2vHFW8zTuxn2vuZGN0HGrQ5No6LkHwlEA9trVgNdPL3TF1dSqKA7Dny6BbBYKSW/rOBDXdC8KJAjUCg==", + "version": "1.25.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz", + "integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==", "requires": { + "@hono/node-server": "^1.19.7", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", @@ -22092,6 +21996,7 @@ "express": "^5.0.1", "express-rate-limit": "^7.5.0", "jose": "^6.1.1", + "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", @@ -22315,9 +22220,9 @@ } }, "@ngtools/webpack": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.3.13.tgz", - "integrity": "sha512-7GyH55pOy8XUwo1lVWHzjZoAmSLtRT/vQbMn43x7WDl8pymAbi5zfwE/cnIX+5xgUOvkmT8sW9gJAD19rkASag==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.3.14.tgz", + "integrity": "sha512-2rI9naKQBtoyQmddCnPp94o0lQVl5VPsCefntMg2T0XCCdyNj2OK4X4QCVRrtAcTwlYMysDSTpPKE/VkPcjIQA==", "requires": {} }, "@noble/secp256k1": { @@ -22776,19 +22681,19 @@ "optional": true }, "@schematics/angular": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.13.tgz", - "integrity": "sha512-ETJ1budKmrkdxojo5QP6TPr6zQZYGxtWWf8NrX1cBIS851zPCmFkKyhSFLZsoksariYF/LP8ljvm8tlcIzt/XA==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.14.tgz", + "integrity": "sha512-JO37puMXFWN8YWqZZJ/URs8vPJNszZXcIyBnYdKDWTGaAnbOZMu0nzQlOC+h5NM7R5cPQtOpJv0wxEnY6EYI4A==", "requires": { - "@angular-devkit/core": "20.3.13", - "@angular-devkit/schematics": "20.3.13", + "@angular-devkit/core": "20.3.14", + "@angular-devkit/schematics": "20.3.14", "jsonc-parser": "3.3.1" }, "dependencies": { "@angular-devkit/core": { - "version": "20.3.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.13.tgz", - "integrity": "sha512-/D84T1Caxll3I2sRihPDR9UaWBhF50M+tAX15PdP6uSh/TxwAlLl9p7Rm1bD0mPjPercqaEKA+h9a9qLP16hug==", + "version": "20.3.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.14.tgz", + "integrity": "sha512-hWQVi73aGdIRInJqNia79Yi6SzqEThkfLug3AdZiNuNvYMaxAI347yPQz4f3Dr/i0QuiqRq/T8zfqbr46tfCqg==", "requires": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -23989,22 +23894,22 @@ "optional": true }, "body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "requires": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "dependencies": { "debug": { @@ -24015,6 +23920,18 @@ "ms": "2.0.0" } }, + "http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "requires": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -24027,6 +23944,11 @@ "requires": { "ee-first": "1.1.1" } + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" } } }, @@ -24280,15 +24202,10 @@ "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^12.0.0", - "tar": "^7.4.3", + "tar": "^7.5.5", "unique-filename": "^4.0.0" }, "dependencies": { - "chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==" - }, "lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -24298,23 +24215,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==" - }, - "tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", - "requires": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - } - }, - "yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==" } } }, @@ -24432,9 +24332,9 @@ } }, "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==" }, "chrome-trace-event": { "version": "1.0.3", @@ -24834,12 +24734,12 @@ "peer": true }, "cypress": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.8.1.tgz", - "integrity": "sha512-ogc62stTQGh1395ipKxfCE5hQuSApTzeH5e0d9U6m7wYO9HQeCpgnkYtBtd0MbkN2Fnch5Od2mX9u4hoTlrH4Q==", + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.9.0.tgz", + "integrity": "sha512-Ks6Bdilz3TtkLZtTQyqYaqtL/WT3X3APKaSLhTV96TmTyudzSjc6EJsJCHmBb7DxO+3R12q3Jkbjgm/iPgmwfg==", "optional": true, "requires": { - "@cypress/request": "^3.0.9", + "@cypress/request": "^3.0.10", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", @@ -25077,9 +24977,9 @@ "peer": true }, "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true }, "dijkstrajs": { @@ -25871,14 +25771,6 @@ "ee-first": "1.1.1" } }, - "qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "requires": { - "side-channel": "^1.1.0" - } - }, "raw-body": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", @@ -26446,6 +26338,12 @@ "function-bind": "^1.1.2" } }, + "hono": { + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz", + "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==", + "peer": true + }, "hosted-git-info": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", @@ -26988,6 +26886,11 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==" + }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -27899,33 +27802,16 @@ "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", - "tar": "^7.4.3", + "tar": "^7.5.5", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "dependencies": { - "chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==" - }, "isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==" }, - "tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", - "requires": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - } - }, "which": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", @@ -27933,11 +27819,6 @@ "requires": { "isexe": "^3.1.1" } - }, - "yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==" } } }, @@ -28401,7 +28282,7 @@ "promise-retry": "^2.0.1", "sigstore": "^3.0.0", "ssri": "^12.0.0", - "tar": "^6.1.11" + "tar": "^7.5.5" }, "dependencies": { "hosted-git-info": { @@ -28829,11 +28710,11 @@ } }, "qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "requires": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" } }, "queue-microtask": { @@ -28855,14 +28736,33 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "dependencies": { + "http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "requires": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + } + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" + } } }, "readable-stream": { @@ -29535,9 +29435,9 @@ }, "dependencies": { "diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", "optional": true } } @@ -29931,64 +29831,21 @@ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" }, "tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz", + "integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==", "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "dependencies": { - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "requires": { - "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "requires": { - "yallist": "^4.0.0" - } - } - } - }, - "minipass": { + "yallist": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==" - }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "requires": { - "yallist": "^4.0.0" - } - } - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==" } } }, @@ -30573,11 +30430,6 @@ "safe-buffer": "5.2.1" } }, - "cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" - }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -30676,14 +30528,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, - "qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "requires": { - "side-channel": "^1.1.0" - } - }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 1234a9766..1b847312e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,16 +24,18 @@ "tsc": "./node_modules/typescript/bin/tsc", "i18n-extract-from-source": "npm run ng -- extract-i18n --out-file ./src/locale/messages.xlf", "i18n-pull-from-transifex": "tx pull -a --minimum-perc 1 --force", - "serve": "npm run generate-config && npm run ng -- serve -c local", - "serve:local-prod": "npm run generate-config && npm run ng -- serve -c local-prod", - "serve:parameterized": "npm run generate-config && npm run ng -- serve -c parameterized", - "start": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local", - "start:parameterized": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c parameterized", - "start:local-esplora": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-esplora", - "start:local-prod": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-prod", - "start:mixed": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c mixed", - "build": "npm run generate-config && npm run ng -- build --configuration production --localize && npm run sync-assets-dev && npm run sync-assets", - "sync-assets": "rsync -av ./src/resources ./dist/mempool/browser && node sync-assets.js 'dist/mempool/browser/resources/'", + "serve": "npm run generate-themes && npm run generate-config && npm run ng -- serve -c local", + "serve:local-prod": "npm run generate-themes && npm run generate-config && npm run ng -- serve -c local-prod", + "serve:parameterized": "npm run generate-themes && npm run generate-config && npm run ng -- serve -c parameterized", + "start": "npm run generate-themes && npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local", + "start:parameterized": "npm run generate-themes && npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c parameterized", + "start:local-esplora": "npm run generate-themes && npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-esplora", + "start:local-prod": "npm run generate-themes && npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-prod", + "start:mixed": "npm run generate-themes && npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c mixed", + "build": "npm run generate-themes && npm run generate-config && npm run ng -- build --configuration production --localize && npm run sync-assets-dev && npm run sync-assets", + "generate-themes": "node generate-themes.js", + "copy-themes": "node generate-themes.js copy", + "sync-assets": "npm run copy-themes && rsync -av ./src/resources ./dist/mempool/browser && node sync-assets.js 'dist/mempool/browser/resources/'", "sync-assets-dev": "node sync-assets.js 'src/resources/'", "generate-config": "node generate-config.js", "test": "npm run ng -- test", @@ -57,19 +59,19 @@ "cypress:run:ci:parameterized": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:parameterized 4200 cypress:run:record" }, "dependencies": { - "@angular-devkit/build-angular": "^20.3.13", - "@angular/animations": "^20.3.15", - "@angular/cli": "^20.3.13", - "@angular/common": "^20.3.15", - "@angular/compiler": "^20.3.15", - "@angular/core": "^20.3.15", - "@angular/forms": "^20.3.15", - "@angular/localize": "^20.3.15", - "@angular/platform-browser": "^20.3.15", - "@angular/platform-browser-dynamic": "^20.3.15", - "@angular/platform-server": "^20.3.15", - "@angular/router": "^20.3.15", - "@angular/ssr": "^20.3.13", + "@angular-devkit/build-angular": "^20.3.14", + "@angular/animations": "^20.3.16", + "@angular/cli": "^20.3.14", + "@angular/common": "^20.3.16", + "@angular/compiler": "^20.3.16", + "@angular/core": "^20.3.16", + "@angular/forms": "^20.3.16", + "@angular/localize": "^20.3.16", + "@angular/platform-browser": "^20.3.16", + "@angular/platform-browser-dynamic": "^20.3.16", + "@angular/platform-server": "^20.3.16", + "@angular/router": "^20.3.16", + "@angular/ssr": "^20.3.14", "@fortawesome/angular-fontawesome": "^3.0.0", "@fortawesome/fontawesome-common-types": "~6.7.2", "@fortawesome/fontawesome-svg-core": "~6.7.2", @@ -89,8 +91,8 @@ "zone.js": "~0.15.1" }, "devDependencies": { - "@angular/compiler-cli": "^20.3.15", - "@angular/language-service": "^20.3.15", + "@angular/compiler-cli": "^20.3.16", + "@angular/language-service": "^20.3.16", "@types/node": "^24.9.2", "@typescript-eslint/eslint-plugin": "^8.46.2", "@typescript-eslint/parser": "^8.46.2", @@ -104,12 +106,15 @@ }, "optionalDependencies": { "@cypress/schematic": "^2.5.0", - "cypress": "^15.8.1", + "cypress": "^15.9.0", "cypress-fail-on-console-error": "~5.1.1", "cypress-wait-until": "^3.0.1", "mock-socket": "~9.3.1", "start-server-and-test": "~2.1.2" }, + "overrides": { + "tar": "^7.5.5" + }, "scarfSettings": { "enabled": false } diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 5f69b55cf..8caba9582 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -1,6 +1,6 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; -import { AppPreloadingStrategy } from '@app/app.preloading-strategy' +import { AppPreloadingStrategy } from '@app/app.preloading-strategy'; import { BlockViewComponent } from '@components/block-view/block-view.component'; import { EightBlocksComponent } from '@components/eight-blocks/eight-blocks.component'; import { MempoolBlockViewComponent } from '@components/mempool-block-view/mempool-block-view.component'; diff --git a/frontend/src/app/app.constants.ts b/frontend/src/app/app.constants.ts index 2b6661fcc..730da5a81 100644 --- a/frontend/src/app/app.constants.ts +++ b/frontend/src/app/app.constants.ts @@ -83,45 +83,45 @@ export const contrastMempoolFeeColors = [ ]; export const chartColors = [ - "#A81524", - "#D81B60", - "#8E24AA", - "#5E35B1", - "#3949AB", - "#1E88E5", - "#039BE5", - "#00ACC1", - "#00897B", - "#43A047", - "#7CB342", - "#C0CA33", - "#FDD835", - "#FFB300", - "#FB8C00", - "#F4511E", - "#6D4C41", - "#757575", - "#546E7A", - "#b71c1c", - "#880E4F", - "#4A148C", - "#311B92", - "#1A237E", - "#0D47A1", - "#01579B", - "#006064", - "#004D40", - "#1B5E20", - "#33691E", - "#827717", - "#F57F17", - "#FF6F00", - "#E65100", - "#BF360C", - "#3E2723", - "#212121", - "#263238", - "#801313", + '#A81524', + '#D81B60', + '#8E24AA', + '#5E35B1', + '#3949AB', + '#1E88E5', + '#039BE5', + '#00ACC1', + '#00897B', + '#43A047', + '#7CB342', + '#C0CA33', + '#FDD835', + '#FFB300', + '#FB8C00', + '#F4511E', + '#6D4C41', + '#757575', + '#546E7A', + '#b71c1c', + '#880E4F', + '#4A148C', + '#311B92', + '#1A237E', + '#0D47A1', + '#01579B', + '#006064', + '#004D40', + '#1B5E20', + '#33691E', + '#827717', + '#F57F17', + '#FF6F00', + '#E65100', + '#BF360C', + '#3E2723', + '#212121', + '#263238', + '#801313', ]; export const originalChartColors = chartColors.slice(1); diff --git a/frontend/src/app/app.preloading-strategy.ts b/frontend/src/app/app.preloading-strategy.ts index f62d072da..dc8c56232 100644 --- a/frontend/src/app/app.preloading-strategy.ts +++ b/frontend/src/app/app.preloading-strategy.ts @@ -3,7 +3,7 @@ import { Observable, timer, mergeMap, of } from 'rxjs'; export class AppPreloadingStrategy implements PreloadingStrategy { preload(route: Route, load: Function): Observable { - return route.data && route.data.preload + return route.data && route.data.preload ? timer(1500).pipe(mergeMap(() => load())) : of(null); } diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index 7c4e11218..866a23b33 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -428,12 +428,20 @@
-

Powered By

+

Powered By

+
+
+

Managed By

+
+ + + +
diff --git a/frontend/src/app/components/address/address.component.ts b/frontend/src/app/components/address/address.component.ts index 729a869da..e11d7cfa4 100644 --- a/frontend/src/app/components/address/address.component.ts +++ b/frontend/src/app/components/address/address.component.ts @@ -102,6 +102,7 @@ export class AddressComponent implements OnInit, OnDestroy { isMobile: boolean; showQR: boolean = false; + officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE; address: Address; addressString: string; diff --git a/frontend/src/app/components/addresses-treemap/addresses-treemap.component.ts b/frontend/src/app/components/addresses-treemap/addresses-treemap.component.ts index ccc58e9c2..0ae1c9bcb 100644 --- a/frontend/src/app/components/addresses-treemap/addresses-treemap.component.ts +++ b/frontend/src/app/components/addresses-treemap/addresses-treemap.component.ts @@ -122,7 +122,7 @@ export class AddressesTreemap implements OnChanges { } } ] - }; + }; } formatValue(sats: number): string { diff --git a/frontend/src/app/components/app/app.component.ts b/frontend/src/app/components/app/app.component.ts index d0384b322..d6fd9a2b0 100644 --- a/frontend/src/app/components/app/app.component.ts +++ b/frontend/src/app/components/app/app.component.ts @@ -46,7 +46,7 @@ export class AppComponent implements OnInit { return; } // prevent arrow key horizontal scrolling - if(["ArrowLeft","ArrowRight"].indexOf(event.code) > -1) { + if(['ArrowLeft','ArrowRight'].indexOf(event.code) > -1) { event.preventDefault(); } this.stateService.keyNavigation$.next(event); diff --git a/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts b/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts index 90bcea9ad..bc5c40f3f 100644 --- a/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts +++ b/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts @@ -73,10 +73,10 @@ export class AssetsNavComponent implements OnInit { return assets.array.slice(0, this.itemsPerPage); } }) - ) + ); }), ); - } + }; itemSelected() { setTimeout(() => this.search()); diff --git a/frontend/src/app/components/balance-widget/balance-widget.component.ts b/frontend/src/app/components/balance-widget/balance-widget.component.ts index 82ebde8ee..c9ac28d59 100644 --- a/frontend/src/app/components/balance-widget/balance-widget.component.ts +++ b/frontend/src/app/components/balance-widget/balance-widget.component.ts @@ -31,7 +31,7 @@ export class BalanceWidgetComponent implements OnInit, OnChanges { ) { } ngOnInit(): void { - + } ngOnChanges(changes: SimpleChanges): void { diff --git a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts index 85dfb9b8a..8394ce608 100644 --- a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts +++ b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts @@ -165,7 +165,7 @@ export class BlockFeeRatesGraphComponent implements OnInit { } if (this.widget) { - let maResolution = 30; + const maResolution = 30; const medianMa = []; for (let i = maResolution - 1; i < seriesData['Median'].length; ++i) { let avg = 0; diff --git a/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.ts b/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.ts index 2c03f999e..ec3c98262 100644 --- a/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.ts +++ b/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.ts @@ -112,7 +112,7 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { blockSubsidyFiat: response.body.filter(val => val['USD'] > 0).map(val => this.subsidyAt(val.avgHeight) / 100_000_000 * val['USD']), blockSubsidyPercent: response.body.map(val => this.subsidyAt(val.avgHeight) / (val.avgFees + this.subsidyAt(val.avgHeight)) * 100), }; - + this.prepareChartOptions(); this.isLoading = false; }), @@ -176,12 +176,12 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { for (let i = data.length - 1; i >= 0; i--) { const tick = data[i]; tooltip += `${tick.marker} ${tick.seriesName.split(' ')[0]}: `; - if (this.displayMode === 'normal') tooltip += `${formatNumber(tick.data, this.locale, '1.0-3')} BTC
`; - else if (this.displayMode === 'fiat') tooltip += `${this.fiatCurrencyPipe.transform(tick.data, null, 'USD') }
`; - else tooltip += `${formatNumber(tick.data, this.locale, '1.0-2')}%
`; + if (this.displayMode === 'normal') {tooltip += `${formatNumber(tick.data, this.locale, '1.0-3')} BTC
`;} + else if (this.displayMode === 'fiat') {tooltip += `${this.fiatCurrencyPipe.transform(tick.data, null, 'USD') }
`;} + else {tooltip += `${formatNumber(tick.data, this.locale, '1.0-2')}%
`;} } - if (this.displayMode === 'normal') tooltip += `
${formatNumber(data.reduce((acc, val) => acc + val.data, 0), this.locale, '1.0-3')} BTC
`; - else if (this.displayMode === 'fiat') tooltip += `
${this.fiatCurrencyPipe.transform(data.reduce((acc, val) => acc + val.data, 0), null, 'USD')}
`; + if (this.displayMode === 'normal') {tooltip += `
${formatNumber(data.reduce((acc, val) => acc + val.data, 0), this.locale, '1.0-3')} BTC
`;} + else if (this.displayMode === 'fiat') {tooltip += `
${this.fiatCurrencyPipe.transform(data.reduce((acc, val) => acc + val.data, 0), null, 'USD')}
`;} if (['24h', '3d'].includes(this.zoomTimeSpan)) { tooltip += `` + $localize`At block ${'' + data[0].axisValue}` + ``; } else { @@ -410,7 +410,7 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { mode = 'normal'; } - if (this.displayMode === mode) return; + if (this.displayMode === mode) {return;} const isActivation = params.selected[params.name]; @@ -486,7 +486,7 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { tap((response) => { const startIndex = option.dataZoom[0].startValue; const endIndex = option.dataZoom[0].endValue; - + // Update series with more granular data const lengthBefore = this.data.timestamp.length; this.data.timestamp.splice(startIndex, endIndex - startIndex, ...response.body.map(val => val.timestamp * 1000)); @@ -537,7 +537,7 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { } getTimeRangeFromTimespan(from: number, to: number): string { - const timespan = to - from; + const timespan = to - from; switch (true) { case timespan >= 3600 * 24 * 365 * 4: return 'all'; case timespan >= 3600 * 24 * 365 * 3: return '4y'; diff --git a/frontend/src/app/components/block-filters/block-filters.component.ts b/frontend/src/app/components/block-filters/block-filters.component.ts index 143ce4fd0..d71e01ab9 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.ts +++ b/frontend/src/app/components/block-filters/block-filters.component.ts @@ -93,7 +93,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy { this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters, gradient: this.gradientMode }); this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters], gradient: this.gradientMode }); } - + getBooleanFlags(): bigint | null { let flags = 0n; for (const key of Object.keys(this.filterFlags)) { diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts index f0a28f071..58f3c8096 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts @@ -64,7 +64,8 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On @ViewChild('blockCanvas') canvas: ElementRef; - themeChangedSubscription: Subscription; + themeStateSubscription: Subscription; + loadedTheme = 'default'; gl: WebGLRenderingContext; animationFrameRequest: number; @@ -129,7 +130,11 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On if (this.gl) { this.initCanvas(); this.resizeCanvas(); - this.themeChangedSubscription = this.themeService.themeChanged$.subscribe(() => { + this.themeStateSubscription = this.themeService.themeState$.subscribe((state) => { + if (state.loading) { + return; + } + this.loadedTheme = state.theme; this.scene.setColorFunction(this.getColorFunction()); }); } @@ -184,7 +189,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On } this.vertexArray.destroy(); this.vertexArray = null; - this.themeChangedSubscription?.unsubscribe(); + this.themeStateSubscription?.unsubscribe(); this.searchSubscription?.unsubscribe(); } @@ -670,13 +675,13 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On break; } if (matches) { - if (this.themeService.theme !== 'contrast' && this.themeService.theme !== 'bukele') { + if (this.loadedTheme !== 'contrast' && this.loadedTheme !== 'bukele') { return (gradient === 'age') ? ageColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000)) : defaultColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000)); } else { return (gradient === 'age') ? ageColorFunction(tx, contrastColors.fee, contrastAuditColors, this.relativeTime || (Date.now() / 1000)) : contrastColorFunction(tx, contrastColors.fee, contrastAuditColors, this.relativeTime || (Date.now() / 1000)); } } else { - if (this.themeService.theme !== 'contrast' && this.themeService.theme !== 'bukele') { + if (this.loadedTheme !== 'contrast' && this.loadedTheme !== 'bukele') { return (gradient === 'age') ? { r: 1, g: 1, b: 1, a: 0.05 } : defaultColorFunction( tx, defaultColors.unmatchedfee, diff --git a/frontend/src/app/components/block-overview-graph/utils.ts b/frontend/src/app/components/block-overview-graph/utils.ts index 47677c4f1..05fb9931f 100644 --- a/frontend/src/app/components/block-overview-graph/utils.ts +++ b/frontend/src/app/components/block-overview-graph/utils.ts @@ -67,7 +67,7 @@ const defaultColors: { [key: string]: ColorPalette } = { marginal: [], baseLevel: (tx: TxView, rate: number) => feeLevels.findIndex((feeLvl) => Math.max(0, rate) < feeLvl) - 1 }, -} +}; for (const key in defaultColors) { const base = defaultColors[key].base; defaultColors[key].audit = base.map((color) => darken(desaturate(color, 0.3), 0.9)); @@ -98,7 +98,7 @@ const contrastColors: { [key: string]: ColorPalette } = { marginal: [], baseLevel: (tx: TxView, rate: number) => feeLevels.findIndex((feeLvl) => Math.max(0, rate) < feeLvl) - 1 }, -} +}; for (const key in contrastColors) { const base = contrastColors[key].base; contrastColors[key].audit = base.map((color) => darken(desaturate(color, 0.3), 0.9)); diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts index e76d6be97..08f7bc7d8 100644 --- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts @@ -72,7 +72,7 @@ export class BlockOverviewTooltipComponent implements OnChanges { this.hasEffectiveRate = this.tx.acc || !(Math.abs((this.fee / this.vsize) - this.effectiveRate) <= 0.1 && Math.abs((this.fee / Math.ceil(this.vsize)) - this.effectiveRate) <= 0.1) || (txFlags && (txFlags & (TransactionFlags.cpfp_child | TransactionFlags.cpfp_parent)) > 0n); this.filters = this.tx.flags ? toFilters(txFlags).filter(f => f.tooltip) : []; - this.activeFilters = {} + this.activeFilters = {}; for (const filter of this.filters) { if (this.filterFlags && (this.filterFlags & BigInt(filter.flag))) { this.activeFilters[filter.key] = true; diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts index be63af042..e6b75d408 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts @@ -30,7 +30,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { @Input() spotlight: number = 0; @Input() showPools: boolean = true; @Input() getHref?: (index, block) => string = (index, block) => `/block/${block.id}`; - + specialBlocks = specialBlocks; network = ''; blocks: BlockchainBlock[] = []; @@ -174,7 +174,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { } else { this.moveArrowToPosition(true, false); } - }) + }); } else { this.blockPageSubscription = this.cacheService.loadedBlocks$.subscribe((block) => { if (block.height <= this.height && block.height > this.height - this.count) { @@ -363,7 +363,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { convertStyleForLoadingBlock(style) { return { ...style, - background: "var(--secondary)", + background: 'var(--secondary)', }; } @@ -372,7 +372,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { return { left: addLeft + (this.blockOffset * index) + 'px', - background: "var(--secondary)", + background: 'var(--secondary)', }; } @@ -388,7 +388,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { return { left: addLeft + this.blockOffset * this.emptyBlocks.indexOf(block) + 'px', - background: "var(--secondary)", + background: 'var(--secondary)', }; } diff --git a/frontend/src/app/components/blockchain/blockchain.component.ts b/frontend/src/app/components/blockchain/blockchain.component.ts index 757bfee7f..8ac128844 100644 --- a/frontend/src/app/components/blockchain/blockchain.component.ts +++ b/frontend/src/app/components/blockchain/blockchain.component.ts @@ -33,7 +33,7 @@ export class BlockchainComponent implements OnInit, OnDestroy, OnChanges { dividerOffset: number | null = null; mempoolOffset: number | null = null; positionStyle = { - transform: "translateX(1280px)", + transform: 'translateX(1280px)', }; blockDisplayToggleStyle = {}; @@ -91,8 +91,8 @@ export class BlockchainComponent implements OnInit, OnDestroy, OnChanges { } toggleBlockDisplayMode(): void { - if (this.blockDisplayMode === 'size') this.blockDisplayMode = 'fees'; - else this.blockDisplayMode = 'size'; + if (this.blockDisplayMode === 'size') {this.blockDisplayMode = 'fees';} + else {this.blockDisplayMode = 'size';} this.StorageService.setValue('block-display-mode-preference', this.blockDisplayMode); this.stateService.blockDisplayMode$.next(this.blockDisplayMode); } diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.scss b/frontend/src/app/components/blocks-list/blocks-list.component.scss index 9e4465cf1..6c47eb0bf 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.scss +++ b/frontend/src/app/components/blocks-list/blocks-list.component.scss @@ -53,13 +53,13 @@ tr, td, th { .pool { width: 17%; - @media (max-width: 576px) { - width: 34%; - } overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 160px; + @media (max-width: 576px) { + width: 34%; + } } .pool.widget { width: 40%; @@ -138,16 +138,16 @@ tr, td, th { .txs { padding-right: 20px; width: 6%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 100px; @media (max-width: 1100px) { padding-right: 10px; } @media (max-width: 875px) { display: none; } - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 100px; } .txs.widget { padding-right: 0; @@ -186,14 +186,14 @@ tr, td, th { .reward { width: 8%; - @media (max-width: 576px) { - width: 7%; - padding-right: 30px; - } overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 130px; + @media (max-width: 576px) { + width: 7%; + padding-right: 30px; + } } .reward.widget { width: 20%; diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.ts b/frontend/src/app/components/blocks-list/blocks-list.component.ts index 43469d179..be9660081 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.ts +++ b/frontend/src/app/components/blocks-list/blocks-list.component.ts @@ -67,7 +67,7 @@ export class BlocksList implements OnInit { if (!this.widget) { this.websocketService.want(['blocks']); - + this.seoService.setTitle($localize`:@@8a7b4bd44c0ac71b2e72de0398b303257f7d2f54:Blocks`); this.ogService.setManualOgImage('recent-blocks.jpg'); if( this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet' ) { @@ -110,7 +110,7 @@ export class BlocksList implements OnInit { this.skeletonLines = this.widget === true ? [...Array(6).keys()] : [...Array(15).keys()]; this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5; - + this.blocks$ = combineLatest([ this.fromHeightSubject.pipe( filter(fromBlockHeight => fromBlockHeight !== this.lastBlockHeightFetched), diff --git a/frontend/src/app/components/calculator/calculator.component.ts b/frontend/src/app/components/calculator/calculator.component.ts index 1beadeed5..94732f86e 100644 --- a/frontend/src/app/components/calculator/calculator.component.ts +++ b/frontend/src/app/components/calculator/calculator.component.ts @@ -41,7 +41,7 @@ export class CalculatorComponent implements OnInit { let currency; this.price$ = this.currency$.pipe( switchMap((result) => { - currency = result; + currency = result; return this.stateService.conversions$.asObservable(); }), map((conversions) => { @@ -124,7 +124,7 @@ export class CalculatorComponent implements OnInit { countDecimals(numberString: string): number { const decimalPos = numberString.indexOf('.'); - if (decimalPos === -1) return 0; + if (decimalPos === -1) {return 0;} return numberString.length - decimalPos - 1; } diff --git a/frontend/src/app/components/clock/clock.component.ts b/frontend/src/app/components/clock/clock.component.ts index 8b7db9f5f..592e5bd64 100644 --- a/frontend/src/app/components/clock/clock.component.ts +++ b/frontend/src/app/components/clock/clock.component.ts @@ -108,7 +108,7 @@ export class ClockComponent implements OnInit { )`, }; } - + @HostListener('window:resize', ['$event']) resizeCanvas(): void { const windowWidth = this.limitWidth || window.innerWidth || 800; diff --git a/frontend/src/app/components/custom-dashboard/custom-dashboard.component.ts b/frontend/src/app/components/custom-dashboard/custom-dashboard.component.ts index ba955c21c..587763a59 100644 --- a/frontend/src/app/components/custom-dashboard/custom-dashboard.component.ts +++ b/frontend/src/app/components/custom-dashboard/custom-dashboard.component.ts @@ -286,7 +286,7 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni getArrayFromNumber(num: number): number[] { return Array.from({ length: num }, (_, i) => i + 1); } - + setFilter(index): void { const selected = this.goggleCycle[index]; this.stateService.activeGoggles$.next(selected); @@ -296,7 +296,7 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni if (this.stateService.env.customize && this.stateService.env.customize.dashboard.widgets.some(w => w.props?.address)) { let addressString = this.stateService.env.customize.dashboard.widgets.find(w => w.props?.address).props.address; addressString = (/^[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100}|04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}$/.test(addressString)) ? addressString.toLowerCase() : addressString; - + this.addressSubscription = ( addressString.match(/04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}/) ? this.electrsApiService.getPubKeyAddress$(addressString) diff --git a/frontend/src/app/components/difficulty/difficulty-tooltip.component.ts b/frontend/src/app/components/difficulty/difficulty-tooltip.component.ts index 9ad44a629..f889c68e1 100644 --- a/frontend/src/app/components/difficulty/difficulty-tooltip.component.ts +++ b/frontend/src/app/components/difficulty/difficulty-tooltip.component.ts @@ -26,7 +26,7 @@ const EPOCH_BLOCK_LENGTH = 2016; // Bitcoin mainnet }) export class DifficultyTooltipComponent implements OnChanges { @Input() status: string | void; - @Input() progress: EpochProgress | void = null; + @Input() progress: EpochProgress | void = null; @Input() cursorPosition: { x: number, y: number }; mined: number; @@ -49,7 +49,7 @@ export class DifficultyTooltipComponent implements OnChanges { ngOnChanges(changes): void { if (changes.cursorPosition && changes.cursorPosition.currentValue) { let x = changes.cursorPosition.currentValue.x; - let y = changes.cursorPosition.currentValue.y - 50; + const y = changes.cursorPosition.currentValue.y - 50; if (this.tooltipElement) { const elementBounds = this.tooltipElement.nativeElement.getBoundingClientRect(); x -= elementBounds.width / 2; diff --git a/frontend/src/app/components/difficulty/difficulty.component.ts b/frontend/src/app/components/difficulty/difficulty.component.ts index 229b9194b..74347d6f0 100644 --- a/frontend/src/app/components/difficulty/difficulty.component.ts +++ b/frontend/src/app/components/difficulty/difficulty.component.ts @@ -48,7 +48,7 @@ export class DifficultyComponent implements OnInit { @Input() showTitle = true; @ViewChild('epochSvg') epochSvgElement: ElementRef; - + isLoadingWebSocket$: Observable; difficultyEpoch$: Observable; diff --git a/frontend/src/app/components/faucet/faucet.component.ts b/frontend/src/app/components/faucet/faucet.component.ts index 6b4881176..25a239330 100644 --- a/frontend/src/app/components/faucet/faucet.component.ts +++ b/frontend/src/app/components/faucet/faucet.component.ts @@ -174,12 +174,12 @@ export class FaucetComponent implements OnInit, OnDestroy { get amount() { return this.faucetForm.get('satoshis')!; } get invalidAmount() { const amount = this.faucetForm.get('satoshis')!; - return amount?.invalid && (amount.dirty || amount.touched) + return amount?.invalid && (amount.dirty || amount.touched); } get address() { return this.faucetForm.get('address')!; } get invalidAddress() { const address = this.faucetForm.get('address')!; - return address?.invalid && (address.dirty || address.touched) + return address?.invalid && (address.dirty || address.touched); } } diff --git a/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts b/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts index 1bc5aaa1d..34d2c6d66 100644 --- a/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts +++ b/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts @@ -145,7 +145,7 @@ export class FeeDistributionGraphComponent implements OnInit, OnChanges, OnDestr const unitValue = this.weightMode ? value / 4 : value; const selectedPowerOfTen = selectPowerOfTen(unitValue); const scaledValue = unitValue / selectedPowerOfTen.divider; - let newVal = ''; + const newVal = ''; switch (true) { case scaledValue >= 100: return Math.round(scaledValue).toString(); diff --git a/frontend/src/app/components/fees-box/fees-box.component.scss b/frontend/src/app/components/fees-box/fees-box.component.scss index c5843f58b..d1a5b4fff 100644 --- a/frontend/src/app/components/fees-box/fees-box.component.scss +++ b/frontend/src/app/components/fees-box/fees-box.component.scss @@ -26,12 +26,12 @@ width: 100px; margin: 0; width: -webkit-fill-available; + margin: 0 auto 0px; &:first-child { @media (767px < width < 992px), (width < 576px) { display: none } } - margin: 0 auto 0px; &:last-child { margin-bottom: 0; } @@ -81,11 +81,11 @@ transition: background-color 1s; color: #fff; &.priority { + width: 75%; + border-radius: 0px 10px 10px 0px; @media (767px < width < 992px), (width < 576px) { width: 100%; } - width: 75%; - border-radius: 0px 10px 10px 0px; } &:first-child { @media (767px < width < 992px), (width < 576px) { @@ -115,15 +115,15 @@ padding-top: 2px; font-size: 12px; width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + padding-left: 5px; + padding-right: 5px; @media (767px < width < 992px), (width < 576px) { width: 33%; } &.prority { width: 33%; } - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - padding-left: 5px; - padding-right: 5px; } \ No newline at end of file diff --git a/frontend/src/app/components/fees-box/fees-box.component.ts b/frontend/src/app/components/fees-box/fees-box.component.ts index a1c8e7698..728b2be9e 100644 --- a/frontend/src/app/components/fees-box/fees-box.component.ts +++ b/frontend/src/app/components/fees-box/fees-box.component.ts @@ -16,7 +16,7 @@ import { ThemeService } from '@app/services/theme.service'; export class FeesBoxComponent implements OnInit, OnDestroy { isLoading$: Observable; recommendedFees$: Observable; - themeSubscription: Subscription; + themeStateSubscription: Subscription; gradient = 'linear-gradient(to right, var(--skeleton-bg), var(--skeleton-bg))'; noPriority = 'var(--skeleton-bg)'; fees: Recommendedfees; @@ -42,12 +42,17 @@ export class FeesBoxComponent implements OnInit, OnDestroy { } ) ); - this.themeSubscription = this.themeService.themeChanged$.subscribe(() => { - this.setFeeGradient(); - }) + this.themeStateSubscription = this.themeService.themeState$.subscribe((state) => { + if (!state.loading) { + this.setFeeGradient(); + } + }); } setFeeGradient() { + if (!this.fees || !this.themeService.mempoolFeeColors) { + return; + } let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => this.fees.minimumFee >= feeLvl); feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex; const startColor = '#' + (this.themeService.mempoolFeeColors[feeLevelIndex - 1] || this.themeService.mempoolFeeColors[this.themeService.mempoolFeeColors.length - 1]); @@ -63,6 +68,6 @@ export class FeesBoxComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { - this.themeSubscription.unsubscribe(); + this.themeStateSubscription.unsubscribe(); } } diff --git a/frontend/src/app/components/footer/footer.component.ts b/frontend/src/app/components/footer/footer.component.ts index 1df81bc62..191726da4 100644 --- a/frontend/src/app/components/footer/footer.component.ts +++ b/frontend/src/app/components/footer/footer.component.ts @@ -50,7 +50,7 @@ export class FooterComponent implements OnInit { .pipe( map(([mempoolInfo, vbytesPerSecond]) => { const percent = Math.round((Math.min(vbytesPerSecond, this.vBytesPerSecondLimit) / this.vBytesPerSecondLimit) * 100); - + let progressColor = '#7CB342'; if (vbytesPerSecond > 1667) { progressColor = '#FDD835'; @@ -67,7 +67,7 @@ export class FooterComponent implements OnInit { if (vbytesPerSecond > 3500) { progressColor = '#D81B60'; } - + const mempoolSizePercentage = (mempoolInfo.usage / mempoolInfo.maxmempool * 100); let mempoolSizeProgress = 'bg-danger'; if (mempoolSizePercentage <= 50) { @@ -75,7 +75,7 @@ export class FooterComponent implements OnInit { } else if (mempoolSizePercentage <= 75) { mempoolSizeProgress = 'bg-warning'; } - + return { memPoolInfo: mempoolInfo, vBytesPerSecond: vbytesPerSecond, diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts index d72800aa2..ca3a42bd2 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts @@ -164,7 +164,7 @@ export class HashrateChartComponent implements OnInit { diffIndex++; } - let maResolution = 15; + const maResolution = 15; const hashrateMa = []; for (let i = maResolution - 1; i < data.hashrates.length; ++i) { let avg = 0; @@ -258,7 +258,7 @@ export class HashrateChartComponent implements OnInit { if (tick.seriesIndex === 0) { // Hashrate hashrateString = `${tick.marker} ${tick.seriesName}: ${this.amountShortenerPipe.transform(tick.data[1], 3, 'H/s', false, true)}
`; } else if (tick.seriesIndex === 1) { // Difficulty - let difficulty = tick.data[1]; + const difficulty = tick.data[1]; if (difficulty === null) { difficultyString = `${tick.marker} ${tick.seriesName}: No data
`; } else { @@ -361,7 +361,7 @@ export class HashrateChartComponent implements OnInit { return value.min; } const selectedPowerOfTen: any = selectPowerOfTen(firstYAxisMin); - const newMin = Math.floor(firstYAxisMin / selectedPowerOfTen.divider / 10) + const newMin = Math.floor(firstYAxisMin / selectedPowerOfTen.divider / 10); return 600 / 2 ** 32 * newMin * selectedPowerOfTen.divider * 10; }, max: (value) => { diff --git a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts index f7bc7607b..53dc0e54d 100644 --- a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts +++ b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts @@ -110,7 +110,7 @@ export class HashrateChartPoolsComponent implements OnInit { map((response) => { return { blockCount: parseInt(response.headers.get('x-total-count'), 10), - } + }; }), retryWhen((errors) => errors.pipe( delay(60000) @@ -178,7 +178,7 @@ export class HashrateChartPoolsComponent implements OnInit { }, icon: 'roundRect', itemStyle: { - color: poolsColor[name.replace(/[^a-zA-Z0-9]/g, "").toLowerCase()], + color: poolsColor[name.replace(/[^a-zA-Z0-9]/g, '').toLowerCase()], }, }); } diff --git a/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts b/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts index 1aff1fb1c..eaf588527 100644 --- a/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts +++ b/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts @@ -76,7 +76,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On rendered() { if (!this.data) { - return; + return; } } @@ -161,7 +161,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On symbol: 'none', lineStyle: { width: 2, - color: "white", + color: 'white', } }); } diff --git a/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html b/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html index 10af378b1..e6b98f13e 100644 --- a/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html +++ b/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html @@ -4,17 +4,17 @@