From befb18e70d181d957f2b25344e98673d8f796fc0 Mon Sep 17 00:00:00 2001 From: "snyk-io[bot]" <141718529+snyk-io[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 16:37:33 +0000 Subject: [PATCH 1/3] feat: upgrade @typescript-eslint/parser from 5.30.6 to 8.45.0 Snyk has created this PR to upgrade @typescript-eslint/parser from 5.30.6 to 8.45.0. See this package in npm: @typescript-eslint/parser See this project in Snyk: https://app.snyk.io/org/dargon789/project/2e0da4bb-e888-4501-bb36-730035214d1a?utm_source=github-cloud-app&utm_medium=referral&page=upgrade-pr --- unfurler/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unfurler/package.json b/unfurler/package.json index 64f637518e..bcb8c83973 100644 --- a/unfurler/package.json +++ b/unfurler/package.json @@ -27,7 +27,7 @@ }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.30.5", - "@typescript-eslint/parser": "^5.30.5", + "@typescript-eslint/parser": "^8.45.0", "eslint": "^8.19.0", "eslint-config-prettier": "^8.5.0", "prettier": "^2.7.1" From 7f993233cc03855672e3aca61f23f4abbe8fb6ad Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 10 Nov 2025 06:15:36 +0700 Subject: [PATCH 2/3] fix: node bug error (#75) * set content limit to 10mb * Refactor the sync assets script * Tag only release Docker builds as latest * fix chain validation with partial block index * limit stale block index by INDEXING_BLOCKS_AMOUNT * don't fetch stale block data from outdated cache * Update cached blocks with new stale data * Bump dtolnay/rust-toolchain Bumps [dtolnay/rust-toolchain](https://github.com/dtolnay/rust-toolchain) from d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a to 6d653acede28d24f02e3cd41383119e8b1b35921. - [Release notes](https://github.com/dtolnay/rust-toolchain/releases) - [Commits](https://github.com/dtolnay/rust-toolchain/compare/d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a...6d653acede28d24f02e3cd41383119e8b1b35921) --- updated-dependencies: - dependency-name: dtolnay/rust-toolchain dependency-version: 6d653acede28d24f02e3cd41383119e8b1b35921 dependency-type: direct:production ... Signed-off-by: dependabot[bot] * npm audit fix * Replace Citadel with Nirvati * Accept CLA for AaronDewes * Apply suggestions from code review Co-authored-by: mononaut <83316221+mononaut@users.noreply.github.com> * [accelerator] use /accelerator/accelerations/:txid in transaction component * add /accelerator/accelerations/:txid backend route * reduce unnecessary acceleration history requests * update tracker acceleration history pipeline --------- Signed-off-by: dependabot[bot] Co-authored-by: nymkappa <1612910616@pm.me> Co-authored-by: nymkappa <9780671+nymkappa@users.noreply.github.com> Co-authored-by: Felipe Knorr Kuhn Co-authored-by: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Co-authored-by: Mononaut Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: softsimon Co-authored-by: mononaut <83316221+mononaut@users.noreply.github.com> Co-authored-by: softsimon Co-authored-by: Aaron Dewes Co-authored-by: nymkappa --- .github/workflows/ci.yml | 2 +- .github/workflows/on-tag.yml | 8 +- .../api/acceleration/acceleration.routes.ts | 14 + backend/src/api/blocks.ts | 10 +- backend/src/api/chain-tips.ts | 35 +- backend/src/index.ts | 6 +- .../repositories/AccelerationRepository.ts | 26 + backend/src/repositories/BlocksRepository.ts | 11 +- contributors/AaronDewes.txt | 3 + frontend/package-lock.json | 923 +++++++++++------- .../app/components/about/about.component.html | 6 +- .../block/block-preview.component.ts | 2 +- .../app/components/block/block.component.ts | 2 +- .../components/tracker/tracker.component.ts | 65 +- .../transaction/transaction.component.ts | 71 +- .../src/app/services/services-api.service.ts | 4 + frontend/src/resources/profile/nirvati.svg | 1 + frontend/src/resources/profile/runcitadel.svg | 17 - frontend/sync-assets.js | 693 ++++++------- 19 files changed, 1077 insertions(+), 822 deletions(-) create mode 100644 contributors/AaronDewes.txt create mode 100644 frontend/src/resources/profile/nirvati.svg delete mode 100644 frontend/src/resources/profile/runcitadel.svg diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd90358a32..f4a35e5d7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: - name: Install ${{ steps.gettoolchain.outputs.toolchain }} Rust toolchain # Latest version available on this commit is 1.71.1 # Commit date is Aug 3, 2023 - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a + uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 with: toolchain: ${{ steps.gettoolchain.outputs.toolchain }} diff --git a/.github/workflows/on-tag.yml b/.github/workflows/on-tag.yml index 18da775b37..cbebb2cb45 100644 --- a/.github/workflows/on-tag.yml +++ b/.github/workflows/on-tag.yml @@ -117,10 +117,10 @@ jobs: tag-latest: needs: build - if: ${{ needs.build.result == 'success' }} + if: ${{ needs.build.result == 'success' && !contains(github.ref_name, '-') }} runs-on: ubuntu-latest timeout-minutes: 30 - name: Tag images as latest + name: Tag release build as latest strategy: matrix: service: @@ -145,8 +145,8 @@ jobs: - name: Login to Docker Hub run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin - - name: Tag multi-arch image as latest for ${{ matrix.service }} + - name: Tag as latest for ${{ matrix.service }} run: | docker buildx imagetools create \ --tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:latest \ - ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \ No newline at end of file + ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG diff --git a/backend/src/api/acceleration/acceleration.routes.ts b/backend/src/api/acceleration/acceleration.routes.ts index 1c959ad4c6..d70b41b2d2 100644 --- a/backend/src/api/acceleration/acceleration.routes.ts +++ b/backend/src/api/acceleration/acceleration.routes.ts @@ -11,6 +11,7 @@ class AccelerationRoutes { public initRoutes(app: Application): void { app .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations', this.$getAcceleratorAccelerations.bind(this)) + .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/:txid', this.$getAcceleratorAcceleration.bind(this)) .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/history', this.$getAcceleratorAccelerationsHistory.bind(this)) .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/history/aggregated', this.$getAcceleratorAccelerationsHistoryAggregated.bind(this)) .get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/stats', this.$getAcceleratorAccelerationsStats.bind(this)) @@ -23,6 +24,19 @@ class AccelerationRoutes { res.status(200).send(Object.values(accelerations)); } + private async $getAcceleratorAcceleration(req: Request, res: Response): Promise { + if (req.params.txid) { + const acceleration = await AccelerationRepository.$getAccelerationInfoForTxid(req.params.txid); + if (acceleration) { + res.status(200).send(acceleration); + } else { + res.status(404).send('Acceleration not found'); + } + } else { + res.status(400).send('txid is required'); + } + } + private async $getAcceleratorAccelerationsHistory(req: Request, res: Response): Promise { const history = await AccelerationRepository.$getAccelerationInfo(null, req.query.blockHeight ? parseInt(req.query.blockHeight as string, 10) : null); res.status(200).send(history.map(accel => ({ diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 075389196a..d33e8fda6b 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -1272,11 +1272,13 @@ class Blocks { /** * Get one block by its hash */ - public async $getBlock(hash: string): Promise { + public async $getBlock(hash: string, skipMemoryCache: boolean = false): Promise { // Check the memory cache - const blockByHash = this.getBlocks().find((b) => b.id === hash); - if (blockByHash) { - return blockByHash; + if (!skipMemoryCache) { + const blockByHash = this.getBlocks().find((b) => b.id === hash); + if (blockByHash) { + return blockByHash; + } } // Not Bitcoin network, return the block as it from the bitcoin backend diff --git a/backend/src/api/chain-tips.ts b/backend/src/api/chain-tips.ts index 37bd07cae7..b42a83c7e6 100644 --- a/backend/src/api/chain-tips.ts +++ b/backend/src/api/chain-tips.ts @@ -1,7 +1,8 @@ +import config from '../config'; import logger from '../logger'; import { BlockExtended } from '../mempool.interfaces'; import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository'; -import { bitcoinCoreApi } from './bitcoin/bitcoin-api-factory'; +import bitcoinApi, { bitcoinCoreApi } from './bitcoin/bitcoin-api-factory'; import bitcoinClient from './bitcoin/bitcoin-client'; import { IEsploraApi } from './bitcoin/esplora-api.interface'; import blocks from './blocks'; @@ -42,6 +43,13 @@ class ChainTips { try { this.chainTips = await bitcoinClient.getChainTips(); + const activeTipHeight = this.chainTips.find(tip => tip.status === 'active')?.height || (await bitcoinApi.$getBlockHeightTip()); + let minIndexHeight = 0; + const indexedBlockAmount = Math.min(config.MEMPOOL.INDEXING_BLOCKS_AMOUNT, activeTipHeight); + if (indexedBlockAmount > 0) { + minIndexHeight = Math.max(0, activeTipHeight - indexedBlockAmount + 1); + } + const start = Date.now(); const breakAt = start + 10000; let newOrphans = 0; @@ -64,11 +72,24 @@ class ChainTips { prevhash: block.previousblockhash, }; this.blockCache[hash] = orphan; - if (this.indexingQueue.length < this.maxIndexingQueueSize) { - this.indexingQueue.push({ block, tip: orphan }); - } else { - // re-fetch blocks lazily if the queue is big to keep memory usage sane - this.indexingQueue.push({ blockhash: hash, tip: orphan }); + // don't index stale blocks below the INDEXING_BLOCKS_AMOUNT cutoff + if (block.height >= minIndexHeight) { + if (this.indexingQueue.length < this.maxIndexingQueueSize) { + this.indexingQueue.push({ block, tip: orphan }); + } else { + // re-fetch blocks lazily if the queue is big to keep memory usage sane + this.indexingQueue.push({ blockhash: hash, tip: orphan }); + } + } + // make sure the cached canonical block at this height is correct & up to date + if (block.height >= (activeTipHeight - (config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4))) { + const cachedBlocks = blocks.getBlocks(); + for (const cachedBlock of cachedBlocks) { + if (cachedBlock.height === block.height) { + // ensure this stale block is included in the orphans list + cachedBlock.extras.orphans = Array.from(new Set([...(cachedBlock.extras.orphans || []), orphan])); + } + } } } } @@ -141,7 +162,7 @@ class ChainTips { // don't DDOS core by indexing too fast await Common.sleep$(5000); } else if (needToCache) { - staleBlock = await blocks.$getBlock(block.id) as BlockExtended; + staleBlock = await blocks.$getBlock(block.id, true) as BlockExtended; } if (staleBlock && needToCache) { diff --git a/backend/src/index.ts b/backend/src/index.ts index 8fe7d58cac..249d080564 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -142,9 +142,9 @@ class Server { res.setHeader('Access-Control-Expose-Headers', 'X-Total-Count,X-Mempool-Auth'); next(); }) - .use(express.urlencoded({ extended: true })) - .use(express.text({ type: ['text/plain', 'application/base64'] })) - .use(express.json()) + .use(express.urlencoded({ extended: true, limit: '10mb' })) + .use(express.text({ type: ['text/plain', 'application/base64'], limit: '10mb' })) + .use(express.json({ limit: '10mb' })) ; if (config.DATABASE.ENABLED && config.FIAT_PRICE.ENABLED) { diff --git a/backend/src/repositories/AccelerationRepository.ts b/backend/src/repositories/AccelerationRepository.ts index 9c1ae2f904..aa39c89297 100644 --- a/backend/src/repositories/AccelerationRepository.ts +++ b/backend/src/repositories/AccelerationRepository.ts @@ -60,6 +60,32 @@ class AccelerationRepository { } } + public async $getAccelerationInfoForTxid(txid: string): Promise { + const [rows] = await DB.query(` + SELECT *, UNIX_TIMESTAMP(requested) as requested_timestamp, UNIX_TIMESTAMP(added) as block_timestamp FROM accelerations + JOIN pools on pools.unique_id = accelerations.pool + WHERE txid = ? + `, [txid]) as RowDataPacket[][]; + if (rows?.length) { + const row = rows[0]; + return { + txid: row.txid, + height: row.height, + added: row.requested_timestamp || row.block_timestamp, + pool: { + id: row.id, + slug: row.slug, + name: row.name, + }, + effective_vsize: row.effective_vsize, + effective_fee: row.effective_fee, + boost_rate: row.boost_rate, + boost_cost: row.boost_cost, + }; + } + return null; + } + public async $getAccelerationInfo(poolSlug: string | null = null, height: number | null = null, interval: string | null = null): Promise { if (!interval || !['24h', '3d', '1w', '1m'].includes(interval)) { interval = '1m'; diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 445bb147f8..922f96ada7 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -681,7 +681,16 @@ class BlocksRepository { // iterate back to genesis, resetting canonical status where necessary let hash = tip; const tipHeight = blocksByHash[hash].height || (await bitcoinApi.$getBlock(hash))?.height; - for (let height = tipHeight; height > 0; height--) { + + // stop at the last canonical block we're supposed to have indexed already + let lastIndexedBlockHeight = minHeight; + const indexedBlockAmount = Math.min(config.MEMPOOL.INDEXING_BLOCKS_AMOUNT, tipHeight); + if (indexedBlockAmount > 0) { + lastIndexedBlockHeight = Math.max(0, tipHeight - indexedBlockAmount + 1); + } + + + for (let height = tipHeight; height > lastIndexedBlockHeight; height--) { const block = blocksByHash[hash]; if (!block) { // block hasn't been indexed diff --git a/contributors/AaronDewes.txt b/contributors/AaronDewes.txt new file mode 100644 index 0000000000..5628ffd75e --- /dev/null +++ b/contributors/AaronDewes.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of October 31, 2025. + +Signed: AaronDewes diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2ac14c8d5a..aa90042c07 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -39,7 +39,6 @@ "ngx-infinite-scroll": "^17.0.0", "qrcode": "1.5.1", "rxjs": "~7.8.1", - "start-server-and-test": "~2.1.0", "tinyify": "^4.0.0", "tlite": "^0.1.9", "tslib": "~2.8.0", @@ -780,11 +779,12 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.1.tgz", - "integrity": "sha512-c3tp5zC5zp6XpK9w8wJf3d4Dyw9BNbmg/VEoXtePGivp4hzks6zuMAFknNRwdK7roOlH0HyM5No4WUZHBFpOmw==", + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.17.tgz", + "integrity": "sha512-ZXsIJXZm0I0dNu1BqmjfEtQhnzqoupUHHZb4GHm5NeQHBFZctQlkkNxLUU27GVeBUwFgEmP7kFgSLlMPTGSL5g==", + "license": "MIT", "dependencies": { - "@angular-devkit/core": "17.3.1", + "@angular-devkit/core": "17.3.17", "jsonc-parser": "3.2.1", "magic-string": "0.30.8", "ora": "5.4.1", @@ -796,6 +796,67 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.17.tgz", + "integrity": "sha512-7aNVqS3rOGsSZYAOO44xl2KURwaoOP+EJhJs+LqOGOFpok2kd8YLf4CAMUossMF4H7HsJpgKwYqGrV5eXunrpw==", + "license": "MIT", + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@angular-devkit/schematics/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/@angular-devkit/schematics/node_modules/picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@angular/animations": { "version": "17.3.1", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.3.1.tgz", @@ -811,14 +872,15 @@ } }, "node_modules/@angular/cli": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.3.1.tgz", - "integrity": "sha512-IVnnbRi53BZvZ3LE0PCfFefoB2uHlO1sHtilZf/xCpdV4E1Mkz0/hHln5CRHwAXErdSiY57VoMsF5tffxAfaBQ==", + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.3.17.tgz", + "integrity": "sha512-FgOvf9q5d23Cpa7cjP1FYti/v8S1FTm8DEkW3TY8lkkoxh3isu28GFKcLD1p/XF3yqfPkPVHToOFla5QwsEgBQ==", + "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1703.1", - "@angular-devkit/core": "17.3.1", - "@angular-devkit/schematics": "17.3.1", - "@schematics/angular": "17.3.1", + "@angular-devkit/architect": "0.1703.17", + "@angular-devkit/core": "17.3.17", + "@angular-devkit/schematics": "17.3.17", + "@schematics/angular": "17.3.17", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.3", "ini": "4.1.2", @@ -843,6 +905,64 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { + "version": "0.1703.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1703.17.tgz", + "integrity": "sha512-LD6po8lGP2FI7WbnsSxtvpiIi+FYL0aNfteunkT+7po9jUNflBEYHA64UWNO56u7ryKNdbuiN8/TEh7FEUnmCw==", + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "17.3.17", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli/node_modules/@angular-devkit/core": { + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.17.tgz", + "integrity": "sha512-7aNVqS3rOGsSZYAOO44xl2KURwaoOP+EJhJs+LqOGOFpok2kd8YLf4CAMUossMF4H7HsJpgKwYqGrV5eXunrpw==", + "license": "MIT", + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular/cli/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@angular/cli/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -886,6 +1006,24 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/@angular/cli/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/@angular/cli/node_modules/picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@angular/cli/node_modules/semver": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", @@ -1327,9 +1465,10 @@ } }, "node_modules/@angular/ssr": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-17.3.1.tgz", - "integrity": "sha512-K/2FGTSC3xJOUJEvqRNVhhhoNGMDFMXUKJqnLXe6cNE8xNkOzO52tWTc0ZZr4ZYvFSwtVMuFY4E65HUxbhGTvA==", + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-17.3.17.tgz", + "integrity": "sha512-hRszFhFPh74n17cjhbRuR0igSwHm4ssB0LgkE4A5tBgYTSoWomRM6nmiyrk10OdQHlLOTFwdTIZ3aPDM5174Ow==", + "license": "MIT", "dependencies": { "critters": "0.0.22", "tslib": "^2.3.0" @@ -1340,12 +1479,14 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.2", - "picocolors": "^1.0.0" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -1712,17 +1853,19 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -1749,36 +1892,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz", - "integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "license": "MIT", "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", - "@babel/types": "^7.24.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", - "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/types": "^7.28.5" }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", - "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", "bin": { "parser": "bin/babel-parser.js" }, @@ -2940,13 +3073,14 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2996,13 +3130,13 @@ } }, "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -4223,9 +4357,10 @@ } }, "node_modules/@npmcli/package-json/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -4555,12 +4690,13 @@ ] }, "node_modules/@schematics/angular": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-17.3.1.tgz", - "integrity": "sha512-B3TkpjDjZhxX+tUc2ySEHU33x82Da0sssq/EMqQ1PQBHeRMa0ecyCeExjFEs2y57ZuC+QeVTaUt+TW45lLSjQw==", + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-17.3.17.tgz", + "integrity": "sha512-S5HwYem5Yjeceb5OLvforNcjfTMh2qsHnTP1BAYL81XPpqeg2udjAkJjKBxCwxMZSqdCMw3ne0eKppEYTaEZ+A==", + "license": "MIT", "dependencies": { - "@angular-devkit/core": "17.3.1", - "@angular-devkit/schematics": "17.3.1", + "@angular-devkit/core": "17.3.17", + "@angular-devkit/schematics": "17.3.17", "jsonc-parser": "3.2.1" }, "engines": { @@ -4569,6 +4705,67 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.17.tgz", + "integrity": "sha512-7aNVqS3rOGsSZYAOO44xl2KURwaoOP+EJhJs+LqOGOFpok2kd8YLf4CAMUossMF4H7HsJpgKwYqGrV5eXunrpw==", + "license": "MIT", + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@schematics/angular/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@schematics/angular/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/@schematics/angular/node_modules/picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -4751,9 +4948,10 @@ } }, "node_modules/@tufjs/models/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -5175,10 +5373,11 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -5633,17 +5832,6 @@ "node": ">=8" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -6185,9 +6373,10 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6997,9 +7186,10 @@ } }, "node_modules/cacache/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -7165,19 +7355,6 @@ "node": ">=4" } }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -7403,19 +7580,6 @@ "node": ">=6" } }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -7960,9 +8124,10 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -8296,9 +8461,10 @@ } }, "node_modules/cypress/node_modules/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "license": "MIT", "optional": true, "engines": { "node": ">=14.14" @@ -8721,9 +8887,9 @@ } }, "node_modules/eazy-logger": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/eazy-logger/-/eazy-logger-4.0.1.tgz", - "integrity": "sha512-2GSFtnnC6U4IEKhEI7+PvdxrmjJ04mdsj3wHZTFiw0tUtG4HCWzTr13ZYTk8XOGnA1xQMaDljoBOYlk3D/MMSw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eazy-logger/-/eazy-logger-4.1.0.tgz", + "integrity": "sha512-+mn7lRm+Zf1UT/YaH8WXtpU6PIV2iOjzP6jgKoiaq/VNrjYKp+OHZGe2znaLgDeFkw8cL9ffuaUm+nNnzcYyGw==", "devOptional": true, "dependencies": { "chalk": "4.1.2" @@ -8838,9 +9004,10 @@ "integrity": "sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg==" }, "node_modules/elliptic": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", - "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -9832,9 +9999,10 @@ "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==" }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -9855,7 +10023,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -9870,6 +10038,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/debug": { @@ -10686,14 +10858,6 @@ "node": ">=0.10.0" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -11064,9 +11228,10 @@ } }, "node_modules/ignore-walk/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -11661,7 +11826,8 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" }, "node_modules/js-yaml": { "version": "3.14.0", @@ -11922,16 +12088,14 @@ } }, "node_modules/karma/node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "license": "MIT", "optional": true, "peer": true, - "dependencies": { - "rimraf": "^3.0.0" - }, "engines": { - "node": ">=8.17.0" + "node": ">=14.14" } }, "node_modules/keyv": { @@ -13092,15 +13256,16 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -13311,9 +13476,10 @@ } }, "node_modules/node-gyp/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -14043,9 +14209,10 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" }, "node_modules/path-type": { "version": "4.0.0", @@ -14150,9 +14317,10 @@ "optional": true }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -14801,9 +14969,10 @@ } }, "node_modules/read-package-json/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -15448,9 +15617,10 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } @@ -16330,17 +16500,6 @@ "minimist": "^1.1.0" } }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -16704,14 +16863,6 @@ } ] }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -18839,15 +18990,51 @@ } }, "@angular-devkit/schematics": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.1.tgz", - "integrity": "sha512-c3tp5zC5zp6XpK9w8wJf3d4Dyw9BNbmg/VEoXtePGivp4hzks6zuMAFknNRwdK7roOlH0HyM5No4WUZHBFpOmw==", + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.17.tgz", + "integrity": "sha512-ZXsIJXZm0I0dNu1BqmjfEtQhnzqoupUHHZb4GHm5NeQHBFZctQlkkNxLUU27GVeBUwFgEmP7kFgSLlMPTGSL5g==", "requires": { - "@angular-devkit/core": "17.3.1", + "@angular-devkit/core": "17.3.17", "jsonc-parser": "3.2.1", "magic-string": "0.30.8", "ora": "5.4.1", "rxjs": "7.8.1" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.17.tgz", + "integrity": "sha512-7aNVqS3rOGsSZYAOO44xl2KURwaoOP+EJhJs+LqOGOFpok2kd8YLf4CAMUossMF4H7HsJpgKwYqGrV5eXunrpw==", + "requires": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + } + }, + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==" + } } }, "@angular/animations": { @@ -18859,14 +19046,14 @@ } }, "@angular/cli": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.3.1.tgz", - "integrity": "sha512-IVnnbRi53BZvZ3LE0PCfFefoB2uHlO1sHtilZf/xCpdV4E1Mkz0/hHln5CRHwAXErdSiY57VoMsF5tffxAfaBQ==", - "requires": { - "@angular-devkit/architect": "0.1703.1", - "@angular-devkit/core": "17.3.1", - "@angular-devkit/schematics": "17.3.1", - "@schematics/angular": "17.3.1", + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.3.17.tgz", + "integrity": "sha512-FgOvf9q5d23Cpa7cjP1FYti/v8S1FTm8DEkW3TY8lkkoxh3isu28GFKcLD1p/XF3yqfPkPVHToOFla5QwsEgBQ==", + "requires": { + "@angular-devkit/architect": "0.1703.17", + "@angular-devkit/core": "17.3.17", + "@angular-devkit/schematics": "17.3.17", + "@schematics/angular": "17.3.17", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.3", "ini": "4.1.2", @@ -18883,6 +19070,39 @@ "yargs": "17.7.2" }, "dependencies": { + "@angular-devkit/architect": { + "version": "0.1703.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1703.17.tgz", + "integrity": "sha512-LD6po8lGP2FI7WbnsSxtvpiIi+FYL0aNfteunkT+7po9jUNflBEYHA64UWNO56u7ryKNdbuiN8/TEh7FEUnmCw==", + "requires": { + "@angular-devkit/core": "17.3.17", + "rxjs": "7.8.1" + } + }, + "@angular-devkit/core": { + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.17.tgz", + "integrity": "sha512-7aNVqS3rOGsSZYAOO44xl2KURwaoOP+EJhJs+LqOGOFpok2kd8YLf4CAMUossMF4H7HsJpgKwYqGrV5eXunrpw==", + "requires": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + } + }, + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -18914,6 +19134,16 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==" + }, "semver": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", @@ -19190,21 +19420,22 @@ } }, "@angular/ssr": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-17.3.1.tgz", - "integrity": "sha512-K/2FGTSC3xJOUJEvqRNVhhhoNGMDFMXUKJqnLXe6cNE8xNkOzO52tWTc0ZZr4ZYvFSwtVMuFY4E65HUxbhGTvA==", + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-17.3.17.tgz", + "integrity": "sha512-hRszFhFPh74n17cjhbRuR0igSwHm4ssB0LgkE4A5tBgYTSoWomRM6nmiyrk10OdQHlLOTFwdTIZ3aPDM5174Ow==", "requires": { "critters": "0.0.22", "tslib": "^2.3.0" } }, "@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "requires": { - "@babel/highlight": "^7.24.2", - "picocolors": "^1.0.0" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" } }, "@babel/compat-data": { @@ -19481,14 +19712,14 @@ } }, "@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==" + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" }, "@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==" }, "@babel/helper-validator-option": { "version": "7.23.5", @@ -19506,31 +19737,22 @@ } }, "@babel/helpers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz", - "integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "requires": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", - "@babel/types": "^7.24.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" } }, - "@babel/highlight": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", - "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "requires": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/types": "^7.28.5" } }, - "@babel/parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", - "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==" - }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", @@ -20282,13 +20504,13 @@ } }, "@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "requires": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" } }, "@babel/traverse": { @@ -20331,13 +20553,12 @@ } }, "@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "requires": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" } }, "@browserify/envify": { @@ -21123,9 +21344,9 @@ }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "requires": { "balanced-match": "^1.0.0" } @@ -21316,13 +21537,49 @@ "optional": true }, "@schematics/angular": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-17.3.1.tgz", - "integrity": "sha512-B3TkpjDjZhxX+tUc2ySEHU33x82Da0sssq/EMqQ1PQBHeRMa0ecyCeExjFEs2y57ZuC+QeVTaUt+TW45lLSjQw==", + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-17.3.17.tgz", + "integrity": "sha512-S5HwYem5Yjeceb5OLvforNcjfTMh2qsHnTP1BAYL81XPpqeg2udjAkJjKBxCwxMZSqdCMw3ne0eKppEYTaEZ+A==", "requires": { - "@angular-devkit/core": "17.3.1", - "@angular-devkit/schematics": "17.3.1", + "@angular-devkit/core": "17.3.17", + "@angular-devkit/schematics": "17.3.17", "jsonc-parser": "3.2.1" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "17.3.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.17.tgz", + "integrity": "sha512-7aNVqS3rOGsSZYAOO44xl2KURwaoOP+EJhJs+LqOGOFpok2kd8YLf4CAMUossMF4H7HsJpgKwYqGrV5eXunrpw==", + "requires": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + } + }, + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==" + } } }, "@sideway/address": { @@ -21485,9 +21742,9 @@ }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "requires": { "balanced-match": "^1.0.0" } @@ -21832,9 +22089,9 @@ }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "requires": { "balanced-match": "^1.0.0" @@ -22192,14 +22449,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -22611,9 +22860,9 @@ "requires": {} }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -23253,9 +23502,9 @@ }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "requires": { "balanced-match": "^1.0.0" } @@ -23363,16 +23612,6 @@ "type-detect": "^4.0.8" } }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -23518,19 +23757,6 @@ "shallow-clone": "^3.0.0" } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, "colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -23957,9 +24183,9 @@ } }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -24184,9 +24410,9 @@ } }, "tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "optional": true } } @@ -24530,9 +24756,9 @@ } }, "eazy-logger": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/eazy-logger/-/eazy-logger-4.0.1.tgz", - "integrity": "sha512-2GSFtnnC6U4IEKhEI7+PvdxrmjJ04mdsj3wHZTFiw0tUtG4HCWzTr13ZYTk8XOGnA1xQMaDljoBOYlk3D/MMSw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eazy-logger/-/eazy-logger-4.1.0.tgz", + "integrity": "sha512-+mn7lRm+Zf1UT/YaH8WXtpU6PIV2iOjzP6jgKoiaq/VNrjYKp+OHZGe2znaLgDeFkw8cL9ffuaUm+nNnzcYyGw==", "devOptional": true, "requires": { "chalk": "4.1.2" @@ -24626,9 +24852,9 @@ "integrity": "sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg==" }, "elliptic": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", - "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", "requires": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -25407,9 +25633,9 @@ "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==" }, "express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -25430,7 +25656,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -26042,11 +26268,6 @@ } } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - }, "has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -26298,9 +26519,9 @@ }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "requires": { "balanced-match": "^1.0.0" } @@ -26934,14 +27155,11 @@ "peer": true }, "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "optional": true, - "peer": true, - "requires": { - "rimraf": "^3.0.0" - } + "peer": true } } }, @@ -27831,9 +28049,9 @@ } }, "nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==" }, "natural-compare": { "version": "1.4.0", @@ -28001,9 +28219,9 @@ }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "requires": { "balanced-match": "^1.0.0" } @@ -28535,9 +28753,9 @@ } }, "path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "path-type": { "version": "4.0.0", @@ -28620,9 +28838,9 @@ "optional": true }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "picomatch": { "version": "2.3.1", @@ -29042,9 +29260,9 @@ }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "requires": { "balanced-match": "^1.0.0" } @@ -29549,9 +29767,9 @@ } }, "serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "requires": { "randombytes": "^2.1.0" } @@ -30225,14 +30443,6 @@ "minimist": "^1.1.0" } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -30498,11 +30708,6 @@ } } }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index 5484342d0e..1f4aab850c 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -277,9 +277,9 @@

Community Integrations

RoninDojo - - - Citadel + + + Nirvati diff --git a/frontend/src/app/components/block/block-preview.component.ts b/frontend/src/app/components/block/block-preview.component.ts index f5b31e846e..29a13308c4 100644 --- a/frontend/src/app/components/block/block-preview.component.ts +++ b/frontend/src/app/components/block/block-preview.component.ts @@ -138,7 +138,7 @@ export class BlockPreviewComponent implements OnInit, OnDestroy { return of(transactions); }) ), - this.stateService.env.ACCELERATOR === true && block.height > 819500 + this.stateService.env.ACCELERATOR === true && block.height > 819500 && this.stateService.network === '' ? this.servicesApiService.getAllAccelerationHistory$({ blockHeight: block.height }) .pipe( catchError(() => { diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index fce33b0dd9..c3ad82b8e8 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -384,7 +384,7 @@ export class BlockComponent implements OnInit, OnDestroy { this.accelerationsSubscription = this.block$.pipe( switchMap((block) => { - return this.stateService.env.ACCELERATOR === true && block.height > 819500 + return this.stateService.env.ACCELERATOR === true && block.height > 819500 && this.stateService.network === '' ? this.servicesApiService.getAllAccelerationHistory$({ blockHeight: block.height }) .pipe(catchError(() => { return of([]); diff --git a/frontend/src/app/components/tracker/tracker.component.ts b/frontend/src/app/components/tracker/tracker.component.ts index f78b7a2a94..185fcddc2f 100644 --- a/frontend/src/app/components/tracker/tracker.component.ts +++ b/frontend/src/app/components/tracker/tracker.component.ts @@ -10,10 +10,11 @@ import { mergeMap, tap, map, - startWith + startWith, + retry } from 'rxjs/operators'; import { Transaction } from '@interfaces/electrs.interface'; -import { of, merge, Subscription, Observable, Subject, throwError, combineLatest, BehaviorSubject } from 'rxjs'; +import { of, merge, Subscription, Observable, Subject, throwError, combineLatest, BehaviorSubject, timer } from 'rxjs'; import { StateService } from '@app/services/state.service'; import { CacheService } from '@app/services/cache.service'; import { WebsocketService } from '@app/services/websocket.service'; @@ -101,7 +102,7 @@ export class TrackerComponent implements OnInit, OnDestroy { fetchCpfp$ = new Subject(); fetchRbfHistory$ = new Subject(); fetchCachedTx$ = new Subject(); - fetchAcceleration$ = new Subject(); + fetchAcceleration$ = new Subject(); fetchMiningInfo$ = new Subject<{ hash: string, height: number, txid: string }>(); txChanged$ = new BehaviorSubject(false); // triggered whenever this.tx changes (long term, we should refactor to make this.tx an observable itself) isAccelerated$ = new BehaviorSubject(false); // refactor this to make isAccelerated an observable itself @@ -284,24 +285,48 @@ export class TrackerComponent implements OnInit, OnDestroy { filter(() => this.stateService.env.ACCELERATOR === true), tap(() => { this.accelerationInfo = null; + this.setIsAccelerated(); }), - switchMap((blockHash: string) => { - return this.servicesApiService.getAllAccelerationHistory$({ blockHash }, null, this.txId); - }), - catchError(() => { - return of(null); - }) - ).subscribe((accelerationHistory) => { - for (const acceleration of accelerationHistory) { - if (acceleration.txid === this.txId && (acceleration.status === 'completed' || acceleration.status === 'completed_provisional') && acceleration.pools.includes(acceleration.minedByPoolUniqueId)) { - const boostCost = acceleration.boostCost || acceleration.bidBoost; - acceleration.acceleratedFeeRate = Math.max(acceleration.effectiveFee, acceleration.effectiveFee + boostCost) / acceleration.effectiveVsize; - acceleration.boost = boostCost; - - this.accelerationInfo = acceleration; - this.setIsAccelerated(); + switchMap((blockHeight: number) => { + if (this.stateService.network === '' && this.stateService.env.ACCELERATOR && blockHeight >= 819500 ) { + return this.servicesApiService.getAccelerationDataForTxid$(this.txId).pipe( + switchMap((accelerationData: Acceleration) => { + if (this.tx.acceleration && !accelerationData) { // If the just mined transaction was accelerated, but services backend did not return any acceleration data, retry + return throwError(() => 'retry'); + } + return of(accelerationData); + }), + retry({ + count: 3, + delay: (error) => { + if (error === 'retry') { + return timer(2000); + } + return throwError(() => error); // Don't retry, just rethrow + } + }), + catchError(() => { + return of(null); + }) + ); + } else { + return of(null); } + }), + filter((acceleration: Acceleration) => !!acceleration), + ).subscribe((acceleration: Acceleration) => { + if (acceleration.txid === this.txId && (acceleration.status === 'completed' || acceleration.status === 'completed_provisional') && acceleration.pools.includes(acceleration.minedByPoolUniqueId)) { + const boostCost = acceleration.boostCost || acceleration.bidBoost; + acceleration.acceleratedFeeRate = Math.max(acceleration.effectiveFee, acceleration.effectiveFee + boostCost) / acceleration.effectiveVsize; + acceleration.boost = boostCost; + this.tx.acceleratedAt = acceleration.added; + this.accelerationInfo = acceleration; + } + if (acceleration.txid === this.txId && (acceleration.status === 'failed' || acceleration.status === 'failed_provisional')) { + this.tx.acceleratedAt = acceleration.added; + this.accelerationInfo = acceleration; } + this.setIsAccelerated(); }); this.miningSubscription = this.fetchMiningInfo$.pipe( @@ -476,7 +501,7 @@ export class TrackerComponent implements OnInit, OnDestroy { } else { this.trackerStage = 'confirmed'; this.loadingPosition = false; - this.fetchAcceleration$.next(tx.status.block_hash); + this.fetchAcceleration$.next(tx.status.block_height); this.fetchMiningInfo$.next({ hash: tx.status.block_hash, height: tx.status.block_height, txid: tx.txid }); this.transactionTime = 0; } @@ -540,7 +565,7 @@ export class TrackerComponent implements OnInit, OnDestroy { } else { this.audioService.playSound('magic'); } - this.fetchAcceleration$.next(block.id); + this.fetchAcceleration$.next(block.height); this.fetchMiningInfo$.next({ hash: block.id, height: block.height, txid: this.tx.txid }); } }); diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 12992084b5..fb59e82560 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -16,7 +16,7 @@ import { take } from 'rxjs/operators'; import { Transaction } from '@interfaces/electrs.interface'; -import { of, merge, Subscription, Observable, Subject, from, throwError, combineLatest, BehaviorSubject } from 'rxjs'; +import { of, merge, Subscription, Observable, Subject, from, throwError, combineLatest, BehaviorSubject, timer } from 'rxjs'; import { StateService } from '@app/services/state.service'; import { CacheService } from '@app/services/cache.service'; import { WebsocketService } from '@app/services/websocket.service'; @@ -343,38 +343,47 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { this.setIsAccelerated(); }), switchMap((blockHeight: number) => { - return this.servicesApiService.getAllAccelerationHistory$({ blockHeight }, null, this.txId).pipe( - switchMap((accelerationHistory: Acceleration[]) => { - if (this.tx.acceleration && !accelerationHistory.length) { // If the just mined transaction was accelerated, but services backend did not return any acceleration data, retry - return throwError('retry'); - } - return of(accelerationHistory); - }), - retry({ count: 3, delay: 2000 }), - catchError(() => { - return of([]); - }) - ); - }), - ).subscribe((accelerationHistory) => { - for (const acceleration of accelerationHistory) { - if (acceleration.txid === this.txId) { - if ((acceleration.status === 'completed' || acceleration.status === 'completed_provisional') && acceleration.pools.includes(acceleration.minedByPoolUniqueId)) { - const boostCost = acceleration.boostCost || acceleration.bidBoost; - acceleration.acceleratedFeeRate = Math.max(acceleration.effectiveFee, acceleration.effectiveFee + boostCost) / acceleration.effectiveVsize; - acceleration.boost = boostCost; - this.tx.acceleratedAt = acceleration.added; - this.accelerationInfo = acceleration; - } - if (acceleration.status === 'failed' || acceleration.status === 'failed_provisional') { - this.accelerationCanceled = true; - this.tx.acceleratedAt = acceleration.added; - this.accelerationInfo = acceleration; - } - this.waitingForAccelerationInfo = false; - this.setIsAccelerated(); + if (this.stateService.network === '' && this.stateService.env.ACCELERATOR && blockHeight >= 819500 ) { + return this.servicesApiService.getAccelerationDataForTxid$(this.txId).pipe( + switchMap((accelerationData: Acceleration) => { + if (this.tx.acceleration && !accelerationData) { // If the just mined transaction was accelerated, but services backend did not return any acceleration data, retry + return throwError(() => 'retry'); + } + return of(accelerationData); + }), + retry({ + count: 3, + delay: (error) => { + if (error === 'retry') { + return timer(2000); + } + return throwError(() => error); + } + }), + catchError(() => { + return of(null); + }) + ); + } else { + return of(null); } + }), + filter((acceleration: Acceleration) => !!acceleration), + ).subscribe((acceleration: Acceleration) => { + if (acceleration.txid === this.txId && (acceleration.status === 'completed' || acceleration.status === 'completed_provisional') && acceleration.pools.includes(acceleration.minedByPoolUniqueId)) { + const boostCost = acceleration.boostCost || acceleration.bidBoost; + acceleration.acceleratedFeeRate = Math.max(acceleration.effectiveFee, acceleration.effectiveFee + boostCost) / acceleration.effectiveVsize; + acceleration.boost = boostCost; + this.tx.acceleratedAt = acceleration.added; + this.accelerationInfo = acceleration; } + if (acceleration.txid === this.txId && (acceleration.status === 'failed' || acceleration.status === 'failed_provisional')) { + this.accelerationCanceled = true; + this.tx.acceleratedAt = acceleration.added; + this.accelerationInfo = acceleration; + } + this.waitingForAccelerationInfo = false; + this.setIsAccelerated(); }); this.miningSubscription = this.fetchMiningInfo$.pipe( diff --git a/frontend/src/app/services/services-api.service.ts b/frontend/src/app/services/services-api.service.ts index d0b04cf794..d85698e421 100644 --- a/frontend/src/app/services/services-api.service.ts +++ b/frontend/src/app/services/services-api.service.ts @@ -163,6 +163,10 @@ export class ServicesApiServices { return this.httpClient.get(`${this.stateService.env.SERVICES_API}/accelerator/accelerations/history`, { params: { ...params } }); } + getAccelerationDataForTxid$(txid: string) { + return this.httpClient.get(`${this.stateService.env.SERVICES_API}/accelerator/accelerations/${txid}`); + } + getAllAccelerationHistory$(params: AccelerationHistoryParams, limit?: number, findTxid?: string): Observable { const getPage$ = (page: number, accelerations: Acceleration[] = []): Observable<{ page: number, total: number, accelerations: Acceleration[] }> => { return this.getAccelerationHistoryObserveResponse$({...params, page}).pipe( diff --git a/frontend/src/resources/profile/nirvati.svg b/frontend/src/resources/profile/nirvati.svg new file mode 100644 index 0000000000..3089d1d4c9 --- /dev/null +++ b/frontend/src/resources/profile/nirvati.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/resources/profile/runcitadel.svg b/frontend/src/resources/profile/runcitadel.svg deleted file mode 100644 index 2425753438..0000000000 --- a/frontend/src/resources/profile/runcitadel.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/frontend/sync-assets.js b/frontend/sync-assets.js index 9614dd7f9e..e988bcb77b 100644 --- a/frontend/sync-assets.js +++ b/frontend/sync-assets.js @@ -1,425 +1,378 @@ -var https = require('https'); -var fs = require('fs'); -var crypto = require('crypto'); -var path = require('node:path'); +const https = require('https'); +const fs = require('fs').promises; +const fsSync = require('fs'); +const crypto = require('crypto'); +const path = require('node:path'); + +// Configuration const LOG_TAG = '[sync-assets]'; -let verbose = false; -let MEMPOOL_CDN = false; -let DRY_RUN = false; +const CONFIG_FILE_NAME = 'mempool-frontend-config.json'; + +const config = { + verbose: parseInt(process.env.VERBOSE) === 1, + mempoolCDN: parseInt(process.env.MEMPOOL_CDN) === 1, + dryRun: parseInt(process.env.DRY_RUN) === 1, + githubToken: process.env.GITHUB_TOKEN, +}; +// Early exit if SKIP_SYNC is set if (parseInt(process.env.SKIP_SYNC) === 1) { console.log(`${LOG_TAG} SKIP_SYNC is set, not checking any assets`); process.exit(0); } -if (parseInt(process.env.VERBOSE) === 1) { - console.log(`${LOG_TAG} VERBOSE is set, logs will be more verbose`); - verbose = true; -} - -if (parseInt(process.env.MEMPOOL_CDN) === 1) { - console.log(`${LOG_TAG} MEMPOOL_CDN is set, assets will be downloaded from mempool.space`); - MEMPOOL_CDN = true; -} +// Log configuration +if (config.verbose) console.log(`${LOG_TAG} VERBOSE is set, logs will be more verbose`); +if (config.mempoolCDN) console.log(`${LOG_TAG} MEMPOOL_CDN is set, assets will be downloaded from mempool.space`); +if (config.dryRun) console.log(`${LOG_TAG} DRY_RUN is set, not downloading any assets`); -if (parseInt(process.env.DRY_RUN) === 1) { - console.log(`${LOG_TAG} DRY_RUN is set, not downloading any assets`); - DRY_RUN = true; -} - -const githubSecret = process.env.GITHUB_TOKEN; - -const CONFIG_FILE_NAME = 'mempool-frontend-config.json'; -let configContent = {}; - -var ASSETS_PATH; -if (process.argv[2]) { - ASSETS_PATH = process.argv[2]; - ASSETS_PATH += ASSETS_PATH.endsWith("/") ? "" : "/" - ASSETS_PATH = path.resolve(path.normalize(ASSETS_PATH)); - console.log(`[sync-assets] using ASSETS_PATH ${ASSETS_PATH}`); - if (!fs.existsSync(ASSETS_PATH)){ - console.log(`${LOG_TAG} ${ASSETS_PATH} does not exist, creating`); - fs.mkdirSync(ASSETS_PATH, { recursive: true }); +// Setup assets path +const ASSETS_PATH = (() => { + if (!process.argv[2]) { + throw new Error('Resource path argument is not set'); } -} + const rawPath = process.argv[2].endsWith("/") ? process.argv[2] : `${process.argv[2]}/`; + const normalizedPath = path.resolve(path.normalize(rawPath)); + console.log(`${LOG_TAG} using ASSETS_PATH ${normalizedPath}`); -if (!ASSETS_PATH) { - throw new Error('Resource path argument is not set'); -} + if (!fsSync.existsSync(normalizedPath)) { + console.log(`${LOG_TAG} ${normalizedPath} does not exist, creating`); + fsSync.mkdirSync(normalizedPath, { recursive: true }); + } -try { - const rawConfig = fs.readFileSync(CONFIG_FILE_NAME); - configContent = JSON.parse(rawConfig); - console.log(`${LOG_TAG} ${CONFIG_FILE_NAME} file found, using provided config`); -} catch (e) { - if (e.code !== 'ENOENT') { - throw new Error(e); - } else { + return normalizedPath; +})(); + +// Load frontend config +const loadConfig = () => { + try { + const rawConfig = fsSync.readFileSync(CONFIG_FILE_NAME, 'utf8'); + console.log(`${LOG_TAG} ${CONFIG_FILE_NAME} file found, using provided config`); + return JSON.parse(rawConfig); + } catch (e) { + if (e.code !== 'ENOENT') throw e; console.log(`${LOG_TAG} ${CONFIG_FILE_NAME} file not found, using default config`); + return {}; } -} +}; + +const configContent = loadConfig(); -function download(filename, url) { - if (!filename || !url) { - if (verbose) { - console.log('skipping malformed download request: ', filename, url); +// Utility: Make HTTPS request +const httpsRequest = (options) => { + return new Promise((resolve, reject) => { + https.get(options, (response) => { + const chunks = []; + + response.on('data', (chunk) => chunks.push(chunk)); + response.on('end', () => resolve(Buffer.concat(chunks))); + response.on('error', reject); + }).on('error', reject); + }); +}; + +// Utility: Download file +const downloadFile = (filePath, url) => { + if (!filePath || !url) { + if (config.verbose) { + console.log('skipping malformed download request: ', filePath, url); } - return; + return Promise.resolve(); } - https.get(url, (response) => { - if (response.statusCode < 200 || response.statusCode > 299) { - throw new Error('HTTP Error ' + response.statusCode + ' while fetching \'' + filename + '\''); - } - response.pipe(fs.createWriteStream(filename)); - }) - .on('error', function(e) { - throw new Error(e); - }) - .on('finish', () => { - if (verbose) { - console.log(`${LOG_TAG} \tFinished downloading ${url} to ${filename}`); - } - }); -} -function getLocalHash(filePath) { - const size = fs.statSync(filePath); - const buffer = fs.readFileSync(filePath); - const bufferWithHeader = Buffer.concat([Buffer.from('blob '), Buffer.from(`${size.size}`), Buffer.from('\0'), buffer]); + return new Promise((resolve, reject) => { + https.get(url, (response) => { + if (response.statusCode < 200 || response.statusCode > 299) { + reject(new Error(`HTTP Error ${response.statusCode} while fetching '${filePath}'`)); + return; + } + + const writeStream = fsSync.createWriteStream(filePath); + response.pipe(writeStream); + + writeStream.on('finish', () => { + if (config.verbose) { + console.log(`${LOG_TAG} \tFinished downloading ${url} to ${filePath}`); + } + resolve(); + }); + + writeStream.on('error', reject); + }).on('error', reject); + }); +}; + +// Utility: Get local file hash (Git blob format) +const getLocalHash = (filePath) => { + const stats = fsSync.statSync(filePath); + const buffer = fsSync.readFileSync(filePath); + const bufferWithHeader = Buffer.concat([ + Buffer.from('blob '), + Buffer.from(`${stats.size}`), + Buffer.from('\0'), + buffer + ]); const hash = crypto.createHash('sha1').update(bufferWithHeader).digest('hex'); - if (verbose) { + if (config.verbose) { console.log(`${LOG_TAG} \t\tgetLocalHash ${filePath} ${hash}`); } return hash; -} +}; + +// Utility: Create GitHub API options +const createGitHubOptions = (repoPath) => { + const options = { + host: 'api.github.com', + path: repoPath, + method: 'GET', + headers: { 'user-agent': 'node.js' } + }; + + if (config.githubToken) { + options.headers['authorization'] = `Bearer ${config.githubToken}`; + options.headers['X-GitHub-Api-Version'] = '2022-11-28'; + } -function downloadMiningPoolLogos$() { - return new Promise((resolve, reject) => { - console.log(`${LOG_TAG} \tChecking if mining pool logos needs downloading or updating...`); - const options = { - host: 'api.github.com', - path: '/repos/mempool/mining-pool-logos/contents/', - method: 'GET', - headers: {'user-agent': 'node.js'} - }; - - if (githubSecret) { - console.log(`${LOG_TAG} Downloading the mining pool logos with authentication`); - options.headers['authorization'] = `Bearer ${githubSecret}`; - options.headers['X-GitHub-Api-Version'] = '2022-11-28'; - } + return options; +}; - https.get(options, (response) => { - const chunks_of_data = []; +// Utility: Replace URL for CDN +const getCDNUrl = (url, replacePattern) => { + return config.mempoolCDN ? url.replace(replacePattern.from, replacePattern.to) : url; +}; - response.on('data', (fragments) => { - chunks_of_data.push(fragments); - }); +// Utility: Ensure directory exists +const ensureDirectory = (dirPath) => { + if (!fsSync.existsSync(dirPath)) { + fsSync.mkdirSync(dirPath, { recursive: true }); + } +}; + +// Core: Process file item (handles checking and downloading) +const processFileItem = async (item, options) => { + const { + filePath, + remoteHash, + downloadUrl, + cdnPattern, + itemName, + downloadDir + } = options; + + const fileExists = fsSync.existsSync(filePath); + + if (fileExists) { + const localHash = getLocalHash(filePath); + + if (config.verbose) { + console.log(`${LOG_TAG} \t\tremote ${itemName} hash ${remoteHash}`); + } - response.on('end', () => { - const response_body = Buffer.concat(chunks_of_data); - try { - const poolLogos = JSON.parse(response_body.toString()); - if (poolLogos.message) { - reject(poolLogos.message); - } - let downloadedCount = 0; - for (const poolLogo of poolLogos) { - if (poolLogo.type !== 'file' || poolLogo.download_url == null) { - continue; - } - if (verbose) { - console.log(`${LOG_TAG} Processing ${poolLogo.name}`); - } - console.log(`${ASSETS_PATH}/mining-pools/${poolLogo.name}`); - const filePath = `${ASSETS_PATH}/mining-pools/${poolLogo.name}`; - if (fs.existsSync(filePath)) { - const localHash = getLocalHash(filePath); - if (verbose) { - console.log(`${LOG_TAG} \t\tremote ${poolLogo.name} logo hash ${poolLogo.sha}`); - console.log(`${LOG_TAG} \t\t\tchecking if ${filePath} exists: ${fs.existsSync(filePath)}`); - } - if (localHash !== poolLogo.sha) { - console.log(`${LOG_TAG} \t\t\t\t${poolLogo.name} is different on the remote, downloading...`); - let download_url = poolLogo.download_url; - if (MEMPOOL_CDN) { - download_url = download_url.replace("raw.githubusercontent.com/mempool/mining-pool-logos/master", "mempool.space/resources/mining-pools"); - } - if (DRY_RUN) { - console.log(`${LOG_TAG} \t\tDRY_RUN is set, not downloading ${poolLogo.name} but we should`); - } else { - if (verbose) { - console.log(`${LOG_TAG} \t\tDownloading ${download_url} to ${filePath}`); - } - download(filePath, download_url); - downloadedCount++; - } - } else { - console.log(`${LOG_TAG} \t\t${poolLogo.name} is already up to date. Skipping.`); - } - } else { - console.log(`${LOG_TAG} \t\t${poolLogo.name} is missing, downloading...`); - const miningPoolsDir = `${ASSETS_PATH}/mining-pools/`; - if (!fs.existsSync(miningPoolsDir)){ - fs.mkdirSync(miningPoolsDir, { recursive: true }); - } - let download_url = poolLogo.download_url; - if (MEMPOOL_CDN) { - download_url = download_url.replace("raw.githubusercontent.com/mempool/mining-pool-logos/master", "mempool.space/resources/mining-pools"); - } - if (DRY_RUN) { - console.log(`${LOG_TAG} DRY_RUN is set, not downloading ${poolLogo.name} but it should`); - } else { - console.log(`${LOG_TAG} \tDownloading ${download_url} to ${filePath}`); - download(filePath, download_url); - downloadedCount++; - } - } - } - console.log(`${LOG_TAG} \t\tDownloaded ${downloadedCount} and skipped ${poolLogos.length - downloadedCount} existing mining pool logos`); - resolve(); - } catch (e) { - reject(`Unable to download mining pool logos. Trying again at next restart. Reason: ${e instanceof Error ? e.message : e}`); - } - }); + if (localHash !== remoteHash) { + console.log(`${LOG_TAG} \t\t${itemName} is different on the remote, downloading...`); + + if (config.dryRun) { + console.log(`${LOG_TAG} \t\tDRY_RUN is set, not downloading ${itemName} but we should`); + return false; + } + + const url = getCDNUrl(downloadUrl, cdnPattern); + if (config.verbose) { + console.log(`${LOG_TAG} \t\tDownloading ${url} to ${filePath}`); + } + await downloadFile(filePath, url); + return true; + } else { + console.log(`${LOG_TAG} \t\t${itemName} is already up to date. Skipping.`); + return false; + } + } else { + console.log(`${LOG_TAG} \t\t${itemName} is missing, downloading...`); + ensureDirectory(downloadDir); - response.on('error', (error) => { - reject(error); - }); - }); - }); -} + if (config.dryRun) { + console.log(`${LOG_TAG} \t\tDRY_RUN is set, not downloading ${itemName} but we should`); + return false; + } -function downloadPromoVideoSubtiles$() { - return new Promise((resolve, reject) => { - console.log(`${LOG_TAG} \tChecking if promo video subtitles needs downloading or updating...`); - const options = { - host: 'api.github.com', - path: '/repos/mempool/mempool-promo/contents/subtitles', - method: 'GET', - headers: {'user-agent': 'node.js'} - }; - - if (githubSecret) { - console.log(`${LOG_TAG} \tDownloading the promo video subtitles with authentication`); - options.headers['authorization'] = `Bearer ${githubSecret}`; - options.headers['X-GitHub-Api-Version'] = '2022-11-28'; + const url = getCDNUrl(downloadUrl, cdnPattern); + if (config.verbose) { + console.log(`${LOG_TAG} \t\tDownloading ${url} to ${filePath}`); } + await downloadFile(filePath, url); + return true; + } +}; +// Core: Fetch GitHub directory contents +const fetchGitHubContents = async (repoPath, useAuth = false) => { + if (useAuth && config.githubToken) { + console.log(`${LOG_TAG} \tDownloading with authentication`); + } - https.get(options, (response) => { - const chunks_of_data = []; + const options = createGitHubOptions(repoPath); + const responseBody = await httpsRequest(options); + const contents = JSON.parse(responseBody.toString()); - response.on('data', (fragments) => { - chunks_of_data.push(fragments); - }); + if (contents.message) { + throw new Error(contents.message); + } - response.on('end', () => { - const response_body = Buffer.concat(chunks_of_data); - try { - const videoLanguages = JSON.parse(response_body.toString()); - if (videoLanguages.message) { - reject(videoLanguages.message); - } - let downloadedCount = 0; - for (const language of videoLanguages) { - if (language.type !== 'file' || language.download_url == null) { - continue; - } - if (verbose) { - console.log(`${LOG_TAG} Processing ${language.name}`); - } - const filePath = `${ASSETS_PATH}/promo-video/${language.name}`; - if (fs.existsSync(filePath)) { - if (verbose) { - console.log(`${LOG_TAG} \t${language.name} remote promo video hash ${language.sha}`); - } - const localHash = getLocalHash(filePath); - if (localHash !== language.sha) { - console.log(`${LOG_TAG} \t\t${language.name} is different on the remote, updating`); - let download_url = language.download_url; - if (MEMPOOL_CDN) { - download_url = download_url.replace("raw.githubusercontent.com/mempool/mempool-promo/master/subtitles", "mempool.space/resources/promo-video"); - } - if (DRY_RUN) { - console.log(`${LOG_TAG} \t\tDRY_RUN is set, not downloading ${language.name} but we should`); - } else { - if (verbose) { - console.log(`${LOG_TAG} \t\tdownloading ${download_url} to ${filePath}`); - } - download(filePath, download_url); - downloadedCount++; - } - } else { - console.log(`${LOG_TAG} \t\t${language.name} is already up to date. Skipping.`); - } - } else { - console.log(`${LOG_TAG} \t\t${language.name} is missing, downloading`); - const promoVideosDir = `${ASSETS_PATH}/promo-video/`; - if (!fs.existsSync(promoVideosDir)){ - fs.mkdirSync(promoVideosDir, { recursive: true }); - } - - let download_url = language.download_url; - if (MEMPOOL_CDN) { - download_url = download_url.replace("raw.githubusercontent.com/mempool/mempool-promo/master/subtitles", "mempool.space/resources/promo-video"); - } - if (DRY_RUN) { - console.log(`${LOG_TAG} \tDRY_RUN is set, not downloading ${language.name} but we should`); - } else { - if (verbose) { - console.log(`${LOG_TAG} downloading ${download_url} to ${filePath}`); - } - download(filePath, download_url); - downloadedCount++; - } - } - } - console.log(`${LOG_TAG} Downloaded ${downloadedCount} and skipped ${videoLanguages.length - downloadedCount} existing video subtitles`); - resolve(); - } catch (e) { - reject(`Unable to download video subtitles. Trying again at next restart. Reason: ${e instanceof Error ? e.message : e}`); - } + return contents; +}; + +// Main: Download mining pool logos +const downloadMiningPoolLogos = async () => { + console.log(`${LOG_TAG} \tChecking if mining pool logos needs downloading or updating...`); + + try { + const poolLogos = await fetchGitHubContents('/repos/mempool/mining-pool-logos/contents/', !!config.githubToken); + + let downloadedCount = 0; + const validFiles = poolLogos.filter(item => item.type === 'file' && item.download_url); + + for (const poolLogo of validFiles) { + if (config.verbose) { + console.log(`${LOG_TAG} Processing ${poolLogo.name}`); + } + + const downloaded = await processFileItem(poolLogo, { + filePath: `${ASSETS_PATH}/mining-pools/${poolLogo.name}`, + remoteHash: poolLogo.sha, + downloadUrl: poolLogo.download_url, + cdnPattern: { + from: "raw.githubusercontent.com/mempool/mining-pool-logos/master", + to: "mempool.space/resources/mining-pools" + }, + itemName: poolLogo.name, + downloadDir: `${ASSETS_PATH}/mining-pools/` }); - response.on('error', (error) => { - reject(error); + if (downloaded) downloadedCount++; + } + + console.log(`${LOG_TAG} \t\tDownloaded ${downloadedCount} and skipped ${validFiles.length - downloadedCount} existing mining pool logos`); + } catch (e) { + throw new Error(`Unable to download mining pool logos. Trying again at next restart. Reason: ${e instanceof Error ? e.message : e}`); + } +}; + +// Main: Download promo video subtitles +const downloadPromoVideoSubtitles = async () => { + console.log(`${LOG_TAG} \tChecking if promo video subtitles needs downloading or updating...`); + + try { + const subtitles = await fetchGitHubContents('/repos/mempool/mempool-promo/contents/subtitles', !!config.githubToken); + + let downloadedCount = 0; + const validFiles = subtitles.filter(item => item.type === 'file' && item.download_url); + + for (const subtitle of validFiles) { + if (config.verbose) { + console.log(`${LOG_TAG} Processing ${subtitle.name}`); + } + + const downloaded = await processFileItem(subtitle, { + filePath: `${ASSETS_PATH}/promo-video/${subtitle.name}`, + remoteHash: subtitle.sha, + downloadUrl: subtitle.download_url, + cdnPattern: { + from: "raw.githubusercontent.com/mempool/mempool-promo/master/subtitles", + to: "mempool.space/resources/promo-video" + }, + itemName: subtitle.name, + downloadDir: `${ASSETS_PATH}/promo-video/` }); - }); - }); -} -function downloadPromoVideo$() { - return new Promise((resolve, reject) => { - console.log(`${LOG_TAG} \tChecking if promo video needs downloading or updating...`); - const options = { - host: 'api.github.com', - path: '/repos/mempool/mempool-promo/contents', - method: 'GET', - headers: {'user-agent': 'node.js'} - }; - - if (githubSecret) { - console.log(`${LOG_TAG} \tDownloading the promo video with authentication`); - options.headers['authorization'] = `Bearer ${githubSecret}`; - options.headers['X-GitHub-Api-Version'] = '2022-11-28'; + if (downloaded) downloadedCount++; } - https.get(options, (response) => { - const chunks_of_data = []; + console.log(`${LOG_TAG} Downloaded ${downloadedCount} and skipped ${validFiles.length - downloadedCount} existing video subtitles`); + } catch (e) { + throw new Error(`Unable to download video subtitles. Trying again at next restart. Reason: ${e instanceof Error ? e.message : e}`); + } +}; - response.on('data', (fragments) => { - chunks_of_data.push(fragments); - }); +// Main: Download promo video +const downloadPromoVideo = async () => { + console.log(`${LOG_TAG} \tChecking if promo video needs downloading or updating...`); - response.on('end', () => { - const response_body = Buffer.concat(chunks_of_data); - try { - const contents = JSON.parse(response_body.toString()); - if (contents.message) { - reject(contents.message); - } - for (const item of contents) { - if (item.name !== 'promo.mp4') { - continue; - } - const filePath = `${ASSETS_PATH}/promo-video/mempool-promo.mp4`; - if (fs.existsSync(filePath)) { - const localHash = getLocalHash(filePath); - - if (localHash !== item.sha) { - console.log(`${LOG_TAG} \tmempool-promo.mp4 is different on the remote, updating`); - let download_url = item.download_url; - if (MEMPOOL_CDN) { - download_url = download_url.replace("raw.githubusercontent.com/mempool/mempool-promo/master/promo.mp4", "mempool.space/resources/promo-video/mempool-promo.mp4"); - } - if (DRY_RUN) { - console.log(`${LOG_TAG} DRY_RUN is set, not downloading mempool-promo.mp4 but we should`); - } else { - if (verbose) { - console.log(`${LOG_TAG} downloading ${download_url} to ${filePath}`); - } - download(filePath, download_url); - console.log(`${LOG_TAG} \tmempool-promo.mp4 downloaded.`); - } - } else { - console.log(`${LOG_TAG} \t\tmempool-promo.mp4 is already up to date. Skipping.`); - } - } else { - console.log(`${LOG_TAG} \tmempool-promo.mp4 is missing, downloading`); - let download_url = item.download_url; - if (MEMPOOL_CDN) { - download_url = download_url.replace("raw.githubusercontent.com/mempool/mempool-promo/master/promo.mp4", "mempool.space/resources/promo-video/mempool-promo.mp4"); - } - if (DRY_RUN) { - console.log(`${LOG_TAG} DRY_RUN is set, not downloading mempool-promo.mp4 but we should`); - } else { - if (verbose) { - console.log(`${LOG_TAG} downloading ${download_url} to ${filePath}`); - } - download(filePath, download_url); - } - } - } - resolve(); - } catch (e) { - reject(`Unable to download video. Trying again at next restart. Reason: ${e instanceof Error ? e.message : e}`); - } - }); + try { + const contents = await fetchGitHubContents('/repos/mempool/mempool-promo/contents', !!config.githubToken); - response.on('error', (error) => { - reject(error); - }); - }); - }); + const videoItem = contents.find(item => item.name === 'promo.mp4'); + if (!videoItem) { + console.log(`${LOG_TAG} \tpromo.mp4 not found in repository`); + return; + } -} + await processFileItem(videoItem, { + filePath: `${ASSETS_PATH}/promo-video/mempool-promo.mp4`, + remoteHash: videoItem.sha, + downloadUrl: videoItem.download_url, + cdnPattern: { + from: "raw.githubusercontent.com/mempool/mempool-promo/master/promo.mp4", + to: "mempool.space/resources/promo-video/mempool-promo.mp4" + }, + itemName: 'mempool-promo.mp4', + downloadDir: `${ASSETS_PATH}/promo-video/` + }); + } catch (e) { + throw new Error(`Unable to download video. Trying again at next restart. Reason: ${e instanceof Error ? e.message : e}`); + } +}; +// Download Liquid assets if configured +const downloadLiquidAssets = () => { + if (configContent.BASE_MODULE !== 'liquid') { + if (config.verbose) { + console.log(`${LOG_TAG} BASE_MODULE is not set to Liquid (currently ${configContent.BASE_MODULE}), skipping downloading assets`); + } + return; + } -if (configContent.BASE_MODULE && configContent.BASE_MODULE === 'liquid') { - const assetsJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_db/master/index.json'; - const assetsMinimalJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_db/master/index.minimal.json'; - const testnetAssetsJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.json'; - const testnetAssetsMinimalJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.minimal.json'; + const liquidAssets = [ + { file: 'assets.json', url: 'https://raw.githubusercontent.com/Blockstream/asset_registry_db/master/index.json' }, + { file: 'assets.minimal.json', url: 'https://raw.githubusercontent.com/Blockstream/asset_registry_db/master/index.minimal.json' }, + { file: 'assets-testnet.json', url: 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.json' }, + { file: 'assets-testnet.minimal.json', url: 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.minimal.json' } + ]; console.log(`${LOG_TAG} Downloading assets`); - download(`${ASSETS_PATH}/assets.json`, assetsJsonUrl); - - console.log(`${LOG_TAG} Downloading assets minimal`); - download(`${ASSETS_PATH}/assets.minimal.json`, assetsMinimalJsonUrl); + liquidAssets.forEach(({ file, url }) => { + const fileName = file.replace(/^assets/, 'assets'); + console.log(`${LOG_TAG} Downloading ${fileName}`); + downloadFile(`${ASSETS_PATH}/${fileName}`, url); + }); +}; - console.log(`${LOG_TAG} Downloading testnet assets`); - download(`${ASSETS_PATH}/assets-testnet.json`, testnetAssetsJsonUrl); +// Main execution +(async () => { + try { + // Download Liquid assets (non-blocking) + downloadLiquidAssets(); - console.log(`${LOG_TAG} Downloading testnet assets minimal`); - download(`${ASSETS_PATH}/assets-testnet.minimal.json`, testnetAssetsMinimalJsonUrl); -} else { - if (verbose) { - console.log(`${LOG_TAG} BASE_MODULE is not set to Liquid (currently ${configContent.BASE_MODULE}), skipping downloading assets`); - } -} + // Download GitHub assets sequentially + if (config.verbose) { + console.log(`${LOG_TAG} Downloading mining pool logos`); + } + await downloadMiningPoolLogos(); -(() => { - if (verbose) { - console.log(`${LOG_TAG} Downloading mining pool logos`); - } - downloadMiningPoolLogos$() - .then(() => { - if (verbose) { + if (config.verbose) { console.log(`${LOG_TAG} Downloading promo video subtitles`); } - downloadPromoVideoSubtiles$(); - }) - .then(() => { - if (verbose) { + await downloadPromoVideoSubtitles(); + + if (config.verbose) { console.log(`${LOG_TAG} Downloading promo video`); } - downloadPromoVideo$(); - }) - .catch((error) => { - throw new Error(error); - }); -})(); \ No newline at end of file + await downloadPromoVideo(); + + console.log(`${LOG_TAG} Asset synchronization complete`); + } catch (error) { + console.error(`${LOG_TAG} Error:`, error.message); + process.exit(1); + } +})(); From 1feaa53b4927ad83147a51ccad020fdfadc4d02d Mon Sep 17 00:00:00 2001 From: Dargon789 <64915515+Dargon789@users.noreply.github.com> Date: Mon, 10 Nov 2025 06:25:05 +0700 Subject: [PATCH 3/3] Potential fix for code scanning alert no. 56: Replacement of a substring with itself Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Dargon789 <64915515+Dargon789@users.noreply.github.com> --- frontend/sync-assets.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/sync-assets.js b/frontend/sync-assets.js index e988bcb77b..4d5e30abdd 100644 --- a/frontend/sync-assets.js +++ b/frontend/sync-assets.js @@ -342,7 +342,7 @@ const downloadLiquidAssets = () => { console.log(`${LOG_TAG} Downloading assets`); liquidAssets.forEach(({ file, url }) => { - const fileName = file.replace(/^assets/, 'assets'); + const fileName = file; console.log(`${LOG_TAG} Downloading ${fileName}`); downloadFile(`${ASSETS_PATH}/${fileName}`, url); });