diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2303ea..c10d3fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [ master, dev ] + branches: [ main, dev ] pull_request: - branches: [ master, dev ] + branches: [ main, dev ] jobs: test: @@ -17,7 +17,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: 1.3.10 - name: Install dependencies run: bun install --frozen-lockfile @@ -25,6 +25,9 @@ jobs: - name: Type check run: bun run tsc --noEmit + - name: Run unit tests + run: bun run test:unit + - name: Build frontend run: bun run build @@ -54,7 +57,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: 1.3.10 - name: Install dependencies run: bun install --frozen-lockfile @@ -71,16 +74,51 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.10 + + - name: Install dependencies + run: bun install --frozen-lockfile + - name: Run security audit run: | - bun audit || true # Don't fail on audit issues for now + audit_log="$(mktemp)" + last_exit=0 + for attempt in 1 2 3; do + if bun audit >"$audit_log" 2>&1; then + cat "$audit_log" + rm -f "$audit_log" + exit 0 + else + audit_exit=$? + last_exit=$audit_exit + last_attempt=$attempt + if [ "$attempt" -lt 3 ]; then + echo "bun audit failed (attempt $attempt), retrying..." + sleep 5 + fi + fi + done + + audit_output="$(cat "$audit_log")" + echo "bun audit failed after ${last_attempt:-3} attempts with exit code ${last_exit}" + if grep -Eiq 'network|registry|ENOTFOUND|ECONNREFUSED|EAI_AGAIN|ETIMEDOUT' <<< "$audit_output"; then + echo "bun audit failed after retries due to network/registry error: $audit_output" + else + echo "bun audit failed after retries - vulnerabilities detected: $audit_output" + fi + rm -f "$audit_log" + exit 1 - name: Check for secrets - uses: trufflesecurity/trufflehog@main + # Pinned to immutable commit (v3.93.4) for supply-chain safety. + # Maintenance: periodically verify this SHA still corresponds to the intended upstream release. + uses: trufflesecurity/trufflehog@7c0734f987ad0bb30ee8da210773b800ee2016d3 with: path: ./ extra_args: --debug --only-verified - continue-on-error: true docker: runs-on: ubuntu-latest @@ -104,13 +142,13 @@ jobs: - name: Test Docker image run: | + set -euo pipefail + trap 'docker stop test-container >/dev/null 2>&1 || true; docker rm test-container >/dev/null 2>&1 || true' EXIT docker run -d --name test-container -p 8002:8002 \ -e AUTO_ADMIN_SECRET=true \ igloo-server:test sleep 10 - curl -f http://localhost:8002/api/status || exit 1 - docker stop test-container - docker rm test-container + curl -f http://localhost:8002/api/status - name: Build Umbrel Docker image uses: docker/build-push-action@v5 @@ -124,12 +162,12 @@ jobs: - name: Test Umbrel Docker image run: | + set -euo pipefail + trap 'docker stop test-umbrel >/dev/null 2>&1 || true; docker rm test-umbrel >/dev/null 2>&1 || true' EXIT docker run -d --name test-umbrel -p 8003:8002 \ -e ADMIN_SECRET=ci-admin-secret \ -e ALLOWED_ORIGINS=http://localhost:8003 \ -e TRUST_PROXY=true \ igloo-server-umbrel:test sleep 10 - curl -f http://localhost:8003/api/status || exit 1 - docker stop test-umbrel - docker rm test-umbrel + curl -f http://localhost:8003/api/status diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc2ddea..9096905 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,6 @@ name: Release on: - push: - branches: [ master ] workflow_dispatch: inputs: version: @@ -13,7 +11,7 @@ on: jobs: release: runs-on: ubuntu-latest - if: github.ref == 'refs/heads/master' && github.event_name == 'workflow_dispatch' + if: github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' permissions: contents: write pull-requests: write @@ -31,7 +29,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: 1.3.10 - name: Install dependencies run: bun install --frozen-lockfile @@ -73,6 +71,12 @@ jobs: echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT echo "version_number=${NEW_VERSION#v}" >> $GITHUB_OUTPUT + - name: Type check + run: bun run tsc --noEmit + + - name: Run backend tests + run: bun run test:unit + - name: Build application run: bun run build @@ -83,25 +87,45 @@ jobs: echo "# CHANGELOG" > CHANGELOG.md echo "" >> CHANGELOG.md fi - - # Add new version entry + + VERSION="${{ steps.new_version.outputs.version_number }}" DATE=$(date +%Y-%m-%d) - sed -i "3i\\## [${{ steps.new_version.outputs.version_number }}] - $DATE\\n" CHANGELOG.md - - # Add commit messages since last tag + if ! grep -Fq "## [${VERSION}]" CHANGELOG.md; then + awk -v version="$VERSION" -v date="$DATE" ' + NR == 1 { print; print ""; print "## [" version "] - " date; print ""; next } + { print } + ' CHANGELOG.md > CHANGELOG.md.tmp + mv CHANGELOG.md.tmp CHANGELOG.md + fi + + temp_changelog="$(mktemp)" + + # Add commit messages since last tag (safe for special chars in subjects) LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") if [ -n "$LAST_TAG" ]; then - echo "### Changes since $LAST_TAG:" >> temp_changelog.md - git log --pretty=format:"- %s" $LAST_TAG..HEAD >> temp_changelog.md + printf '### Changes since %s:\n' "$LAST_TAG" > "$temp_changelog" + git log --pretty=format:'%s%x00' "$LAST_TAG..HEAD" \ + | tr '\0' '\n' \ + | awk 'NF { print "- " $0 }' >> "$temp_changelog" else - echo "### Changes:" >> temp_changelog.md - git log --pretty=format:"- %s" -n 10 >> temp_changelog.md + printf '### Changes:\n' > "$temp_changelog" + git log --pretty=format:'%s%x00' -n 10 \ + | tr '\0' '\n' \ + | awk 'NF { print "- " $0 }' >> "$temp_changelog" fi - echo "" >> temp_changelog.md - - # Insert changes into changelog - sed -i "/## \[${{ steps.new_version.outputs.version_number }}\]/r temp_changelog.md" CHANGELOG.md - rm temp_changelog.md + printf '\n' >> "$temp_changelog" + + # Insert changes into changelog after the current version heading + awk -v target="## [${VERSION}]" -v insert_file="$temp_changelog" ' + { print } + $0 == target && !inserted { + while ((getline line < insert_file) > 0) print line + close(insert_file) + inserted = 1 + } + ' CHANGELOG.md > CHANGELOG.md.tmp + mv CHANGELOG.md.tmp CHANGELOG.md + rm -f "$temp_changelog" - name: Create release archive run: | @@ -122,28 +146,33 @@ jobs: --exclude=.git \ --exclude=release \ --exclude=frontend \ - src static package.json bun.lock tsconfig.json dockerfile compose.yml README.md LICENSE + src static package.json bun.lock tsconfig.json Dockerfile compose.yml README.md LICENSE - name: Create release tag run: | # Create git tag for release (works with branch protection) - # Note: Version changes are not committed back to master due to branch protection + # Note: Version changes are not committed back to main due to branch protection # The release archives will contain the correct versions - git tag ${{ steps.new_version.outputs.new_version }} - git push origin ${{ steps.new_version.outputs.new_version }} + TAG="${{ steps.new_version.outputs.new_version }}" + git fetch --tags origin + if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then + echo "Tag ${TAG} already exists, skipping tag creation" + else + git tag "${TAG}" + git push origin "${TAG}" + fi - - name: Create GitHub Release - uses: actions/create-release@v1 - id: create_release + - name: Create GitHub release and upload assets + uses: softprops/action-gh-release@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ steps.new_version.outputs.new_version }} - release_name: Release ${{ steps.new_version.outputs.new_version }} + name: Release ${{ steps.new_version.outputs.new_version }} body: | ## Changes in ${{ steps.new_version.outputs.new_version }} - See [CHANGELOG.md](https://github.com/FROSTR-ORG/igloo-server/blob/master/CHANGELOG.md) for full details. + See [CHANGELOG.md](https://github.com/FROSTR-ORG/igloo-server/blob/main/CHANGELOG.md) for full details. ### Installation @@ -162,36 +191,21 @@ jobs: ``` draft: false prerelease: false - - - name: Upload source archive - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./release/igloo-server-${{ steps.new_version.outputs.version_number }}-src.tar.gz - asset_name: igloo-server-${{ steps.new_version.outputs.version_number }}-src.tar.gz - asset_content_type: application/gzip - - - name: Upload binary archive - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./release/igloo-server-${{ steps.new_version.outputs.version_number }}.tar.gz - asset_name: igloo-server-${{ steps.new_version.outputs.version_number }}.tar.gz - asset_content_type: application/gzip + files: | + ./release/igloo-server-${{ steps.new_version.outputs.version_number }}-src.tar.gz + ./release/igloo-server-${{ steps.new_version.outputs.version_number }}.tar.gz docker: runs-on: ubuntu-latest needs: release + permissions: + packages: write steps: - name: Checkout code uses: actions/checkout@v4 with: - ref: master + ref: refs/tags/${{ needs.release.outputs.new_version }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/umbrel-dev.yml b/.github/workflows/umbrel-dev.yml index 706ec5a..669a49b 100644 --- a/.github/workflows/umbrel-dev.yml +++ b/.github/workflows/umbrel-dev.yml @@ -3,7 +3,7 @@ name: Umbrel Dev Image on: push: branches: - - master + - main - dev workflow_dispatch: @@ -39,6 +39,12 @@ jobs: - name: Smoke test Umbrel image run: | + set -e + cleanup() { + docker rm -f umbrel-dev-test >/dev/null 2>&1 || true + } + trap cleanup EXIT + docker run -d --name umbrel-dev-test -p 8003:8002 \ -e ADMIN_SECRET=ci-admin-secret \ -e ALLOWED_ORIGINS=http://localhost:8003 \ @@ -46,4 +52,3 @@ jobs: igloo-server-umbrel:dev-ci sleep 12 curl -f http://localhost:8003/api/status - docker rm -f umbrel-dev-test diff --git a/.gitignore b/.gitignore index bf245db..ee63590 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,8 @@ dist static/app.js static/app.css static/styles.css +static/docs/swagger-ui-bundle.js +static/docs/swagger-ui-bundle.js.map static/qr-scanner-worker.min.js # VSCode @@ -64,7 +66,7 @@ data/.session-secret test-*.sh debug-*.js verify-*.md -.DS_Store +test-results/ # LLM files .claude diff --git a/Dockerfile b/Dockerfile index 65fae40..f918c40 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Multi-stage build for smaller production image -FROM oven/bun:latest AS build +FROM oven/bun:1.3.10 AS build WORKDIR /app @@ -21,7 +21,7 @@ COPY tsconfig.json ./ RUN bun run build # --- Production stage --- -FROM oven/bun:latest AS production +FROM oven/bun:1.3.10 AS production WORKDIR /app diff --git a/bun.lock b/bun.lock index 0dc4d79..66e5497 100644 --- a/bun.lock +++ b/bun.lock @@ -24,16 +24,17 @@ "react-dom": "^18.3.1", "tailwind-merge": "^3.3.1", "yaml": "^2.8.1", + "zod": "^3.25.76", }, "devDependencies": { "@redocly/cli": "^1.34.5", "@types/node": "^22.18.12", "@types/react": "^18.3.26", "@types/react-dom": "^18.3.7", - "@types/yaml": "^1.9.7", + "ajv": "^8.18.0", "bun-types": "^1.3.1", "concurrently": "^9.2.1", - "esbuild": "^0.24.2", + "esbuild": "^0.25.0", "postcss": "^8.5.6", "tailwindcss": "^3.4.18", "tailwindcss-animate": "^1.0.7", @@ -41,6 +42,15 @@ }, }, }, + "overrides": { + "ajv": "8.18.0", + "dompurify": "3.3.2", + "fast-xml-parser": "5.4.1", + "glob": "10.5.0", + "js-yaml": "4.1.1", + "minimatch": "10.2.4", + "undici": "6.23.0", + }, "packages": { "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -64,55 +74,57 @@ "@emotion/unitless": ["@emotion/unitless@0.8.1", "", {}, "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.24.2", "", { "os": "android", "cpu": "x64" }, "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="], + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.2", "", { "os": "none", "cpu": "x64" }, "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="], + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="], + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], "@exodus/schemasafe": ["@exodus/schemasafe@1.3.0", "", {}, "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw=="], @@ -270,8 +282,6 @@ "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], - "@types/yaml": ["@types/yaml@1.9.7", "", { "dependencies": { "yaml": "*" } }, "sha512-8WMXRDD1D+wCohjfslHDgICd2JtMATZU8CkhH8LVJqcJs6dyYj5TGptzP8wApbmEullGBSsCEzzap73DQ1HJaA=="], - "@vbyte/buff": ["@vbyte/buff@1.0.2", "", {}, "sha512-h/3CU+9H6fWZzAfM9/ar9FpQdRfupYyL5ug3fJ9hofzSuWytojVUAT32pnBtTIatowGVburZ/qj0xSpTXWA8qA=="], "@vbyte/micro-lib": ["@vbyte/micro-lib@1.1.2", "", { "dependencies": { "@noble/curves": "^1.9.6", "@noble/hashes": "^1.8.0", "@scure/base": "^1.2.6", "@vbyte/buff": "^1.0.2", "zod": "4.0.14" } }, "sha512-SYOTaaY4zAxuIZPdzVQAyyxAi83ZGt+LtXsjyrUjYsQuWzipLsTIF0h5fyLccOn7Mi+4tY9fPUrvMZNQJDijQg=="], @@ -280,7 +290,7 @@ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], - "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + "ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -296,13 +306,13 @@ "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "balanced-match": ["balanced-match@4.0.3", "", {}, "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g=="], "better-ajv-errors": ["better-ajv-errors@1.2.0", "", { "dependencies": { "@babel/code-frame": "^7.16.0", "@humanwhocodes/momoa": "^2.0.2", "chalk": "^4.1.2", "jsonpointer": "^5.0.0", "leven": "^3.1.0 < 4" }, "peerDependencies": { "ajv": "4.11.8 - 8" } }, "sha512-UW+IsFycygIo7bclP9h5ugkNH8EjCSgqyFB/yQ4Hqqa1OEYDtb0uFIkYE0b6+CjkgJYVM5UKI/pJPxjYe9EZlA=="], "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], - "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "brace-expansion": ["brace-expansion@5.0.2", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], @@ -342,8 +352,6 @@ "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - "concat-stream": ["concat-stream@2.0.0", "", { "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.0.2", "typedarray": "^0.0.6" } }, "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A=="], "concurrently": ["concurrently@9.2.1", "", { "dependencies": { "chalk": "4.1.2", "rxjs": "7.8.2", "shell-quote": "1.8.3", "supports-color": "8.1.1", "tree-kill": "1.2.2", "yargs": "17.7.2" }, "bin": { "conc": "dist/bin/concurrently.js", "concurrently": "dist/bin/concurrently.js" } }, "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng=="], @@ -380,7 +388,7 @@ "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], - "dompurify": ["dompurify@3.3.0", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ=="], + "dompurify": ["dompurify@3.3.2", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ=="], "dotenv": ["dotenv@16.4.7", "", {}, "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ=="], @@ -400,7 +408,7 @@ "es6-promise": ["es6-promise@3.3.1", "", {}, "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg=="], - "esbuild": ["esbuild@0.24.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="], + "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], @@ -416,7 +424,9 @@ "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], - "fast-xml-parser": ["fast-xml-parser@4.5.3", "", { "dependencies": { "strnum": "^1.1.1" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig=="], + "fast-xml-builder": ["fast-xml-builder@1.0.0", "", {}, "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ=="], + + "fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="], "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], @@ -428,8 +438,6 @@ "form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="], - "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -442,7 +450,7 @@ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], @@ -462,8 +470,6 @@ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], - "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], @@ -500,7 +506,7 @@ "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], "jsep": ["jsep@1.4.0", "", {}, "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw=="], @@ -542,7 +548,7 @@ "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - "minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + "minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], @@ -588,8 +594,6 @@ "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], - "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], - "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], "openapi-sampler": ["openapi-sampler@1.6.2", "", { "dependencies": { "@types/json-schema": "^7.0.7", "fast-xml-parser": "^4.5.0", "json-pointer": "0.6.2" } }, "sha512-NyKGiFKfSWAZr4srD/5WDhInOWDhfml32h/FKUqLpEwKJt0kG0LGUU0MdyNkKrVGuJnw6DuPWq/sHCwAMpiRxg=="], @@ -600,8 +604,6 @@ "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], - "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], @@ -734,7 +736,7 @@ "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="], + "strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], "styled-components": ["styled-components@6.1.19", "", { "dependencies": { "@emotion/is-prop-valid": "1.2.2", "@emotion/unitless": "0.8.1", "@types/stylis": "4.2.5", "css-to-react-native": "3.2.0", "csstype": "3.1.3", "postcss": "8.4.49", "shallowequal": "1.1.0", "stylis": "4.3.2", "tslib": "2.6.2" }, "peerDependencies": { "react": ">= 16.8.0", "react-dom": ">= 16.8.0" } }, "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA=="], @@ -774,7 +776,7 @@ "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="], - "undici": ["undici@6.22.0", "", {}, "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw=="], + "undici": ["undici@6.23.0", "", {}, "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], @@ -798,8 +800,6 @@ "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - "ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], @@ -844,8 +844,6 @@ "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - "glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "nostr-tools/@noble/ciphers": ["@noble/ciphers@0.5.3", "", {}, "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w=="], "nostr-tools/@noble/curves": ["@noble/curves@1.2.0", "", { "dependencies": { "@noble/hashes": "1.3.2" } }, "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw=="], @@ -870,8 +868,6 @@ "styled-components/postcss": ["postcss@8.4.49", "", { "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA=="], - "sucrase/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], - "swagger2openapi/yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="], "swagger2openapi/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], @@ -888,16 +884,12 @@ "concurrently/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], - "glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - "nostr-tools/@noble/curves/@noble/hashes": ["@noble/hashes@1.3.2", "", {}, "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ=="], "oas-resolver/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "oas-resolver/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], - "sucrase/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - "swagger2openapi/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "swagger2openapi/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], diff --git a/compose.yml b/compose.yml index 96b5bb0..ebac7a3 100644 --- a/compose.yml +++ b/compose.yml @@ -15,8 +15,8 @@ services: - HOST_NAME=0.0.0.0 - HOST_PORT=8002 - NODE_ENV=production - # Explicit DB path inside the container; lives under /app/data - - DB_PATH=/app/data/igloo.db + # DB directory inside the container; database file defaults to /app/data/igloo.db + - DB_PATH=/app/data container_name: igloo-server hostname: igloo-server @@ -49,4 +49,3 @@ services: networks: infranet: driver: bridge - diff --git a/docs/CONFIG.md b/docs/CONFIG.md index f30b6c6..d9e6d04 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -90,6 +90,7 @@ Timeouts: WebSocket abuse controls: - `RATE_LIMIT_WS_UPGRADE_WINDOW`, `RATE_LIMIT_WS_UPGRADE_MAX` - `WS_MAX_CONNECTIONS_PER_IP`, `WS_MSG_RATE`, `WS_MSG_BURST` +- `ALLOW_QUERY_CREDENTIALS` (default `true` for legacy `/api/events?apiKey=...` / `sessionId` compatibility; set `false` to disable query-param auth on upgrades) Recovery throttling: - `RATE_LIMIT_RECOVERY_WINDOW`, `RATE_LIMIT_RECOVERY_MAX` diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index 00acb3f..611f4bb 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -32,14 +32,14 @@ sudo chmod +x /usr/local/bin/docker-compose ``` 2) Pull and run (pin a release tag for reproducibility, e.g., `1.1.1` or `umbrel-1.1.1`): ```bash -docker pull ghcr.io/frostr-org/igloo-server:latest +docker pull ghcr.io/frostr-org/igloo-server:1.1.1 docker run -d --name igloo-server -p 8002:8002 \ -v $PWD/data:/app/data \ -e ADMIN_SECRET=$(openssl rand -hex 32) \ -e AUTH_ENABLED=true \ -e TRUST_PROXY=true \ -e ALLOWED_ORIGINS=https://yourdomain.com \ - ghcr.io/frostr-org/igloo-server:latest + ghcr.io/frostr-org/igloo-server:1.1.1 ``` 3) Docker Compose option (create `docker-compose.yml`): ```yaml @@ -62,7 +62,9 @@ Note: `env_file: .env` injects values only at container start. If you also bind- 4) Firewall (UFW): ```bash -sudo ufw allow 80 443 22 +sudo ufw allow 22 +sudo ufw allow 80 +sudo ufw allow 443 sudo ufw allow 8002 # only if accessing without reverse proxy sudo ufw enable ``` diff --git a/docs/RELEASE.md b/docs/RELEASE.md index ab3ef2c..400a61e 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -37,10 +37,10 @@ bun run docs:validate git checkout -b release/prepare-v1.1.1 git push origin release/prepare-v1.1.1 ``` -Create PR: `release/prepare-v1.1.1` → `master` +Create PR: `release/prepare-v1.1.1` → `main` ### 3. Merge & Release -- Merge PR to `master` +- Merge PR to `main` - GitHub Actions automatically: - Bumps version in `package.json` - Updates `CHANGELOG.md` @@ -50,7 +50,7 @@ Create PR: `release/prepare-v1.1.1` → `master` ### 4. Verify Release - ✅ Check [GitHub Releases](https://github.com/FROSTR-ORG/igloo-server/releases) - ✅ Test Docker image: `docker pull ghcr.io/frostr-org/igloo-server:latest` -- ✅ Sync dev: `git checkout dev && git merge master && git push origin dev` +- ✅ Sync dev: `git checkout dev && git merge main && git push origin dev` ## 🔄 Version Bumping Logic @@ -66,11 +66,11 @@ GitHub Actions automatically detects version type from commit messages: For critical fixes: ```bash -git checkout master +git checkout main git checkout -b hotfix/critical-fix # Make fix and commit git push origin hotfix/critical-fix -# Create PR to master +# Create PR to main ``` ## 📦 Release Artifacts diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 43a2abe..95d80e5 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -647,7 +647,7 @@ bun run start # 2. Test with API key authentication AUTH_ENABLED=true API_KEY= # Replace with test API key -curl -H "X-API-Key: test-api-key-12345" http://localhost:8002/api/status +curl -H "X-API-Key: " http://localhost:8002/api/status # 3. Test with basic authentication AUTH_ENABLED=true diff --git a/docs/openapi/README.md b/docs/openapi/README.md index d27d77e..c6069d5 100644 --- a/docs/openapi/README.md +++ b/docs/openapi/README.md @@ -4,8 +4,8 @@ This directory contains the comprehensive OpenAPI 3.1 specification for the Iglo ## Files -- **`openapi/openapi.yaml`** - Complete OpenAPI 3.1 specification in YAML format -- **`openapi/openapi.json`** - Bundled JSON representation generated from the YAML spec +- **`docs/openapi/openapi.yaml`** - Complete OpenAPI 3.1 specification in YAML format +- **`docs/openapi/openapi.json`** - Bundled JSON representation generated from the YAML spec - **`README.md`** - This documentation file ## Accessing the Documentation @@ -39,8 +39,9 @@ Mode and auth nuance: ### Authentication for API Documentation -- **Development**: No authentication required for easy testing -- **Production**: Authentication required for security (enforced by the server) +Authentication behavior is configuration-dependent: +- **Development**: authentication is commonly disabled by default for local testing, but controlled by environment variables (see `AUTH_ENABLED` in `env.example`) +- **Production**: authentication should be enabled and enforced by server configuration To authenticate in Swagger UI: 1. Use the "Authorize" button in Swagger UI @@ -102,7 +103,7 @@ This ensures the YAML syntax is correct and the specification is well-formed. When adding or modifying API endpoints: -1. Update the corresponding section in `openapi/openapi.yaml` +1. Update the corresponding section in `docs/openapi/openapi.yaml` 2. Add/update request and response schemas 3. Include relevant examples 4. Validate the specification: `bun run docs:validate` diff --git a/docs/openapi/openapi.json b/docs/openapi/openapi.json index 0bbca29..61d362d 100644 --- a/docs/openapi/openapi.json +++ b/docs/openapi/openapi.json @@ -85,6 +85,14 @@ { "name": "Event Log", "description": "Persisted UI event log endpoints (database mode only)" + }, + { + "name": "Crypto", + "description": "Cryptographic operations and key management" + }, + { + "name": "NIP‑46", + "description": "NIP‑46 protocol and signing interactions" } ], "paths": { @@ -419,22 +427,14 @@ } }, "400": { - "$ref": "#/components/responses/BadRequest", + "description": "Bad request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AuthStatus" + "$ref": "#/components/schemas/ErrorResponse" }, "example": { - "enabled": true, - "methods": [ - "api-key", - "bearer", - "basic-auth", - "session" - ], - "rateLimiting": true, - "sessionTimeout": 3600 + "error": "Invalid authentication status request" } } } @@ -450,6 +450,7 @@ "tags": [ "Authentication" ], + "security": [], "requestBody": { "required": true, "content": { @@ -532,6 +533,7 @@ "tags": [ "Authentication" ], + "security": [], "responses": { "200": { "description": "Logout successful", @@ -1392,15 +1394,36 @@ "$ref": "#/components/schemas/StoredShare" } }, - "example": [ - { - "shareCredential": "bfshare1qqsqp...", - "groupCredential": "bfgroup1qqsqp...", - "savedAt": "2025-01-20T12:00:00.000Z", - "id": "env-stored-share", - "source": "environment" + "examples": { + "metadataOnly": { + "summary": "Default response with credential presence flags only", + "value": [ + { + "hasShareCredential": true, + "hasGroupCredential": true, + "isValid": true, + "savedAt": "2025-01-20T12:00:00.000Z", + "id": "env-stored-share", + "source": "environment" + } + ] + }, + "includesRaw": { + "summary": "Debug response with raw credentials included", + "value": [ + { + "hasShareCredential": true, + "hasGroupCredential": true, + "isValid": true, + "shareCredential": "bfshare1qqsqp...", + "groupCredential": "bfgroup1qqsqp...", + "savedAt": "2025-01-20T12:00:00.000Z", + "id": "env-stored-share", + "source": "environment" + } + ] } - ] + } } } }, @@ -2083,76 +2106,122 @@ } } }, - "/api/admin/api-keys": { + "/api/nip46/requests": { "get": { - "operationId": "listAdminApiKeys", - "summary": "List API keys", - "description": "Returns metadata for all API keys managed in database mode.\n\nAuthentication: provide `ADMIN_SECRET` as a Bearer token, or use a valid session for an admin user (first user or `role=admin`).\n", + "operationId": "listNip46Requests", + "summary": "List persisted NIP‑46 requests", "tags": [ - "Admin" + "NIP‑46" ], - "security": [ + "parameters": [ { - "bearerAuth": [] + "in": "query", + "name": "status", + "schema": { + "type": "string" + }, + "description": "Comma-separated request statuses (pending, approved, denied, completed, failed, expired)" }, { - "sessionHeader": [] + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 500 + }, + "description": "Number of requests to return (default 100)" }, { - "sessionCookie": [] + "in": "query", + "name": "beforeCreatedAt", + "schema": { + "type": "string" + }, + "description": "Cursor created_at value for pagination (must be paired with beforeId)" + }, + { + "in": "query", + "name": "beforeId", + "schema": { + "type": "string" + }, + "description": "Cursor id value for pagination (must be paired with beforeCreatedAt)" } ], "responses": { "200": { - "description": "API key metadata retrieved successfully", + "description": "Requests", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AdminApiKeyListResponse" + "type": "object", + "properties": { + "requests": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Nip46Request" + } + }, + "nextCursor": { + "oneOf": [ + { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "id": { + "type": "string" + } + } + }, + { + "type": "null" + } + ] + } + } } } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" } } }, "post": { - "operationId": "createAdminApiKey", - "summary": "Create API key", - "description": "Creates a new API key and returns the token once.\n\nAuthentication: provide `ADMIN_SECRET` as a Bearer token, or use a valid session for an admin user (first user or `role=admin`).\n", + "operationId": "updateNip46Request", + "summary": "Update a NIP‑46 request status", "tags": [ - "Admin" - ], - "security": [ - { - "bearerAuth": [] - }, - { - "sessionHeader": [] - }, - { - "sessionCookie": [] - } + "NIP‑46" ], "requestBody": { - "required": false, + "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AdminApiKeyCreateRequest" + "$ref": "#/components/schemas/Nip46RequestActionInput" } } } }, "responses": { - "201": { - "description": "API key created successfully", + "200": { + "description": "Updated request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AdminApiKeyCreateResponse" + "type": "object", + "properties": { + "request": { + "$ref": "#/components/schemas/Nip46Request" + } + } } } } @@ -2163,52 +2232,47 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, - "500": { - "$ref": "#/components/responses/InternalServerError" + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "$ref": "#/components/responses/NotFound" } } - } - }, - "/api/admin/api-keys/revoke": { - "post": { - "operationId": "revokeAdminApiKey", - "summary": "Revoke API key", - "description": "Revokes an existing API key by ID.\n\nAuthentication: provide `ADMIN_SECRET` as a Bearer token, or use a valid session for an admin user (first user or `role=admin`).\n", + }, + "delete": { + "operationId": "deleteNip46Request", + "summary": "Delete a persisted NIP‑46 request", "tags": [ - "Admin" - ], - "security": [ - { - "bearerAuth": [] - }, - { - "sessionHeader": [] - }, - { - "sessionCookie": [] - } + "NIP‑46" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AdminApiKeyRevokeRequest" + "$ref": "#/components/schemas/Nip46RequestDeleteInput" } } } }, "responses": { "200": { - "description": "API key revoked successfully", + "description": "Request deleted", "content": { "application/json": { "schema": { "type": "object", "properties": { - "success": { - "type": "boolean", - "const": true + "ok": { + "type": "boolean" } } } @@ -2221,49 +2285,81 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "404": { "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" } } } }, - "/api/admin/users": { - "get": { - "operationId": "listUsers", - "summary": "List users", - "description": "Returns basic information about users. Database mode only.", + "/api/nip46/connect": { + "post": { + "operationId": "createNip46Connect", + "summary": "Process a nostrconnect invite string", "tags": [ - "Admin" + "NIP‑46" ], - "security": [ - { - "bearerAuth": [] + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Nip46ConnectInput" + } + } + } + }, + "responses": { + "200": { + "description": "Session created/updated from invite", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Nip46ConnectResponse" + } + } + } }, - { - "sessionHeader": [] + "400": { + "$ref": "#/components/responses/BadRequest" }, - { - "sessionCookie": [] + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "503": { + "$ref": "#/components/responses/ServiceUnavailable" } + } + } + }, + "/api/nip46/relays": { + "get": { + "operationId": "listNip46Relays", + "summary": "Get NIP‑46 relay pool", + "tags": [ + "NIP‑46" ], "responses": { "200": { - "description": "Users listed", + "description": "Relay list", "content": { "application/json": { "schema": { "type": "object", "properties": { - "users": { + "relays": { "type": "array", "items": { - "$ref": "#/components/schemas/AdminUser" + "type": "string" } } } @@ -2273,59 +2369,38 @@ }, "401": { "$ref": "#/components/responses/Unauthorized" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - }, - "503": { - "$ref": "#/components/responses/ServiceUnavailable" } } - } - }, - "/api/admin/users/delete": { - "post": { - "operationId": "deleteUser", - "summary": "Delete a user", - "description": "Deletes a user by ID with last-admin guard. Database mode only.", + }, + "put": { + "operationId": "updateNip46Relays", + "summary": "Replace NIP‑46 relay pool", "tags": [ - "Admin" - ], - "security": [ - { - "bearerAuth": [] - }, - { - "sessionHeader": [] - }, - { - "sessionCookie": [] - } + "NIP‑46" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AdminUserDeleteRequest" + "$ref": "#/components/schemas/Nip46RelaysInput" } } } }, "responses": { "200": { - "description": "Deleted", + "description": "Updated relays", "content": { "application/json": { "schema": { "type": "object", "properties": { - "success": { - "type": "boolean", - "const": true - }, - "message": { - "type": "string" + "relays": { + "type": "array", + "items": { + "type": "string" + } } } } @@ -2337,19 +2412,387 @@ }, "401": { "$ref": "#/components/responses/Unauthorized" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" } } - } - }, - "/api/user/profile": { - "get": { - "operationId": "getUserProfile", + }, + "post": { + "operationId": "mergeNip46Relays", + "summary": "Merge relays into NIP‑46 relay pool", + "tags": [ + "NIP‑46" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Nip46RelaysInput" + } + } + } + }, + "responses": { + "200": { + "description": "Updated relays", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "relays": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + } + }, + "/api/nip46/transport": { + "get": { + "operationId": "getNip46Transport", + "summary": "Get NIP‑46 transport key", + "tags": [ + "NIP‑46" + ], + "responses": { + "200": { + "description": "Transport key", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Nip46Transport" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + }, + "put": { + "operationId": "updateNip46Transport", + "summary": "Set or rotate NIP‑46 transport key", + "tags": [ + "NIP‑46" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Nip46Transport" + } + } + } + }, + "responses": { + "200": { + "description": "Updated transport key", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean" + }, + "transport_sk": { + "type": "string" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + } + }, + "/api/admin/api-keys": { + "get": { + "operationId": "listAdminApiKeys", + "summary": "List API keys", + "description": "Returns metadata for all API keys managed in database mode.\n\nAuthentication: provide `ADMIN_SECRET` as a Bearer token, or use a valid session for an admin user (first user or `role=admin`).\n", + "tags": [ + "Admin" + ], + "security": [ + { + "bearerAuth": [] + }, + { + "sessionHeader": [] + }, + { + "sessionCookie": [] + } + ], + "responses": { + "200": { + "description": "API key metadata retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminApiKeyListResponse" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + }, + "post": { + "operationId": "createAdminApiKey", + "summary": "Create API key", + "description": "Creates a new API key and returns the token once.\n\nAuthentication: provide `ADMIN_SECRET` as a Bearer token, or use a valid session for an admin user (first user or `role=admin`).\n", + "tags": [ + "Admin" + ], + "security": [ + { + "bearerAuth": [] + }, + { + "sessionHeader": [] + }, + { + "sessionCookie": [] + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminApiKeyCreateRequest" + } + } + } + }, + "responses": { + "201": { + "description": "API key created successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminApiKeyCreateResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/api/admin/api-keys/revoke": { + "post": { + "operationId": "revokeAdminApiKey", + "summary": "Revoke API key", + "description": "Revokes an existing API key by ID.\n\nAuthentication: provide `ADMIN_SECRET` as a Bearer token, or use a valid session for an admin user (first user or `role=admin`).\n", + "tags": [ + "Admin" + ], + "security": [ + { + "bearerAuth": [] + }, + { + "sessionHeader": [] + }, + { + "sessionCookie": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminApiKeyRevokeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "API key revoked successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "const": true + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/api/admin/users": { + "get": { + "operationId": "listUsers", + "summary": "List users", + "description": "Returns basic information about users. Database mode only.", + "tags": [ + "Admin" + ], + "security": [ + { + "bearerAuth": [] + }, + { + "sessionHeader": [] + }, + { + "sessionCookie": [] + } + ], + "responses": { + "200": { + "description": "Users listed", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "users": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdminUser" + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + }, + "503": { + "$ref": "#/components/responses/ServiceUnavailable" + } + } + } + }, + "/api/admin/users/delete": { + "post": { + "operationId": "deleteUser", + "summary": "Delete a user", + "description": "Deletes a user by ID with last-admin guard. Database mode only.", + "tags": [ + "Admin" + ], + "security": [ + { + "bearerAuth": [] + }, + { + "sessionHeader": [] + }, + { + "sessionCookie": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminUserDeleteRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "const": true + }, + "message": { + "type": "string" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/api/user/profile": { + "get": { + "operationId": "getUserProfile", "summary": "Get current user profile", "description": "Returns basic information for the authenticated database user. Database mode only.", "tags": [ @@ -3011,7 +3454,6 @@ "type": "string", "enum": [ "api-key", - "bearer", "basic-auth", "session" ] @@ -3219,16 +3661,37 @@ "StoredShare": { "type": "object", "properties": { + "hasShareCredential": { + "type": "boolean", + "description": "Whether a share credential is currently stored" + }, + "hasGroupCredential": { + "type": "boolean", + "description": "Whether a group credential is currently stored" + }, + "isValid": { + "type": "boolean", + "description": "Whether both credentials are present and usable together" + }, "shareCredential": { - "type": "string", - "description": "The share credential" + "type": [ + "string", + "null" + ], + "description": "Optional raw share credential (only returned in explicit debug mode)" }, "groupCredential": { - "type": "string", - "description": "The group credential" + "type": [ + "string", + "null" + ], + "description": "Optional raw group credential (only returned in explicit debug mode)" }, "savedAt": { - "type": "string", + "type": [ + "string", + "null" + ], "format": "date-time", "description": "When the share was saved" }, @@ -3242,8 +3705,9 @@ } }, "required": [ - "shareCredential", - "groupCredential", + "hasShareCredential", + "hasGroupCredential", + "isValid", "savedAt", "id", "source" @@ -3526,7 +3990,7 @@ "type": "object", "description": "Client-supplied fields when creating or updating a NIP‑46 session", "properties": { - "client_pubkey": { + "pubkey": { "type": "string", "description": "Client public key (hex encoded)" }, @@ -3554,7 +4018,7 @@ } }, "required": [ - "client_pubkey" + "pubkey" ] }, "Nip46Session": { @@ -3635,6 +4099,176 @@ } } }, + "Nip46Request": { + "type": "object", + "description": "Persisted NIP‑46 request payload and lifecycle status", + "properties": { + "id": { + "type": "string" + }, + "user_id": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ] + }, + "session_pubkey": { + "type": "string" + }, + "method": { + "type": "string" + }, + "params": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "approved", + "denied", + "completed", + "failed", + "expired" + ] + }, + "result": { + "type": [ + "string", + "null" + ] + }, + "error": { + "type": [ + "string", + "null" + ] + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "expires_at": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "user_id", + "session_pubkey", + "method", + "params", + "status", + "created_at", + "updated_at" + ] + }, + "Nip46RequestActionInput": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Persisted request identifier" + }, + "action": { + "type": "string", + "enum": [ + "approve", + "deny", + "fail", + "complete" + ] + }, + "policy": { + "$ref": "#/components/schemas/Nip46Policy" + }, + "result": { + "type": [ + "string", + "null" + ] + }, + "error": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "action" + ] + }, + "Nip46RequestDeleteInput": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + "Nip46ConnectInput": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "description": "nostrconnect:// invite URI" + } + }, + "required": [ + "uri" + ] + }, + "Nip46ConnectResponse": { + "type": "object", + "properties": { + "session": { + "$ref": "#/components/schemas/Nip46Session" + } + } + }, + "Nip46RelaysInput": { + "type": "object", + "properties": { + "relays": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "required": [ + "relays" + ] + }, + "Nip46Transport": { + "type": "object", + "properties": { + "transport_sk": { + "type": "string", + "description": "32-byte transport secret key encoded as hex" + } + }, + "required": [ + "transport_sk" + ] + }, "AdminApiKey": { "type": "object", "description": "Metadata about an API key managed in database mode", @@ -4076,7 +4710,6 @@ "error": "Authentication required", "authMethods": [ "api-key", - "bearer", "basic-auth", "session" ] diff --git a/docs/openapi/openapi.yaml b/docs/openapi/openapi.yaml index db5e2ca..0dc8e65 100644 --- a/docs/openapi/openapi.yaml +++ b/docs/openapi/openapi.yaml @@ -132,6 +132,12 @@ paths: - The stream emits JSON objects shaped like `{ type, message, data?, timestamp, id }`. tags: - Events + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] responses: '200': description: | @@ -187,6 +193,12 @@ paths: - Use `types` (comma-separated) to filter by event type. tags: - Event Log + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] parameters: - name: limit in: query @@ -234,6 +246,12 @@ paths: The UI typically loads this lazily when a log entry is expanded. tags: - Event Log + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] parameters: - name: hash in: path @@ -269,6 +287,12 @@ paths: - `types`: comma-separated list of event types to include. tags: - Event Log + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] parameters: - name: sinceSeq in: query @@ -327,16 +351,13 @@ paths: schema: $ref: '#/components/schemas/AuthStatus' '400': - $ref: '#/components/responses/BadRequest' + description: Bad request content: application/json: schema: - $ref: '#/components/schemas/AuthStatus' + $ref: '#/components/schemas/ErrorResponse' example: - enabled: true - methods: ["api-key", "bearer", "basic-auth", "session"] - rateLimiting: true - sessionTimeout: 3600 + error: "Invalid authentication status request" /api/auth/login: post: @@ -345,6 +366,7 @@ paths: description: Login with username/password or API key to get a session tags: - Authentication + security: [] requestBody: required: true content: @@ -397,6 +419,7 @@ paths: description: Clear the current session tags: - Authentication + security: [] responses: '200': description: Logout successful @@ -423,6 +446,12 @@ paths: description: Retrieve current environment configuration (whitelisted variables only) tags: - Configuration + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] responses: '200': description: Environment variables retrieved successfully @@ -446,6 +475,12 @@ paths: description: Update environment configuration (whitelisted variables only) tags: - Configuration + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] requestBody: required: true content: @@ -496,6 +531,12 @@ paths: description: Remove specified environment variables tags: - Configuration + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] requestBody: required: true content: @@ -548,6 +589,12 @@ paths: description: Return the signing group public key and quorum information tags: - Peers + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] responses: '200': description: Group metadata retrieved successfully @@ -585,6 +632,12 @@ paths: description: Get all peers from the group credential with their current status tags: - Peers + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] responses: '200': description: Peers retrieved successfully @@ -603,12 +656,12 @@ paths: type: integer example: peers: - - pubkey: "02abcd1234..." + - pubkey: "021111111111111111111111111111111111111111111111111111111111111111" online: true lastSeen: "2025-01-20T12:00:00.000Z" latency: 150 lastPingAttempt: "2025-01-20T11:59:00.000Z" - - pubkey: "03efgh5678..." + - pubkey: "032222222222222222222222222222222222222222222222222222222222222222" online: false lastSeen: null latency: null @@ -633,6 +686,12 @@ paths: description: Get the public key of this node from the share credential tags: - Peers + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] responses: '200': description: Self public key retrieved successfully @@ -669,6 +728,12 @@ paths: description: Ping specific peer or all peers to check connectivity tags: - Peers + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] requestBody: required: true content: @@ -756,6 +821,12 @@ paths: description: Use threshold shares to recover the original secret key tags: - Key Recovery + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] requestBody: required: true content: @@ -860,6 +931,12 @@ paths: description: Validate group or share credentials without performing recovery tags: - Key Recovery + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] requestBody: required: true content: @@ -941,6 +1018,12 @@ paths: description: Retrieve currently stored share information when running in headless mode tags: - Share Management + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] responses: '200': description: Shares retrieved successfully @@ -950,12 +1033,27 @@ paths: type: array items: $ref: '#/components/schemas/StoredShare' - example: - - shareCredential: "bfshare1qqsqp..." - groupCredential: "bfgroup1qqsqp..." - savedAt: "2025-01-20T12:00:00.000Z" - id: "env-stored-share" - source: "environment" + examples: + metadataOnly: + summary: Default response with credential presence flags only + value: + - hasShareCredential: true + hasGroupCredential: true + isValid: true + savedAt: "2025-01-20T12:00:00.000Z" + id: "env-stored-share" + source: "environment" + includesRaw: + summary: Debug response with raw credentials included + value: + - hasShareCredential: true + hasGroupCredential: true + isValid: true + shareCredential: "bfshare1qqsqp..." + groupCredential: "bfgroup1qqsqp..." + savedAt: "2025-01-20T12:00:00.000Z" + id: "env-stored-share" + source: "environment" '401': $ref: '#/components/responses/Unauthorized' '500': @@ -967,6 +1065,12 @@ paths: description: Save share and group credentials when running in headless mode tags: - Share Management + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] requestBody: required: true content: @@ -1208,6 +1312,12 @@ paths: operationId: listNip46Sessions summary: List NIP‑46 sessions tags: [NIP‑46] + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] parameters: - in: query name: history @@ -1233,6 +1343,12 @@ paths: operationId: upsertNip46Session summary: Create or update a NIP‑46 session tags: [NIP‑46] + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] requestBody: required: true content: @@ -1259,6 +1375,12 @@ paths: operationId: updateNip46Policy summary: Update session policy tags: [NIP‑46] + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] parameters: - $ref: '#/components/parameters/PubkeyParam' requestBody: @@ -1276,6 +1398,12 @@ paths: operationId: updateNip46Status summary: Update session status (revoked deletes) tags: [NIP‑46] + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] parameters: - $ref: '#/components/parameters/PubkeyParam' requestBody: @@ -1300,6 +1428,12 @@ paths: operationId: deleteNip46Session summary: Delete a NIP‑46 session tags: [NIP‑46] + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] parameters: - $ref: '#/components/parameters/PubkeyParam' responses: @@ -1321,6 +1455,12 @@ paths: operationId: nip46History summary: Compact NIP‑46 history summary tags: [NIP‑46] + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] responses: '200': description: History @@ -1336,6 +1476,289 @@ paths: '401': $ref: '#/components/responses/Unauthorized' + /api/nip46/requests: + get: + operationId: listNip46Requests + summary: List persisted NIP‑46 requests + tags: [NIP‑46] + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] + parameters: + - in: query + name: status + schema: + type: string + description: Comma-separated request statuses (pending, approved, denied, completed, failed, expired) + - in: query + name: limit + schema: + type: integer + minimum: 1 + maximum: 500 + description: Number of requests to return (default 100) + - in: query + name: beforeCreatedAt + schema: + type: string + description: Cursor created_at value for pagination (must be paired with beforeId) + - in: query + name: beforeId + schema: + type: string + description: Cursor id value for pagination (must be paired with beforeCreatedAt) + responses: + '200': + description: Requests + content: + application/json: + schema: + type: object + properties: + requests: + type: array + items: + $ref: '#/components/schemas/Nip46Request' + nextCursor: + oneOf: + - type: object + properties: + createdAt: { type: string } + id: { type: string } + - type: 'null' + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + post: + operationId: updateNip46Request + summary: Update a NIP‑46 request status + tags: [NIP‑46] + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Nip46RequestActionInput' + responses: + '200': + description: Updated request + content: + application/json: + schema: + type: object + properties: + request: + $ref: '#/components/schemas/Nip46Request' + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': { $ref: '#/components/responses/NotFound' } + delete: + operationId: deleteNip46Request + summary: Delete a persisted NIP‑46 request + tags: [NIP‑46] + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Nip46RequestDeleteInput' + responses: + '200': + description: Request deleted + content: + application/json: + schema: + type: object + properties: + ok: { type: boolean } + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': { $ref: '#/components/responses/NotFound' } + + /api/nip46/connect: + post: + operationId: createNip46Connect + summary: Process a nostrconnect invite string + tags: [NIP‑46] + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Nip46ConnectInput' + responses: + '200': + description: Session created/updated from invite + content: + application/json: + schema: + $ref: '#/components/schemas/Nip46ConnectResponse' + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + '503': { $ref: '#/components/responses/ServiceUnavailable' } + + /api/nip46/relays: + get: + operationId: listNip46Relays + summary: Get NIP‑46 relay pool + tags: [NIP‑46] + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] + responses: + '200': + description: Relay list + content: + application/json: + schema: + type: object + properties: + relays: + type: array + items: { type: string } + '401': { $ref: '#/components/responses/Unauthorized' } + put: + operationId: updateNip46Relays + summary: Replace NIP‑46 relay pool + tags: [NIP‑46] + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Nip46RelaysInput' + responses: + '200': + description: Updated relays + content: + application/json: + schema: + type: object + properties: + relays: + type: array + items: { type: string } + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + post: + operationId: mergeNip46Relays + summary: Merge relays into NIP‑46 relay pool + tags: [NIP‑46] + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Nip46RelaysInput' + responses: + '200': + description: Updated relays + content: + application/json: + schema: + type: object + properties: + relays: + type: array + items: { type: string } + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + + /api/nip46/transport: + get: + operationId: getNip46Transport + summary: Get NIP‑46 transport public key + tags: [NIP‑46] + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] + responses: + '200': + description: Transport public key + content: + application/json: + schema: + $ref: '#/components/schemas/Nip46TransportPublic' + '401': { $ref: '#/components/responses/Unauthorized' } + put: + operationId: updateNip46Transport + summary: Set or rotate NIP‑46 transport key + tags: [NIP‑46] + security: + - apiKeyAuth: [] + - bearerAuth: [] + - basicAuth: [] + - sessionCookie: [] + - sessionHeader: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Nip46TransportInput' + responses: + '200': + description: Updated transport public key + content: + application/json: + schema: + type: object + properties: + ok: { type: boolean } + transport_pubkey: { type: string } + '400': { $ref: '#/components/responses/BadRequest' } + '401': { $ref: '#/components/responses/Unauthorized' } + /api/admin/api-keys: get: operationId: listAdminApiKeys @@ -1786,6 +2209,8 @@ components: description: | API key authentication via X-API-Key header. + This scheme is enforced only when `AUTH_ENABLED=true`. When `AUTH_ENABLED=false`, server auth checks are bypassed. + **Security Requirements:** - HTTPS is mandatory in production environments - API keys must be transmitted over encrypted connections only @@ -1800,6 +2225,8 @@ components: description: | Bearer token authentication (alternative to X-API-Key header). + This scheme is enforced only when `AUTH_ENABLED=true`. When `AUTH_ENABLED=false`, server auth checks are bypassed. + **Security Requirements:** - HTTPS is mandatory in production environments - Bearer tokens must be transmitted over encrypted connections only @@ -1814,6 +2241,8 @@ components: description: | HTTP Basic Authentication using username and password. + This scheme is enforced only when `AUTH_ENABLED=true`. When `AUTH_ENABLED=false`, server auth checks are bypassed. + **Security Requirements:** - HTTPS is absolutely critical - Basic Auth transmits credentials in Base64 encoding (not encryption) - Never use Basic Auth over HTTP in production as credentials are easily intercepted @@ -1829,6 +2258,8 @@ components: description: | Session-based authentication via the `X-Session-ID` header. + This scheme is enforced only when `AUTH_ENABLED=true`. When `AUTH_ENABLED=false`, server auth checks are bypassed. + **Security Requirements:** - HTTPS is mandatory to protect session identifiers from interception - Treat the header value like a credential and avoid logging it @@ -1844,6 +2275,8 @@ components: description: | Session-based authentication via HttpOnly cookie. + This scheme is enforced only when `AUTH_ENABLED=true`. When `AUTH_ENABLED=false`, server auth checks are bypassed. + **Security Requirements:** - HTTPS is mandatory so cookies are only sent over encrypted channels - Cookies should be issued with Secure, HttpOnly, and SameSite attributes where appropriate @@ -1960,7 +2393,7 @@ components: type: array items: type: string - enum: ["api-key", "bearer", "basic-auth", "session"] + enum: ["api-key", "basic-auth", "session"] description: Available authentication methods rateLimiting: type: boolean @@ -2103,14 +2536,23 @@ components: StoredShare: type: object properties: + hasShareCredential: + type: boolean + description: Whether a share credential is currently stored + hasGroupCredential: + type: boolean + description: Whether a group credential is currently stored + isValid: + type: boolean + description: Whether both credentials are present and usable together shareCredential: - type: string - description: The share credential + type: ["string", "null"] + description: Optional raw share credential (only returned in explicit debug mode) groupCredential: - type: string - description: The group credential + type: ["string", "null"] + description: Optional raw group credential (only returned in explicit debug mode) savedAt: - type: string + type: ["string", "null"] format: date-time description: When the share was saved id: @@ -2120,8 +2562,9 @@ components: type: string description: Source of the share (e.g., "environment") required: - - shareCredential - - groupCredential + - hasShareCredential + - hasGroupCredential + - isValid - savedAt - id - source @@ -2323,7 +2766,7 @@ components: type: object description: Client-supplied fields when creating or updating a NIP‑46 session properties: - client_pubkey: + pubkey: type: string description: Client public key (hex encoded) status: @@ -2338,7 +2781,7 @@ components: description: Preferred relays for the session policy: $ref: '#/components/schemas/Nip46Policy' - required: [client_pubkey] + required: [pubkey] Nip46Session: type: object @@ -2385,6 +2828,93 @@ components: type: array items: { type: string } + Nip46Request: + type: object + description: Persisted NIP‑46 request payload and lifecycle status + properties: + id: { type: string } + user_id: + oneOf: + - type: integer + - type: string + session_pubkey: { type: string } + method: { type: string } + params: { type: string } + status: + type: string + enum: [pending, approved, denied, completed, failed, expired] + result: + type: [string, 'null'] + error: + type: [string, 'null'] + created_at: { type: string } + updated_at: { type: string } + expires_at: + type: [string, 'null'] + required: [id, user_id, session_pubkey, method, params, status, created_at, updated_at] + + Nip46RequestActionInput: + type: object + properties: + id: + type: string + description: Persisted request identifier + action: + type: string + enum: [approve, deny, fail, complete] + policy: + $ref: '#/components/schemas/Nip46Policy' + result: + type: [string, 'null'] + error: + type: [string, 'null'] + required: [id, action] + + Nip46RequestDeleteInput: + type: object + properties: + id: + type: string + required: [id] + + Nip46ConnectInput: + type: object + properties: + uri: + type: string + description: nostrconnect:// invite URI + required: [uri] + + Nip46ConnectResponse: + type: object + properties: + session: + $ref: '#/components/schemas/Nip46Session' + + Nip46RelaysInput: + type: object + properties: + relays: + type: ['array', 'null'] + items: { type: string } + required: [relays] + + Nip46TransportInput: + type: object + properties: + transport_sk: + type: string + description: 32-byte transport secret key encoded as hex + required: [transport_sk] + + Nip46TransportPublic: + type: object + properties: + transport_pubkey: + type: string + description: Transport public key derived from the stored NIP-46 secret + required: [transport_pubkey] + AdminApiKey: type: object description: Metadata about an API key managed in database mode @@ -2631,7 +3161,7 @@ components: type: string example: error: "Authentication required" - authMethods: ["api-key", "bearer", "basic-auth", "session"] + authMethods: ["api-key", "basic-auth", "session"] InternalServerError: description: Internal server error @@ -2720,4 +3250,8 @@ tags: - name: Onboarding description: First-run onboarding and admin validation (database mode) - name: Event Log - description: Persisted UI event log endpoints (database mode only) \ No newline at end of file + description: Persisted UI event log endpoints (database mode only) + - name: Crypto + description: Cryptographic operations and key management + - name: NIP‑46 + description: NIP‑46 protocol and signing interactions diff --git a/env.example b/env.example index 03a0b80..83e02a1 100644 --- a/env.example +++ b/env.example @@ -151,6 +151,11 @@ NIP46_SESSION_RATE_LIMIT_MAX=120 # WS_MAX_CONNECTIONS_PER_IP=5 # WS_MSG_RATE=20 # WS_MSG_BURST=40 +# +# Legacy WebSocket auth compatibility for `/api/events`: +# - true (default): allow `?apiKey=` / `?sessionId=` query params on upgrades. +# - false: require headers or `Sec-WebSocket-Protocol` auth hints instead. +# ALLOW_QUERY_CREDENTIALS=true # ============================================================================= # RECOVERY RATE LIMITS (ADVANCED) @@ -252,7 +257,7 @@ NODE_ENV=development # PERSONAL (Home server - medium security) # AUTH_ENABLED=true # API_KEY=personal-server-key-2024 -# SESSION_SECRET=personal-session-secret-32chars +# SESSION_SECRET= # SESSION_TIMEOUT=7200 # RATE_LIMIT_MAX=50 @@ -261,7 +266,7 @@ NODE_ENV=development # BASIC_AUTH_USER=teamadmin # BASIC_AUTH_PASS=SecureTeamPassword123! # API_KEY=team-automation-key-64chars -# SESSION_SECRET=team-session-secret-32plus-chars +# SESSION_SECRET= # SESSION_TIMEOUT=3600 # RATE_LIMIT_MAX=100 @@ -271,7 +276,7 @@ NODE_ENV=development # BASIC_AUTH_USER=prodadmin # BASIC_AUTH_PASS=VerySecurePassword456! # API_KEY=prod-api-key-with-64-random-chars-abcdef123456789 -# SESSION_SECRET=prod-session-secret-256-bits-of-entropy-required-for-production +# SESSION_SECRET= # SESSION_TIMEOUT=1800 # RATE_LIMIT_ENABLED=true # RATE_LIMIT_WINDOW=300 diff --git a/frontend/App.tsx b/frontend/App.tsx index 8874f96..782426e 100644 --- a/frontend/App.tsx +++ b/frontend/App.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef, useMemo } from "react" +import React, { useState, useEffect, useRef, useMemo, useCallback } from "react" import Configure from "./components/Configure" import Signer from "./components/Signer" import Recover from "./components/Recover" @@ -87,18 +87,6 @@ const App: React.FC = () => { }; }, []); - // Global handler for authentication/credentials expiry from child components - useEffect(() => { - const onAuthExpired = () => { - // If already unauthenticated, ignore; otherwise trigger logout to show login screen - if (authState.isAuthenticated) { - handleLogout().catch(console.error); - } - }; - window.addEventListener('authExpired', onAuthExpired as EventListener); - return () => window.removeEventListener('authExpired', onAuthExpired as EventListener); - }, [authState.isAuthenticated]); - const initializeApp = async () => { try { // Check onboarding status first with retry logic @@ -323,7 +311,9 @@ const App: React.FC = () => { setSignerData(prev => prev ?? { share: '', groupCredential: '', name: 'Server credentials' }); } } - } catch {} + } catch (error) { + console.error('Failed to fetch /api/status while checking headless fallback state:', error); + } } } // If no saved credentials, we'll show Configure page (default state) @@ -333,7 +323,7 @@ const App: React.FC = () => { } }; - const getAuthHeaders = (): Record => { + const getAuthHeaders = useCallback((): Record => { const headers: Record = {}; // Try session-based auth first @@ -351,13 +341,13 @@ const App: React.FC = () => { } return headers; - }; + }, [authState.sessionId, authState.apiKey, authState.basicAuth]); // Memoize auth headers to prevent unnecessary re-renders in child components // Only recreate when authentication state changes const memoizedAuthHeaders = useMemo(() => { return getAuthHeaders(); - }, [authState.sessionId, authState.apiKey, authState.basicAuth]); + }, [getAuthHeaders]); const handleOnboardingComplete = () => { // After onboarding, reset state to show login @@ -401,7 +391,7 @@ const App: React.FC = () => { } }; - const handleLogout = async () => { + const handleLogout = useCallback(async () => { try { // Stop signer first await signerRef.current?.stopSigner().catch(console.error); @@ -422,7 +412,19 @@ const App: React.FC = () => { }); setSignerData(null); } - }; + }, [getAuthHeaders]); + + // Global handler for authentication/credentials expiry from child components + useEffect(() => { + const onAuthExpired = () => { + // If already unauthenticated, ignore; otherwise trigger logout to show login screen + if (authState.isAuthenticated) { + handleLogout().catch(console.error); + } + }; + window.addEventListener('authExpired', onAuthExpired as EventListener); + return () => window.removeEventListener('authExpired', onAuthExpired as EventListener); + }, [authState.isAuthenticated, handleLogout]); const checkAdmin = async (headers?: Record) => { try { diff --git a/frontend/components/ApiKeys.tsx b/frontend/components/ApiKeys.tsx index 8ac7905..a248ab5 100644 --- a/frontend/components/ApiKeys.tsx +++ b/frontend/components/ApiKeys.tsx @@ -53,6 +53,7 @@ const ApiKeys: React.FC = ({ authHeaders = {}, headlessMode = fals // Track copy timeout to avoid leaks if component unmounts before it fires const copyTimeoutRef = useRef | null>(null) + const initialAdminLoadRef = useRef(false) const combinedHeaders = useCallback( (contentType = true) => { @@ -223,9 +224,15 @@ const ApiKeys: React.FC = ({ authHeaders = {}, headlessMode = fals const revokedKeys = useMemo(() => keys.filter(key => key.revokedAt), [keys]) useEffect(() => { - if (isAdminUser) { - loadKeys().catch(err => console.error('Failed to load keys:', err)) + if (!isAdminUser) { + initialAdminLoadRef.current = false + setKeys([]) + setIssuedKey(null) + return } + if (initialAdminLoadRef.current) return + initialAdminLoadRef.current = true + loadKeys().catch(err => console.error('Failed to load keys:', err)) }, [isAdminUser, loadKeys]) if (headlessMode) { diff --git a/frontend/components/Configure.tsx b/frontend/components/Configure.tsx index 7ed4967..bdbdd2f 100644 --- a/frontend/components/Configure.tsx +++ b/frontend/components/Configure.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from "react" +import React, { useState, useEffect, useRef, useId, useCallback } from "react" import { Button } from "./ui/button" import { Input } from "./ui/input" import { Tooltip } from "./ui/tooltip" @@ -41,11 +41,17 @@ const defaultAdvancedSettings: AdvancedSettingsState = { interface ConfigureProps { onKeysetCreated: (data: { groupCredential: string; shareCredentials: string[]; name: string }) => void; - onCredentialsSaved?: () => void; + onCredentialsSaved?: () => void | Promise; onBack?: () => void; authHeaders?: Record; } +function isAbortError(err: unknown): boolean { + if (err instanceof DOMException) return err.name === 'AbortError'; + if (err instanceof Error) return err.name === 'AbortError'; + return false; +} + const Configure: React.FC = ({ onKeysetCreated, onCredentialsSaved, onBack, authHeaders = {} }) => { const [keysetGenerated, setKeysetGenerated] = useState<{ success: boolean; location: string | React.ReactNode | null }>({ success: false, location: null }); const [isGenerating, setIsGenerating] = useState(false); @@ -80,6 +86,10 @@ const Configure: React.FC = ({ onKeysetCreated, onCredentialsSav const [isLoadingConfig, setIsLoadingConfig] = useState(true); const [advancedError, setAdvancedError] = useState(undefined); const loadAdvancedSettingsRef = useRef(null); + const clearConfirmButtonRef = useRef(null); + const clearCancelButtonRef = useRef(null); + const clearTriggerButtonRef = useRef(null); + const clearDialogTitleId = useId(); /** * Convert an environment value of unknown type to a string suitable for input fields. @@ -106,7 +116,7 @@ const Configure: React.FC = ({ onKeysetCreated, onCredentialsSav } // Function to load advanced settings from env - const loadAdvancedSettings = async () => { + const loadAdvancedSettings = useCallback(async (headlessMode: boolean) => { // Load advanced settings in both headless and database modes if (loadAdvancedSettingsRef.current) { try { loadAdvancedSettingsRef.current.abort() } catch {} @@ -144,7 +154,7 @@ const Configure: React.FC = ({ onKeysetCreated, onCredentialsSav // Only include RELAYS in headless mode (server-wide configuration) // In database mode, relays are managed per-user through the Signer component - if (isHeadlessMode) { + if (headlessMode) { newSettings.RELAYS = coerceEnvValueToString(envVars.RELAYS, '["wss://relay.primal.net"]'); } setAdvancedSettings(newSettings); @@ -154,7 +164,7 @@ const Configure: React.FC = ({ onKeysetCreated, onCredentialsSav setAdvancedError(err.error || `Failed to load settings: ${envResponse.status}`); } } catch (error) { - if ((error as any)?.name === 'AbortError') { + if (isAbortError(error)) { return; // newer request superseded this one } console.error('Error loading advanced settings:', error); @@ -162,7 +172,7 @@ const Configure: React.FC = ({ onKeysetCreated, onCredentialsSav } finally { setIsLoadingAdvanced(false); } - }; + }, [authHeaders]); const handleRevealAdminSecret = async () => { if (!canRevealAdminSecret) return; @@ -261,7 +271,7 @@ const Configure: React.FC = ({ onKeysetCreated, onCredentialsSav } // Load advanced settings in both modes - await loadAdvancedSettings(); + await loadAdvancedSettings(headlessMode); // Store existing relays (if any) if (savedRelays && Array.isArray(savedRelays) && savedRelays.length > 0) { @@ -305,15 +315,15 @@ const Configure: React.FC = ({ onKeysetCreated, onCredentialsSav } }; loadExistingData(); - }, []); + }, [authHeaders, loadAdvancedSettings]); // Reload advanced settings when window regains focus or when showAdvanced changes // This ensures relay changes from Signer.tsx are reflected here useEffect(() => { if (!showAdvanced) return; - const handleFocus = () => { loadAdvancedSettings() }; - loadAdvancedSettings(); + const handleFocus = () => { void loadAdvancedSettings(isHeadlessMode) }; + void loadAdvancedSettings(isHeadlessMode); window.addEventListener('focus', handleFocus); return () => { window.removeEventListener('focus', handleFocus); @@ -322,7 +332,41 @@ const Configure: React.FC = ({ onKeysetCreated, onCredentialsSav loadAdvancedSettingsRef.current = null } }; - }, [showAdvanced]); + }, [showAdvanced, isHeadlessMode, loadAdvancedSettings]); + + useEffect(() => { + if (!showClearConfirm) return; + const previousFocus = (document.activeElement as HTMLElement | null) ?? clearTriggerButtonRef.current; + const focusPrimary = () => clearCancelButtonRef.current?.focus(); + const raf = window.requestAnimationFrame(focusPrimary); + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + event.preventDefault(); + setShowClearConfirm(false); + return; + } + if (event.key !== 'Tab') return; + const focusables = [clearCancelButtonRef.current, clearConfirmButtonRef.current].filter(Boolean) as HTMLElement[]; + if (focusables.length === 0) return; + const first = focusables[0]; + const last = focusables[focusables.length - 1]; + if (event.shiftKey && document.activeElement === first) { + event.preventDefault(); + last.focus(); + } else if (!event.shiftKey && document.activeElement === last) { + event.preventDefault(); + first.focus(); + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => { + window.cancelAnimationFrame(raf); + window.removeEventListener('keydown', handleKeyDown); + previousFocus?.focus(); + }; + }, [showClearConfirm]); const handleNameChange = (value: string) => { setKeysetName(value); @@ -525,7 +569,7 @@ const Configure: React.FC = ({ onKeysetCreated, onCredentialsSav // Notify parent component to refresh views if (onCredentialsSaved) { - onCredentialsSaved(); + await onCredentialsSaved(); } // Clear the form @@ -556,10 +600,43 @@ const Configure: React.FC = ({ onKeysetCreated, onCredentialsSav setIsGenerating(true); try { + const parseRelayList = (raw: string): string[] | null => { + try { + const parsed: unknown = JSON.parse(raw); + if (!Array.isArray(parsed)) return null; + const relays = parsed + .filter((relay): relay is string => typeof relay === 'string') + .map((relay) => relay.trim()) + .filter((relay) => relay.length > 0); + return relays.length > 0 ? relays : null; + } catch { + return null; + } + }; + + const resolveRelaysToSave = (): string[] => { + if (!isHeadlessMode) { + if (Array.isArray(existingRelays) && existingRelays.length > 0) { + return existingRelays.map((relay) => relay.trim()).filter((relay) => relay.length > 0); + } + return ["wss://relay.primal.net"]; + } + + if (typeof advancedSettings.RELAYS === 'string' && advancedSettings.RELAYS.trim().length > 0) { + const parsedRelays = parseRelayList(advancedSettings.RELAYS); + if (parsedRelays) return parsedRelays; + } + if (Array.isArray(existingRelays) && existingRelays.length > 0) { + return existingRelays.map((relay) => relay.trim()).filter((relay) => relay.length > 0); + } + return ["wss://relay.primal.net"]; + }; + // Save credentials based on mode if (isHeadlessMode) { // Headless mode - save to env - await fetch('/api/env', { + const relaysToSave = resolveRelaysToSave(); + const response = await fetch('/api/env', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -569,16 +646,20 @@ const Configure: React.FC = ({ onKeysetCreated, onCredentialsSav SHARE_CRED: share, GROUP_CRED: groupCredential, GROUP_NAME: keysetName, - // Ensure we have at least one valid relay for the server to use - RELAYS: JSON.stringify(["wss://relay.primal.net"]) + RELAYS: JSON.stringify(relaysToSave) }) }); + if (!response.ok) { + const detail = await response.text().catch(() => ''); + throw new Error(detail || `Failed to save headless credentials (${response.status})`); + } + setExistingRelays(relaysToSave); } else { // Database mode - save to user credentials // Preserve existing relays or use default if none exist - const relaysToSave = existingRelays || ["wss://relay.primal.net"]; + const relaysToSave = resolveRelaysToSave(); - await fetch('/api/user/credentials', { + const response = await fetch('/api/user/credentials', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -591,6 +672,11 @@ const Configure: React.FC = ({ onKeysetCreated, onCredentialsSav relays: relaysToSave }) }); + if (!response.ok) { + const detail = await response.text().catch(() => ''); + throw new Error(detail || `Failed to save credentials (${response.status})`); + } + setExistingRelays(relaysToSave); } setHasExistingCredentials(true); @@ -670,6 +756,7 @@ const Configure: React.FC = ({ onKeysetCreated, onCredentialsSav {hasExistingCredentials && ( - {transportKey && ( + {transportPubkey && (
- Transport + Transport pubkey - {showFullKeys ? transportKey : truncate(transportKey, 8)} + {showFullKeys ? transportPubkey : truncate(transportPubkey, 8)} -
diff --git a/frontend/components/Onboarding.tsx b/frontend/components/Onboarding.tsx index 58123fc..803bbae 100644 --- a/frontend/components/Onboarding.tsx +++ b/frontend/components/Onboarding.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef, useCallback } from 'react'; import { Button } from './ui/button'; import { Input } from './ui/input'; import { Alert } from './ui/alert'; @@ -15,6 +15,10 @@ interface OnboardingProps { initialSkipAdminValidation?: boolean; } +interface OnboardingValidationResponse { + error?: string; +} + const Onboarding: React.FC = ({ onComplete, initialSkipAdminValidation = false }) => { // Always start with instructions step regardless of skip admin validation const [step, setStep] = useState<'instructions' | 'admin' | 'setup' | 'complete'>('instructions'); @@ -35,7 +39,7 @@ const Onboarding: React.FC = ({ onComplete, initialSkipAdminVal // Match server-side password policy exactly (see src/routes/onboarding.ts) // - Minimum 8 characters // - At least one uppercase letter, one lowercase letter, one digit, and one special character - const PASSWORD_REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/; + const PASSWORD_REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9\s]).{8,}$/; const parseRetryAfter = (retryAfterHeader: string | null): number | null => { if (!retryAfterHeader) return null; @@ -59,17 +63,6 @@ const Onboarding: React.FC = ({ onComplete, initialSkipAdminVal } }, [initialSkipAdminValidation]); - useEffect(() => { - checkStatus(); - - // Cleanup timeout on unmount - return () => { - if (timeoutRef.current !== null) { - clearTimeout(timeoutRef.current); - } - }; - }, []); - // Sync adminSecret with ref to ensure it's not lost during re-renders useEffect(() => { adminSecretRef.current = adminSecret; @@ -83,7 +76,7 @@ const Onboarding: React.FC = ({ onComplete, initialSkipAdminVal } }, [skipAdminValidation, step]); - const checkStatus = async () => { + const checkStatus = useCallback(async () => { setIsCheckingStatus(true); setNetworkError(''); @@ -110,7 +103,18 @@ const Onboarding: React.FC = ({ onComplete, initialSkipAdminVal } finally { setIsCheckingStatus(false); } - }; + }, []); + + useEffect(() => { + void checkStatus(); + + // Cleanup timeout on unmount + return () => { + if (timeoutRef.current !== null) { + clearTimeout(timeoutRef.current); + } + }; + }, [checkStatus]); const validateAdminSecret = async () => { if (!adminSecret.trim()) { @@ -144,7 +148,7 @@ const Onboarding: React.FC = ({ onComplete, initialSkipAdminVal } // Try to parse JSON response, but handle non-JSON gracefully - let data: any = {}; + let data: OnboardingValidationResponse = {}; try { data = await response.json(); } catch (jsonError) { @@ -168,12 +172,11 @@ const Onboarding: React.FC = ({ onComplete, initialSkipAdminVal }; const createUser = async () => { - // Trim username and password for consistent validation and submission + // Trim username only; keep password exactly as entered. const trimmedUsername = username.trim(); - const trimmedPassword = password.trim(); // Validate inputs - if (!trimmedUsername || !trimmedPassword) { + if (!trimmedUsername || !password) { setError('Username and password are required'); return; } @@ -183,18 +186,18 @@ const Onboarding: React.FC = ({ onComplete, initialSkipAdminVal return; } - if (trimmedPassword.length < 8) { + if (password.length < 8) { setError('Password must be at least 8 characters long'); return; } // Enforce same password rules as server (uppercase, lowercase, digit, special) - if (!PASSWORD_REGEX.test(trimmedPassword)) { + if (!PASSWORD_REGEX.test(password)) { setError('Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character'); return; } - if (trimmedPassword !== confirmPassword.trim()) { + if (password !== confirmPassword) { setError('Passwords do not match'); return; } @@ -224,7 +227,7 @@ const Onboarding: React.FC = ({ onComplete, initialSkipAdminVal headers, body: JSON.stringify({ username: trimmedUsername, - password: trimmedPassword, + password, }), }); @@ -557,7 +560,6 @@ const Onboarding: React.FC = ({ onComplete, initialSkipAdminVal onChange={(e) => setPassword(e.target.value)} disabled={isLoading} className="bg-gray-800/50 border-gray-700/50 text-blue-300 placeholder:text-gray-500" - pattern={PASSWORD_REGEX.source} title="Minimum 8 characters, with at least one uppercase letter, one lowercase letter, one number, and one special character" />
diff --git a/frontend/components/Recover.tsx b/frontend/components/Recover.tsx index f61ca53..1e02daa 100644 --- a/frontend/components/Recover.tsx +++ b/frontend/components/Recover.tsx @@ -88,6 +88,7 @@ const Recover: React.FC = ({ // Add a timeout ref to clear the autofilled indicator const autofilledTimeoutRef = useRef(null); + const hasAutoDetectedRef = useRef(false); const [sharesFormValid, setSharesFormValid] = useState(false); @@ -97,6 +98,9 @@ const Recover: React.FC = ({ message: null }); const [isProcessing, setIsProcessing] = useState(false); + const [recoveredNsec, setRecoveredNsec] = useState(null); + const [showRecoveredNsec, setShowRecoveredNsec] = useState(false); + const [copyStatus, setCopyStatus] = useState<'idle' | 'copied' | 'error'>('idle'); // Add state for the dynamic threshold const [currentThreshold, setCurrentThreshold] = useState(defaultThreshold); @@ -111,14 +115,18 @@ const Recover: React.FC = ({ // Auto-detect shares from storage useEffect(() => { + if (hasAutoDetectedRef.current) return; + const autoDetectShares = async () => { // If we already have initial data, don't auto-detect if (initialShare || initialGroupCredential) { + hasAutoDetectedRef.current = true; return; } // If we already have user input, don't override if (sharesInputs.some(s => s.trim()) || groupCredential.trim()) { + hasAutoDetectedRef.current = true; return; } @@ -135,8 +143,13 @@ const Recover: React.FC = ({ } } - // If no localStorage data, try server API + const hasAuthHeaders = Object.keys(authHeaders).length > 0; + + // If no localStorage data, try server API once auth headers are available if (!shares || shares.length === 0) { + if (!hasAuthHeaders) { + return; + } try { const response = await fetch('/api/env/shares', { headers: authHeaders @@ -204,13 +217,14 @@ const Recover: React.FC = ({ } } } + hasAutoDetectedRef.current = true; } catch (error) { console.warn('Auto-detection failed:', error); } }; - autoDetectShares(); - }, [initialShare, initialGroupCredential, defaultThreshold, defaultTotalShares, sharesInputs, groupCredential]); + void autoDetectShares(); + }, [initialShare, initialGroupCredential, defaultThreshold, defaultTotalShares, authHeaders]); // Handle initialShare and initialGroupCredential useEffect(() => { @@ -322,7 +336,7 @@ const Recover: React.FC = ({ // Additional structure validation if (typeof decodedGroup.threshold !== 'number' || - typeof decodedGroup.group_pk !== 'string' || + !(typeof decodedGroup.group_pk === 'string' || decodedGroup.group_pk instanceof Uint8Array) || !Array.isArray(decodedGroup.commits) || decodedGroup.commits.length === 0) { setIsGroupValid(false); @@ -373,6 +387,9 @@ const Recover: React.FC = ({ if (!sharesFormValid) return; + setRecoveredNsec(null); + setShowRecoveredNsec(false); + setCopyStatus('idle'); setIsProcessing(true); try { // Get valid share credentials @@ -399,33 +416,36 @@ const Recover: React.FC = ({ } if (result.success) { + if (typeof result.nsec !== 'string' || result.nsec.length === 0) { + throw new Error('Recovery completed without an nsec payload'); + } + setRecoveredNsec(result.nsec); setResult({ success: true, - message: ( -
-
- Successfully recovered NSEC using {result.details.sharesUsed} shares -
-
-
-
Recovered NSEC:
-
- {result.nsec} -
+ message: `Successfully recovered NSEC using ${result.details.sharesUsed} shares` + }); + if (Array.isArray(result.details.invalidShares) && result.details.invalidShares.length > 0) { + setResult({ + success: true, + message: ( +
+
+ Successfully recovered NSEC using {result.details.sharesUsed} shares +
+
+ Note: {result.details.invalidShares.length} invalid shares were ignored
- {result.details.invalidShares && ( -
- Note: {result.details.invalidShares.length} invalid shares were ignored -
- )}
-
- ) - }); + ) + }); + } } else { throw new Error(result.error || 'Recovery failed'); } } catch (error) { + setRecoveredNsec(null); + setShowRecoveredNsec(false); + setCopyStatus('idle'); setResult({ success: false, message: `Error recovering NSEC: ${error instanceof Error ? error.message : 'Unknown error'}` @@ -435,6 +455,17 @@ const Recover: React.FC = ({ } }; + const handleCopyRecoveredNsec = async () => { + if (!recoveredNsec) return; + try { + await navigator.clipboard.writeText(recoveredNsec); + setCopyStatus('copied'); + } catch (error) { + console.error('Failed to copy recovered NSEC:', error); + setCopyStatus('error'); + } + }; + return (
@@ -537,6 +568,32 @@ const Recover: React.FC = ({ result.success ? 'bg-green-900/30 text-green-200' : 'bg-red-900/30 text-red-200' }`}> {result.message} + {result.success && recoveredNsec && ( +
+
Recovered NSEC:
+
+ {showRecoveredNsec ? recoveredNsec : '••••••••••••••••••••••••••••••••'} +
+
+ + + {copyStatus === 'copied' && Copied} + {copyStatus === 'error' && Copy failed} +
+
+ )}
)}
diff --git a/frontend/components/Signer.tsx b/frontend/components/Signer.tsx index e219b56..0067841 100644 --- a/frontend/components/Signer.tsx +++ b/frontend/components/Signer.tsx @@ -49,6 +49,14 @@ const DEFAULT_RELAY = "wss://relay.primal.net"; const MAX_EVENT_LOG_IN_MEMORY = 10000; const AUTO_EXPAND_EVENT_TYPES: string[] = ['sign']; +function areRelayListsEqual(left: string[], right: string[]): boolean { + if (left.length !== right.length) return false; + for (let i = 0; i < left.length; i += 1) { + if (left[i] !== right[i]) return false; + } + return true; +} + const sanitizeLogEntry = (entry: unknown): LogEntryData | null => { if (!entry || typeof entry !== "object") return null; const log = entry as Partial; @@ -182,11 +190,14 @@ const Signer = forwardRef(({ initialData, authHeaders const [loadingOlder, setLoadingOlder] = useState(false); const [downloadingLogs, setDownloadingLogs] = useState(false); const [realSelfPubkey, setRealSelfPubkey] = useState(null); + const relayMutationIdRef = useRef(0); + const relayUrlsRef = useRef([DEFAULT_RELAY]); // Reference for compatibility with parent component const nodeRef = useRef(null); const authHeadersRef = useRef(authHeaders); useEffect(() => { authHeadersRef.current = authHeaders; }, [authHeaders]); + useEffect(() => { relayUrlsRef.current = relayUrls; }, [relayUrls]); // Expose methods to parent components through ref useImperativeHandle(ref, () => ({ @@ -274,7 +285,7 @@ const Signer = forwardRef(({ initialData, authHeaders const checkServerStatus = useCallback(async () => { try { const response = await fetch('/api/status', { - headers: authHeaders + headers: authHeadersRef.current }); const status = await response.json(); setServerStatus(status); @@ -331,8 +342,8 @@ const Signer = forwardRef(({ initialData, authHeaders const fetchSelfPubkey = async () => { try { const response = await fetch('/api/peers/self', { - headers: authHeaders - }); + headers: authHeadersRef.current + }); if (response.ok) { const data = await response.json(); setRealSelfPubkey(data.pubkey); @@ -382,27 +393,19 @@ const Signer = forwardRef(({ initialData, authHeaders const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; let wsUrl = `${protocol}//${window.location.host}/api/events`; - // Add authentication parameters for WebSocket connection - // Since WebSocket doesn't support custom headers during upgrade, - // we need to pass auth info via URL parameters - const params = new URLSearchParams(); - - // Check if we have auth headers and convert them to URL params + // Avoid exposing long-lived credentials in URL query params. + // Prefer WebSocket subprotocol auth hints supported by the backend. + const protocols: string[] = []; const currentAuth = authHeadersRef.current; if (currentAuth['X-API-Key']) { - params.set('apiKey', currentAuth['X-API-Key']); + protocols.push(`api-key.${currentAuth['X-API-Key']}`); } else if (currentAuth['X-Session-ID']) { - params.set('sessionId', currentAuth['X-Session-ID']); + protocols.push(`session.${currentAuth['X-Session-ID']}`); } else if (currentAuth['Authorization'] && currentAuth['Authorization'].startsWith('Basic ')) { - // For basic auth, we'll rely on cookies or handle it server-side - // The server should accept the connection if the user is already authenticated - } - - if (params.toString()) { - wsUrl += '?' + params.toString(); + // For basic auth, rely on existing browser credentials/cookies. } - - ws = new WebSocket(wsUrl); + + ws = protocols.length > 0 ? new WebSocket(wsUrl, protocols) : new WebSocket(wsUrl); ws.onopen = () => { isConnecting = false; @@ -687,7 +690,7 @@ const Signer = forwardRef(({ initialData, authHeaders setRelayUrls(relays); } else { // If no valid relays found, save default relays - saveRelaysToEnv([DEFAULT_RELAY]); + void saveRelaysToEnv([DEFAULT_RELAY]); } } catch (error) { console.warn('Failed to parse RELAYS from env:', error); @@ -696,12 +699,12 @@ const Signer = forwardRef(({ initialData, authHeaders setRelayUrls([envVars.RELAYS]); } else { // Save default relays if parsing failed - saveRelaysToEnv([DEFAULT_RELAY]); + void saveRelaysToEnv([DEFAULT_RELAY]); } } } else { // If no RELAYS environment variable exists, save the default - saveRelaysToEnv([DEFAULT_RELAY]); + void saveRelaysToEnv([DEFAULT_RELAY]); } } catch (error) { console.error('Error fetching environment variables:', error); @@ -939,9 +942,9 @@ const Signer = forwardRef(({ initialData, authHeaders }; // Save relay URLs to user credentials (database mode) - const saveRelaysToUserCredentials = async (relays: string[]) => { + const saveRelaysToUserCredentials = async (relays: string[]): Promise => { try { - await fetch('/api/user/relays', { + const response = await fetch('/api/user/relays', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -951,15 +954,28 @@ const Signer = forwardRef(({ initialData, authHeaders relays: relays }) }); + if (!response.ok) { + const detail = await response.text().catch(() => ''); + console.error('[Signer] Failed to save relays to user credentials:', { + status: response.status, + detail + }); + setCredentialSaveError('Unable to save relays. Please try again.'); + return false; + } + setCredentialSaveError(null); + return true; } catch (error) { console.error('Error saving relays to user credentials:', error); + setCredentialSaveError('Unable to save relays. Please try again.'); + return false; } }; // Save relay URLs to server .env file (headless mode) - const saveRelaysToServerEnv = async (relays: string[]) => { + const saveRelaysToServerEnv = async (relays: string[]): Promise => { try { - await fetch('/api/env', { + const response = await fetch('/api/env', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -969,34 +985,75 @@ const Signer = forwardRef(({ initialData, authHeaders RELAYS: JSON.stringify(relays) }) }); + if (!response.ok) { + const detail = await response.text().catch(() => ''); + console.error('[Signer] Failed to save relays to env:', { + status: response.status, + detail + }); + setCredentialSaveError('Unable to save relays. Please try again.'); + return false; + } + setCredentialSaveError(null); + return true; } catch (error) { console.error('Error saving relays to env:', error); + setCredentialSaveError('Unable to save relays. Please try again.'); + return false; } }; // Save relay URLs (routes to appropriate endpoint based on mode) - const saveRelaysToEnv = async (relays: string[]) => { + const saveRelaysToEnv = async (relays: string[]): Promise => { if (isDatabaseMode()) { - await saveRelaysToUserCredentials(relays); + return await saveRelaysToUserCredentials(relays); } else { - await saveRelaysToServerEnv(relays); + return await saveRelaysToServerEnv(relays); } }; - const handleAddRelay = () => { - const isAlreadyAdded = relayUrls.indexOf(newRelayUrl) !== -1; - if (newRelayUrl && !isAlreadyAdded) { - const newRelays = [...relayUrls, newRelayUrl]; - setRelayUrls(newRelays); - setNewRelayUrl(""); - saveRelaysToEnv(newRelays); + const handleAddRelay = async () => { + const relayToAdd = newRelayUrl.trim(); + const currentRelays = relayUrlsRef.current; + const isAlreadyAdded = currentRelays.indexOf(relayToAdd) !== -1; + if (!relayToAdd || isAlreadyAdded) return; + + const previousRelays = currentRelays; + const newRelays = [...currentRelays, relayToAdd]; + const mutationId = ++relayMutationIdRef.current; + relayUrlsRef.current = newRelays; + setRelayUrls(newRelays); + setNewRelayUrl(""); + + const saved = await saveRelaysToEnv(newRelays); + if (!saved) { + const isLatestMutation = mutationId === relayMutationIdRef.current; + const relaysStillMatchFailedAttempt = areRelayListsEqual(relayUrlsRef.current, newRelays); + if (isLatestMutation && relaysStillMatchFailedAttempt) { + relayUrlsRef.current = previousRelays; + setRelayUrls(previousRelays); + setNewRelayUrl(relayToAdd); + } } }; - const handleRemoveRelay = (urlToRemove: string) => { - const newRelays = relayUrls.filter(url => url !== urlToRemove); + const handleRemoveRelay = async (urlToRemove: string) => { + const currentRelays = relayUrlsRef.current; + const newRelays = currentRelays.filter(url => url !== urlToRemove); + if (newRelays.length === currentRelays.length) return; + const previousRelays = currentRelays; + const mutationId = ++relayMutationIdRef.current; + relayUrlsRef.current = newRelays; setRelayUrls(newRelays); - saveRelaysToEnv(newRelays); + const saved = await saveRelaysToEnv(newRelays); + if (!saved) { + const isLatestMutation = mutationId === relayMutationIdRef.current; + const relaysStillMatchFailedAttempt = areRelayListsEqual(relayUrlsRef.current, newRelays); + if (isLatestMutation && relaysStillMatchFailedAttempt) { + relayUrlsRef.current = previousRelays; + setRelayUrls(previousRelays); + } + } }; // Expose the stopSigner method for compatibility (server-managed, no action needed) @@ -1285,7 +1342,7 @@ const Signer = forwardRef(({ initialData, authHeaders className="bg-gray-800/50 border-gray-700/50 text-blue-300 py-2 text-sm w-full" />
@@ -72,4 +76,4 @@ const Collapsible: React.FC = ({ ); }; -export { Collapsible }; \ No newline at end of file +export { Collapsible }; diff --git a/frontend/components/ui/event-log.tsx b/frontend/components/ui/event-log.tsx index 07af788..9401c1c 100644 --- a/frontend/components/ui/event-log.tsx +++ b/frontend/components/ui/event-log.tsx @@ -37,7 +37,7 @@ export const EventLog = memo(({ }: EventLogProps) => { const logEndRef = useRef(null); const containerRef = useRef(null); - const [isExpanded, setIsExpanded] = useState(false); + const [isExpanded, setIsExpanded] = useState(hideHeader); const [activeFilters, setActiveFilters] = useState>(new Set()); const [showFilters, setShowFilters] = useState(false); const previousLogIdsRef = useRef(null); @@ -56,6 +56,12 @@ export const EventLog = memo(({ return logs.filter(log => activeFilters.has(log.type)); }, [logs, activeFilters]); + useEffect(() => { + if (hideHeader) { + setIsExpanded(true); + } + }, [hideHeader]); + const scrollToBottom = useCallback(() => { if (!containerRef.current) return; diff --git a/frontend/components/ui/icon-button.tsx b/frontend/components/ui/icon-button.tsx index 304939a..0b460b4 100644 --- a/frontend/components/ui/icon-button.tsx +++ b/frontend/components/ui/icon-button.tsx @@ -38,8 +38,6 @@ const IconButton = React.forwardRef( ({ className, variant, size, icon, tooltip, ...props }, ref) => { return (
)} -
+ {!title && showCloseButton && ( +
+ +
+ )} +
{children}
@@ -82,4 +116,4 @@ const Modal: React.FC = ({ ); }; -export { Modal }; \ No newline at end of file +export { Modal }; diff --git a/frontend/components/ui/peer-list.tsx b/frontend/components/ui/peer-list.tsx index 1ccd38f..cbd891f 100644 --- a/frontend/components/ui/peer-list.tsx +++ b/frontend/components/ui/peer-list.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; +import React, { useState, useEffect, useCallback, useMemo, useRef, useId } from 'react'; import { Button } from './button'; import { IconButton } from './icon-button'; import { Badge, type BadgeProps } from './badge'; @@ -150,8 +150,8 @@ const derivePolicyState = ( }; const hasCustomPolicy = (policy: PeerPolicy): boolean => { - const sendOverride = typeof policy.allowSend === 'boolean' && policy.allowSend === false; - const receiveOverride = typeof policy.allowReceive === 'boolean' && policy.allowReceive === false; + const sendOverride = typeof policy.allowSend === 'boolean'; + const receiveOverride = typeof policy.allowReceive === 'boolean'; return sendOverride || receiveOverride; }; @@ -166,6 +166,7 @@ const PeerList: React.FC = ({ defaultExpanded = false }) => { const [isExpanded, setIsExpanded] = useState(defaultExpanded); + const [shouldRenderContent, setShouldRenderContent] = useState(defaultExpanded); const [peers, setPeers] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); @@ -177,6 +178,8 @@ const PeerList: React.FC = ({ const [policySavingPeers, setPolicySavingPeers] = useState>(new Set()); const [policyPeerErrors, setPolicyPeerErrors] = useState>(new Map()); const hasUserToggledRef = useRef(false); + const panelRef = useRef(null); + const panelId = useId(); useEffect(() => { if (defaultExpanded && !hasUserToggledRef.current) { @@ -184,6 +187,26 @@ const PeerList: React.FC = ({ } }, [defaultExpanded]); + useEffect(() => { + if (isExpanded) { + setShouldRenderContent(true); + } + }, [isExpanded]); + + useEffect(() => { + const panel = panelRef.current; + if (!panel) return; + if (isExpanded) panel.removeAttribute('inert'); + else panel.setAttribute('inert', ''); + }, [isExpanded]); + + const handleCollapseTransitionEnd = useCallback((event: React.TransitionEvent) => { + if (event.target !== event.currentTarget) return; + if (!isExpanded) { + setShouldRenderContent(false); + } + }, [isExpanded]); + const setPolicyBusyState = useCallback((key: string, busy: boolean) => { setPolicySavingPeers(prev => { const next = new Set(prev); @@ -307,7 +330,7 @@ const PeerList: React.FC = ({ // Perform initial ping sweep setIsInitialPingSweep(true); try { - await fetch('/api/peers/ping', { + const pingResponse = await fetch('/api/peers/ping', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -315,9 +338,13 @@ const PeerList: React.FC = ({ }, body: JSON.stringify({ target: 'all' }) }); - - // Refresh peer list after ping sweep - await fetchPeers(); + if (!pingResponse.ok) { + const detail = await pingResponse.text().catch(() => '(unreadable)'); + console.debug(`[PeerList] Initial ping sweep failed (${pingResponse.status}): ${detail}`); + } else { + // Refresh peer list after ping sweep + await fetchPeers(); + } } catch (pingError) { console.debug('Initial ping sweep failed:', pingError); // Don't set error state for ping failures @@ -344,40 +371,50 @@ const PeerList: React.FC = ({ return () => { isActive = false; }; - }, [isSignerRunning, groupCredential, shareCredential, disabled, fetchSelfPubkey, fetchPeers]); + }, [isSignerRunning, groupCredential, shareCredential, disabled, fetchSelfPubkey, fetchPeers, authHeaders]); // Unified handler for peer status and ping updates - const handlePeerUpdate = (event: CustomEvent) => { + const handlePeerUpdate = useCallback((event: CustomEvent) => { const { pubkey, status } = event.detail; setPeers(prev => { const updated = prev.map(peer => { // Try exact match first if (peer.pubkey === pubkey) { + const hasLatency = status.latency !== undefined && status.latency !== null; + const parsedLatency = Number(status.latency); + const latency = hasLatency && Number.isFinite(parsedLatency) ? parsedLatency : peer.latency; + const parsedLastSeen = parseDate(status.lastSeen); + const parsedLastPingAttempt = parseDate(status.lastPingAttempt); return { ...peer, online: Boolean(status.online), - lastSeen: parseDate(status.lastSeen) ?? peer.lastSeen, - latency: status.latency ? Number(status.latency) : peer.latency, - lastPingAttempt: parseDate(status.lastPingAttempt) ?? peer.lastPingAttempt + lastSeen: parsedLastSeen ?? peer.lastSeen, + latency, + lastPingAttempt: parsedLastPingAttempt ?? peer.lastPingAttempt } as PeerStatus; } - // Try match without 02 prefix - const peerWithout02 = peer.pubkey.startsWith('02') ? peer.pubkey.slice(2) : peer.pubkey; - const pingWithout02 = pubkey.startsWith('02') ? pubkey.slice(2) : pubkey; - if (peerWithout02 === pingWithout02) { + // Try match with compressed-prefix normalization (02/03) + const peerNormalized = toPolicyKey(peer.pubkey); + const pingNormalized = toPolicyKey(pubkey); + if (peerNormalized !== '' && peerNormalized === pingNormalized) { + const hasLatency = status.latency !== undefined && status.latency !== null; + const parsedLatency = Number(status.latency); + const latency = hasLatency && Number.isFinite(parsedLatency) ? parsedLatency : peer.latency; + const parsedLastSeen = parseDate(status.lastSeen); + const parsedLastPingAttempt = parseDate(status.lastPingAttempt); return { ...peer, online: Boolean(status.online), - lastSeen: parseDate(status.lastSeen) ?? peer.lastSeen, - latency: status.latency ? Number(status.latency) : peer.latency, - lastPingAttempt: parseDate(status.lastPingAttempt) ?? peer.lastPingAttempt + lastSeen: parsedLastSeen ?? peer.lastSeen, + latency, + lastPingAttempt: parsedLastPingAttempt ?? peer.lastPingAttempt } as PeerStatus; } return peer; }); return updated; }); - }; + }, []); // Listen for peer status and ping updates via custom events from the main SSE connection useEffect(() => { @@ -390,7 +427,7 @@ const PeerList: React.FC = ({ window.removeEventListener('peerStatusUpdate', handlePeerUpdate as EventListener); window.removeEventListener('peerPingUpdate', handlePeerUpdate as EventListener); }; - }, [isSignerRunning]); + }, [isSignerRunning, handlePeerUpdate]); // Ping individual peer const handlePingPeer = useCallback(async (peerPubkey: string) => { @@ -412,15 +449,20 @@ const PeerList: React.FC = ({ const result = await response.json(); if (result.status) { + const hasLatency = result.status.latency !== undefined && result.status.latency !== null; + const parsedLatency = Number(result.status.latency); + const latency = hasLatency && Number.isFinite(parsedLatency) ? parsedLatency : null; + const parsedLastSeen = parseDate(result.status.lastSeen); + const parsedLastPingAttempt = parseDate(result.status.lastPingAttempt); // Update peer status immediately setPeers(prev => prev.map(peer => peer.pubkey === peerPubkey ? { ...peer, online: Boolean(result.status.online), - lastSeen: parseDate(result.status.lastSeen) ?? peer.lastSeen, - latency: result.status.latency ? Number(result.status.latency) : peer.latency, - lastPingAttempt: parseDate(result.status.lastPingAttempt) ?? peer.lastPingAttempt + lastSeen: parsedLastSeen ?? peer.lastSeen, + latency: latency ?? peer.latency, + lastPingAttempt: parsedLastPingAttempt ?? peer.lastPingAttempt } as PeerStatus : peer )); @@ -434,7 +476,7 @@ const PeerList: React.FC = ({ return newSet; }); } - }, [isSignerRunning]); + }, [authHeaders, isSignerRunning]); const updatePeerPolicy = useCallback(async (peer: PeerStatus, changes: { allowSend?: boolean; allowReceive?: boolean }) => { if (!isSignerRunning || disabled) { @@ -533,15 +575,18 @@ const PeerList: React.FC = ({ }, body: JSON.stringify({ target: 'all' }) }); - - const result = await response.json(); - + if (!response.ok) { + const detail = await response.text().catch(() => '(unreadable)'); + console.warn(`[PeerList] Ping all failed (${response.status}): ${detail}`); + return; + } + // Refresh peer list after pinging all await fetchPeers(); } catch (error) { console.warn('[PeerList] Ping all failed:', error); } - }, [isSignerRunning, peers.length, fetchPeers]); + }, [authHeaders, isSignerRunning, peers.length, fetchPeers]); // Enhanced refresh that includes pinging const handleRefresh = useCallback(async () => { @@ -597,6 +642,8 @@ const PeerList: React.FC = ({ className="flex flex-col sm:flex-row sm:items-center justify-between bg-gray-800/50 p-2.5 rounded cursor-pointer hover:bg-gray-800/70 transition-colors gap-2 sm:gap-0" onClick={handleToggle} role="button" + aria-expanded={isExpanded} + aria-controls={panelId} tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { @@ -611,11 +658,17 @@ const PeerList: React.FC = ({ } Peer List -
e.stopPropagation()}> +
e.stopPropagation()} onKeyDown={e => e.stopPropagation()}> } + focusable + ariaLabel="Peer list help" + trigger={( + + )} content={

Shows the signing peers in your FROSTR group with online/offline status and ping latency. Use the refresh button to ping all peers and update their status.

} @@ -654,18 +707,25 @@ const PeerList: React.FC = ({ )}
-
e.stopPropagation()} className="flex-shrink-0"> +
e.stopPropagation()} onKeyDown={e => e.stopPropagation()} className="flex-shrink-0"> {actions}
{/* Collapsible Content */}
+ {shouldRenderContent && (
{isLoading ? (
@@ -770,10 +830,7 @@ const PeerList: React.FC = ({
Policy: out {outboundPolicy.statusLabel}, in {inboundPolicy.statusLabel} - + {policyBadgeLabel}
@@ -816,6 +873,7 @@ const PeerList: React.FC = ({ width="w-72" triggerClassName="cursor-help" focusable + ariaLabel="Policy controls help" trigger={} content={
@@ -896,6 +954,7 @@ const PeerList: React.FC = ({
)}
+ )}
); diff --git a/frontend/components/ui/relay-input.tsx b/frontend/components/ui/relay-input.tsx index df7b61a..e53506c 100644 --- a/frontend/components/ui/relay-input.tsx +++ b/frontend/components/ui/relay-input.tsx @@ -121,8 +121,8 @@ const RelayInput: React.FC = ({
- {relays.map((relay, index) => ( -
+ {relays.map((relay) => ( +
{relay} @@ -130,7 +149,7 @@ const Tooltip: React.FC = ({ } return ( -
+
{triggerContent}
); diff --git a/frontend/styles.css b/frontend/styles.css index f9a6b01..f9eb7f5 100644 --- a/frontend/styles.css +++ b/frontend/styles.css @@ -80,11 +80,6 @@ @apply bg-gray-900 text-foreground; font-feature-settings: "rlig" 1, "calt" 1; @apply font-sharetech; - @apply dark; /* Force dark mode for Igloo UI */ - } - - html { - @apply dark; } } diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 2a69a46..be0cbce 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "ES2020", - "lib": ["DOM", "DOM.Iterable", "ES6"], + "lib": ["DOM", "DOM.Iterable", "ES2020"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, diff --git a/frontend/types/index.ts b/frontend/types/index.ts index 0563d44..a7d1627 100644 --- a/frontend/types/index.ts +++ b/frontend/types/index.ts @@ -124,6 +124,7 @@ export interface UpdateInfo { updateAvailable: boolean; releaseUrl?: string; checkedAt?: string; + source?: 'github-release' | 'github-tags'; error?: string; } diff --git a/frontend/types/nostr-connect.d.ts b/frontend/types/nostr-connect.d.ts index 578ea30..012969e 100644 --- a/frontend/types/nostr-connect.d.ts +++ b/frontend/types/nostr-connect.d.ts @@ -1 +1,7 @@ -declare module '@cmdcode/nostr-connect' +declare module '@cmdcode/nostr-connect' { + export const InviteEncoder: { + decode: (uri: string) => unknown + } + export const SignerAgent: new (...args: unknown[]) => unknown + export const SimpleSigner: new (...args: unknown[]) => unknown +} diff --git a/llm/implementation/auth-implementation.md b/llm/implementation/auth-implementation.md index bc50934..de655af 100644 --- a/llm/implementation/auth-implementation.md +++ b/llm/implementation/auth-implementation.md @@ -117,7 +117,7 @@ Files: - `RATE_LIMIT_ENV_WRITE_WINDOW` and `RATE_LIMIT_ENV_WRITE_MAX` override env write limits. Files: -- `src/utils/rate-limiter.ts` +- `src/util/rate-limiter.ts` - `src/routes/auth.ts` (checkRateLimit) - `src/routes/onboarding.ts` (per-IP rate limiting) - `src/routes/env.ts` (env write throttling) diff --git a/llm/implementation/node-lifecycle-implementation.md b/llm/implementation/node-lifecycle-implementation.md index f4e19ae..d312156 100644 --- a/llm/implementation/node-lifecycle-implementation.md +++ b/llm/implementation/node-lifecycle-implementation.md @@ -42,7 +42,8 @@ DB user updates (`/api/user/credentials`): - GET: if stored credentials exist and the node is missing, the server auto-starts a node under the lock. - POST/PUT: credentials are saved, then self-echo + broadcast echo are fired (non-blocking). The node is started if missing; existing nodes are not force-restarted here. - DELETE: deletes credentials and cleans up the node under the lock. -- Relay-only updates (`/api/user/relays`) update the DB but do not restart the node. The running node keeps its current relay set until a restart occurs. +- Relay-only updates (`/api/user/relays`) update the DB but do not restart the node. The running node keeps its current relay set until a restart occurs, so clients should expect relay changes to take effect only after reloading/restarting the node. +- Consider returning an explicit message from `/api/user/relays` so users know updates are persisted but not yet active (for example: "Relays saved; restart required to apply changes."), and document that clients should call the restart endpoint (for example `/api/node/restart`) to apply the new relays. ## Peer Policy Persistence and Application - `PEER_POLICIES` env is JSON-parsed and normalized via igloo-core's `normalizeNodePolicies`. @@ -61,7 +62,7 @@ DB user updates (`/api/user/credentials`): - The node client request timeout is adjusted to `getOpTimeoutMs()` (bounded) when possible. - The node is wrapped in an instrumented proxy to track publish metrics and optionally swallow benign publish errors. - `NODE_PUBLISH_METRICS=false` disables instrumentation. -- `NODE_ALLOW_BENIGN_PUBLISH_SWALLOW=false` (or `RELAY_ALLOW_BENIGN_SWALLOW`) forces publish errors to surface. +- `NODE_ALLOW_BENIGN_PUBLISH_SWALLOW` is authoritative; `RELAY_ALLOW_BENIGN_SWALLOW` is a backward-compatibility fallback consulted only when `NODE_ALLOW_BENIGN_PUBLISH_SWALLOW` is unset (`NODE_ALLOW_BENIGN_PUBLISH_SWALLOW ?? RELAY_ALLOW_BENIGN_SWALLOW`). Any explicit value on `NODE_ALLOW_BENIGN_PUBLISH_SWALLOW` (including `true` or `false`) takes precedence. To force publish errors to surface, set `NODE_ALLOW_BENIGN_PUBLISH_SWALLOW=false`; if `NODE_ALLOW_BENIGN_PUBLISH_SWALLOW` is unset, set `RELAY_ALLOW_BENIGN_SWALLOW=false`. - Initial connectivity check runs after optional `INITIAL_CONNECTIVITY_DELAY` to avoid startup races. ## Monitoring and Recovery diff --git a/llm/implementation/umbrel-implementation.md b/llm/implementation/umbrel-implementation.md index 8c3b8c9..36d643b 100644 --- a/llm/implementation/umbrel-implementation.md +++ b/llm/implementation/umbrel-implementation.md @@ -66,11 +66,11 @@ These values are set in the store compose and expected by the UI flow: ## Operational Notes - Healthcheck uses `curl http://localhost:8002/api/status` with retries and start period. -- The Umbrel store uses a pinned digest to avoid tag caching issues; update the digest on each new release. -- `packages/umbrel/igloo/docker-compose.yml` remains a sideload/dev bundle and still points at `:umbrel-dev` without a digest. +- `igloo-server/docker-compose.yml` (Umbrel store artifact) intentionally uses the `:umbrel-dev` tag pinned to a digest (e.g. `ghcr.io/frostr-org/igloo-server:umbrel-dev@sha256:...`). The tag stays `:umbrel-dev` on every release; only the digest is updated. This avoids Umbrel app-store tag-caching issues. +- `packages/umbrel/igloo/docker-compose.yml` is a sideload/dev bundle and also points at `:umbrel-dev` but without a pinned digest. ## Update Checklist for Future Releases 1. Build and push the new Umbrel image (`:umbrel-` and `:umbrel-latest`). -2. Update `igloo-server-store/igloo-server/docker-compose.yml` to the new image digest. -3. Update `igloo-server-store/igloo-server/umbrel-app.yml` version and release notes. +2. Bump the digest in `igloo-server/docker-compose.yml` (keep the `:umbrel-dev` tag; only the `@sha256:...` digest changes). +3. Revise `igloo-server/umbrel-app.yml` version and release notes. 4. Refresh gallery assets if the UI has changed. diff --git a/llm/workflows/RELEASE_PROCESS.md b/llm/workflows/RELEASE_PROCESS.md index 101d11c..e024860 100644 --- a/llm/workflows/RELEASE_PROCESS.md +++ b/llm/workflows/RELEASE_PROCESS.md @@ -179,7 +179,7 @@ The script performs these checks in sequence: ### 3. Manual Steps (Developer Action Required) -After the script pushes the release branch, you must create a pull request to merge the changes into the `master` branch. +After the script pushes the release branch, you must create a pull request to merge the changes into the `main` branch. 1. **Create a Pull Request** @@ -189,16 +189,16 @@ After the script pushes the release branch, you must create a pull request to me ```bash # Optional: Create the pull request using the GitHub CLI # Replace with the new version number from the script's output - gh pr create --base master --head "release/prepare-v" --title "chore(release): Release v" --body "Prepares for release v" + gh pr create --base main --head "release/prepare-v" --title "chore(release): Release v" --body "Prepares for release v" ``` 2. **Review and Merge PR** - Get approvals if required. - - Merge the pull request to `master` to trigger the automated release workflow. + - Merge the pull request to `main` to trigger the automated release workflow. ### 4. GitHub Actions Automation -When the PR is merged to master: +When the PR is merged to main: 1. **Release Creation** - GitHub automatically creates a release @@ -325,7 +325,7 @@ Apply appropriate labels for changelog categorization: ## Release Artifacts Each release produces: -1. **Git Tag**: `v{version}` on master branch +1. **Git Tag**: `v{version}` on main branch 2. **GitHub Release**: With auto-generated changelog 3. **Updated package.json**: New version number 4. **Release Branch**: Archived as `release/prepare-v{version}` @@ -334,20 +334,20 @@ Each release produces: If a release needs to be rolled back: -1. **Revert on Master** +1. **Revert on Main** ```bash - git checkout master + git checkout main git revert -m 1 - git push origin master + git push origin main ``` - _Use `-m 1` for the common case where master was the first parent of the merge. If you merged from a different base, substitute the parent number that represents the stable branch you want to keep._ + _Use `-m 1` for the common case where main was the first parent of the merge. If you merged from a different base, substitute the parent number that represents the stable branch you want to keep._ 2. **Create Hotfix** ```bash git checkout -b hotfix/v{version}-rollback # Make fixes git push origin hotfix/v{version}-rollback - # Create PR to master + # Create PR to main ``` ## Monitoring Post-Release diff --git a/package.json b/package.json index aed086c..3a1fa50 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.1.1", "scripts": { "start": "bun run src/server.ts", - "start:headless": "if [ \"$HEADLESS\" = \"true\" ]; then echo 'Headless mode: skipping frontend build'; else bun run build; fi && bun run start", + "start:headless": "bun scripts/start-headless.js", "build:css": "cd frontend && bunx tailwindcss -i ./styles.css -o ../static/styles.css --watch", "build:css:prod": "cd frontend && bunx tailwindcss -i ./styles.css -o ../static/styles.css --minify", "copy:qr-worker": "bun scripts/copy-qr-worker.mjs", @@ -33,6 +33,7 @@ "api:test:get:openapi": "bun scripts/api/test-get-openapi-sweep.ts", "api:test:ws": "bun scripts/api/test-ws-events.ts", "api:test:nip": "bun scripts/api/test-nip44-nip04.ts", + "test:unit": "bun test --max-concurrency=1 src tests/routes", "typecheck": "tsc --noEmit", "tsc": "tsc --noEmit" }, @@ -55,20 +56,30 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "tailwind-merge": "^3.3.1", - "yaml": "^2.8.1" + "yaml": "^2.8.1", + "zod": "^3.25.76" }, "devDependencies": { "@redocly/cli": "^1.34.5", "@types/node": "^22.18.12", "@types/react": "^18.3.26", "@types/react-dom": "^18.3.7", - "@types/yaml": "^1.9.7", + "ajv": "^8.18.0", "bun-types": "^1.3.1", "concurrently": "^9.2.1", - "esbuild": "^0.24.2", + "esbuild": "^0.25.0", "postcss": "^8.5.6", "tailwindcss": "^3.4.18", "tailwindcss-animate": "^1.0.7", "typescript": "^5.7.3" + }, + "overrides": { + "glob": "10.5.0", + "minimatch": "10.2.4", + "js-yaml": "4.1.1", + "dompurify": "3.3.2", + "undici": "6.23.0", + "ajv": "8.18.0", + "fast-xml-parser": "5.4.1" } } diff --git a/packages/umbrel/igloo/Dockerfile b/packages/umbrel/igloo/Dockerfile index 9f3659d..fb6bd0e 100644 --- a/packages/umbrel/igloo/Dockerfile +++ b/packages/umbrel/igloo/Dockerfile @@ -1,5 +1,5 @@ # Umbrel-specific igloo-server image with non-root runtime user -FROM oven/bun:1.1.30 AS build +FROM oven/bun:1.3.10 AS build WORKDIR /app @@ -21,7 +21,7 @@ COPY tsconfig.json ./ RUN bun run build # Production stage with non-root user that matches Umbrel defaults -FROM oven/bun:1.1.30 AS production +FROM oven/bun:1.3.10 AS production ARG IGLOO_USER=igloo ARG IGLOO_UID=1000 diff --git a/scripts/api/README.md b/scripts/api/README.md index 154dade..1b5e165 100644 --- a/scripts/api/README.md +++ b/scripts/api/README.md @@ -28,3 +28,4 @@ Notes - Tests intentionally include public endpoints (status, docs) while sending the API key to exercise the header parsing path. - In database mode, API keys authenticate, but routes requiring a numeric DB user or admin secret will still respond 401/403. That’s expected and checked by the permission probes. - WS auth is performed via `?apiKey=...` query param to avoid client header limitations; the server maps it to `X-API-Key` during upgrade. + If you set `ALLOW_QUERY_CREDENTIALS=false`, switch to header or `Sec-WebSocket-Protocol` auth hints for `/api/events`. diff --git a/scripts/api/test-get-endpoints.ts b/scripts/api/test-get-endpoints.ts index 4224b76..62780f5 100644 --- a/scripts/api/test-get-endpoints.ts +++ b/scripts/api/test-get-endpoints.ts @@ -75,7 +75,7 @@ const tests: TestCase[] = [ { name: 'Docs UI', path: '/api/docs', - expectStatus: s => s === 200 || s === 500, // 500 if swagger assets weren’t vendored + expectStatus: s => s === 200, expectContentType: /^text\/html/i, }, { @@ -142,4 +142,3 @@ async function main() { } main(); - diff --git a/scripts/api/test-ws-events.ts b/scripts/api/test-ws-events.ts index aacaba3..d57a1ff 100644 --- a/scripts/api/test-ws-events.ts +++ b/scripts/api/test-ws-events.ts @@ -23,7 +23,9 @@ async function main() { console.log('\n== WS /api/events test =='); console.log(`Base URL: ${BASE_URL}`); const url = wsUrl(); - console.log(`Connecting: ${url}`); + const safeUrl = new URL(url); + safeUrl.search = ''; + console.log(`Connecting: ${safeUrl.toString()}`); let gotMessage = false; let closedEarly = false; @@ -77,4 +79,3 @@ async function main() { } main(); - diff --git a/scripts/fetch-swagger-ui.mjs b/scripts/fetch-swagger-ui.mjs index f6f301f..f583f87 100644 --- a/scripts/fetch-swagger-ui.mjs +++ b/scripts/fetch-swagger-ui.mjs @@ -6,7 +6,7 @@ import { mkdir } from 'node:fs/promises'; -const VERSION = process.env.SWAGGER_UI_VERSION || '5.9.0'; +const VERSION = process.env.SWAGGER_UI_VERSION || '5.31.1'; const BASE = `https://unpkg.com/swagger-ui-dist@${VERSION}`; const FILES = [ 'swagger-ui.css', @@ -36,7 +36,7 @@ async function main() { for (const name of FILES) { const buf = await fetchFile(name); - await Bun.write(`${outDir}/${name}`, buf, { createPath: true }); + await Bun.write(`${outDir}/${name}`, buf); console.log(`[docs:vendor] wrote ${outDir}/${name} (${buf.length} bytes)`); } console.log(`[docs:vendor] Swagger UI assets pinned at ${VERSION}.`); diff --git a/scripts/patch-zod-compat.mjs b/scripts/patch-zod-compat.mjs index 5006721..9cfac1e 100644 --- a/scripts/patch-zod-compat.mjs +++ b/scripts/patch-zod-compat.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env bun -import { readFileSync, writeFileSync, readdirSync } from 'fs' +import { readFileSync, writeFileSync, readdirSync, existsSync, mkdirSync } from 'fs' import { fileURLToPath } from 'url' import { dirname, join } from 'path' @@ -9,7 +9,12 @@ const ZOD_DIR = join(PROJECT_ROOT, 'node_modules', 'zod') const NOSTR_SCHEMA_DIR = join(PROJECT_ROOT, 'node_modules', '@cmdcode', 'nostr-connect', 'dist', 'schema') function ensureFile(path, content) { - const current = readFileSync(path, 'utf8') + let current = '' + try { + current = readFileSync(path, 'utf8') + } catch (error) { + if (error?.code !== 'ENOENT') throw error + } if (current !== content) { writeFileSync(path, content) } @@ -19,22 +24,32 @@ const esmStub = `import * as z from "./v3/external.js";\nexport * from "./v3/ext const cjsStub = `"use strict";\nvar __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n}));\nvar __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n});\nvar __importStar = (this && this.__importStar) || function (mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\n __setModuleDefault(result, mod);\n return result;\n};\nvar __exportStar = (this && this.__exportStar) || function(m, exports) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.z = void 0;\nconst z = __importStar(require(\"./v3/external.cjs\"));\nexports.z = z;\n__exportStar(require(\"./v3/external.cjs\"), exports);\nexports.default = z;\n` const cjsDeclarationStub = `import * as z from "./v3/external.cjs";\ndeclare const exported: typeof z & { readonly z: typeof z; readonly default: typeof z };\nexport = exported;\n` -ensureFile(join(ZOD_DIR, 'index.js'), esmStub) -ensureFile(join(ZOD_DIR, 'index.d.ts'), esmStub) -ensureFile(join(ZOD_DIR, 'index.cjs'), cjsStub) -ensureFile(join(ZOD_DIR, 'index.d.cts'), cjsDeclarationStub) +if (!existsSync(ZOD_DIR)) { + console.log('[patch-zod-compat] zod directory not found, skipping zod compatibility patch') +} else { + mkdirSync(ZOD_DIR, { recursive: true }) + ensureFile(join(ZOD_DIR, 'index.js'), esmStub) + ensureFile(join(ZOD_DIR, 'index.d.ts'), esmStub) + ensureFile(join(ZOD_DIR, 'index.cjs'), cjsStub) + ensureFile(join(ZOD_DIR, 'index.d.cts'), cjsDeclarationStub) +} -try { +if (!existsSync(NOSTR_SCHEMA_DIR)) { + console.log('[patch-zod-compat] Nostr schema directory not found, skipping schema patch') +} else { + try { const schemaFiles = readdirSync(NOSTR_SCHEMA_DIR).filter(name => name.endsWith('.js')) for (const file of schemaFiles) { const fullPath = join(NOSTR_SCHEMA_DIR, file) const current = readFileSync(fullPath, 'utf8') const target = "import { zod as z } from '@vbyte/micro-lib/schema';" if (current.includes("import { z } from 'zod';")) { - const updated = current.replace("import { z } from 'zod';", target) + const updated = current.replaceAll("import { z } from 'zod';", target) writeFileSync(fullPath, updated) } } -} catch (error) { - console.error('[patch-zod-compat] Failed to patch nostr-connect schemas:', error.message) + } catch (error) { + console.error('[patch-zod-compat] Failed to patch nostr-connect schemas:', error) + process.exit(1) + } } diff --git a/scripts/release.sh b/scripts/release.sh index 107ff4f..0d7bbb9 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -28,8 +28,12 @@ echo "📥 Pulling latest changes..." git pull origin dev # Run tests and build +echo "🧪 Running type checks and backend tests..." +bun install --frozen-lockfile +bun run typecheck +bun run test:unit + echo "🔨 Building project..." -bun install bun run build echo "✅ Testing server startup..." @@ -69,13 +73,9 @@ for i in {1..5}; do fi done -if [ "$SERVER_HEALTHY" = false ]; then - echo "❌ Server failed to respond after 5 attempts" -fi - # Cleanup will be handled by trap, just check if we should fail if [ "$SERVER_HEALTHY" = false ]; then - echo "❌ Server startup test failed - cannot proceed with release" + echo "❌ Server failed to respond after 5 attempts - cannot proceed with release" exit 1 fi @@ -83,8 +83,9 @@ echo "📋 Validating documentation..." bun run docs:validate # Determine version -if [[ "$VERSION_TYPE" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - NEW_VERSION="$VERSION_TYPE" +if [[ "$VERSION_TYPE" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?$ ]]; then + echo "📈 Explicit version: $VERSION_TYPE" + NEW_VERSION=$(npm version "$VERSION_TYPE" --no-git-tag-version --allow-same-version) else CURRENT_VERSION=$(node -p "require('./package.json').version") echo "📊 Current version: $CURRENT_VERSION" @@ -92,9 +93,15 @@ else # Compute new version using npm version command NEW_VERSION=$(npm version $VERSION_TYPE --no-git-tag-version) - # Remove 'v' prefix from version (npm version returns v1.2.3) - NEW_VERSION=${NEW_VERSION#v} - echo "🎯 New version: $NEW_VERSION" +fi + +# Remove 'v' prefix from version (npm version returns v1.2.3) +NEW_VERSION=${NEW_VERSION#v} +echo "🎯 New version: $NEW_VERSION" + +if [[ -z "$NEW_VERSION" || ! "$NEW_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?$ ]]; then + echo "❌ Invalid NEW_VERSION computed from npm version output: '${NEW_VERSION}'" + exit 1 fi # Create release branch diff --git a/scripts/start-headless.js b/scripts/start-headless.js new file mode 100755 index 0000000..3f389bc --- /dev/null +++ b/scripts/start-headless.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +const { spawnSync } = require('node:child_process'); + +function runScript(name) { + const result = spawnSync('bun', ['run', name], { stdio: 'inherit' }); + if (result.error) { + throw result.error; + } + if (result.status !== 0) { + process.exit(result.status); + } +} + +if (process.env.HEADLESS !== 'true') { + console.log('HEADLESS is not true. Skipping headless start flow.'); + process.exit(0); +} + +try { + runScript('build'); + runScript('start'); +} catch (error) { + console.error('Failed to run headless startup flow:', error); + process.exit(1); +} diff --git a/src/class/relay.test.ts b/src/class/relay.test.ts new file mode 100644 index 0000000..38df3a1 --- /dev/null +++ b/src/class/relay.test.ts @@ -0,0 +1,177 @@ +import { describe, expect, it } from 'bun:test'; +import { NostrRelay } from './relay.js'; + +type FakeSocket = { + data: unknown; + sent: string[]; + closed: boolean; + send: (message: string) => void; + close: () => void; +}; + +type RelayHandler = ReturnType; +type HandlerSocket = Parameters>[0]; + +function asHandlerSocket(socket: FakeSocket): HandlerSocket { + return socket as unknown as HandlerSocket; +} + +function createFakeSocket(): FakeSocket { + return { + data: null, + sent: [], + closed: false, + send(message: string) { + this.sent.push(message); + }, + close() { + this.closed = true; + }, + }; +} + +function decodeSent(socket: FakeSocket): unknown[][] { + return socket.sent.map((message) => JSON.parse(message) as unknown[]); +} + +describe('NostrRelay REQ handling', () => { + it('normalizes wrapped nostr-tools REQ filter arrays into subscriptions', () => { + const relay = new NostrRelay({ info: false, debug: false }); + const socket = createFakeSocket(); + const ws = asHandlerSocket(socket); + const handler = relay.handler(); + + handler.open?.(ws); + handler.message?.(ws, JSON.stringify(['REQ', 'sub-1', [{ kinds: [1] }]])); + + expect(relay.subs.size).toBe(1); + const [sub] = Array.from(relay.subs.values()); + expect(sub?.sub_id).toBe('sub-1'); + expect(sub?.filters).toHaveLength(1); + expect((sub?.filters[0] as { kinds?: number[] }).kinds).toEqual([1]); + + const messages = decodeSent(socket); + expect(messages).toContainEqual(['EOSE', 'sub-1']); + }); + + it('rejects REQ with an empty wrapped filter array', () => { + const relay = new NostrRelay({ info: false, debug: false }); + const socket = createFakeSocket(); + const ws = asHandlerSocket(socket); + const handler = relay.handler(); + + handler.open?.(ws); + handler.message?.(ws, JSON.stringify(['REQ', 'sub-empty', []])); + + expect(relay.subs.size).toBe(0); + const messages = decodeSent(socket); + expect(messages).toContainEqual(['NOTICE', 'sub-empty', 'REQ requires at least one filter']); + }); + + it('rejects REQ with no filters', () => { + const relay = new NostrRelay({ info: false, debug: false }); + const socket = createFakeSocket(); + const ws = asHandlerSocket(socket); + const handler = relay.handler(); + + handler.open?.(ws); + handler.message?.(ws, JSON.stringify(['REQ', 'sub-empty-no-filters'])); + + expect(relay.subs.size).toBe(0); + const messages = decodeSent(socket); + expect(messages).toContainEqual(['NOTICE', 'sub-empty-no-filters', 'REQ requires at least one filter']); + }); + + it('accepts canonical multi-filter REQ payloads and creates a subscription', () => { + const relay = new NostrRelay({ info: false, debug: false }); + const socket = createFakeSocket(); + const ws = asHandlerSocket(socket); + const handler = relay.handler(); + + handler.open?.(ws); + const authorHex = 'f'.repeat(64); + handler.message?.(ws, JSON.stringify(['REQ', 'sub-multi', { kinds: [1] }, { authors: [authorHex] }])); + + expect(relay.subs.size).toBe(1); + const [sub] = Array.from(relay.subs.values()); + expect(sub?.sub_id).toBe('sub-multi'); + expect(sub?.filters).toHaveLength(2); + expect((sub?.filters[0] as { kinds?: number[] }).kinds).toEqual([1]); + expect((sub?.filters[1] as { authors?: string[] }).authors).toEqual([authorHex]); + + const messages = decodeSent(socket); + expect(messages).toContainEqual(['EOSE', 'sub-multi']); + }); + + it('removes composed-key subscriptions when CLOSE/unsubscribe is processed', () => { + const relay = new NostrRelay({ info: false, debug: false }); + const socket = createFakeSocket(); + const ws = asHandlerSocket(socket); + const handler = relay.handler(); + + handler.open?.(ws); + handler.message?.(ws, JSON.stringify(['REQ', 'sub-close', { kinds: [1] }])); + expect(relay.subs.size).toBe(1); + + handler.message?.(ws, JSON.stringify(['CLOSE', 'sub-close'])); + expect(relay.subs.size).toBe(0); + + handler.message?.(ws, JSON.stringify(['REQ', 'sub-cleanup', { kinds: [1] }])); + expect(relay.subs.size).toBe(1); + + const closeHandler = handler.close as unknown as ((socketArg: HandlerSocket, code: number) => void) | undefined; + closeHandler?.(ws, 1000); + expect(relay.subs.size).toBe(0); + expect(socket.closed).toBe(true); + }); + + it('applies filter.limit to matched events only', () => { + const relay = new NostrRelay({ info: false, debug: false }); + const socket = createFakeSocket(); + const ws = asHandlerSocket(socket); + const handler = relay.handler(); + + const unmatched = { + id: 'u'.repeat(64), + pubkey: 'a'.repeat(64), + created_at: 1, + kind: 9, + tags: [], + content: '', + sig: 'b'.repeat(128), + } as Parameters[0]; + const matchedA = { + id: 'c'.repeat(64), + pubkey: 'a'.repeat(64), + created_at: 2, + kind: 1, + tags: [], + content: '', + sig: 'd'.repeat(128), + } as Parameters[0]; + const matchedB = { + id: 'e'.repeat(64), + pubkey: 'a'.repeat(64), + created_at: 3, + kind: 1, + tags: [], + content: '', + sig: 'f'.repeat(128), + } as Parameters[0]; + + relay.store(unmatched); + relay.store(matchedA); + relay.store(matchedB); + + handler.open?.(ws); + handler.message?.(ws, JSON.stringify(['REQ', 'sub-limit', { kinds: [1], limit: 1 }])); + + const messages = decodeSent(socket); + const eventMessages = messages.filter((msg) => msg[0] === 'EVENT'); + expect(eventMessages).toHaveLength(1); + expect(eventMessages[0]?.[1]).toBe('sub-limit'); + expect((eventMessages[0]?.[2] as { kind?: number }).kind).toBe(1); + expect((eventMessages[0]?.[2] as { id?: string }).id).toBe(matchedB.id); + expect(messages).toContainEqual(['EOSE', 'sub-limit']); + }); +}); diff --git a/src/class/relay.ts b/src/class/relay.ts index e849402..2e23e46 100644 --- a/src/class/relay.ts +++ b/src/class/relay.ts @@ -97,7 +97,11 @@ export class NostrRelay extends EventEmitter { } store (event : SignedEvent) { - this._cache = this._cache.concat(event).sort((a, b) => a > b ? -1 : 1) + this._cache = this._cache.concat(event).sort((a, b) => { + const createdAtDiff = (b.created_at ?? 0) - (a.created_at ?? 0) + if (createdAtDiff !== 0) return createdAtDiff + return b.id.localeCompare(a.id) + }) } } @@ -152,7 +156,23 @@ class RelaySession { switch (verb) { case 'REQ': + // Normalize nostr-tools 2.x format where filters are wrapped in an extra array: + // New format: ["REQ", "sub_id", [{filter1}, {filter2}]] + // NIP-01 format: ["REQ", "sub_id", {filter1}, {filter2}] + if (payload.length === 2 && Array.isArray(payload[1]) && payload[1].length === 0) { + this.log.info('ignoring REQ with empty filter array') + this.send(['NOTICE', String(payload[0] ?? ''), 'REQ requires at least one filter']) + return + } + if (payload.length === 2 && Array.isArray(payload[1])) { + payload = [payload[0], ...payload[1]] + } const [ id, ...filters ] = sub_schema.parse(payload) + if (filters.length === 0) { + this.log.info('ignoring REQ with no filters') + this.send(['NOTICE', id, 'REQ requires at least one filter']) + return + } return this._onreq(id, filters) case 'EVENT': const event = Nostr.parse_event(payload.at(0), this.relay.config.debug) @@ -185,7 +205,7 @@ class RelaySession { this.log.debug('event:', event) if (!Nostr.verify_event(event)) { - this.log.debug('event failed validation:', event) + this.log.debug(`event failed validation (id=${event.id.slice(0, 8)} kind=${event.kind})`) this.send([ 'OK', event.id, false, 'event failed validation' ]) return } @@ -210,7 +230,7 @@ class RelaySession { this.log.client('received subscription request:', sub_id) this.log.debug('filters:', filters) // Add the subscription to our set. - this.addSub(sub_id, filters) + this.addSub(sub_id, ...filters) // For each filter: for (const filter of filters) { // Set the limit count, if any. @@ -225,9 +245,12 @@ class RelaySession { this.send(['EVENT', sub_id, event]) this.log.client(`event matched in cache: ${event.id}`) this.log.client(`event matched subscription: ${sub_id}`) + // Decrement only when we actually sent a matching event. + if (limit_count !== undefined) { + limit_count -= 1 + if (limit_count === 0) break + } } - // Update the limit count. - if (limit_count !== undefined) limit_count -= 1 } } } @@ -254,7 +277,7 @@ class RelaySession { } remSub (subId : string) { - this.relay.subs.delete(subId) + this.relay.subs.delete(`${this.sid}/${subId}`) this._subs.delete(subId) } diff --git a/src/config/crypto.ts b/src/config/crypto.ts index d72e59b..deba3c9 100644 --- a/src/config/crypto.ts +++ b/src/config/crypto.ts @@ -5,7 +5,7 @@ // PBKDF2 Configuration for Key Derivation export const PBKDF2_CONFIG = { - ITERATIONS: 200000, // Number of iterations (higher = more secure but slower) + ITERATIONS: 600000, // OWASP-aligned baseline for PBKDF2-HMAC-SHA256 KEY_LENGTH: 32, // 256 bits ALGORITHM: 'sha256', // Hash algorithm } as const; @@ -36,9 +36,8 @@ export const VALIDATION = { MAX_PASSWORD_LENGTH: 128, // Prevent DoS from extremely long passwords MAX_USERNAME_LENGTH: 50, MIN_USERNAME_LENGTH: 3, - // Regex for password validation: uppercase, lowercase, digit, special char (length checked separately) - // Restricts to safe character set: letters, digits, and specific special characters - PASSWORD_REGEX: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[a-zA-Z\d@$!%*?&]+$/, + // Regex for password validation: uppercase, lowercase, digit, and any non-alphanumeric symbol (length checked separately) + PASSWORD_REGEX: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).+$/, } as const; // Export type-safe config objects @@ -60,4 +59,4 @@ export function isPasswordValid(pwd: string): boolean { return false; } return VALIDATION.PASSWORD_REGEX.test(pwd); -} \ No newline at end of file +} diff --git a/src/const.ts b/src/const.ts index 31e6a4f..cc6a787 100644 --- a/src/const.ts +++ b/src/const.ts @@ -23,7 +23,11 @@ export const RELAYS: string[] = (() => { })(); export const HOST_NAME = process.env['HOST_NAME'] ?? 'localhost' -export const HOST_PORT = parseInt(process.env['HOST_PORT'] ?? '8002', 10) +const rawHostPort = process.env['HOST_PORT']?.trim() +const parsedHostPort = rawHostPort && /^\d+$/.test(rawHostPort) ? Number(rawHostPort) : NaN +export const HOST_PORT = Number.isInteger(parsedHostPort) && parsedHostPort >= 1 && parsedHostPort <= 65535 + ? parsedHostPort + : 8002 // Raw credential strings for igloo-core functions - treat empty/whitespace as absent export const GROUP_CRED = (() => { @@ -56,7 +60,6 @@ export const ADMIN_SECRET = (() => { if (isUnset && shouldAutoGenerateAdminSecret) { const fallback = 'ci-auto-admin-secret'; - process.env['ADMIN_SECRET'] = fallback; console.warn('[init] ADMIN_SECRET was unset. Generated ephemeral secret for CI/test environment.'); return fallback; } diff --git a/src/db/database.ts b/src/db/database.ts index 446a493..75841ec 100644 --- a/src/db/database.ts +++ b/src/db/database.ts @@ -8,8 +8,28 @@ import { PBKDF2_CONFIG, AES_CONFIG, SALT_CONFIG, PASSWORD_HASH_CONFIG } from '.. // Database configuration const defaultDbDir = path.join(process.cwd(), 'data'); -const envPath = process.env.DB_PATH; -const isEnvPathFile = !!envPath && (envPath.endsWith('.db') || path.extname(envPath) !== ''); +const envPathRaw = process.env.DB_PATH; +const envPath = typeof envPathRaw === 'string' ? envPathRaw.trim() : undefined; +const DB_FILE_EXTENSIONS = ['.db', '.sqlite', '.sqlite3']; +const isEnvPathFile = (() => { + if (!envPath) return false; + + if (existsSync(envPath)) { + try { + return statSync(envPath).isFile(); + } catch { + return false; + } + } + + if (envPath.endsWith(path.sep) || envPath.endsWith('/') || envPath.endsWith('\\')) { + return false; + } + + // Keep inference consistent with session-secret path handling in routes/auth.ts. + const normalizedBase = path.basename(path.normalize(envPath)).toLowerCase(); + return DB_FILE_EXTENSIONS.some((ext) => normalizedBase.endsWith(ext)); +})(); const DB_DIR = isEnvPathFile ? path.dirname(envPath as string) : (envPath || defaultDbDir); const DB_FILE = isEnvPathFile ? (envPath as string) : path.join(DB_DIR, 'igloo.db'); @@ -371,31 +391,34 @@ export function cleanupExpiredSessionsDB(ttlMs: number): string[] { const nowSeconds = Math.floor(Date.now() / 1000); const ttlSeconds = Math.max(1, Math.floor(ttlMs / 1000)); const cutoff = nowSeconds - ttlSeconds; - const rows = db - .prepare( - `SELECT id FROM sessions WHERE CAST(strftime('%s', last_access) AS INTEGER) <= ?` - ) - .all(cutoff) as { id: string }[]; - if (rows.length === 0) return []; - const ids = rows.map(r => r.id); // SQLite historically enforces a max bind parameter count (often 999). // Delete in safe batches within a single transaction for performance and atomicity. const CHUNK = 900; // stay under 999 to be robust across builds - db.exec('BEGIN'); + db.exec('BEGIN IMMEDIATE'); try { + const rows = db + .prepare( + `SELECT id FROM sessions WHERE CAST(strftime('%s', last_access) AS INTEGER) <= ?` + ) + .all(cutoff) as { id: string }[]; + if (rows.length === 0) { + db.exec('COMMIT'); + return []; + } + + const ids = rows.map(r => r.id); for (let i = 0; i < ids.length; i += CHUNK) { const chunk = ids.slice(i, i + CHUNK); const placeholders = chunk.map(() => '?').join(','); db.prepare(`DELETE FROM sessions WHERE id IN (${placeholders})`).run(...chunk); } db.exec('COMMIT'); + return ids; } catch (err) { try { db.exec('ROLLBACK'); } catch {} throw err; } - - return ids; } catch (e) { console.error('[db] cleanupExpiredSessionsDB failed:', e); return []; @@ -1115,7 +1138,7 @@ export const getAllUsers = (): AdminUserListItem[] => { /** * Delete a user with an atomic last-admin guard. - * A user is considered an admin if they have both encrypted credentials stored. + * A user is considered an admin when role='admin'. * The function prevents deleting the last such admin user via a transaction. */ export const deleteUserSafely = ( @@ -1127,7 +1150,7 @@ export const deleteUserSafely = ( const row = db .prepare( - `SELECT id, (group_cred_encrypted IS NOT NULL AND share_cred_encrypted IS NOT NULL) AS isAdmin + `SELECT id, (role = 'admin') AS isAdmin FROM users WHERE id = ?` ) .get(userId) as { id: number | bigint; isAdmin: 0 | 1 } | undefined; @@ -1143,7 +1166,7 @@ export const deleteUserSafely = ( const countRow = db .query( - `SELECT COUNT(*) as cnt FROM users WHERE group_cred_encrypted IS NOT NULL AND share_cred_encrypted IS NOT NULL` + `SELECT COUNT(*) as cnt FROM users WHERE role = 'admin'` ) .get() as { cnt: number } | null; const adminCount = countRow?.cnt ?? 0; diff --git a/src/db/migrations/20250916_0004_audit_nip46_data_sizes.sql b/src/db/migrations/20250916_0004_audit_nip46_data_sizes.sql index b14563e..6e0d98e 100644 --- a/src/db/migrations/20250916_0004_audit_nip46_data_sizes.sql +++ b/src/db/migrations/20250916_0004_audit_nip46_data_sizes.sql @@ -19,6 +19,9 @@ CREATE TABLE IF NOT EXISTS nip46_data_audit ( audited_at DATETIME DEFAULT CURRENT_TIMESTAMP ); +-- Keep this migration idempotent if rerun. +DELETE FROM nip46_data_audit; + -- Audit existing sessions INSERT INTO nip46_data_audit ( session_id, @@ -35,27 +38,28 @@ SELECT id, user_id, client_pubkey, - CASE WHEN relays IS NOT NULL THEN LENGTH(relays) ELSE 0 END as relay_size, - CASE WHEN policy_methods IS NOT NULL THEN LENGTH(policy_methods) ELSE 0 END as methods_size, - CASE WHEN policy_kinds IS NOT NULL THEN LENGTH(policy_kinds) ELSE 0 END as kinds_size, + CASE WHEN relays IS NOT NULL THEN LENGTH(CAST(relays AS BLOB)) ELSE 0 END as relay_size, + CASE WHEN policy_methods IS NOT NULL THEN LENGTH(CAST(policy_methods AS BLOB)) ELSE 0 END as methods_size, + CASE WHEN policy_kinds IS NOT NULL THEN LENGTH(CAST(policy_kinds AS BLOB)) ELSE 0 END as kinds_size, CASE WHEN relays IS NOT NULL OR policy_methods IS NOT NULL OR policy_kinds IS NOT NULL - THEN COALESCE(LENGTH(relays), 0) + COALESCE(LENGTH(policy_methods), 0) + COALESCE(LENGTH(policy_kinds), 0) + THEN COALESCE(LENGTH(CAST(relays AS BLOB)), 0) + COALESCE(LENGTH(CAST(policy_methods AS BLOB)), 0) + COALESCE(LENGTH(CAST(policy_kinds AS BLOB)), 0) ELSE 0 END as total_size, -- Check if any field would exceed old 10KB limit + -- (legacy threshold retained for historical comparison) CASE - WHEN COALESCE(LENGTH(relays), 0) > 10000 - OR COALESCE(LENGTH(policy_methods), 0) > 10000 - OR COALESCE(LENGTH(policy_kinds), 0) > 10000 + WHEN COALESCE(LENGTH(CAST(relays AS BLOB)), 0) > 10000 + OR COALESCE(LENGTH(CAST(policy_methods AS BLOB)), 0) > 10000 + OR COALESCE(LENGTH(CAST(policy_kinds AS BLOB)), 0) > 10000 THEN 1 ELSE 0 END as has_risk_old_limit, - -- Check if any field would exceed new 50KB limit + -- Check if any field would exceed current MAX_JSON_FIELD_SIZE (50KB) CASE - WHEN COALESCE(LENGTH(relays), 0) > 50000 - OR COALESCE(LENGTH(policy_methods), 0) > 50000 - OR COALESCE(LENGTH(policy_kinds), 0) > 50000 + WHEN COALESCE(LENGTH(CAST(relays AS BLOB)), 0) > 50000 + OR COALESCE(LENGTH(CAST(policy_methods AS BLOB)), 0) > 50000 + OR COALESCE(LENGTH(CAST(policy_kinds AS BLOB)), 0) > 50000 THEN 1 ELSE 0 END as has_risk_new_limit @@ -80,4 +84,4 @@ SELECT 'Methods size: ' || methods_size || ' bytes' as methods_info, 'Kinds size: ' || kinds_size || ' bytes' as kinds_info FROM nip46_data_audit -WHERE has_risk_new_limit = 1; \ No newline at end of file +WHERE has_risk_new_limit = 1; diff --git a/src/db/migrations/20250922_0007_add_nip46_relays.sql b/src/db/migrations/20250922_0007_add_nip46_relays.sql index fe78e7a..229a861 100644 --- a/src/db/migrations/20250922_0007_add_nip46_relays.sql +++ b/src/db/migrations/20250922_0007_add_nip46_relays.sql @@ -2,9 +2,9 @@ CREATE TABLE IF NOT EXISTS nip46_relays ( user_id INTEGER PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, - relays TEXT, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + relays TEXT NOT NULL DEFAULT '[]', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- NOTE: we intentionally avoid an UPDATE-in-trigger pattern here. Application-level diff --git a/src/db/migrator.ts b/src/db/migrator.ts index b0d2f9e..c5f56c1 100644 --- a/src/db/migrator.ts +++ b/src/db/migrator.ts @@ -1,7 +1,14 @@ import path from 'path' -import { existsSync, readdirSync, readFileSync } from 'fs' +import { existsSync, readdirSync, readFileSync, realpathSync } from 'fs' import db from './database.js' +class MigrationDirSecurityError extends Error { + constructor(message: string) { + super(message) + this.name = 'MigrationDirSecurityError' + } +} + function ensureMigrationsTable() { db.exec(` CREATE TABLE IF NOT EXISTS schema_migrations ( @@ -26,14 +33,25 @@ export function runMigrations(migrationsDirRel = 'src/db/migrations', opts?: { s path.isAbsolute(migrationsDirRel) ? migrationsDirRel : path.join(process.cwd(), migrationsDirRel) ) + if (!existsSync(dir)) return [] + // Security: Ensure migrations directory is within project boundaries const projectRoot = path.resolve(process.cwd()) - if (!dir.startsWith(projectRoot + path.sep) && dir !== projectRoot) { - throw new Error(`Security: Migration directory must be within project root. Attempted: ${dir}`) + let resolvedDir = dir + try { + resolvedDir = realpathSync(dir) + const resolvedProjectRoot = realpathSync(projectRoot) + if (resolvedDir !== resolvedProjectRoot && !resolvedDir.startsWith(resolvedProjectRoot + path.sep)) { + throw new MigrationDirSecurityError(`Security: Migration directory must be within project root. Attempted: ${dir}`) + } + } catch (error) { + if (error instanceof MigrationDirSecurityError) { + throw error + } + const detail = error instanceof Error ? error.message : String(error) + throw new Error(`Security: Failed to validate migration directory path: ${detail}`) } - - if (!existsSync(dir)) return [] - const files = readdirSync(dir) + const files = readdirSync(resolvedDir) .filter(f => f.toLowerCase().endsWith('.sql')) .sort() @@ -41,7 +59,7 @@ export function runMigrations(migrationsDirRel = 'src/db/migrations', opts?: { s const stopOnError = opts?.stopOnError ?? true for (const file of files) { if (applied.has(file)) continue - const full = path.join(dir, file) + const full = path.join(resolvedDir, file) const sql = readFileSync(full, 'utf-8') // Basic sanity checks for managed SQL files if (sql.length > 1_000_000) { diff --git a/src/db/nip46.ts b/src/db/nip46.ts index cd8c116..d68b0e7 100644 --- a/src/db/nip46.ts +++ b/src/db/nip46.ts @@ -34,21 +34,30 @@ let initializationPromise: Promise | null = null export async function initializeNip46DB(): Promise { if (!initializationPromise) { - initializationPromise = Promise.resolve().then(() => { - // runMigrations is synchronous, returns array of applied migrations - const applied = runMigrations('src/db/migrations') - if (applied.length > 0) { - console.log(`[nip46] Applied ${applied.length} migration(s):`, applied.join(', ')) - } + initializationPromise = (async () => { + try { + // runMigrations is synchronous, returns array of applied migrations + const applied = runMigrations('src/db/migrations') + if (applied.length > 0) { + console.log(`[nip46] Applied ${applied.length} migration(s):`, applied.join(', ')) + } - // Defensive check: Verify critical tables exist and recreate if missing - // This handles cases where migrations partially failed - verifyAndCreateMissingTables() - }) + // Defensive check: Verify critical tables exist and recreate if missing + // This handles cases where migrations partially failed + verifyAndCreateMissingTables() + } catch (error) { + initializationPromise = null + throw error + } + })() } return initializationPromise } +function normalizeClientPubkey(clientPubkey: string): string { + return (clientPubkey || '').trim().toLowerCase() +} + // Defensive function to ensure critical NIP46 tables exist function verifyAndCreateMissingTables(): void { const requiredTables = [ @@ -168,6 +177,36 @@ export interface Nip46RequestRecord { expires_at?: string | null } +function parseRelayArray(raw: string): string[] { + const parsed = JSON.parse(raw) + if (!Array.isArray(parsed)) { + throw new Error('[nip46] Data integrity violation: relays must be an array') + } + const relays: string[] = [] + for (const value of parsed) { + if (typeof value !== 'string') { + throw new Error('[nip46] Data integrity violation: relay entries must be strings') + } + relays.push(value) + } + return relays +} + +function parseBooleanMap(raw: string, label: string): Record { + const parsed = JSON.parse(raw) + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { + throw new Error(`[nip46] Data integrity violation: ${label} must be an object`) + } + const normalized: Record = {} + for (const [key, value] of Object.entries(parsed)) { + if (typeof value !== 'boolean') { + throw new Error(`[nip46] Data integrity violation: ${label}.${key} must be a boolean`) + } + normalized[key] = value + } + return normalized +} + function rowToSession(row: any): Nip46Session { let relays: string[] | null = null @@ -178,8 +217,11 @@ function rowToSession(row: any): Nip46Session { throw new Error('[nip46] Data integrity violation: Relay data exceeds maximum size') } try { - relays = JSON.parse(row.relays) + relays = parseRelayArray(row.relays) } catch (e) { + if (e instanceof Error && e.message.includes('Data integrity violation')) { + throw e + } // Log for debugging but don't expose error details to client console.error('[nip46] Failed to parse relays JSON, using null:', e) relays = null @@ -196,8 +238,11 @@ function rowToSession(row: any): Nip46Session { throw new Error('[nip46] Data integrity violation: Policy methods data exceeds maximum size') } try { - methods = JSON.parse(row.policy_methods) + methods = parseBooleanMap(row.policy_methods, 'policy_methods') } catch (e) { + if (e instanceof Error && e.message.includes('Data integrity violation')) { + throw e + } // Log for debugging but don't expose error details to client console.error('[nip46] Failed to parse policy_methods JSON, using empty:', e) methods = {} @@ -211,8 +256,11 @@ function rowToSession(row: any): Nip46Session { throw new Error('[nip46] Data integrity violation: Policy kinds data exceeds maximum size') } try { - kinds = JSON.parse(row.policy_kinds) + kinds = parseBooleanMap(row.policy_kinds, 'policy_kinds') } catch (e) { + if (e instanceof Error && e.message.includes('Data integrity violation')) { + throw e + } // Log for debugging but don't expose error details to client console.error('[nip46] Failed to parse policy_kinds JSON, using empty:', e) kinds = {} @@ -244,7 +292,7 @@ export function listSessions(userId: number | bigint, opts?: { includeRevoked?: } export function getSession(userId: number | bigint, client_pubkey: string): Nip46Session | null { - const key = (client_pubkey || '').trim().toLowerCase() + const key = normalizeClientPubkey(client_pubkey) if (!key || !/^[0-9a-f]{64}$/.test(key)) return null const row = db .prepare('SELECT * FROM nip46_sessions WHERE user_id = ? AND client_pubkey = ?') @@ -336,6 +384,40 @@ export function getNip46RequestById(id: string): Nip46RequestRecord | null { return row ?? null } +export function getPendingNip46RequestByClientId( + userId: number | bigint, + sessionPubkey: string, + clientRequestId: string +): Nip46RequestRecord | null { + const normalizedPubkey = normalizeClientPubkey(sessionPubkey) + const normalizedClientRequestId = clientRequestId.trim() + if (!normalizedPubkey || !/^[0-9a-f]{64}$/.test(normalizedPubkey) || !normalizedClientRequestId) { + return null + } + + const rows = db + .prepare( + `SELECT * FROM nip46_requests + WHERE user_id = ? AND session_pubkey = ? AND status = 'pending' + ORDER BY created_at DESC, id DESC + LIMIT 500` + ) + .all(userId, normalizedPubkey) as Nip46RequestRecord[] + + for (const row of rows) { + try { + const payload = JSON.parse(row.params) + const payloadId = payload?.id + if (payloadId !== undefined && String(payloadId) === normalizedClientRequestId) { + return row + } + } catch { + continue + } + } + return null +} + export function listNip46Requests( userId: number | bigint, opts?: { @@ -503,11 +585,12 @@ export function upsertSession(params: { } export function updatePolicy(userId: number | bigint, client_pubkey: string, policy: Nip46Policy): Nip46Session | null { + const normalizedKey = normalizeClientPubkey(client_pubkey) // Use transaction to ensure atomicity between UPDATE and SELECT db.exec('BEGIN') try { // Diff against existing to record grants/revocations - const existing = db.prepare('SELECT policy_methods, policy_kinds FROM nip46_sessions WHERE user_id = ? AND client_pubkey = ?').get(userId, client_pubkey) as { policy_methods: string | null, policy_kinds: string | null } | undefined + const existing = db.prepare('SELECT policy_methods, policy_kinds FROM nip46_sessions WHERE user_id = ? AND client_pubkey = ?').get(userId, normalizedKey) as { policy_methods: string | null, policy_kinds: string | null } | undefined // Return early if session doesn't exist (prevents orphan audit records) if (!existing) { @@ -540,10 +623,10 @@ export function updatePolicy(userId: number | bigint, client_pubkey: string, pol policy_kinds = COALESCE(?, policy_kinds), updated_at = CURRENT_TIMESTAMP WHERE user_id = ? AND client_pubkey = ? - `).run(methodsValue, kindsValue, userId, client_pubkey) + `).run(methodsValue, kindsValue, userId, normalizedKey) // Fetch the updated session within the transaction to ensure consistency - const row = db.prepare('SELECT * FROM nip46_sessions WHERE user_id = ? AND client_pubkey = ?').get(userId, client_pubkey) + const row = db.prepare('SELECT * FROM nip46_sessions WHERE user_id = ? AND client_pubkey = ?').get(userId, normalizedKey) const session = row ? rowToSession(row) : null // Commit the transaction @@ -555,12 +638,12 @@ export function updatePolicy(userId: number | bigint, client_pubkey: string, pol for (const k of Object.keys({ ...prevMethods, ...nextMethods })) { const before = !!prevMethods[k] const after = !!nextMethods[k] - if (before !== after) logSessionEvent(userId, client_pubkey, after ? 'grant_method' : 'revoke_method', k) + if (before !== after) logSessionEvent(userId, normalizedKey, after ? 'grant_method' : 'revoke_method', k) } for (const k of Object.keys({ ...prevKinds, ...nextKinds })) { const before = !!prevKinds[k] const after = !!nextKinds[k] - if (before !== after) logSessionEvent(userId, client_pubkey, after ? 'grant_kind' : 'revoke_kind', k) + if (before !== after) logSessionEvent(userId, normalizedKey, after ? 'grant_kind' : 'revoke_kind', k) } } catch {} @@ -572,6 +655,7 @@ export function updatePolicy(userId: number | bigint, client_pubkey: string, pol } export function updateStatus(userId: number | bigint, client_pubkey: string, status: Nip46Status, touchActive = false): Nip46Session | null { + const normalizedKey = normalizeClientPubkey(client_pubkey) // Use transaction to ensure atomicity between UPDATE and SELECT db.exec('BEGIN') try { @@ -580,7 +664,7 @@ export function updateStatus(userId: number | bigint, client_pubkey: string, sta UPDATE nip46_sessions SET status = ?, updated_at = CURRENT_TIMESTAMP, last_active_at = CASE WHEN ? THEN CURRENT_TIMESTAMP ELSE last_active_at END WHERE user_id = ? AND client_pubkey = ? - `).run(status, touchActive ? 1 : 0, userId, client_pubkey) + `).run(status, touchActive ? 1 : 0, userId, normalizedKey) // Check if UPDATE affected any rows before logging events const changed = db.query('SELECT changes() as c').get() as { c: number } | null @@ -590,14 +674,14 @@ export function updateStatus(userId: number | bigint, client_pubkey: string, sta } // Fetch the updated session within the transaction to ensure consistency - const row = db.prepare('SELECT * FROM nip46_sessions WHERE user_id = ? AND client_pubkey = ?').get(userId, client_pubkey) + const row = db.prepare('SELECT * FROM nip46_sessions WHERE user_id = ? AND client_pubkey = ?').get(userId, normalizedKey) const session = row ? rowToSession(row) : null // Commit the transaction db.exec('COMMIT') // Log the status change after successful commit (non-critical) - try { logSessionEvent(userId, client_pubkey, 'status_change', undefined, status) } catch {} + try { logSessionEvent(userId, normalizedKey, 'status_change', undefined, status) } catch {} return session } catch (e) { @@ -607,7 +691,8 @@ export function updateStatus(userId: number | bigint, client_pubkey: string, sta } export function deleteSession(userId: number | bigint, client_pubkey: string): boolean { - db.prepare('DELETE FROM nip46_sessions WHERE user_id = ? AND client_pubkey = ?').run(userId, client_pubkey) + const normalizedKey = normalizeClientPubkey(client_pubkey) + db.prepare('DELETE FROM nip46_sessions WHERE user_id = ? AND client_pubkey = ?').run(userId, normalizedKey) const changed = db.query('SELECT changes() as c').get() as { c: number } | null return !!changed && changed.c > 0 } @@ -619,10 +704,10 @@ export function deleteSession(userId: number | bigint, client_pubkey: string): b * @returns Number of sessions created in the window */ export function countUserSessionsInWindow(userId: number | bigint, windowMs: number): number { - const cutoff = new Date(Date.now() - windowMs).toISOString() + const cutoffEpochSeconds = Math.floor((Date.now() - windowMs) / 1000) const row = db.prepare( - 'SELECT COUNT(*) as count FROM nip46_sessions WHERE user_id = ? AND created_at >= ?' - ).get(userId, cutoff) as { count: number } | undefined + 'SELECT COUNT(*) as count FROM nip46_sessions WHERE user_id = ? AND CAST(strftime(\'%s\', created_at) AS INTEGER) >= ?' + ).get(userId, cutoffEpochSeconds) as { count: number } | undefined return row?.count || 0 } @@ -644,14 +729,15 @@ export interface Nip46SessionEvent { } export function logSessionEvent(userId: number | bigint, client_pubkey: string, event_type: string, detail?: string, value?: string) { + const normalizedKey = normalizeClientPubkey(client_pubkey) db.prepare(` INSERT INTO nip46_session_events (user_id, client_pubkey, event_type, detail, value) VALUES (?, ?, ?, ?, ?) - `).run(userId, client_pubkey, event_type, detail ?? null, value ?? null) + `).run(userId, normalizedKey, event_type, detail ?? null, value ?? null) } export function listSessionEvents(userId: number | bigint, client_pubkey: string, limit = 50): Nip46SessionEvent[] { - const key = (client_pubkey || '').trim().toLowerCase() + const key = normalizeClientPubkey(client_pubkey) const rows = db.prepare( 'SELECT * FROM nip46_session_events WHERE user_id = ? AND client_pubkey = ? ORDER BY created_at DESC, id DESC LIMIT ?' ).all(userId, key, limit) as any[] diff --git a/src/db/ui-event-log.ts b/src/db/ui-event-log.ts index acbeb13..f730432 100644 --- a/src/db/ui-event-log.ts +++ b/src/db/ui-event-log.ts @@ -438,10 +438,34 @@ export function createUiEventLogStore(dbConn: Database) { } } -const defaultStore = createUiEventLogStore(db) +let defaultStore: ReturnType | null = null -export const appendUiEventLogEntry = defaultStore.append -export const listUiEventLogEntries = defaultStore.list -export const getUiEventLogBlob = defaultStore.getBlob -export const exportUiEventLogChunk = defaultStore.exportChunk -export const pruneUiEventLog = defaultStore.prune +function getDefaultStore(): ReturnType { + if (!defaultStore) defaultStore = createUiEventLogStore(db) + return defaultStore +} + +export function appendUiEventLogEntry(entry: UiEventLogStreamEntry): { seq: number; dataHash: string | null } { + return getDefaultStore().append(entry) +} + +export function listUiEventLogEntries(opts?: { limit?: number; beforeSeq?: number; types?: string[] }): UiEventLogListResult { + return getDefaultStore().list(opts) +} + +export function getUiEventLogBlob(hash: string): { data: unknown; byteLength: number } | null { + return getDefaultStore().getBlob(hash) +} + +export function exportUiEventLogChunk(opts?: { + afterSeq?: number; + untilSeq?: number; + limit?: number; + types?: string[]; +}): { rows: UiEventLogExportRow[]; nextAfterSeq: number | null } { + return getDefaultStore().exportChunk(opts) +} + +export function pruneUiEventLog(opts?: { retentionDays?: number }): UiEventLogPruneResult | null { + return getDefaultStore().prune(opts) +} diff --git a/src/nip46/index.ts b/src/nip46/index.ts index e2699b6..1d3d9ac 100644 --- a/src/nip46/index.ts +++ b/src/nip46/index.ts @@ -2,6 +2,7 @@ import type { ServerBifrostNode } from '../routes/types.js' import { Nip46Service } from './service.js' let service: Nip46Service | null = null +let serviceInitOptions: InitOptions | null = null interface InitOptions { addServerLog: (type: string, message: string, data?: any) => void @@ -9,8 +10,23 @@ interface InitOptions { getNode: () => ServerBifrostNode | null } +function areInitOptionsEquivalent(a: InitOptions, b: InitOptions): boolean { + return ( + a.addServerLog === b.addServerLog && + a.broadcastEvent === b.broadcastEvent && + a.getNode === b.getNode + ) +} + export function initNip46Service(opts: InitOptions): Nip46Service { + if (service) { + if (!serviceInitOptions || !areInitOptionsEquivalent(serviceInitOptions, opts)) { + console.warn('Reusing existing Nip46Service with different init options.') + } + return service + } service = new Nip46Service(opts) + serviceInitOptions = { ...opts } return service } diff --git a/src/nip46/service.ts b/src/nip46/service.ts index e614650..471a08f 100644 --- a/src/nip46/service.ts +++ b/src/nip46/service.ts @@ -3,7 +3,7 @@ import type { ServerBifrostNode } from '../routes/types.js' import { createNip46Request, getSession, - getNip46RequestById, + getPendingNip46RequestByClientId, getNip46Relays, getTransportKey, mergeNip46Relays, @@ -74,10 +74,9 @@ function normalizeRequestedPolicy(input: any): Nip46Policy | null { const [name, arg] = token.split(':') if (!name) continue if (name === 'sign_event') { + methods.sign_event = true if (arg && /^\d+$/.test(arg)) { kinds[arg] = true - } else { - methods[name] = true } } else { methods[name] = true @@ -178,7 +177,7 @@ export class Nip46Service { private agent: any | null = null private activeUserId: number | bigint | null = null private currentRelays: string[] = [] - private starting = false + private startingPromise: Promise | null = null private stopping = false private started = false private readonly onRequestBound: (req: any) => void @@ -215,15 +214,21 @@ export class Nip46Service { async ensureStarted(): Promise { if (this.activeUserId == null) return - if (this.started || this.starting) return - this.starting = true - try { - await this.startInternal() - } catch (error) { - this.log('error', 'Failed to start NIP-46 service', { error: this.serializeError(error) }) - } finally { - this.starting = false + if (this.started) return + if (this.startingPromise) { + await this.startingPromise + return } + this.startingPromise = (async () => { + try { + await this.startInternal() + } catch (error) { + this.log('error', 'Failed to start NIP-46 service', { error: this.serializeError(error) }) + } finally { + this.startingPromise = null + } + })() + await this.startingPromise } async stop(): Promise { @@ -397,11 +402,23 @@ export class Nip46Service { this.agent = new SignerAgent(this.signer, config) this.registerAgentListeners() - - await this.agent.connect(relays) - this.started = true - this.currentRelays = relays - this.log('info', 'NIP-46 service started', { relays }) + try { + await this.agent.connect(relays) + this.started = true + this.currentRelays = relays + this.log('info', 'NIP-46 service started', { relays }) + } catch (error) { + try { + await this.agent?.close?.() + } catch (closeError) { + this.log('warn', 'Error closing NIP-46 agent after failed start', { error: this.serializeError(closeError) }) + } + this.removeAgentListeners() + this.agent = null + this.signer = null + this.started = false + throw error + } } private async loadRelays(userId: number | bigint): Promise { @@ -474,7 +491,7 @@ export class Nip46Service { return } - const existing = getNip46RequestById(String(req.id)) + const existing = getPendingNip46RequestByClientId(this.activeUserId, pubkey, String(req.id)) if (existing) { this.log('info', 'Ignoring duplicate NIP-46 request', { id: req.id }) return diff --git a/src/node/manager.ts b/src/node/manager.ts index 2e7209c..f7844a7 100644 --- a/src/node/manager.ts +++ b/src/node/manager.ts @@ -125,6 +125,22 @@ const CONNECTIVITY_PING_TIMEOUT = (() => { return 10000; // default 10s })(); +let simplePoolSubscribeManyLock: Promise = Promise.resolve(); + +async function withSimplePoolSubscribeManyLock(fn: () => Promise): Promise { + const previous = simplePoolSubscribeManyLock; + let release: (() => void) | undefined; + simplePoolSubscribeManyLock = new Promise((resolve) => { + release = resolve; + }); + await previous; + try { + return await fn(); + } finally { + release?.(); + } +} + async function respondToEchoRequest(node: ServerBifrostNode, msg: any): Promise { const requesterPubkey = msg?.env?.pubkey; if (typeof requesterPubkey !== 'string' || requesterPubkey.trim().length === 0) { @@ -635,6 +651,7 @@ function withTimeout(promise: Promise, timeoutMs: number): Promise { interface NodeHealth { lastActivity: Date; lastConnectivityCheck: Date; + connectivityMonitoringStart: number; isConnected: boolean; consecutiveConnectivityFailures: number; } @@ -642,6 +659,7 @@ interface NodeHealth { let nodeHealth: NodeHealth = { lastActivity: new Date(), lastConnectivityCheck: new Date(), + connectivityMonitoringStart: Date.now(), isConnected: true, consecutiveConnectivityFailures: 0 }; @@ -796,6 +814,7 @@ function updateNodeActivity(addServerLog: ReturnType, // Reset connectivity failures on real activity (not keepalive) if (!isKeepalive && nodeHealth.consecutiveConnectivityFailures > 0) { + nodeHealth.connectivityMonitoringStart = now.getTime(); nodeHealth.consecutiveConnectivityFailures = 0; nodeHealth.isConnected = true; addServerLog('info', 'Node activity detected - connectivity restored'); @@ -806,6 +825,14 @@ function updateNodeActivity(addServerLog: ReturnType, } } +function markConnectivityHealthy(now: Date): void { + if (nodeHealth.consecutiveConnectivityFailures > 0) { + nodeHealth.connectivityMonitoringStart = now.getTime(); + } + nodeHealth.isConnected = true; + nodeHealth.consecutiveConnectivityFailures = 0; +} + // Helper function to check if node and client are valid async function checkNodeValidity( node: ServerBifrostNode | null, @@ -974,120 +1001,108 @@ async function checkRelayConnectivity( ): Promise { const now = new Date(); nodeHealth.lastConnectivityCheck = now; + let requestRecreate = false; + let nodeValidation: { valid: boolean; client: any; shouldRecreate: boolean } = { valid: false, client: null, shouldRecreate: false }; try { // Step 1: Validate node and client - const nodeValidation = await checkNodeValidity(node, addServerLog); + nodeValidation = await checkNodeValidity(node, addServerLog); if (!nodeValidation.valid) { nodeHealth.isConnected = false; nodeHealth.consecutiveConnectivityFailures++; + requestRecreate = nodeValidation.shouldRecreate; + } else { + const { client } = nodeValidation; + const pool = client._pool || client.pool; - if (nodeValidation.shouldRecreate && nodeHealth.consecutiveConnectivityFailures >= 3 && nodeRecreateCallback) { - addServerLog('info', 'Recreating node after 3 failed checks'); - await nodeRecreateCallback(); + // Step 2: Check activity timeout + const activityCheck = checkActivityTimeout(now, addServerLog); + if (activityCheck.shouldRecreate) { + requestRecreate = true; + nodeHealth.consecutiveConnectivityFailures++; + nodeHealth.isConnected = false; } - return false; - } - const { client } = nodeValidation; - const pool = client._pool || client.pool; + // Step 3: Check and handle relay connections + const relayStatus = checkRelayConnectionStatus(pool, addServerLog); - // Step 2: Check activity timeout - const activityCheck = checkActivityTimeout(now, addServerLog); - if (activityCheck.shouldRecreate && nodeRecreateCallback) { - await nodeRecreateCallback(); - return false; - } - - // Step 3: Check and handle relay connections - const relayStatus = checkRelayConnectionStatus(pool, addServerLog); - - if (relayStatus.disconnectedRelays.length > 0) { - // Check if it's been too long since real activity with relay issues - const timeSinceRealActivity = now.getTime() - nodeHealth.lastActivity.getTime(); - const tooLongWithoutActivity = timeSinceRealActivity > 300000; // 5 minutes - - if (tooLongWithoutActivity) { - addServerLog('warning', `No real activity for ${Math.round(timeSinceRealActivity / 60000)} minutes and relays disconnected`); - nodeHealth.consecutiveConnectivityFailures++; + if (relayStatus.disconnectedRelays.length > 0) { + // Check if it's been too long since real activity with relay issues + const timeSinceRealActivity = now.getTime() - nodeHealth.lastActivity.getTime(); + const tooLongWithoutActivity = timeSinceRealActivity > 300000; // 5 minutes - if (nodeRecreateCallback) { - addServerLog('info', 'Recreating node due to prolonged inactivity with relay issues'); - await nodeRecreateCallback(); - return false; + if (tooLongWithoutActivity) { + addServerLog('warning', `No real activity for ${Math.round(timeSinceRealActivity / 60000)} minutes and relays disconnected`); + nodeHealth.consecutiveConnectivityFailures++; + requestRecreate = true; } - } - - // Attempt to reconnect disconnected relays - const stillDisconnected = await reconnectDisconnectedRelays(pool, relayStatus.disconnectedRelays, addServerLog); - if (stillDisconnected > 0) { - nodeHealth.consecutiveConnectivityFailures++; + // Attempt to reconnect disconnected relays + const stillDisconnected = await reconnectDisconnectedRelays(pool, relayStatus.disconnectedRelays, addServerLog); - if (nodeHealth.consecutiveConnectivityFailures >= 3 && nodeRecreateCallback) { - addServerLog('error', 'Unable to reconnect to relays after 3 attempts, recreating node'); - await nodeRecreateCallback(); - return false; + if (stillDisconnected > 0) { + nodeHealth.consecutiveConnectivityFailures++; + requestRecreate = true; + } else { + // Successfully reconnected + markConnectivityHealthy(now); } - return false; - } else { - // Successfully reconnected - nodeHealth.consecutiveConnectivityFailures = 0; + if (activityCheck.isIdle && nodeHealth.consecutiveConnectivityFailures > 0) { + nodeHealth.isConnected = false; + } } - } - // Step 4: Handle idle state with keep-alive ping - if (activityCheck.isIdle) { - const pingResult = await performKeepAlivePing(node, addServerLog); + // Step 4: Handle idle state with keep-alive ping + if (activityCheck.isIdle) { + const pingResult = await performKeepAlivePing(node, addServerLog); - if (!pingResult.hadPingCapability) { - // No ping capability - check if we have connected relays - if (relayStatus.hasConnectedRelays) { - nodeHealth.isConnected = true; - nodeHealth.consecutiveConnectivityFailures = 0; - return true; - } - - // No connected relays and no ping capability - nodeHealth.isConnected = false; - nodeHealth.consecutiveConnectivityFailures++; + if (!pingResult.hadPingCapability) { + // No ping capability - check if we have connected relays + if (relayStatus.hasConnectedRelays) { + markConnectivityHealthy(now); + return true; + } - if (nodeHealth.consecutiveConnectivityFailures >= 3 && nodeRecreateCallback) { - addServerLog('info', 'No connected relays and no ping capability, recreating node'); - await nodeRecreateCallback(); + // No connected relays and no ping capability + nodeHealth.isConnected = false; + nodeHealth.consecutiveConnectivityFailures++; + requestRecreate = true; + } else if (pingResult.success) { + markConnectivityHealthy(now); + return true; + } else { + // Ping failed + nodeHealth.isConnected = false; + nodeHealth.consecutiveConnectivityFailures++; + requestRecreate = true; } - return false; } - if (pingResult.success) { - nodeHealth.isConnected = true; - nodeHealth.consecutiveConnectivityFailures = 0; + // Everything looks good + if (!requestRecreate && nodeHealth.consecutiveConnectivityFailures === 0) { + markConnectivityHealthy(now); return true; - } else { - // Ping failed - nodeHealth.isConnected = false; - nodeHealth.consecutiveConnectivityFailures++; - - if (nodeHealth.consecutiveConnectivityFailures >= 3 && nodeRecreateCallback) { - addServerLog('info', 'Persistent ping failures, recreating node'); - await nodeRecreateCallback(); - } - return false; } } - // Everything looks good - nodeHealth.isConnected = true; - nodeHealth.consecutiveConnectivityFailures = 0; - return true; + if (requestRecreate && nodeHealth.consecutiveConnectivityFailures >= 3 && nodeRecreateCallback) { + addServerLog('info', 'Recreating node after sustained connectivity failures'); + nodeHealth.consecutiveConnectivityFailures = 0; + await nodeRecreateCallback(); + return false; + } + + return false; } catch (error) { addServerLog('error', 'Connectivity check error', error); nodeHealth.consecutiveConnectivityFailures++; + requestRecreate = true; - if (nodeHealth.consecutiveConnectivityFailures >= 3 && nodeRecreateCallback) { + if (requestRecreate && nodeHealth.consecutiveConnectivityFailures >= 3 && nodeRecreateCallback) { addServerLog('info', 'Connectivity errors exceeded threshold, recreating node'); + nodeHealth.consecutiveConnectivityFailures = 0; await nodeRecreateCallback(); } return false; @@ -1154,7 +1169,7 @@ function startConnectivityMonitoring( } } else if (isConnected && nodeHealth.consecutiveConnectivityFailures === 0) { // Log every 10 successful checks (10 minutes) - const timeSinceStart = Date.now() - nodeHealth.lastConnectivityCheck.getTime(); + const timeSinceStart = Date.now() - nodeHealth.connectivityMonitoringStart; const checkCount = Math.floor(timeSinceStart / CONNECTIVITY_CHECK_INTERVAL); if (checkCount % 10 === 0 && checkCount > 0) { addServerLog('debug', `Connectivity maintained for ${Math.round(timeSinceStart / 60000)} minutes`); @@ -1209,6 +1224,7 @@ function startConnectivityMonitoring( nodeHealth = { lastActivity: new Date(), lastConnectivityCheck: new Date(), + connectivityMonitoringStart: Date.now(), isConnected: true, consecutiveConnectivityFailures: 0 }; @@ -1707,6 +1723,7 @@ export function setupNodeEventListeners( // Catch-all for any other events - but exclude ping events since they're handled by message handler node.on('*', (event: any) => { + const isStringEvent = typeof event === 'string'; // Only log events that aren't already handled above, and exclude ping events to avoid duplicates if (event !== 'message' && event !== 'closed' && @@ -1717,11 +1734,13 @@ export function setupNodeEventListeners( event !== 'disconnect' && event !== 'reconnect' && event !== 'reconnecting' && - !event.startsWith('/ping/') && - !event.startsWith('/sign/') && - !event.startsWith('/ecdh/')) { + (!isStringEvent || ( + !event.startsWith('/ping/') && + !event.startsWith('/sign/') && + !event.startsWith('/ecdh/') + ))) { updateNodeActivity(addServerLog); - addServerLog('bifrost', `Bifrost event: ${event}`); + addServerLog('bifrost', `Bifrost event: ${String(event)}`); } }); @@ -2163,32 +2182,55 @@ export async function createNodeWithCredentials( autoReconnect: true // Enable auto-reconnection }; - // Normalize nostr-tools subscribeMany inputs so single-filter arrays are treated as plain objects. - try { - const poolProto: any = SimplePool?.prototype; - if (poolProto && !poolProto.__iglooFilterNormalizePatched) { - const originalSubscribeMany = poolProto.subscribeMany; - if (typeof originalSubscribeMany === 'function') { - poolProto.subscribeMany = function normalizedSubscribeMany(this: any, relays: any, filters: any, params: any) { + const result = await withSimplePoolSubscribeManyLock(async () => { + let restoreSubscribeMany: (() => void) | null = null; + try { + const poolProtoUnknown: unknown = SimplePool?.prototype; + const poolProto = poolProtoUnknown && typeof poolProtoUnknown === 'object' + ? poolProtoUnknown as { subscribeMany?: unknown } + : null; + const originalSubscribeMany: unknown = poolProto?.subscribeMany; + if (poolProto && typeof originalSubscribeMany === 'function') { + const originalSubscribeManyFn = originalSubscribeMany as ( + this: unknown, + relays: unknown, + filters: unknown, + params: unknown + ) => unknown; + // Scope filter normalization to this node-creation attempt only. + poolProto.subscribeMany = function normalizedSubscribeMany( + this: unknown, + relays: unknown, + filters: unknown, + params: unknown + ) { const normalizedFilters = Array.isArray(filters) && filters.length === 1 && filters[0] && typeof filters[0] === 'object' && !Array.isArray(filters[0]) ? filters[0] : filters; - return originalSubscribeMany.call(this, relays, normalizedFilters, params); + return originalSubscribeManyFn.call(this, relays, normalizedFilters, params); }; - poolProto.__iglooFilterNormalizePatched = true; + restoreSubscribeMany = () => { + poolProto.subscribeMany = originalSubscribeMany; + }; + } + } catch (patchError) { + if (addServerLog) { + addServerLog('warn', 'Failed to prepare SimplePool filter normalization', { + error: patchError instanceof Error ? patchError.message : String(patchError) + }); } } - } catch (patchError) { - if (addServerLog) { - addServerLog('warn', 'Failed to normalize SimplePool filters', { - error: patchError instanceof Error ? patchError.message : String(patchError) + + try { + return await createConnectedNode(enhancedConfig, { + enableLogging: false, // Disable internal logging to avoid duplication + logLevel: 'error' // Only log errors from igloo-core }); + } finally { + try { + restoreSubscribeMany?.(); + } catch {} } - } - - const result = await createConnectedNode(enhancedConfig, { - enableLogging: false, // Disable internal logging to avoid duplication - logLevel: 'error' // Only log errors from igloo-core }); if (result.node) { @@ -2415,6 +2457,7 @@ export function resetHealthMonitoring() { nodeHealth = { lastActivity: new Date(), lastConnectivityCheck: new Date(), + connectivityMonitoringStart: Date.now(), isConnected: true, consecutiveConnectivityFailures: 0 }; diff --git a/src/routes/admin.ts b/src/routes/admin.ts index 2a8e4e0..29c447c 100644 --- a/src/routes/admin.ts +++ b/src/routes/admin.ts @@ -24,6 +24,15 @@ interface RevokeApiKeyRequest { reason?: unknown; } +const KNOWN_ADMIN_PATHS = new Set([ + '/api/admin/whoami', + '/api/admin/users', + '/api/admin/users/delete', + '/api/admin/api-keys', + '/api/admin/api-keys/revoke', + '/api/admin/status', +]); + /** * Convert various input types into a normalized positive integer (number or bigint). * Accepts number, numeric string (e.g. "1", "42"), and bigint (e.g. 1n). @@ -76,6 +85,7 @@ function normalizeAuthUserId(auth?: RequestAuth | null): number | bigint | null if (typeof id === 'string' && /^\d+$/.test(id)) { try { const asBigInt = BigInt(id); + if (asBigInt <= 0n) return null; return asBigInt <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(asBigInt) : asBigInt; } catch { return null; @@ -126,13 +136,19 @@ export async function handleAdminRoute( // Check rate limit before admin authentication to prevent brute force attacks const rate = await checkRateLimit(req, 'auth', { clientIp: _context.clientIp }); if (!rate.allowed) { + const windowSecondsRaw = Number.parseInt(process.env.RATE_LIMIT_WINDOW || '900', 10); + const validatedWindowSeconds = Number.isFinite(windowSecondsRaw) ? windowSecondsRaw : 900; + const fallbackRetryAfterSeconds = Math.ceil(validatedWindowSeconds).toString(); + const retryAfterSeconds = typeof rate.resetAt === 'number' + ? Math.max(1, Math.ceil((rate.resetAt - Date.now()) / 1000)).toString() + : fallbackRetryAfterSeconds; return Response.json( { error: 'Rate limit exceeded. Try again later.' }, { status: 429, headers: { ...headers, - 'Retry-After': Math.ceil(parseInt(process.env.RATE_LIMIT_WINDOW || '900')).toString() + 'Retry-After': retryAfterSeconds } } ); @@ -159,7 +175,7 @@ export async function handleAdminRoute( // All admin routes require ADMIN_SECRET authentication const authHeader = req.headers.get('Authorization'); let adminSecret: string | undefined; - if (authHeader && authHeader.startsWith('Bearer ')) { + if (authHeader && authHeader.toLowerCase().startsWith('bearer ')) { adminSecret = authHeader.substring(7).trim(); if (adminSecret.length === 0) { adminSecret = undefined; @@ -272,7 +288,7 @@ export async function handleAdminRoute( createdAt: key.createdAt, updatedAt: key.updatedAt, lastUsedAt: key.lastUsedAt, - lastUsedIp: key.lastUsedIp, + lastUsedIp: null, revokedAt: key.revokedAt, revokedReason: key.revokedReason, createdByUserId: key.createdByUserId, @@ -329,9 +345,7 @@ export async function handleAdminRoute( ); } - const createdByAdminFlag = sessionAdmin || (typeof body.createdByAdmin === 'boolean' - ? body.createdByAdmin - : normalizedUserId == null); + const createdByAdminFlag = Boolean(sessionAdmin) || normalizedUserId == null; try { const result = createApiKey({ @@ -448,9 +462,10 @@ export async function handleAdminRoute( break; } + const isKnownPath = KNOWN_ADMIN_PATHS.has(url.pathname); return Response.json( - { error: 'Method not allowed' }, - { status: 405, headers } + { error: isKnownPath ? 'Method not allowed' : 'Not found' }, + { status: isKnownPath ? 405 : 404, headers } ); } catch (error) { console.error('Admin API Error:', error); diff --git a/src/routes/auth-factory.ts b/src/routes/auth-factory.ts index 207323a..e311300 100644 --- a/src/routes/auth-factory.ts +++ b/src/routes/auth-factory.ts @@ -78,7 +78,7 @@ export function createRequestAuth(params: { // Add secure getter for derivedKey with lazy vault retrieval // Only add if we have a direct key or password-based auth with sessionId - if (params.derivedKey != null || (params.sessionId && params.hasPassword)) { + if (params.derivedKey != null || params.sessionId) { Object.defineProperty(auth, 'getDerivedKey', { enumerable: false, configurable: false, diff --git a/src/routes/auth.ts b/src/routes/auth.ts index d496161..f04b79d 100644 --- a/src/routes/auth.ts +++ b/src/routes/auth.ts @@ -1,4 +1,4 @@ -import { randomBytes, timingSafeEqual, pbkdf2Sync } from 'crypto'; +import { randomBytes, timingSafeEqual, pbkdf2Sync, createHash } from 'crypto'; import { existsSync, mkdirSync, readFileSync, statSync, openSync, writeSync, fsyncSync, renameSync, unlinkSync, closeSync, chmodSync } from 'fs'; import path from 'path'; import { HEADLESS } from '../const.js'; @@ -230,6 +230,10 @@ function validateSessionSecret(): string | null { } const DEFAULT_RATE_LIMIT_MAX = HEADLESS ? 300 : 600; +const parsePositiveIntEnv = (value: string | undefined, fallback: number): number => { + const parsed = Number.parseInt(value ?? '', 10); + return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; +}; // Authentication configuration from environment variables export const AUTH_CONFIG = { @@ -243,13 +247,13 @@ export const AUTH_CONFIG = { // Session configuration SESSION_SECRET: validateSessionSecret(), - SESSION_TIMEOUT: parseInt(process.env.SESSION_TIMEOUT || '3600') * 1000, // Default 1 hour + SESSION_TIMEOUT: parsePositiveIntEnv(process.env.SESSION_TIMEOUT, 3600) * 1000, // Default 1 hour // Rate limiting RATE_LIMIT_ENABLED: process.env.RATE_LIMIT_ENABLED !== 'false', - RATE_LIMIT_WINDOW: parseInt(process.env.RATE_LIMIT_WINDOW || '900') * 1000, // 15 minutes + RATE_LIMIT_WINDOW: parsePositiveIntEnv(process.env.RATE_LIMIT_WINDOW, 900) * 1000, // 15 minutes // Default is 300 per 15m in headless mode, 600 per 15m when backed by the database; override for production - RATE_LIMIT_MAX: parseInt(process.env.RATE_LIMIT_MAX || String(DEFAULT_RATE_LIMIT_MAX)), + RATE_LIMIT_MAX: parsePositiveIntEnv(process.env.RATE_LIMIT_MAX, DEFAULT_RATE_LIMIT_MAX), }; // Use centralized crypto constants for consistency @@ -306,9 +310,27 @@ const sessionStore = new Map(); +function parseEnvInt(raw: string | undefined, fallback: number): number { + if (!raw) return fallback; + const parsed = Number.parseInt(raw, 10); + return Number.isFinite(parsed) ? parsed : fallback; +} + +function compareConstantTime(a: string, b: string): boolean { + const digestA = createHash('sha256').update(a).digest(); + const digestB = createHash('sha256').update(b).digest(); + return timingSafeEqual(digestA, digestB); +} + // Ephemeral derived key vault: TTL + bounded reads; zeroizes on removal -const AUTH_DERIVED_KEY_TTL_MS = Math.max(10_000, Math.min(10 * 60_000, parseInt(process.env.AUTH_DERIVED_KEY_TTL_MS || '120000'))); -const AUTH_DERIVED_KEY_MAX_READS = Math.max(1, Math.min(1000, parseInt(process.env.AUTH_DERIVED_KEY_MAX_READS || '100'))); +const AUTH_DERIVED_KEY_TTL_MS = Math.max( + 10_000, + Math.min(10 * 60_000, parseEnvInt(process.env.AUTH_DERIVED_KEY_TTL_MS, 120_000)) +); +const AUTH_DERIVED_KEY_MAX_READS = Math.max( + 1, + Math.min(1000, parseEnvInt(process.env.AUTH_DERIVED_KEY_MAX_READS, 100)) +); function getAuthDerivedKeyMaxRehydrations(): number { const fallback = 3; @@ -323,7 +345,7 @@ const VAULT_CLEANUP_INTERVAL_MS = Math.max( 30_000, // minimum 30 seconds Math.min( 10 * 60_000, // maximum 10 minutes - parseInt(process.env.VAULT_CLEANUP_INTERVAL_MS || '120000') // default 2 minutes + parseEnvInt(process.env.VAULT_CLEANUP_INTERVAL_MS, 120_000) // default 2 minutes ) ); @@ -437,6 +459,8 @@ export interface AuthResult { userId?: string | number; // Only JSON-serializable types error?: string; rateLimited?: boolean; + retryAfter?: number; + resetAt?: number; derivedKey?: Uint8Array; // Derived key for decryption operations (ephemeral - cleared after extraction) sessionId?: string; // Session ID for lazy vault retrieval hasPassword?: boolean; // Flag indicating if password-based derived key is available @@ -453,7 +477,7 @@ export async function checkRateLimit( req: Request, bucket: string = 'auth', opts?: { windowMs?: number; max?: number; clientIp?: string } -): Promise<{ allowed: boolean; remaining: number }> { +): Promise<{ allowed: boolean; remaining: number; resetAt?: number }> { if (!AUTH_CONFIG.RATE_LIMIT_ENABLED) { return { allowed: true, remaining: AUTH_CONFIG.RATE_LIMIT_MAX }; } @@ -469,7 +493,8 @@ export async function checkRateLimit( return { allowed: result.allowed, - remaining: result.remaining + remaining: result.remaining, + resetAt: result.resetAt }; } @@ -553,16 +578,15 @@ function authenticateBasicAuth(req: Request): AuthResult { try { const credentials = atob(authHeader.slice(6)); - const [username, password] = credentials.split(':'); + const separatorIndex = credentials.indexOf(':'); + if (separatorIndex === -1) { + return { authenticated: false, error: 'Invalid authorization header' }; + } + const username = credentials.slice(0, separatorIndex); + const password = credentials.slice(separatorIndex + 1); - const userValid = timingSafeEqual( - Buffer.from(username || ''), - Buffer.from(AUTH_CONFIG.BASIC_AUTH_USER) - ); - const passValid = timingSafeEqual( - Buffer.from(password || ''), - Buffer.from(AUTH_CONFIG.BASIC_AUTH_PASS) - ); + const userValid = compareConstantTime(username || '', AUTH_CONFIG.BASIC_AUTH_USER); + const passValid = compareConstantTime(password || '', AUTH_CONFIG.BASIC_AUTH_PASS); if (userValid && passValid) { return { authenticated: true, userId: username }; @@ -831,7 +855,14 @@ export async function authenticate(req: Request): Promise { } else { // In non-headless mode, check if database is initialized // If not initialized, allow access to onboarding routes only - const isOnboardingRoute = req.url.includes('/api/onboarding'); + const onboardingPath = (() => { + try { + return new URL(req.url).pathname; + } catch { + return ''; + } + })(); + const isOnboardingRoute = onboardingPath === '/api/onboarding' || onboardingPath.startsWith('/api/onboarding/'); try { const initialized = isDatabaseInitialized(); if (!initialized && !isOnboardingRoute) { @@ -848,7 +879,16 @@ export async function authenticate(req: Request): Promise { const rateLimit = await checkRateLimit(req); if (!rateLimit.allowed) { - return { authenticated: false, error: 'Rate limit exceeded', rateLimited: true }; + const retryAfter = typeof rateLimit.resetAt === 'number' && Number.isFinite(rateLimit.resetAt) + ? Math.max(0, Math.ceil((rateLimit.resetAt - Date.now()) / 1000)) + : undefined; + return { + authenticated: false, + error: 'Rate limit exceeded', + rateLimited: true, + retryAfter, + resetAt: rateLimit.resetAt + }; } // Try API Key first @@ -924,6 +964,21 @@ export async function handleLogin(req: Request): Promise { const { username, password, apiKey } = body; + const rate = await checkRateLimit(req); + if (!rate.allowed) { + return Response.json( + { error: 'Too many login attempts. Please try again later.' }, + { + status: 429, + headers: { + ...baseHeaders, + 'Retry-After': Math.ceil(AUTH_CONFIG.RATE_LIMIT_WINDOW / 1000).toString(), + 'Set-Cookie': `session=; HttpOnly; Path=/; ${process.env.NODE_ENV === 'production' ? 'Secure; ' : ''}SameSite=Strict; Max-Age=0` + } + } + ); + } + let authenticated = false; let userId: string | number | bigint = ''; let userPassword: string | undefined; // Store for database users @@ -962,22 +1017,6 @@ export async function handleLogin(req: Request): Promise { } } if (!authenticated && !HEADLESS && username && password && dbInitialized) { - // Check rate limit for database authentication attempts - const rate = await checkRateLimit(req); - if (!rate.allowed) { - return Response.json( - { error: 'Too many login attempts. Please try again later.' }, - { - status: 429, - headers: { - ...baseHeaders, - 'Retry-After': Math.ceil(parseInt(process.env.RATE_LIMIT_WINDOW || '900')).toString(), - 'Set-Cookie': `session=; HttpOnly; Path=/; ${process.env.NODE_ENV === 'production' ? 'Secure; ' : ''}SameSite=Strict; Max-Age=0` - } - } - ); - } - try { const dbResult = await authenticateUser(username, password); // authenticateUser returns a structured result, not throwing for auth failures @@ -1009,7 +1048,7 @@ export async function handleLogin(req: Request): Promise { return Response.json({ success: false, error: 'Database temporarily unavailable. Please try again.' - }, { status: 503 }); // 503 Service Unavailable + }, { status: 503, headers: baseHeaders }); // 503 Service Unavailable } // For unexpected errors, log but don't expose details @@ -1025,14 +1064,8 @@ export async function handleLogin(req: Request): Promise { // Try env-based basic auth (headless mode or fallback) if (!authenticated && username && password && AUTH_CONFIG.BASIC_AUTH_USER && AUTH_CONFIG.BASIC_AUTH_PASS) { - const userValid = timingSafeEqual( - Buffer.from(username), - Buffer.from(AUTH_CONFIG.BASIC_AUTH_USER) - ); - const passValid = timingSafeEqual( - Buffer.from(password), - Buffer.from(AUTH_CONFIG.BASIC_AUTH_PASS) - ); + const userValid = compareConstantTime(username, AUTH_CONFIG.BASIC_AUTH_USER); + const passValid = compareConstantTime(password, AUTH_CONFIG.BASIC_AUTH_PASS); if (userValid && passValid) { authenticated = true; @@ -1093,12 +1126,29 @@ export function handleLogout(req: Request): Response { 'Vary': mergedVary, 'Access-Control-Allow-Methods': 'POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Session-ID', + }; + + const logoutHeaders = { + ...headers, 'Set-Cookie': `session=; HttpOnly; Path=/; ${process.env.NODE_ENV === 'production' ? 'Secure; ' : ''}SameSite=Strict; Max-Age=0` }; if (req.method === 'OPTIONS') { return new Response(null, { status: 204, headers }); } + + if (req.method !== 'POST') { + return Response.json( + { error: 'Method not allowed' }, + { + status: 405, + headers: { + ...headers, + 'Allow': 'POST, OPTIONS' + } + } + ); + } const sessionId = req.headers.get('x-session-id') || extractSessionFromCookie(req); @@ -1113,7 +1163,7 @@ export function handleLogout(req: Request): Response { try { zeroizeVaultEntryAndDelete(sessionId) } catch {} } - return Response.json({ success: true }, { headers }); + return Response.json({ success: true }, { headers: logoutHeaders }); } // Authentication middleware wrapper (deprecated - use explicit auth parameters instead) @@ -1184,7 +1234,12 @@ function getAvailableAuthMethods(): string[] { } // Status endpoint for authentication info -export function getAuthStatus(): object { +export function getAuthStatus(): { + enabled: boolean; + methods: string[]; + rateLimiting: boolean; + sessionTimeout: number; +} { return { enabled: AUTH_CONFIG.ENABLED, methods: getAvailableAuthMethods(), diff --git a/src/routes/docs.ts b/src/routes/docs.ts index 3f99b91..78c5a1e 100644 --- a/src/routes/docs.ts +++ b/src/routes/docs.ts @@ -90,7 +90,7 @@ const swaggerUIHtml = (specUrl: string) => ` var el = document.getElementById('swagger-ui') || document.body; el.innerHTML = \`

API docs assets not found

-

Run bun run docs:vendor to fetch Swagger UI files into static/docs/, then refresh this page.

+

Load the Swagger UI assets from a CDN or run bun run docs:vendor to fetch local copies.

You can still view the raw spec: openapi.json or openapi.yaml.

\`; } @@ -169,7 +169,7 @@ export async function handleDocsRoute(req: Request, url: URL): Promise { @@ -92,9 +92,17 @@ export async function handleEnvRoute(req: Request, url: URL, context: Privileged // but we keep the broader classification for clarity. const isWrite = req.method === 'POST' || req.method === 'PUT' || req.method === 'DELETE'; // Resolve authenticated DB user id (database mode only) - const authenticatedNumericUserId = (!HEADLESS && auth?.authenticated && ( - typeof auth.userId === 'number' || (typeof auth.userId === 'string' && /^\d+$/.test(auth.userId)) - )) ? BigInt(auth!.userId as any) : null; + const authenticatedNumericUserId = (() => { + if (HEADLESS || !auth?.authenticated) return null; + if (typeof auth.userId === 'number') { + if (!Number.isInteger(auth.userId) || auth.userId <= 0) return null; + return BigInt(auth.userId); + } + if (typeof auth.userId === 'string' && /^[1-9]\d*$/.test(auth.userId)) { + return BigInt(auth.userId); + } + return null; + })(); const isRoleAdmin = await (async () => { try { if (authenticatedNumericUserId === null) return false; @@ -121,10 +129,13 @@ export async function handleEnvRoute(req: Request, url: URL, context: Privileged if (!AUTH_CONFIG.API_KEY) return false; const provided = extractApiKeyFromHeaders(r); if (!provided) return false; - const a = Buffer.from(provided); - const b = Buffer.from(AUTH_CONFIG.API_KEY); - if (a.length !== b.length) return false; - try { return timingSafeEqual(a, b); } catch { return false; } + try { + const a = createHash('sha256').update(provided).digest(); + const b = createHash('sha256').update(AUTH_CONFIG.API_KEY).digest(); + return timingSafeEqual(a, b); + } catch { + return false; + } }; const hasValidHeadlessBasic = (r: Request): boolean => { @@ -148,6 +159,18 @@ export async function handleEnvRoute(req: Request, url: URL, context: Privileged hasValidHeadlessApiKey(r) || hasValidHeadlessBasic(r) ); + const extractNonEmptyAdminSecret = (r: Request): string | undefined => { + const headerSecret = r.headers.get('X-Admin-Secret')?.trim(); + if (headerSecret && headerSecret.length > 0) return headerSecret; + + const authHeader = r.headers.get('Authorization'); + if (!authHeader) return undefined; + const bearerMatch = authHeader.match(/^Bearer\s+(.+)$/i); + if (!bearerMatch) return undefined; + const bearerToken = bearerMatch[1]?.trim(); + return bearerToken && bearerToken.length > 0 ? bearerToken : undefined; + }; + const isHeadlessReadAuthorized = (r: Request, a?: RequestAuth | null): boolean => { // If global auth is enabled and a session is present, allow; otherwise require API key or Basic. if (AUTH_CONFIG.ENABLED && a?.authenticated) return true; @@ -276,18 +299,11 @@ export async function handleEnvRoute(req: Request, url: URL, context: Privileged const env = await readEnvFile(); const { validKeys, invalidKeys: rejectedKeys } = validateEnvKeys(Object.keys(body)); - if (validKeys.includes('RELAYS') && body.RELAYS !== undefined) { - const relayValidation = validateRelayUrls(body.RELAYS); - if (!relayValidation.valid) { - return Response.json({ success: false, error: relayValidation.error }, { status: 400, headers }); - } - } - // DB mode privilege gate for env writes (no legacy fallback): // - allow with valid ADMIN_SECRET (header: X-Admin-Secret or Bearer token), or // - allow when the authenticated DB user has role=admin. // validateAdminSecret() returns false when the header is missing; there is no bypass. - const adminSecret = req.headers.get('X-Admin-Secret') ?? req.headers.get('Authorization')?.replace(/^Bearer\s+/i, ''); + const adminSecret = extractNonEmptyAdminSecret(req); const isAdminSecret = await validateAdminSecret(adminSecret); if (!isAdminSecret && !isRoleAdmin) { return Response.json( @@ -296,6 +312,30 @@ export async function handleEnvRoute(req: Request, url: URL, context: Privileged ); } + if (validKeys.includes('RELAYS') && body.RELAYS !== undefined) { + const relayValidation = validateRelayUrls(body.RELAYS); + if (!relayValidation.valid) { + return Response.json({ success: false, error: relayValidation.error }, { status: 400, headers }); + } + if (!relayValidation.urls || relayValidation.urls.length === 0) { + return Response.json({ success: false, error: 'At least one relay URL is required' }, { status: 400, headers }); + } + } + + if (validKeys.includes('GROUP_CRED') && body.GROUP_CRED !== undefined) { + const groupValidation = validateGroup(body.GROUP_CRED); + if (!groupValidation.isValid) { + return Response.json({ success: false, error: 'Invalid GROUP_CRED' }, { status: 400, headers }); + } + } + + if (validKeys.includes('SHARE_CRED') && body.SHARE_CRED !== undefined) { + const shareValidation = validateShare(body.SHARE_CRED); + if (!shareValidation.isValid) { + return Response.json({ success: false, error: 'Invalid SHARE_CRED' }, { status: 400, headers }); + } + } + for (const key of validKeys) { if (body[key] !== undefined) { env[key] = body[key]; @@ -322,14 +362,6 @@ export async function handleEnvRoute(req: Request, url: URL, context: Privileged return Response.json({ success: false, message: 'Failed to update .env file' }, { status: 500, headers }); } - // Headless writes must be authorized by API key or Basic (sessions are not sufficient) - if (HEADLESS && !hasHeadlessWriteAuthorization(req)) { - return Response.json( - { error: 'Authentication required' }, - { status: 401, headers } - ); - } - let body; try { body = await parseJsonRequestBody(req); @@ -348,6 +380,23 @@ export async function handleEnvRoute(req: Request, url: URL, context: Privileged if (!relayValidation.valid) { return Response.json({ success: false, error: relayValidation.error }, { status: 400, headers }); } + if (!relayValidation.urls || relayValidation.urls.length === 0) { + return Response.json({ success: false, error: 'At least one relay URL is required' }, { status: 400, headers }); + } + } + + if (validKeys.includes('GROUP_CRED') && body.GROUP_CRED !== undefined) { + const groupValidation = validateGroup(body.GROUP_CRED); + if (!groupValidation.isValid) { + return Response.json({ success: false, error: 'Invalid GROUP_CRED' }, { status: 400, headers }); + } + } + + if (validKeys.includes('SHARE_CRED') && body.SHARE_CRED !== undefined) { + const shareValidation = validateShare(body.SHARE_CRED); + if (!shareValidation.isValid) { + return Response.json({ success: false, error: 'Invalid SHARE_CRED' }, { status: 400, headers }); + } } for (const key of validKeys) { @@ -361,92 +410,103 @@ export async function handleEnvRoute(req: Request, url: URL, context: Privileged if (updatingCredentials) { // Set the timestamp explicitly here to avoid relying on downstream helpers // for correctness, then perform a single write. - (env as any).CREDENTIALS_SAVED_AT = new Date().toISOString(); + env.CREDENTIALS_SAVED_AT = new Date().toISOString(); } - const writeOk = await writeEnvFile(env); - if (writeOk) { - try { - if (validKeys.includes('FROSTR_SIGN_TIMEOUT') && typeof env.FROSTR_SIGN_TIMEOUT === 'string') { - process.env.FROSTR_SIGN_TIMEOUT = env.FROSTR_SIGN_TIMEOUT; - } - if (validKeys.includes('ALLOWED_ORIGINS') && typeof env.ALLOWED_ORIGINS === 'string') { - process.env.ALLOWED_ORIGINS = env.ALLOWED_ORIGINS; - } - if (updatingRelays) { - const relaysVal = (env as any).RELAYS; - if (Array.isArray(relaysVal)) { - process.env.RELAYS = relaysVal.join(','); - } else if (typeof relaysVal === 'string') { - process.env.RELAYS = relaysVal; - } - } - } catch {} + const writeOk = await writeEnvFile(env); + if (!writeOk) { + return Response.json({ success: false, message: 'Failed to update .env file' }, { status: 500, headers }); + } - if (updatingCredentials || updatingRelays) { - try { - // Make restart intent explicit for observability and reviews - context.addServerLog('info', 'Recreating Bifrost node due to env changes', { - updatingCredentials, - updatingRelays - }); - - const echoPayload = (() => { - if (!updatingCredentials) return null; - const groupCred = typeof env.GROUP_CRED === 'string' ? env.GROUP_CRED : null; - const shareCred = typeof env.SHARE_CRED === 'string' ? env.SHARE_CRED : null; - if (!groupCred || !shareCred) return null; - const relaysArray = normalizeRelayListForEcho(env.RELAYS); - const relaysEnvValue = Array.isArray(env.RELAYS) - ? env.RELAYS.join(',') - : typeof env.RELAYS === 'string' - ? env.RELAYS - : undefined; - return { - groupCred, - shareCred, - relaysArray, - relaysEnvValue, - contextLabel: HEADLESS ? 'headless env credential update' : 'env credential update' - }; - })(); - - // Serialize node restart under the global node lock. createAndConnectServerNode() - // calls context.updateNode(newNode), which performs prior-node cleanup and - // listener re-wiring atomically to avoid resource leaks or races. - await executeUnderNodeLock(async () => { - await createAndConnectServerNode(env, context); - }, context); - - if (echoPayload) { - const echoOptions = { - relays: echoPayload.relaysArray, - relaysEnv: echoPayload.relaysEnvValue, - addServerLog: context.addServerLog, - contextLabel: echoPayload.contextLabel, - timeoutMs: 30000 - } as const; - sendSelfEcho(echoPayload.groupCred, echoPayload.shareCred, echoOptions).catch((error) => { - try { context.addServerLog('warn', 'Self-echo failed after env credential update', error); } catch {} - }); - broadcastShareEcho(echoPayload.groupCred, echoPayload.shareCred, echoOptions).catch((error) => { - try { context.addServerLog('warn', 'Credential echo broadcast failed after env credential update', error); } catch {} - }); - } - } catch (error) { - context.addServerLog('error', 'Error recreating Bifrost node', error); - throw (error instanceof Error) ? error : new Error(String(error)); + try { + if (validKeys.includes('FROSTR_SIGN_TIMEOUT') && typeof env.FROSTR_SIGN_TIMEOUT === 'string') { + process.env.FROSTR_SIGN_TIMEOUT = env.FROSTR_SIGN_TIMEOUT; + } + if (validKeys.includes('ALLOWED_ORIGINS') && typeof env.ALLOWED_ORIGINS === 'string') { + process.env.ALLOWED_ORIGINS = env.ALLOWED_ORIGINS; + } + if (validKeys.includes('GROUP_CRED')) { + if (typeof env.GROUP_CRED === 'string') process.env.GROUP_CRED = env.GROUP_CRED; + else delete process.env.GROUP_CRED; + } + if (validKeys.includes('SHARE_CRED')) { + if (typeof env.SHARE_CRED === 'string') process.env.SHARE_CRED = env.SHARE_CRED; + else delete process.env.SHARE_CRED; + } + if (updatingRelays) { + const relaysVal = (env as any).RELAYS; + if (Array.isArray(relaysVal)) { + process.env.RELAYS = relaysVal.join(','); + } else if (typeof relaysVal === 'string') { + process.env.RELAYS = relaysVal; + } else { + delete process.env.RELAYS; } } + } catch {} - const responseMessage = rejectedKeys.length > 0 - ? `Environment variables updated. Rejected unauthorized keys: ${rejectedKeys.join(', ')}` - : 'Environment variables updated'; + if (updatingCredentials || updatingRelays) { + try { + // Make restart intent explicit for observability and reviews + context.addServerLog('info', 'Recreating Bifrost node due to env changes', { + updatingCredentials, + updatingRelays + }); + + // Serialize node restart under the global node lock. + // createAndConnectServerNode() updates the active node from the new env. + await executeUnderNodeLock(async () => { + await createAndConnectServerNode(env, context); + }, context); + } catch (error) { + context.addServerLog('error', 'Error recreating Bifrost node', error); + return Response.json({ success: false, message: 'Failed to recreate Bifrost node' }, { status: 500, headers }); + } + } - return Response.json({ success: true, message: responseMessage, rejectedKeys: rejectedKeys.length > 0 ? rejectedKeys : undefined }, { headers }); + if (updatingCredentials || updatingRelays) { + const echoPayload = (() => { + if (!updatingCredentials) return null; + const groupCred = typeof env.GROUP_CRED === 'string' ? env.GROUP_CRED : null; + const shareCred = typeof env.SHARE_CRED === 'string' ? env.SHARE_CRED : null; + if (!groupCred || !shareCred) return null; + const relaysArray = normalizeRelayListForEcho(env.RELAYS); + const relaysEnvValue = Array.isArray(env.RELAYS) + ? env.RELAYS.join(',') + : typeof env.RELAYS === 'string' + ? env.RELAYS + : undefined; + return { + groupCred, + shareCred, + relaysArray, + relaysEnvValue, + contextLabel: HEADLESS ? 'headless env credential update' : 'env credential update' + }; + })(); + + if (echoPayload) { + const echoOptions = { + relays: echoPayload.relaysArray, + relaysEnv: echoPayload.relaysEnvValue, + addServerLog: context.addServerLog, + contextLabel: echoPayload.contextLabel, + timeoutMs: 30000 + } as const; + sendSelfEcho(echoPayload.groupCred, echoPayload.shareCred, echoOptions).catch((error) => { + try { context.addServerLog('warn', 'Self-echo failed after env credential update', error); } catch {} + }); + broadcastShareEcho(echoPayload.groupCred, echoPayload.shareCred, echoOptions).catch((error) => { + try { context.addServerLog('warn', 'Credential echo broadcast failed after env credential update', error); } catch {} + }); + } } - return Response.json({ success: false, message: 'Failed to update .env file' }, { status: 500, headers }); + const responseMessage = rejectedKeys.length > 0 + ? `Environment variables updated. Rejected unauthorized keys: ${rejectedKeys.join(', ')}` + : 'Environment variables updated'; + + return Response.json({ success: true, message: responseMessage, rejectedKeys: rejectedKeys.length > 0 ? rejectedKeys : undefined }, { headers }); } break; @@ -551,6 +611,10 @@ export async function handleEnvRoute(req: Request, url: URL, context: Privileged env.GROUP_CRED = groupCredential; if (await writeEnvFileWithTimestamp(env)) { + try { + process.env.SHARE_CRED = shareCredential; + process.env.GROUP_CRED = groupCredential; + } catch {} try { // Serialize node restart under the global node lock; updateNode inside // createAndConnectServerNode() handles teardown of any existing node. @@ -629,8 +693,7 @@ export async function handleEnvRoute(req: Request, url: URL, context: Privileged ); } if (!HEADLESS) { - const adminSecret = req.headers.get('X-Admin-Secret') ?? - req.headers.get('Authorization')?.replace(/^Bearer\s+/i, ''); + const adminSecret = extractNonEmptyAdminSecret(req); const isAdminSecret = await validateAdminSecret(adminSecret); if (!isAdminSecret && !isRoleAdmin) { return Response.json( @@ -670,6 +733,15 @@ export async function handleEnvRoute(req: Request, url: URL, context: Privileged } if (await writeEnvFile(env)) { + try { + for (const key of validKeys) { + if (key === 'GROUP_CRED') delete process.env.GROUP_CRED; + if (key === 'SHARE_CRED') delete process.env.SHARE_CRED; + if (key === 'RELAYS') delete process.env.RELAYS; + if (key === 'ALLOWED_ORIGINS') delete process.env.ALLOWED_ORIGINS; + if (key === 'FROSTR_SIGN_TIMEOUT') delete process.env.FROSTR_SIGN_TIMEOUT; + } + } catch {} // If credentials were deleted, clean up the node if (deletingCredentials) { try { diff --git a/src/routes/index.ts b/src/routes/index.ts index 8183c72..3ab363e 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -65,6 +65,24 @@ export async function handleRequest( 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-API-Key, X-Session-ID', 'Vary': mergedVary, }; + const parsedRateLimitWindowSeconds = Number.parseInt(process.env.RATE_LIMIT_WINDOW ?? '', 10); + const retryAfterSeconds = ( + Number.isFinite(parsedRateLimitWindowSeconds) && parsedRateLimitWindowSeconds > 0 + ? Math.ceil(parsedRateLimitWindowSeconds) + : 900 + ).toString(); + const hasAuthHint = (() => { + const authz = req.headers.get('authorization'); + const apiKey = req.headers.get('x-api-key'); + const sessionHeader = req.headers.get('x-session-id'); + const cookie = req.headers.get('cookie'); + return Boolean( + (authz && authz.trim().length > 0) || + (apiKey && apiKey.trim().length > 0) || + (sessionHeader && sessionHeader.trim().length > 0) || + (cookie && /(?:^|;\s*)session=/.test(cookie)) + ); + })(); // Handle preflight OPTIONS request for all API endpoints if (req.method === 'OPTIONS' && url.pathname.startsWith('/api/')) { @@ -85,7 +103,7 @@ export async function handleRequest( status: 429, headers: { ...headers, - 'Retry-After': Math.ceil(parseInt(process.env.RATE_LIMIT_WINDOW || '900')).toString() + 'Retry-After': retryAfterSeconds } }); } @@ -108,15 +126,38 @@ export async function handleRequest( return Response.json(getAuthStatus(), { headers }); } + if (url.pathname === '/api/events' && req.headers.get('upgrade') !== 'websocket') { + return Response.json({ error: 'Not Found' }, { status: 404, headers }); + } + // Handle API documentation (require auth in production for security) if (url.pathname.startsWith('/api/docs')) { // Require authentication for docs in production if (AUTH_CONFIG.ENABLED && process.env.NODE_ENV === 'production') { const authResult = await authenticate(req); if (!authResult.authenticated) { + if (authResult.rateLimited) { + const retryAfterSeconds = typeof authResult.retryAfter === 'number' ? authResult.retryAfter : 60; + const retryHeaders = { + ...headers, + 'Retry-After': String(retryAfterSeconds) + }; + return Response.json( + { + error: 'Rate limit exceeded', + retryAfter: retryAfterSeconds, + resetAt: authResult.resetAt + }, + { + status: 429, + headers: retryHeaders + } + ); + } + const authStatus = getAuthStatus(); return Response.json({ error: 'Authentication required for API documentation in production', - authMethods: getAuthStatus() + authMethods: authStatus.methods }, { status: 401, headers @@ -190,147 +231,146 @@ export async function handleRequest( // Admin endpoints have their own ADMIN_SECRET authentication const isAdminEndpoint = url.pathname.startsWith('/api/admin'); - // Authentication check for API endpoints (skip public endpoints, status, and admin) - if (url.pathname.startsWith('/api/') && AUTH_CONFIG.ENABLED && !isPublicEndpoint && !isStatusEndpoint && !isAdminEndpoint) { - const authResult = await authenticate(req); - - if (authResult.rateLimited) { - const response = Response.json({ - error: 'Rate limit exceeded. Try again later.' - }, { - status: 429, - headers: { - ...headers, - 'Retry-After': Math.ceil(parseInt(process.env.RATE_LIMIT_WINDOW || '900')).toString() - } - }); - finalizeAuth(); - return response; - } - - if (!authResult.authenticated) { - // Don't set WWW-Authenticate header to avoid browser's native auth dialog - // The frontend will handle authentication through its own UI - const response = Response.json({ - error: authResult.error || 'Authentication required', - authMethods: getAuthStatus() - }, { - status: 401, - headers - }); - finalizeAuth(); - return response; - } - - authInfo = createRequestAuth({ - userId: authResult.userId, - authenticated: true, - derivedKey: authResult.derivedKey ? authResult.derivedKey : undefined, - sessionId: authResult.sessionId, - hasPassword: authResult.hasPassword - }); - } else if (isStatusEndpoint && AUTH_CONFIG.ENABLED) { - // Special handling for /api/status: attempt authentication if headers are present - // but don't require it (allow unauthenticated health checks) - try { + try { + // Authentication check for API endpoints (skip public endpoints, status, and admin) + if (url.pathname.startsWith('/api/') && AUTH_CONFIG.ENABLED && !isPublicEndpoint && !isStatusEndpoint && !isAdminEndpoint) { const authResult = await authenticate(req); - // Only use auth info if authentication actually succeeded (not rate limited or failed) - if (authResult.authenticated && !authResult.rateLimited) { - // Create auth info with secure ephemeral storage for secrets - authInfo = createRequestAuth({ - userId: authResult.userId, - authenticated: true, - derivedKey: authResult.derivedKey ? authResult.derivedKey : undefined, - sessionId: authResult.sessionId, - hasPassword: authResult.hasPassword + if (authResult.rateLimited) { + return Response.json({ + error: 'Rate limit exceeded. Try again later.' + }, { + status: 429, + headers: { + ...headers, + 'Retry-After': retryAfterSeconds + } + }); + } + + if (!authResult.authenticated) { + // Don't set WWW-Authenticate header to avoid browser's native auth dialog + // The frontend will handle authentication through its own UI + const authStatus = getAuthStatus(); + return Response.json({ + error: authResult.error || 'Authentication required', + authMethods: authStatus.methods + }, { + status: 401, + headers }); } - // If authentication failed or was rate limited, authInfo remains null (unauthenticated access) - } catch (error) { - // If authentication throws an error, allow unauthenticated access - // Authentication attempt failed, allowing unauthenticated access for health checks + + authInfo = createRequestAuth({ + userId: authResult.userId, + authenticated: true, + derivedKey: authResult.derivedKey ? authResult.derivedKey : undefined, + sessionId: authResult.sessionId, + hasPassword: authResult.hasPassword + }); + } else if (isStatusEndpoint && AUTH_CONFIG.ENABLED) { + // Special handling for /api/status: attempt authentication if headers are present + // but don't require it (allow unauthenticated health checks) + if (hasAuthHint) { + try { + const authResult = await authenticate(req); + + // Only use auth info if authentication actually succeeded (not rate limited or failed) + if (authResult.authenticated && !authResult.rateLimited) { + // Create auth info with secure ephemeral storage for secrets + authInfo = createRequestAuth({ + userId: authResult.userId, + authenticated: true, + derivedKey: authResult.derivedKey ? authResult.derivedKey : undefined, + sessionId: authResult.sessionId, + hasPassword: authResult.hasPassword + }); + } + // If authentication failed or was rate limited, authInfo remains null (unauthenticated access) + } catch (error) { + // If authentication throws an error, allow unauthenticated access + // Authentication attempt failed, allowing unauthenticated access for health checks + } + } } - } - // Note: Authentication is now handled above for all non-public API endpoints + // Note: Authentication is now handled above for all non-public API endpoints - // Handle user routes (database mode only) - if (!HEADLESS && url.pathname.startsWith('/api/user')) { - const userResult = await handleUserRoute(req, url, privilegedContext, authInfo); - if (userResult) { - finalizeAuth(); - return userResult; + // Handle user routes (database mode only) + if (!HEADLESS && url.pathname.startsWith('/api/user')) { + const userResult = await handleUserRoute(req, url, privilegedContext, authInfo); + if (userResult) { + return userResult; + } } - } - // Handle admin routes (database mode only). Admin routes primarily use ADMIN_SECRET, - // but when a valid session exists for an admin user we allow that too. - if (!HEADLESS && url.pathname.startsWith('/api/admin')) { - // Attempt optional authentication for admin endpoints to support session-admin access. - // Do not enforce auth result here; handleAdminRoute will decide based on ADMIN_SECRET or session. - if (AUTH_CONFIG.ENABLED && !authInfo) { - try { - const adminAuth = await authenticate(req); - if (adminAuth.authenticated && !adminAuth.rateLimited) { - authInfo = createRequestAuth({ - userId: adminAuth.userId, - authenticated: true, - derivedKey: adminAuth.derivedKey ? adminAuth.derivedKey : undefined, - sessionId: adminAuth.sessionId, - hasPassword: adminAuth.hasPassword - }); - } - } catch {} - } + // Handle admin routes (database mode only). Admin routes primarily use ADMIN_SECRET, + // but when a valid session exists for an admin user we allow that too. + if (!HEADLESS && url.pathname.startsWith('/api/admin')) { + // Attempt optional authentication for admin endpoints to support session-admin access. + // Do not enforce auth result here; handleAdminRoute will decide based on ADMIN_SECRET or session. + if (AUTH_CONFIG.ENABLED && !authInfo) { + try { + const adminAuth = await authenticate(req); + if (adminAuth.authenticated && !adminAuth.rateLimited) { + authInfo = createRequestAuth({ + userId: adminAuth.userId, + authenticated: true, + derivedKey: adminAuth.derivedKey ? adminAuth.derivedKey : undefined, + sessionId: adminAuth.sessionId, + hasPassword: adminAuth.hasPassword + }); + } + } catch {} + } - const adminResult = await handleAdminRoute(req, url, baseContext, authInfo); - if (adminResult) { - finalizeAuth(); - return adminResult; + const adminResult = await handleAdminRoute(req, url, baseContext, authInfo); + if (adminResult) { + return adminResult; + } } - } - - // Handle privileged routes separately - if (needsPrivilegedAccess && url.pathname.startsWith('/api/env')) { - const result = await handleEnvRoute(req, url, privilegedContext, authInfo); - if (result) { - finalizeAuth(); - return result; + + // Handle privileged routes separately + if (needsPrivilegedAccess && url.pathname.startsWith('/api/env')) { + const result = await handleEnvRoute(req, url, privilegedContext, authInfo); + if (result) { + return result; + } } - } - if (!HEADLESS && url.pathname.startsWith('/api/nip46/')) { - const nip46Result = await handleNip46Route(req, url, privilegedContext, authInfo); - if (nip46Result) { - finalizeAuth(); - return nip46Result; + if (!HEADLESS && url.pathname.startsWith('/api/nip46/')) { + const nip46Result = await handleNip46Route(req, url, privilegedContext, authInfo); + if (nip46Result) { + return nip46Result; + } } - } - // Try each non-privileged route handler in order - // Note: These handlers now accept auth as an optional parameter - const routeHandlers = [ - handleStatusRoute, // Allow unauthenticated for health checks - handleUpdateRoute, - handleEventLogRoute, - handlePeersRoute, - handleSignRoute, - handleNip44Route, - handleNip04Route, - handleRecoveryRoute, - ]; + // Try each non-privileged route handler in order + // Note: These handlers now accept auth as an optional parameter + const routeHandlers = [ + handleStatusRoute, // Allow unauthenticated for health checks + handleUpdateRoute, + handleEventLogRoute, + handlePeersRoute, + handleSignRoute, + handleNip44Route, + handleNip04Route, + handleRecoveryRoute, + ]; - for (const handler of routeHandlers) { - const result = await handler(req, url, context, authInfo); - if (result) { - finalizeAuth(); - return result; + for (const handler of routeHandlers) { + const result = await handler(req, url, context, authInfo); + if (result) { + return result; + } } - } - // If no route matched, return 404 - const notFound = new Response('Not Found', { status: 404 }); - finalizeAuth(); - return notFound; + // If no route matched, return 404 + if (url.pathname.startsWith('/api/')) { + return Response.json({ error: 'Not Found' }, { status: 404, headers }); + } + return new Response('Not Found', { status: 404 }); + } finally { + finalizeAuth(); + } } diff --git a/src/routes/nip04.ts b/src/routes/nip04.ts index 9592a0e..43c586d 100644 --- a/src/routes/nip04.ts +++ b/src/routes/nip04.ts @@ -79,14 +79,25 @@ export async function handleNip04Route(req: Request, url: URL, context: RouteCon if (!isContentLengthWithin(req, DEFAULT_MAX_JSON_BODY)) { return Response.json({ error: 'Request too large' }, { status: 413, headers }) } + const authContext = _auth ?? context.auth + if (!authContext?.authenticated) { + return Response.json({ error: 'Unauthorized' }, { status: 401, headers }) + } if (!context.node) return Response.json({ error: 'Node not available' }, { status: 503, headers }) - // Separate bucket for e2e crypto ops + // Separate bucket for crypto operations const rate = await checkRateLimit(req, 'crypto', { clientIp: context.clientIp }); if (!rate.allowed) { + const resetAt = typeof rate.resetAt === 'number' && Number.isFinite(rate.resetAt) ? rate.resetAt : null + const retryAfterFromReset = resetAt !== null + ? Math.max(0, Math.ceil((resetAt - Date.now()) / 1000)) + : null + const retryAfterWindow = Number.parseInt(process.env.RATE_LIMIT_WINDOW || '900', 10) + const retryAfterFallback = Number.isFinite(retryAfterWindow) && retryAfterWindow > 0 ? retryAfterWindow : 900 + const retryAfter = retryAfterFromReset !== null ? retryAfterFromReset : retryAfterFallback return Response.json({ error: 'Rate limit exceeded. Try again later.' }, { status: 429, - headers: { ...headers, 'Retry-After': Math.ceil(parseInt(process.env.RATE_LIMIT_WINDOW || '900')).toString() } + headers: { ...headers, 'Retry-After': retryAfter.toString() } }) } diff --git a/src/routes/nip44.ts b/src/routes/nip44.ts index 3947620..b566bbb 100644 --- a/src/routes/nip44.ts +++ b/src/routes/nip44.ts @@ -34,13 +34,15 @@ export async function handleNip44Route(req: Request, url: URL, context: RouteCon } if (!context.node) return Response.json({ error: 'Node not available' }, { status: 503, headers }); - // Basic rate limit for e2e crypto ops - // Separate bucket for e2e crypto ops + // Basic rate limit for crypto operations + // Use a dedicated bucket separate from signing traffic. const rate = await checkRateLimit(req, 'crypto', { clientIp: context.clientIp }); if (!rate.allowed) { + const retryAfterWindow = Number.parseInt(process.env.RATE_LIMIT_WINDOW || '', 10); + const retryAfterSeconds = Number.isFinite(retryAfterWindow) && retryAfterWindow > 0 ? retryAfterWindow : 900; return Response.json({ error: 'Rate limit exceeded. Try again later.' }, { status: 429, - headers: { ...headers, 'Retry-After': Math.ceil(parseInt(process.env.RATE_LIMIT_WINDOW || '900')).toString() } + headers: { ...headers, 'Retry-After': Math.ceil(retryAfterSeconds).toString() } }); } @@ -62,20 +64,18 @@ export async function handleNip44Route(req: Request, url: URL, context: RouteCon const timeoutMs = getOpTimeoutMs(); try { const secretHex = await deriveSharedSecret(context.node, peer, timeoutMs); + const conversationKey = Uint8Array.from(Buffer.from(secretHex, 'hex')); const mode = url.pathname.endsWith('/encrypt') ? 'encrypt' : url.pathname.endsWith('/decrypt') ? 'decrypt' : null; if (!mode) return Response.json({ error: 'Unknown operation' }, { status: 404, headers }); - // Platform-agnostic hex to Uint8Array conversion - const hexBytes = secretHex.match(/.{1,2}/g); - if (!hexBytes) { - throw new Error('Invalid hex string format'); + if (conversationKey.length !== 32) { + throw new Error('Invalid shared secret length'); } - const key = new Uint8Array(hexBytes.map(byte => parseInt(byte, 16))); if (mode === 'encrypt') { - const ciphertext = await nip44.encrypt(content, key); + const ciphertext = nip44.encrypt(content, conversationKey); return Response.json({ result: ciphertext }, { status: 200, headers }); } else { - const plaintext = await nip44.decrypt(content, key); + const plaintext = nip44.decrypt(content, conversationKey); return Response.json({ result: plaintext }, { status: 200, headers }); } } catch (e: any) { diff --git a/src/routes/nip46.ts b/src/routes/nip46.ts index 75f1d6a..29ef5a1 100644 --- a/src/routes/nip46.ts +++ b/src/routes/nip46.ts @@ -3,15 +3,41 @@ import { getSecureCorsHeaders, mergeVaryHeaders, parseJsonRequestBody } from './ import type { PrivilegedRouteContext, RequestAuth } from './types.js' import { listSessionEvents, listSessions, logSessionEvent, upsertSession, updatePolicy, updateStatus, deleteSession, countUserSessionsInWindow, initializeNip46DB, type Nip46Policy, type Nip46Profile, getTransportKey, setTransportKey, getNip46Relays, setNip46Relays, mergeNip46Relays, listNip46Requests, updateNip46RequestStatus, deleteNip46Request, type Nip46RequestStatus, getSession, getNip46RequestById } from '../db/nip46.js' import { getNip46Service } from '../nip46/index.js' +import { get_pubkey } from '../util/ecc.js' const DEFAULT_NIP46_SESSION_RATE_LIMIT_MAX = HEADLESS ? 30 : 120; const DEFAULT_NIP46_SESSION_RATE_LIMIT_WINDOW_SECONDS = 3600; // Keep a 1 hour window by default +const NIP46_JSON_BODY_LIMIT_BYTES = 16_384; + +function parsePositiveInt(value: string | undefined, fallback: number): number { + const parsed = Number.parseInt(value ?? '', 10) + return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback +} + +async function parseJsonWithLimit(req: Request, maxBytes: number): Promise { + const contentLengthHeader = req.headers.get('content-length') + if (contentLengthHeader) { + const contentLength = Number.parseInt(contentLengthHeader, 10) + if (!Number.isNaN(contentLength) && contentLength > maxBytes) { + return null + } + } + const text = await req.text() + if (new TextEncoder().encode(text).byteLength > maxBytes) { + return null + } + try { + return JSON.parse(text) + } catch (error) { + throw error + } +} // Rate limiting configuration for NIP-46 session creation const NIP46_RATE_LIMIT = { // Slightly relaxed default for headless/local testing, significantly higher default once persisted to the database - MAX: parseInt(process.env.NIP46_SESSION_RATE_LIMIT_MAX || String(DEFAULT_NIP46_SESSION_RATE_LIMIT_MAX)), - WINDOW_MS: parseInt(process.env.NIP46_SESSION_RATE_LIMIT_WINDOW || String(DEFAULT_NIP46_SESSION_RATE_LIMIT_WINDOW_SECONDS)) * 1000 + MAX: parsePositiveInt(process.env.NIP46_SESSION_RATE_LIMIT_MAX, DEFAULT_NIP46_SESSION_RATE_LIMIT_MAX), + WINDOW_MS: parsePositiveInt(process.env.NIP46_SESSION_RATE_LIMIT_WINDOW, DEFAULT_NIP46_SESSION_RATE_LIMIT_WINDOW_SECONDS) * 1000 } const MAX_NIP46_RELAYS = 32; @@ -69,7 +95,7 @@ function parsePolicyPatch(value: unknown): PolicyPatch | null { const patch: PolicyPatch = {} if (value && typeof (value as any).methods === 'object' && !Array.isArray((value as any).methods)) { - const methods: Record = {} + const methods = Object.create(null) as Record for (const [name, flag] of Object.entries((value as any).methods)) { if (typeof flag === 'boolean' && name.trim()) { methods[name.trim()] = flag @@ -79,7 +105,7 @@ function parsePolicyPatch(value: unknown): PolicyPatch | null { } if (value && typeof (value as any).kinds === 'object' && !Array.isArray((value as any).kinds)) { - const kinds: Record = {} + const kinds = Object.create(null) as Record for (const [rawKind, flag] of Object.entries((value as any).kinds)) { if (typeof flag === 'boolean') { const key = String(rawKind).trim() @@ -95,9 +121,62 @@ function parsePolicyPatch(value: unknown): PolicyPatch | null { return Object.keys(patch).length ? patch : null } +function parseBooleanPolicyMap( + value: unknown, + fieldPath: string, + options?: { validateKey?: (key: string) => boolean; keyValidationMessage?: string } +): Record { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + throw new Error(`Field "${fieldPath}" must be an object`) + } + + const normalized = Object.create(null) as Record + for (const [rawKey, rawValue] of Object.entries(value as Record)) { + const key = rawKey.trim() + if (!key) { + throw new Error(`Field "${fieldPath}" must not contain empty keys`) + } + if (options?.validateKey && !options.validateKey(key)) { + const suffix = options.keyValidationMessage ?? 'contains an invalid key' + throw new Error(`Field "${fieldPath}.${key}" ${suffix}`) + } + if (typeof rawValue !== 'boolean') { + throw new Error(`Field "${fieldPath}.${key}" must be a boolean`) + } + normalized[key] = rawValue + } + + return normalized +} + +function parseSessionPolicyInput(policyValue: unknown): Nip46Policy | undefined { + if (policyValue === undefined) return undefined + if (!policyValue || typeof policyValue !== 'object' || Array.isArray(policyValue)) { + throw new Error('Field "policy" must be an object') + } + + const policyObject = policyValue as Record + const hasMethods = Object.prototype.hasOwnProperty.call(policyObject, 'methods') + const hasKinds = Object.prototype.hasOwnProperty.call(policyObject, 'kinds') + const parsed: Nip46Policy = {} + + if (hasMethods) { + parsed.methods = parseBooleanPolicyMap(policyObject.methods, 'policy.methods') + } + + if (hasKinds) { + parsed.kinds = parseBooleanPolicyMap(policyObject.kinds, 'policy.kinds', { + validateKey: (key: string) => key === '*' || /^\d+$/.test(key), + keyValidationMessage: 'must be numeric or "*"' + }) + } + + return hasMethods || hasKinds ? parsed : undefined +} + function applyPolicyPatch(current: Nip46Policy | null | undefined, patch: PolicyPatch): Nip46Policy { - const baseMethods = { ...(current?.methods ?? {}) } - const baseKinds = { ...(current?.kinds ?? {}) } + const baseMethods = Object.assign(Object.create(null), current?.methods ?? {}) as Record + const baseKinds = Object.assign(Object.create(null), current?.kinds ?? {}) as Record const result: Nip46Policy = {} if (patch.methods) { @@ -106,6 +185,8 @@ function applyPolicyPatch(current: Nip46Policy | null | undefined, patch: Policy else delete baseMethods[name] } result.methods = baseMethods + } else if (current?.methods !== undefined) { + result.methods = { ...baseMethods } } if (patch.kinds) { @@ -114,6 +195,8 @@ function applyPolicyPatch(current: Nip46Policy | null | undefined, patch: Policy else delete baseKinds[kind] } result.kinds = baseKinds + } else if (current?.kinds !== undefined) { + result.kinds = { ...baseKinds } } if (result.methods && Object.keys(result.methods).length === 0) { @@ -187,15 +270,6 @@ export async function handleNip46Route( ): Promise { if (!url.pathname.startsWith('/api/nip46/')) return null - // Only available in non-headless (DB-backed) mode - if (HEADLESS) { - return Response.json({ error: 'NIP-46 persistence unavailable in headless mode' }, { status: 404 }) - } - - // Ensure database is initialized before processing any NIP46 requests - // This prevents race conditions where routes are accessed before migrations complete - await initializeNip46DB() - const corsHeaders = getSecureCorsHeaders(req) const mergedVary = mergeVaryHeaders(corsHeaders) const headers = { @@ -206,8 +280,28 @@ export async function handleNip46Route( 'Vary': mergedVary, } + // Only available in non-headless (DB-backed) mode + if (HEADLESS) { + return Response.json( + { error: 'NIP-46 persistence unavailable in headless mode' }, + { status: 404, headers } + ) + } + if (req.method === 'OPTIONS') return new Response(null, { status: 200, headers }) + // Ensure database is initialized before processing any NIP46 requests. + // This prevents race conditions where routes are accessed before migrations complete. + try { + await initializeNip46DB() + } catch (error) { + console.error('[NIP46] Failed to initialize DB:', error) + return Response.json( + { error: 'DB_INIT_FAILED', message: 'Internal server error' }, + { status: 500, headers } + ) + } + // Require authenticated DB user if (!auth || !auth.authenticated || (typeof auth.userId !== 'number' && (typeof auth.userId !== 'string' || !/^\d+$/.test(auth.userId)))) { return Response.json({ error: 'Authentication required' }, { status: 401, headers }) @@ -226,7 +320,7 @@ export async function handleNip46Route( sk = Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('') setTransportKey(userId, sk) } - return Response.json({ transport_sk: sk }, { headers }) + return Response.json({ transport_pubkey: get_pubkey(sk) }, { headers }) } catch (error) { console.error('[NIP46] Failed to get transport key:', error) return Response.json({ error: 'Failed to get transport key' }, { status: 500, headers }) @@ -236,10 +330,14 @@ export async function handleNip46Route( // PUT /api/nip46/transport – rotate or set transport key if (url.pathname === '/api/nip46/transport' && req.method === 'PUT') { try { - const body = await req.json().catch(() => null) as any + const parsedBody = await parseJsonWithLimit(req, NIP46_JSON_BODY_LIMIT_BYTES) + if (parsedBody === null) { + return Response.json({ error: 'Payload exceeds maximum size' }, { status: 413, headers }) + } + const body = parsedBody as Record const sk = typeof body?.transport_sk === 'string' ? body.transport_sk : '' const saved = setTransportKey(userId, sk) - return Response.json({ ok: true, transport_sk: saved }, { headers }) + return Response.json({ ok: true, transport_pubkey: get_pubkey(saved) }, { headers }) } catch (error) { const msg = error instanceof Error ? error.message : 'Failed to set transport key' return Response.json({ error: msg }, { status: 400, headers }) @@ -336,33 +434,33 @@ export async function handleNip46Route( const result = typeof body?.result === 'string' ? body.result : null const errorMessage = typeof body?.error === 'string' ? body.error : null - const policyPatch = parsePolicyPatch(body?.policy) - let existingRecord = policyPatch ? getNip46RequestById(id) : null - if (policyPatch) { - if (!existingRecord) { - return Response.json({ error: 'Request not found' }, { status: 404, headers }) - } - const recordUserId = typeof existingRecord.user_id === 'bigint' - ? existingRecord.user_id.toString() - : String(existingRecord.user_id) - const requestUserId = typeof userId === 'bigint' ? userId.toString() : String(userId) - if (recordUserId !== requestUserId) { - return Response.json({ error: 'Request not found' }, { status: 404, headers }) - } - - const session = getSession(userId, existingRecord.session_pubkey) - if (!session) { - return Response.json({ error: 'Session not found for policy update' }, { status: 404, headers }) - } - - try { - const mergedPolicy = applyPolicyPatch(session.policy, policyPatch) - updatePolicy(userId, existingRecord.session_pubkey, mergedPolicy) - } catch (error) { - const message = error instanceof Error ? error.message : 'Failed to update policy' - return Response.json({ error: message }, { status: 400, headers }) - } - } + const existingRecord = getNip46RequestById(id) + if (!existingRecord) { + return Response.json({ error: 'Request not found' }, { status: 404, headers }) + } + const recordUserId = typeof existingRecord.user_id === 'bigint' + ? existingRecord.user_id.toString() + : String(existingRecord.user_id) + const requestUserId = typeof userId === 'bigint' ? userId.toString() : String(userId) + if (recordUserId !== requestUserId) { + return Response.json({ error: 'Forbidden' }, { status: 403, headers }) + } + + const policyPatch = parsePolicyPatch(body?.policy) + if (policyPatch) { + const session = getSession(userId, existingRecord.session_pubkey) + if (!session) { + return Response.json({ error: 'Session not found for policy update' }, { status: 404, headers }) + } + + try { + const mergedPolicy = applyPolicyPatch(session.policy, policyPatch) + updatePolicy(userId, existingRecord.session_pubkey, mergedPolicy) + } catch (error) { + const message = error instanceof Error ? error.message : 'Failed to update policy' + return Response.json({ error: message }, { status: 400, headers }) + } + } const record = updateNip46RequestStatus(id, status, { result, error: errorMessage }) if (!record) { @@ -388,6 +486,18 @@ export async function handleNip46Route( return Response.json({ error: 'Field "id" is required' }, { status: 400, headers }) } + const existingRecord = getNip46RequestById(id) + if (!existingRecord) { + return Response.json({ error: 'Request not found' }, { status: 404, headers }) + } + const recordUserId = typeof existingRecord.user_id === 'bigint' + ? existingRecord.user_id.toString() + : String(existingRecord.user_id) + const requestUserId = typeof userId === 'bigint' ? userId.toString() : String(userId) + if (recordUserId !== requestUserId) { + return Response.json({ error: 'Forbidden' }, { status: 403, headers }) + } + deleteNip46Request(id) return Response.json({ ok: true }, { headers }) } @@ -470,7 +580,15 @@ export async function handleNip46Route( // POST /api/nip46/sessions if (url.pathname === '/api/nip46/sessions' && req.method === 'POST') { let body: any - try { body = await req.json() } catch { return Response.json({ error: 'Invalid JSON' }, { status: 400, headers }) } + try { + const parsedBody = await parseJsonWithLimit(req, NIP46_JSON_BODY_LIMIT_BYTES) + if (parsedBody === null) { + return Response.json({ error: 'Payload exceeds maximum size' }, { status: 413, headers }) + } + body = parsedBody + } catch { + return Response.json({ error: 'Invalid JSON' }, { status: 400, headers }) + } const pubkey = typeof body?.pubkey === 'string' ? body.pubkey.trim().toLowerCase() : '' if (!pubkey || !isValidHex(pubkey)) { return Response.json({ error: 'Invalid pubkey' }, { status: 400, headers }) @@ -513,16 +631,13 @@ export async function handleNip46Route( } const relays = Array.isArray(body?.relays) ? body.relays.filter((r: any) => typeof r === 'string') : null - const policyMethods = body?.policy?.methods && typeof body.policy.methods === 'object' && !Array.isArray(body.policy.methods) - ? body.policy.methods as Record - : undefined - const policyKinds = body?.policy?.kinds && typeof body.policy.kinds === 'object' && !Array.isArray(body.policy.kinds) - ? body.policy.kinds as Record - : undefined - const policy: Nip46Policy | undefined = - policyMethods !== undefined || policyKinds !== undefined - ? { methods: policyMethods, kinds: policyKinds } - : undefined + let policy: Nip46Policy | undefined + try { + policy = parseSessionPolicyInput(body?.policy) + } catch (error) { + const message = error instanceof Error ? error.message : 'Invalid policy' + return Response.json({ error: message }, { status: 400, headers }) + } try { const session = upsertSession({ userId, client_pubkey: pubkey, status, profile, relays, policy }) try { @@ -561,18 +676,38 @@ export async function handleNip46Route( const pubkey = parsePubkeyFromPath(url.pathname) if (!pubkey || !isValidHex(pubkey)) return Response.json({ error: 'Invalid pubkey' }, { status: 400, headers }) let body: any - try { body = await req.json() } catch { return Response.json({ error: 'Invalid JSON' }, { status: 400, headers }) } - const methods = body?.methods && typeof body.methods === 'object' && !Array.isArray(body.methods) - ? body.methods as Record - : undefined - const kinds = body?.kinds && typeof body.kinds === 'object' && !Array.isArray(body.kinds) - ? body.kinds as Record - : undefined - - if (methods === undefined && kinds === undefined) { + try { + const parsedBody = await parseJsonWithLimit(req, NIP46_JSON_BODY_LIMIT_BYTES) + if (parsedBody === null) { + return Response.json({ error: 'Payload exceeds maximum size' }, { status: 413, headers }) + } + body = parsedBody + } catch { + return Response.json({ error: 'Invalid JSON' }, { status: 400, headers }) + } + const hasMethods = body && typeof body === 'object' && Object.prototype.hasOwnProperty.call(body, 'methods') + const hasKinds = body && typeof body === 'object' && Object.prototype.hasOwnProperty.call(body, 'kinds') + if (!hasMethods && !hasKinds) { return Response.json({ error: 'No policy changes provided' }, { status: 400, headers }) } + let methods: Record | undefined + let kinds: Record | undefined + try { + if (hasMethods) { + methods = parseBooleanPolicyMap((body as Record).methods, 'methods') + } + if (hasKinds) { + kinds = parseBooleanPolicyMap((body as Record).kinds, 'kinds', { + validateKey: (key: string) => key === '*' || /^\d+$/.test(key), + keyValidationMessage: 'must be numeric or "*"' + }) + } + } catch (error) { + const message = error instanceof Error ? error.message : 'Invalid policy update' + return Response.json({ error: message }, { status: 400, headers }) + } + const session = updatePolicy(userId, pubkey.toLowerCase(), { methods, kinds }) if (!session) return Response.json({ error: 'Session not found' }, { status: 404, headers }) return Response.json({ ok: true }, { headers }) @@ -583,7 +718,15 @@ export async function handleNip46Route( const pubkey = parsePubkeyFromPath(url.pathname) if (!pubkey || !isValidHex(pubkey)) return Response.json({ error: 'Invalid pubkey' }, { status: 400, headers }) let body: any - try { body = await req.json() } catch { return Response.json({ error: 'Invalid JSON' }, { status: 400, headers }) } + try { + const parsedBody = await parseJsonWithLimit(req, NIP46_JSON_BODY_LIMIT_BYTES) + if (parsedBody === null) { + return Response.json({ error: 'Payload exceeds maximum size' }, { status: 413, headers }) + } + body = parsedBody + } catch { + return Response.json({ error: 'Invalid JSON' }, { status: 400, headers }) + } const status = body?.status if (status !== 'pending' && status !== 'active' && status !== 'revoked') { return Response.json({ error: 'Invalid status' }, { status: 400, headers }) @@ -603,6 +746,9 @@ export async function handleNip46Route( if (url.pathname.startsWith('/api/nip46/sessions/') && req.method === 'DELETE') { const pubkey = parsePubkeyFromPath(url.pathname) if (!pubkey || !isValidHex(pubkey)) return Response.json({ error: 'Invalid pubkey' }, { status: 400, headers }) + if (url.pathname !== `/api/nip46/sessions/${pubkey}`) { + return Response.json({ error: 'Not Found' }, { status: 404, headers }) + } const ok = deleteSession(userId, pubkey.toLowerCase()) return Response.json({ ok }, { headers }) } diff --git a/src/routes/node-manager.ts b/src/routes/node-manager.ts index 5db4528..3b5768c 100644 --- a/src/routes/node-manager.ts +++ b/src/routes/node-manager.ts @@ -91,6 +91,7 @@ export async function createAndStartNode( } catch (enhancedError) { // Fall back to basic node creation context.addServerLog('info', 'Enhanced node creation failed, using basic connection...'); + let basicFailure: unknown = null; try { const fallbackConfig: BasicNodeConfig = { @@ -114,6 +115,12 @@ export async function createAndStartNode( } } catch (basicError) { context.addServerLog('error', 'Failed to create node with basic connection', basicError); + basicFailure = basicError; + } + + if (!newNode && basicFailure) { + const detail = basicFailure instanceof Error ? basicFailure.message : String(basicFailure); + throw new Error(`Failed to create node with basic connection: ${detail}`); } } diff --git a/src/routes/onboarding.ts b/src/routes/onboarding.ts index a3d6efa..e686c20 100644 --- a/src/routes/onboarding.ts +++ b/src/routes/onboarding.ts @@ -1,4 +1,4 @@ -import { timingSafeEqual } from 'crypto'; +import { createHash, randomBytes, timingSafeEqual } from 'crypto'; import { hmac } from '@noble/hashes/hmac'; import { sha256 } from '@noble/hashes/sha256'; import { ADMIN_SECRET, HEADLESS, SKIP_ADMIN_SECRET_VALIDATION } from '../const.js'; @@ -25,8 +25,11 @@ const MAX_ATTEMPTS_PER_WINDOW = 5; // Stable client identifier cache (maps canonical fingerprint input -> stable ID) const clientIdCache = new Map(); const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000; -const CLIENT_ID_TTL_MS = Math.max(10 * 60_000, Math.min(SEVEN_DAYS_MS, parseInt(process.env.CLIENT_ID_TTL_MS || '86400000'))); +const parsedClientIdTtl = Number.parseInt(process.env.CLIENT_ID_TTL_MS ?? '', 10); +const clientIdTtlBase = Number.isFinite(parsedClientIdTtl) && parsedClientIdTtl > 0 ? parsedClientIdTtl : 86400000; +const CLIENT_ID_TTL_MS = Math.max(10 * 60_000, Math.min(SEVEN_DAYS_MS, clientIdTtlBase)); const FINGERPRINT_SECRET = process.env.FINGERPRINT_SECRET || ''; +const FALLBACK_FINGERPRINT_SECRET = FINGERPRINT_SECRET || process.env.SESSION_SECRET || ADMIN_SECRET || randomBytes(32).toString('hex'); const LOG_FINGERPRINT_FALLBACK = process.env.LOG_FINGERPRINT_FALLBACK === 'true'; let clientIdCleanupTimer: ReturnType | null = null; @@ -226,9 +229,7 @@ function getClientIp(req: Request, fallbackFromServer?: string | null): string { const cached = clientIdCache.get(canonical); if (cached && cached.expiresAt > now) return cached.id; const encoder = new TextEncoder(); - const digest = FINGERPRINT_SECRET - ? hmac(sha256, encoder.encode(FINGERPRINT_SECRET), encoder.encode(canonical)) - : sha256(encoder.encode(canonical)); + const digest = hmac(sha256, encoder.encode(FALLBACK_FINGERPRINT_SECRET), encoder.encode(canonical)); const hex = Buffer.from(digest).toString('hex'); const id = `fp_${hex.slice(0, 32)}`; clientIdCache.set(canonical, { id, expiresAt: now + CLIENT_ID_TTL_MS }); @@ -266,9 +267,10 @@ const UNIFORM_AUTH_ERROR = { error: 'Authentication failed' }; // - Uppercase letter // - Lowercase letter // - Digit -// - Special character (at least one of @$!%*?&, but allows any special chars) -// Note: Length validation is handled by VALIDATION.MIN_PASSWORD_LENGTH and VALIDATION.MAX_PASSWORD_LENGTH -const PASSWORD_REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])\S*$/; +// - Special character (any non-alphanumeric, excluding whitespace) +// Note: Length validation is handled by VALIDATION.MIN_PASSWORD_LENGTH and VALIDATION.MAX_PASSWORD_LENGTH. +// Whitespace is allowed and preserved by policy. +const PASSWORD_REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9\s]).*$/; /** * Validates the admin secret in a timing-safe manner @@ -289,21 +291,15 @@ export async function validateAdminSecret(adminSecret: string | undefined): Prom try { // Coerce to string to prevent type errors const adminSecretStr = String(adminSecret); - const providedSecret = Buffer.from(adminSecretStr); - const expectedSecret = Buffer.from(ADMIN_SECRET); - - // Timing-safe comparison - if (providedSecret.length !== expectedSecret.length) { - return false; - } - - return timingSafeEqual(providedSecret, expectedSecret); + const providedDigest = createHash('sha256').update(adminSecretStr).digest(); + const expectedDigest = createHash('sha256').update(String(ADMIN_SECRET)).digest(); + return timingSafeEqual(providedDigest, expectedDigest); } catch { // On any error, perform dummy comparison to maintain consistent timing - const expectedSecret = Buffer.from(String(ADMIN_SECRET)); - const dummySecret = Buffer.alloc(expectedSecret.length); + const expectedDigest = createHash('sha256').update(String(ADMIN_SECRET)).digest(); + const dummyDigest = Buffer.alloc(expectedDigest.length); try { - timingSafeEqual(dummySecret, expectedSecret); + timingSafeEqual(dummyDigest, expectedDigest); } catch {} return false; } @@ -349,7 +345,7 @@ function validatePasswordStrength(password: string, username?: string): string | } if (!PASSWORD_REGEX.test(password)) { - return 'Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character (must include at least one of @$!%*?&)'; + return 'Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character (any non-alphanumeric character, excluding whitespace).'; } // Check for sequential or repeated characters diff --git a/src/routes/peers.ts b/src/routes/peers.ts index 1ba8d1e..fe17885 100644 --- a/src/routes/peers.ts +++ b/src/routes/peers.ts @@ -22,6 +22,10 @@ type StoredPeerPolicy = import('../db/database.js').StoredPeerPolicy; // Constants - use igloo-core default const PING_TIMEOUT_MS = DEFAULT_PING_TIMEOUT; +const parsedPingConcurrency = Number.parseInt(process.env.PING_CONCURRENCY ?? '', 10); +const MAX_CONCURRENT_PINGS = Number.isFinite(parsedPingConcurrency) && parsedPingConcurrency > 0 + ? Math.min(parsedPingConcurrency, 64) + : 8; function safeNormalizePubkey(pubkey: string): string | null { try { @@ -107,15 +111,16 @@ async function persistUserPeerPolicies( })); const sanitizedPolicies = sanitizePeerPolicyEntries(rawPolicies) as StoredPeerPolicy[]; const hasPolicies = sanitizedPolicies.length > 0; + const fallbackPolicies = hasPolicies ? sanitizedPolicies : null; if (HEADLESS) { - await saveFallbackPeerPolicies(hasPolicies ? sanitizedPolicies : null); + await saveFallbackPeerPolicies(fallbackPolicies); return; } const userId = resolveDatabaseUserId(auth); if (userId === null) { - await saveFallbackPeerPolicies(hasPolicies ? sanitizedPolicies : null); + await saveFallbackPeerPolicies(fallbackPolicies); return; } @@ -123,22 +128,159 @@ async function persistUserPeerPolicies( const { updateUserPeerPolicies } = await import('../db/database.js'); if (!context.node || summaries.length === 0) { - updateUserPeerPolicies(userId, null); - await saveFallbackPeerPolicies(null); + const success = updateUserPeerPolicies(userId, null); + if (!success) { + console.warn('Failed to clear peer policies for user', userId); + throw new Error('Failed to persist peer policies'); + } + try { + await saveFallbackPeerPolicies(null); + } catch (fallbackError) { + console.warn('Peer policies saved to DB, but failed to clear fallback cache:', fallbackError); + } return; } const success = updateUserPeerPolicies(userId, sanitizedPolicies); if (!success) { console.warn('Failed to persist peer policies for user', userId); - await saveFallbackPeerPolicies(hasPolicies ? sanitizedPolicies : null); - } else { - await saveFallbackPeerPolicies(hasPolicies ? sanitizedPolicies : null); + throw new Error('Failed to persist peer policies'); + } + try { + await saveFallbackPeerPolicies(fallbackPolicies); + } catch (fallbackError) { + console.warn('Peer policies saved to DB, but failed to update fallback cache:', fallbackError); } } catch (error) { console.error('Failed to persist peer policies:', error); - await saveFallbackPeerPolicies(hasPolicies ? sanitizedPolicies : null); + throw error; + } +} + +async function persistUserPeerPoliciesWithRetry( + context: RouteContext, + auth: RequestAuth | null | undefined, + details: { pubkey: string; previousPolicy: NodePolicyInput | null } +): Promise { + const maxAttempts = 3; + let lastError: unknown = null; + for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { + try { + await persistUserPeerPolicies(context, auth); + return; + } catch (error) { + lastError = error; + try { + context.addServerLog('warning', 'Peer policy persistence attempt failed', { + pubkey: details.pubkey, + previousPolicy: summarizePolicyForLog(details.previousPolicy), + attempt, + maxAttempts, + error: error instanceof Error ? error.message : String(error) + }); + } catch {} + if (attempt < maxAttempts) { + const delayMs = 100 * (2 ** (attempt - 1)); + await new Promise(resolve => setTimeout(resolve, delayMs)); + } + } + } + throw lastError instanceof Error ? lastError : new Error('Failed to persist peer policies'); +} + +function clonePolicyMetadata( + metadata: unknown +): Record | undefined { + if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) { + return undefined; + } + try { + return structuredClone(metadata as Record); + } catch { + return { ...(metadata as Record) }; + } +} + +function summarizePolicyForLog(policy: NodePolicyInput | null | undefined): Record | null { + if (!policy || typeof policy !== 'object') { + return null; + } + const summary = structuredClone ? structuredClone(policy) : { ...policy }; + if ('note' in summary) { + delete (summary as { [key: string]: unknown }).note; + } + if ('metadata' in summary) { + delete (summary as { [key: string]: unknown }).metadata; + } + return summary; +} + +function getPingFailureMessage(result: unknown): string { + if (!result || typeof result !== 'object') { + return 'Timeout'; + } + + const record = result as { error?: unknown; reason?: unknown; message?: unknown }; + if (typeof record.error === 'string' && record.error.length > 0) { + return record.error; + } + if (typeof record.reason === 'string' && record.reason.length > 0) { + return record.reason; + } + if (typeof record.message === 'string' && record.message.length > 0) { + return record.message; + } + return 'Timeout'; +} + +function toPolicyInput(summary: NodePolicySummary): NodePolicyInput { + const input: NodePolicyInput = { + pubkey: summary.pubkey, + allowSend: summary.allowSend, + allowReceive: summary.allowReceive + }; + + if (typeof summary.label === 'string') { + input.label = summary.label; } + if (Array.isArray(summary.roles)) { + input.roles = summary.roles.filter((role): role is string => typeof role === 'string'); + } + const metadata = clonePolicyMetadata(summary.metadata); + if (metadata) { + input.metadata = metadata; + } + if (typeof summary.note === 'string') { + input.note = summary.note; + } + if (summary.source === 'config' || summary.source === 'runtime') { + input.source = summary.source; + } + + return input; +} + +async function mapWithConcurrency( + items: T[], + limit: number, + worker: (item: T) => Promise +): Promise { + if (items.length === 0) return []; + const results = new Array(items.length); + let cursor = 0; + const concurrency = Math.max(1, Math.min(limit, items.length)); + + const runWorker = async () => { + while (true) { + const index = cursor; + cursor += 1; + if (index >= items.length) return; + results[index] = await worker(items[index]); + } + }; + + await Promise.all(Array.from({ length: concurrency }, () => runWorker())); + return results; } // Helper function to get credentials based on mode @@ -354,17 +496,22 @@ export async function handlePeersRoute(req: Request, url: URL, context: RouteCon return Response.json({ error: 'Missing credentials' }, { status: statusCode, headers }); } - const selfPubkeyResult = extractSelfPubkeyFromCredentials(credentials.group_cred, credentials.share_cred); - if (selfPubkeyResult.pubkey) { - return Response.json({ - pubkey: selfPubkeyResult.pubkey, - warnings: selfPubkeyResult.warnings - }, { headers }); - } else { - return Response.json({ - error: 'Could not extract self pubkey', - warnings: selfPubkeyResult.warnings - }, { status: 400, headers }); + try { + const selfPubkeyResult = extractSelfPubkeyFromCredentials(credentials.group_cred, credentials.share_cred); + if (selfPubkeyResult.pubkey) { + return Response.json({ + pubkey: selfPubkeyResult.pubkey, + warnings: selfPubkeyResult.warnings + }, { headers }); + } else { + return Response.json({ + error: 'Could not extract self pubkey', + warnings: selfPubkeyResult.warnings + }, { status: 400, headers }); + } + } catch (error) { + console.error('Failed to extract self pubkey from credentials:', error); + return Response.json({ error: 'Malformed credentials', warnings: ['Invalid credentials format'] }, { status: 400, headers }); } } break; @@ -500,6 +647,7 @@ async function handlePeerPolicyRoute( const currentSummary = getNodePolicy(context.node, normalized); const nextAllowSend = allowSend ?? currentSummary?.allowSend ?? false; const nextAllowReceive = allowReceive ?? currentSummary?.allowReceive ?? false; + const previousPolicy = currentSummary ? toPolicyInput(currentSummary) : null; const policyInput: NodePolicyInput = { pubkey: normalized, @@ -518,29 +666,60 @@ async function handlePeerPolicyRoute( allowReceive: policy.allowReceive }); } catch {} - await persistUserPeerPolicies(context, auth); + await persistUserPeerPoliciesWithRetry(context, auth, { pubkey: normalized, previousPolicy }); return Response.json({ policy }, { headers }); } catch (error) { - console.error('Failed to update peer policy:', error); + let rollbackSucceeded = false; + let rollbackSkipped = false; + try { + const latestSummary = getNodePolicy(context.node, normalized); + const stillMatchesAttempt = + latestSummary && + latestSummary.allowSend === policyInput.allowSend && + latestSummary.allowReceive === policyInput.allowReceive; + + if (stillMatchesAttempt) { + if (previousPolicy) { + setNodePolicies(context.node, [previousPolicy], { merge: true }); + } else { + const remainingInputs = getNodePolicies(context.node) + .filter(summary => !comparePubkeys(summary.pubkey, normalized)) + .map(toPolicyInput); + setNodePolicies(context.node, remainingInputs, { merge: false }); + } + rollbackSucceeded = true; + } else { + rollbackSkipped = true; + } + } catch (rollbackError) { + console.error('Failed to roll back peer policy update:', rollbackError); + } + const errorDetails = { + pubkey: normalized, + previousPolicy: summarizePolicyForLog(previousPolicy), + attemptedPolicy: summarizePolicyForLog(policyInput), + persistError: error instanceof Error ? error.message : String(error), + rollbackSucceeded, + rollbackSkipped + }; + try { + context.addServerLog('error', 'Failed to update peer policy', errorDetails); + } catch {} + console.error('Failed to update peer policy:', errorDetails); return Response.json({ error: 'Failed to update peer policy' }, { status: 500, headers }); } } if (req.method === 'DELETE') { + const summaries = getNodePolicies(context.node); + const previousSummary = summaries.find(summary => comparePubkeys(summary.pubkey, normalized)); + const previousPolicy = previousSummary ? toPolicyInput(previousSummary) : null; try { - const summaries = getNodePolicies(context.node); const remainingInputs = summaries .filter(summary => !comparePubkeys(summary.pubkey, normalized)) - .map(summary => ({ - pubkey: summary.pubkey, - allowSend: summary.allowSend, - allowReceive: summary.allowReceive, - label: summary.label, - note: summary.note, - source: summary.source - })); - - if (remainingInputs.length === summaries.length) { + .map(toPolicyInput); + + if (!previousSummary) { return Response.json({ error: 'Policy not found' }, { status: 404, headers }); } @@ -550,10 +729,35 @@ async function handlePeerPolicyRoute( try { context.addServerLog('info', 'Peer policy removed', { pubkey: normalized }); } catch {} - await persistUserPeerPolicies(context, auth); + await persistUserPeerPoliciesWithRetry(context, auth, { pubkey: normalized, previousPolicy }); return Response.json({ removed: true, policy }, { headers }); } catch (error) { - console.error('Failed to remove peer policy:', error); + let rollbackSucceeded = false; + let rollbackSkipped = false; + try { + const latestSummary = getNodePolicy(context.node, normalized); + // Restore only when target is still absent; if another request already set it, + // avoid clobbering newer state. + if (!latestSummary && previousPolicy) { + setNodePolicies(context.node, [previousPolicy], { merge: true }); + rollbackSucceeded = true; + } else { + rollbackSkipped = true; + } + } catch (rollbackError) { + console.error('Failed to roll back peer policy removal:', rollbackError); + } + const errorDetails = { + pubkey: normalized, + previousPolicy: summarizePolicyForLog(previousPolicy), + persistError: error instanceof Error ? error.message : String(error), + rollbackSucceeded, + rollbackSkipped + }; + try { + context.addServerLog('error', 'Failed to remove peer policy', errorDetails); + } catch {} + console.error('Failed to remove peer policy:', errorDetails); return Response.json({ error: 'Failed to remove peer policy' }, { status: 500, headers }); } } @@ -587,9 +791,9 @@ async function handlePingAllPeers(context: RouteContext, headers: Record { - const normalizedPubkey = normalizePubkey(pubkey); + const results = await mapWithConcurrency(allPeers, MAX_CONCURRENT_PINGS, async (pubkey) => { try { + const normalizedPubkey = normalizePubkey(pubkey); const startTime = Date.now(); let result; if (context.node) { @@ -603,12 +807,12 @@ async function handlePingAllPeers(context: RouteContext, headers: Record): Promise { - // Ping specific peer - const normalizedPubkey = normalizePubkey(target); - try { + // Ping specific peer + const normalizedPubkey = normalizePubkey(target); const startTime = Date.now(); let result; if (context.node) { @@ -703,7 +908,7 @@ async function handlePingSinglePeer(target: string, context: RouteContext, heade return Response.json({ pubkey: target, success: false, - error: 'Timeout', + error: getPingFailureMessage(result), status: updatedStatus }, { headers }); } @@ -714,7 +919,10 @@ async function handlePingSinglePeer(target: string, context: RouteContext, heade online: false, lastPingAttempt: new Date() }; - context.peerStatuses.set(normalizedPubkey, updatedStatus); + const safePubkey = safeNormalizePubkey(target); + if (safePubkey) { + context.peerStatuses.set(safePubkey, updatedStatus); + } return Response.json({ pubkey: target, diff --git a/src/routes/sign.ts b/src/routes/sign.ts index e5b197f..ff7d288 100644 --- a/src/routes/sign.ts +++ b/src/routes/sign.ts @@ -97,6 +97,35 @@ function isTimeoutReason(reason: string): boolean { return value.includes('timeout'); } +async function readTextBodyWithLimit(req: Request, maxBytes: number): Promise { + if (!req.body) return ''; + + const reader = req.body.getReader(); + const chunks: Uint8Array[] = []; + let totalBytes = 0; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + const chunk = value instanceof Uint8Array ? value : new Uint8Array(value); + totalBytes += chunk.byteLength; + if (totalBytes > maxBytes) { + await reader.cancel(); + return null; + } + chunks.push(chunk); + } + + const bodyBytes = new Uint8Array(totalBytes); + let offset = 0; + for (const chunk of chunks) { + bodyBytes.set(chunk, offset); + offset += chunk.byteLength; + } + + return new TextDecoder().decode(bodyBytes); +} + export async function handleSignRoute(req: Request, url: URL, context: RouteContext, _auth?: RequestAuth | null) { if (url.pathname !== '/api/sign') return null; @@ -127,20 +156,40 @@ export async function handleSignRoute(req: Request, url: URL, context: RouteCont // Use a separate bucket so signing traffic doesn't compete with auth/login const rate = await checkRateLimit(req, 'sign', { clientIp: context.clientIp }); if (!rate.allowed) { + const resetAt = typeof rate.resetAt === 'number' && Number.isFinite(rate.resetAt) ? rate.resetAt : null; + const retryAfterFromReset = resetAt !== null + ? Math.max(0, Math.ceil((resetAt - Date.now()) / 1000)) + : null; + const retryAfterWindow = Number.parseInt(process.env.RATE_LIMIT_WINDOW || '900', 10); + const retryAfterFallback = Number.isFinite(retryAfterWindow) && retryAfterWindow > 0 ? retryAfterWindow : 900; + const retryAfterSeconds = retryAfterFromReset !== null ? retryAfterFromReset : retryAfterFallback; return Response.json({ code: 'RATE_LIMITED', error: 'Rate limit exceeded. Try again later.' }, { status: 429, - headers: { ...headers, 'Retry-After': Math.ceil(parseInt(process.env.RATE_LIMIT_WINDOW || '900')).toString() } + headers: { ...headers, 'Retry-After': retryAfterSeconds.toString() } }); } + const maxBodyBytes = 1024 * 100; const contentLength = req.headers.get('content-length'); - if (contentLength && parseInt(contentLength) > 1024 * 100) { // 100KB limit + const declaredLength = contentLength ? Number.parseInt(contentLength, 10) : null; + if (declaredLength !== null && Number.isFinite(declaredLength) && declaredLength > maxBodyBytes) { return Response.json({ code: 'REQUEST_TOO_LARGE', error: 'Request too large' }, { status: 413, headers }); } + let rawBody = ''; + try { + const bodyText = await readTextBodyWithLimit(req, maxBodyBytes); + if (bodyText === null) { + return Response.json({ code: 'REQUEST_TOO_LARGE', error: 'Request too large' }, { status: 413, headers }); + } + rawBody = bodyText; + } catch { + return Response.json({ code: 'INVALID_JSON', error: 'Invalid JSON' }, { status: 400, headers }); + } + let body: SignRequestBody; try { - body = await req.json(); + body = JSON.parse(rawBody) as SignRequestBody; } catch { return Response.json({ code: 'INVALID_JSON', error: 'Invalid JSON' }, { status: 400, headers }); } @@ -199,7 +248,19 @@ export async function handleSignRoute(req: Request, url: URL, context: RouteCont } } - if (!signatureHex || typeof signatureHex !== 'string') { + if (typeof signatureHex === 'string' && signatureHex.startsWith('0x')) { + signatureHex = signatureHex.slice(2); + } + if (!signatureHex || !/^[0-9a-fA-F]{128}$/.test(signatureHex)) { + try { + context.addServerLog('error', 'Invalid signature format', { id, signature: signatureHex }); + } catch { + try { console.error('Invalid signature format', id, signatureHex); } catch {} + } + signatureHex = null; + } + + if (!signatureHex) { return Response.json({ code: 'INVALID_NODE_RESPONSE', error: 'invalid signature response from node' }, { status: 502, headers }); } diff --git a/src/routes/status.ts b/src/routes/status.ts index e70593b..6beda61 100644 --- a/src/routes/status.ts +++ b/src/routes/status.ts @@ -59,7 +59,7 @@ export async function handleStatusRoute(req: Request, url: URL, context: RouteCo // Lazy-load DB only in non-headless, authenticated path const { userHasStoredCredentials } = await import('../db/database.js'); // Convert to bigint for database operation - const dbUserId = typeof parsedUserId === 'string' ? BigInt(parsedUserId) : parsedUserId; + const dbUserId = BigInt(parsedUserId); hasStoredCredentials = userHasStoredCredentials(dbUserId); } } diff --git a/src/routes/update.ts b/src/routes/update.ts index fe79555..ba743b3 100644 --- a/src/routes/update.ts +++ b/src/routes/update.ts @@ -40,6 +40,7 @@ interface UpdateResponse { const UPDATE_CHECK_TIMEOUT_MS = parseInt(process.env['UPDATE_CHECK_TIMEOUT_MS'] ?? '5000', 10) || 5000; const UPDATE_CHECK_TTL_MS = parseInt(process.env['UPDATE_CHECK_TTL_MS'] ?? '21600000', 10) || 21_600_000; // 6 hours const UPDATE_CHECK_FAILURE_TTL_MS = parseInt(process.env['UPDATE_CHECK_FAILURE_TTL_MS'] ?? '900000', 10) || 900_000; // 15 minutes +const ALLOW_PRERELEASE_UPDATES = parseBoolean(process.env['ALLOW_PRERELEASE_UPDATES']); const GITHUB_OWNER = 'FROSTR-ORG'; const GITHUB_REPO = 'igloo-server'; @@ -154,7 +155,7 @@ async function fetchLatestVersion(): Promise { if (releaseResponse.ok) { const payload = await releaseResponse.json() as { tag_name?: string; html_url?: string }; const tagName = typeof payload.tag_name === 'string' ? payload.tag_name : ''; - const parsed = parseVersion(tagName, true); + const parsed = parseVersion(tagName, ALLOW_PRERELEASE_UPDATES); if (parsed) { return { latestVersion: parsed.normalized, @@ -175,7 +176,7 @@ async function fetchLatestVersion(): Promise { for (const tag of tags) { if (!tag?.name) continue; - const parsed = parseVersion(tag.name, false); + const parsed = parseVersion(tag.name, ALLOW_PRERELEASE_UPDATES); if (!parsed) continue; if (!latest || compareVersions(parsed, latest) > 0) { latest = parsed; diff --git a/src/routes/user.ts b/src/routes/user.ts index 05ab10c..47f8ffd 100644 --- a/src/routes/user.ts +++ b/src/routes/user.ts @@ -110,13 +110,7 @@ export async function handleUserRoute( } // Database users have numeric IDs (number or string representation of bigint) - let userId: number | bigint | null = null; - if (typeof auth.userId === 'number') { - userId = auth.userId; - } else if (typeof auth.userId === 'string' && /^\d+$/.test(auth.userId)) { - // Convert string representation back to bigint for database - userId = BigInt(auth.userId); - } + const userId = resolveUserId(auth.userId); // Require a valid database user // Note: Environment auth users (API Key/Basic Auth) have string userIds and @@ -283,13 +277,16 @@ export async function handleUserRoute( if ('relays' in body) { // Validate relays format - if (body.relays === null || - (Array.isArray(body.relays) && - body.relays.every((r: any) => typeof r === 'string'))) { - updates.relays = body.relays; + if (body.relays === null) { + updates.relays = null; + } else if ( + Array.isArray(body.relays) && + body.relays.every((r: unknown): r is string => typeof r === 'string' && isValidWebSocketUrl(r)) + ) { + updates.relays = body.relays.map((relay: string) => relay.trim()); } else { return Response.json( - { error: 'Invalid relays format. Must be an array of strings or null.' }, + { error: 'Invalid relay URLs. Must use ws:// or wss://' }, { status: 400, headers } ); } @@ -365,13 +362,24 @@ export async function handleUserRoute( // Start the node under the shared lock to avoid races try { await executeUnderNodeLock(async () => { - if (!context.node && credentials) { + let latestCredentials: UserCredentials | null = null; + try { + latestCredentials = getUserCredentials( + userId, + authSecret.secret, + authSecret.isDerivedKey + ); + } catch (error) { + context.addServerLog('warn', 'Failed to re-read credentials inside node lock', error); + } + + if (!context.node && latestCredentials?.group_cred && latestCredentials?.share_cred) { context.addServerLog('info', 'Starting Bifrost node with saved credentials...'); const peerPolicies = getUserPeerPolicies(userId!); const peerPoliciesJson = peerPolicies.length > 0 ? JSON.stringify(peerPolicies) : undefined; - const groupCred = credentials.group_cred!; - const shareCred = credentials.share_cred!; - const relays = credentials.relays; + const groupCred = latestCredentials.group_cred; + const shareCred = latestCredentials.share_cred; + const relays = latestCredentials.relays; const relaysEnv = relays?.length ? relays.join(',') : undefined; const node = await createNodeWithCredentials( @@ -481,6 +489,7 @@ export async function handleUserRoute( } const relays = (body as any).relays; + let normalizedRelays: string[] | null = null; if (relays !== null) { if (!Array.isArray(relays)) { @@ -496,13 +505,14 @@ export async function handleUserRoute( { status: 400, headers } ); } + normalizedRelays = relays.map((relay: string) => relay.trim()); } // Relays are stored as plain JSON, so no auth secret needed for relay-only updates // Pass empty string and false to indicate no encryption needed const success = updateUserCredentials( userId, - { relays }, + { relays: normalizedRelays }, '', // No password/key needed for unencrypted fields false // Not a derived key ); diff --git a/src/routes/utils.test.ts b/src/routes/utils.test.ts index 6236e1e..cf0c64a 100644 --- a/src/routes/utils.test.ts +++ b/src/routes/utils.test.ts @@ -1,5 +1,16 @@ import { describe, expect, it } from 'bun:test'; -import { getValidRelays } from './utils.js'; +import { getValidRelays, normalizeRelayListForEcho } from './utils.js'; + +async function withEnv(key: string, value: string, fn: () => Promise | T): Promise { + const previous = process.env[key]; + process.env[key] = value; + try { + return await fn(); + } finally { + if (previous === undefined) delete process.env[key]; + else process.env[key] = previous; + } +} describe('getValidRelays', () => { it('returns default relay when fallback is enabled and input is empty', () => { @@ -20,4 +31,111 @@ describe('getValidRelays', () => { it('filters invalid relays and returns empty when fallback disabled', () => { expect(getValidRelays('["not-a-relay","ftp://example.com"]', { fallbackToDefault: false })).toEqual([]); }); + + it('filters IPv6 localhost relay when localhost relays are disallowed', async () => { + await withEnv('ALLOW_LOCALHOST_RELAY', 'false', () => { + expect(getValidRelays('["ws://[::1]:18002"]', { fallbackToDefault: false })).toEqual([]); + }); + }); + + it('filters 127.0.0.0/8 localhost relay range when localhost relays are disallowed', async () => { + await withEnv('ALLOW_LOCALHOST_RELAY', 'false', () => { + expect(getValidRelays('["ws://127.0.0.2:18002"]', { fallbackToDefault: false })).toEqual([]); + }); + }); + + it('filters localhost hostname relay when localhost relays are disallowed', async () => { + await withEnv('ALLOW_LOCALHOST_RELAY', 'false', () => { + expect(getValidRelays('["ws://localhost:18002"]', { fallbackToDefault: false })).toEqual([]); + }); + }); + + it('filters localhost hostname with trailing dot when localhost relays are disallowed', async () => { + await withEnv('ALLOW_LOCALHOST_RELAY', 'false', () => { + expect(getValidRelays('["ws://localhost.:18002"]', { fallbackToDefault: false })).toEqual([]); + }); + }); + + it('filters localhost subdomain relay when localhost relays are disallowed', async () => { + await withEnv('ALLOW_LOCALHOST_RELAY', 'false', () => { + expect(getValidRelays('["ws://relay.localhost:18002"]', { fallbackToDefault: false })).toEqual([]); + }); + }); + + it('filters IPv4-mapped IPv6 relay when localhost relays are disallowed', async () => { + await withEnv('ALLOW_LOCALHOST_RELAY', 'false', () => { + expect(getValidRelays('["ws://[::ffff:127.0.0.1]:18002"]', { fallbackToDefault: false })).toEqual([]); + }); + }); + + it('filters IPv4-mapped IPv6 hex relay when localhost relays are disallowed', async () => { + await withEnv('ALLOW_LOCALHOST_RELAY', 'false', () => { + expect(getValidRelays('["ws://[::ffff:7f00:1]:18002"]', { fallbackToDefault: false })).toEqual([]); + }); + }); + + it('filters IPv4-mapped IPv6 ::ffff:0: relay when localhost relays are disallowed', async () => { + await withEnv('ALLOW_LOCALHOST_RELAY', 'false', () => { + expect(getValidRelays('["ws://[::ffff:0:7f00:1]:18002"]', { fallbackToDefault: false })).toEqual([]); + }); + }); + + it('filters expanded IPv6 loopback relay when localhost relays are disallowed', async () => { + await withEnv('ALLOW_LOCALHOST_RELAY', 'false', () => { + expect(getValidRelays('["ws://[0:0:0:0:0:0:0:1]:18002"]', { fallbackToDefault: false })).toEqual([]); + }); + }); + + it('filters expanded IPv4-mapped IPv6 relay when localhost relays are disallowed', async () => { + await withEnv('ALLOW_LOCALHOST_RELAY', 'false', () => { + expect(getValidRelays('["ws://[0:0:0:0:0:ffff:7f00:1]:18002"]', { fallbackToDefault: false })).toEqual([]); + }); + }); + + it('keeps localhost relay when localhost relays are explicitly allowed', async () => { + await withEnv('ALLOW_LOCALHOST_RELAY', 'true', () => { + expect(getValidRelays('["ws://127.0.0.1:18002"]', { fallbackToDefault: false })) + .toEqual(['ws://127.0.0.1:18002']); + }); + }); +}); + +describe('normalizeRelayListForEcho', () => { + it('filters localhost relays when localhost relays are disallowed', async () => { + await withEnv('ALLOW_LOCALHOST_RELAY', 'false', () => { + expect( + normalizeRelayListForEcho([ + 'ws://127.0.0.1:18002', + 'ws://[::1]:18002', + 'ws://[::ffff:127.0.0.1]:18002', + 'ws://localhost:18002', + 'wss://relay.example.com' + ]) + ).toEqual(['wss://relay.example.com']); + }); + }); + + it('keeps localhost relay in echo list when explicitly allowed', async () => { + await withEnv('ALLOW_LOCALHOST_RELAY', 'true', () => { + expect(normalizeRelayListForEcho(['ws://127.0.0.1:18002'])).toEqual(['ws://127.0.0.1:18002']); + }); + }); + + it('keeps localhost hostname in echo list when explicitly allowed', async () => { + await withEnv('ALLOW_LOCALHOST_RELAY', 'true', () => { + expect(normalizeRelayListForEcho(['ws://localhost:18002'])).toEqual(['ws://localhost:18002']); + }); + }); + + it('keeps IPv6 localhost relay in echo list when explicitly allowed', async () => { + await withEnv('ALLOW_LOCALHOST_RELAY', 'true', () => { + expect(normalizeRelayListForEcho(['ws://[::1]:18002'])).toEqual(['ws://[::1]:18002']); + }); + }); + + it('keeps IPv4-mapped IPv6 relay in echo list when explicitly allowed', async () => { + await withEnv('ALLOW_LOCALHOST_RELAY', 'true', () => { + expect(normalizeRelayListForEcho(['ws://[::ffff:127.0.0.1]:18002'])).toEqual(['ws://[::ffff:127.0.0.1]:18002']); + }); + }); }); diff --git a/src/routes/utils.ts b/src/routes/utils.ts index 732859d..0e56085 100644 --- a/src/routes/utils.ts +++ b/src/routes/utils.ts @@ -32,6 +32,56 @@ export function binaryToHex(data: Uint8Array | Buffer): string | null { return hex.toLowerCase(); } +function isValidIpv4Address(hostname: string): boolean { + const octets = hostname.split('.'); + if (octets.length !== 4) return false; + return octets.every((octet) => /^\d+$/.test(octet) && Number(octet) >= 0 && Number(octet) <= 255); +} + +function decodeMappedIpv4(segment: string): string | null { + if (isValidIpv4Address(segment)) return segment; + const mappedHex = segment.match(/^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i); + if (!mappedHex) return null; + const hi = Number.parseInt(mappedHex[1], 16); + const lo = Number.parseInt(mappedHex[2], 16); + const decoded = `${(hi >> 8) & 0xff}.${hi & 0xff}.${(lo >> 8) & 0xff}.${lo & 0xff}`; + return isValidIpv4Address(decoded) ? decoded : null; +} + +function extractIpv4MappedIpv6(hostname: string): string | null { + const mappedPrefixes = [ + /^::ffff:(.+)$/i, + /^::ffff:0:(.+)$/i, + ]; + for (const pattern of mappedPrefixes) { + const match = hostname.match(pattern); + if (!match) continue; + const decoded = decodeMappedIpv4(match[1]); + if (decoded) return decoded; + } + return null; +} + +function isLoopbackRelayHost(hostname: string): boolean { + let normalized = hostname.replace(/\.+$/, '').replace(/^\[(.*)\]$/, '$1').toLowerCase(); + if ( + normalized === 'localhost' || + normalized.endsWith('.localhost') || + normalized === '::1' || + normalized === '0:0:0:0:0:0:0:1' + ) return true; + + const mappedIpv4 = extractIpv4MappedIpv6(normalized); + if (mappedIpv4) { + normalized = mappedIpv4; + } + + const octets = normalized.split('.'); + if (octets.length !== 4) return false; + if (octets[0] !== '127') return false; + return isValidIpv4Address(normalized); +} + // Helper function to get valid relay URLs export function getValidRelays( envRelays?: string, @@ -60,11 +110,13 @@ export function getValidRelays( } // Validate each relay URL and exclude localhost to avoid conflicts + const allowLocalhost = process.env['ALLOW_LOCALHOST_RELAY'] === 'true'; const validRelays = relayList.filter(relay => { try { const url = new URL(relay); // Exclude localhost relays to avoid conflicts with our server - if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') { + // (unless explicitly allowed, e.g. for testing) + if (!allowLocalhost && isLoopbackRelayHost(url.hostname)) { console.warn(`Excluding localhost relay to avoid conflicts: ${relay}`); return false; } @@ -96,7 +148,9 @@ export function getValidRelays( } // Helper functions for .env file management -const ENV_FILE_PATH = '.env'; +function getEnvFilePath(): string { + return process.env.ENV_FILE_PATH?.trim() || '.env'; +} // Security: Whitelist of allowed environment variable keys (for write/validation) // IMPORTANT: SESSION_SECRET must NEVER be included here - it's strictly server-only @@ -246,8 +300,9 @@ function stringifyEnvFile(env: Record): string { export async function readEnvFile(): Promise> { try { - await fs.access(ENV_FILE_PATH); - const content = await fs.readFile(ENV_FILE_PATH, 'utf-8'); + const envFilePath = getEnvFilePath(); + await fs.access(envFilePath); + const content = await fs.readFile(envFilePath, 'utf-8'); const fileEnv = parseEnvFile(content); // Merge with actual environment variables as fallback @@ -290,7 +345,7 @@ function getEnvVarsFromProcess(): Record { // Get the modification time of the environment file export async function getEnvFileModTime(): Promise { try { - const stats = await fs.stat(ENV_FILE_PATH); + const stats = await fs.stat(getEnvFilePath()); return stats.mtime.toISOString(); } catch (error) { // File doesn't exist or error accessing it @@ -354,7 +409,7 @@ export async function writeEnvFileWithTimestamp(env: Record): Pr } const content = stringifyEnvFile(env); - await fs.writeFile(ENV_FILE_PATH, content, 'utf-8'); + await fs.writeFile(getEnvFilePath(), content, 'utf-8'); return true; } catch (error) { console.error('Error writing .env file:', error); @@ -365,7 +420,7 @@ export async function writeEnvFileWithTimestamp(env: Record): Pr export async function writeEnvFile(env: Record): Promise { try { const content = stringifyEnvFile(env); - await fs.writeFile(ENV_FILE_PATH, content, 'utf-8'); + await fs.writeFile(getEnvFilePath(), content, 'utf-8'); return true; } catch (error) { console.error('Error writing .env file:', error); @@ -392,51 +447,48 @@ export function getContentType(filePath: string): string { } // Helper function to safely serialize data with circular reference handling -export function safeStringify(obj: any, maxDepth = 3): string { - const seen = new WeakSet(); - - const replacer = (_key: string, value: any, depth = 0): any => { +export function safeStringify(obj: unknown, maxDepth = 3): string { + const seen = new WeakSet(); + + const serializeValue = (value: unknown, depth = 0): unknown => { if (depth > maxDepth) { return '[Max Depth Reached]'; } - + if (value === null || typeof value !== 'object') { return value; } - + if (seen.has(value)) { return '[Circular Reference]'; } - seen.add(value); - + if (Array.isArray(value)) { - return value.map((item, index) => replacer(String(index), item, depth + 1)); + return value.map((item) => serializeValue(item, depth + 1)); } - - const result: any = {}; - for (const [k, v] of Object.entries(value)) { - // Skip functions and undefined values - if (typeof v === 'function' || v === undefined) { + + const result: Record = {}; + for (const [key, entryValue] of Object.entries(value as Record)) { + if (typeof entryValue === 'function' || entryValue === undefined) { continue; } // Explicitly skip sensitive binary keys to avoid accidental exposure - if (k === 'derivedKey') { + if (key === 'derivedKey') { continue; } - result[k] = replacer(k, v, depth + 1); + result[key] = serializeValue(entryValue, depth + 1); } - return result; }; - + try { - return JSON.stringify(replacer('', obj)); + return JSON.stringify(serializeValue(obj, 0)); } catch (error) { return JSON.stringify({ error: 'Failed to serialize object', type: typeof obj, - constructor: obj?.constructor?.name || 'Unknown' + constructor: (obj as { constructor?: { name?: string } } | null | undefined)?.constructor?.name || 'Unknown' }); } } @@ -795,14 +847,15 @@ export function validateRelayUrls(relays: any): { valid: boolean; urls?: string[ export function normalizeRelayListForEcho(relays: any): string[] | undefined { const validation = validateRelayUrls(relays); if (!validation.valid || !validation.urls || validation.urls.length === 0) return undefined; + const allowLocalhost = process.env['ALLOW_LOCALHOST_RELAY'] === 'true'; const filtered = validation.urls .map((r) => r.trim()) .filter((r) => r.length > 0) .filter((r) => { try { const u = new URL(r); - return (u.protocol === 'ws:' || u.protocol === 'wss:') && - u.hostname !== 'localhost' && u.hostname !== '127.0.0.1' && u.hostname !== '::1'; + if (!allowLocalhost && isLoopbackRelayHost(u.hostname)) return false; + return true; } catch { return false; } diff --git a/src/server.ts b/src/server.ts index d720965..f15ff76 100644 --- a/src/server.ts +++ b/src/server.ts @@ -415,12 +415,14 @@ async function initializeDatabase(): Promise { } } -// Initialize database with single exit point -initializeDatabase().catch((err) => { +// Initialize database before starting relay/node setup +try { + await initializeDatabase(); +} catch (err) { console.error('❌ Fatal initialization error:'); console.error(' ', err instanceof Error ? err.message : String(err)); process.exit(1); -}); +} // Create the Nostr relay const relay = new NostrRelay(); @@ -653,6 +655,11 @@ const parsedMsgBurst = Number.parseInt(process.env.WS_MSG_BURST ?? '', 10); const WS_MSG_BURST = Number.isFinite(parsedMsgBurst) && parsedMsgBurst > 0 ? Math.max(parsedMsgBurst, WS_MSG_RATE) : Math.max(WS_MSG_RATE, 40); +const parsedAllowQueryCredentials = (process.env.ALLOW_QUERY_CREDENTIALS ?? '').trim().toLowerCase(); +// Backward-compatible default: allow legacy ?apiKey= / ?sessionId= WS auth unless explicitly disabled. +const WS_ALLOW_QUERY_CREDENTIALS = parsedAllowQueryCredentials === '' + ? true + : !['0', 'false', 'no', 'off'].includes(parsedAllowQueryCredentials); const WS_POLICY_CLOSE = 1008; // Policy violation const wsConnectionsPerIp = new Map(); @@ -817,10 +824,12 @@ const server = serve({ let authReq = req; const qpHeaders = new Headers(req.headers); let qpTouched = false; - const qpApiKey = url.searchParams.get('apiKey'); - const qpSessionId = url.searchParams.get('sessionId'); - if (qpApiKey) { qpHeaders.set('X-API-Key', qpApiKey); qpTouched = true; } - if (qpSessionId) { qpHeaders.set('X-Session-ID', qpSessionId); qpTouched = true; } + if (WS_ALLOW_QUERY_CREDENTIALS) { + const qpApiKey = url.searchParams.get('apiKey'); + const qpSessionId = url.searchParams.get('sessionId'); + if (qpApiKey) { qpHeaders.set('X-API-Key', qpApiKey); qpTouched = true; } + if (qpSessionId) { qpHeaders.set('X-Session-ID', qpSessionId); qpTouched = true; } + } if (qpTouched) { authReq = new Request(req.url, { method: req.method, headers: qpHeaders }); } diff --git a/src/types/global.d.ts b/src/types/global.d.ts index 3a4c5de..180e07b 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -1,2 +1,8 @@ +/** + * Global runtime type augmentations for the Bun-based server build. + * This file exports no runtime symbols; `export {}` keeps module scope while + * still allowing ambient/global declarations to be merged safely. + * Keep this file loaded before dependent declarations and pinned to bun-types. + */ /// export {}; diff --git a/src/utils/rate-limiter.ts b/src/utils/rate-limiter.ts index 8334059..5e1b538 100644 --- a/src/utils/rate-limiter.ts +++ b/src/utils/rate-limiter.ts @@ -74,16 +74,17 @@ export class PersistentRateLimiter { private cleanupTimer: ReturnType | null = null; constructor(db?: Database, fallbackState?: Map) { - // Only initialize if not in headless mode and DB provided - if (!HEADLESS && db) { - this.db = db; - this.startCleanup(); - } - - if (fallbackState && fallbackState.size > 0) { + if (fallbackState) { // Preserve any in-memory state so new instances retain prior counts if needed this.fallbackStore = new Map(fallbackState); } + + if (!HEADLESS) { + if (db) { + this.db = db; + } + this.startCleanup(); + } } /** @@ -182,7 +183,8 @@ export class PersistentRateLimiter { } } - throw new RateLimiterUnavailableError(); + // Defensive fallback for type completeness; loop paths above should always return or throw. + return this.checkMemoryLimit(identifier, config, Date.now()); } /** @@ -247,8 +249,11 @@ export class PersistentRateLimiter { this.fallbackStore.delete(`${bucket}:${identifier}`); } else { // Clear all buckets for this identifier - for (const key of this.fallbackStore.keys()) { - if (key.endsWith(`:${identifier}`)) { + const keys = Array.from(this.fallbackStore.keys()); + for (const key of keys) { + const separatorIndex = key.indexOf(':'); + const keyIdentifier = separatorIndex === -1 ? key : key.slice(separatorIndex + 1); + if (keyIdentifier === identifier) { this.fallbackStore.delete(key); } } @@ -264,14 +269,13 @@ export class PersistentRateLimiter { if (this.db) { try { - this.db + const result = this.db .prepare('DELETE FROM rate_limits WHERE last_attempt < ?') .run(cutoff); // Only log if entries were deleted - const changes = this.db.query('SELECT changes() as c').get() as { c: number } | null; - if (changes && changes.c > 0) { - console.log(`[RateLimiter] Cleaned up ${changes.c} expired entries`); + if (result.changes > 0) { + console.log(`[RateLimiter] Cleaned up ${result.changes} expired entries`); } } catch (error) { console.error('[RateLimiter] Cleanup failed:', error); diff --git a/static/docs/swagger-ui-bundle.js b/static/docs/swagger-ui-bundle.js deleted file mode 100644 index 8dbca51..0000000 --- a/static/docs/swagger-ui-bundle.js +++ /dev/null @@ -1,3 +0,0 @@ -/*! For license information please see swagger-ui-bundle.js.LICENSE.txt */ -!function webpackUniversalModuleDefinition(i,s){"object"==typeof exports&&"object"==typeof module?module.exports=s():"function"==typeof define&&define.amd?define([],s):"object"==typeof exports?exports.SwaggerUIBundle=s():i.SwaggerUIBundle=s()}(this,(()=>(()=>{var i={17967:(i,s)=>{"use strict";s.Nm=s.Rq=void 0;var u=/^([^\w]*)(javascript|data|vbscript)/im,m=/&#(\w+)(^\w|;)?/g,v=/&(newline|tab);/gi,_=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,j=/^.+(:|:)/gim,M=[".","/"];s.Rq="about:blank",s.Nm=function sanitizeUrl(i){if(!i)return s.Rq;var $=function decodeHtmlCharacters(i){return i.replace(_,"").replace(m,(function(i,s){return String.fromCharCode(s)}))}(i).replace(v,"").replace(_,"").trim();if(!$)return s.Rq;if(function isRelativeUrlWithoutProtocol(i){return M.indexOf(i[0])>-1}($))return $;var W=$.match(j);if(!W)return $;var X=W[0];return u.test(X)?s.Rq:$}},79742:(i,s)=>{"use strict";s.byteLength=function byteLength(i){var s=getLens(i),u=s[0],m=s[1];return 3*(u+m)/4-m},s.toByteArray=function toByteArray(i){var s,u,_=getLens(i),j=_[0],M=_[1],$=new v(function _byteLength(i,s,u){return 3*(s+u)/4-u}(0,j,M)),W=0,X=M>0?j-4:j;for(u=0;u>16&255,$[W++]=s>>8&255,$[W++]=255&s;2===M&&(s=m[i.charCodeAt(u)]<<2|m[i.charCodeAt(u+1)]>>4,$[W++]=255&s);1===M&&(s=m[i.charCodeAt(u)]<<10|m[i.charCodeAt(u+1)]<<4|m[i.charCodeAt(u+2)]>>2,$[W++]=s>>8&255,$[W++]=255&s);return $},s.fromByteArray=function fromByteArray(i){for(var s,m=i.length,v=m%3,_=[],j=16383,M=0,$=m-v;M<$;M+=j)_.push(encodeChunk(i,M,M+j>$?$:M+j));1===v?(s=i[m-1],_.push(u[s>>2]+u[s<<4&63]+"==")):2===v&&(s=(i[m-2]<<8)+i[m-1],_.push(u[s>>10]+u[s>>4&63]+u[s<<2&63]+"="));return _.join("")};for(var u=[],m=[],v="undefined"!=typeof Uint8Array?Uint8Array:Array,_="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",j=0;j<64;++j)u[j]=_[j],m[_.charCodeAt(j)]=j;function getLens(i){var s=i.length;if(s%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var u=i.indexOf("=");return-1===u&&(u=s),[u,u===s?0:4-u%4]}function encodeChunk(i,s,m){for(var v,_,j=[],M=s;M>18&63]+u[_>>12&63]+u[_>>6&63]+u[63&_]);return j.join("")}m["-".charCodeAt(0)]=62,m["_".charCodeAt(0)]=63},48764:(i,s,u)=>{"use strict";const m=u(79742),v=u(80645),_="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;s.Buffer=Buffer,s.SlowBuffer=function SlowBuffer(i){+i!=i&&(i=0);return Buffer.alloc(+i)},s.INSPECT_MAX_BYTES=50;const j=2147483647;function createBuffer(i){if(i>j)throw new RangeError('The value "'+i+'" is invalid for option "size"');const s=new Uint8Array(i);return Object.setPrototypeOf(s,Buffer.prototype),s}function Buffer(i,s,u){if("number"==typeof i){if("string"==typeof s)throw new TypeError('The "string" argument must be of type string. Received type number');return allocUnsafe(i)}return from(i,s,u)}function from(i,s,u){if("string"==typeof i)return function fromString(i,s){"string"==typeof s&&""!==s||(s="utf8");if(!Buffer.isEncoding(s))throw new TypeError("Unknown encoding: "+s);const u=0|byteLength(i,s);let m=createBuffer(u);const v=m.write(i,s);v!==u&&(m=m.slice(0,v));return m}(i,s);if(ArrayBuffer.isView(i))return function fromArrayView(i){if(isInstance(i,Uint8Array)){const s=new Uint8Array(i);return fromArrayBuffer(s.buffer,s.byteOffset,s.byteLength)}return fromArrayLike(i)}(i);if(null==i)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof i);if(isInstance(i,ArrayBuffer)||i&&isInstance(i.buffer,ArrayBuffer))return fromArrayBuffer(i,s,u);if("undefined"!=typeof SharedArrayBuffer&&(isInstance(i,SharedArrayBuffer)||i&&isInstance(i.buffer,SharedArrayBuffer)))return fromArrayBuffer(i,s,u);if("number"==typeof i)throw new TypeError('The "value" argument must not be of type number. Received type number');const m=i.valueOf&&i.valueOf();if(null!=m&&m!==i)return Buffer.from(m,s,u);const v=function fromObject(i){if(Buffer.isBuffer(i)){const s=0|checked(i.length),u=createBuffer(s);return 0===u.length||i.copy(u,0,0,s),u}if(void 0!==i.length)return"number"!=typeof i.length||numberIsNaN(i.length)?createBuffer(0):fromArrayLike(i);if("Buffer"===i.type&&Array.isArray(i.data))return fromArrayLike(i.data)}(i);if(v)return v;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof i[Symbol.toPrimitive])return Buffer.from(i[Symbol.toPrimitive]("string"),s,u);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof i)}function assertSize(i){if("number"!=typeof i)throw new TypeError('"size" argument must be of type number');if(i<0)throw new RangeError('The value "'+i+'" is invalid for option "size"')}function allocUnsafe(i){return assertSize(i),createBuffer(i<0?0:0|checked(i))}function fromArrayLike(i){const s=i.length<0?0:0|checked(i.length),u=createBuffer(s);for(let m=0;m=j)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+j.toString(16)+" bytes");return 0|i}function byteLength(i,s){if(Buffer.isBuffer(i))return i.length;if(ArrayBuffer.isView(i)||isInstance(i,ArrayBuffer))return i.byteLength;if("string"!=typeof i)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof i);const u=i.length,m=arguments.length>2&&!0===arguments[2];if(!m&&0===u)return 0;let v=!1;for(;;)switch(s){case"ascii":case"latin1":case"binary":return u;case"utf8":case"utf-8":return utf8ToBytes(i).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*u;case"hex":return u>>>1;case"base64":return base64ToBytes(i).length;default:if(v)return m?-1:utf8ToBytes(i).length;s=(""+s).toLowerCase(),v=!0}}function slowToString(i,s,u){let m=!1;if((void 0===s||s<0)&&(s=0),s>this.length)return"";if((void 0===u||u>this.length)&&(u=this.length),u<=0)return"";if((u>>>=0)<=(s>>>=0))return"";for(i||(i="utf8");;)switch(i){case"hex":return hexSlice(this,s,u);case"utf8":case"utf-8":return utf8Slice(this,s,u);case"ascii":return asciiSlice(this,s,u);case"latin1":case"binary":return latin1Slice(this,s,u);case"base64":return base64Slice(this,s,u);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return utf16leSlice(this,s,u);default:if(m)throw new TypeError("Unknown encoding: "+i);i=(i+"").toLowerCase(),m=!0}}function swap(i,s,u){const m=i[s];i[s]=i[u],i[u]=m}function bidirectionalIndexOf(i,s,u,m,v){if(0===i.length)return-1;if("string"==typeof u?(m=u,u=0):u>2147483647?u=2147483647:u<-2147483648&&(u=-2147483648),numberIsNaN(u=+u)&&(u=v?0:i.length-1),u<0&&(u=i.length+u),u>=i.length){if(v)return-1;u=i.length-1}else if(u<0){if(!v)return-1;u=0}if("string"==typeof s&&(s=Buffer.from(s,m)),Buffer.isBuffer(s))return 0===s.length?-1:arrayIndexOf(i,s,u,m,v);if("number"==typeof s)return s&=255,"function"==typeof Uint8Array.prototype.indexOf?v?Uint8Array.prototype.indexOf.call(i,s,u):Uint8Array.prototype.lastIndexOf.call(i,s,u):arrayIndexOf(i,[s],u,m,v);throw new TypeError("val must be string, number or Buffer")}function arrayIndexOf(i,s,u,m,v){let _,j=1,M=i.length,$=s.length;if(void 0!==m&&("ucs2"===(m=String(m).toLowerCase())||"ucs-2"===m||"utf16le"===m||"utf-16le"===m)){if(i.length<2||s.length<2)return-1;j=2,M/=2,$/=2,u/=2}function read(i,s){return 1===j?i[s]:i.readUInt16BE(s*j)}if(v){let m=-1;for(_=u;_M&&(u=M-$),_=u;_>=0;_--){let u=!0;for(let m=0;m<$;m++)if(read(i,_+m)!==read(s,m)){u=!1;break}if(u)return _}return-1}function hexWrite(i,s,u,m){u=Number(u)||0;const v=i.length-u;m?(m=Number(m))>v&&(m=v):m=v;const _=s.length;let j;for(m>_/2&&(m=_/2),j=0;j>8,v=u%256,_.push(v),_.push(m);return _}(s,i.length-u),i,u,m)}function base64Slice(i,s,u){return 0===s&&u===i.length?m.fromByteArray(i):m.fromByteArray(i.slice(s,u))}function utf8Slice(i,s,u){u=Math.min(i.length,u);const m=[];let v=s;for(;v239?4:s>223?3:s>191?2:1;if(v+j<=u){let u,m,M,$;switch(j){case 1:s<128&&(_=s);break;case 2:u=i[v+1],128==(192&u)&&($=(31&s)<<6|63&u,$>127&&(_=$));break;case 3:u=i[v+1],m=i[v+2],128==(192&u)&&128==(192&m)&&($=(15&s)<<12|(63&u)<<6|63&m,$>2047&&($<55296||$>57343)&&(_=$));break;case 4:u=i[v+1],m=i[v+2],M=i[v+3],128==(192&u)&&128==(192&m)&&128==(192&M)&&($=(15&s)<<18|(63&u)<<12|(63&m)<<6|63&M,$>65535&&$<1114112&&(_=$))}}null===_?(_=65533,j=1):_>65535&&(_-=65536,m.push(_>>>10&1023|55296),_=56320|1023&_),m.push(_),v+=j}return function decodeCodePointsArray(i){const s=i.length;if(s<=M)return String.fromCharCode.apply(String,i);let u="",m=0;for(;mm.length?(Buffer.isBuffer(s)||(s=Buffer.from(s)),s.copy(m,v)):Uint8Array.prototype.set.call(m,s,v);else{if(!Buffer.isBuffer(s))throw new TypeError('"list" argument must be an Array of Buffers');s.copy(m,v)}v+=s.length}return m},Buffer.byteLength=byteLength,Buffer.prototype._isBuffer=!0,Buffer.prototype.swap16=function swap16(){const i=this.length;if(i%2!=0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(let s=0;su&&(i+=" ... "),""},_&&(Buffer.prototype[_]=Buffer.prototype.inspect),Buffer.prototype.compare=function compare(i,s,u,m,v){if(isInstance(i,Uint8Array)&&(i=Buffer.from(i,i.offset,i.byteLength)),!Buffer.isBuffer(i))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof i);if(void 0===s&&(s=0),void 0===u&&(u=i?i.length:0),void 0===m&&(m=0),void 0===v&&(v=this.length),s<0||u>i.length||m<0||v>this.length)throw new RangeError("out of range index");if(m>=v&&s>=u)return 0;if(m>=v)return-1;if(s>=u)return 1;if(this===i)return 0;let _=(v>>>=0)-(m>>>=0),j=(u>>>=0)-(s>>>=0);const M=Math.min(_,j),$=this.slice(m,v),W=i.slice(s,u);for(let i=0;i>>=0,isFinite(u)?(u>>>=0,void 0===m&&(m="utf8")):(m=u,u=void 0)}const v=this.length-s;if((void 0===u||u>v)&&(u=v),i.length>0&&(u<0||s<0)||s>this.length)throw new RangeError("Attempt to write outside buffer bounds");m||(m="utf8");let _=!1;for(;;)switch(m){case"hex":return hexWrite(this,i,s,u);case"utf8":case"utf-8":return utf8Write(this,i,s,u);case"ascii":case"latin1":case"binary":return asciiWrite(this,i,s,u);case"base64":return base64Write(this,i,s,u);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return ucs2Write(this,i,s,u);default:if(_)throw new TypeError("Unknown encoding: "+m);m=(""+m).toLowerCase(),_=!0}},Buffer.prototype.toJSON=function toJSON(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};const M=4096;function asciiSlice(i,s,u){let m="";u=Math.min(i.length,u);for(let v=s;vm)&&(u=m);let v="";for(let m=s;mu)throw new RangeError("Trying to access beyond buffer length")}function checkInt(i,s,u,m,v,_){if(!Buffer.isBuffer(i))throw new TypeError('"buffer" argument must be a Buffer instance');if(s>v||s<_)throw new RangeError('"value" argument is out of bounds');if(u+m>i.length)throw new RangeError("Index out of range")}function wrtBigUInt64LE(i,s,u,m,v){checkIntBI(s,m,v,i,u,7);let _=Number(s&BigInt(4294967295));i[u++]=_,_>>=8,i[u++]=_,_>>=8,i[u++]=_,_>>=8,i[u++]=_;let j=Number(s>>BigInt(32)&BigInt(4294967295));return i[u++]=j,j>>=8,i[u++]=j,j>>=8,i[u++]=j,j>>=8,i[u++]=j,u}function wrtBigUInt64BE(i,s,u,m,v){checkIntBI(s,m,v,i,u,7);let _=Number(s&BigInt(4294967295));i[u+7]=_,_>>=8,i[u+6]=_,_>>=8,i[u+5]=_,_>>=8,i[u+4]=_;let j=Number(s>>BigInt(32)&BigInt(4294967295));return i[u+3]=j,j>>=8,i[u+2]=j,j>>=8,i[u+1]=j,j>>=8,i[u]=j,u+8}function checkIEEE754(i,s,u,m,v,_){if(u+m>i.length)throw new RangeError("Index out of range");if(u<0)throw new RangeError("Index out of range")}function writeFloat(i,s,u,m,_){return s=+s,u>>>=0,_||checkIEEE754(i,0,u,4),v.write(i,s,u,m,23,4),u+4}function writeDouble(i,s,u,m,_){return s=+s,u>>>=0,_||checkIEEE754(i,0,u,8),v.write(i,s,u,m,52,8),u+8}Buffer.prototype.slice=function slice(i,s){const u=this.length;(i=~~i)<0?(i+=u)<0&&(i=0):i>u&&(i=u),(s=void 0===s?u:~~s)<0?(s+=u)<0&&(s=0):s>u&&(s=u),s>>=0,s>>>=0,u||checkOffset(i,s,this.length);let m=this[i],v=1,_=0;for(;++_>>=0,s>>>=0,u||checkOffset(i,s,this.length);let m=this[i+--s],v=1;for(;s>0&&(v*=256);)m+=this[i+--s]*v;return m},Buffer.prototype.readUint8=Buffer.prototype.readUInt8=function readUInt8(i,s){return i>>>=0,s||checkOffset(i,1,this.length),this[i]},Buffer.prototype.readUint16LE=Buffer.prototype.readUInt16LE=function readUInt16LE(i,s){return i>>>=0,s||checkOffset(i,2,this.length),this[i]|this[i+1]<<8},Buffer.prototype.readUint16BE=Buffer.prototype.readUInt16BE=function readUInt16BE(i,s){return i>>>=0,s||checkOffset(i,2,this.length),this[i]<<8|this[i+1]},Buffer.prototype.readUint32LE=Buffer.prototype.readUInt32LE=function readUInt32LE(i,s){return i>>>=0,s||checkOffset(i,4,this.length),(this[i]|this[i+1]<<8|this[i+2]<<16)+16777216*this[i+3]},Buffer.prototype.readUint32BE=Buffer.prototype.readUInt32BE=function readUInt32BE(i,s){return i>>>=0,s||checkOffset(i,4,this.length),16777216*this[i]+(this[i+1]<<16|this[i+2]<<8|this[i+3])},Buffer.prototype.readBigUInt64LE=defineBigIntMethod((function readBigUInt64LE(i){validateNumber(i>>>=0,"offset");const s=this[i],u=this[i+7];void 0!==s&&void 0!==u||boundsError(i,this.length-8);const m=s+256*this[++i]+65536*this[++i]+this[++i]*2**24,v=this[++i]+256*this[++i]+65536*this[++i]+u*2**24;return BigInt(m)+(BigInt(v)<>>=0,"offset");const s=this[i],u=this[i+7];void 0!==s&&void 0!==u||boundsError(i,this.length-8);const m=s*2**24+65536*this[++i]+256*this[++i]+this[++i],v=this[++i]*2**24+65536*this[++i]+256*this[++i]+u;return(BigInt(m)<>>=0,s>>>=0,u||checkOffset(i,s,this.length);let m=this[i],v=1,_=0;for(;++_=v&&(m-=Math.pow(2,8*s)),m},Buffer.prototype.readIntBE=function readIntBE(i,s,u){i>>>=0,s>>>=0,u||checkOffset(i,s,this.length);let m=s,v=1,_=this[i+--m];for(;m>0&&(v*=256);)_+=this[i+--m]*v;return v*=128,_>=v&&(_-=Math.pow(2,8*s)),_},Buffer.prototype.readInt8=function readInt8(i,s){return i>>>=0,s||checkOffset(i,1,this.length),128&this[i]?-1*(255-this[i]+1):this[i]},Buffer.prototype.readInt16LE=function readInt16LE(i,s){i>>>=0,s||checkOffset(i,2,this.length);const u=this[i]|this[i+1]<<8;return 32768&u?4294901760|u:u},Buffer.prototype.readInt16BE=function readInt16BE(i,s){i>>>=0,s||checkOffset(i,2,this.length);const u=this[i+1]|this[i]<<8;return 32768&u?4294901760|u:u},Buffer.prototype.readInt32LE=function readInt32LE(i,s){return i>>>=0,s||checkOffset(i,4,this.length),this[i]|this[i+1]<<8|this[i+2]<<16|this[i+3]<<24},Buffer.prototype.readInt32BE=function readInt32BE(i,s){return i>>>=0,s||checkOffset(i,4,this.length),this[i]<<24|this[i+1]<<16|this[i+2]<<8|this[i+3]},Buffer.prototype.readBigInt64LE=defineBigIntMethod((function readBigInt64LE(i){validateNumber(i>>>=0,"offset");const s=this[i],u=this[i+7];void 0!==s&&void 0!==u||boundsError(i,this.length-8);const m=this[i+4]+256*this[i+5]+65536*this[i+6]+(u<<24);return(BigInt(m)<>>=0,"offset");const s=this[i],u=this[i+7];void 0!==s&&void 0!==u||boundsError(i,this.length-8);const m=(s<<24)+65536*this[++i]+256*this[++i]+this[++i];return(BigInt(m)<>>=0,s||checkOffset(i,4,this.length),v.read(this,i,!0,23,4)},Buffer.prototype.readFloatBE=function readFloatBE(i,s){return i>>>=0,s||checkOffset(i,4,this.length),v.read(this,i,!1,23,4)},Buffer.prototype.readDoubleLE=function readDoubleLE(i,s){return i>>>=0,s||checkOffset(i,8,this.length),v.read(this,i,!0,52,8)},Buffer.prototype.readDoubleBE=function readDoubleBE(i,s){return i>>>=0,s||checkOffset(i,8,this.length),v.read(this,i,!1,52,8)},Buffer.prototype.writeUintLE=Buffer.prototype.writeUIntLE=function writeUIntLE(i,s,u,m){if(i=+i,s>>>=0,u>>>=0,!m){checkInt(this,i,s,u,Math.pow(2,8*u)-1,0)}let v=1,_=0;for(this[s]=255&i;++_>>=0,u>>>=0,!m){checkInt(this,i,s,u,Math.pow(2,8*u)-1,0)}let v=u-1,_=1;for(this[s+v]=255&i;--v>=0&&(_*=256);)this[s+v]=i/_&255;return s+u},Buffer.prototype.writeUint8=Buffer.prototype.writeUInt8=function writeUInt8(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,1,255,0),this[s]=255&i,s+1},Buffer.prototype.writeUint16LE=Buffer.prototype.writeUInt16LE=function writeUInt16LE(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,2,65535,0),this[s]=255&i,this[s+1]=i>>>8,s+2},Buffer.prototype.writeUint16BE=Buffer.prototype.writeUInt16BE=function writeUInt16BE(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,2,65535,0),this[s]=i>>>8,this[s+1]=255&i,s+2},Buffer.prototype.writeUint32LE=Buffer.prototype.writeUInt32LE=function writeUInt32LE(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,4,4294967295,0),this[s+3]=i>>>24,this[s+2]=i>>>16,this[s+1]=i>>>8,this[s]=255&i,s+4},Buffer.prototype.writeUint32BE=Buffer.prototype.writeUInt32BE=function writeUInt32BE(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,4,4294967295,0),this[s]=i>>>24,this[s+1]=i>>>16,this[s+2]=i>>>8,this[s+3]=255&i,s+4},Buffer.prototype.writeBigUInt64LE=defineBigIntMethod((function writeBigUInt64LE(i,s=0){return wrtBigUInt64LE(this,i,s,BigInt(0),BigInt("0xffffffffffffffff"))})),Buffer.prototype.writeBigUInt64BE=defineBigIntMethod((function writeBigUInt64BE(i,s=0){return wrtBigUInt64BE(this,i,s,BigInt(0),BigInt("0xffffffffffffffff"))})),Buffer.prototype.writeIntLE=function writeIntLE(i,s,u,m){if(i=+i,s>>>=0,!m){const m=Math.pow(2,8*u-1);checkInt(this,i,s,u,m-1,-m)}let v=0,_=1,j=0;for(this[s]=255&i;++v>0)-j&255;return s+u},Buffer.prototype.writeIntBE=function writeIntBE(i,s,u,m){if(i=+i,s>>>=0,!m){const m=Math.pow(2,8*u-1);checkInt(this,i,s,u,m-1,-m)}let v=u-1,_=1,j=0;for(this[s+v]=255&i;--v>=0&&(_*=256);)i<0&&0===j&&0!==this[s+v+1]&&(j=1),this[s+v]=(i/_>>0)-j&255;return s+u},Buffer.prototype.writeInt8=function writeInt8(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,1,127,-128),i<0&&(i=255+i+1),this[s]=255&i,s+1},Buffer.prototype.writeInt16LE=function writeInt16LE(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,2,32767,-32768),this[s]=255&i,this[s+1]=i>>>8,s+2},Buffer.prototype.writeInt16BE=function writeInt16BE(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,2,32767,-32768),this[s]=i>>>8,this[s+1]=255&i,s+2},Buffer.prototype.writeInt32LE=function writeInt32LE(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,4,2147483647,-2147483648),this[s]=255&i,this[s+1]=i>>>8,this[s+2]=i>>>16,this[s+3]=i>>>24,s+4},Buffer.prototype.writeInt32BE=function writeInt32BE(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,4,2147483647,-2147483648),i<0&&(i=4294967295+i+1),this[s]=i>>>24,this[s+1]=i>>>16,this[s+2]=i>>>8,this[s+3]=255&i,s+4},Buffer.prototype.writeBigInt64LE=defineBigIntMethod((function writeBigInt64LE(i,s=0){return wrtBigUInt64LE(this,i,s,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),Buffer.prototype.writeBigInt64BE=defineBigIntMethod((function writeBigInt64BE(i,s=0){return wrtBigUInt64BE(this,i,s,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),Buffer.prototype.writeFloatLE=function writeFloatLE(i,s,u){return writeFloat(this,i,s,!0,u)},Buffer.prototype.writeFloatBE=function writeFloatBE(i,s,u){return writeFloat(this,i,s,!1,u)},Buffer.prototype.writeDoubleLE=function writeDoubleLE(i,s,u){return writeDouble(this,i,s,!0,u)},Buffer.prototype.writeDoubleBE=function writeDoubleBE(i,s,u){return writeDouble(this,i,s,!1,u)},Buffer.prototype.copy=function copy(i,s,u,m){if(!Buffer.isBuffer(i))throw new TypeError("argument should be a Buffer");if(u||(u=0),m||0===m||(m=this.length),s>=i.length&&(s=i.length),s||(s=0),m>0&&m=this.length)throw new RangeError("Index out of range");if(m<0)throw new RangeError("sourceEnd out of bounds");m>this.length&&(m=this.length),i.length-s>>=0,u=void 0===u?this.length:u>>>0,i||(i=0),"number"==typeof i)for(v=s;v=m+4;u-=3)s=`_${i.slice(u-3,u)}${s}`;return`${i.slice(0,u)}${s}`}function checkIntBI(i,s,u,m,v,_){if(i>u||i3?0===s||s===BigInt(0)?`>= 0${m} and < 2${m} ** ${8*(_+1)}${m}`:`>= -(2${m} ** ${8*(_+1)-1}${m}) and < 2 ** ${8*(_+1)-1}${m}`:`>= ${s}${m} and <= ${u}${m}`,new $.ERR_OUT_OF_RANGE("value",v,i)}!function checkBounds(i,s,u){validateNumber(s,"offset"),void 0!==i[s]&&void 0!==i[s+u]||boundsError(s,i.length-(u+1))}(m,v,_)}function validateNumber(i,s){if("number"!=typeof i)throw new $.ERR_INVALID_ARG_TYPE(s,"number",i)}function boundsError(i,s,u){if(Math.floor(i)!==i)throw validateNumber(i,u),new $.ERR_OUT_OF_RANGE(u||"offset","an integer",i);if(s<0)throw new $.ERR_BUFFER_OUT_OF_BOUNDS;throw new $.ERR_OUT_OF_RANGE(u||"offset",`>= ${u?1:0} and <= ${s}`,i)}E("ERR_BUFFER_OUT_OF_BOUNDS",(function(i){return i?`${i} is outside of buffer bounds`:"Attempt to access memory outside buffer bounds"}),RangeError),E("ERR_INVALID_ARG_TYPE",(function(i,s){return`The "${i}" argument must be of type number. Received type ${typeof s}`}),TypeError),E("ERR_OUT_OF_RANGE",(function(i,s,u){let m=`The value of "${i}" is out of range.`,v=u;return Number.isInteger(u)&&Math.abs(u)>2**32?v=addNumericalSeparator(String(u)):"bigint"==typeof u&&(v=String(u),(u>BigInt(2)**BigInt(32)||u<-(BigInt(2)**BigInt(32)))&&(v=addNumericalSeparator(v)),v+="n"),m+=` It must be ${s}. Received ${v}`,m}),RangeError);const W=/[^+/0-9A-Za-z-_]/g;function utf8ToBytes(i,s){let u;s=s||1/0;const m=i.length;let v=null;const _=[];for(let j=0;j55295&&u<57344){if(!v){if(u>56319){(s-=3)>-1&&_.push(239,191,189);continue}if(j+1===m){(s-=3)>-1&&_.push(239,191,189);continue}v=u;continue}if(u<56320){(s-=3)>-1&&_.push(239,191,189),v=u;continue}u=65536+(v-55296<<10|u-56320)}else v&&(s-=3)>-1&&_.push(239,191,189);if(v=null,u<128){if((s-=1)<0)break;_.push(u)}else if(u<2048){if((s-=2)<0)break;_.push(u>>6|192,63&u|128)}else if(u<65536){if((s-=3)<0)break;_.push(u>>12|224,u>>6&63|128,63&u|128)}else{if(!(u<1114112))throw new Error("Invalid code point");if((s-=4)<0)break;_.push(u>>18|240,u>>12&63|128,u>>6&63|128,63&u|128)}}return _}function base64ToBytes(i){return m.toByteArray(function base64clean(i){if((i=(i=i.split("=")[0]).trim().replace(W,"")).length<2)return"";for(;i.length%4!=0;)i+="=";return i}(i))}function blitBuffer(i,s,u,m){let v;for(v=0;v=s.length||v>=i.length);++v)s[v+u]=i[v];return v}function isInstance(i,s){return i instanceof s||null!=i&&null!=i.constructor&&null!=i.constructor.name&&i.constructor.name===s.name}function numberIsNaN(i){return i!=i}const X=function(){const i="0123456789abcdef",s=new Array(256);for(let u=0;u<16;++u){const m=16*u;for(let v=0;v<16;++v)s[m+v]=i[u]+i[v]}return s}();function defineBigIntMethod(i){return"undefined"==typeof BigInt?BufferBigIntNotDefined:i}function BufferBigIntNotDefined(){throw new Error("BigInt not supported")}},21924:(i,s,u)=>{"use strict";var m=u(40210),v=u(55559),_=v(m("String.prototype.indexOf"));i.exports=function callBoundIntrinsic(i,s){var u=m(i,!!s);return"function"==typeof u&&_(i,".prototype.")>-1?v(u):u}},55559:(i,s,u)=>{"use strict";var m=u(58612),v=u(40210),_=v("%Function.prototype.apply%"),j=v("%Function.prototype.call%"),M=v("%Reflect.apply%",!0)||m.call(j,_),$=v("%Object.getOwnPropertyDescriptor%",!0),W=v("%Object.defineProperty%",!0),X=v("%Math.max%");if(W)try{W({},"a",{value:1})}catch(i){W=null}i.exports=function callBind(i){var s=M(m,j,arguments);$&&W&&($(s,"length").configurable&&W(s,"length",{value:1+X(0,i.length-(arguments.length-1))}));return s};var Y=function applyBind(){return M(m,_,arguments)};W?W(i.exports,"apply",{value:Y}):i.exports.apply=Y},94184:(i,s)=>{var u;!function(){"use strict";var m={}.hasOwnProperty;function classNames(){for(var i=[],s=0;s{"use strict";s.parse=function parse(i,s){if("string"!=typeof i)throw new TypeError("argument str must be a string");var u={},m=(s||{}).decode||decode,v=0;for(;v{"use strict";var m=u(11742),v={"text/plain":"Text","text/html":"Url",default:"Text"};i.exports=function copy(i,s){var u,_,j,M,$,W,X=!1;s||(s={}),u=s.debug||!1;try{if(j=m(),M=document.createRange(),$=document.getSelection(),(W=document.createElement("span")).textContent=i,W.ariaHidden="true",W.style.all="unset",W.style.position="fixed",W.style.top=0,W.style.clip="rect(0, 0, 0, 0)",W.style.whiteSpace="pre",W.style.webkitUserSelect="text",W.style.MozUserSelect="text",W.style.msUserSelect="text",W.style.userSelect="text",W.addEventListener("copy",(function(m){if(m.stopPropagation(),s.format)if(m.preventDefault(),void 0===m.clipboardData){u&&console.warn("unable to use e.clipboardData"),u&&console.warn("trying IE specific stuff"),window.clipboardData.clearData();var _=v[s.format]||v.default;window.clipboardData.setData(_,i)}else m.clipboardData.clearData(),m.clipboardData.setData(s.format,i);s.onCopy&&(m.preventDefault(),s.onCopy(m.clipboardData))})),document.body.appendChild(W),M.selectNodeContents(W),$.addRange(M),!document.execCommand("copy"))throw new Error("copy command was unsuccessful");X=!0}catch(m){u&&console.error("unable to copy using execCommand: ",m),u&&console.warn("trying IE specific stuff");try{window.clipboardData.setData(s.format||"text",i),s.onCopy&&s.onCopy(window.clipboardData),X=!0}catch(m){u&&console.error("unable to copy using clipboardData: ",m),u&&console.error("falling back to prompt"),_=function format(i){var s=(/mac os x/i.test(navigator.userAgent)?"⌘":"Ctrl")+"+C";return i.replace(/#{\s*key\s*}/g,s)}("message"in s?s.message:"Copy to clipboard: #{key}, Enter"),window.prompt(_,i)}}finally{$&&("function"==typeof $.removeRange?$.removeRange(M):$.removeAllRanges()),W&&document.body.removeChild(W),j()}return X}},44101:(i,s,u)=>{var m=u(18957);i.exports=m},90093:(i,s,u)=>{var m=u(28196);i.exports=m},65362:(i,s,u)=>{var m=u(63383);i.exports=m},50415:(i,s,u)=>{u(61181),u(47627),u(24415),u(66274),u(77971);var m=u(54058);i.exports=m.AggregateError},27700:(i,s,u)=>{u(73381);var m=u(35703);i.exports=m("Function").bind},16246:(i,s,u)=>{var m=u(7046),v=u(27700),_=Function.prototype;i.exports=function(i){var s=i.bind;return i===_||m(_,i)&&s===_.bind?v:s}},45999:(i,s,u)=>{u(49221);var m=u(54058);i.exports=m.Object.assign},16121:(i,s,u)=>{i.exports=u(38644)},14122:(i,s,u)=>{i.exports=u(89097)},60269:(i,s,u)=>{i.exports=u(76936)},38644:(i,s,u)=>{u(89731);var m=u(44101);i.exports=m},89097:(i,s,u)=>{var m=u(90093);i.exports=m},76936:(i,s,u)=>{var m=u(65362);i.exports=m},24883:(i,s,u)=>{var m=u(57475),v=u(69826),_=TypeError;i.exports=function(i){if(m(i))return i;throw _(v(i)+" is not a function")}},11851:(i,s,u)=>{var m=u(57475),v=String,_=TypeError;i.exports=function(i){if("object"==typeof i||m(i))return i;throw _("Can't set "+v(i)+" as a prototype")}},18479:i=>{i.exports=function(){}},96059:(i,s,u)=>{var m=u(10941),v=String,_=TypeError;i.exports=function(i){if(m(i))return i;throw _(v(i)+" is not an object")}},31692:(i,s,u)=>{var m=u(74529),v=u(59413),_=u(10623),createMethod=function(i){return function(s,u,j){var M,$=m(s),W=_($),X=v(j,W);if(i&&u!=u){for(;W>X;)if((M=$[X++])!=M)return!0}else for(;W>X;X++)if((i||X in $)&&$[X]===u)return i||X||0;return!i&&-1}};i.exports={includes:createMethod(!0),indexOf:createMethod(!1)}},93765:(i,s,u)=>{var m=u(95329);i.exports=m([].slice)},82532:(i,s,u)=>{var m=u(95329),v=m({}.toString),_=m("".slice);i.exports=function(i){return _(v(i),8,-1)}},9697:(i,s,u)=>{var m=u(22885),v=u(57475),_=u(82532),j=u(99813)("toStringTag"),M=Object,$="Arguments"==_(function(){return arguments}());i.exports=m?_:function(i){var s,u,m;return void 0===i?"Undefined":null===i?"Null":"string"==typeof(u=function(i,s){try{return i[s]}catch(i){}}(s=M(i),j))?u:$?_(s):"Object"==(m=_(s))&&v(s.callee)?"Arguments":m}},23489:(i,s,u)=>{var m=u(90953),v=u(31136),_=u(49677),j=u(65988);i.exports=function(i,s,u){for(var M=v(s),$=j.f,W=_.f,X=0;X{var m=u(95981);i.exports=!m((function(){function F(){}return F.prototype.constructor=null,Object.getPrototypeOf(new F)!==F.prototype}))},23538:i=>{i.exports=function(i,s){return{value:i,done:s}}},32029:(i,s,u)=>{var m=u(55746),v=u(65988),_=u(31887);i.exports=m?function(i,s,u){return v.f(i,s,_(1,u))}:function(i,s,u){return i[s]=u,i}},31887:i=>{i.exports=function(i,s){return{enumerable:!(1&i),configurable:!(2&i),writable:!(4&i),value:s}}},95929:(i,s,u)=>{var m=u(32029);i.exports=function(i,s,u,v){return v&&v.enumerable?i[s]=u:m(i,s,u),i}},75609:(i,s,u)=>{var m=u(21899),v=Object.defineProperty;i.exports=function(i,s){try{v(m,i,{value:s,configurable:!0,writable:!0})}catch(u){m[i]=s}return s}},55746:(i,s,u)=>{var m=u(95981);i.exports=!m((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},76616:i=>{var s="object"==typeof document&&document.all,u=void 0===s&&void 0!==s;i.exports={all:s,IS_HTMLDDA:u}},61333:(i,s,u)=>{var m=u(21899),v=u(10941),_=m.document,j=v(_)&&v(_.createElement);i.exports=function(i){return j?_.createElement(i):{}}},63281:i=>{i.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},2861:i=>{i.exports="undefined"!=typeof navigator&&String(navigator.userAgent)||""},53385:(i,s,u)=>{var m,v,_=u(21899),j=u(2861),M=_.process,$=_.Deno,W=M&&M.versions||$&&$.version,X=W&&W.v8;X&&(v=(m=X.split("."))[0]>0&&m[0]<4?1:+(m[0]+m[1])),!v&&j&&(!(m=j.match(/Edge\/(\d+)/))||m[1]>=74)&&(m=j.match(/Chrome\/(\d+)/))&&(v=+m[1]),i.exports=v},35703:(i,s,u)=>{var m=u(54058);i.exports=function(i){return m[i+"Prototype"]}},56759:i=>{i.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},53995:(i,s,u)=>{var m=u(95329),v=Error,_=m("".replace),j=String(v("zxcasd").stack),M=/\n\s*at [^:]*:[^\n]*/,$=M.test(j);i.exports=function(i,s){if($&&"string"==typeof i&&!v.prepareStackTrace)for(;s--;)i=_(i,M,"");return i}},79585:(i,s,u)=>{var m=u(32029),v=u(53995),_=u(18780),j=Error.captureStackTrace;i.exports=function(i,s,u,M){_&&(j?j(i,s):m(i,"stack",v(u,M)))}},18780:(i,s,u)=>{var m=u(95981),v=u(31887);i.exports=!m((function(){var i=Error("a");return!("stack"in i)||(Object.defineProperty(i,"stack",v(1,7)),7!==i.stack)}))},76887:(i,s,u)=>{"use strict";var m=u(21899),v=u(79730),_=u(97484),j=u(57475),M=u(49677).f,$=u(37252),W=u(54058),X=u(86843),Y=u(32029),Z=u(90953),wrapConstructor=function(i){var Wrapper=function(s,u,m){if(this instanceof Wrapper){switch(arguments.length){case 0:return new i;case 1:return new i(s);case 2:return new i(s,u)}return new i(s,u,m)}return v(i,this,arguments)};return Wrapper.prototype=i.prototype,Wrapper};i.exports=function(i,s){var u,v,ee,ae,ie,le,ce,pe,de,fe=i.target,ye=i.global,be=i.stat,_e=i.proto,we=ye?m:be?m[fe]:(m[fe]||{}).prototype,Se=ye?W:W[fe]||Y(W,fe,{})[fe],xe=Se.prototype;for(ae in s)v=!(u=$(ye?ae:fe+(be?".":"#")+ae,i.forced))&&we&&Z(we,ae),le=Se[ae],v&&(ce=i.dontCallGetSet?(de=M(we,ae))&&de.value:we[ae]),ie=v&&ce?ce:s[ae],v&&typeof le==typeof ie||(pe=i.bind&&v?X(ie,m):i.wrap&&v?wrapConstructor(ie):_e&&j(ie)?_(ie):ie,(i.sham||ie&&ie.sham||le&&le.sham)&&Y(pe,"sham",!0),Y(Se,ae,pe),_e&&(Z(W,ee=fe+"Prototype")||Y(W,ee,{}),Y(W[ee],ae,ie),i.real&&xe&&(u||!xe[ae])&&Y(xe,ae,ie)))}},95981:i=>{i.exports=function(i){try{return!!i()}catch(i){return!0}}},79730:(i,s,u)=>{var m=u(18285),v=Function.prototype,_=v.apply,j=v.call;i.exports="object"==typeof Reflect&&Reflect.apply||(m?j.bind(_):function(){return j.apply(_,arguments)})},86843:(i,s,u)=>{var m=u(97484),v=u(24883),_=u(18285),j=m(m.bind);i.exports=function(i,s){return v(i),void 0===s?i:_?j(i,s):function(){return i.apply(s,arguments)}}},18285:(i,s,u)=>{var m=u(95981);i.exports=!m((function(){var i=function(){}.bind();return"function"!=typeof i||i.hasOwnProperty("prototype")}))},98308:(i,s,u)=>{"use strict";var m=u(95329),v=u(24883),_=u(10941),j=u(90953),M=u(93765),$=u(18285),W=Function,X=m([].concat),Y=m([].join),Z={};i.exports=$?W.bind:function bind(i){var s=v(this),u=s.prototype,m=M(arguments,1),$=function bound(){var u=X(m,M(arguments));return this instanceof $?function(i,s,u){if(!j(Z,s)){for(var m=[],v=0;v{var m=u(18285),v=Function.prototype.call;i.exports=m?v.bind(v):function(){return v.apply(v,arguments)}},79417:(i,s,u)=>{var m=u(55746),v=u(90953),_=Function.prototype,j=m&&Object.getOwnPropertyDescriptor,M=v(_,"name"),$=M&&"something"===function something(){}.name,W=M&&(!m||m&&j(_,"name").configurable);i.exports={EXISTS:M,PROPER:$,CONFIGURABLE:W}},45526:(i,s,u)=>{var m=u(95329),v=u(24883);i.exports=function(i,s,u){try{return m(v(Object.getOwnPropertyDescriptor(i,s)[u]))}catch(i){}}},97484:(i,s,u)=>{var m=u(82532),v=u(95329);i.exports=function(i){if("Function"===m(i))return v(i)}},95329:(i,s,u)=>{var m=u(18285),v=Function.prototype,_=v.call,j=m&&v.bind.bind(_,_);i.exports=m?j:function(i){return function(){return _.apply(i,arguments)}}},626:(i,s,u)=>{var m=u(54058),v=u(21899),_=u(57475),aFunction=function(i){return _(i)?i:void 0};i.exports=function(i,s){return arguments.length<2?aFunction(m[i])||aFunction(v[i]):m[i]&&m[i][s]||v[i]&&v[i][s]}},22902:(i,s,u)=>{var m=u(9697),v=u(14229),_=u(82119),j=u(12077),M=u(99813)("iterator");i.exports=function(i){if(!_(i))return v(i,M)||v(i,"@@iterator")||j[m(i)]}},53476:(i,s,u)=>{var m=u(78834),v=u(24883),_=u(96059),j=u(69826),M=u(22902),$=TypeError;i.exports=function(i,s){var u=arguments.length<2?M(i):s;if(v(u))return _(m(u,i));throw $(j(i)+" is not iterable")}},14229:(i,s,u)=>{var m=u(24883),v=u(82119);i.exports=function(i,s){var u=i[s];return v(u)?void 0:m(u)}},21899:function(i,s,u){var check=function(i){return i&&i.Math==Math&&i};i.exports=check("object"==typeof globalThis&&globalThis)||check("object"==typeof window&&window)||check("object"==typeof self&&self)||check("object"==typeof u.g&&u.g)||function(){return this}()||this||Function("return this")()},90953:(i,s,u)=>{var m=u(95329),v=u(89678),_=m({}.hasOwnProperty);i.exports=Object.hasOwn||function hasOwn(i,s){return _(v(i),s)}},27748:i=>{i.exports={}},15463:(i,s,u)=>{var m=u(626);i.exports=m("document","documentElement")},2840:(i,s,u)=>{var m=u(55746),v=u(95981),_=u(61333);i.exports=!m&&!v((function(){return 7!=Object.defineProperty(_("div"),"a",{get:function(){return 7}}).a}))},37026:(i,s,u)=>{var m=u(95329),v=u(95981),_=u(82532),j=Object,M=m("".split);i.exports=v((function(){return!j("z").propertyIsEnumerable(0)}))?function(i){return"String"==_(i)?M(i,""):j(i)}:j},70926:(i,s,u)=>{var m=u(57475),v=u(10941),_=u(88929);i.exports=function(i,s,u){var j,M;return _&&m(j=s.constructor)&&j!==u&&v(M=j.prototype)&&M!==u.prototype&&_(i,M),i}},53794:(i,s,u)=>{var m=u(10941),v=u(32029);i.exports=function(i,s){m(s)&&"cause"in s&&v(i,"cause",s.cause)}},45402:(i,s,u)=>{var m,v,_,j=u(47093),M=u(21899),$=u(10941),W=u(32029),X=u(90953),Y=u(63030),Z=u(44262),ee=u(27748),ae="Object already initialized",ie=M.TypeError,le=M.WeakMap;if(j||Y.state){var ce=Y.state||(Y.state=new le);ce.get=ce.get,ce.has=ce.has,ce.set=ce.set,m=function(i,s){if(ce.has(i))throw ie(ae);return s.facade=i,ce.set(i,s),s},v=function(i){return ce.get(i)||{}},_=function(i){return ce.has(i)}}else{var pe=Z("state");ee[pe]=!0,m=function(i,s){if(X(i,pe))throw ie(ae);return s.facade=i,W(i,pe,s),s},v=function(i){return X(i,pe)?i[pe]:{}},_=function(i){return X(i,pe)}}i.exports={set:m,get:v,has:_,enforce:function(i){return _(i)?v(i):m(i,{})},getterFor:function(i){return function(s){var u;if(!$(s)||(u=v(s)).type!==i)throw ie("Incompatible receiver, "+i+" required");return u}}}},6782:(i,s,u)=>{var m=u(99813),v=u(12077),_=m("iterator"),j=Array.prototype;i.exports=function(i){return void 0!==i&&(v.Array===i||j[_]===i)}},57475:(i,s,u)=>{var m=u(76616),v=m.all;i.exports=m.IS_HTMLDDA?function(i){return"function"==typeof i||i===v}:function(i){return"function"==typeof i}},37252:(i,s,u)=>{var m=u(95981),v=u(57475),_=/#|\.prototype\./,isForced=function(i,s){var u=M[j(i)];return u==W||u!=$&&(v(s)?m(s):!!s)},j=isForced.normalize=function(i){return String(i).replace(_,".").toLowerCase()},M=isForced.data={},$=isForced.NATIVE="N",W=isForced.POLYFILL="P";i.exports=isForced},82119:i=>{i.exports=function(i){return null==i}},10941:(i,s,u)=>{var m=u(57475),v=u(76616),_=v.all;i.exports=v.IS_HTMLDDA?function(i){return"object"==typeof i?null!==i:m(i)||i===_}:function(i){return"object"==typeof i?null!==i:m(i)}},82529:i=>{i.exports=!0},56664:(i,s,u)=>{var m=u(626),v=u(57475),_=u(7046),j=u(32302),M=Object;i.exports=j?function(i){return"symbol"==typeof i}:function(i){var s=m("Symbol");return v(s)&&_(s.prototype,M(i))}},93091:(i,s,u)=>{var m=u(86843),v=u(78834),_=u(96059),j=u(69826),M=u(6782),$=u(10623),W=u(7046),X=u(53476),Y=u(22902),Z=u(7609),ee=TypeError,Result=function(i,s){this.stopped=i,this.result=s},ae=Result.prototype;i.exports=function(i,s,u){var ie,le,ce,pe,de,fe,ye,be=u&&u.that,_e=!(!u||!u.AS_ENTRIES),we=!(!u||!u.IS_RECORD),Se=!(!u||!u.IS_ITERATOR),xe=!(!u||!u.INTERRUPTED),Pe=m(s,be),stop=function(i){return ie&&Z(ie,"normal",i),new Result(!0,i)},callFn=function(i){return _e?(_(i),xe?Pe(i[0],i[1],stop):Pe(i[0],i[1])):xe?Pe(i,stop):Pe(i)};if(we)ie=i.iterator;else if(Se)ie=i;else{if(!(le=Y(i)))throw ee(j(i)+" is not iterable");if(M(le)){for(ce=0,pe=$(i);pe>ce;ce++)if((de=callFn(i[ce]))&&W(ae,de))return de;return new Result(!1)}ie=X(i,le)}for(fe=we?i.next:ie.next;!(ye=v(fe,ie)).done;){try{de=callFn(ye.value)}catch(i){Z(ie,"throw",i)}if("object"==typeof de&&de&&W(ae,de))return de}return new Result(!1)}},7609:(i,s,u)=>{var m=u(78834),v=u(96059),_=u(14229);i.exports=function(i,s,u){var j,M;v(i);try{if(!(j=_(i,"return"))){if("throw"===s)throw u;return u}j=m(j,i)}catch(i){M=!0,j=i}if("throw"===s)throw u;if(M)throw j;return v(j),u}},53847:(i,s,u)=>{"use strict";var m=u(35143).IteratorPrototype,v=u(29290),_=u(31887),j=u(90904),M=u(12077),returnThis=function(){return this};i.exports=function(i,s,u,$){var W=s+" Iterator";return i.prototype=v(m,{next:_(+!$,u)}),j(i,W,!1,!0),M[W]=returnThis,i}},75105:(i,s,u)=>{"use strict";var m=u(76887),v=u(78834),_=u(82529),j=u(79417),M=u(57475),$=u(53847),W=u(249),X=u(88929),Y=u(90904),Z=u(32029),ee=u(95929),ae=u(99813),ie=u(12077),le=u(35143),ce=j.PROPER,pe=j.CONFIGURABLE,de=le.IteratorPrototype,fe=le.BUGGY_SAFARI_ITERATORS,ye=ae("iterator"),be="keys",_e="values",we="entries",returnThis=function(){return this};i.exports=function(i,s,u,j,ae,le,Se){$(u,s,j);var xe,Pe,Ie,getIterationMethod=function(i){if(i===ae&&Ve)return Ve;if(!fe&&i in qe)return qe[i];switch(i){case be:return function keys(){return new u(this,i)};case _e:return function values(){return new u(this,i)};case we:return function entries(){return new u(this,i)}}return function(){return new u(this)}},Te=s+" Iterator",Re=!1,qe=i.prototype,ze=qe[ye]||qe["@@iterator"]||ae&&qe[ae],Ve=!fe&&ze||getIterationMethod(ae),We="Array"==s&&qe.entries||ze;if(We&&(xe=W(We.call(new i)))!==Object.prototype&&xe.next&&(_||W(xe)===de||(X?X(xe,de):M(xe[ye])||ee(xe,ye,returnThis)),Y(xe,Te,!0,!0),_&&(ie[Te]=returnThis)),ce&&ae==_e&&ze&&ze.name!==_e&&(!_&&pe?Z(qe,"name",_e):(Re=!0,Ve=function values(){return v(ze,this)})),ae)if(Pe={values:getIterationMethod(_e),keys:le?Ve:getIterationMethod(be),entries:getIterationMethod(we)},Se)for(Ie in Pe)(fe||Re||!(Ie in qe))&&ee(qe,Ie,Pe[Ie]);else m({target:s,proto:!0,forced:fe||Re},Pe);return _&&!Se||qe[ye]===Ve||ee(qe,ye,Ve,{name:ae}),ie[s]=Ve,Pe}},35143:(i,s,u)=>{"use strict";var m,v,_,j=u(95981),M=u(57475),$=u(10941),W=u(29290),X=u(249),Y=u(95929),Z=u(99813),ee=u(82529),ae=Z("iterator"),ie=!1;[].keys&&("next"in(_=[].keys())?(v=X(X(_)))!==Object.prototype&&(m=v):ie=!0),!$(m)||j((function(){var i={};return m[ae].call(i)!==i}))?m={}:ee&&(m=W(m)),M(m[ae])||Y(m,ae,(function(){return this})),i.exports={IteratorPrototype:m,BUGGY_SAFARI_ITERATORS:ie}},12077:i=>{i.exports={}},10623:(i,s,u)=>{var m=u(43057);i.exports=function(i){return m(i.length)}},35331:i=>{var s=Math.ceil,u=Math.floor;i.exports=Math.trunc||function trunc(i){var m=+i;return(m>0?u:s)(m)}},14649:(i,s,u)=>{var m=u(85803);i.exports=function(i,s){return void 0===i?arguments.length<2?"":s:m(i)}},24420:(i,s,u)=>{"use strict";var m=u(55746),v=u(95329),_=u(78834),j=u(95981),M=u(14771),$=u(87857),W=u(36760),X=u(89678),Y=u(37026),Z=Object.assign,ee=Object.defineProperty,ae=v([].concat);i.exports=!Z||j((function(){if(m&&1!==Z({b:1},Z(ee({},"a",{enumerable:!0,get:function(){ee(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var i={},s={},u=Symbol(),v="abcdefghijklmnopqrst";return i[u]=7,v.split("").forEach((function(i){s[i]=i})),7!=Z({},i)[u]||M(Z({},s)).join("")!=v}))?function assign(i,s){for(var u=X(i),v=arguments.length,j=1,Z=$.f,ee=W.f;v>j;)for(var ie,le=Y(arguments[j++]),ce=Z?ae(M(le),Z(le)):M(le),pe=ce.length,de=0;pe>de;)ie=ce[de++],m&&!_(ee,le,ie)||(u[ie]=le[ie]);return u}:Z},29290:(i,s,u)=>{var m,v=u(96059),_=u(59938),j=u(56759),M=u(27748),$=u(15463),W=u(61333),X=u(44262),Y="prototype",Z="script",ee=X("IE_PROTO"),EmptyConstructor=function(){},scriptTag=function(i){return"<"+Z+">"+i+""},NullProtoObjectViaActiveX=function(i){i.write(scriptTag("")),i.close();var s=i.parentWindow.Object;return i=null,s},NullProtoObject=function(){try{m=new ActiveXObject("htmlfile")}catch(i){}var i,s,u;NullProtoObject="undefined"!=typeof document?document.domain&&m?NullProtoObjectViaActiveX(m):(s=W("iframe"),u="java"+Z+":",s.style.display="none",$.appendChild(s),s.src=String(u),(i=s.contentWindow.document).open(),i.write(scriptTag("document.F=Object")),i.close(),i.F):NullProtoObjectViaActiveX(m);for(var v=j.length;v--;)delete NullProtoObject[Y][j[v]];return NullProtoObject()};M[ee]=!0,i.exports=Object.create||function create(i,s){var u;return null!==i?(EmptyConstructor[Y]=v(i),u=new EmptyConstructor,EmptyConstructor[Y]=null,u[ee]=i):u=NullProtoObject(),void 0===s?u:_.f(u,s)}},59938:(i,s,u)=>{var m=u(55746),v=u(83937),_=u(65988),j=u(96059),M=u(74529),$=u(14771);s.f=m&&!v?Object.defineProperties:function defineProperties(i,s){j(i);for(var u,m=M(s),v=$(s),W=v.length,X=0;W>X;)_.f(i,u=v[X++],m[u]);return i}},65988:(i,s,u)=>{var m=u(55746),v=u(2840),_=u(83937),j=u(96059),M=u(83894),$=TypeError,W=Object.defineProperty,X=Object.getOwnPropertyDescriptor,Y="enumerable",Z="configurable",ee="writable";s.f=m?_?function defineProperty(i,s,u){if(j(i),s=M(s),j(u),"function"==typeof i&&"prototype"===s&&"value"in u&&ee in u&&!u[ee]){var m=X(i,s);m&&m[ee]&&(i[s]=u.value,u={configurable:Z in u?u[Z]:m[Z],enumerable:Y in u?u[Y]:m[Y],writable:!1})}return W(i,s,u)}:W:function defineProperty(i,s,u){if(j(i),s=M(s),j(u),v)try{return W(i,s,u)}catch(i){}if("get"in u||"set"in u)throw $("Accessors not supported");return"value"in u&&(i[s]=u.value),i}},49677:(i,s,u)=>{var m=u(55746),v=u(78834),_=u(36760),j=u(31887),M=u(74529),$=u(83894),W=u(90953),X=u(2840),Y=Object.getOwnPropertyDescriptor;s.f=m?Y:function getOwnPropertyDescriptor(i,s){if(i=M(i),s=$(s),X)try{return Y(i,s)}catch(i){}if(W(i,s))return j(!v(_.f,i,s),i[s])}},10946:(i,s,u)=>{var m=u(55629),v=u(56759).concat("length","prototype");s.f=Object.getOwnPropertyNames||function getOwnPropertyNames(i){return m(i,v)}},87857:(i,s)=>{s.f=Object.getOwnPropertySymbols},249:(i,s,u)=>{var m=u(90953),v=u(57475),_=u(89678),j=u(44262),M=u(91310),$=j("IE_PROTO"),W=Object,X=W.prototype;i.exports=M?W.getPrototypeOf:function(i){var s=_(i);if(m(s,$))return s[$];var u=s.constructor;return v(u)&&s instanceof u?u.prototype:s instanceof W?X:null}},7046:(i,s,u)=>{var m=u(95329);i.exports=m({}.isPrototypeOf)},55629:(i,s,u)=>{var m=u(95329),v=u(90953),_=u(74529),j=u(31692).indexOf,M=u(27748),$=m([].push);i.exports=function(i,s){var u,m=_(i),W=0,X=[];for(u in m)!v(M,u)&&v(m,u)&&$(X,u);for(;s.length>W;)v(m,u=s[W++])&&(~j(X,u)||$(X,u));return X}},14771:(i,s,u)=>{var m=u(55629),v=u(56759);i.exports=Object.keys||function keys(i){return m(i,v)}},36760:(i,s)=>{"use strict";var u={}.propertyIsEnumerable,m=Object.getOwnPropertyDescriptor,v=m&&!u.call({1:2},1);s.f=v?function propertyIsEnumerable(i){var s=m(this,i);return!!s&&s.enumerable}:u},88929:(i,s,u)=>{var m=u(45526),v=u(96059),_=u(11851);i.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var i,s=!1,u={};try{(i=m(Object.prototype,"__proto__","set"))(u,[]),s=u instanceof Array}catch(i){}return function setPrototypeOf(u,m){return v(u),_(m),s?i(u,m):u.__proto__=m,u}}():void 0)},95623:(i,s,u)=>{"use strict";var m=u(22885),v=u(9697);i.exports=m?{}.toString:function toString(){return"[object "+v(this)+"]"}},39811:(i,s,u)=>{var m=u(78834),v=u(57475),_=u(10941),j=TypeError;i.exports=function(i,s){var u,M;if("string"===s&&v(u=i.toString)&&!_(M=m(u,i)))return M;if(v(u=i.valueOf)&&!_(M=m(u,i)))return M;if("string"!==s&&v(u=i.toString)&&!_(M=m(u,i)))return M;throw j("Can't convert object to primitive value")}},31136:(i,s,u)=>{var m=u(626),v=u(95329),_=u(10946),j=u(87857),M=u(96059),$=v([].concat);i.exports=m("Reflect","ownKeys")||function ownKeys(i){var s=_.f(M(i)),u=j.f;return u?$(s,u(i)):s}},54058:i=>{i.exports={}},9056:(i,s,u)=>{var m=u(65988).f;i.exports=function(i,s,u){u in i||m(i,u,{configurable:!0,get:function(){return s[u]},set:function(i){s[u]=i}})}},48219:(i,s,u)=>{var m=u(82119),v=TypeError;i.exports=function(i){if(m(i))throw v("Can't call method on "+i);return i}},90904:(i,s,u)=>{var m=u(22885),v=u(65988).f,_=u(32029),j=u(90953),M=u(95623),$=u(99813)("toStringTag");i.exports=function(i,s,u,W){if(i){var X=u?i:i.prototype;j(X,$)||v(X,$,{configurable:!0,value:s}),W&&!m&&_(X,"toString",M)}}},44262:(i,s,u)=>{var m=u(68726),v=u(99418),_=m("keys");i.exports=function(i){return _[i]||(_[i]=v(i))}},63030:(i,s,u)=>{var m=u(21899),v=u(75609),_="__core-js_shared__",j=m[_]||v(_,{});i.exports=j},68726:(i,s,u)=>{var m=u(82529),v=u(63030);(i.exports=function(i,s){return v[i]||(v[i]=void 0!==s?s:{})})("versions",[]).push({version:"3.31.1",mode:m?"pure":"global",copyright:"© 2014-2023 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.31.1/LICENSE",source:"https://github.com/zloirock/core-js"})},64620:(i,s,u)=>{var m=u(95329),v=u(62435),_=u(85803),j=u(48219),M=m("".charAt),$=m("".charCodeAt),W=m("".slice),createMethod=function(i){return function(s,u){var m,X,Y=_(j(s)),Z=v(u),ee=Y.length;return Z<0||Z>=ee?i?"":void 0:(m=$(Y,Z))<55296||m>56319||Z+1===ee||(X=$(Y,Z+1))<56320||X>57343?i?M(Y,Z):m:i?W(Y,Z,Z+2):X-56320+(m-55296<<10)+65536}};i.exports={codeAt:createMethod(!1),charAt:createMethod(!0)}},63405:(i,s,u)=>{var m=u(53385),v=u(95981),_=u(21899).String;i.exports=!!Object.getOwnPropertySymbols&&!v((function(){var i=Symbol();return!_(i)||!(Object(i)instanceof Symbol)||!Symbol.sham&&m&&m<41}))},59413:(i,s,u)=>{var m=u(62435),v=Math.max,_=Math.min;i.exports=function(i,s){var u=m(i);return u<0?v(u+s,0):_(u,s)}},74529:(i,s,u)=>{var m=u(37026),v=u(48219);i.exports=function(i){return m(v(i))}},62435:(i,s,u)=>{var m=u(35331);i.exports=function(i){var s=+i;return s!=s||0===s?0:m(s)}},43057:(i,s,u)=>{var m=u(62435),v=Math.min;i.exports=function(i){return i>0?v(m(i),9007199254740991):0}},89678:(i,s,u)=>{var m=u(48219),v=Object;i.exports=function(i){return v(m(i))}},46935:(i,s,u)=>{var m=u(78834),v=u(10941),_=u(56664),j=u(14229),M=u(39811),$=u(99813),W=TypeError,X=$("toPrimitive");i.exports=function(i,s){if(!v(i)||_(i))return i;var u,$=j(i,X);if($){if(void 0===s&&(s="default"),u=m($,i,s),!v(u)||_(u))return u;throw W("Can't convert object to primitive value")}return void 0===s&&(s="number"),M(i,s)}},83894:(i,s,u)=>{var m=u(46935),v=u(56664);i.exports=function(i){var s=m(i,"string");return v(s)?s:s+""}},22885:(i,s,u)=>{var m={};m[u(99813)("toStringTag")]="z",i.exports="[object z]"===String(m)},85803:(i,s,u)=>{var m=u(9697),v=String;i.exports=function(i){if("Symbol"===m(i))throw TypeError("Cannot convert a Symbol value to a string");return v(i)}},69826:i=>{var s=String;i.exports=function(i){try{return s(i)}catch(i){return"Object"}}},99418:(i,s,u)=>{var m=u(95329),v=0,_=Math.random(),j=m(1..toString);i.exports=function(i){return"Symbol("+(void 0===i?"":i)+")_"+j(++v+_,36)}},32302:(i,s,u)=>{var m=u(63405);i.exports=m&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},83937:(i,s,u)=>{var m=u(55746),v=u(95981);i.exports=m&&v((function(){return 42!=Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype}))},47093:(i,s,u)=>{var m=u(21899),v=u(57475),_=m.WeakMap;i.exports=v(_)&&/native code/.test(String(_))},99813:(i,s,u)=>{var m=u(21899),v=u(68726),_=u(90953),j=u(99418),M=u(63405),$=u(32302),W=m.Symbol,X=v("wks"),Y=$?W.for||W:W&&W.withoutSetter||j;i.exports=function(i){return _(X,i)||(X[i]=M&&_(W,i)?W[i]:Y("Symbol."+i)),X[i]}},62864:(i,s,u)=>{"use strict";var m=u(626),v=u(90953),_=u(32029),j=u(7046),M=u(88929),$=u(23489),W=u(9056),X=u(70926),Y=u(14649),Z=u(53794),ee=u(79585),ae=u(55746),ie=u(82529);i.exports=function(i,s,u,le){var ce="stackTraceLimit",pe=le?2:1,de=i.split("."),fe=de[de.length-1],ye=m.apply(null,de);if(ye){var be=ye.prototype;if(!ie&&v(be,"cause")&&delete be.cause,!u)return ye;var _e=m("Error"),we=s((function(i,s){var u=Y(le?s:i,void 0),m=le?new ye(i):new ye;return void 0!==u&&_(m,"message",u),ee(m,we,m.stack,2),this&&j(be,this)&&X(m,this,we),arguments.length>pe&&Z(m,arguments[pe]),m}));if(we.prototype=be,"Error"!==fe?M?M(we,_e):$(we,_e,{name:!0}):ae&&ce in ye&&(W(we,ye,ce),W(we,ye,"prepareStackTrace")),$(we,ye),!ie)try{be.name!==fe&&_(be,"name",fe),be.constructor=we}catch(i){}return we}}},24415:(i,s,u)=>{var m=u(76887),v=u(626),_=u(79730),j=u(95981),M=u(62864),$="AggregateError",W=v($),X=!j((function(){return 1!==W([1]).errors[0]}))&&j((function(){return 7!==W([1],$,{cause:7}).cause}));m({global:!0,constructor:!0,arity:2,forced:X},{AggregateError:M($,(function(i){return function AggregateError(s,u){return _(i,this,arguments)}}),X,!0)})},49812:(i,s,u)=>{"use strict";var m=u(76887),v=u(7046),_=u(249),j=u(88929),M=u(23489),$=u(29290),W=u(32029),X=u(31887),Y=u(53794),Z=u(79585),ee=u(93091),ae=u(14649),ie=u(99813)("toStringTag"),le=Error,ce=[].push,pe=function AggregateError(i,s){var u,m=v(de,this);j?u=j(le(),m?_(this):de):(u=m?this:$(de),W(u,ie,"Error")),void 0!==s&&W(u,"message",ae(s)),Z(u,pe,u.stack,1),arguments.length>2&&Y(u,arguments[2]);var M=[];return ee(i,ce,{that:M}),W(u,"errors",M),u};j?j(pe,le):M(pe,le,{name:!0});var de=pe.prototype=$(le.prototype,{constructor:X(1,pe),message:X(1,""),name:X(1,"AggregateError")});m({global:!0,constructor:!0,arity:2},{AggregateError:pe})},47627:(i,s,u)=>{u(49812)},66274:(i,s,u)=>{"use strict";var m=u(74529),v=u(18479),_=u(12077),j=u(45402),M=u(65988).f,$=u(75105),W=u(23538),X=u(82529),Y=u(55746),Z="Array Iterator",ee=j.set,ae=j.getterFor(Z);i.exports=$(Array,"Array",(function(i,s){ee(this,{type:Z,target:m(i),index:0,kind:s})}),(function(){var i=ae(this),s=i.target,u=i.kind,m=i.index++;return!s||m>=s.length?(i.target=void 0,W(void 0,!0)):W("keys"==u?m:"values"==u?s[m]:[m,s[m]],!1)}),"values");var ie=_.Arguments=_.Array;if(v("keys"),v("values"),v("entries"),!X&&Y&&"values"!==ie.name)try{M(ie,"name",{value:"values"})}catch(i){}},61181:(i,s,u)=>{var m=u(76887),v=u(21899),_=u(79730),j=u(62864),M="WebAssembly",$=v[M],W=7!==Error("e",{cause:7}).cause,exportGlobalErrorCauseWrapper=function(i,s){var u={};u[i]=j(i,s,W),m({global:!0,constructor:!0,arity:1,forced:W},u)},exportWebAssemblyErrorCauseWrapper=function(i,s){if($&&$[i]){var u={};u[i]=j(M+"."+i,s,W),m({target:M,stat:!0,constructor:!0,arity:1,forced:W},u)}};exportGlobalErrorCauseWrapper("Error",(function(i){return function Error(s){return _(i,this,arguments)}})),exportGlobalErrorCauseWrapper("EvalError",(function(i){return function EvalError(s){return _(i,this,arguments)}})),exportGlobalErrorCauseWrapper("RangeError",(function(i){return function RangeError(s){return _(i,this,arguments)}})),exportGlobalErrorCauseWrapper("ReferenceError",(function(i){return function ReferenceError(s){return _(i,this,arguments)}})),exportGlobalErrorCauseWrapper("SyntaxError",(function(i){return function SyntaxError(s){return _(i,this,arguments)}})),exportGlobalErrorCauseWrapper("TypeError",(function(i){return function TypeError(s){return _(i,this,arguments)}})),exportGlobalErrorCauseWrapper("URIError",(function(i){return function URIError(s){return _(i,this,arguments)}})),exportWebAssemblyErrorCauseWrapper("CompileError",(function(i){return function CompileError(s){return _(i,this,arguments)}})),exportWebAssemblyErrorCauseWrapper("LinkError",(function(i){return function LinkError(s){return _(i,this,arguments)}})),exportWebAssemblyErrorCauseWrapper("RuntimeError",(function(i){return function RuntimeError(s){return _(i,this,arguments)}}))},73381:(i,s,u)=>{var m=u(76887),v=u(98308);m({target:"Function",proto:!0,forced:Function.bind!==v},{bind:v})},49221:(i,s,u)=>{var m=u(76887),v=u(24420);m({target:"Object",stat:!0,arity:2,forced:Object.assign!==v},{assign:v})},77971:(i,s,u)=>{"use strict";var m=u(64620).charAt,v=u(85803),_=u(45402),j=u(75105),M=u(23538),$="String Iterator",W=_.set,X=_.getterFor($);j(String,"String",(function(i){W(this,{type:$,string:v(i),index:0})}),(function next(){var i,s=X(this),u=s.string,v=s.index;return v>=u.length?M(void 0,!0):(i=m(u,v),s.index+=i.length,M(i,!1))}))},89731:(i,s,u)=>{u(47627)},7634:(i,s,u)=>{u(66274);var m=u(63281),v=u(21899),_=u(9697),j=u(32029),M=u(12077),$=u(99813)("toStringTag");for(var W in m){var X=v[W],Y=X&&X.prototype;Y&&_(Y)!==$&&j(Y,$,W),M[W]=M.Array}},18957:(i,s,u)=>{u(89731);var m=u(50415);u(7634),i.exports=m},28196:(i,s,u)=>{var m=u(16246);i.exports=m},63383:(i,s,u)=>{var m=u(45999);i.exports=m},8269:function(i,s,u){var m;m=void 0!==u.g?u.g:this,i.exports=function(i){if(i.CSS&&i.CSS.escape)return i.CSS.escape;var cssEscape=function(i){if(0==arguments.length)throw new TypeError("`CSS.escape` requires an argument.");for(var s,u=String(i),m=u.length,v=-1,_="",j=u.charCodeAt(0);++v=1&&s<=31||127==s||0==v&&s>=48&&s<=57||1==v&&s>=48&&s<=57&&45==j?"\\"+s.toString(16)+" ":0==v&&1==m&&45==s||!(s>=128||45==s||95==s||s>=48&&s<=57||s>=65&&s<=90||s>=97&&s<=122)?"\\"+u.charAt(v):u.charAt(v):_+="�";return _};return i.CSS||(i.CSS={}),i.CSS.escape=cssEscape,cssEscape}(m)},27698:(i,s,u)=>{"use strict";var m=u(48764).Buffer;function isSpecificValue(i){return i instanceof m||i instanceof Date||i instanceof RegExp}function cloneSpecificValue(i){if(i instanceof m){var s=m.alloc?m.alloc(i.length):new m(i.length);return i.copy(s),s}if(i instanceof Date)return new Date(i.getTime());if(i instanceof RegExp)return new RegExp(i);throw new Error("Unexpected situation")}function deepCloneArray(i){var s=[];return i.forEach((function(i,u){"object"==typeof i&&null!==i?Array.isArray(i)?s[u]=deepCloneArray(i):isSpecificValue(i)?s[u]=cloneSpecificValue(i):s[u]=v({},i):s[u]=i})),s}function safeGetProperty(i,s){return"__proto__"===s?void 0:i[s]}var v=i.exports=function(){if(arguments.length<1||"object"!=typeof arguments[0])return!1;if(arguments.length<2)return arguments[0];var i,s,u=arguments[0];return Array.prototype.slice.call(arguments,1).forEach((function(m){"object"!=typeof m||null===m||Array.isArray(m)||Object.keys(m).forEach((function(_){return s=safeGetProperty(u,_),(i=safeGetProperty(m,_))===u?void 0:"object"!=typeof i||null===i?void(u[_]=i):Array.isArray(i)?void(u[_]=deepCloneArray(i)):isSpecificValue(i)?void(u[_]=cloneSpecificValue(i)):"object"!=typeof s||null===s||Array.isArray(s)?void(u[_]=v({},i)):void(u[_]=v(s,i))}))})),u}},9996:i=>{"use strict";var s=function isMergeableObject(i){return function isNonNullObject(i){return!!i&&"object"==typeof i}(i)&&!function isSpecial(i){var s=Object.prototype.toString.call(i);return"[object RegExp]"===s||"[object Date]"===s||function isReactElement(i){return i.$$typeof===u}(i)}(i)};var u="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function cloneUnlessOtherwiseSpecified(i,s){return!1!==s.clone&&s.isMergeableObject(i)?deepmerge(function emptyTarget(i){return Array.isArray(i)?[]:{}}(i),i,s):i}function defaultArrayMerge(i,s,u){return i.concat(s).map((function(i){return cloneUnlessOtherwiseSpecified(i,u)}))}function getKeys(i){return Object.keys(i).concat(function getEnumerableOwnPropertySymbols(i){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(i).filter((function(s){return Object.propertyIsEnumerable.call(i,s)})):[]}(i))}function propertyIsOnObject(i,s){try{return s in i}catch(i){return!1}}function mergeObject(i,s,u){var m={};return u.isMergeableObject(i)&&getKeys(i).forEach((function(s){m[s]=cloneUnlessOtherwiseSpecified(i[s],u)})),getKeys(s).forEach((function(v){(function propertyIsUnsafe(i,s){return propertyIsOnObject(i,s)&&!(Object.hasOwnProperty.call(i,s)&&Object.propertyIsEnumerable.call(i,s))})(i,v)||(propertyIsOnObject(i,v)&&u.isMergeableObject(s[v])?m[v]=function getMergeFunction(i,s){if(!s.customMerge)return deepmerge;var u=s.customMerge(i);return"function"==typeof u?u:deepmerge}(v,u)(i[v],s[v],u):m[v]=cloneUnlessOtherwiseSpecified(s[v],u))})),m}function deepmerge(i,u,m){(m=m||{}).arrayMerge=m.arrayMerge||defaultArrayMerge,m.isMergeableObject=m.isMergeableObject||s,m.cloneUnlessOtherwiseSpecified=cloneUnlessOtherwiseSpecified;var v=Array.isArray(u);return v===Array.isArray(i)?v?m.arrayMerge(i,u,m):mergeObject(i,u,m):cloneUnlessOtherwiseSpecified(u,m)}deepmerge.all=function deepmergeAll(i,s){if(!Array.isArray(i))throw new Error("first argument should be an array");return i.reduce((function(i,u){return deepmerge(i,u,s)}),{})};var m=deepmerge;i.exports=m},27856:function(i){i.exports=function(){"use strict";const{entries:i,setPrototypeOf:s,isFrozen:u,getPrototypeOf:m,getOwnPropertyDescriptor:v}=Object;let{freeze:_,seal:j,create:M}=Object,{apply:$,construct:W}="undefined"!=typeof Reflect&&Reflect;_||(_=function freeze(i){return i}),j||(j=function seal(i){return i}),$||($=function apply(i,s,u){return i.apply(s,u)}),W||(W=function construct(i,s){return new i(...s)});const X=unapply(Array.prototype.forEach),Y=unapply(Array.prototype.pop),Z=unapply(Array.prototype.push),ee=unapply(String.prototype.toLowerCase),ae=unapply(String.prototype.toString),ie=unapply(String.prototype.match),le=unapply(String.prototype.replace),ce=unapply(String.prototype.indexOf),pe=unapply(String.prototype.trim),de=unapply(RegExp.prototype.test),fe=unconstruct(TypeError);function unapply(i){return function(s){for(var u=arguments.length,m=new Array(u>1?u-1:0),v=1;v2&&void 0!==arguments[2]?arguments[2]:ee;s&&s(i,null);let _=m.length;for(;_--;){let s=m[_];if("string"==typeof s){const i=v(s);i!==s&&(u(m)||(m[_]=i),s=i)}i[s]=!0}return i}function clone(s){const u=M(null);for(const[m,_]of i(s))void 0!==v(s,m)&&(u[m]=_);return u}function lookupGetter(i,s){for(;null!==i;){const u=v(i,s);if(u){if(u.get)return unapply(u.get);if("function"==typeof u.value)return unapply(u.value)}i=m(i)}function fallbackValue(i){return console.warn("fallback value for",i),null}return fallbackValue}const ye=_(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dialog","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","section","select","shadow","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),be=_(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","circle","clippath","defs","desc","ellipse","filter","font","g","glyph","glyphref","hkern","image","line","lineargradient","marker","mask","metadata","mpath","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","view","vkern"]),_e=_(["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]),we=_(["animate","color-profile","cursor","discard","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignobject","hatch","hatchpath","mesh","meshgradient","meshpatch","meshrow","missing-glyph","script","set","solidcolor","unknown","use"]),Se=_(["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmultiscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mspace","msqrt","mstyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover","mprescripts"]),xe=_(["maction","maligngroup","malignmark","mlongdiv","mscarries","mscarry","msgroup","mstack","msline","msrow","semantics","annotation","annotation-xml","mprescripts","none"]),Pe=_(["#text"]),Ie=_(["accept","action","align","alt","autocapitalize","autocomplete","autopictureinpicture","autoplay","background","bgcolor","border","capture","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","controls","controlslist","coords","crossorigin","datetime","decoding","default","dir","disabled","disablepictureinpicture","disableremoteplayback","download","draggable","enctype","enterkeyhint","face","for","headers","height","hidden","high","href","hreflang","id","inputmode","integrity","ismap","kind","label","lang","list","loading","loop","low","max","maxlength","media","method","min","minlength","multiple","muted","name","nonce","noshade","novalidate","nowrap","open","optimum","pattern","placeholder","playsinline","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","sizes","span","srclang","start","src","srcset","step","style","summary","tabindex","title","translate","type","usemap","valign","value","width","xmlns","slot"]),Te=_(["accent-height","accumulate","additive","alignment-baseline","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","class","clip","clippathunits","clip-path","clip-rule","color","color-interpolation","color-interpolation-filters","color-profile","color-rendering","cx","cy","d","dx","dy","diffuseconstant","direction","display","divisor","dur","edgemode","elevation","end","fill","fill-opacity","fill-rule","filter","filterunits","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","fx","fy","g1","g2","glyph-name","glyphref","gradientunits","gradienttransform","height","href","id","image-rendering","in","in2","k","k1","k2","k3","k4","kerning","keypoints","keysplines","keytimes","lang","lengthadjust","letter-spacing","kernelmatrix","kernelunitlength","lighting-color","local","marker-end","marker-mid","marker-start","markerheight","markerunits","markerwidth","maskcontentunits","maskunits","max","mask","media","method","mode","min","name","numoctaves","offset","operator","opacity","order","orient","orientation","origin","overflow","paint-order","path","pathlength","patterncontentunits","patterntransform","patternunits","points","preservealpha","preserveaspectratio","primitiveunits","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","specularconstant","specularexponent","spreadmethod","startoffset","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","style","surfacescale","systemlanguage","tabindex","targetx","targety","transform","transform-origin","text-anchor","text-decoration","text-rendering","textlength","type","u1","u2","unicode","values","viewbox","visibility","version","vert-adv-y","vert-origin-x","vert-origin-y","width","word-spacing","wrap","writing-mode","xchannelselector","ychannelselector","x","x1","x2","xmlns","y","y1","y2","z","zoomandpan"]),Re=_(["accent","accentunder","align","bevelled","close","columnsalign","columnlines","columnspan","denomalign","depth","dir","display","displaystyle","encoding","fence","frame","height","href","id","largeop","length","linethickness","lspace","lquote","mathbackground","mathcolor","mathsize","mathvariant","maxsize","minsize","movablelimits","notation","numalign","open","rowalign","rowlines","rowspacing","rowspan","rspace","rquote","scriptlevel","scriptminsize","scriptsizemultiplier","selection","separator","separators","stretchy","subscriptshift","supscriptshift","symmetric","voffset","width","xmlns"]),qe=_(["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"]),ze=j(/\{\{[\w\W]*|[\w\W]*\}\}/gm),Ve=j(/<%[\w\W]*|[\w\W]*%>/gm),We=j(/\${[\w\W]*}/gm),He=j(/^data-[\-\w.\u00B7-\uFFFF]/),Xe=j(/^aria-[\-\w]+$/),Ye=j(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),Qe=j(/^(?:\w+script|data):/i),et=j(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),tt=j(/^html$/i);var rt=Object.freeze({__proto__:null,MUSTACHE_EXPR:ze,ERB_EXPR:Ve,TMPLIT_EXPR:We,DATA_ATTR:He,ARIA_ATTR:Xe,IS_ALLOWED_URI:Ye,IS_SCRIPT_OR_DATA:Qe,ATTR_WHITESPACE:et,DOCTYPE_NAME:tt});const nt=function getGlobal(){return"undefined"==typeof window?null:window},ot=function _createTrustedTypesPolicy(i,s){if("object"!=typeof i||"function"!=typeof i.createPolicy)return null;let u=null;const m="data-tt-policy-suffix";s&&s.hasAttribute(m)&&(u=s.getAttribute(m));const v="dompurify"+(u?"#"+u:"");try{return i.createPolicy(v,{createHTML:i=>i,createScriptURL:i=>i})}catch(i){return console.warn("TrustedTypes policy "+v+" could not be created."),null}};function createDOMPurify(){let s=arguments.length>0&&void 0!==arguments[0]?arguments[0]:nt();const DOMPurify=i=>createDOMPurify(i);if(DOMPurify.version="3.0.6",DOMPurify.removed=[],!s||!s.document||9!==s.document.nodeType)return DOMPurify.isSupported=!1,DOMPurify;let{document:u}=s;const m=u,v=m.currentScript,{DocumentFragment:j,HTMLTemplateElement:$,Node:W,Element:ze,NodeFilter:Ve,NamedNodeMap:We=s.NamedNodeMap||s.MozNamedAttrMap,HTMLFormElement:He,DOMParser:Xe,trustedTypes:Qe}=s,et=ze.prototype,at=lookupGetter(et,"cloneNode"),it=lookupGetter(et,"nextSibling"),st=lookupGetter(et,"childNodes"),lt=lookupGetter(et,"parentNode");if("function"==typeof $){const i=u.createElement("template");i.content&&i.content.ownerDocument&&(u=i.content.ownerDocument)}let ct,ut="";const{implementation:pt,createNodeIterator:ht,createDocumentFragment:dt,getElementsByTagName:mt}=u,{importNode:gt}=m;let yt={};DOMPurify.isSupported="function"==typeof i&&"function"==typeof lt&&pt&&void 0!==pt.createHTMLDocument;const{MUSTACHE_EXPR:vt,ERB_EXPR:bt,TMPLIT_EXPR:_t,DATA_ATTR:Et,ARIA_ATTR:wt,IS_SCRIPT_OR_DATA:St,ATTR_WHITESPACE:xt}=rt;let{IS_ALLOWED_URI:kt}=rt,Ot=null;const At=addToSet({},[...ye,...be,..._e,...Se,...Pe]);let Ct=null;const jt=addToSet({},[...Ie,...Te,...Re,...qe]);let Pt=Object.seal(M(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),It=null,Nt=null,Tt=!0,Mt=!0,Rt=!1,Bt=!0,Dt=!1,Lt=!1,Ft=!1,qt=!1,$t=!1,zt=!1,Ut=!1,Vt=!0,Wt=!1;const Kt="user-content-";let Ht=!0,Jt=!1,Gt={},Xt=null;const Yt=addToSet({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let Qt=null;const Zt=addToSet({},["audio","video","img","source","image","track"]);let er=null;const tr=addToSet({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),rr="http://www.w3.org/1998/Math/MathML",nr="http://www.w3.org/2000/svg",ar="http://www.w3.org/1999/xhtml";let ir=ar,sr=!1,lr=null;const cr=addToSet({},[rr,nr,ar],ae);let ur=null;const pr=["application/xhtml+xml","text/html"],dr="text/html";let fr=null,mr=null;const gr=u.createElement("form"),yr=function isRegexOrFunction(i){return i instanceof RegExp||i instanceof Function},vr=function _parseConfig(){let i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!mr||mr!==i){if(i&&"object"==typeof i||(i={}),i=clone(i),ur=ur=-1===pr.indexOf(i.PARSER_MEDIA_TYPE)?dr:i.PARSER_MEDIA_TYPE,fr="application/xhtml+xml"===ur?ae:ee,Ot="ALLOWED_TAGS"in i?addToSet({},i.ALLOWED_TAGS,fr):At,Ct="ALLOWED_ATTR"in i?addToSet({},i.ALLOWED_ATTR,fr):jt,lr="ALLOWED_NAMESPACES"in i?addToSet({},i.ALLOWED_NAMESPACES,ae):cr,er="ADD_URI_SAFE_ATTR"in i?addToSet(clone(tr),i.ADD_URI_SAFE_ATTR,fr):tr,Qt="ADD_DATA_URI_TAGS"in i?addToSet(clone(Zt),i.ADD_DATA_URI_TAGS,fr):Zt,Xt="FORBID_CONTENTS"in i?addToSet({},i.FORBID_CONTENTS,fr):Yt,It="FORBID_TAGS"in i?addToSet({},i.FORBID_TAGS,fr):{},Nt="FORBID_ATTR"in i?addToSet({},i.FORBID_ATTR,fr):{},Gt="USE_PROFILES"in i&&i.USE_PROFILES,Tt=!1!==i.ALLOW_ARIA_ATTR,Mt=!1!==i.ALLOW_DATA_ATTR,Rt=i.ALLOW_UNKNOWN_PROTOCOLS||!1,Bt=!1!==i.ALLOW_SELF_CLOSE_IN_ATTR,Dt=i.SAFE_FOR_TEMPLATES||!1,Lt=i.WHOLE_DOCUMENT||!1,$t=i.RETURN_DOM||!1,zt=i.RETURN_DOM_FRAGMENT||!1,Ut=i.RETURN_TRUSTED_TYPE||!1,qt=i.FORCE_BODY||!1,Vt=!1!==i.SANITIZE_DOM,Wt=i.SANITIZE_NAMED_PROPS||!1,Ht=!1!==i.KEEP_CONTENT,Jt=i.IN_PLACE||!1,kt=i.ALLOWED_URI_REGEXP||Ye,ir=i.NAMESPACE||ar,Pt=i.CUSTOM_ELEMENT_HANDLING||{},i.CUSTOM_ELEMENT_HANDLING&&yr(i.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(Pt.tagNameCheck=i.CUSTOM_ELEMENT_HANDLING.tagNameCheck),i.CUSTOM_ELEMENT_HANDLING&&yr(i.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(Pt.attributeNameCheck=i.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),i.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof i.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(Pt.allowCustomizedBuiltInElements=i.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Dt&&(Mt=!1),zt&&($t=!0),Gt&&(Ot=addToSet({},[...Pe]),Ct=[],!0===Gt.html&&(addToSet(Ot,ye),addToSet(Ct,Ie)),!0===Gt.svg&&(addToSet(Ot,be),addToSet(Ct,Te),addToSet(Ct,qe)),!0===Gt.svgFilters&&(addToSet(Ot,_e),addToSet(Ct,Te),addToSet(Ct,qe)),!0===Gt.mathMl&&(addToSet(Ot,Se),addToSet(Ct,Re),addToSet(Ct,qe))),i.ADD_TAGS&&(Ot===At&&(Ot=clone(Ot)),addToSet(Ot,i.ADD_TAGS,fr)),i.ADD_ATTR&&(Ct===jt&&(Ct=clone(Ct)),addToSet(Ct,i.ADD_ATTR,fr)),i.ADD_URI_SAFE_ATTR&&addToSet(er,i.ADD_URI_SAFE_ATTR,fr),i.FORBID_CONTENTS&&(Xt===Yt&&(Xt=clone(Xt)),addToSet(Xt,i.FORBID_CONTENTS,fr)),Ht&&(Ot["#text"]=!0),Lt&&addToSet(Ot,["html","head","body"]),Ot.table&&(addToSet(Ot,["tbody"]),delete It.tbody),i.TRUSTED_TYPES_POLICY){if("function"!=typeof i.TRUSTED_TYPES_POLICY.createHTML)throw fe('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof i.TRUSTED_TYPES_POLICY.createScriptURL)throw fe('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');ct=i.TRUSTED_TYPES_POLICY,ut=ct.createHTML("")}else void 0===ct&&(ct=ot(Qe,v)),null!==ct&&"string"==typeof ut&&(ut=ct.createHTML(""));_&&_(i),mr=i}},br=addToSet({},["mi","mo","mn","ms","mtext"]),_r=addToSet({},["foreignobject","desc","title","annotation-xml"]),Er=addToSet({},["title","style","font","a","script"]),wr=addToSet({},be);addToSet(wr,_e),addToSet(wr,we);const Sr=addToSet({},Se);addToSet(Sr,xe);const xr=function _checkValidNamespace(i){let s=lt(i);s&&s.tagName||(s={namespaceURI:ir,tagName:"template"});const u=ee(i.tagName),m=ee(s.tagName);return!!lr[i.namespaceURI]&&(i.namespaceURI===nr?s.namespaceURI===ar?"svg"===u:s.namespaceURI===rr?"svg"===u&&("annotation-xml"===m||br[m]):Boolean(wr[u]):i.namespaceURI===rr?s.namespaceURI===ar?"math"===u:s.namespaceURI===nr?"math"===u&&_r[m]:Boolean(Sr[u]):i.namespaceURI===ar?!(s.namespaceURI===nr&&!_r[m])&&!(s.namespaceURI===rr&&!br[m])&&!Sr[u]&&(Er[u]||!wr[u]):!("application/xhtml+xml"!==ur||!lr[i.namespaceURI]))},kr=function _forceRemove(i){Z(DOMPurify.removed,{element:i});try{i.parentNode.removeChild(i)}catch(s){i.remove()}},Or=function _removeAttribute(i,s){try{Z(DOMPurify.removed,{attribute:s.getAttributeNode(i),from:s})}catch(i){Z(DOMPurify.removed,{attribute:null,from:s})}if(s.removeAttribute(i),"is"===i&&!Ct[i])if($t||zt)try{kr(s)}catch(i){}else try{s.setAttribute(i,"")}catch(i){}},Ar=function _initDocument(i){let s=null,m=null;if(qt)i=""+i;else{const s=ie(i,/^[\r\n\t ]+/);m=s&&s[0]}"application/xhtml+xml"===ur&&ir===ar&&(i=''+i+"");const v=ct?ct.createHTML(i):i;if(ir===ar)try{s=(new Xe).parseFromString(v,ur)}catch(i){}if(!s||!s.documentElement){s=pt.createDocument(ir,"template",null);try{s.documentElement.innerHTML=sr?ut:v}catch(i){}}const _=s.body||s.documentElement;return i&&m&&_.insertBefore(u.createTextNode(m),_.childNodes[0]||null),ir===ar?mt.call(s,Lt?"html":"body")[0]:Lt?s.documentElement:_},Cr=function _createNodeIterator(i){return ht.call(i.ownerDocument||i,i,Ve.SHOW_ELEMENT|Ve.SHOW_COMMENT|Ve.SHOW_TEXT,null)},jr=function _isClobbered(i){return i instanceof He&&("string"!=typeof i.nodeName||"string"!=typeof i.textContent||"function"!=typeof i.removeChild||!(i.attributes instanceof We)||"function"!=typeof i.removeAttribute||"function"!=typeof i.setAttribute||"string"!=typeof i.namespaceURI||"function"!=typeof i.insertBefore||"function"!=typeof i.hasChildNodes)},Pr=function _isNode(i){return"function"==typeof W&&i instanceof W},Ir=function _executeHook(i,s,u){yt[i]&&X(yt[i],(i=>{i.call(DOMPurify,s,u,mr)}))},Nr=function _sanitizeElements(i){let s=null;if(Ir("beforeSanitizeElements",i,null),jr(i))return kr(i),!0;const u=fr(i.nodeName);if(Ir("uponSanitizeElement",i,{tagName:u,allowedTags:Ot}),i.hasChildNodes()&&!Pr(i.firstElementChild)&&de(/<[/\w]/g,i.innerHTML)&&de(/<[/\w]/g,i.textContent))return kr(i),!0;if(!Ot[u]||It[u]){if(!It[u]&&Mr(u)){if(Pt.tagNameCheck instanceof RegExp&&de(Pt.tagNameCheck,u))return!1;if(Pt.tagNameCheck instanceof Function&&Pt.tagNameCheck(u))return!1}if(Ht&&!Xt[u]){const s=lt(i)||i.parentNode,u=st(i)||i.childNodes;if(u&&s)for(let m=u.length-1;m>=0;--m)s.insertBefore(at(u[m],!0),it(i))}return kr(i),!0}return i instanceof ze&&!xr(i)?(kr(i),!0):"noscript"!==u&&"noembed"!==u&&"noframes"!==u||!de(/<\/no(script|embed|frames)/i,i.innerHTML)?(Dt&&3===i.nodeType&&(s=i.textContent,X([vt,bt,_t],(i=>{s=le(s,i," ")})),i.textContent!==s&&(Z(DOMPurify.removed,{element:i.cloneNode()}),i.textContent=s)),Ir("afterSanitizeElements",i,null),!1):(kr(i),!0)},Tr=function _isValidAttribute(i,s,m){if(Vt&&("id"===s||"name"===s)&&(m in u||m in gr))return!1;if(Mt&&!Nt[s]&&de(Et,s));else if(Tt&&de(wt,s));else if(!Ct[s]||Nt[s]){if(!(Mr(i)&&(Pt.tagNameCheck instanceof RegExp&&de(Pt.tagNameCheck,i)||Pt.tagNameCheck instanceof Function&&Pt.tagNameCheck(i))&&(Pt.attributeNameCheck instanceof RegExp&&de(Pt.attributeNameCheck,s)||Pt.attributeNameCheck instanceof Function&&Pt.attributeNameCheck(s))||"is"===s&&Pt.allowCustomizedBuiltInElements&&(Pt.tagNameCheck instanceof RegExp&&de(Pt.tagNameCheck,m)||Pt.tagNameCheck instanceof Function&&Pt.tagNameCheck(m))))return!1}else if(er[s]);else if(de(kt,le(m,xt,"")));else if("src"!==s&&"xlink:href"!==s&&"href"!==s||"script"===i||0!==ce(m,"data:")||!Qt[i])if(Rt&&!de(St,le(m,xt,"")));else if(m)return!1;return!0},Mr=function _isBasicCustomElement(i){return i.indexOf("-")>0},Rr=function _sanitizeAttributes(i){Ir("beforeSanitizeAttributes",i,null);const{attributes:s}=i;if(!s)return;const u={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:Ct};let m=s.length;for(;m--;){const v=s[m],{name:_,namespaceURI:j,value:M}=v,$=fr(_);let W="value"===_?M:pe(M);if(u.attrName=$,u.attrValue=W,u.keepAttr=!0,u.forceKeepAttr=void 0,Ir("uponSanitizeAttribute",i,u),W=u.attrValue,u.forceKeepAttr)continue;if(Or(_,i),!u.keepAttr)continue;if(!Bt&&de(/\/>/i,W)){Or(_,i);continue}Dt&&X([vt,bt,_t],(i=>{W=le(W,i," ")}));const Z=fr(i.nodeName);if(Tr(Z,$,W)){if(!Wt||"id"!==$&&"name"!==$||(Or(_,i),W=Kt+W),ct&&"object"==typeof Qe&&"function"==typeof Qe.getAttributeType)if(j);else switch(Qe.getAttributeType(Z,$)){case"TrustedHTML":W=ct.createHTML(W);break;case"TrustedScriptURL":W=ct.createScriptURL(W)}try{j?i.setAttributeNS(j,_,W):i.setAttribute(_,W),Y(DOMPurify.removed)}catch(i){}}}Ir("afterSanitizeAttributes",i,null)},Br=function _sanitizeShadowDOM(i){let s=null;const u=Cr(i);for(Ir("beforeSanitizeShadowDOM",i,null);s=u.nextNode();)Ir("uponSanitizeShadowNode",s,null),Nr(s)||(s.content instanceof j&&_sanitizeShadowDOM(s.content),Rr(s));Ir("afterSanitizeShadowDOM",i,null)};return DOMPurify.sanitize=function(i){let s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},u=null,v=null,_=null,M=null;if(sr=!i,sr&&(i="\x3c!--\x3e"),"string"!=typeof i&&!Pr(i)){if("function"!=typeof i.toString)throw fe("toString is not a function");if("string"!=typeof(i=i.toString()))throw fe("dirty is not a string, aborting")}if(!DOMPurify.isSupported)return i;if(Ft||vr(s),DOMPurify.removed=[],"string"==typeof i&&(Jt=!1),Jt){if(i.nodeName){const s=fr(i.nodeName);if(!Ot[s]||It[s])throw fe("root node is forbidden and cannot be sanitized in-place")}}else if(i instanceof W)u=Ar("\x3c!----\x3e"),v=u.ownerDocument.importNode(i,!0),1===v.nodeType&&"BODY"===v.nodeName||"HTML"===v.nodeName?u=v:u.appendChild(v);else{if(!$t&&!Dt&&!Lt&&-1===i.indexOf("<"))return ct&&Ut?ct.createHTML(i):i;if(u=Ar(i),!u)return $t?null:Ut?ut:""}u&&qt&&kr(u.firstChild);const $=Cr(Jt?i:u);for(;_=$.nextNode();)Nr(_)||(_.content instanceof j&&Br(_.content),Rr(_));if(Jt)return i;if($t){if(zt)for(M=dt.call(u.ownerDocument);u.firstChild;)M.appendChild(u.firstChild);else M=u;return(Ct.shadowroot||Ct.shadowrootmode)&&(M=gt.call(m,M,!0)),M}let Y=Lt?u.outerHTML:u.innerHTML;return Lt&&Ot["!doctype"]&&u.ownerDocument&&u.ownerDocument.doctype&&u.ownerDocument.doctype.name&&de(tt,u.ownerDocument.doctype.name)&&(Y="\n"+Y),Dt&&X([vt,bt,_t],(i=>{Y=le(Y,i," ")})),ct&&Ut?ct.createHTML(Y):Y},DOMPurify.setConfig=function(){vr(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),Ft=!0},DOMPurify.clearConfig=function(){mr=null,Ft=!1},DOMPurify.isValidAttribute=function(i,s,u){mr||vr({});const m=fr(i),v=fr(s);return Tr(m,v,u)},DOMPurify.addHook=function(i,s){"function"==typeof s&&(yt[i]=yt[i]||[],Z(yt[i],s))},DOMPurify.removeHook=function(i){if(yt[i])return Y(yt[i])},DOMPurify.removeHooks=function(i){yt[i]&&(yt[i]=[])},DOMPurify.removeAllHooks=function(){yt={}},DOMPurify}return createDOMPurify()}()},69450:i=>{"use strict";class SubRange{constructor(i,s){this.low=i,this.high=s,this.length=1+s-i}overlaps(i){return!(this.highi.high)}touches(i){return!(this.high+1i.high)}add(i){return new SubRange(Math.min(this.low,i.low),Math.max(this.high,i.high))}subtract(i){return i.low<=this.low&&i.high>=this.high?[]:i.low>this.low&&i.highi+s.length),0)}add(i,s){var _add=i=>{for(var s=0;s{for(var s=0;s{for(var s=0;s{for(var u=s.low;u<=s.high;)i.push(u),u++;return i}),[])}subranges(){return this.ranges.map((i=>({low:i.low,high:i.high,length:1+i.high-i.low})))}}i.exports=DRange},17187:i=>{"use strict";var s,u="object"==typeof Reflect?Reflect:null,m=u&&"function"==typeof u.apply?u.apply:function ReflectApply(i,s,u){return Function.prototype.apply.call(i,s,u)};s=u&&"function"==typeof u.ownKeys?u.ownKeys:Object.getOwnPropertySymbols?function ReflectOwnKeys(i){return Object.getOwnPropertyNames(i).concat(Object.getOwnPropertySymbols(i))}:function ReflectOwnKeys(i){return Object.getOwnPropertyNames(i)};var v=Number.isNaN||function NumberIsNaN(i){return i!=i};function EventEmitter(){EventEmitter.init.call(this)}i.exports=EventEmitter,i.exports.once=function once(i,s){return new Promise((function(u,m){function errorListener(u){i.removeListener(s,resolver),m(u)}function resolver(){"function"==typeof i.removeListener&&i.removeListener("error",errorListener),u([].slice.call(arguments))}eventTargetAgnosticAddListener(i,s,resolver,{once:!0}),"error"!==s&&function addErrorHandlerIfEventEmitter(i,s,u){"function"==typeof i.on&&eventTargetAgnosticAddListener(i,"error",s,u)}(i,errorListener,{once:!0})}))},EventEmitter.EventEmitter=EventEmitter,EventEmitter.prototype._events=void 0,EventEmitter.prototype._eventsCount=0,EventEmitter.prototype._maxListeners=void 0;var _=10;function checkListener(i){if("function"!=typeof i)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof i)}function _getMaxListeners(i){return void 0===i._maxListeners?EventEmitter.defaultMaxListeners:i._maxListeners}function _addListener(i,s,u,m){var v,_,j;if(checkListener(u),void 0===(_=i._events)?(_=i._events=Object.create(null),i._eventsCount=0):(void 0!==_.newListener&&(i.emit("newListener",s,u.listener?u.listener:u),_=i._events),j=_[s]),void 0===j)j=_[s]=u,++i._eventsCount;else if("function"==typeof j?j=_[s]=m?[u,j]:[j,u]:m?j.unshift(u):j.push(u),(v=_getMaxListeners(i))>0&&j.length>v&&!j.warned){j.warned=!0;var M=new Error("Possible EventEmitter memory leak detected. "+j.length+" "+String(s)+" listeners added. Use emitter.setMaxListeners() to increase limit");M.name="MaxListenersExceededWarning",M.emitter=i,M.type=s,M.count=j.length,function ProcessEmitWarning(i){console&&console.warn&&console.warn(i)}(M)}return i}function onceWrapper(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function _onceWrap(i,s,u){var m={fired:!1,wrapFn:void 0,target:i,type:s,listener:u},v=onceWrapper.bind(m);return v.listener=u,m.wrapFn=v,v}function _listeners(i,s,u){var m=i._events;if(void 0===m)return[];var v=m[s];return void 0===v?[]:"function"==typeof v?u?[v.listener||v]:[v]:u?function unwrapListeners(i){for(var s=new Array(i.length),u=0;u0&&(j=s[0]),j instanceof Error)throw j;var M=new Error("Unhandled error."+(j?" ("+j.message+")":""));throw M.context=j,M}var $=_[i];if(void 0===$)return!1;if("function"==typeof $)m($,this,s);else{var W=$.length,X=arrayClone($,W);for(u=0;u=0;_--)if(u[_]===s||u[_].listener===s){j=u[_].listener,v=_;break}if(v<0)return this;0===v?u.shift():function spliceOne(i,s){for(;s+1=0;m--)this.removeListener(i,s[m]);return this},EventEmitter.prototype.listeners=function listeners(i){return _listeners(this,i,!0)},EventEmitter.prototype.rawListeners=function rawListeners(i){return _listeners(this,i,!1)},EventEmitter.listenerCount=function(i,s){return"function"==typeof i.listenerCount?i.listenerCount(s):listenerCount.call(i,s)},EventEmitter.prototype.listenerCount=listenerCount,EventEmitter.prototype.eventNames=function eventNames(){return this._eventsCount>0?s(this._events):[]}},21102:(i,s,u)=>{"use strict";var m=u(46291),v=create(Error);function create(i){return FormattedError.displayName=i.displayName||i.name,FormattedError;function FormattedError(s){return s&&(s=m.apply(null,arguments)),new i(s)}}i.exports=v,v.eval=create(EvalError),v.range=create(RangeError),v.reference=create(ReferenceError),v.syntax=create(SyntaxError),v.type=create(TypeError),v.uri=create(URIError),v.create=create},46291:i=>{!function(){var s;function format(i){for(var s,u,m,v,_=1,j=[].slice.call(arguments),M=0,$=i.length,W="",X=!1,Y=!1,nextArg=function(){return j[_++]},slurpNumber=function(){for(var u="";/\d/.test(i[M]);)u+=i[M++],s=i[M];return u.length>0?parseInt(u):null};M<$;++M)if(s=i[M],X)switch(X=!1,"."==s?(Y=!1,s=i[++M]):"0"==s&&"."==i[M+1]?(Y=!0,s=i[M+=2]):Y=!0,v=slurpNumber(),s){case"b":W+=parseInt(nextArg(),10).toString(2);break;case"c":W+="string"==typeof(u=nextArg())||u instanceof String?u:String.fromCharCode(parseInt(u,10));break;case"d":W+=parseInt(nextArg(),10);break;case"f":m=String(parseFloat(nextArg()).toFixed(v||6)),W+=Y?m:m.replace(/^0/,"");break;case"j":W+=JSON.stringify(nextArg());break;case"o":W+="0"+parseInt(nextArg(),10).toString(8);break;case"s":W+=nextArg();break;case"x":W+="0x"+parseInt(nextArg(),10).toString(16);break;case"X":W+="0x"+parseInt(nextArg(),10).toString(16).toUpperCase();break;default:W+=s}else"%"===s?X=!0:W+=s;return W}(s=i.exports=format).format=format,s.vsprintf=function vsprintf(i,s){return format.apply(null,[i].concat(s))},"undefined"!=typeof console&&"function"==typeof console.log&&(s.printf=function printf(){console.log(format.apply(null,arguments))})}()},17648:i=>{"use strict";var s=Array.prototype.slice,u=Object.prototype.toString;i.exports=function bind(i){var m=this;if("function"!=typeof m||"[object Function]"!==u.call(m))throw new TypeError("Function.prototype.bind called on incompatible "+m);for(var v,_=s.call(arguments,1),j=Math.max(0,m.length-_.length),M=[],$=0;${"use strict";var m=u(17648);i.exports=Function.prototype.bind||m},40210:(i,s,u)=>{"use strict";var m,v=SyntaxError,_=Function,j=TypeError,getEvalledConstructor=function(i){try{return _('"use strict"; return ('+i+").constructor;")()}catch(i){}},M=Object.getOwnPropertyDescriptor;if(M)try{M({},"")}catch(i){M=null}var throwTypeError=function(){throw new j},$=M?function(){try{return throwTypeError}catch(i){try{return M(arguments,"callee").get}catch(i){return throwTypeError}}}():throwTypeError,W=u(41405)(),X=u(28185)(),Y=Object.getPrototypeOf||(X?function(i){return i.__proto__}:null),Z={},ee="undefined"!=typeof Uint8Array&&Y?Y(Uint8Array):m,ae={"%AggregateError%":"undefined"==typeof AggregateError?m:AggregateError,"%Array%":Array,"%ArrayBuffer%":"undefined"==typeof ArrayBuffer?m:ArrayBuffer,"%ArrayIteratorPrototype%":W&&Y?Y([][Symbol.iterator]()):m,"%AsyncFromSyncIteratorPrototype%":m,"%AsyncFunction%":Z,"%AsyncGenerator%":Z,"%AsyncGeneratorFunction%":Z,"%AsyncIteratorPrototype%":Z,"%Atomics%":"undefined"==typeof Atomics?m:Atomics,"%BigInt%":"undefined"==typeof BigInt?m:BigInt,"%BigInt64Array%":"undefined"==typeof BigInt64Array?m:BigInt64Array,"%BigUint64Array%":"undefined"==typeof BigUint64Array?m:BigUint64Array,"%Boolean%":Boolean,"%DataView%":"undefined"==typeof DataView?m:DataView,"%Date%":Date,"%decodeURI%":decodeURI,"%decodeURIComponent%":decodeURIComponent,"%encodeURI%":encodeURI,"%encodeURIComponent%":encodeURIComponent,"%Error%":Error,"%eval%":eval,"%EvalError%":EvalError,"%Float32Array%":"undefined"==typeof Float32Array?m:Float32Array,"%Float64Array%":"undefined"==typeof Float64Array?m:Float64Array,"%FinalizationRegistry%":"undefined"==typeof FinalizationRegistry?m:FinalizationRegistry,"%Function%":_,"%GeneratorFunction%":Z,"%Int8Array%":"undefined"==typeof Int8Array?m:Int8Array,"%Int16Array%":"undefined"==typeof Int16Array?m:Int16Array,"%Int32Array%":"undefined"==typeof Int32Array?m:Int32Array,"%isFinite%":isFinite,"%isNaN%":isNaN,"%IteratorPrototype%":W&&Y?Y(Y([][Symbol.iterator]())):m,"%JSON%":"object"==typeof JSON?JSON:m,"%Map%":"undefined"==typeof Map?m:Map,"%MapIteratorPrototype%":"undefined"!=typeof Map&&W&&Y?Y((new Map)[Symbol.iterator]()):m,"%Math%":Math,"%Number%":Number,"%Object%":Object,"%parseFloat%":parseFloat,"%parseInt%":parseInt,"%Promise%":"undefined"==typeof Promise?m:Promise,"%Proxy%":"undefined"==typeof Proxy?m:Proxy,"%RangeError%":RangeError,"%ReferenceError%":ReferenceError,"%Reflect%":"undefined"==typeof Reflect?m:Reflect,"%RegExp%":RegExp,"%Set%":"undefined"==typeof Set?m:Set,"%SetIteratorPrototype%":"undefined"!=typeof Set&&W&&Y?Y((new Set)[Symbol.iterator]()):m,"%SharedArrayBuffer%":"undefined"==typeof SharedArrayBuffer?m:SharedArrayBuffer,"%String%":String,"%StringIteratorPrototype%":W&&Y?Y(""[Symbol.iterator]()):m,"%Symbol%":W?Symbol:m,"%SyntaxError%":v,"%ThrowTypeError%":$,"%TypedArray%":ee,"%TypeError%":j,"%Uint8Array%":"undefined"==typeof Uint8Array?m:Uint8Array,"%Uint8ClampedArray%":"undefined"==typeof Uint8ClampedArray?m:Uint8ClampedArray,"%Uint16Array%":"undefined"==typeof Uint16Array?m:Uint16Array,"%Uint32Array%":"undefined"==typeof Uint32Array?m:Uint32Array,"%URIError%":URIError,"%WeakMap%":"undefined"==typeof WeakMap?m:WeakMap,"%WeakRef%":"undefined"==typeof WeakRef?m:WeakRef,"%WeakSet%":"undefined"==typeof WeakSet?m:WeakSet};if(Y)try{null.error}catch(i){var ie=Y(Y(i));ae["%Error.prototype%"]=ie}var le=function doEval(i){var s;if("%AsyncFunction%"===i)s=getEvalledConstructor("async function () {}");else if("%GeneratorFunction%"===i)s=getEvalledConstructor("function* () {}");else if("%AsyncGeneratorFunction%"===i)s=getEvalledConstructor("async function* () {}");else if("%AsyncGenerator%"===i){var u=doEval("%AsyncGeneratorFunction%");u&&(s=u.prototype)}else if("%AsyncIteratorPrototype%"===i){var m=doEval("%AsyncGenerator%");m&&Y&&(s=Y(m.prototype))}return ae[i]=s,s},ce={"%ArrayBufferPrototype%":["ArrayBuffer","prototype"],"%ArrayPrototype%":["Array","prototype"],"%ArrayProto_entries%":["Array","prototype","entries"],"%ArrayProto_forEach%":["Array","prototype","forEach"],"%ArrayProto_keys%":["Array","prototype","keys"],"%ArrayProto_values%":["Array","prototype","values"],"%AsyncFunctionPrototype%":["AsyncFunction","prototype"],"%AsyncGenerator%":["AsyncGeneratorFunction","prototype"],"%AsyncGeneratorPrototype%":["AsyncGeneratorFunction","prototype","prototype"],"%BooleanPrototype%":["Boolean","prototype"],"%DataViewPrototype%":["DataView","prototype"],"%DatePrototype%":["Date","prototype"],"%ErrorPrototype%":["Error","prototype"],"%EvalErrorPrototype%":["EvalError","prototype"],"%Float32ArrayPrototype%":["Float32Array","prototype"],"%Float64ArrayPrototype%":["Float64Array","prototype"],"%FunctionPrototype%":["Function","prototype"],"%Generator%":["GeneratorFunction","prototype"],"%GeneratorPrototype%":["GeneratorFunction","prototype","prototype"],"%Int8ArrayPrototype%":["Int8Array","prototype"],"%Int16ArrayPrototype%":["Int16Array","prototype"],"%Int32ArrayPrototype%":["Int32Array","prototype"],"%JSONParse%":["JSON","parse"],"%JSONStringify%":["JSON","stringify"],"%MapPrototype%":["Map","prototype"],"%NumberPrototype%":["Number","prototype"],"%ObjectPrototype%":["Object","prototype"],"%ObjProto_toString%":["Object","prototype","toString"],"%ObjProto_valueOf%":["Object","prototype","valueOf"],"%PromisePrototype%":["Promise","prototype"],"%PromiseProto_then%":["Promise","prototype","then"],"%Promise_all%":["Promise","all"],"%Promise_reject%":["Promise","reject"],"%Promise_resolve%":["Promise","resolve"],"%RangeErrorPrototype%":["RangeError","prototype"],"%ReferenceErrorPrototype%":["ReferenceError","prototype"],"%RegExpPrototype%":["RegExp","prototype"],"%SetPrototype%":["Set","prototype"],"%SharedArrayBufferPrototype%":["SharedArrayBuffer","prototype"],"%StringPrototype%":["String","prototype"],"%SymbolPrototype%":["Symbol","prototype"],"%SyntaxErrorPrototype%":["SyntaxError","prototype"],"%TypedArrayPrototype%":["TypedArray","prototype"],"%TypeErrorPrototype%":["TypeError","prototype"],"%Uint8ArrayPrototype%":["Uint8Array","prototype"],"%Uint8ClampedArrayPrototype%":["Uint8ClampedArray","prototype"],"%Uint16ArrayPrototype%":["Uint16Array","prototype"],"%Uint32ArrayPrototype%":["Uint32Array","prototype"],"%URIErrorPrototype%":["URIError","prototype"],"%WeakMapPrototype%":["WeakMap","prototype"],"%WeakSetPrototype%":["WeakSet","prototype"]},pe=u(58612),de=u(17642),fe=pe.call(Function.call,Array.prototype.concat),ye=pe.call(Function.apply,Array.prototype.splice),be=pe.call(Function.call,String.prototype.replace),_e=pe.call(Function.call,String.prototype.slice),we=pe.call(Function.call,RegExp.prototype.exec),Se=/[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g,xe=/\\(\\)?/g,Pe=function getBaseIntrinsic(i,s){var u,m=i;if(de(ce,m)&&(m="%"+(u=ce[m])[0]+"%"),de(ae,m)){var _=ae[m];if(_===Z&&(_=le(m)),void 0===_&&!s)throw new j("intrinsic "+i+" exists, but is not available. Please file an issue!");return{alias:u,name:m,value:_}}throw new v("intrinsic "+i+" does not exist!")};i.exports=function GetIntrinsic(i,s){if("string"!=typeof i||0===i.length)throw new j("intrinsic name must be a non-empty string");if(arguments.length>1&&"boolean"!=typeof s)throw new j('"allowMissing" argument must be a boolean');if(null===we(/^%?[^%]*%?$/,i))throw new v("`%` may not be present anywhere but at the beginning and end of the intrinsic name");var u=function stringToPath(i){var s=_e(i,0,1),u=_e(i,-1);if("%"===s&&"%"!==u)throw new v("invalid intrinsic syntax, expected closing `%`");if("%"===u&&"%"!==s)throw new v("invalid intrinsic syntax, expected opening `%`");var m=[];return be(i,Se,(function(i,s,u,v){m[m.length]=u?be(v,xe,"$1"):s||i})),m}(i),m=u.length>0?u[0]:"",_=Pe("%"+m+"%",s),$=_.name,W=_.value,X=!1,Y=_.alias;Y&&(m=Y[0],ye(u,fe([0,1],Y)));for(var Z=1,ee=!0;Z=u.length){var pe=M(W,ie);W=(ee=!!pe)&&"get"in pe&&!("originalValue"in pe.get)?pe.get:W[ie]}else ee=de(W,ie),W=W[ie];ee&&!X&&(ae[$]=W)}}return W}},28185:i=>{"use strict";var s={foo:{}},u=Object;i.exports=function hasProto(){return{__proto__:s}.foo===s.foo&&!({__proto__:null}instanceof u)}},41405:(i,s,u)=>{"use strict";var m="undefined"!=typeof Symbol&&Symbol,v=u(55419);i.exports=function hasNativeSymbols(){return"function"==typeof m&&("function"==typeof Symbol&&("symbol"==typeof m("foo")&&("symbol"==typeof Symbol("bar")&&v())))}},55419:i=>{"use strict";i.exports=function hasSymbols(){if("function"!=typeof Symbol||"function"!=typeof Object.getOwnPropertySymbols)return!1;if("symbol"==typeof Symbol.iterator)return!0;var i={},s=Symbol("test"),u=Object(s);if("string"==typeof s)return!1;if("[object Symbol]"!==Object.prototype.toString.call(s))return!1;if("[object Symbol]"!==Object.prototype.toString.call(u))return!1;for(s in i[s]=42,i)return!1;if("function"==typeof Object.keys&&0!==Object.keys(i).length)return!1;if("function"==typeof Object.getOwnPropertyNames&&0!==Object.getOwnPropertyNames(i).length)return!1;var m=Object.getOwnPropertySymbols(i);if(1!==m.length||m[0]!==s)return!1;if(!Object.prototype.propertyIsEnumerable.call(i,s))return!1;if("function"==typeof Object.getOwnPropertyDescriptor){var v=Object.getOwnPropertyDescriptor(i,s);if(42!==v.value||!0!==v.enumerable)return!1}return!0}},17642:(i,s,u)=>{"use strict";var m=u(58612);i.exports=m.call(Function.call,Object.prototype.hasOwnProperty)},47802:i=>{function deepFreeze(i){return i instanceof Map?i.clear=i.delete=i.set=function(){throw new Error("map is read-only")}:i instanceof Set&&(i.add=i.clear=i.delete=function(){throw new Error("set is read-only")}),Object.freeze(i),Object.getOwnPropertyNames(i).forEach((function(s){var u=i[s];"object"!=typeof u||Object.isFrozen(u)||deepFreeze(u)})),i}var s=deepFreeze,u=deepFreeze;s.default=u;class Response{constructor(i){void 0===i.data&&(i.data={}),this.data=i.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}}function escapeHTML(i){return i.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function inherit(i,...s){const u=Object.create(null);for(const s in i)u[s]=i[s];return s.forEach((function(i){for(const s in i)u[s]=i[s]})),u}const emitsWrappingTags=i=>!!i.kind;class HTMLRenderer{constructor(i,s){this.buffer="",this.classPrefix=s.classPrefix,i.walk(this)}addText(i){this.buffer+=escapeHTML(i)}openNode(i){if(!emitsWrappingTags(i))return;let s=i.kind;i.sublanguage||(s=`${this.classPrefix}${s}`),this.span(s)}closeNode(i){emitsWrappingTags(i)&&(this.buffer+="")}value(){return this.buffer}span(i){this.buffer+=``}}class TokenTree{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(i){this.top.children.push(i)}openNode(i){const s={kind:i,children:[]};this.add(s),this.stack.push(s)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(i){return this.constructor._walk(i,this.rootNode)}static _walk(i,s){return"string"==typeof s?i.addText(s):s.children&&(i.openNode(s),s.children.forEach((s=>this._walk(i,s))),i.closeNode(s)),i}static _collapse(i){"string"!=typeof i&&i.children&&(i.children.every((i=>"string"==typeof i))?i.children=[i.children.join("")]:i.children.forEach((i=>{TokenTree._collapse(i)})))}}class TokenTreeEmitter extends TokenTree{constructor(i){super(),this.options=i}addKeyword(i,s){""!==i&&(this.openNode(s),this.addText(i),this.closeNode())}addText(i){""!==i&&this.add(i)}addSublanguage(i,s){const u=i.root;u.kind=s,u.sublanguage=!0,this.add(u)}toHTML(){return new HTMLRenderer(this,this.options).value()}finalize(){return!0}}function source(i){return i?"string"==typeof i?i:i.source:null}const m=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;const v="[a-zA-Z]\\w*",_="[a-zA-Z_]\\w*",j="\\b\\d+(\\.\\d+)?",M="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",$="\\b(0b[01]+)",W={begin:"\\\\[\\s\\S]",relevance:0},X={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[W]},Y={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[W]},Z={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},COMMENT=function(i,s,u={}){const m=inherit({className:"comment",begin:i,end:s,contains:[]},u);return m.contains.push(Z),m.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),m},ee=COMMENT("//","$"),ae=COMMENT("/\\*","\\*/"),ie=COMMENT("#","$"),le={className:"number",begin:j,relevance:0},ce={className:"number",begin:M,relevance:0},pe={className:"number",begin:$,relevance:0},de={className:"number",begin:j+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},fe={begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[W,{begin:/\[/,end:/\]/,relevance:0,contains:[W]}]}]},ye={className:"title",begin:v,relevance:0},be={className:"title",begin:_,relevance:0},_e={begin:"\\.\\s*"+_,relevance:0};var we=Object.freeze({__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:v,UNDERSCORE_IDENT_RE:_,NUMBER_RE:j,C_NUMBER_RE:M,BINARY_NUMBER_RE:$,RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(i={})=>{const s=/^#![ ]*\//;return i.binary&&(i.begin=function concat(...i){return i.map((i=>source(i))).join("")}(s,/.*\b/,i.binary,/\b.*/)),inherit({className:"meta",begin:s,end:/$/,relevance:0,"on:begin":(i,s)=>{0!==i.index&&s.ignoreMatch()}},i)},BACKSLASH_ESCAPE:W,APOS_STRING_MODE:X,QUOTE_STRING_MODE:Y,PHRASAL_WORDS_MODE:Z,COMMENT,C_LINE_COMMENT_MODE:ee,C_BLOCK_COMMENT_MODE:ae,HASH_COMMENT_MODE:ie,NUMBER_MODE:le,C_NUMBER_MODE:ce,BINARY_NUMBER_MODE:pe,CSS_NUMBER_MODE:de,REGEXP_MODE:fe,TITLE_MODE:ye,UNDERSCORE_TITLE_MODE:be,METHOD_GUARD:_e,END_SAME_AS_BEGIN:function(i){return Object.assign(i,{"on:begin":(i,s)=>{s.data._beginMatch=i[1]},"on:end":(i,s)=>{s.data._beginMatch!==i[1]&&s.ignoreMatch()}})}});function skipIfhasPrecedingDot(i,s){"."===i.input[i.index-1]&&s.ignoreMatch()}function beginKeywords(i,s){s&&i.beginKeywords&&(i.begin="\\b("+i.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",i.__beforeBegin=skipIfhasPrecedingDot,i.keywords=i.keywords||i.beginKeywords,delete i.beginKeywords,void 0===i.relevance&&(i.relevance=0))}function compileIllegal(i,s){Array.isArray(i.illegal)&&(i.illegal=function either(...i){return"("+i.map((i=>source(i))).join("|")+")"}(...i.illegal))}function compileMatch(i,s){if(i.match){if(i.begin||i.end)throw new Error("begin & end are not supported with match");i.begin=i.match,delete i.match}}function compileRelevance(i,s){void 0===i.relevance&&(i.relevance=1)}const Se=["of","and","for","in","not","or","if","then","parent","list","value"],xe="keyword";function compileKeywords(i,s,u=xe){const m={};return"string"==typeof i?compileList(u,i.split(" ")):Array.isArray(i)?compileList(u,i):Object.keys(i).forEach((function(u){Object.assign(m,compileKeywords(i[u],s,u))})),m;function compileList(i,u){s&&(u=u.map((i=>i.toLowerCase()))),u.forEach((function(s){const u=s.split("|");m[u[0]]=[i,scoreForKeyword(u[0],u[1])]}))}}function scoreForKeyword(i,s){return s?Number(s):function commonKeyword(i){return Se.includes(i.toLowerCase())}(i)?0:1}function compileLanguage(i,{plugins:s}){function langRe(s,u){return new RegExp(source(s),"m"+(i.case_insensitive?"i":"")+(u?"g":""))}class MultiRegex{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(i,s){s.position=this.position++,this.matchIndexes[this.matchAt]=s,this.regexes.push([s,i]),this.matchAt+=function countMatchGroups(i){return new RegExp(i.toString()+"|").exec("").length-1}(i)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const i=this.regexes.map((i=>i[1]));this.matcherRe=langRe(function join(i,s="|"){let u=0;return i.map((i=>{u+=1;const s=u;let v=source(i),_="";for(;v.length>0;){const i=m.exec(v);if(!i){_+=v;break}_+=v.substring(0,i.index),v=v.substring(i.index+i[0].length),"\\"===i[0][0]&&i[1]?_+="\\"+String(Number(i[1])+s):(_+=i[0],"("===i[0]&&u++)}return _})).map((i=>`(${i})`)).join(s)}(i),!0),this.lastIndex=0}exec(i){this.matcherRe.lastIndex=this.lastIndex;const s=this.matcherRe.exec(i);if(!s)return null;const u=s.findIndex(((i,s)=>s>0&&void 0!==i)),m=this.matchIndexes[u];return s.splice(0,u),Object.assign(s,m)}}class ResumableMultiRegex{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(i){if(this.multiRegexes[i])return this.multiRegexes[i];const s=new MultiRegex;return this.rules.slice(i).forEach((([i,u])=>s.addRule(i,u))),s.compile(),this.multiRegexes[i]=s,s}resumingScanAtSamePosition(){return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(i,s){this.rules.push([i,s]),"begin"===s.type&&this.count++}exec(i){const s=this.getMatcher(this.regexIndex);s.lastIndex=this.lastIndex;let u=s.exec(i);if(this.resumingScanAtSamePosition())if(u&&u.index===this.lastIndex);else{const s=this.getMatcher(0);s.lastIndex=this.lastIndex+1,u=s.exec(i)}return u&&(this.regexIndex+=u.position+1,this.regexIndex===this.count&&this.considerAll()),u}}if(i.compilerExtensions||(i.compilerExtensions=[]),i.contains&&i.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return i.classNameAliases=inherit(i.classNameAliases||{}),function compileMode(s,u){const m=s;if(s.isCompiled)return m;[compileMatch].forEach((i=>i(s,u))),i.compilerExtensions.forEach((i=>i(s,u))),s.__beforeBegin=null,[beginKeywords,compileIllegal,compileRelevance].forEach((i=>i(s,u))),s.isCompiled=!0;let v=null;if("object"==typeof s.keywords&&(v=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=compileKeywords(s.keywords,i.case_insensitive)),s.lexemes&&v)throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return v=v||s.lexemes||/\w+/,m.keywordPatternRe=langRe(v,!0),u&&(s.begin||(s.begin=/\B|\b/),m.beginRe=langRe(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(m.endRe=langRe(s.end)),m.terminatorEnd=source(s.end)||"",s.endsWithParent&&u.terminatorEnd&&(m.terminatorEnd+=(s.end?"|":"")+u.terminatorEnd)),s.illegal&&(m.illegalRe=langRe(s.illegal)),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(i){return function expandOrCloneMode(i){i.variants&&!i.cachedVariants&&(i.cachedVariants=i.variants.map((function(s){return inherit(i,{variants:null},s)})));if(i.cachedVariants)return i.cachedVariants;if(dependencyOnParent(i))return inherit(i,{starts:i.starts?inherit(i.starts):null});if(Object.isFrozen(i))return inherit(i);return i}("self"===i?s:i)}))),s.contains.forEach((function(i){compileMode(i,m)})),s.starts&&compileMode(s.starts,u),m.matcher=function buildModeRegex(i){const s=new ResumableMultiRegex;return i.contains.forEach((i=>s.addRule(i.begin,{rule:i,type:"begin"}))),i.terminatorEnd&&s.addRule(i.terminatorEnd,{type:"end"}),i.illegal&&s.addRule(i.illegal,{type:"illegal"}),s}(m),m}(i)}function dependencyOnParent(i){return!!i&&(i.endsWithParent||dependencyOnParent(i.starts))}function BuildVuePlugin(i){const s={props:["language","code","autodetect"],data:function(){return{detectedLanguage:"",unknownLanguage:!1}},computed:{className(){return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){if(!this.autoDetect&&!i.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),this.unknownLanguage=!0,escapeHTML(this.code);let s={};return this.autoDetect?(s=i.highlightAuto(this.code),this.detectedLanguage=s.language):(s=i.highlight(this.language,this.code,this.ignoreIllegals),this.detectedLanguage=this.language),s.value},autoDetect(){return!this.language||function hasValueOrEmptyAttribute(i){return Boolean(i||""===i)}(this.autodetect)},ignoreIllegals:()=>!0},render(i){return i("pre",{},[i("code",{class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{Component:s,VuePlugin:{install(i){i.component("highlightjs",s)}}}}const Pe={"after:highlightElement":({el:i,result:s,text:u})=>{const m=nodeStream(i);if(!m.length)return;const v=document.createElement("div");v.innerHTML=s.value,s.value=function mergeStreams(i,s,u){let m=0,v="";const _=[];function selectStream(){return i.length&&s.length?i[0].offset!==s[0].offset?i[0].offset"}function close(i){v+=""}function render(i){("start"===i.event?open:close)(i.node)}for(;i.length||s.length;){let s=selectStream();if(v+=escapeHTML(u.substring(m,s[0].offset)),m=s[0].offset,s===i){_.reverse().forEach(close);do{render(s.splice(0,1)[0]),s=selectStream()}while(s===i&&s.length&&s[0].offset===m);_.reverse().forEach(open)}else"start"===s[0].event?_.push(s[0].node):_.pop(),render(s.splice(0,1)[0])}return v+escapeHTML(u.substr(m))}(m,nodeStream(v),u)}};function tag(i){return i.nodeName.toLowerCase()}function nodeStream(i){const s=[];return function _nodeStream(i,u){for(let m=i.firstChild;m;m=m.nextSibling)3===m.nodeType?u+=m.nodeValue.length:1===m.nodeType&&(s.push({event:"start",offset:u,node:m}),u=_nodeStream(m,u),tag(m).match(/br|hr|img|input/)||s.push({event:"stop",offset:u,node:m}));return u}(i,0),s}const Ie={},error=i=>{console.error(i)},warn=(i,...s)=>{console.log(`WARN: ${i}`,...s)},deprecated=(i,s)=>{Ie[`${i}/${s}`]||(console.log(`Deprecated as of ${i}. ${s}`),Ie[`${i}/${s}`]=!0)},Te=escapeHTML,Re=inherit,qe=Symbol("nomatch");var ze=function(i){const u=Object.create(null),m=Object.create(null),v=[];let _=!0;const j=/(^(<[^>]+>|\t|)+|\n)/gm,M="Could not find the language '{}', did you forget to load/include a language module?",$={disableAutodetect:!0,name:"Plain text",contains:[]};let W={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:TokenTreeEmitter};function shouldNotHighlight(i){return W.noHighlightRe.test(i)}function highlight(i,s,u,m){let v="",_="";"object"==typeof s?(v=i,u=s.ignoreIllegals,_=s.language,m=void 0):(deprecated("10.7.0","highlight(lang, code, ...args) has been deprecated."),deprecated("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),_=i,v=s);const j={code:v,language:_};fire("before:highlight",j);const M=j.result?j.result:_highlight(j.language,j.code,u,m);return M.code=j.code,fire("after:highlight",M),M}function _highlight(i,s,m,j){function keywordData(i,s){const u=X.case_insensitive?s[0].toLowerCase():s[0];return Object.prototype.hasOwnProperty.call(i.keywords,u)&&i.keywords[u]}function processBuffer(){null!=ee.subLanguage?function processSubLanguage(){if(""===le)return;let i=null;if("string"==typeof ee.subLanguage){if(!u[ee.subLanguage])return void ie.addText(le);i=_highlight(ee.subLanguage,le,!0,ae[ee.subLanguage]),ae[ee.subLanguage]=i.top}else i=highlightAuto(le,ee.subLanguage.length?ee.subLanguage:null);ee.relevance>0&&(ce+=i.relevance),ie.addSublanguage(i.emitter,i.language)}():function processKeywords(){if(!ee.keywords)return void ie.addText(le);let i=0;ee.keywordPatternRe.lastIndex=0;let s=ee.keywordPatternRe.exec(le),u="";for(;s;){u+=le.substring(i,s.index);const m=keywordData(ee,s);if(m){const[i,v]=m;if(ie.addText(u),u="",ce+=v,i.startsWith("_"))u+=s[0];else{const u=X.classNameAliases[i]||i;ie.addKeyword(s[0],u)}}else u+=s[0];i=ee.keywordPatternRe.lastIndex,s=ee.keywordPatternRe.exec(le)}u+=le.substr(i),ie.addText(u)}(),le=""}function startNewMode(i){return i.className&&ie.openNode(X.classNameAliases[i.className]||i.className),ee=Object.create(i,{parent:{value:ee}}),ee}function endOfMode(i,s,u){let m=function startsWith(i,s){const u=i&&i.exec(s);return u&&0===u.index}(i.endRe,u);if(m){if(i["on:end"]){const u=new Response(i);i["on:end"](s,u),u.isMatchIgnored&&(m=!1)}if(m){for(;i.endsParent&&i.parent;)i=i.parent;return i}}if(i.endsWithParent)return endOfMode(i.parent,s,u)}function doIgnore(i){return 0===ee.matcher.regexIndex?(le+=i[0],1):(fe=!0,0)}function doBeginMatch(i){const s=i[0],u=i.rule,m=new Response(u),v=[u.__beforeBegin,u["on:begin"]];for(const u of v)if(u&&(u(i,m),m.isMatchIgnored))return doIgnore(s);return u&&u.endSameAsBegin&&(u.endRe=function escape(i){return new RegExp(i.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")}(s)),u.skip?le+=s:(u.excludeBegin&&(le+=s),processBuffer(),u.returnBegin||u.excludeBegin||(le=s)),startNewMode(u),u.returnBegin?0:s.length}function doEndMatch(i){const u=i[0],m=s.substr(i.index),v=endOfMode(ee,i,m);if(!v)return qe;const _=ee;_.skip?le+=u:(_.returnEnd||_.excludeEnd||(le+=u),processBuffer(),_.excludeEnd&&(le=u));do{ee.className&&ie.closeNode(),ee.skip||ee.subLanguage||(ce+=ee.relevance),ee=ee.parent}while(ee!==v.parent);return v.starts&&(v.endSameAsBegin&&(v.starts.endRe=v.endRe),startNewMode(v.starts)),_.returnEnd?0:u.length}let $={};function processLexeme(u,v){const j=v&&v[0];if(le+=u,null==j)return processBuffer(),0;if("begin"===$.type&&"end"===v.type&&$.index===v.index&&""===j){if(le+=s.slice(v.index,v.index+1),!_){const s=new Error("0 width match regex");throw s.languageName=i,s.badRule=$.rule,s}return 1}if($=v,"begin"===v.type)return doBeginMatch(v);if("illegal"===v.type&&!m){const i=new Error('Illegal lexeme "'+j+'" for mode "'+(ee.className||"")+'"');throw i.mode=ee,i}if("end"===v.type){const i=doEndMatch(v);if(i!==qe)return i}if("illegal"===v.type&&""===j)return 1;if(de>1e5&&de>3*v.index){throw new Error("potential infinite loop, way more iterations than matches")}return le+=j,j.length}const X=getLanguage(i);if(!X)throw error(M.replace("{}",i)),new Error('Unknown language: "'+i+'"');const Y=compileLanguage(X,{plugins:v});let Z="",ee=j||Y;const ae={},ie=new W.__emitter(W);!function processContinuations(){const i=[];for(let s=ee;s!==X;s=s.parent)s.className&&i.unshift(s.className);i.forEach((i=>ie.openNode(i)))}();let le="",ce=0,pe=0,de=0,fe=!1;try{for(ee.matcher.considerAll();;){de++,fe?fe=!1:ee.matcher.considerAll(),ee.matcher.lastIndex=pe;const i=ee.matcher.exec(s);if(!i)break;const u=processLexeme(s.substring(pe,i.index),i);pe=i.index+u}return processLexeme(s.substr(pe)),ie.closeAllNodes(),ie.finalize(),Z=ie.toHTML(),{relevance:Math.floor(ce),value:Z,language:i,illegal:!1,emitter:ie,top:ee}}catch(u){if(u.message&&u.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:u.message,context:s.slice(pe-100,pe+100),mode:u.mode},sofar:Z,relevance:0,value:Te(s),emitter:ie};if(_)return{illegal:!1,relevance:0,value:Te(s),emitter:ie,language:i,top:ee,errorRaised:u};throw u}}function highlightAuto(i,s){s=s||W.languages||Object.keys(u);const m=function justTextHighlightResult(i){const s={relevance:0,emitter:new W.__emitter(W),value:Te(i),illegal:!1,top:$};return s.emitter.addText(i),s}(i),v=s.filter(getLanguage).filter(autoDetection).map((s=>_highlight(s,i,!1)));v.unshift(m);const _=v.sort(((i,s)=>{if(i.relevance!==s.relevance)return s.relevance-i.relevance;if(i.language&&s.language){if(getLanguage(i.language).supersetOf===s.language)return 1;if(getLanguage(s.language).supersetOf===i.language)return-1}return 0})),[j,M]=_,X=j;return X.second_best=M,X}const X={"before:highlightElement":({el:i})=>{W.useBR&&(i.innerHTML=i.innerHTML.replace(/\n/g,"").replace(//g,"\n"))},"after:highlightElement":({result:i})=>{W.useBR&&(i.value=i.value.replace(/\n/g,"
"))}},Y=/^(<[^>]+>|\t)+/gm,Z={"after:highlightElement":({result:i})=>{W.tabReplace&&(i.value=i.value.replace(Y,(i=>i.replace(/\t/g,W.tabReplace))))}};function highlightElement(i){let s=null;const u=function blockLanguage(i){let s=i.className+" ";s+=i.parentNode?i.parentNode.className:"";const u=W.languageDetectRe.exec(s);if(u){const s=getLanguage(u[1]);return s||(warn(M.replace("{}",u[1])),warn("Falling back to no-highlight mode for this block.",i)),s?u[1]:"no-highlight"}return s.split(/\s+/).find((i=>shouldNotHighlight(i)||getLanguage(i)))}(i);if(shouldNotHighlight(u))return;fire("before:highlightElement",{el:i,language:u}),s=i;const v=s.textContent,_=u?highlight(v,{language:u,ignoreIllegals:!0}):highlightAuto(v);fire("after:highlightElement",{el:i,result:_,text:v}),i.innerHTML=_.value,function updateClassName(i,s,u){const v=s?m[s]:u;i.classList.add("hljs"),v&&i.classList.add(v)}(i,u,_.language),i.result={language:_.language,re:_.relevance,relavance:_.relevance},_.second_best&&(i.second_best={language:_.second_best.language,re:_.second_best.relevance,relavance:_.second_best.relevance})}const initHighlighting=()=>{if(initHighlighting.called)return;initHighlighting.called=!0,deprecated("10.6.0","initHighlighting() is deprecated. Use highlightAll() instead.");document.querySelectorAll("pre code").forEach(highlightElement)};let ee=!1;function highlightAll(){if("loading"===document.readyState)return void(ee=!0);document.querySelectorAll("pre code").forEach(highlightElement)}function getLanguage(i){return i=(i||"").toLowerCase(),u[i]||u[m[i]]}function registerAliases(i,{languageName:s}){"string"==typeof i&&(i=[i]),i.forEach((i=>{m[i.toLowerCase()]=s}))}function autoDetection(i){const s=getLanguage(i);return s&&!s.disableAutodetect}function fire(i,s){const u=i;v.forEach((function(i){i[u]&&i[u](s)}))}"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(function boot(){ee&&highlightAll()}),!1),Object.assign(i,{highlight,highlightAuto,highlightAll,fixMarkup:function deprecateFixMarkup(i){return deprecated("10.2.0","fixMarkup will be removed entirely in v11.0"),deprecated("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"),function fixMarkup(i){return W.tabReplace||W.useBR?i.replace(j,(i=>"\n"===i?W.useBR?"
":i:W.tabReplace?i.replace(/\t/g,W.tabReplace):i)):i}(i)},highlightElement,highlightBlock:function deprecateHighlightBlock(i){return deprecated("10.7.0","highlightBlock will be removed entirely in v12.0"),deprecated("10.7.0","Please use highlightElement now."),highlightElement(i)},configure:function configure(i){i.useBR&&(deprecated("10.3.0","'useBR' will be removed entirely in v11.0"),deprecated("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")),W=Re(W,i)},initHighlighting,initHighlightingOnLoad:function initHighlightingOnLoad(){deprecated("10.6.0","initHighlightingOnLoad() is deprecated. Use highlightAll() instead."),ee=!0},registerLanguage:function registerLanguage(s,m){let v=null;try{v=m(i)}catch(i){if(error("Language definition for '{}' could not be registered.".replace("{}",s)),!_)throw i;error(i),v=$}v.name||(v.name=s),u[s]=v,v.rawDefinition=m.bind(null,i),v.aliases&®isterAliases(v.aliases,{languageName:s})},unregisterLanguage:function unregisterLanguage(i){delete u[i];for(const s of Object.keys(m))m[s]===i&&delete m[s]},listLanguages:function listLanguages(){return Object.keys(u)},getLanguage,registerAliases,requireLanguage:function requireLanguage(i){deprecated("10.4.0","requireLanguage will be removed entirely in v11."),deprecated("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844");const s=getLanguage(i);if(s)return s;throw new Error("The '{}' language is required, but not loaded.".replace("{}",i))},autoDetection,inherit:Re,addPlugin:function addPlugin(i){!function upgradePluginAPI(i){i["before:highlightBlock"]&&!i["before:highlightElement"]&&(i["before:highlightElement"]=s=>{i["before:highlightBlock"](Object.assign({block:s.el},s))}),i["after:highlightBlock"]&&!i["after:highlightElement"]&&(i["after:highlightElement"]=s=>{i["after:highlightBlock"](Object.assign({block:s.el},s))})}(i),v.push(i)},vuePlugin:BuildVuePlugin(i).VuePlugin}),i.debugMode=function(){_=!1},i.safeMode=function(){_=!0},i.versionString="10.7.3";for(const i in we)"object"==typeof we[i]&&s(we[i]);return Object.assign(i,we),i.addPlugin(X),i.addPlugin(Pe),i.addPlugin(Z),i}({});i.exports=ze},61519:i=>{function concat(...i){return i.map((i=>function source(i){return i?"string"==typeof i?i:i.source:null}(i))).join("")}i.exports=function bash(i){const s={},u={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[s]}]};Object.assign(s,{className:"variable",variants:[{begin:concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},u]});const m={className:"subst",begin:/\$\(/,end:/\)/,contains:[i.BACKSLASH_ESCAPE]},v={begin:/<<-?\s*(?=\w+)/,starts:{contains:[i.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},_={className:"string",begin:/"/,end:/"/,contains:[i.BACKSLASH_ESCAPE,s,m]};m.contains.push(_);const j={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},i.NUMBER_MODE,s]},M=i.SHEBANG({binary:`(${["fish","bash","zsh","sh","csh","ksh","tcsh","dash","scsh"].join("|")})`,relevance:10}),$={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[i.inherit(i.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z._-]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp"},contains:[M,i.SHEBANG(),$,j,i.HASH_COMMENT_MODE,v,_,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}},30786:i=>{function concat(...i){return i.map((i=>function source(i){return i?"string"==typeof i?i:i.source:null}(i))).join("")}i.exports=function http(i){const s="HTTP/(2|1\\.[01])",u={className:"attribute",begin:concat("^",/[A-Za-z][A-Za-z0-9-]*/,"(?=\\:\\s)"),starts:{contains:[{className:"punctuation",begin:/: /,relevance:0,starts:{end:"$",relevance:0}}]}},m=[u,{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}];return{name:"HTTP",aliases:["https"],illegal:/\S/,contains:[{begin:"^(?="+s+" \\d{3})",end:/$/,contains:[{className:"meta",begin:s},{className:"number",begin:"\\b\\d{3}\\b"}],starts:{end:/\b\B/,illegal:/\S/,contains:m}},{begin:"(?=^[A-Z]+ (.*?) "+s+"$)",end:/$/,contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{className:"meta",begin:s},{className:"keyword",begin:"[A-Z]+"}],starts:{end:/\b\B/,illegal:/\S/,contains:m}},i.inherit(u,{relevance:0})]}}},96344:i=>{const s="[A-Za-z$_][0-9A-Za-z$_]*",u=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],m=["true","false","null","undefined","NaN","Infinity"],v=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function lookahead(i){return concat("(?=",i,")")}function concat(...i){return i.map((i=>function source(i){return i?"string"==typeof i?i:i.source:null}(i))).join("")}i.exports=function javascript(i){const _=s,j="<>",M="",$={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(i,s)=>{const u=i[0].length+i.index,m=i.input[u];"<"!==m?">"===m&&(((i,{after:s})=>{const u="",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:i.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:W,contains:ye}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:j,end:M},{begin:$.begin,"on:begin":$.isTrulyOpeningTag,end:$.end}],subLanguage:"xml",contains:[{begin:$.begin,end:$.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:W,contains:["self",i.inherit(i.TITLE_MODE,{begin:_}),be],illegal:/%/},{beginKeywords:"while if switch catch for"},{className:"function",begin:i.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,contains:[be,i.inherit(i.TITLE_MODE,{begin:_})]},{variants:[{begin:"\\."+_},{begin:"\\$"+_}],relevance:0},{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{beginKeywords:"extends"},i.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/,end:/[{;]/,excludeEnd:!0,contains:[i.inherit(i.TITLE_MODE,{begin:_}),"self",be]},{begin:"(get|set)\\s+(?="+_+"\\()",end:/\{/,keywords:"get set",contains:[i.inherit(i.TITLE_MODE,{begin:_}),{begin:/\(\)/},be]},{begin:/\$[(.]/}]}}},82026:i=>{i.exports=function json(i){const s={literal:"true false null"},u=[i.C_LINE_COMMENT_MODE,i.C_BLOCK_COMMENT_MODE],m=[i.QUOTE_STRING_MODE,i.C_NUMBER_MODE],v={end:",",endsWithParent:!0,excludeEnd:!0,contains:m,keywords:s},_={begin:/\{/,end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/,contains:[i.BACKSLASH_ESCAPE],illegal:"\\n"},i.inherit(v,{begin:/:/})].concat(u),illegal:"\\S"},j={begin:"\\[",end:"\\]",contains:[i.inherit(v)],illegal:"\\S"};return m.push(_,j),u.forEach((function(i){m.push(i)})),{name:"JSON",contains:m,keywords:s,illegal:"\\S"}}},66336:i=>{i.exports=function powershell(i){const s={$pattern:/-?[A-z\.\-]+\b/,keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter",built_in:"ac asnp cat cd CFS chdir clc clear clhy cli clp cls clv cnsn compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo|0 epal epcsv epsn erase etsn exsn fc fhx fl ft fw gal gbp gc gcb gci gcm gcs gdr gerr ghy gi gin gjb gl gm gmo gp gps gpv group gsn gsnp gsv gtz gu gv gwmi h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc scb select set shcm si sl sleep sls sort sp spjb spps spsv start stz sujb sv swmi tee trcm type wget where wjb write"},u={begin:"`[\\s\\S]",relevance:0},m={className:"variable",variants:[{begin:/\$\B/},{className:"keyword",begin:/\$this/},{begin:/\$[\w\d][\w\d_:]*/}]},v={className:"string",variants:[{begin:/"/,end:/"/},{begin:/@"/,end:/^"@/}],contains:[u,m,{className:"variable",begin:/\$[A-z]/,end:/[^A-z]/}]},_={className:"string",variants:[{begin:/'/,end:/'/},{begin:/@'/,end:/^'@/}]},j=i.inherit(i.COMMENT(null,null),{variants:[{begin:/#/,end:/$/},{begin:/<#/,end:/#>/}],contains:[{className:"doctag",variants:[{begin:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/},{begin:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/}]}]}),M={className:"built_in",variants:[{begin:"(".concat("Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|Mount|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Build|Complete|Confirm|Deny|Deploy|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where",")+(-)[\\w\\d]+")}]},$={className:"class",beginKeywords:"class enum",end:/\s*[{]/,excludeEnd:!0,relevance:0,contains:[i.TITLE_MODE]},W={className:"function",begin:/function\s+/,end:/\s*\{|$/,excludeEnd:!0,returnBegin:!0,relevance:0,contains:[{begin:"function",relevance:0,className:"keyword"},{className:"title",begin:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{begin:/\(/,end:/\)/,className:"params",relevance:0,contains:[m]}]},X={begin:/using\s/,end:/$/,returnBegin:!0,contains:[v,_,{className:"keyword",begin:/(using|assembly|command|module|namespace|type)/}]},Y={variants:[{className:"operator",begin:"(".concat("-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor",")\\b")},{className:"literal",begin:/(-)[\w\d]+/,relevance:0}]},Z={className:"function",begin:/\[.*\]\s*[\w]+[ ]??\(/,end:/$/,returnBegin:!0,relevance:0,contains:[{className:"keyword",begin:"(".concat(s.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,relevance:0},i.inherit(i.TITLE_MODE,{endsParent:!0})]},ee=[Z,j,u,i.NUMBER_MODE,v,_,M,m,{className:"literal",begin:/\$(null|true|false)\b/},{className:"selector-tag",begin:/@\B/,relevance:0}],ae={begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[].concat("self",ee,{begin:"("+["string","char","byte","int","long","bool","decimal","single","double","DateTime","xml","array","hashtable","void"].join("|")+")",className:"built_in",relevance:0},{className:"type",begin:/[\.\w\d]+/,relevance:0})};return Z.contains.unshift(ae),{name:"PowerShell",aliases:["ps","ps1"],case_insensitive:!0,keywords:s,contains:ee.concat($,W,X,Y,ae)}}},42157:i=>{function source(i){return i?"string"==typeof i?i:i.source:null}function lookahead(i){return concat("(?=",i,")")}function concat(...i){return i.map((i=>source(i))).join("")}function either(...i){return"("+i.map((i=>source(i))).join("|")+")"}i.exports=function xml(i){const s=concat(/[A-Z_]/,function optional(i){return concat("(",i,")?")}(/[A-Z0-9_.-]*:/),/[A-Z0-9_.-]*/),u={className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},m={begin:/\s/,contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},v=i.inherit(m,{begin:/\(/,end:/\)/}),_=i.inherit(i.APOS_STRING_MODE,{className:"meta-string"}),j=i.inherit(i.QUOTE_STRING_MODE,{className:"meta-string"}),M={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin://,relevance:10,contains:[m,j,_,v,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[m,v,j,_]}]}]},i.COMMENT(//,{relevance:10}),{begin://,relevance:10},u,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:/)/,end:/>/,keywords:{name:"style"},contains:[M],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:/)/,end:/>/,keywords:{name:"script"},contains:[M],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:/<>|<\/>/},{className:"tag",begin:concat(//,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",begin:s,relevance:0,starts:M}]},{className:"tag",begin:concat(/<\//,lookahead(concat(s,/>/))),contains:[{className:"name",begin:s,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}},54587:i=>{i.exports=function yaml(i){var s="true false yes no null",u="[\\w#;/?:@&=+$,.~*'()[\\]]+",m={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[i.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},v=i.inherit(m,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),_={className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},j={end:",",endsWithParent:!0,excludeEnd:!0,keywords:s,relevance:0},M={begin:/\{/,end:/\}/,contains:[j],illegal:"\\n",relevance:0},$={begin:"\\[",end:"\\]",contains:[j],illegal:"\\n",relevance:0},W=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$",relevance:10},{className:"string",begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+u},{className:"type",begin:"!<"+u+">"},{className:"type",begin:"!"+u},{className:"type",begin:"!!"+u},{className:"meta",begin:"&"+i.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+i.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",relevance:0},i.HASH_COMMENT_MODE,{beginKeywords:s,keywords:{literal:s}},_,{className:"number",begin:i.C_NUMBER_RE+"\\b",relevance:0},M,$,m],X=[...W];return X.pop(),X.push(v),j.contains=X,{name:"YAML",case_insensitive:!0,aliases:["yml"],contains:W}}},8679:(i,s,u)=>{"use strict";var m=u(59864),v={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},_={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},j={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},M={};function getStatics(i){return m.isMemo(i)?j:M[i.$$typeof]||v}M[m.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},M[m.Memo]=j;var $=Object.defineProperty,W=Object.getOwnPropertyNames,X=Object.getOwnPropertySymbols,Y=Object.getOwnPropertyDescriptor,Z=Object.getPrototypeOf,ee=Object.prototype;i.exports=function hoistNonReactStatics(i,s,u){if("string"!=typeof s){if(ee){var m=Z(s);m&&m!==ee&&hoistNonReactStatics(i,m,u)}var v=W(s);X&&(v=v.concat(X(s)));for(var j=getStatics(i),M=getStatics(s),ae=0;ae{s.read=function(i,s,u,m,v){var _,j,M=8*v-m-1,$=(1<>1,X=-7,Y=u?v-1:0,Z=u?-1:1,ee=i[s+Y];for(Y+=Z,_=ee&(1<<-X)-1,ee>>=-X,X+=M;X>0;_=256*_+i[s+Y],Y+=Z,X-=8);for(j=_&(1<<-X)-1,_>>=-X,X+=m;X>0;j=256*j+i[s+Y],Y+=Z,X-=8);if(0===_)_=1-W;else{if(_===$)return j?NaN:1/0*(ee?-1:1);j+=Math.pow(2,m),_-=W}return(ee?-1:1)*j*Math.pow(2,_-m)},s.write=function(i,s,u,m,v,_){var j,M,$,W=8*_-v-1,X=(1<>1,Z=23===v?Math.pow(2,-24)-Math.pow(2,-77):0,ee=m?0:_-1,ae=m?1:-1,ie=s<0||0===s&&1/s<0?1:0;for(s=Math.abs(s),isNaN(s)||s===1/0?(M=isNaN(s)?1:0,j=X):(j=Math.floor(Math.log(s)/Math.LN2),s*($=Math.pow(2,-j))<1&&(j--,$*=2),(s+=j+Y>=1?Z/$:Z*Math.pow(2,1-Y))*$>=2&&(j++,$/=2),j+Y>=X?(M=0,j=X):j+Y>=1?(M=(s*$-1)*Math.pow(2,v),j+=Y):(M=s*Math.pow(2,Y-1)*Math.pow(2,v),j=0));v>=8;i[u+ee]=255&M,ee+=ae,M/=256,v-=8);for(j=j<0;i[u+ee]=255&j,ee+=ae,j/=256,W-=8);i[u+ee-ae]|=128*ie}},43393:function(i){i.exports=function(){"use strict";var i=Array.prototype.slice;function createClass(i,s){s&&(i.prototype=Object.create(s.prototype)),i.prototype.constructor=i}function Iterable(i){return isIterable(i)?i:Seq(i)}function KeyedIterable(i){return isKeyed(i)?i:KeyedSeq(i)}function IndexedIterable(i){return isIndexed(i)?i:IndexedSeq(i)}function SetIterable(i){return isIterable(i)&&!isAssociative(i)?i:SetSeq(i)}function isIterable(i){return!(!i||!i[s])}function isKeyed(i){return!(!i||!i[u])}function isIndexed(i){return!(!i||!i[m])}function isAssociative(i){return isKeyed(i)||isIndexed(i)}function isOrdered(i){return!(!i||!i[v])}createClass(KeyedIterable,Iterable),createClass(IndexedIterable,Iterable),createClass(SetIterable,Iterable),Iterable.isIterable=isIterable,Iterable.isKeyed=isKeyed,Iterable.isIndexed=isIndexed,Iterable.isAssociative=isAssociative,Iterable.isOrdered=isOrdered,Iterable.Keyed=KeyedIterable,Iterable.Indexed=IndexedIterable,Iterable.Set=SetIterable;var s="@@__IMMUTABLE_ITERABLE__@@",u="@@__IMMUTABLE_KEYED__@@",m="@@__IMMUTABLE_INDEXED__@@",v="@@__IMMUTABLE_ORDERED__@@",_="delete",j=5,M=1<>>0;if(""+u!==s||4294967295===u)return NaN;s=u}return s<0?ensureSize(i)+s:s}function returnTrue(){return!0}function wholeSlice(i,s,u){return(0===i||void 0!==u&&i<=-u)&&(void 0===s||void 0!==u&&s>=u)}function resolveBegin(i,s){return resolveIndex(i,s,0)}function resolveEnd(i,s){return resolveIndex(i,s,s)}function resolveIndex(i,s,u){return void 0===i?u:i<0?Math.max(0,s+i):void 0===s?i:Math.min(s,i)}var Z=0,ee=1,ae=2,ie="function"==typeof Symbol&&Symbol.iterator,le="@@iterator",ce=ie||le;function Iterator(i){this.next=i}function iteratorValue(i,s,u,m){var v=0===i?s:1===i?u:[s,u];return m?m.value=v:m={value:v,done:!1},m}function iteratorDone(){return{value:void 0,done:!0}}function hasIterator(i){return!!getIteratorFn(i)}function isIterator(i){return i&&"function"==typeof i.next}function getIterator(i){var s=getIteratorFn(i);return s&&s.call(i)}function getIteratorFn(i){var s=i&&(ie&&i[ie]||i[le]);if("function"==typeof s)return s}function isArrayLike(i){return i&&"number"==typeof i.length}function Seq(i){return null==i?emptySequence():isIterable(i)?i.toSeq():seqFromValue(i)}function KeyedSeq(i){return null==i?emptySequence().toKeyedSeq():isIterable(i)?isKeyed(i)?i.toSeq():i.fromEntrySeq():keyedSeqFromValue(i)}function IndexedSeq(i){return null==i?emptySequence():isIterable(i)?isKeyed(i)?i.entrySeq():i.toIndexedSeq():indexedSeqFromValue(i)}function SetSeq(i){return(null==i?emptySequence():isIterable(i)?isKeyed(i)?i.entrySeq():i:indexedSeqFromValue(i)).toSetSeq()}Iterator.prototype.toString=function(){return"[Iterator]"},Iterator.KEYS=Z,Iterator.VALUES=ee,Iterator.ENTRIES=ae,Iterator.prototype.inspect=Iterator.prototype.toSource=function(){return this.toString()},Iterator.prototype[ce]=function(){return this},createClass(Seq,Iterable),Seq.of=function(){return Seq(arguments)},Seq.prototype.toSeq=function(){return this},Seq.prototype.toString=function(){return this.__toString("Seq {","}")},Seq.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},Seq.prototype.__iterate=function(i,s){return seqIterate(this,i,s,!0)},Seq.prototype.__iterator=function(i,s){return seqIterator(this,i,s,!0)},createClass(KeyedSeq,Seq),KeyedSeq.prototype.toKeyedSeq=function(){return this},createClass(IndexedSeq,Seq),IndexedSeq.of=function(){return IndexedSeq(arguments)},IndexedSeq.prototype.toIndexedSeq=function(){return this},IndexedSeq.prototype.toString=function(){return this.__toString("Seq [","]")},IndexedSeq.prototype.__iterate=function(i,s){return seqIterate(this,i,s,!1)},IndexedSeq.prototype.__iterator=function(i,s){return seqIterator(this,i,s,!1)},createClass(SetSeq,Seq),SetSeq.of=function(){return SetSeq(arguments)},SetSeq.prototype.toSetSeq=function(){return this},Seq.isSeq=isSeq,Seq.Keyed=KeyedSeq,Seq.Set=SetSeq,Seq.Indexed=IndexedSeq;var pe,de,fe,ye="@@__IMMUTABLE_SEQ__@@";function ArraySeq(i){this._array=i,this.size=i.length}function ObjectSeq(i){var s=Object.keys(i);this._object=i,this._keys=s,this.size=s.length}function IterableSeq(i){this._iterable=i,this.size=i.length||i.size}function IteratorSeq(i){this._iterator=i,this._iteratorCache=[]}function isSeq(i){return!(!i||!i[ye])}function emptySequence(){return pe||(pe=new ArraySeq([]))}function keyedSeqFromValue(i){var s=Array.isArray(i)?new ArraySeq(i).fromEntrySeq():isIterator(i)?new IteratorSeq(i).fromEntrySeq():hasIterator(i)?new IterableSeq(i).fromEntrySeq():"object"==typeof i?new ObjectSeq(i):void 0;if(!s)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+i);return s}function indexedSeqFromValue(i){var s=maybeIndexedSeqFromValue(i);if(!s)throw new TypeError("Expected Array or iterable object of values: "+i);return s}function seqFromValue(i){var s=maybeIndexedSeqFromValue(i)||"object"==typeof i&&new ObjectSeq(i);if(!s)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+i);return s}function maybeIndexedSeqFromValue(i){return isArrayLike(i)?new ArraySeq(i):isIterator(i)?new IteratorSeq(i):hasIterator(i)?new IterableSeq(i):void 0}function seqIterate(i,s,u,m){var v=i._cache;if(v){for(var _=v.length-1,j=0;j<=_;j++){var M=v[u?_-j:j];if(!1===s(M[1],m?M[0]:j,i))return j+1}return j}return i.__iterateUncached(s,u)}function seqIterator(i,s,u,m){var v=i._cache;if(v){var _=v.length-1,j=0;return new Iterator((function(){var i=v[u?_-j:j];return j++>_?iteratorDone():iteratorValue(s,m?i[0]:j-1,i[1])}))}return i.__iteratorUncached(s,u)}function fromJS(i,s){return s?fromJSWith(s,i,"",{"":i}):fromJSDefault(i)}function fromJSWith(i,s,u,m){return Array.isArray(s)?i.call(m,u,IndexedSeq(s).map((function(u,m){return fromJSWith(i,u,m,s)}))):isPlainObj(s)?i.call(m,u,KeyedSeq(s).map((function(u,m){return fromJSWith(i,u,m,s)}))):s}function fromJSDefault(i){return Array.isArray(i)?IndexedSeq(i).map(fromJSDefault).toList():isPlainObj(i)?KeyedSeq(i).map(fromJSDefault).toMap():i}function isPlainObj(i){return i&&(i.constructor===Object||void 0===i.constructor)}function is(i,s){if(i===s||i!=i&&s!=s)return!0;if(!i||!s)return!1;if("function"==typeof i.valueOf&&"function"==typeof s.valueOf){if((i=i.valueOf())===(s=s.valueOf())||i!=i&&s!=s)return!0;if(!i||!s)return!1}return!("function"!=typeof i.equals||"function"!=typeof s.equals||!i.equals(s))}function deepEqual(i,s){if(i===s)return!0;if(!isIterable(s)||void 0!==i.size&&void 0!==s.size&&i.size!==s.size||void 0!==i.__hash&&void 0!==s.__hash&&i.__hash!==s.__hash||isKeyed(i)!==isKeyed(s)||isIndexed(i)!==isIndexed(s)||isOrdered(i)!==isOrdered(s))return!1;if(0===i.size&&0===s.size)return!0;var u=!isAssociative(i);if(isOrdered(i)){var m=i.entries();return s.every((function(i,s){var v=m.next().value;return v&&is(v[1],i)&&(u||is(v[0],s))}))&&m.next().done}var v=!1;if(void 0===i.size)if(void 0===s.size)"function"==typeof i.cacheResult&&i.cacheResult();else{v=!0;var _=i;i=s,s=_}var j=!0,M=s.__iterate((function(s,m){if(u?!i.has(s):v?!is(s,i.get(m,W)):!is(i.get(m,W),s))return j=!1,!1}));return j&&i.size===M}function Repeat(i,s){if(!(this instanceof Repeat))return new Repeat(i,s);if(this._value=i,this.size=void 0===s?1/0:Math.max(0,s),0===this.size){if(de)return de;de=this}}function invariant(i,s){if(!i)throw new Error(s)}function Range(i,s,u){if(!(this instanceof Range))return new Range(i,s,u);if(invariant(0!==u,"Cannot step a Range by 0"),i=i||0,void 0===s&&(s=1/0),u=void 0===u?1:Math.abs(u),sm?iteratorDone():iteratorValue(i,v,u[s?m-v++:v++])}))},createClass(ObjectSeq,KeyedSeq),ObjectSeq.prototype.get=function(i,s){return void 0===s||this.has(i)?this._object[i]:s},ObjectSeq.prototype.has=function(i){return this._object.hasOwnProperty(i)},ObjectSeq.prototype.__iterate=function(i,s){for(var u=this._object,m=this._keys,v=m.length-1,_=0;_<=v;_++){var j=m[s?v-_:_];if(!1===i(u[j],j,this))return _+1}return _},ObjectSeq.prototype.__iterator=function(i,s){var u=this._object,m=this._keys,v=m.length-1,_=0;return new Iterator((function(){var j=m[s?v-_:_];return _++>v?iteratorDone():iteratorValue(i,j,u[j])}))},ObjectSeq.prototype[v]=!0,createClass(IterableSeq,IndexedSeq),IterableSeq.prototype.__iterateUncached=function(i,s){if(s)return this.cacheResult().__iterate(i,s);var u=getIterator(this._iterable),m=0;if(isIterator(u))for(var v;!(v=u.next()).done&&!1!==i(v.value,m++,this););return m},IterableSeq.prototype.__iteratorUncached=function(i,s){if(s)return this.cacheResult().__iterator(i,s);var u=getIterator(this._iterable);if(!isIterator(u))return new Iterator(iteratorDone);var m=0;return new Iterator((function(){var s=u.next();return s.done?s:iteratorValue(i,m++,s.value)}))},createClass(IteratorSeq,IndexedSeq),IteratorSeq.prototype.__iterateUncached=function(i,s){if(s)return this.cacheResult().__iterate(i,s);for(var u,m=this._iterator,v=this._iteratorCache,_=0;_=m.length){var s=u.next();if(s.done)return s;m[v]=s.value}return iteratorValue(i,v,m[v++])}))},createClass(Repeat,IndexedSeq),Repeat.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},Repeat.prototype.get=function(i,s){return this.has(i)?this._value:s},Repeat.prototype.includes=function(i){return is(this._value,i)},Repeat.prototype.slice=function(i,s){var u=this.size;return wholeSlice(i,s,u)?this:new Repeat(this._value,resolveEnd(s,u)-resolveBegin(i,u))},Repeat.prototype.reverse=function(){return this},Repeat.prototype.indexOf=function(i){return is(this._value,i)?0:-1},Repeat.prototype.lastIndexOf=function(i){return is(this._value,i)?this.size:-1},Repeat.prototype.__iterate=function(i,s){for(var u=0;u=0&&s=0&&uu?iteratorDone():iteratorValue(i,_++,j)}))},Range.prototype.equals=function(i){return i instanceof Range?this._start===i._start&&this._end===i._end&&this._step===i._step:deepEqual(this,i)},createClass(Collection,Iterable),createClass(KeyedCollection,Collection),createClass(IndexedCollection,Collection),createClass(SetCollection,Collection),Collection.Keyed=KeyedCollection,Collection.Indexed=IndexedCollection,Collection.Set=SetCollection;var be="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function imul(i,s){var u=65535&(i|=0),m=65535&(s|=0);return u*m+((i>>>16)*m+u*(s>>>16)<<16>>>0)|0};function smi(i){return i>>>1&1073741824|3221225471&i}function hash(i){if(!1===i||null==i)return 0;if("function"==typeof i.valueOf&&(!1===(i=i.valueOf())||null==i))return 0;if(!0===i)return 1;var s=typeof i;if("number"===s){if(i!=i||i===1/0)return 0;var u=0|i;for(u!==i&&(u^=4294967295*i);i>4294967295;)u^=i/=4294967295;return smi(u)}if("string"===s)return i.length>Te?cachedHashString(i):hashString(i);if("function"==typeof i.hashCode)return i.hashCode();if("object"===s)return hashJSObj(i);if("function"==typeof i.toString)return hashString(i.toString());throw new Error("Value type "+s+" cannot be hashed.")}function cachedHashString(i){var s=ze[i];return void 0===s&&(s=hashString(i),qe===Re&&(qe=0,ze={}),qe++,ze[i]=s),s}function hashString(i){for(var s=0,u=0;u0)switch(i.nodeType){case 1:return i.uniqueID;case 9:return i.documentElement&&i.documentElement.uniqueID}}var Se,xe="function"==typeof WeakMap;xe&&(Se=new WeakMap);var Pe=0,Ie="__immutablehash__";"function"==typeof Symbol&&(Ie=Symbol(Ie));var Te=16,Re=255,qe=0,ze={};function assertNotInfinite(i){invariant(i!==1/0,"Cannot perform this action with an infinite size.")}function Map(i){return null==i?emptyMap():isMap(i)&&!isOrdered(i)?i:emptyMap().withMutations((function(s){var u=KeyedIterable(i);assertNotInfinite(u.size),u.forEach((function(i,u){return s.set(u,i)}))}))}function isMap(i){return!(!i||!i[We])}createClass(Map,KeyedCollection),Map.of=function(){var s=i.call(arguments,0);return emptyMap().withMutations((function(i){for(var u=0;u=s.length)throw new Error("Missing value for key: "+s[u]);i.set(s[u],s[u+1])}}))},Map.prototype.toString=function(){return this.__toString("Map {","}")},Map.prototype.get=function(i,s){return this._root?this._root.get(0,void 0,i,s):s},Map.prototype.set=function(i,s){return updateMap(this,i,s)},Map.prototype.setIn=function(i,s){return this.updateIn(i,W,(function(){return s}))},Map.prototype.remove=function(i){return updateMap(this,i,W)},Map.prototype.deleteIn=function(i){return this.updateIn(i,(function(){return W}))},Map.prototype.update=function(i,s,u){return 1===arguments.length?i(this):this.updateIn([i],s,u)},Map.prototype.updateIn=function(i,s,u){u||(u=s,s=void 0);var m=updateInDeepMap(this,forceIterator(i),s,u);return m===W?void 0:m},Map.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):emptyMap()},Map.prototype.merge=function(){return mergeIntoMapWith(this,void 0,arguments)},Map.prototype.mergeWith=function(s){return mergeIntoMapWith(this,s,i.call(arguments,1))},Map.prototype.mergeIn=function(s){var u=i.call(arguments,1);return this.updateIn(s,emptyMap(),(function(i){return"function"==typeof i.merge?i.merge.apply(i,u):u[u.length-1]}))},Map.prototype.mergeDeep=function(){return mergeIntoMapWith(this,deepMerger,arguments)},Map.prototype.mergeDeepWith=function(s){var u=i.call(arguments,1);return mergeIntoMapWith(this,deepMergerWith(s),u)},Map.prototype.mergeDeepIn=function(s){var u=i.call(arguments,1);return this.updateIn(s,emptyMap(),(function(i){return"function"==typeof i.mergeDeep?i.mergeDeep.apply(i,u):u[u.length-1]}))},Map.prototype.sort=function(i){return OrderedMap(sortFactory(this,i))},Map.prototype.sortBy=function(i,s){return OrderedMap(sortFactory(this,s,i))},Map.prototype.withMutations=function(i){var s=this.asMutable();return i(s),s.wasAltered()?s.__ensureOwner(this.__ownerID):this},Map.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new OwnerID)},Map.prototype.asImmutable=function(){return this.__ensureOwner()},Map.prototype.wasAltered=function(){return this.__altered},Map.prototype.__iterator=function(i,s){return new MapIterator(this,i,s)},Map.prototype.__iterate=function(i,s){var u=this,m=0;return this._root&&this._root.iterate((function(s){return m++,i(s[1],s[0],u)}),s),m},Map.prototype.__ensureOwner=function(i){return i===this.__ownerID?this:i?makeMap(this.size,this._root,i,this.__hash):(this.__ownerID=i,this.__altered=!1,this)},Map.isMap=isMap;var Ve,We="@@__IMMUTABLE_MAP__@@",He=Map.prototype;function ArrayMapNode(i,s){this.ownerID=i,this.entries=s}function BitmapIndexedNode(i,s,u){this.ownerID=i,this.bitmap=s,this.nodes=u}function HashArrayMapNode(i,s,u){this.ownerID=i,this.count=s,this.nodes=u}function HashCollisionNode(i,s,u){this.ownerID=i,this.keyHash=s,this.entries=u}function ValueNode(i,s,u){this.ownerID=i,this.keyHash=s,this.entry=u}function MapIterator(i,s,u){this._type=s,this._reverse=u,this._stack=i._root&&mapIteratorFrame(i._root)}function mapIteratorValue(i,s){return iteratorValue(i,s[0],s[1])}function mapIteratorFrame(i,s){return{node:i,index:0,__prev:s}}function makeMap(i,s,u,m){var v=Object.create(He);return v.size=i,v._root=s,v.__ownerID=u,v.__hash=m,v.__altered=!1,v}function emptyMap(){return Ve||(Ve=makeMap(0))}function updateMap(i,s,u){var m,v;if(i._root){var _=MakeRef(X),j=MakeRef(Y);if(m=updateNode(i._root,i.__ownerID,0,void 0,s,u,_,j),!j.value)return i;v=i.size+(_.value?u===W?-1:1:0)}else{if(u===W)return i;v=1,m=new ArrayMapNode(i.__ownerID,[[s,u]])}return i.__ownerID?(i.size=v,i._root=m,i.__hash=void 0,i.__altered=!0,i):m?makeMap(v,m):emptyMap()}function updateNode(i,s,u,m,v,_,j,M){return i?i.update(s,u,m,v,_,j,M):_===W?i:(SetRef(M),SetRef(j),new ValueNode(s,m,[v,_]))}function isLeafNode(i){return i.constructor===ValueNode||i.constructor===HashCollisionNode}function mergeIntoNode(i,s,u,m,v){if(i.keyHash===m)return new HashCollisionNode(s,m,[i.entry,v]);var _,M=(0===u?i.keyHash:i.keyHash>>>u)&$,W=(0===u?m:m>>>u)&$;return new BitmapIndexedNode(s,1<>>=1)j[$]=1&u?s[_++]:void 0;return j[m]=v,new HashArrayMapNode(i,_+1,j)}function mergeIntoMapWith(i,s,u){for(var m=[],v=0;v>1&1431655765))+(i>>2&858993459))+(i>>4)&252645135,i+=i>>8,127&(i+=i>>16)}function setIn(i,s,u,m){var v=m?i:arrCopy(i);return v[s]=u,v}function spliceIn(i,s,u,m){var v=i.length+1;if(m&&s+1===v)return i[s]=u,i;for(var _=new Array(v),j=0,M=0;M=Xe)return createNodes(i,$,m,v);var ee=i&&i===this.ownerID,ae=ee?$:arrCopy($);return Z?M?X===Y-1?ae.pop():ae[X]=ae.pop():ae[X]=[m,v]:ae.push([m,v]),ee?(this.entries=ae,this):new ArrayMapNode(i,ae)}},BitmapIndexedNode.prototype.get=function(i,s,u,m){void 0===s&&(s=hash(u));var v=1<<((0===i?s:s>>>i)&$),_=this.bitmap;return 0==(_&v)?m:this.nodes[popCount(_&v-1)].get(i+j,s,u,m)},BitmapIndexedNode.prototype.update=function(i,s,u,m,v,_,M){void 0===u&&(u=hash(m));var X=(0===s?u:u>>>s)&$,Y=1<=Ye)return expandNodes(i,ie,Z,X,ce);if(ee&&!ce&&2===ie.length&&isLeafNode(ie[1^ae]))return ie[1^ae];if(ee&&ce&&1===ie.length&&isLeafNode(ce))return ce;var pe=i&&i===this.ownerID,de=ee?ce?Z:Z^Y:Z|Y,fe=ee?ce?setIn(ie,ae,ce,pe):spliceOut(ie,ae,pe):spliceIn(ie,ae,ce,pe);return pe?(this.bitmap=de,this.nodes=fe,this):new BitmapIndexedNode(i,de,fe)},HashArrayMapNode.prototype.get=function(i,s,u,m){void 0===s&&(s=hash(u));var v=(0===i?s:s>>>i)&$,_=this.nodes[v];return _?_.get(i+j,s,u,m):m},HashArrayMapNode.prototype.update=function(i,s,u,m,v,_,M){void 0===u&&(u=hash(m));var X=(0===s?u:u>>>s)&$,Y=v===W,Z=this.nodes,ee=Z[X];if(Y&&!ee)return this;var ae=updateNode(ee,i,s+j,u,m,v,_,M);if(ae===ee)return this;var ie=this.count;if(ee){if(!ae&&--ie0&&m=0&&i>>s&$;if(m>=this.array.length)return new VNode([],i);var v,_=0===m;if(s>0){var M=this.array[m];if((v=M&&M.removeBefore(i,s-j,u))===M&&_)return this}if(_&&!v)return this;var W=editableVNode(this,i);if(!_)for(var X=0;X>>s&$;if(v>=this.array.length)return this;if(s>0){var _=this.array[v];if((m=_&&_.removeAfter(i,s-j,u))===_&&v===this.array.length-1)return this}var M=editableVNode(this,i);return M.array.splice(v+1),m&&(M.array[v]=m),M};var rt,nt,ot={};function iterateList(i,s){var u=i._origin,m=i._capacity,v=getTailOffset(m),_=i._tail;return iterateNodeOrLeaf(i._root,i._level,0);function iterateNodeOrLeaf(i,s,u){return 0===s?iterateLeaf(i,u):iterateNode(i,s,u)}function iterateLeaf(i,j){var $=j===v?_&&_.array:i&&i.array,W=j>u?0:u-j,X=m-j;return X>M&&(X=M),function(){if(W===X)return ot;var i=s?--X:W++;return $&&$[i]}}function iterateNode(i,v,_){var $,W=i&&i.array,X=_>u?0:u-_>>v,Y=1+(m-_>>v);return Y>M&&(Y=M),function(){for(;;){if($){var i=$();if(i!==ot)return i;$=null}if(X===Y)return ot;var u=s?--Y:X++;$=iterateNodeOrLeaf(W&&W[u],v-j,_+(u<=i.size||s<0)return i.withMutations((function(i){s<0?setListBounds(i,s).set(0,u):setListBounds(i,0,s+1).set(s,u)}));s+=i._origin;var m=i._tail,v=i._root,_=MakeRef(Y);return s>=getTailOffset(i._capacity)?m=updateVNode(m,i.__ownerID,0,s,u,_):v=updateVNode(v,i.__ownerID,i._level,s,u,_),_.value?i.__ownerID?(i._root=v,i._tail=m,i.__hash=void 0,i.__altered=!0,i):makeList(i._origin,i._capacity,i._level,v,m):i}function updateVNode(i,s,u,m,v,_){var M,W=m>>>u&$,X=i&&W0){var Y=i&&i.array[W],Z=updateVNode(Y,s,u-j,m,v,_);return Z===Y?i:((M=editableVNode(i,s)).array[W]=Z,M)}return X&&i.array[W]===v?i:(SetRef(_),M=editableVNode(i,s),void 0===v&&W===M.array.length-1?M.array.pop():M.array[W]=v,M)}function editableVNode(i,s){return s&&i&&s===i.ownerID?i:new VNode(i?i.array.slice():[],s)}function listNodeFor(i,s){if(s>=getTailOffset(i._capacity))return i._tail;if(s<1<0;)u=u.array[s>>>m&$],m-=j;return u}}function setListBounds(i,s,u){void 0!==s&&(s|=0),void 0!==u&&(u|=0);var m=i.__ownerID||new OwnerID,v=i._origin,_=i._capacity,M=v+s,W=void 0===u?_:u<0?_+u:v+u;if(M===v&&W===_)return i;if(M>=W)return i.clear();for(var X=i._level,Y=i._root,Z=0;M+Z<0;)Y=new VNode(Y&&Y.array.length?[void 0,Y]:[],m),Z+=1<<(X+=j);Z&&(M+=Z,v+=Z,W+=Z,_+=Z);for(var ee=getTailOffset(_),ae=getTailOffset(W);ae>=1<ee?new VNode([],m):ie;if(ie&&ae>ee&&M<_&&ie.array.length){for(var ce=Y=editableVNode(Y,m),pe=X;pe>j;pe-=j){var de=ee>>>pe&$;ce=ce.array[de]=editableVNode(ce.array[de],m)}ce.array[ee>>>j&$]=ie}if(W<_&&(le=le&&le.removeAfter(m,0,W)),M>=ae)M-=ae,W-=ae,X=j,Y=null,le=le&&le.removeBefore(m,0,M);else if(M>v||ae>>X&$;if(fe!==ae>>>X&$)break;fe&&(Z+=(1<v&&(Y=Y.removeBefore(m,X,M-Z)),Y&&aev&&(v=M.size),isIterable(j)||(M=M.map((function(i){return fromJS(i)}))),m.push(M)}return v>i.size&&(i=i.setSize(v)),mergeIntoCollectionWith(i,s,m)}function getTailOffset(i){return i>>j<=M&&j.size>=2*_.size?(m=(v=j.filter((function(i,s){return void 0!==i&&$!==s}))).toKeyedSeq().map((function(i){return i[0]})).flip().toMap(),i.__ownerID&&(m.__ownerID=v.__ownerID=i.__ownerID)):(m=_.remove(s),v=$===j.size-1?j.pop():j.set($,void 0))}else if(X){if(u===j.get($)[1])return i;m=_,v=j.set($,[s,u])}else m=_.set(s,j.size),v=j.set(j.size,[s,u]);return i.__ownerID?(i.size=m.size,i._map=m,i._list=v,i.__hash=void 0,i):makeOrderedMap(m,v)}function ToKeyedSequence(i,s){this._iter=i,this._useKeys=s,this.size=i.size}function ToIndexedSequence(i){this._iter=i,this.size=i.size}function ToSetSequence(i){this._iter=i,this.size=i.size}function FromEntriesSequence(i){this._iter=i,this.size=i.size}function flipFactory(i){var s=makeSequence(i);return s._iter=i,s.size=i.size,s.flip=function(){return i},s.reverse=function(){var s=i.reverse.apply(this);return s.flip=function(){return i.reverse()},s},s.has=function(s){return i.includes(s)},s.includes=function(s){return i.has(s)},s.cacheResult=cacheResultThrough,s.__iterateUncached=function(s,u){var m=this;return i.__iterate((function(i,u){return!1!==s(u,i,m)}),u)},s.__iteratorUncached=function(s,u){if(s===ae){var m=i.__iterator(s,u);return new Iterator((function(){var i=m.next();if(!i.done){var s=i.value[0];i.value[0]=i.value[1],i.value[1]=s}return i}))}return i.__iterator(s===ee?Z:ee,u)},s}function mapFactory(i,s,u){var m=makeSequence(i);return m.size=i.size,m.has=function(s){return i.has(s)},m.get=function(m,v){var _=i.get(m,W);return _===W?v:s.call(u,_,m,i)},m.__iterateUncached=function(m,v){var _=this;return i.__iterate((function(i,v,j){return!1!==m(s.call(u,i,v,j),v,_)}),v)},m.__iteratorUncached=function(m,v){var _=i.__iterator(ae,v);return new Iterator((function(){var v=_.next();if(v.done)return v;var j=v.value,M=j[0];return iteratorValue(m,M,s.call(u,j[1],M,i),v)}))},m}function reverseFactory(i,s){var u=makeSequence(i);return u._iter=i,u.size=i.size,u.reverse=function(){return i},i.flip&&(u.flip=function(){var s=flipFactory(i);return s.reverse=function(){return i.flip()},s}),u.get=function(u,m){return i.get(s?u:-1-u,m)},u.has=function(u){return i.has(s?u:-1-u)},u.includes=function(s){return i.includes(s)},u.cacheResult=cacheResultThrough,u.__iterate=function(s,u){var m=this;return i.__iterate((function(i,u){return s(i,u,m)}),!u)},u.__iterator=function(s,u){return i.__iterator(s,!u)},u}function filterFactory(i,s,u,m){var v=makeSequence(i);return m&&(v.has=function(m){var v=i.get(m,W);return v!==W&&!!s.call(u,v,m,i)},v.get=function(m,v){var _=i.get(m,W);return _!==W&&s.call(u,_,m,i)?_:v}),v.__iterateUncached=function(v,_){var j=this,M=0;return i.__iterate((function(i,_,$){if(s.call(u,i,_,$))return M++,v(i,m?_:M-1,j)}),_),M},v.__iteratorUncached=function(v,_){var j=i.__iterator(ae,_),M=0;return new Iterator((function(){for(;;){var _=j.next();if(_.done)return _;var $=_.value,W=$[0],X=$[1];if(s.call(u,X,W,i))return iteratorValue(v,m?W:M++,X,_)}}))},v}function countByFactory(i,s,u){var m=Map().asMutable();return i.__iterate((function(v,_){m.update(s.call(u,v,_,i),0,(function(i){return i+1}))})),m.asImmutable()}function groupByFactory(i,s,u){var m=isKeyed(i),v=(isOrdered(i)?OrderedMap():Map()).asMutable();i.__iterate((function(_,j){v.update(s.call(u,_,j,i),(function(i){return(i=i||[]).push(m?[j,_]:_),i}))}));var _=iterableClass(i);return v.map((function(s){return reify(i,_(s))}))}function sliceFactory(i,s,u,m){var v=i.size;if(void 0!==s&&(s|=0),void 0!==u&&(u===1/0?u=v:u|=0),wholeSlice(s,u,v))return i;var _=resolveBegin(s,v),j=resolveEnd(u,v);if(_!=_||j!=j)return sliceFactory(i.toSeq().cacheResult(),s,u,m);var M,$=j-_;$==$&&(M=$<0?0:$);var W=makeSequence(i);return W.size=0===M?M:i.size&&M||void 0,!m&&isSeq(i)&&M>=0&&(W.get=function(s,u){return(s=wrapIndex(this,s))>=0&&sM)return iteratorDone();var i=v.next();return m||s===ee?i:iteratorValue(s,$-1,s===Z?void 0:i.value[1],i)}))},W}function takeWhileFactory(i,s,u){var m=makeSequence(i);return m.__iterateUncached=function(m,v){var _=this;if(v)return this.cacheResult().__iterate(m,v);var j=0;return i.__iterate((function(i,v,M){return s.call(u,i,v,M)&&++j&&m(i,v,_)})),j},m.__iteratorUncached=function(m,v){var _=this;if(v)return this.cacheResult().__iterator(m,v);var j=i.__iterator(ae,v),M=!0;return new Iterator((function(){if(!M)return iteratorDone();var i=j.next();if(i.done)return i;var v=i.value,$=v[0],W=v[1];return s.call(u,W,$,_)?m===ae?i:iteratorValue(m,$,W,i):(M=!1,iteratorDone())}))},m}function skipWhileFactory(i,s,u,m){var v=makeSequence(i);return v.__iterateUncached=function(v,_){var j=this;if(_)return this.cacheResult().__iterate(v,_);var M=!0,$=0;return i.__iterate((function(i,_,W){if(!M||!(M=s.call(u,i,_,W)))return $++,v(i,m?_:$-1,j)})),$},v.__iteratorUncached=function(v,_){var j=this;if(_)return this.cacheResult().__iterator(v,_);var M=i.__iterator(ae,_),$=!0,W=0;return new Iterator((function(){var i,_,X;do{if((i=M.next()).done)return m||v===ee?i:iteratorValue(v,W++,v===Z?void 0:i.value[1],i);var Y=i.value;_=Y[0],X=Y[1],$&&($=s.call(u,X,_,j))}while($);return v===ae?i:iteratorValue(v,_,X,i)}))},v}function concatFactory(i,s){var u=isKeyed(i),m=[i].concat(s).map((function(i){return isIterable(i)?u&&(i=KeyedIterable(i)):i=u?keyedSeqFromValue(i):indexedSeqFromValue(Array.isArray(i)?i:[i]),i})).filter((function(i){return 0!==i.size}));if(0===m.length)return i;if(1===m.length){var v=m[0];if(v===i||u&&isKeyed(v)||isIndexed(i)&&isIndexed(v))return v}var _=new ArraySeq(m);return u?_=_.toKeyedSeq():isIndexed(i)||(_=_.toSetSeq()),(_=_.flatten(!0)).size=m.reduce((function(i,s){if(void 0!==i){var u=s.size;if(void 0!==u)return i+u}}),0),_}function flattenFactory(i,s,u){var m=makeSequence(i);return m.__iterateUncached=function(m,v){var _=0,j=!1;function flatDeep(i,M){var $=this;i.__iterate((function(i,v){return(!s||M0}function zipWithFactory(i,s,u){var m=makeSequence(i);return m.size=new ArraySeq(u).map((function(i){return i.size})).min(),m.__iterate=function(i,s){for(var u,m=this.__iterator(ee,s),v=0;!(u=m.next()).done&&!1!==i(u.value,v++,this););return v},m.__iteratorUncached=function(i,m){var v=u.map((function(i){return i=Iterable(i),getIterator(m?i.reverse():i)})),_=0,j=!1;return new Iterator((function(){var u;return j||(u=v.map((function(i){return i.next()})),j=u.some((function(i){return i.done}))),j?iteratorDone():iteratorValue(i,_++,s.apply(null,u.map((function(i){return i.value}))))}))},m}function reify(i,s){return isSeq(i)?s:i.constructor(s)}function validateEntry(i){if(i!==Object(i))throw new TypeError("Expected [K, V] tuple: "+i)}function resolveSize(i){return assertNotInfinite(i.size),ensureSize(i)}function iterableClass(i){return isKeyed(i)?KeyedIterable:isIndexed(i)?IndexedIterable:SetIterable}function makeSequence(i){return Object.create((isKeyed(i)?KeyedSeq:isIndexed(i)?IndexedSeq:SetSeq).prototype)}function cacheResultThrough(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):Seq.prototype.cacheResult.call(this)}function defaultComparator(i,s){return i>s?1:i=0;u--)s={value:arguments[u],next:s};return this.__ownerID?(this.size=i,this._head=s,this.__hash=void 0,this.__altered=!0,this):makeStack(i,s)},Stack.prototype.pushAll=function(i){if(0===(i=IndexedIterable(i)).size)return this;assertNotInfinite(i.size);var s=this.size,u=this._head;return i.reverse().forEach((function(i){s++,u={value:i,next:u}})),this.__ownerID?(this.size=s,this._head=u,this.__hash=void 0,this.__altered=!0,this):makeStack(s,u)},Stack.prototype.pop=function(){return this.slice(1)},Stack.prototype.unshift=function(){return this.push.apply(this,arguments)},Stack.prototype.unshiftAll=function(i){return this.pushAll(i)},Stack.prototype.shift=function(){return this.pop.apply(this,arguments)},Stack.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):emptyStack()},Stack.prototype.slice=function(i,s){if(wholeSlice(i,s,this.size))return this;var u=resolveBegin(i,this.size);if(resolveEnd(s,this.size)!==this.size)return IndexedCollection.prototype.slice.call(this,i,s);for(var m=this.size-u,v=this._head;u--;)v=v.next;return this.__ownerID?(this.size=m,this._head=v,this.__hash=void 0,this.__altered=!0,this):makeStack(m,v)},Stack.prototype.__ensureOwner=function(i){return i===this.__ownerID?this:i?makeStack(this.size,this._head,i,this.__hash):(this.__ownerID=i,this.__altered=!1,this)},Stack.prototype.__iterate=function(i,s){if(s)return this.reverse().__iterate(i);for(var u=0,m=this._head;m&&!1!==i(m.value,u++,this);)m=m.next;return u},Stack.prototype.__iterator=function(i,s){if(s)return this.reverse().__iterator(i);var u=0,m=this._head;return new Iterator((function(){if(m){var s=m.value;return m=m.next,iteratorValue(i,u++,s)}return iteratorDone()}))},Stack.isStack=isStack;var pt,ht="@@__IMMUTABLE_STACK__@@",dt=Stack.prototype;function makeStack(i,s,u,m){var v=Object.create(dt);return v.size=i,v._head=s,v.__ownerID=u,v.__hash=m,v.__altered=!1,v}function emptyStack(){return pt||(pt=makeStack(0))}function mixin(i,s){var keyCopier=function(u){i.prototype[u]=s[u]};return Object.keys(s).forEach(keyCopier),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(s).forEach(keyCopier),i}dt[ht]=!0,dt.withMutations=He.withMutations,dt.asMutable=He.asMutable,dt.asImmutable=He.asImmutable,dt.wasAltered=He.wasAltered,Iterable.Iterator=Iterator,mixin(Iterable,{toArray:function(){assertNotInfinite(this.size);var i=new Array(this.size||0);return this.valueSeq().__iterate((function(s,u){i[u]=s})),i},toIndexedSeq:function(){return new ToIndexedSequence(this)},toJS:function(){return this.toSeq().map((function(i){return i&&"function"==typeof i.toJS?i.toJS():i})).__toJS()},toJSON:function(){return this.toSeq().map((function(i){return i&&"function"==typeof i.toJSON?i.toJSON():i})).__toJS()},toKeyedSeq:function(){return new ToKeyedSequence(this,!0)},toMap:function(){return Map(this.toKeyedSeq())},toObject:function(){assertNotInfinite(this.size);var i={};return this.__iterate((function(s,u){i[u]=s})),i},toOrderedMap:function(){return OrderedMap(this.toKeyedSeq())},toOrderedSet:function(){return OrderedSet(isKeyed(this)?this.valueSeq():this)},toSet:function(){return Set(isKeyed(this)?this.valueSeq():this)},toSetSeq:function(){return new ToSetSequence(this)},toSeq:function(){return isIndexed(this)?this.toIndexedSeq():isKeyed(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return Stack(isKeyed(this)?this.valueSeq():this)},toList:function(){return List(isKeyed(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(i,s){return 0===this.size?i+s:i+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+s},concat:function(){return reify(this,concatFactory(this,i.call(arguments,0)))},includes:function(i){return this.some((function(s){return is(s,i)}))},entries:function(){return this.__iterator(ae)},every:function(i,s){assertNotInfinite(this.size);var u=!0;return this.__iterate((function(m,v,_){if(!i.call(s,m,v,_))return u=!1,!1})),u},filter:function(i,s){return reify(this,filterFactory(this,i,s,!0))},find:function(i,s,u){var m=this.findEntry(i,s);return m?m[1]:u},forEach:function(i,s){return assertNotInfinite(this.size),this.__iterate(s?i.bind(s):i)},join:function(i){assertNotInfinite(this.size),i=void 0!==i?""+i:",";var s="",u=!0;return this.__iterate((function(m){u?u=!1:s+=i,s+=null!=m?m.toString():""})),s},keys:function(){return this.__iterator(Z)},map:function(i,s){return reify(this,mapFactory(this,i,s))},reduce:function(i,s,u){var m,v;return assertNotInfinite(this.size),arguments.length<2?v=!0:m=s,this.__iterate((function(s,_,j){v?(v=!1,m=s):m=i.call(u,m,s,_,j)})),m},reduceRight:function(i,s,u){var m=this.toKeyedSeq().reverse();return m.reduce.apply(m,arguments)},reverse:function(){return reify(this,reverseFactory(this,!0))},slice:function(i,s){return reify(this,sliceFactory(this,i,s,!0))},some:function(i,s){return!this.every(not(i),s)},sort:function(i){return reify(this,sortFactory(this,i))},values:function(){return this.__iterator(ee)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some((function(){return!0}))},count:function(i,s){return ensureSize(i?this.toSeq().filter(i,s):this)},countBy:function(i,s){return countByFactory(this,i,s)},equals:function(i){return deepEqual(this,i)},entrySeq:function(){var i=this;if(i._cache)return new ArraySeq(i._cache);var s=i.toSeq().map(entryMapper).toIndexedSeq();return s.fromEntrySeq=function(){return i.toSeq()},s},filterNot:function(i,s){return this.filter(not(i),s)},findEntry:function(i,s,u){var m=u;return this.__iterate((function(u,v,_){if(i.call(s,u,v,_))return m=[v,u],!1})),m},findKey:function(i,s){var u=this.findEntry(i,s);return u&&u[0]},findLast:function(i,s,u){return this.toKeyedSeq().reverse().find(i,s,u)},findLastEntry:function(i,s,u){return this.toKeyedSeq().reverse().findEntry(i,s,u)},findLastKey:function(i,s){return this.toKeyedSeq().reverse().findKey(i,s)},first:function(){return this.find(returnTrue)},flatMap:function(i,s){return reify(this,flatMapFactory(this,i,s))},flatten:function(i){return reify(this,flattenFactory(this,i,!0))},fromEntrySeq:function(){return new FromEntriesSequence(this)},get:function(i,s){return this.find((function(s,u){return is(u,i)}),void 0,s)},getIn:function(i,s){for(var u,m=this,v=forceIterator(i);!(u=v.next()).done;){var _=u.value;if((m=m&&m.get?m.get(_,W):W)===W)return s}return m},groupBy:function(i,s){return groupByFactory(this,i,s)},has:function(i){return this.get(i,W)!==W},hasIn:function(i){return this.getIn(i,W)!==W},isSubset:function(i){return i="function"==typeof i.includes?i:Iterable(i),this.every((function(s){return i.includes(s)}))},isSuperset:function(i){return(i="function"==typeof i.isSubset?i:Iterable(i)).isSubset(this)},keyOf:function(i){return this.findKey((function(s){return is(s,i)}))},keySeq:function(){return this.toSeq().map(keyMapper).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(i){return this.toKeyedSeq().reverse().keyOf(i)},max:function(i){return maxFactory(this,i)},maxBy:function(i,s){return maxFactory(this,s,i)},min:function(i){return maxFactory(this,i?neg(i):defaultNegComparator)},minBy:function(i,s){return maxFactory(this,s?neg(s):defaultNegComparator,i)},rest:function(){return this.slice(1)},skip:function(i){return this.slice(Math.max(0,i))},skipLast:function(i){return reify(this,this.toSeq().reverse().skip(i).reverse())},skipWhile:function(i,s){return reify(this,skipWhileFactory(this,i,s,!0))},skipUntil:function(i,s){return this.skipWhile(not(i),s)},sortBy:function(i,s){return reify(this,sortFactory(this,s,i))},take:function(i){return this.slice(0,Math.max(0,i))},takeLast:function(i){return reify(this,this.toSeq().reverse().take(i).reverse())},takeWhile:function(i,s){return reify(this,takeWhileFactory(this,i,s))},takeUntil:function(i,s){return this.takeWhile(not(i),s)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=hashIterable(this))}});var mt=Iterable.prototype;mt[s]=!0,mt[ce]=mt.values,mt.__toJS=mt.toArray,mt.__toStringMapper=quoteString,mt.inspect=mt.toSource=function(){return this.toString()},mt.chain=mt.flatMap,mt.contains=mt.includes,mixin(KeyedIterable,{flip:function(){return reify(this,flipFactory(this))},mapEntries:function(i,s){var u=this,m=0;return reify(this,this.toSeq().map((function(v,_){return i.call(s,[_,v],m++,u)})).fromEntrySeq())},mapKeys:function(i,s){var u=this;return reify(this,this.toSeq().flip().map((function(m,v){return i.call(s,m,v,u)})).flip())}});var gt=KeyedIterable.prototype;function keyMapper(i,s){return s}function entryMapper(i,s){return[s,i]}function not(i){return function(){return!i.apply(this,arguments)}}function neg(i){return function(){return-i.apply(this,arguments)}}function quoteString(i){return"string"==typeof i?JSON.stringify(i):String(i)}function defaultZipper(){return arrCopy(arguments)}function defaultNegComparator(i,s){return is?-1:0}function hashIterable(i){if(i.size===1/0)return 0;var s=isOrdered(i),u=isKeyed(i),m=s?1:0;return murmurHashOfSize(i.__iterate(u?s?function(i,s){m=31*m+hashMerge(hash(i),hash(s))|0}:function(i,s){m=m+hashMerge(hash(i),hash(s))|0}:s?function(i){m=31*m+hash(i)|0}:function(i){m=m+hash(i)|0}),m)}function murmurHashOfSize(i,s){return s=be(s,3432918353),s=be(s<<15|s>>>-15,461845907),s=be(s<<13|s>>>-13,5),s=be((s=(s+3864292196|0)^i)^s>>>16,2246822507),s=smi((s=be(s^s>>>13,3266489909))^s>>>16)}function hashMerge(i,s){return i^s+2654435769+(i<<6)+(i>>2)|0}return gt[u]=!0,gt[ce]=mt.entries,gt.__toJS=mt.toObject,gt.__toStringMapper=function(i,s){return JSON.stringify(s)+": "+quoteString(i)},mixin(IndexedIterable,{toKeyedSeq:function(){return new ToKeyedSequence(this,!1)},filter:function(i,s){return reify(this,filterFactory(this,i,s,!1))},findIndex:function(i,s){var u=this.findEntry(i,s);return u?u[0]:-1},indexOf:function(i){var s=this.keyOf(i);return void 0===s?-1:s},lastIndexOf:function(i){var s=this.lastKeyOf(i);return void 0===s?-1:s},reverse:function(){return reify(this,reverseFactory(this,!1))},slice:function(i,s){return reify(this,sliceFactory(this,i,s,!1))},splice:function(i,s){var u=arguments.length;if(s=Math.max(0|s,0),0===u||2===u&&!s)return this;i=resolveBegin(i,i<0?this.count():this.size);var m=this.slice(0,i);return reify(this,1===u?m:m.concat(arrCopy(arguments,2),this.slice(i+s)))},findLastIndex:function(i,s){var u=this.findLastEntry(i,s);return u?u[0]:-1},first:function(){return this.get(0)},flatten:function(i){return reify(this,flattenFactory(this,i,!1))},get:function(i,s){return(i=wrapIndex(this,i))<0||this.size===1/0||void 0!==this.size&&i>this.size?s:this.find((function(s,u){return u===i}),void 0,s)},has:function(i){return(i=wrapIndex(this,i))>=0&&(void 0!==this.size?this.size===1/0||i{"function"==typeof Object.create?i.exports=function inherits(i,s){s&&(i.super_=s,i.prototype=Object.create(s.prototype,{constructor:{value:i,enumerable:!1,writable:!0,configurable:!0}}))}:i.exports=function inherits(i,s){if(s){i.super_=s;var TempCtor=function(){};TempCtor.prototype=s.prototype,i.prototype=new TempCtor,i.prototype.constructor=i}}},35823:i=>{i.exports=function(i,s,u,m){var v=new Blob(void 0!==m?[m,i]:[i],{type:u||"application/octet-stream"});if(void 0!==window.navigator.msSaveBlob)window.navigator.msSaveBlob(v,s);else{var _=window.URL&&window.URL.createObjectURL?window.URL.createObjectURL(v):window.webkitURL.createObjectURL(v),j=document.createElement("a");j.style.display="none",j.href=_,j.setAttribute("download",s),void 0===j.download&&j.setAttribute("target","_blank"),document.body.appendChild(j),j.click(),setTimeout((function(){document.body.removeChild(j),window.URL.revokeObjectURL(_)}),200)}}},91296:(i,s,u)=>{var m=NaN,v="[object Symbol]",_=/^\s+|\s+$/g,j=/^[-+]0x[0-9a-f]+$/i,M=/^0b[01]+$/i,$=/^0o[0-7]+$/i,W=parseInt,X="object"==typeof u.g&&u.g&&u.g.Object===Object&&u.g,Y="object"==typeof self&&self&&self.Object===Object&&self,Z=X||Y||Function("return this")(),ee=Object.prototype.toString,ae=Math.max,ie=Math.min,now=function(){return Z.Date.now()};function isObject(i){var s=typeof i;return!!i&&("object"==s||"function"==s)}function toNumber(i){if("number"==typeof i)return i;if(function isSymbol(i){return"symbol"==typeof i||function isObjectLike(i){return!!i&&"object"==typeof i}(i)&&ee.call(i)==v}(i))return m;if(isObject(i)){var s="function"==typeof i.valueOf?i.valueOf():i;i=isObject(s)?s+"":s}if("string"!=typeof i)return 0===i?i:+i;i=i.replace(_,"");var u=M.test(i);return u||$.test(i)?W(i.slice(2),u?2:8):j.test(i)?m:+i}i.exports=function debounce(i,s,u){var m,v,_,j,M,$,W=0,X=!1,Y=!1,Z=!0;if("function"!=typeof i)throw new TypeError("Expected a function");function invokeFunc(s){var u=m,_=v;return m=v=void 0,W=s,j=i.apply(_,u)}function shouldInvoke(i){var u=i-$;return void 0===$||u>=s||u<0||Y&&i-W>=_}function timerExpired(){var i=now();if(shouldInvoke(i))return trailingEdge(i);M=setTimeout(timerExpired,function remainingWait(i){var u=s-(i-$);return Y?ie(u,_-(i-W)):u}(i))}function trailingEdge(i){return M=void 0,Z&&m?invokeFunc(i):(m=v=void 0,j)}function debounced(){var i=now(),u=shouldInvoke(i);if(m=arguments,v=this,$=i,u){if(void 0===M)return function leadingEdge(i){return W=i,M=setTimeout(timerExpired,s),X?invokeFunc(i):j}($);if(Y)return M=setTimeout(timerExpired,s),invokeFunc($)}return void 0===M&&(M=setTimeout(timerExpired,s)),j}return s=toNumber(s)||0,isObject(u)&&(X=!!u.leading,_=(Y="maxWait"in u)?ae(toNumber(u.maxWait)||0,s):_,Z="trailing"in u?!!u.trailing:Z),debounced.cancel=function cancel(){void 0!==M&&clearTimeout(M),W=0,m=$=v=M=void 0},debounced.flush=function flush(){return void 0===M?j:trailingEdge(now())},debounced}},18552:(i,s,u)=>{var m=u(10852)(u(55639),"DataView");i.exports=m},1989:(i,s,u)=>{var m=u(51789),v=u(80401),_=u(57667),j=u(21327),M=u(81866);function Hash(i){var s=-1,u=null==i?0:i.length;for(this.clear();++s{var m=u(3118),v=u(9435);function LazyWrapper(i){this.__wrapped__=i,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}LazyWrapper.prototype=m(v.prototype),LazyWrapper.prototype.constructor=LazyWrapper,i.exports=LazyWrapper},38407:(i,s,u)=>{var m=u(27040),v=u(14125),_=u(82117),j=u(67518),M=u(54705);function ListCache(i){var s=-1,u=null==i?0:i.length;for(this.clear();++s{var m=u(3118),v=u(9435);function LodashWrapper(i,s){this.__wrapped__=i,this.__actions__=[],this.__chain__=!!s,this.__index__=0,this.__values__=void 0}LodashWrapper.prototype=m(v.prototype),LodashWrapper.prototype.constructor=LodashWrapper,i.exports=LodashWrapper},57071:(i,s,u)=>{var m=u(10852)(u(55639),"Map");i.exports=m},83369:(i,s,u)=>{var m=u(24785),v=u(11285),_=u(96e3),j=u(49916),M=u(95265);function MapCache(i){var s=-1,u=null==i?0:i.length;for(this.clear();++s{var m=u(10852)(u(55639),"Promise");i.exports=m},58525:(i,s,u)=>{var m=u(10852)(u(55639),"Set");i.exports=m},88668:(i,s,u)=>{var m=u(83369),v=u(90619),_=u(72385);function SetCache(i){var s=-1,u=null==i?0:i.length;for(this.__data__=new m;++s{var m=u(38407),v=u(37465),_=u(63779),j=u(67599),M=u(44758),$=u(34309);function Stack(i){var s=this.__data__=new m(i);this.size=s.size}Stack.prototype.clear=v,Stack.prototype.delete=_,Stack.prototype.get=j,Stack.prototype.has=M,Stack.prototype.set=$,i.exports=Stack},62705:(i,s,u)=>{var m=u(55639).Symbol;i.exports=m},11149:(i,s,u)=>{var m=u(55639).Uint8Array;i.exports=m},70577:(i,s,u)=>{var m=u(10852)(u(55639),"WeakMap");i.exports=m},96874:i=>{i.exports=function apply(i,s,u){switch(u.length){case 0:return i.call(s);case 1:return i.call(s,u[0]);case 2:return i.call(s,u[0],u[1]);case 3:return i.call(s,u[0],u[1],u[2])}return i.apply(s,u)}},77412:i=>{i.exports=function arrayEach(i,s){for(var u=-1,m=null==i?0:i.length;++u{i.exports=function arrayFilter(i,s){for(var u=-1,m=null==i?0:i.length,v=0,_=[];++u{var m=u(42118);i.exports=function arrayIncludes(i,s){return!!(null==i?0:i.length)&&m(i,s,0)>-1}},14636:(i,s,u)=>{var m=u(22545),v=u(35694),_=u(1469),j=u(44144),M=u(65776),$=u(36719),W=Object.prototype.hasOwnProperty;i.exports=function arrayLikeKeys(i,s){var u=_(i),X=!u&&v(i),Y=!u&&!X&&j(i),Z=!u&&!X&&!Y&&$(i),ee=u||X||Y||Z,ae=ee?m(i.length,String):[],ie=ae.length;for(var le in i)!s&&!W.call(i,le)||ee&&("length"==le||Y&&("offset"==le||"parent"==le)||Z&&("buffer"==le||"byteLength"==le||"byteOffset"==le)||M(le,ie))||ae.push(le);return ae}},29932:i=>{i.exports=function arrayMap(i,s){for(var u=-1,m=null==i?0:i.length,v=Array(m);++u{i.exports=function arrayPush(i,s){for(var u=-1,m=s.length,v=i.length;++u{i.exports=function arrayReduce(i,s,u,m){var v=-1,_=null==i?0:i.length;for(m&&_&&(u=i[++v]);++v<_;)u=s(u,i[v],v,i);return u}},82908:i=>{i.exports=function arraySome(i,s){for(var u=-1,m=null==i?0:i.length;++u{i.exports=function asciiToArray(i){return i.split("")}},49029:i=>{var s=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;i.exports=function asciiWords(i){return i.match(s)||[]}},86556:(i,s,u)=>{var m=u(89465),v=u(77813);i.exports=function assignMergeValue(i,s,u){(void 0!==u&&!v(i[s],u)||void 0===u&&!(s in i))&&m(i,s,u)}},34865:(i,s,u)=>{var m=u(89465),v=u(77813),_=Object.prototype.hasOwnProperty;i.exports=function assignValue(i,s,u){var j=i[s];_.call(i,s)&&v(j,u)&&(void 0!==u||s in i)||m(i,s,u)}},18470:(i,s,u)=>{var m=u(77813);i.exports=function assocIndexOf(i,s){for(var u=i.length;u--;)if(m(i[u][0],s))return u;return-1}},44037:(i,s,u)=>{var m=u(98363),v=u(3674);i.exports=function baseAssign(i,s){return i&&m(s,v(s),i)}},63886:(i,s,u)=>{var m=u(98363),v=u(81704);i.exports=function baseAssignIn(i,s){return i&&m(s,v(s),i)}},89465:(i,s,u)=>{var m=u(38777);i.exports=function baseAssignValue(i,s,u){"__proto__"==s&&m?m(i,s,{configurable:!0,enumerable:!0,value:u,writable:!0}):i[s]=u}},85990:(i,s,u)=>{var m=u(46384),v=u(77412),_=u(34865),j=u(44037),M=u(63886),$=u(64626),W=u(278),X=u(18805),Y=u(1911),Z=u(58234),ee=u(46904),ae=u(64160),ie=u(43824),le=u(29148),ce=u(38517),pe=u(1469),de=u(44144),fe=u(56688),ye=u(13218),be=u(72928),_e=u(3674),we=u(81704),Se="[object Arguments]",xe="[object Function]",Pe="[object Object]",Ie={};Ie[Se]=Ie["[object Array]"]=Ie["[object ArrayBuffer]"]=Ie["[object DataView]"]=Ie["[object Boolean]"]=Ie["[object Date]"]=Ie["[object Float32Array]"]=Ie["[object Float64Array]"]=Ie["[object Int8Array]"]=Ie["[object Int16Array]"]=Ie["[object Int32Array]"]=Ie["[object Map]"]=Ie["[object Number]"]=Ie[Pe]=Ie["[object RegExp]"]=Ie["[object Set]"]=Ie["[object String]"]=Ie["[object Symbol]"]=Ie["[object Uint8Array]"]=Ie["[object Uint8ClampedArray]"]=Ie["[object Uint16Array]"]=Ie["[object Uint32Array]"]=!0,Ie["[object Error]"]=Ie[xe]=Ie["[object WeakMap]"]=!1,i.exports=function baseClone(i,s,u,Te,Re,qe){var ze,Ve=1&s,We=2&s,He=4&s;if(u&&(ze=Re?u(i,Te,Re,qe):u(i)),void 0!==ze)return ze;if(!ye(i))return i;var Xe=pe(i);if(Xe){if(ze=ie(i),!Ve)return W(i,ze)}else{var Ye=ae(i),Qe=Ye==xe||"[object GeneratorFunction]"==Ye;if(de(i))return $(i,Ve);if(Ye==Pe||Ye==Se||Qe&&!Re){if(ze=We||Qe?{}:ce(i),!Ve)return We?Y(i,M(ze,i)):X(i,j(ze,i))}else{if(!Ie[Ye])return Re?i:{};ze=le(i,Ye,Ve)}}qe||(qe=new m);var et=qe.get(i);if(et)return et;qe.set(i,ze),be(i)?i.forEach((function(m){ze.add(baseClone(m,s,u,m,i,qe))})):fe(i)&&i.forEach((function(m,v){ze.set(v,baseClone(m,s,u,v,i,qe))}));var tt=Xe?void 0:(He?We?ee:Z:We?we:_e)(i);return v(tt||i,(function(m,v){tt&&(m=i[v=m]),_(ze,v,baseClone(m,s,u,v,i,qe))})),ze}},3118:(i,s,u)=>{var m=u(13218),v=Object.create,_=function(){function object(){}return function(i){if(!m(i))return{};if(v)return v(i);object.prototype=i;var s=new object;return object.prototype=void 0,s}}();i.exports=_},89881:(i,s,u)=>{var m=u(47816),v=u(99291)(m);i.exports=v},41848:i=>{i.exports=function baseFindIndex(i,s,u,m){for(var v=i.length,_=u+(m?1:-1);m?_--:++_{var m=u(62488),v=u(37285);i.exports=function baseFlatten(i,s,u,_,j){var M=-1,$=i.length;for(u||(u=v),j||(j=[]);++M<$;){var W=i[M];s>0&&u(W)?s>1?baseFlatten(W,s-1,u,_,j):m(j,W):_||(j[j.length]=W)}return j}},28483:(i,s,u)=>{var m=u(25063)();i.exports=m},47816:(i,s,u)=>{var m=u(28483),v=u(3674);i.exports=function baseForOwn(i,s){return i&&m(i,s,v)}},97786:(i,s,u)=>{var m=u(71811),v=u(40327);i.exports=function baseGet(i,s){for(var u=0,_=(s=m(s,i)).length;null!=i&&u<_;)i=i[v(s[u++])];return u&&u==_?i:void 0}},68866:(i,s,u)=>{var m=u(62488),v=u(1469);i.exports=function baseGetAllKeys(i,s,u){var _=s(i);return v(i)?_:m(_,u(i))}},44239:(i,s,u)=>{var m=u(62705),v=u(89607),_=u(2333),j=m?m.toStringTag:void 0;i.exports=function baseGetTag(i){return null==i?void 0===i?"[object Undefined]":"[object Null]":j&&j in Object(i)?v(i):_(i)}},13:i=>{i.exports=function baseHasIn(i,s){return null!=i&&s in Object(i)}},42118:(i,s,u)=>{var m=u(41848),v=u(62722),_=u(42351);i.exports=function baseIndexOf(i,s,u){return s==s?_(i,s,u):m(i,v,u)}},9454:(i,s,u)=>{var m=u(44239),v=u(37005);i.exports=function baseIsArguments(i){return v(i)&&"[object Arguments]"==m(i)}},90939:(i,s,u)=>{var m=u(2492),v=u(37005);i.exports=function baseIsEqual(i,s,u,_,j){return i===s||(null==i||null==s||!v(i)&&!v(s)?i!=i&&s!=s:m(i,s,u,_,baseIsEqual,j))}},2492:(i,s,u)=>{var m=u(46384),v=u(67114),_=u(18351),j=u(16096),M=u(64160),$=u(1469),W=u(44144),X=u(36719),Y="[object Arguments]",Z="[object Array]",ee="[object Object]",ae=Object.prototype.hasOwnProperty;i.exports=function baseIsEqualDeep(i,s,u,ie,le,ce){var pe=$(i),de=$(s),fe=pe?Z:M(i),ye=de?Z:M(s),be=(fe=fe==Y?ee:fe)==ee,_e=(ye=ye==Y?ee:ye)==ee,we=fe==ye;if(we&&W(i)){if(!W(s))return!1;pe=!0,be=!1}if(we&&!be)return ce||(ce=new m),pe||X(i)?v(i,s,u,ie,le,ce):_(i,s,fe,u,ie,le,ce);if(!(1&u)){var Se=be&&ae.call(i,"__wrapped__"),xe=_e&&ae.call(s,"__wrapped__");if(Se||xe){var Pe=Se?i.value():i,Ie=xe?s.value():s;return ce||(ce=new m),le(Pe,Ie,u,ie,ce)}}return!!we&&(ce||(ce=new m),j(i,s,u,ie,le,ce))}},25588:(i,s,u)=>{var m=u(64160),v=u(37005);i.exports=function baseIsMap(i){return v(i)&&"[object Map]"==m(i)}},2958:(i,s,u)=>{var m=u(46384),v=u(90939);i.exports=function baseIsMatch(i,s,u,_){var j=u.length,M=j,$=!_;if(null==i)return!M;for(i=Object(i);j--;){var W=u[j];if($&&W[2]?W[1]!==i[W[0]]:!(W[0]in i))return!1}for(;++j{i.exports=function baseIsNaN(i){return i!=i}},28458:(i,s,u)=>{var m=u(23560),v=u(15346),_=u(13218),j=u(80346),M=/^\[object .+?Constructor\]$/,$=Function.prototype,W=Object.prototype,X=$.toString,Y=W.hasOwnProperty,Z=RegExp("^"+X.call(Y).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");i.exports=function baseIsNative(i){return!(!_(i)||v(i))&&(m(i)?Z:M).test(j(i))}},29221:(i,s,u)=>{var m=u(64160),v=u(37005);i.exports=function baseIsSet(i){return v(i)&&"[object Set]"==m(i)}},38749:(i,s,u)=>{var m=u(44239),v=u(41780),_=u(37005),j={};j["[object Float32Array]"]=j["[object Float64Array]"]=j["[object Int8Array]"]=j["[object Int16Array]"]=j["[object Int32Array]"]=j["[object Uint8Array]"]=j["[object Uint8ClampedArray]"]=j["[object Uint16Array]"]=j["[object Uint32Array]"]=!0,j["[object Arguments]"]=j["[object Array]"]=j["[object ArrayBuffer]"]=j["[object Boolean]"]=j["[object DataView]"]=j["[object Date]"]=j["[object Error]"]=j["[object Function]"]=j["[object Map]"]=j["[object Number]"]=j["[object Object]"]=j["[object RegExp]"]=j["[object Set]"]=j["[object String]"]=j["[object WeakMap]"]=!1,i.exports=function baseIsTypedArray(i){return _(i)&&v(i.length)&&!!j[m(i)]}},67206:(i,s,u)=>{var m=u(91573),v=u(16432),_=u(6557),j=u(1469),M=u(39601);i.exports=function baseIteratee(i){return"function"==typeof i?i:null==i?_:"object"==typeof i?j(i)?v(i[0],i[1]):m(i):M(i)}},280:(i,s,u)=>{var m=u(25726),v=u(86916),_=Object.prototype.hasOwnProperty;i.exports=function baseKeys(i){if(!m(i))return v(i);var s=[];for(var u in Object(i))_.call(i,u)&&"constructor"!=u&&s.push(u);return s}},10313:(i,s,u)=>{var m=u(13218),v=u(25726),_=u(33498),j=Object.prototype.hasOwnProperty;i.exports=function baseKeysIn(i){if(!m(i))return _(i);var s=v(i),u=[];for(var M in i)("constructor"!=M||!s&&j.call(i,M))&&u.push(M);return u}},9435:i=>{i.exports=function baseLodash(){}},91573:(i,s,u)=>{var m=u(2958),v=u(1499),_=u(42634);i.exports=function baseMatches(i){var s=v(i);return 1==s.length&&s[0][2]?_(s[0][0],s[0][1]):function(u){return u===i||m(u,i,s)}}},16432:(i,s,u)=>{var m=u(90939),v=u(27361),_=u(79095),j=u(15403),M=u(89162),$=u(42634),W=u(40327);i.exports=function baseMatchesProperty(i,s){return j(i)&&M(s)?$(W(i),s):function(u){var j=v(u,i);return void 0===j&&j===s?_(u,i):m(s,j,3)}}},42980:(i,s,u)=>{var m=u(46384),v=u(86556),_=u(28483),j=u(59783),M=u(13218),$=u(81704),W=u(36390);i.exports=function baseMerge(i,s,u,X,Y){i!==s&&_(s,(function(_,$){if(Y||(Y=new m),M(_))j(i,s,$,u,baseMerge,X,Y);else{var Z=X?X(W(i,$),_,$+"",i,s,Y):void 0;void 0===Z&&(Z=_),v(i,$,Z)}}),$)}},59783:(i,s,u)=>{var m=u(86556),v=u(64626),_=u(77133),j=u(278),M=u(38517),$=u(35694),W=u(1469),X=u(29246),Y=u(44144),Z=u(23560),ee=u(13218),ae=u(68630),ie=u(36719),le=u(36390),ce=u(59881);i.exports=function baseMergeDeep(i,s,u,pe,de,fe,ye){var be=le(i,u),_e=le(s,u),we=ye.get(_e);if(we)m(i,u,we);else{var Se=fe?fe(be,_e,u+"",i,s,ye):void 0,xe=void 0===Se;if(xe){var Pe=W(_e),Ie=!Pe&&Y(_e),Te=!Pe&&!Ie&&ie(_e);Se=_e,Pe||Ie||Te?W(be)?Se=be:X(be)?Se=j(be):Ie?(xe=!1,Se=v(_e,!0)):Te?(xe=!1,Se=_(_e,!0)):Se=[]:ae(_e)||$(_e)?(Se=be,$(be)?Se=ce(be):ee(be)&&!Z(be)||(Se=M(_e))):xe=!1}xe&&(ye.set(_e,Se),de(Se,_e,pe,fe,ye),ye.delete(_e)),m(i,u,Se)}}},40371:i=>{i.exports=function baseProperty(i){return function(s){return null==s?void 0:s[i]}}},79152:(i,s,u)=>{var m=u(97786);i.exports=function basePropertyDeep(i){return function(s){return m(s,i)}}},18674:i=>{i.exports=function basePropertyOf(i){return function(s){return null==i?void 0:i[s]}}},10107:i=>{i.exports=function baseReduce(i,s,u,m,v){return v(i,(function(i,v,_){u=m?(m=!1,i):s(u,i,v,_)})),u}},5976:(i,s,u)=>{var m=u(6557),v=u(45357),_=u(30061);i.exports=function baseRest(i,s){return _(v(i,s,m),i+"")}},10611:(i,s,u)=>{var m=u(34865),v=u(71811),_=u(65776),j=u(13218),M=u(40327);i.exports=function baseSet(i,s,u,$){if(!j(i))return i;for(var W=-1,X=(s=v(s,i)).length,Y=X-1,Z=i;null!=Z&&++W{var m=u(6557),v=u(89250),_=v?function(i,s){return v.set(i,s),i}:m;i.exports=_},56560:(i,s,u)=>{var m=u(75703),v=u(38777),_=u(6557),j=v?function(i,s){return v(i,"toString",{configurable:!0,enumerable:!1,value:m(s),writable:!0})}:_;i.exports=j},14259:i=>{i.exports=function baseSlice(i,s,u){var m=-1,v=i.length;s<0&&(s=-s>v?0:v+s),(u=u>v?v:u)<0&&(u+=v),v=s>u?0:u-s>>>0,s>>>=0;for(var _=Array(v);++m{var m=u(89881);i.exports=function baseSome(i,s){var u;return m(i,(function(i,m,v){return!(u=s(i,m,v))})),!!u}},22545:i=>{i.exports=function baseTimes(i,s){for(var u=-1,m=Array(i);++u{var m=u(62705),v=u(29932),_=u(1469),j=u(33448),M=m?m.prototype:void 0,$=M?M.toString:void 0;i.exports=function baseToString(i){if("string"==typeof i)return i;if(_(i))return v(i,baseToString)+"";if(j(i))return $?$.call(i):"";var s=i+"";return"0"==s&&1/i==-Infinity?"-0":s}},27561:(i,s,u)=>{var m=u(67990),v=/^\s+/;i.exports=function baseTrim(i){return i?i.slice(0,m(i)+1).replace(v,""):i}},7518:i=>{i.exports=function baseUnary(i){return function(s){return i(s)}}},57406:(i,s,u)=>{var m=u(71811),v=u(10928),_=u(40292),j=u(40327);i.exports=function baseUnset(i,s){return s=m(s,i),null==(i=_(i,s))||delete i[j(v(s))]}},1757:i=>{i.exports=function baseZipObject(i,s,u){for(var m=-1,v=i.length,_=s.length,j={};++m{i.exports=function cacheHas(i,s){return i.has(s)}},71811:(i,s,u)=>{var m=u(1469),v=u(15403),_=u(55514),j=u(79833);i.exports=function castPath(i,s){return m(i)?i:v(i,s)?[i]:_(j(i))}},40180:(i,s,u)=>{var m=u(14259);i.exports=function castSlice(i,s,u){var v=i.length;return u=void 0===u?v:u,!s&&u>=v?i:m(i,s,u)}},74318:(i,s,u)=>{var m=u(11149);i.exports=function cloneArrayBuffer(i){var s=new i.constructor(i.byteLength);return new m(s).set(new m(i)),s}},64626:(i,s,u)=>{i=u.nmd(i);var m=u(55639),v=s&&!s.nodeType&&s,_=v&&i&&!i.nodeType&&i,j=_&&_.exports===v?m.Buffer:void 0,M=j?j.allocUnsafe:void 0;i.exports=function cloneBuffer(i,s){if(s)return i.slice();var u=i.length,m=M?M(u):new i.constructor(u);return i.copy(m),m}},57157:(i,s,u)=>{var m=u(74318);i.exports=function cloneDataView(i,s){var u=s?m(i.buffer):i.buffer;return new i.constructor(u,i.byteOffset,i.byteLength)}},93147:i=>{var s=/\w*$/;i.exports=function cloneRegExp(i){var u=new i.constructor(i.source,s.exec(i));return u.lastIndex=i.lastIndex,u}},40419:(i,s,u)=>{var m=u(62705),v=m?m.prototype:void 0,_=v?v.valueOf:void 0;i.exports=function cloneSymbol(i){return _?Object(_.call(i)):{}}},77133:(i,s,u)=>{var m=u(74318);i.exports=function cloneTypedArray(i,s){var u=s?m(i.buffer):i.buffer;return new i.constructor(u,i.byteOffset,i.length)}},52157:i=>{var s=Math.max;i.exports=function composeArgs(i,u,m,v){for(var _=-1,j=i.length,M=m.length,$=-1,W=u.length,X=s(j-M,0),Y=Array(W+X),Z=!v;++${var s=Math.max;i.exports=function composeArgsRight(i,u,m,v){for(var _=-1,j=i.length,M=-1,$=m.length,W=-1,X=u.length,Y=s(j-$,0),Z=Array(Y+X),ee=!v;++_{i.exports=function copyArray(i,s){var u=-1,m=i.length;for(s||(s=Array(m));++u{var m=u(34865),v=u(89465);i.exports=function copyObject(i,s,u,_){var j=!u;u||(u={});for(var M=-1,$=s.length;++M<$;){var W=s[M],X=_?_(u[W],i[W],W,u,i):void 0;void 0===X&&(X=i[W]),j?v(u,W,X):m(u,W,X)}return u}},18805:(i,s,u)=>{var m=u(98363),v=u(99551);i.exports=function copySymbols(i,s){return m(i,v(i),s)}},1911:(i,s,u)=>{var m=u(98363),v=u(51442);i.exports=function copySymbolsIn(i,s){return m(i,v(i),s)}},14429:(i,s,u)=>{var m=u(55639)["__core-js_shared__"];i.exports=m},97991:i=>{i.exports=function countHolders(i,s){for(var u=i.length,m=0;u--;)i[u]===s&&++m;return m}},21463:(i,s,u)=>{var m=u(5976),v=u(16612);i.exports=function createAssigner(i){return m((function(s,u){var m=-1,_=u.length,j=_>1?u[_-1]:void 0,M=_>2?u[2]:void 0;for(j=i.length>3&&"function"==typeof j?(_--,j):void 0,M&&v(u[0],u[1],M)&&(j=_<3?void 0:j,_=1),s=Object(s);++m<_;){var $=u[m];$&&i(s,$,m,j)}return s}))}},99291:(i,s,u)=>{var m=u(98612);i.exports=function createBaseEach(i,s){return function(u,v){if(null==u)return u;if(!m(u))return i(u,v);for(var _=u.length,j=s?_:-1,M=Object(u);(s?j--:++j<_)&&!1!==v(M[j],j,M););return u}}},25063:i=>{i.exports=function createBaseFor(i){return function(s,u,m){for(var v=-1,_=Object(s),j=m(s),M=j.length;M--;){var $=j[i?M:++v];if(!1===u(_[$],$,_))break}return s}}},22402:(i,s,u)=>{var m=u(71774),v=u(55639);i.exports=function createBind(i,s,u){var _=1&s,j=m(i);return function wrapper(){return(this&&this!==v&&this instanceof wrapper?j:i).apply(_?u:this,arguments)}}},98805:(i,s,u)=>{var m=u(40180),v=u(62689),_=u(83140),j=u(79833);i.exports=function createCaseFirst(i){return function(s){s=j(s);var u=v(s)?_(s):void 0,M=u?u[0]:s.charAt(0),$=u?m(u,1).join(""):s.slice(1);return M[i]()+$}}},35393:(i,s,u)=>{var m=u(62663),v=u(53816),_=u(58748),j=RegExp("['’]","g");i.exports=function createCompounder(i){return function(s){return m(_(v(s).replace(j,"")),i,"")}}},71774:(i,s,u)=>{var m=u(3118),v=u(13218);i.exports=function createCtor(i){return function(){var s=arguments;switch(s.length){case 0:return new i;case 1:return new i(s[0]);case 2:return new i(s[0],s[1]);case 3:return new i(s[0],s[1],s[2]);case 4:return new i(s[0],s[1],s[2],s[3]);case 5:return new i(s[0],s[1],s[2],s[3],s[4]);case 6:return new i(s[0],s[1],s[2],s[3],s[4],s[5]);case 7:return new i(s[0],s[1],s[2],s[3],s[4],s[5],s[6])}var u=m(i.prototype),_=i.apply(u,s);return v(_)?_:u}}},46347:(i,s,u)=>{var m=u(96874),v=u(71774),_=u(86935),j=u(94487),M=u(20893),$=u(46460),W=u(55639);i.exports=function createCurry(i,s,u){var X=v(i);return function wrapper(){for(var v=arguments.length,Y=Array(v),Z=v,ee=M(wrapper);Z--;)Y[Z]=arguments[Z];var ae=v<3&&Y[0]!==ee&&Y[v-1]!==ee?[]:$(Y,ee);return(v-=ae.length){var m=u(67206),v=u(98612),_=u(3674);i.exports=function createFind(i){return function(s,u,j){var M=Object(s);if(!v(s)){var $=m(u,3);s=_(s),u=function(i){return $(M[i],i,M)}}var W=i(s,u,j);return W>-1?M[$?s[W]:W]:void 0}}},86935:(i,s,u)=>{var m=u(52157),v=u(14054),_=u(97991),j=u(71774),M=u(94487),$=u(20893),W=u(90451),X=u(46460),Y=u(55639);i.exports=function createHybrid(i,s,u,Z,ee,ae,ie,le,ce,pe){var de=128&s,fe=1&s,ye=2&s,be=24&s,_e=512&s,we=ye?void 0:j(i);return function wrapper(){for(var Se=arguments.length,xe=Array(Se),Pe=Se;Pe--;)xe[Pe]=arguments[Pe];if(be)var Ie=$(wrapper),Te=_(xe,Ie);if(Z&&(xe=m(xe,Z,ee,be)),ae&&(xe=v(xe,ae,ie,be)),Se-=Te,be&&Se1&&xe.reverse(),de&&ce{var m=u(96874),v=u(71774),_=u(55639);i.exports=function createPartial(i,s,u,j){var M=1&s,$=v(i);return function wrapper(){for(var s=-1,v=arguments.length,W=-1,X=j.length,Y=Array(X+v),Z=this&&this!==_&&this instanceof wrapper?$:i;++W{var m=u(86528),v=u(258),_=u(69255);i.exports=function createRecurry(i,s,u,j,M,$,W,X,Y,Z){var ee=8&s;s|=ee?32:64,4&(s&=~(ee?64:32))||(s&=-4);var ae=[i,s,M,ee?$:void 0,ee?W:void 0,ee?void 0:$,ee?void 0:W,X,Y,Z],ie=u.apply(void 0,ae);return m(i)&&v(ie,ae),ie.placeholder=j,_(ie,i,s)}},97727:(i,s,u)=>{var m=u(28045),v=u(22402),_=u(46347),j=u(86935),M=u(84375),$=u(66833),W=u(63833),X=u(258),Y=u(69255),Z=u(40554),ee=Math.max;i.exports=function createWrap(i,s,u,ae,ie,le,ce,pe){var de=2&s;if(!de&&"function"!=typeof i)throw new TypeError("Expected a function");var fe=ae?ae.length:0;if(fe||(s&=-97,ae=ie=void 0),ce=void 0===ce?ce:ee(Z(ce),0),pe=void 0===pe?pe:Z(pe),fe-=ie?ie.length:0,64&s){var ye=ae,be=ie;ae=ie=void 0}var _e=de?void 0:$(i),we=[i,s,u,ae,ie,ye,be,le,ce,pe];if(_e&&W(we,_e),i=we[0],s=we[1],u=we[2],ae=we[3],ie=we[4],!(pe=we[9]=void 0===we[9]?de?0:i.length:ee(we[9]-fe,0))&&24&s&&(s&=-25),s&&1!=s)Se=8==s||16==s?_(i,s,pe):32!=s&&33!=s||ie.length?j.apply(void 0,we):M(i,s,u,ae);else var Se=v(i,s,u);return Y((_e?m:X)(Se,we),i,s)}},60696:(i,s,u)=>{var m=u(68630);i.exports=function customOmitClone(i){return m(i)?void 0:i}},69389:(i,s,u)=>{var m=u(18674)({À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"});i.exports=m},38777:(i,s,u)=>{var m=u(10852),v=function(){try{var i=m(Object,"defineProperty");return i({},"",{}),i}catch(i){}}();i.exports=v},67114:(i,s,u)=>{var m=u(88668),v=u(82908),_=u(74757);i.exports=function equalArrays(i,s,u,j,M,$){var W=1&u,X=i.length,Y=s.length;if(X!=Y&&!(W&&Y>X))return!1;var Z=$.get(i),ee=$.get(s);if(Z&&ee)return Z==s&&ee==i;var ae=-1,ie=!0,le=2&u?new m:void 0;for($.set(i,s),$.set(s,i);++ae{var m=u(62705),v=u(11149),_=u(77813),j=u(67114),M=u(68776),$=u(21814),W=m?m.prototype:void 0,X=W?W.valueOf:void 0;i.exports=function equalByTag(i,s,u,m,W,Y,Z){switch(u){case"[object DataView]":if(i.byteLength!=s.byteLength||i.byteOffset!=s.byteOffset)return!1;i=i.buffer,s=s.buffer;case"[object ArrayBuffer]":return!(i.byteLength!=s.byteLength||!Y(new v(i),new v(s)));case"[object Boolean]":case"[object Date]":case"[object Number]":return _(+i,+s);case"[object Error]":return i.name==s.name&&i.message==s.message;case"[object RegExp]":case"[object String]":return i==s+"";case"[object Map]":var ee=M;case"[object Set]":var ae=1&m;if(ee||(ee=$),i.size!=s.size&&!ae)return!1;var ie=Z.get(i);if(ie)return ie==s;m|=2,Z.set(i,s);var le=j(ee(i),ee(s),m,W,Y,Z);return Z.delete(i),le;case"[object Symbol]":if(X)return X.call(i)==X.call(s)}return!1}},16096:(i,s,u)=>{var m=u(58234),v=Object.prototype.hasOwnProperty;i.exports=function equalObjects(i,s,u,_,j,M){var $=1&u,W=m(i),X=W.length;if(X!=m(s).length&&!$)return!1;for(var Y=X;Y--;){var Z=W[Y];if(!($?Z in s:v.call(s,Z)))return!1}var ee=M.get(i),ae=M.get(s);if(ee&&ae)return ee==s&&ae==i;var ie=!0;M.set(i,s),M.set(s,i);for(var le=$;++Y{var m=u(85564),v=u(45357),_=u(30061);i.exports=function flatRest(i){return _(v(i,void 0,m),i+"")}},31957:(i,s,u)=>{var m="object"==typeof u.g&&u.g&&u.g.Object===Object&&u.g;i.exports=m},58234:(i,s,u)=>{var m=u(68866),v=u(99551),_=u(3674);i.exports=function getAllKeys(i){return m(i,_,v)}},46904:(i,s,u)=>{var m=u(68866),v=u(51442),_=u(81704);i.exports=function getAllKeysIn(i){return m(i,_,v)}},66833:(i,s,u)=>{var m=u(89250),v=u(50308),_=m?function(i){return m.get(i)}:v;i.exports=_},97658:(i,s,u)=>{var m=u(52060),v=Object.prototype.hasOwnProperty;i.exports=function getFuncName(i){for(var s=i.name+"",u=m[s],_=v.call(m,s)?u.length:0;_--;){var j=u[_],M=j.func;if(null==M||M==i)return j.name}return s}},20893:i=>{i.exports=function getHolder(i){return i.placeholder}},45050:(i,s,u)=>{var m=u(37019);i.exports=function getMapData(i,s){var u=i.__data__;return m(s)?u["string"==typeof s?"string":"hash"]:u.map}},1499:(i,s,u)=>{var m=u(89162),v=u(3674);i.exports=function getMatchData(i){for(var s=v(i),u=s.length;u--;){var _=s[u],j=i[_];s[u]=[_,j,m(j)]}return s}},10852:(i,s,u)=>{var m=u(28458),v=u(47801);i.exports=function getNative(i,s){var u=v(i,s);return m(u)?u:void 0}},85924:(i,s,u)=>{var m=u(5569)(Object.getPrototypeOf,Object);i.exports=m},89607:(i,s,u)=>{var m=u(62705),v=Object.prototype,_=v.hasOwnProperty,j=v.toString,M=m?m.toStringTag:void 0;i.exports=function getRawTag(i){var s=_.call(i,M),u=i[M];try{i[M]=void 0;var m=!0}catch(i){}var v=j.call(i);return m&&(s?i[M]=u:delete i[M]),v}},99551:(i,s,u)=>{var m=u(34963),v=u(70479),_=Object.prototype.propertyIsEnumerable,j=Object.getOwnPropertySymbols,M=j?function(i){return null==i?[]:(i=Object(i),m(j(i),(function(s){return _.call(i,s)})))}:v;i.exports=M},51442:(i,s,u)=>{var m=u(62488),v=u(85924),_=u(99551),j=u(70479),M=Object.getOwnPropertySymbols?function(i){for(var s=[];i;)m(s,_(i)),i=v(i);return s}:j;i.exports=M},64160:(i,s,u)=>{var m=u(18552),v=u(57071),_=u(53818),j=u(58525),M=u(70577),$=u(44239),W=u(80346),X="[object Map]",Y="[object Promise]",Z="[object Set]",ee="[object WeakMap]",ae="[object DataView]",ie=W(m),le=W(v),ce=W(_),pe=W(j),de=W(M),fe=$;(m&&fe(new m(new ArrayBuffer(1)))!=ae||v&&fe(new v)!=X||_&&fe(_.resolve())!=Y||j&&fe(new j)!=Z||M&&fe(new M)!=ee)&&(fe=function(i){var s=$(i),u="[object Object]"==s?i.constructor:void 0,m=u?W(u):"";if(m)switch(m){case ie:return ae;case le:return X;case ce:return Y;case pe:return Z;case de:return ee}return s}),i.exports=fe},47801:i=>{i.exports=function getValue(i,s){return null==i?void 0:i[s]}},58775:i=>{var s=/\{\n\/\* \[wrapped with (.+)\] \*/,u=/,? & /;i.exports=function getWrapDetails(i){var m=i.match(s);return m?m[1].split(u):[]}},222:(i,s,u)=>{var m=u(71811),v=u(35694),_=u(1469),j=u(65776),M=u(41780),$=u(40327);i.exports=function hasPath(i,s,u){for(var W=-1,X=(s=m(s,i)).length,Y=!1;++W{var s=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]");i.exports=function hasUnicode(i){return s.test(i)}},93157:i=>{var s=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;i.exports=function hasUnicodeWord(i){return s.test(i)}},51789:(i,s,u)=>{var m=u(94536);i.exports=function hashClear(){this.__data__=m?m(null):{},this.size=0}},80401:i=>{i.exports=function hashDelete(i){var s=this.has(i)&&delete this.__data__[i];return this.size-=s?1:0,s}},57667:(i,s,u)=>{var m=u(94536),v=Object.prototype.hasOwnProperty;i.exports=function hashGet(i){var s=this.__data__;if(m){var u=s[i];return"__lodash_hash_undefined__"===u?void 0:u}return v.call(s,i)?s[i]:void 0}},21327:(i,s,u)=>{var m=u(94536),v=Object.prototype.hasOwnProperty;i.exports=function hashHas(i){var s=this.__data__;return m?void 0!==s[i]:v.call(s,i)}},81866:(i,s,u)=>{var m=u(94536);i.exports=function hashSet(i,s){var u=this.__data__;return this.size+=this.has(i)?0:1,u[i]=m&&void 0===s?"__lodash_hash_undefined__":s,this}},43824:i=>{var s=Object.prototype.hasOwnProperty;i.exports=function initCloneArray(i){var u=i.length,m=new i.constructor(u);return u&&"string"==typeof i[0]&&s.call(i,"index")&&(m.index=i.index,m.input=i.input),m}},29148:(i,s,u)=>{var m=u(74318),v=u(57157),_=u(93147),j=u(40419),M=u(77133);i.exports=function initCloneByTag(i,s,u){var $=i.constructor;switch(s){case"[object ArrayBuffer]":return m(i);case"[object Boolean]":case"[object Date]":return new $(+i);case"[object DataView]":return v(i,u);case"[object Float32Array]":case"[object Float64Array]":case"[object Int8Array]":case"[object Int16Array]":case"[object Int32Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":case"[object Uint16Array]":case"[object Uint32Array]":return M(i,u);case"[object Map]":case"[object Set]":return new $;case"[object Number]":case"[object String]":return new $(i);case"[object RegExp]":return _(i);case"[object Symbol]":return j(i)}}},38517:(i,s,u)=>{var m=u(3118),v=u(85924),_=u(25726);i.exports=function initCloneObject(i){return"function"!=typeof i.constructor||_(i)?{}:m(v(i))}},83112:i=>{var s=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/;i.exports=function insertWrapDetails(i,u){var m=u.length;if(!m)return i;var v=m-1;return u[v]=(m>1?"& ":"")+u[v],u=u.join(m>2?", ":" "),i.replace(s,"{\n/* [wrapped with "+u+"] */\n")}},37285:(i,s,u)=>{var m=u(62705),v=u(35694),_=u(1469),j=m?m.isConcatSpreadable:void 0;i.exports=function isFlattenable(i){return _(i)||v(i)||!!(j&&i&&i[j])}},65776:i=>{var s=/^(?:0|[1-9]\d*)$/;i.exports=function isIndex(i,u){var m=typeof i;return!!(u=null==u?9007199254740991:u)&&("number"==m||"symbol"!=m&&s.test(i))&&i>-1&&i%1==0&&i{var m=u(77813),v=u(98612),_=u(65776),j=u(13218);i.exports=function isIterateeCall(i,s,u){if(!j(u))return!1;var M=typeof s;return!!("number"==M?v(u)&&_(s,u.length):"string"==M&&s in u)&&m(u[s],i)}},15403:(i,s,u)=>{var m=u(1469),v=u(33448),_=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,j=/^\w*$/;i.exports=function isKey(i,s){if(m(i))return!1;var u=typeof i;return!("number"!=u&&"symbol"!=u&&"boolean"!=u&&null!=i&&!v(i))||(j.test(i)||!_.test(i)||null!=s&&i in Object(s))}},37019:i=>{i.exports=function isKeyable(i){var s=typeof i;return"string"==s||"number"==s||"symbol"==s||"boolean"==s?"__proto__"!==i:null===i}},86528:(i,s,u)=>{var m=u(96425),v=u(66833),_=u(97658),j=u(8111);i.exports=function isLaziable(i){var s=_(i),u=j[s];if("function"!=typeof u||!(s in m.prototype))return!1;if(i===u)return!0;var M=v(u);return!!M&&i===M[0]}},15346:(i,s,u)=>{var m,v=u(14429),_=(m=/[^.]+$/.exec(v&&v.keys&&v.keys.IE_PROTO||""))?"Symbol(src)_1."+m:"";i.exports=function isMasked(i){return!!_&&_ in i}},25726:i=>{var s=Object.prototype;i.exports=function isPrototype(i){var u=i&&i.constructor;return i===("function"==typeof u&&u.prototype||s)}},89162:(i,s,u)=>{var m=u(13218);i.exports=function isStrictComparable(i){return i==i&&!m(i)}},27040:i=>{i.exports=function listCacheClear(){this.__data__=[],this.size=0}},14125:(i,s,u)=>{var m=u(18470),v=Array.prototype.splice;i.exports=function listCacheDelete(i){var s=this.__data__,u=m(s,i);return!(u<0)&&(u==s.length-1?s.pop():v.call(s,u,1),--this.size,!0)}},82117:(i,s,u)=>{var m=u(18470);i.exports=function listCacheGet(i){var s=this.__data__,u=m(s,i);return u<0?void 0:s[u][1]}},67518:(i,s,u)=>{var m=u(18470);i.exports=function listCacheHas(i){return m(this.__data__,i)>-1}},54705:(i,s,u)=>{var m=u(18470);i.exports=function listCacheSet(i,s){var u=this.__data__,v=m(u,i);return v<0?(++this.size,u.push([i,s])):u[v][1]=s,this}},24785:(i,s,u)=>{var m=u(1989),v=u(38407),_=u(57071);i.exports=function mapCacheClear(){this.size=0,this.__data__={hash:new m,map:new(_||v),string:new m}}},11285:(i,s,u)=>{var m=u(45050);i.exports=function mapCacheDelete(i){var s=m(this,i).delete(i);return this.size-=s?1:0,s}},96e3:(i,s,u)=>{var m=u(45050);i.exports=function mapCacheGet(i){return m(this,i).get(i)}},49916:(i,s,u)=>{var m=u(45050);i.exports=function mapCacheHas(i){return m(this,i).has(i)}},95265:(i,s,u)=>{var m=u(45050);i.exports=function mapCacheSet(i,s){var u=m(this,i),v=u.size;return u.set(i,s),this.size+=u.size==v?0:1,this}},68776:i=>{i.exports=function mapToArray(i){var s=-1,u=Array(i.size);return i.forEach((function(i,m){u[++s]=[m,i]})),u}},42634:i=>{i.exports=function matchesStrictComparable(i,s){return function(u){return null!=u&&(u[i]===s&&(void 0!==s||i in Object(u)))}}},24523:(i,s,u)=>{var m=u(88306);i.exports=function memoizeCapped(i){var s=m(i,(function(i){return 500===u.size&&u.clear(),i})),u=s.cache;return s}},63833:(i,s,u)=>{var m=u(52157),v=u(14054),_=u(46460),j="__lodash_placeholder__",M=128,$=Math.min;i.exports=function mergeData(i,s){var u=i[1],W=s[1],X=u|W,Y=X<131,Z=W==M&&8==u||W==M&&256==u&&i[7].length<=s[8]||384==W&&s[7].length<=s[8]&&8==u;if(!Y&&!Z)return i;1&W&&(i[2]=s[2],X|=1&u?0:4);var ee=s[3];if(ee){var ae=i[3];i[3]=ae?m(ae,ee,s[4]):ee,i[4]=ae?_(i[3],j):s[4]}return(ee=s[5])&&(ae=i[5],i[5]=ae?v(ae,ee,s[6]):ee,i[6]=ae?_(i[5],j):s[6]),(ee=s[7])&&(i[7]=ee),W&M&&(i[8]=null==i[8]?s[8]:$(i[8],s[8])),null==i[9]&&(i[9]=s[9]),i[0]=s[0],i[1]=X,i}},89250:(i,s,u)=>{var m=u(70577),v=m&&new m;i.exports=v},94536:(i,s,u)=>{var m=u(10852)(Object,"create");i.exports=m},86916:(i,s,u)=>{var m=u(5569)(Object.keys,Object);i.exports=m},33498:i=>{i.exports=function nativeKeysIn(i){var s=[];if(null!=i)for(var u in Object(i))s.push(u);return s}},31167:(i,s,u)=>{i=u.nmd(i);var m=u(31957),v=s&&!s.nodeType&&s,_=v&&i&&!i.nodeType&&i,j=_&&_.exports===v&&m.process,M=function(){try{var i=_&&_.require&&_.require("util").types;return i||j&&j.binding&&j.binding("util")}catch(i){}}();i.exports=M},2333:i=>{var s=Object.prototype.toString;i.exports=function objectToString(i){return s.call(i)}},5569:i=>{i.exports=function overArg(i,s){return function(u){return i(s(u))}}},45357:(i,s,u)=>{var m=u(96874),v=Math.max;i.exports=function overRest(i,s,u){return s=v(void 0===s?i.length-1:s,0),function(){for(var _=arguments,j=-1,M=v(_.length-s,0),$=Array(M);++j{var m=u(97786),v=u(14259);i.exports=function parent(i,s){return s.length<2?i:m(i,v(s,0,-1))}},52060:i=>{i.exports={}},90451:(i,s,u)=>{var m=u(278),v=u(65776),_=Math.min;i.exports=function reorder(i,s){for(var u=i.length,j=_(s.length,u),M=m(i);j--;){var $=s[j];i[j]=v($,u)?M[$]:void 0}return i}},46460:i=>{var s="__lodash_placeholder__";i.exports=function replaceHolders(i,u){for(var m=-1,v=i.length,_=0,j=[];++m{var m=u(31957),v="object"==typeof self&&self&&self.Object===Object&&self,_=m||v||Function("return this")();i.exports=_},36390:i=>{i.exports=function safeGet(i,s){if(("constructor"!==s||"function"!=typeof i[s])&&"__proto__"!=s)return i[s]}},90619:i=>{i.exports=function setCacheAdd(i){return this.__data__.set(i,"__lodash_hash_undefined__"),this}},72385:i=>{i.exports=function setCacheHas(i){return this.__data__.has(i)}},258:(i,s,u)=>{var m=u(28045),v=u(21275)(m);i.exports=v},21814:i=>{i.exports=function setToArray(i){var s=-1,u=Array(i.size);return i.forEach((function(i){u[++s]=i})),u}},30061:(i,s,u)=>{var m=u(56560),v=u(21275)(m);i.exports=v},69255:(i,s,u)=>{var m=u(58775),v=u(83112),_=u(30061),j=u(87241);i.exports=function setWrapToString(i,s,u){var M=s+"";return _(i,v(M,j(m(M),u)))}},21275:i=>{var s=Date.now;i.exports=function shortOut(i){var u=0,m=0;return function(){var v=s(),_=16-(v-m);if(m=v,_>0){if(++u>=800)return arguments[0]}else u=0;return i.apply(void 0,arguments)}}},37465:(i,s,u)=>{var m=u(38407);i.exports=function stackClear(){this.__data__=new m,this.size=0}},63779:i=>{i.exports=function stackDelete(i){var s=this.__data__,u=s.delete(i);return this.size=s.size,u}},67599:i=>{i.exports=function stackGet(i){return this.__data__.get(i)}},44758:i=>{i.exports=function stackHas(i){return this.__data__.has(i)}},34309:(i,s,u)=>{var m=u(38407),v=u(57071),_=u(83369);i.exports=function stackSet(i,s){var u=this.__data__;if(u instanceof m){var j=u.__data__;if(!v||j.length<199)return j.push([i,s]),this.size=++u.size,this;u=this.__data__=new _(j)}return u.set(i,s),this.size=u.size,this}},42351:i=>{i.exports=function strictIndexOf(i,s,u){for(var m=u-1,v=i.length;++m{var m=u(44286),v=u(62689),_=u(676);i.exports=function stringToArray(i){return v(i)?_(i):m(i)}},55514:(i,s,u)=>{var m=u(24523),v=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,_=/\\(\\)?/g,j=m((function(i){var s=[];return 46===i.charCodeAt(0)&&s.push(""),i.replace(v,(function(i,u,m,v){s.push(m?v.replace(_,"$1"):u||i)})),s}));i.exports=j},40327:(i,s,u)=>{var m=u(33448);i.exports=function toKey(i){if("string"==typeof i||m(i))return i;var s=i+"";return"0"==s&&1/i==-Infinity?"-0":s}},80346:i=>{var s=Function.prototype.toString;i.exports=function toSource(i){if(null!=i){try{return s.call(i)}catch(i){}try{return i+""}catch(i){}}return""}},67990:i=>{var s=/\s/;i.exports=function trimmedEndIndex(i){for(var u=i.length;u--&&s.test(i.charAt(u)););return u}},676:i=>{var s="\\ud800-\\udfff",u="["+s+"]",m="[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]",v="\\ud83c[\\udffb-\\udfff]",_="[^"+s+"]",j="(?:\\ud83c[\\udde6-\\uddff]){2}",M="[\\ud800-\\udbff][\\udc00-\\udfff]",$="(?:"+m+"|"+v+")"+"?",W="[\\ufe0e\\ufe0f]?",X=W+$+("(?:\\u200d(?:"+[_,j,M].join("|")+")"+W+$+")*"),Y="(?:"+[_+m+"?",m,j,M,u].join("|")+")",Z=RegExp(v+"(?="+v+")|"+Y+X,"g");i.exports=function unicodeToArray(i){return i.match(Z)||[]}},2757:i=>{var s="\\ud800-\\udfff",u="\\u2700-\\u27bf",m="a-z\\xdf-\\xf6\\xf8-\\xff",v="A-Z\\xc0-\\xd6\\xd8-\\xde",_="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",j="["+_+"]",M="\\d+",$="["+u+"]",W="["+m+"]",X="[^"+s+_+M+u+m+v+"]",Y="(?:\\ud83c[\\udde6-\\uddff]){2}",Z="[\\ud800-\\udbff][\\udc00-\\udfff]",ee="["+v+"]",ae="(?:"+W+"|"+X+")",ie="(?:"+ee+"|"+X+")",le="(?:['’](?:d|ll|m|re|s|t|ve))?",ce="(?:['’](?:D|LL|M|RE|S|T|VE))?",pe="(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?",de="[\\ufe0e\\ufe0f]?",fe=de+pe+("(?:\\u200d(?:"+["[^"+s+"]",Y,Z].join("|")+")"+de+pe+")*"),ye="(?:"+[$,Y,Z].join("|")+")"+fe,be=RegExp([ee+"?"+W+"+"+le+"(?="+[j,ee,"$"].join("|")+")",ie+"+"+ce+"(?="+[j,ee+ae,"$"].join("|")+")",ee+"?"+ae+"+"+le,ee+"+"+ce,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",M,ye].join("|"),"g");i.exports=function unicodeWords(i){return i.match(be)||[]}},87241:(i,s,u)=>{var m=u(77412),v=u(47443),_=[["ary",128],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",32],["partialRight",64],["rearg",256]];i.exports=function updateWrapDetails(i,s){return m(_,(function(u){var m="_."+u[0];s&u[1]&&!v(i,m)&&i.push(m)})),i.sort()}},21913:(i,s,u)=>{var m=u(96425),v=u(7548),_=u(278);i.exports=function wrapperClone(i){if(i instanceof m)return i.clone();var s=new v(i.__wrapped__,i.__chain__);return s.__actions__=_(i.__actions__),s.__index__=i.__index__,s.__values__=i.__values__,s}},39514:(i,s,u)=>{var m=u(97727);i.exports=function ary(i,s,u){return s=u?void 0:s,s=i&&null==s?i.length:s,m(i,128,void 0,void 0,void 0,void 0,s)}},68929:(i,s,u)=>{var m=u(48403),v=u(35393)((function(i,s,u){return s=s.toLowerCase(),i+(u?m(s):s)}));i.exports=v},48403:(i,s,u)=>{var m=u(79833),v=u(11700);i.exports=function capitalize(i){return v(m(i).toLowerCase())}},66678:(i,s,u)=>{var m=u(85990);i.exports=function clone(i){return m(i,4)}},75703:i=>{i.exports=function constant(i){return function(){return i}}},40087:(i,s,u)=>{var m=u(97727);function curry(i,s,u){var v=m(i,8,void 0,void 0,void 0,void 0,void 0,s=u?void 0:s);return v.placeholder=curry.placeholder,v}curry.placeholder={},i.exports=curry},23279:(i,s,u)=>{var m=u(13218),v=u(7771),_=u(14841),j=Math.max,M=Math.min;i.exports=function debounce(i,s,u){var $,W,X,Y,Z,ee,ae=0,ie=!1,le=!1,ce=!0;if("function"!=typeof i)throw new TypeError("Expected a function");function invokeFunc(s){var u=$,m=W;return $=W=void 0,ae=s,Y=i.apply(m,u)}function shouldInvoke(i){var u=i-ee;return void 0===ee||u>=s||u<0||le&&i-ae>=X}function timerExpired(){var i=v();if(shouldInvoke(i))return trailingEdge(i);Z=setTimeout(timerExpired,function remainingWait(i){var u=s-(i-ee);return le?M(u,X-(i-ae)):u}(i))}function trailingEdge(i){return Z=void 0,ce&&$?invokeFunc(i):($=W=void 0,Y)}function debounced(){var i=v(),u=shouldInvoke(i);if($=arguments,W=this,ee=i,u){if(void 0===Z)return function leadingEdge(i){return ae=i,Z=setTimeout(timerExpired,s),ie?invokeFunc(i):Y}(ee);if(le)return clearTimeout(Z),Z=setTimeout(timerExpired,s),invokeFunc(ee)}return void 0===Z&&(Z=setTimeout(timerExpired,s)),Y}return s=_(s)||0,m(u)&&(ie=!!u.leading,X=(le="maxWait"in u)?j(_(u.maxWait)||0,s):X,ce="trailing"in u?!!u.trailing:ce),debounced.cancel=function cancel(){void 0!==Z&&clearTimeout(Z),ae=0,$=ee=W=Z=void 0},debounced.flush=function flush(){return void 0===Z?Y:trailingEdge(v())},debounced}},53816:(i,s,u)=>{var m=u(69389),v=u(79833),_=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,j=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g");i.exports=function deburr(i){return(i=v(i))&&i.replace(_,m).replace(j,"")}},77813:i=>{i.exports=function eq(i,s){return i===s||i!=i&&s!=s}},13311:(i,s,u)=>{var m=u(67740)(u(30998));i.exports=m},30998:(i,s,u)=>{var m=u(41848),v=u(67206),_=u(40554),j=Math.max;i.exports=function findIndex(i,s,u){var M=null==i?0:i.length;if(!M)return-1;var $=null==u?0:_(u);return $<0&&($=j(M+$,0)),m(i,v(s,3),$)}},85564:(i,s,u)=>{var m=u(21078);i.exports=function flatten(i){return(null==i?0:i.length)?m(i,1):[]}},84599:(i,s,u)=>{var m=u(68836),v=u(69306),_=Array.prototype.push;function baseAry(i,s){return 2==s?function(s,u){return i(s,u)}:function(s){return i(s)}}function cloneArray(i){for(var s=i?i.length:0,u=Array(s);s--;)u[s]=i[s];return u}function wrapImmutable(i,s){return function(){var u=arguments.length;if(u){for(var m=Array(u);u--;)m[u]=arguments[u];var v=m[0]=s.apply(void 0,m);return i.apply(void 0,m),v}}}i.exports=function baseConvert(i,s,u,j){var M="function"==typeof s,$=s===Object(s);if($&&(j=u,u=s,s=void 0),null==u)throw new TypeError;j||(j={});var W={cap:!("cap"in j)||j.cap,curry:!("curry"in j)||j.curry,fixed:!("fixed"in j)||j.fixed,immutable:!("immutable"in j)||j.immutable,rearg:!("rearg"in j)||j.rearg},X=M?u:v,Y="curry"in j&&j.curry,Z="fixed"in j&&j.fixed,ee="rearg"in j&&j.rearg,ae=M?u.runInContext():void 0,ie=M?u:{ary:i.ary,assign:i.assign,clone:i.clone,curry:i.curry,forEach:i.forEach,isArray:i.isArray,isError:i.isError,isFunction:i.isFunction,isWeakMap:i.isWeakMap,iteratee:i.iteratee,keys:i.keys,rearg:i.rearg,toInteger:i.toInteger,toPath:i.toPath},le=ie.ary,ce=ie.assign,pe=ie.clone,de=ie.curry,fe=ie.forEach,ye=ie.isArray,be=ie.isError,_e=ie.isFunction,we=ie.isWeakMap,Se=ie.keys,xe=ie.rearg,Pe=ie.toInteger,Ie=ie.toPath,Te=Se(m.aryMethod),Re={castArray:function(i){return function(){var s=arguments[0];return ye(s)?i(cloneArray(s)):i.apply(void 0,arguments)}},iteratee:function(i){return function(){var s=arguments[1],u=i(arguments[0],s),m=u.length;return W.cap&&"number"==typeof s?(s=s>2?s-2:1,m&&m<=s?u:baseAry(u,s)):u}},mixin:function(i){return function(s){var u=this;if(!_e(u))return i(u,Object(s));var m=[];return fe(Se(s),(function(i){_e(s[i])&&m.push([i,u.prototype[i]])})),i(u,Object(s)),fe(m,(function(i){var s=i[1];_e(s)?u.prototype[i[0]]=s:delete u.prototype[i[0]]})),u}},nthArg:function(i){return function(s){var u=s<0?1:Pe(s)+1;return de(i(s),u)}},rearg:function(i){return function(s,u){var m=u?u.length:0;return de(i(s,u),m)}},runInContext:function(s){return function(u){return baseConvert(i,s(u),j)}}};function castCap(i,s){if(W.cap){var u=m.iterateeRearg[i];if(u)return function iterateeRearg(i,s){return overArg(i,(function(i){var u=s.length;return function baseArity(i,s){return 2==s?function(s,u){return i.apply(void 0,arguments)}:function(s){return i.apply(void 0,arguments)}}(xe(baseAry(i,u),s),u)}))}(s,u);var v=!M&&m.iterateeAry[i];if(v)return function iterateeAry(i,s){return overArg(i,(function(i){return"function"==typeof i?baseAry(i,s):i}))}(s,v)}return s}function castFixed(i,s,u){if(W.fixed&&(Z||!m.skipFixed[i])){var v=m.methodSpread[i],j=v&&v.start;return void 0===j?le(s,u):function flatSpread(i,s){return function(){for(var u=arguments.length,m=u-1,v=Array(u);u--;)v[u]=arguments[u];var j=v[s],M=v.slice(0,s);return j&&_.apply(M,j),s!=m&&_.apply(M,v.slice(s+1)),i.apply(this,M)}}(s,j)}return s}function castRearg(i,s,u){return W.rearg&&u>1&&(ee||!m.skipRearg[i])?xe(s,m.methodRearg[i]||m.aryRearg[u]):s}function cloneByPath(i,s){for(var u=-1,m=(s=Ie(s)).length,v=m-1,_=pe(Object(i)),j=_;null!=j&&++u1?de(s,u):s}(0,v=castCap(_,v),i),!1}})),!v})),v||(v=j),v==s&&(v=Y?de(v,1):function(){return s.apply(this,arguments)}),v.convert=createConverter(_,s),v.placeholder=s.placeholder=u,v}if(!$)return wrap(s,u,X);var qe=u,ze=[];return fe(Te,(function(i){fe(m.aryMethod[i],(function(i){var s=qe[m.remap[i]||i];s&&ze.push([i,wrap(i,s,qe)])}))})),fe(Se(qe),(function(i){var s=qe[i];if("function"==typeof s){for(var u=ze.length;u--;)if(ze[u][0]==i)return;s.convert=createConverter(i,s),ze.push([i,s])}})),fe(ze,(function(i){qe[i[0]]=i[1]})),qe.convert=function convertLib(i){return qe.runInContext.convert(i)(void 0)},qe.placeholder=qe,fe(Se(qe),(function(i){fe(m.realToAlias[i]||[],(function(s){qe[s]=qe[i]}))})),qe}},68836:(i,s)=>{s.aliasToReal={each:"forEach",eachRight:"forEachRight",entries:"toPairs",entriesIn:"toPairsIn",extend:"assignIn",extendAll:"assignInAll",extendAllWith:"assignInAllWith",extendWith:"assignInWith",first:"head",conforms:"conformsTo",matches:"isMatch",property:"get",__:"placeholder",F:"stubFalse",T:"stubTrue",all:"every",allPass:"overEvery",always:"constant",any:"some",anyPass:"overSome",apply:"spread",assoc:"set",assocPath:"set",complement:"negate",compose:"flowRight",contains:"includes",dissoc:"unset",dissocPath:"unset",dropLast:"dropRight",dropLastWhile:"dropRightWhile",equals:"isEqual",identical:"eq",indexBy:"keyBy",init:"initial",invertObj:"invert",juxt:"over",omitAll:"omit",nAry:"ary",path:"get",pathEq:"matchesProperty",pathOr:"getOr",paths:"at",pickAll:"pick",pipe:"flow",pluck:"map",prop:"get",propEq:"matchesProperty",propOr:"getOr",props:"at",symmetricDifference:"xor",symmetricDifferenceBy:"xorBy",symmetricDifferenceWith:"xorWith",takeLast:"takeRight",takeLastWhile:"takeRightWhile",unapply:"rest",unnest:"flatten",useWith:"overArgs",where:"conformsTo",whereEq:"isMatch",zipObj:"zipObject"},s.aryMethod={1:["assignAll","assignInAll","attempt","castArray","ceil","create","curry","curryRight","defaultsAll","defaultsDeepAll","floor","flow","flowRight","fromPairs","invert","iteratee","memoize","method","mergeAll","methodOf","mixin","nthArg","over","overEvery","overSome","rest","reverse","round","runInContext","spread","template","trim","trimEnd","trimStart","uniqueId","words","zipAll"],2:["add","after","ary","assign","assignAllWith","assignIn","assignInAllWith","at","before","bind","bindAll","bindKey","chunk","cloneDeepWith","cloneWith","concat","conformsTo","countBy","curryN","curryRightN","debounce","defaults","defaultsDeep","defaultTo","delay","difference","divide","drop","dropRight","dropRightWhile","dropWhile","endsWith","eq","every","filter","find","findIndex","findKey","findLast","findLastIndex","findLastKey","flatMap","flatMapDeep","flattenDepth","forEach","forEachRight","forIn","forInRight","forOwn","forOwnRight","get","groupBy","gt","gte","has","hasIn","includes","indexOf","intersection","invertBy","invoke","invokeMap","isEqual","isMatch","join","keyBy","lastIndexOf","lt","lte","map","mapKeys","mapValues","matchesProperty","maxBy","meanBy","merge","mergeAllWith","minBy","multiply","nth","omit","omitBy","overArgs","pad","padEnd","padStart","parseInt","partial","partialRight","partition","pick","pickBy","propertyOf","pull","pullAll","pullAt","random","range","rangeRight","rearg","reject","remove","repeat","restFrom","result","sampleSize","some","sortBy","sortedIndex","sortedIndexOf","sortedLastIndex","sortedLastIndexOf","sortedUniqBy","split","spreadFrom","startsWith","subtract","sumBy","take","takeRight","takeRightWhile","takeWhile","tap","throttle","thru","times","trimChars","trimCharsEnd","trimCharsStart","truncate","union","uniqBy","uniqWith","unset","unzipWith","without","wrap","xor","zip","zipObject","zipObjectDeep"],3:["assignInWith","assignWith","clamp","differenceBy","differenceWith","findFrom","findIndexFrom","findLastFrom","findLastIndexFrom","getOr","includesFrom","indexOfFrom","inRange","intersectionBy","intersectionWith","invokeArgs","invokeArgsMap","isEqualWith","isMatchWith","flatMapDepth","lastIndexOfFrom","mergeWith","orderBy","padChars","padCharsEnd","padCharsStart","pullAllBy","pullAllWith","rangeStep","rangeStepRight","reduce","reduceRight","replace","set","slice","sortedIndexBy","sortedLastIndexBy","transform","unionBy","unionWith","update","xorBy","xorWith","zipWith"],4:["fill","setWith","updateWith"]},s.aryRearg={2:[1,0],3:[2,0,1],4:[3,2,0,1]},s.iterateeAry={dropRightWhile:1,dropWhile:1,every:1,filter:1,find:1,findFrom:1,findIndex:1,findIndexFrom:1,findKey:1,findLast:1,findLastFrom:1,findLastIndex:1,findLastIndexFrom:1,findLastKey:1,flatMap:1,flatMapDeep:1,flatMapDepth:1,forEach:1,forEachRight:1,forIn:1,forInRight:1,forOwn:1,forOwnRight:1,map:1,mapKeys:1,mapValues:1,partition:1,reduce:2,reduceRight:2,reject:1,remove:1,some:1,takeRightWhile:1,takeWhile:1,times:1,transform:2},s.iterateeRearg={mapKeys:[1],reduceRight:[1,0]},s.methodRearg={assignInAllWith:[1,0],assignInWith:[1,2,0],assignAllWith:[1,0],assignWith:[1,2,0],differenceBy:[1,2,0],differenceWith:[1,2,0],getOr:[2,1,0],intersectionBy:[1,2,0],intersectionWith:[1,2,0],isEqualWith:[1,2,0],isMatchWith:[2,1,0],mergeAllWith:[1,0],mergeWith:[1,2,0],padChars:[2,1,0],padCharsEnd:[2,1,0],padCharsStart:[2,1,0],pullAllBy:[2,1,0],pullAllWith:[2,1,0],rangeStep:[1,2,0],rangeStepRight:[1,2,0],setWith:[3,1,2,0],sortedIndexBy:[2,1,0],sortedLastIndexBy:[2,1,0],unionBy:[1,2,0],unionWith:[1,2,0],updateWith:[3,1,2,0],xorBy:[1,2,0],xorWith:[1,2,0],zipWith:[1,2,0]},s.methodSpread={assignAll:{start:0},assignAllWith:{start:0},assignInAll:{start:0},assignInAllWith:{start:0},defaultsAll:{start:0},defaultsDeepAll:{start:0},invokeArgs:{start:2},invokeArgsMap:{start:2},mergeAll:{start:0},mergeAllWith:{start:0},partial:{start:1},partialRight:{start:1},without:{start:1},zipAll:{start:0}},s.mutate={array:{fill:!0,pull:!0,pullAll:!0,pullAllBy:!0,pullAllWith:!0,pullAt:!0,remove:!0,reverse:!0},object:{assign:!0,assignAll:!0,assignAllWith:!0,assignIn:!0,assignInAll:!0,assignInAllWith:!0,assignInWith:!0,assignWith:!0,defaults:!0,defaultsAll:!0,defaultsDeep:!0,defaultsDeepAll:!0,merge:!0,mergeAll:!0,mergeAllWith:!0,mergeWith:!0},set:{set:!0,setWith:!0,unset:!0,update:!0,updateWith:!0}},s.realToAlias=function(){var i=Object.prototype.hasOwnProperty,u=s.aliasToReal,m={};for(var v in u){var _=u[v];i.call(m,_)?m[_].push(v):m[_]=[v]}return m}(),s.remap={assignAll:"assign",assignAllWith:"assignWith",assignInAll:"assignIn",assignInAllWith:"assignInWith",curryN:"curry",curryRightN:"curryRight",defaultsAll:"defaults",defaultsDeepAll:"defaultsDeep",findFrom:"find",findIndexFrom:"findIndex",findLastFrom:"findLast",findLastIndexFrom:"findLastIndex",getOr:"get",includesFrom:"includes",indexOfFrom:"indexOf",invokeArgs:"invoke",invokeArgsMap:"invokeMap",lastIndexOfFrom:"lastIndexOf",mergeAll:"merge",mergeAllWith:"mergeWith",padChars:"pad",padCharsEnd:"padEnd",padCharsStart:"padStart",propertyOf:"get",rangeStep:"range",rangeStepRight:"rangeRight",restFrom:"rest",spreadFrom:"spread",trimChars:"trim",trimCharsEnd:"trimEnd",trimCharsStart:"trimStart",zipAll:"zip"},s.skipFixed={castArray:!0,flow:!0,flowRight:!0,iteratee:!0,mixin:!0,rearg:!0,runInContext:!0},s.skipRearg={add:!0,assign:!0,assignIn:!0,bind:!0,bindKey:!0,concat:!0,difference:!0,divide:!0,eq:!0,gt:!0,gte:!0,isEqual:!0,lt:!0,lte:!0,matchesProperty:!0,merge:!0,multiply:!0,overArgs:!0,partial:!0,partialRight:!0,propertyOf:!0,random:!0,range:!0,rangeRight:!0,subtract:!0,zip:!0,zipObject:!0,zipObjectDeep:!0}},4269:(i,s,u)=>{i.exports={ary:u(39514),assign:u(44037),clone:u(66678),curry:u(40087),forEach:u(77412),isArray:u(1469),isError:u(64647),isFunction:u(23560),isWeakMap:u(81018),iteratee:u(72594),keys:u(280),rearg:u(4963),toInteger:u(40554),toPath:u(30084)}},72700:(i,s,u)=>{i.exports=u(28252)},92822:(i,s,u)=>{var m=u(84599),v=u(4269);i.exports=function convert(i,s,u){return m(v,i,s,u)}},69306:i=>{i.exports={}},28252:(i,s,u)=>{var m=u(92822)("set",u(36968));m.placeholder=u(69306),i.exports=m},27361:(i,s,u)=>{var m=u(97786);i.exports=function get(i,s,u){var v=null==i?void 0:m(i,s);return void 0===v?u:v}},79095:(i,s,u)=>{var m=u(13),v=u(222);i.exports=function hasIn(i,s){return null!=i&&v(i,s,m)}},6557:i=>{i.exports=function identity(i){return i}},35694:(i,s,u)=>{var m=u(9454),v=u(37005),_=Object.prototype,j=_.hasOwnProperty,M=_.propertyIsEnumerable,$=m(function(){return arguments}())?m:function(i){return v(i)&&j.call(i,"callee")&&!M.call(i,"callee")};i.exports=$},1469:i=>{var s=Array.isArray;i.exports=s},98612:(i,s,u)=>{var m=u(23560),v=u(41780);i.exports=function isArrayLike(i){return null!=i&&v(i.length)&&!m(i)}},29246:(i,s,u)=>{var m=u(98612),v=u(37005);i.exports=function isArrayLikeObject(i){return v(i)&&m(i)}},51584:(i,s,u)=>{var m=u(44239),v=u(37005);i.exports=function isBoolean(i){return!0===i||!1===i||v(i)&&"[object Boolean]"==m(i)}},44144:(i,s,u)=>{i=u.nmd(i);var m=u(55639),v=u(95062),_=s&&!s.nodeType&&s,j=_&&i&&!i.nodeType&&i,M=j&&j.exports===_?m.Buffer:void 0,$=(M?M.isBuffer:void 0)||v;i.exports=$},41609:(i,s,u)=>{var m=u(280),v=u(64160),_=u(35694),j=u(1469),M=u(98612),$=u(44144),W=u(25726),X=u(36719),Y=Object.prototype.hasOwnProperty;i.exports=function isEmpty(i){if(null==i)return!0;if(M(i)&&(j(i)||"string"==typeof i||"function"==typeof i.splice||$(i)||X(i)||_(i)))return!i.length;var s=v(i);if("[object Map]"==s||"[object Set]"==s)return!i.size;if(W(i))return!m(i).length;for(var u in i)if(Y.call(i,u))return!1;return!0}},18446:(i,s,u)=>{var m=u(90939);i.exports=function isEqual(i,s){return m(i,s)}},64647:(i,s,u)=>{var m=u(44239),v=u(37005),_=u(68630);i.exports=function isError(i){if(!v(i))return!1;var s=m(i);return"[object Error]"==s||"[object DOMException]"==s||"string"==typeof i.message&&"string"==typeof i.name&&!_(i)}},23560:(i,s,u)=>{var m=u(44239),v=u(13218);i.exports=function isFunction(i){if(!v(i))return!1;var s=m(i);return"[object Function]"==s||"[object GeneratorFunction]"==s||"[object AsyncFunction]"==s||"[object Proxy]"==s}},41780:i=>{i.exports=function isLength(i){return"number"==typeof i&&i>-1&&i%1==0&&i<=9007199254740991}},56688:(i,s,u)=>{var m=u(25588),v=u(7518),_=u(31167),j=_&&_.isMap,M=j?v(j):m;i.exports=M},45220:i=>{i.exports=function isNull(i){return null===i}},81763:(i,s,u)=>{var m=u(44239),v=u(37005);i.exports=function isNumber(i){return"number"==typeof i||v(i)&&"[object Number]"==m(i)}},13218:i=>{i.exports=function isObject(i){var s=typeof i;return null!=i&&("object"==s||"function"==s)}},37005:i=>{i.exports=function isObjectLike(i){return null!=i&&"object"==typeof i}},68630:(i,s,u)=>{var m=u(44239),v=u(85924),_=u(37005),j=Function.prototype,M=Object.prototype,$=j.toString,W=M.hasOwnProperty,X=$.call(Object);i.exports=function isPlainObject(i){if(!_(i)||"[object Object]"!=m(i))return!1;var s=v(i);if(null===s)return!0;var u=W.call(s,"constructor")&&s.constructor;return"function"==typeof u&&u instanceof u&&$.call(u)==X}},72928:(i,s,u)=>{var m=u(29221),v=u(7518),_=u(31167),j=_&&_.isSet,M=j?v(j):m;i.exports=M},47037:(i,s,u)=>{var m=u(44239),v=u(1469),_=u(37005);i.exports=function isString(i){return"string"==typeof i||!v(i)&&_(i)&&"[object String]"==m(i)}},33448:(i,s,u)=>{var m=u(44239),v=u(37005);i.exports=function isSymbol(i){return"symbol"==typeof i||v(i)&&"[object Symbol]"==m(i)}},36719:(i,s,u)=>{var m=u(38749),v=u(7518),_=u(31167),j=_&&_.isTypedArray,M=j?v(j):m;i.exports=M},81018:(i,s,u)=>{var m=u(64160),v=u(37005);i.exports=function isWeakMap(i){return v(i)&&"[object WeakMap]"==m(i)}},72594:(i,s,u)=>{var m=u(85990),v=u(67206);i.exports=function iteratee(i){return v("function"==typeof i?i:m(i,1))}},3674:(i,s,u)=>{var m=u(14636),v=u(280),_=u(98612);i.exports=function keys(i){return _(i)?m(i):v(i)}},81704:(i,s,u)=>{var m=u(14636),v=u(10313),_=u(98612);i.exports=function keysIn(i){return _(i)?m(i,!0):v(i)}},10928:i=>{i.exports=function last(i){var s=null==i?0:i.length;return s?i[s-1]:void 0}},88306:(i,s,u)=>{var m=u(83369);function memoize(i,s){if("function"!=typeof i||null!=s&&"function"!=typeof s)throw new TypeError("Expected a function");var memoized=function(){var u=arguments,m=s?s.apply(this,u):u[0],v=memoized.cache;if(v.has(m))return v.get(m);var _=i.apply(this,u);return memoized.cache=v.set(m,_)||v,_};return memoized.cache=new(memoize.Cache||m),memoized}memoize.Cache=m,i.exports=memoize},82492:(i,s,u)=>{var m=u(42980),v=u(21463)((function(i,s,u){m(i,s,u)}));i.exports=v},94885:i=>{i.exports=function negate(i){if("function"!=typeof i)throw new TypeError("Expected a function");return function(){var s=arguments;switch(s.length){case 0:return!i.call(this);case 1:return!i.call(this,s[0]);case 2:return!i.call(this,s[0],s[1]);case 3:return!i.call(this,s[0],s[1],s[2])}return!i.apply(this,s)}}},50308:i=>{i.exports=function noop(){}},7771:(i,s,u)=>{var m=u(55639);i.exports=function(){return m.Date.now()}},57557:(i,s,u)=>{var m=u(29932),v=u(85990),_=u(57406),j=u(71811),M=u(98363),$=u(60696),W=u(99021),X=u(46904),Y=W((function(i,s){var u={};if(null==i)return u;var W=!1;s=m(s,(function(s){return s=j(s,i),W||(W=s.length>1),s})),M(i,X(i),u),W&&(u=v(u,7,$));for(var Y=s.length;Y--;)_(u,s[Y]);return u}));i.exports=Y},39601:(i,s,u)=>{var m=u(40371),v=u(79152),_=u(15403),j=u(40327);i.exports=function property(i){return _(i)?m(j(i)):v(i)}},4963:(i,s,u)=>{var m=u(97727),v=u(99021),_=v((function(i,s){return m(i,256,void 0,void 0,void 0,s)}));i.exports=_},54061:(i,s,u)=>{var m=u(62663),v=u(89881),_=u(67206),j=u(10107),M=u(1469);i.exports=function reduce(i,s,u){var $=M(i)?m:j,W=arguments.length<3;return $(i,_(s,4),u,W,v)}},36968:(i,s,u)=>{var m=u(10611);i.exports=function set(i,s,u){return null==i?i:m(i,s,u)}},59704:(i,s,u)=>{var m=u(82908),v=u(67206),_=u(5076),j=u(1469),M=u(16612);i.exports=function some(i,s,u){var $=j(i)?m:_;return u&&M(i,s,u)&&(s=void 0),$(i,v(s,3))}},70479:i=>{i.exports=function stubArray(){return[]}},95062:i=>{i.exports=function stubFalse(){return!1}},18601:(i,s,u)=>{var m=u(14841),v=1/0;i.exports=function toFinite(i){return i?(i=m(i))===v||i===-1/0?17976931348623157e292*(i<0?-1:1):i==i?i:0:0===i?i:0}},40554:(i,s,u)=>{var m=u(18601);i.exports=function toInteger(i){var s=m(i),u=s%1;return s==s?u?s-u:s:0}},7334:(i,s,u)=>{var m=u(79833);i.exports=function toLower(i){return m(i).toLowerCase()}},14841:(i,s,u)=>{var m=u(27561),v=u(13218),_=u(33448),j=/^[-+]0x[0-9a-f]+$/i,M=/^0b[01]+$/i,$=/^0o[0-7]+$/i,W=parseInt;i.exports=function toNumber(i){if("number"==typeof i)return i;if(_(i))return NaN;if(v(i)){var s="function"==typeof i.valueOf?i.valueOf():i;i=v(s)?s+"":s}if("string"!=typeof i)return 0===i?i:+i;i=m(i);var u=M.test(i);return u||$.test(i)?W(i.slice(2),u?2:8):j.test(i)?NaN:+i}},30084:(i,s,u)=>{var m=u(29932),v=u(278),_=u(1469),j=u(33448),M=u(55514),$=u(40327),W=u(79833);i.exports=function toPath(i){return _(i)?m(i,$):j(i)?[i]:v(M(W(i)))}},59881:(i,s,u)=>{var m=u(98363),v=u(81704);i.exports=function toPlainObject(i){return m(i,v(i))}},79833:(i,s,u)=>{var m=u(80531);i.exports=function toString(i){return null==i?"":m(i)}},11700:(i,s,u)=>{var m=u(98805)("toUpperCase");i.exports=m},58748:(i,s,u)=>{var m=u(49029),v=u(93157),_=u(79833),j=u(2757);i.exports=function words(i,s,u){return i=_(i),void 0===(s=u?void 0:s)?v(i)?j(i):m(i):i.match(s)||[]}},8111:(i,s,u)=>{var m=u(96425),v=u(7548),_=u(9435),j=u(1469),M=u(37005),$=u(21913),W=Object.prototype.hasOwnProperty;function lodash(i){if(M(i)&&!j(i)&&!(i instanceof m)){if(i instanceof v)return i;if(W.call(i,"__wrapped__"))return $(i)}return new v(i)}lodash.prototype=_.prototype,lodash.prototype.constructor=lodash,i.exports=lodash},7287:(i,s,u)=>{var m=u(34865),v=u(1757);i.exports=function zipObject(i,s){return v(i||[],s||[],m)}},96470:(i,s,u)=>{"use strict";var m=u(47802),v=u(21102);s.highlight=highlight,s.highlightAuto=function highlightAuto(i,s){var u,j,M,$,W=s||{},X=W.subset||m.listLanguages(),Y=W.prefix,Z=X.length,ee=-1;null==Y&&(Y=_);if("string"!=typeof i)throw v("Expected `string` for value, got `%s`",i);j={relevance:0,language:null,value:[]},u={relevance:0,language:null,value:[]};for(;++eej.relevance&&(j=M),M.relevance>u.relevance&&(j=u,u=M));j.language&&(u.secondBest=j);return u},s.registerLanguage=function registerLanguage(i,s){m.registerLanguage(i,s)},s.listLanguages=function listLanguages(){return m.listLanguages()},s.registerAlias=function registerAlias(i,s){var u,v=i;s&&((v={})[i]=s);for(u in v)m.registerAliases(v[u],{languageName:u})},Emitter.prototype.addText=function text(i){var s,u,m=this.stack;if(""===i)return;s=m[m.length-1],(u=s.children[s.children.length-1])&&"text"===u.type?u.value+=i:s.children.push({type:"text",value:i})},Emitter.prototype.addKeyword=function addKeyword(i,s){this.openNode(s),this.addText(i),this.closeNode()},Emitter.prototype.addSublanguage=function addSublanguage(i,s){var u=this.stack,m=u[u.length-1],v=i.rootNode.children,_=s?{type:"element",tagName:"span",properties:{className:[s]},children:v}:v;m.children=m.children.concat(_)},Emitter.prototype.openNode=function open(i){var s=this.stack,u=this.options.classPrefix+i,m=s[s.length-1],v={type:"element",tagName:"span",properties:{className:[u]},children:[]};m.children.push(v),s.push(v)},Emitter.prototype.closeNode=function close(){this.stack.pop()},Emitter.prototype.closeAllNodes=noop,Emitter.prototype.finalize=noop,Emitter.prototype.toHTML=function toHtmlNoop(){return""};var _="hljs-";function highlight(i,s,u){var j,M=m.configure({}),$=(u||{}).prefix;if("string"!=typeof i)throw v("Expected `string` for name, got `%s`",i);if(!m.getLanguage(i))throw v("Unknown language: `%s` is not registered",i);if("string"!=typeof s)throw v("Expected `string` for value, got `%s`",s);if(null==$&&($=_),m.configure({__emitter:Emitter,classPrefix:$}),j=m.highlight(s,{language:i,ignoreIllegals:!0}),m.configure(M||{}),j.errorRaised)throw j.errorRaised;return{relevance:j.relevance,language:j.language,value:j.emitter.rootNode.children}}function Emitter(i){this.options=i,this.rootNode={children:[]},this.stack=[this.rootNode]}function noop(){}},42566:(i,s,u)=>{const m=u(94885);function coerceElementMatchingCallback(i){return"string"==typeof i?s=>s.element===i:i.constructor&&i.extend?s=>s instanceof i:i}class ArraySlice{constructor(i){this.elements=i||[]}toValue(){return this.elements.map((i=>i.toValue()))}map(i,s){return this.elements.map(i,s)}flatMap(i,s){return this.map(i,s).reduce(((i,s)=>i.concat(s)),[])}compactMap(i,s){const u=[];return this.forEach((m=>{const v=i.bind(s)(m);v&&u.push(v)})),u}filter(i,s){return i=coerceElementMatchingCallback(i),new ArraySlice(this.elements.filter(i,s))}reject(i,s){return i=coerceElementMatchingCallback(i),new ArraySlice(this.elements.filter(m(i),s))}find(i,s){return i=coerceElementMatchingCallback(i),this.elements.find(i,s)}forEach(i,s){this.elements.forEach(i,s)}reduce(i,s){return this.elements.reduce(i,s)}includes(i){return this.elements.some((s=>s.equals(i)))}shift(){return this.elements.shift()}unshift(i){this.elements.unshift(this.refract(i))}push(i){return this.elements.push(this.refract(i)),this}add(i){this.push(i)}get(i){return this.elements[i]}getValue(i){const s=this.elements[i];if(s)return s.toValue()}get length(){return this.elements.length}get isEmpty(){return 0===this.elements.length}get first(){return this.elements[0]}}"undefined"!=typeof Symbol&&(ArraySlice.prototype[Symbol.iterator]=function symbol(){return this.elements[Symbol.iterator]()}),i.exports=ArraySlice},17645:i=>{class KeyValuePair{constructor(i,s){this.key=i,this.value=s}clone(){const i=new KeyValuePair;return this.key&&(i.key=this.key.clone()),this.value&&(i.value=this.value.clone()),i}}i.exports=KeyValuePair},78520:(i,s,u)=>{const m=u(45220),v=u(47037),_=u(81763),j=u(51584),M=u(13218),$=u(28219),W=u(99829);class Namespace{constructor(i){this.elementMap={},this.elementDetection=[],this.Element=W.Element,this.KeyValuePair=W.KeyValuePair,i&&i.noDefault||this.useDefault(),this._attributeElementKeys=[],this._attributeElementArrayKeys=[]}use(i){return i.namespace&&i.namespace({base:this}),i.load&&i.load({base:this}),this}useDefault(){return this.register("null",W.NullElement).register("string",W.StringElement).register("number",W.NumberElement).register("boolean",W.BooleanElement).register("array",W.ArrayElement).register("object",W.ObjectElement).register("member",W.MemberElement).register("ref",W.RefElement).register("link",W.LinkElement),this.detect(m,W.NullElement,!1).detect(v,W.StringElement,!1).detect(_,W.NumberElement,!1).detect(j,W.BooleanElement,!1).detect(Array.isArray,W.ArrayElement,!1).detect(M,W.ObjectElement,!1),this}register(i,s){return this._elements=void 0,this.elementMap[i]=s,this}unregister(i){return this._elements=void 0,delete this.elementMap[i],this}detect(i,s,u){return void 0===u||u?this.elementDetection.unshift([i,s]):this.elementDetection.push([i,s]),this}toElement(i){if(i instanceof this.Element)return i;let s;for(let u=0;u{const s=i[0].toUpperCase()+i.substr(1);this._elements[s]=this.elementMap[i]}))),this._elements}get serialiser(){return new $(this)}}$.prototype.Namespace=Namespace,i.exports=Namespace},87526:(i,s,u)=>{const m=u(94885),v=u(42566);class ObjectSlice extends v{map(i,s){return this.elements.map((u=>i.bind(s)(u.value,u.key,u)))}filter(i,s){return new ObjectSlice(this.elements.filter((u=>i.bind(s)(u.value,u.key,u))))}reject(i,s){return this.filter(m(i.bind(s)))}forEach(i,s){return this.elements.forEach(((u,m)=>{i.bind(s)(u.value,u.key,u,m)}))}keys(){return this.map(((i,s)=>s.toValue()))}values(){return this.map((i=>i.toValue()))}}i.exports=ObjectSlice},99829:(i,s,u)=>{const m=u(3079),v=u(96295),_=u(16036),j=u(91090),M=u(18866),$=u(35804),W=u(5946),X=u(76735),Y=u(59964),Z=u(38588),ee=u(42566),ae=u(87526),ie=u(17645);function refract(i){if(i instanceof m)return i;if("string"==typeof i)return new _(i);if("number"==typeof i)return new j(i);if("boolean"==typeof i)return new M(i);if(null===i)return new v;if(Array.isArray(i))return new $(i.map(refract));if("object"==typeof i){return new X(i)}return i}m.prototype.ObjectElement=X,m.prototype.RefElement=Z,m.prototype.MemberElement=W,m.prototype.refract=refract,ee.prototype.refract=refract,i.exports={Element:m,NullElement:v,StringElement:_,NumberElement:j,BooleanElement:M,ArrayElement:$,MemberElement:W,ObjectElement:X,LinkElement:Y,RefElement:Z,refract,ArraySlice:ee,ObjectSlice:ae,KeyValuePair:ie}},59964:(i,s,u)=>{const m=u(3079);i.exports=class LinkElement extends m{constructor(i,s,u){super(i||[],s,u),this.element="link"}get relation(){return this.attributes.get("relation")}set relation(i){this.attributes.set("relation",i)}get href(){return this.attributes.get("href")}set href(i){this.attributes.set("href",i)}}},38588:(i,s,u)=>{const m=u(3079);i.exports=class RefElement extends m{constructor(i,s,u){super(i||[],s,u),this.element="ref",this.path||(this.path="element")}get path(){return this.attributes.get("path")}set path(i){this.attributes.set("path",i)}}},43500:(i,s,u)=>{const m=u(78520),v=u(99829);s.lS=m,u(17645),s.O4=v.ArraySlice,v.ObjectSlice,s.W_=v.Element,s.RP=v.StringElement,s.VL=v.NumberElement,s.hh=v.BooleanElement,s.zr=v.NullElement,s.ON=v.ArrayElement,s.Sb=v.ObjectElement,s.c6=v.MemberElement,s.tK=v.RefElement,s.EA=v.LinkElement,s.Qc=v.refract,u(28219),u(3414)},35804:(i,s,u)=>{const m=u(94885),v=u(3079),_=u(42566);class ArrayElement extends v{constructor(i,s,u){super(i||[],s,u),this.element="array"}primitive(){return"array"}get(i){return this.content[i]}getValue(i){const s=this.get(i);if(s)return s.toValue()}getIndex(i){return this.content[i]}set(i,s){return this.content[i]=this.refract(s),this}remove(i){const s=this.content.splice(i,1);return s.length?s[0]:null}map(i,s){return this.content.map(i,s)}flatMap(i,s){return this.map(i,s).reduce(((i,s)=>i.concat(s)),[])}compactMap(i,s){const u=[];return this.forEach((m=>{const v=i.bind(s)(m);v&&u.push(v)})),u}filter(i,s){return new _(this.content.filter(i,s))}reject(i,s){return this.filter(m(i),s)}reduce(i,s){let u,m;void 0!==s?(u=0,m=this.refract(s)):(u=1,m="object"===this.primitive()?this.first.value:this.first);for(let s=u;s{i.bind(s)(u,this.refract(m))}))}shift(){return this.content.shift()}unshift(i){this.content.unshift(this.refract(i))}push(i){return this.content.push(this.refract(i)),this}add(i){this.push(i)}findElements(i,s){const u=s||{},m=!!u.recursive,v=void 0===u.results?[]:u.results;return this.forEach(((s,u,_)=>{m&&void 0!==s.findElements&&s.findElements(i,{results:v,recursive:m}),i(s,u,_)&&v.push(s)})),v}find(i){return new _(this.findElements(i,{recursive:!0}))}findByElement(i){return this.find((s=>s.element===i))}findByClass(i){return this.find((s=>s.classes.includes(i)))}getById(i){return this.find((s=>s.id.toValue()===i)).first}includes(i){return this.content.some((s=>s.equals(i)))}contains(i){return this.includes(i)}empty(){return new this.constructor([])}"fantasy-land/empty"(){return this.empty()}concat(i){return new this.constructor(this.content.concat(i.content))}"fantasy-land/concat"(i){return this.concat(i)}"fantasy-land/map"(i){return new this.constructor(this.map(i))}"fantasy-land/chain"(i){return this.map((s=>i(s)),this).reduce(((i,s)=>i.concat(s)),this.empty())}"fantasy-land/filter"(i){return new this.constructor(this.content.filter(i))}"fantasy-land/reduce"(i,s){return this.content.reduce(i,s)}get length(){return this.content.length}get isEmpty(){return 0===this.content.length}get first(){return this.getIndex(0)}get second(){return this.getIndex(1)}get last(){return this.getIndex(this.length-1)}}ArrayElement.empty=function empty(){return new this},ArrayElement["fantasy-land/empty"]=ArrayElement.empty,"undefined"!=typeof Symbol&&(ArrayElement.prototype[Symbol.iterator]=function symbol(){return this.content[Symbol.iterator]()}),i.exports=ArrayElement},18866:(i,s,u)=>{const m=u(3079);i.exports=class BooleanElement extends m{constructor(i,s,u){super(i,s,u),this.element="boolean"}primitive(){return"boolean"}}},3079:(i,s,u)=>{const m=u(18446),v=u(17645),_=u(42566);class Element{constructor(i,s,u){s&&(this.meta=s),u&&(this.attributes=u),this.content=i}freeze(){Object.isFrozen(this)||(this._meta&&(this.meta.parent=this,this.meta.freeze()),this._attributes&&(this.attributes.parent=this,this.attributes.freeze()),this.children.forEach((i=>{i.parent=this,i.freeze()}),this),this.content&&Array.isArray(this.content)&&Object.freeze(this.content),Object.freeze(this))}primitive(){}clone(){const i=new this.constructor;return i.element=this.element,this.meta.length&&(i._meta=this.meta.clone()),this.attributes.length&&(i._attributes=this.attributes.clone()),this.content?this.content.clone?i.content=this.content.clone():Array.isArray(this.content)?i.content=this.content.map((i=>i.clone())):i.content=this.content:i.content=this.content,i}toValue(){return this.content instanceof Element?this.content.toValue():this.content instanceof v?{key:this.content.key.toValue(),value:this.content.value?this.content.value.toValue():void 0}:this.content&&this.content.map?this.content.map((i=>i.toValue()),this):this.content}toRef(i){if(""===this.id.toValue())throw Error("Cannot create reference to an element that does not contain an ID");const s=new this.RefElement(this.id.toValue());return i&&(s.path=i),s}findRecursive(...i){if(arguments.length>1&&!this.isFrozen)throw new Error("Cannot find recursive with multiple element names without first freezing the element. Call `element.freeze()`");const s=i.pop();let u=new _;const append=(i,s)=>(i.push(s),i),checkElement=(i,u)=>{u.element===s&&i.push(u);const m=u.findRecursive(s);return m&&m.reduce(append,i),u.content instanceof v&&(u.content.key&&checkElement(i,u.content.key),u.content.value&&checkElement(i,u.content.value)),i};return this.content&&(this.content.element&&checkElement(u,this.content),Array.isArray(this.content)&&this.content.reduce(checkElement,u)),i.isEmpty||(u=u.filter((s=>{let u=s.parents.map((i=>i.element));for(const s in i){const m=i[s],v=u.indexOf(m);if(-1===v)return!1;u=u.splice(0,v)}return!0}))),u}set(i){return this.content=i,this}equals(i){return m(this.toValue(),i)}getMetaProperty(i,s){if(!this.meta.hasKey(i)){if(this.isFrozen){const i=this.refract(s);return i.freeze(),i}this.meta.set(i,s)}return this.meta.get(i)}setMetaProperty(i,s){this.meta.set(i,s)}get element(){return this._storedElement||"element"}set element(i){this._storedElement=i}get content(){return this._content}set content(i){if(i instanceof Element)this._content=i;else if(i instanceof _)this.content=i.elements;else if("string"==typeof i||"number"==typeof i||"boolean"==typeof i||"null"===i||null==i)this._content=i;else if(i instanceof v)this._content=i;else if(Array.isArray(i))this._content=i.map(this.refract);else{if("object"!=typeof i)throw new Error("Cannot set content to given value");this._content=Object.keys(i).map((s=>new this.MemberElement(s,i[s])))}}get meta(){if(!this._meta){if(this.isFrozen){const i=new this.ObjectElement;return i.freeze(),i}this._meta=new this.ObjectElement}return this._meta}set meta(i){i instanceof this.ObjectElement?this._meta=i:this.meta.set(i||{})}get attributes(){if(!this._attributes){if(this.isFrozen){const i=new this.ObjectElement;return i.freeze(),i}this._attributes=new this.ObjectElement}return this._attributes}set attributes(i){i instanceof this.ObjectElement?this._attributes=i:this.attributes.set(i||{})}get id(){return this.getMetaProperty("id","")}set id(i){this.setMetaProperty("id",i)}get classes(){return this.getMetaProperty("classes",[])}set classes(i){this.setMetaProperty("classes",i)}get title(){return this.getMetaProperty("title","")}set title(i){this.setMetaProperty("title",i)}get description(){return this.getMetaProperty("description","")}set description(i){this.setMetaProperty("description",i)}get links(){return this.getMetaProperty("links",[])}set links(i){this.setMetaProperty("links",i)}get isFrozen(){return Object.isFrozen(this)}get parents(){let{parent:i}=this;const s=new _;for(;i;)s.push(i),i=i.parent;return s}get children(){if(Array.isArray(this.content))return new _(this.content);if(this.content instanceof v){const i=new _([this.content.key]);return this.content.value&&i.push(this.content.value),i}return this.content instanceof Element?new _([this.content]):new _}get recursiveChildren(){const i=new _;return this.children.forEach((s=>{i.push(s),s.recursiveChildren.forEach((s=>{i.push(s)}))})),i}}i.exports=Element},5946:(i,s,u)=>{const m=u(17645),v=u(3079);i.exports=class MemberElement extends v{constructor(i,s,u,v){super(new m,u,v),this.element="member",this.key=i,this.value=s}get key(){return this.content.key}set key(i){this.content.key=this.refract(i)}get value(){return this.content.value}set value(i){this.content.value=this.refract(i)}}},96295:(i,s,u)=>{const m=u(3079);i.exports=class NullElement extends m{constructor(i,s,u){super(i||null,s,u),this.element="null"}primitive(){return"null"}set(){return new Error("Cannot set the value of null")}}},91090:(i,s,u)=>{const m=u(3079);i.exports=class NumberElement extends m{constructor(i,s,u){super(i,s,u),this.element="number"}primitive(){return"number"}}},76735:(i,s,u)=>{const m=u(94885),v=u(13218),_=u(35804),j=u(5946),M=u(87526);i.exports=class ObjectElement extends _{constructor(i,s,u){super(i||[],s,u),this.element="object"}primitive(){return"object"}toValue(){return this.content.reduce(((i,s)=>(i[s.key.toValue()]=s.value?s.value.toValue():void 0,i)),{})}get(i){const s=this.getMember(i);if(s)return s.value}getMember(i){if(void 0!==i)return this.content.find((s=>s.key.toValue()===i))}remove(i){let s=null;return this.content=this.content.filter((u=>u.key.toValue()!==i||(s=u,!1))),s}getKey(i){const s=this.getMember(i);if(s)return s.key}set(i,s){if(v(i))return Object.keys(i).forEach((s=>{this.set(s,i[s])})),this;const u=i,m=this.getMember(u);return m?m.value=s:this.content.push(new j(u,s)),this}keys(){return this.content.map((i=>i.key.toValue()))}values(){return this.content.map((i=>i.value.toValue()))}hasKey(i){return this.content.some((s=>s.key.equals(i)))}items(){return this.content.map((i=>[i.key.toValue(),i.value.toValue()]))}map(i,s){return this.content.map((u=>i.bind(s)(u.value,u.key,u)))}compactMap(i,s){const u=[];return this.forEach(((m,v,_)=>{const j=i.bind(s)(m,v,_);j&&u.push(j)})),u}filter(i,s){return new M(this.content).filter(i,s)}reject(i,s){return this.filter(m(i),s)}forEach(i,s){return this.content.forEach((u=>i.bind(s)(u.value,u.key,u)))}}},16036:(i,s,u)=>{const m=u(3079);i.exports=class StringElement extends m{constructor(i,s,u){super(i,s,u),this.element="string"}primitive(){return"string"}get length(){return this.content.length}}},3414:(i,s,u)=>{const m=u(28219);i.exports=class JSON06Serialiser extends m{serialise(i){if(!(i instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${i}\` is not an Element instance`);let s;i._attributes&&i.attributes.get("variable")&&(s=i.attributes.get("variable"));const u={element:i.element};i._meta&&i._meta.length>0&&(u.meta=this.serialiseObject(i.meta));const m="enum"===i.element||-1!==i.attributes.keys().indexOf("enumerations");if(m){const s=this.enumSerialiseAttributes(i);s&&(u.attributes=s)}else if(i._attributes&&i._attributes.length>0){let{attributes:m}=i;m.get("metadata")&&(m=m.clone(),m.set("meta",m.get("metadata")),m.remove("metadata")),"member"===i.element&&s&&(m=m.clone(),m.remove("variable")),m.length>0&&(u.attributes=this.serialiseObject(m))}if(m)u.content=this.enumSerialiseContent(i,u);else if(this[`${i.element}SerialiseContent`])u.content=this[`${i.element}SerialiseContent`](i,u);else if(void 0!==i.content){let m;s&&i.content.key?(m=i.content.clone(),m.key.attributes.set("variable",s),m=this.serialiseContent(m)):m=this.serialiseContent(i.content),this.shouldSerialiseContent(i,m)&&(u.content=m)}else this.shouldSerialiseContent(i,i.content)&&i instanceof this.namespace.elements.Array&&(u.content=[]);return u}shouldSerialiseContent(i,s){return"parseResult"===i.element||"httpRequest"===i.element||"httpResponse"===i.element||"category"===i.element||"link"===i.element||void 0!==s&&(!Array.isArray(s)||0!==s.length)}refSerialiseContent(i,s){return delete s.attributes,{href:i.toValue(),path:i.path.toValue()}}sourceMapSerialiseContent(i){return i.toValue()}dataStructureSerialiseContent(i){return[this.serialiseContent(i.content)]}enumSerialiseAttributes(i){const s=i.attributes.clone(),u=s.remove("enumerations")||new this.namespace.elements.Array([]),m=s.get("default");let v=s.get("samples")||new this.namespace.elements.Array([]);if(m&&m.content&&(m.content.attributes&&m.content.attributes.remove("typeAttributes"),s.set("default",new this.namespace.elements.Array([m.content]))),v.forEach((i=>{i.content&&i.content.element&&i.content.attributes.remove("typeAttributes")})),i.content&&0!==u.length&&v.unshift(i.content),v=v.map((i=>i instanceof this.namespace.elements.Array?[i]:new this.namespace.elements.Array([i.content]))),v.length&&s.set("samples",v),s.length>0)return this.serialiseObject(s)}enumSerialiseContent(i){if(i._attributes){const s=i.attributes.get("enumerations");if(s&&s.length>0)return s.content.map((i=>{const s=i.clone();return s.attributes.remove("typeAttributes"),this.serialise(s)}))}if(i.content){const s=i.content.clone();return s.attributes.remove("typeAttributes"),[this.serialise(s)]}return[]}deserialise(i){if("string"==typeof i)return new this.namespace.elements.String(i);if("number"==typeof i)return new this.namespace.elements.Number(i);if("boolean"==typeof i)return new this.namespace.elements.Boolean(i);if(null===i)return new this.namespace.elements.Null;if(Array.isArray(i))return new this.namespace.elements.Array(i.map(this.deserialise,this));const s=this.namespace.getElementClass(i.element),u=new s;u.element!==i.element&&(u.element=i.element),i.meta&&this.deserialiseObject(i.meta,u.meta),i.attributes&&this.deserialiseObject(i.attributes,u.attributes);const m=this.deserialiseContent(i.content);if(void 0===m&&null!==u.content||(u.content=m),"enum"===u.element){u.content&&u.attributes.set("enumerations",u.content);let i=u.attributes.get("samples");if(u.attributes.remove("samples"),i){const m=i;i=new this.namespace.elements.Array,m.forEach((m=>{m.forEach((m=>{const v=new s(m);v.element=u.element,i.push(v)}))}));const v=i.shift();u.content=v?v.content:void 0,u.attributes.set("samples",i)}else u.content=void 0;let m=u.attributes.get("default");if(m&&m.length>0){m=m.get(0);const i=new s(m);i.element=u.element,u.attributes.set("default",i)}}else if("dataStructure"===u.element&&Array.isArray(u.content))[u.content]=u.content;else if("category"===u.element){const i=u.attributes.get("meta");i&&(u.attributes.set("metadata",i),u.attributes.remove("meta"))}else"member"===u.element&&u.key&&u.key._attributes&&u.key._attributes.getValue("variable")&&(u.attributes.set("variable",u.key.attributes.get("variable")),u.key.attributes.remove("variable"));return u}serialiseContent(i){if(i instanceof this.namespace.elements.Element)return this.serialise(i);if(i instanceof this.namespace.KeyValuePair){const s={key:this.serialise(i.key)};return i.value&&(s.value=this.serialise(i.value)),s}return i&&i.map?i.map(this.serialise,this):i}deserialiseContent(i){if(i){if(i.element)return this.deserialise(i);if(i.key){const s=new this.namespace.KeyValuePair(this.deserialise(i.key));return i.value&&(s.value=this.deserialise(i.value)),s}if(i.map)return i.map(this.deserialise,this)}return i}shouldRefract(i){return!!(i._attributes&&i.attributes.keys().length||i._meta&&i.meta.keys().length)||"enum"!==i.element&&(i.element!==i.primitive()||"member"===i.element)}convertKeyToRefract(i,s){return this.shouldRefract(s)?this.serialise(s):"enum"===s.element?this.serialiseEnum(s):"array"===s.element?s.map((s=>this.shouldRefract(s)||"default"===i?this.serialise(s):"array"===s.element||"object"===s.element||"enum"===s.element?s.children.map((i=>this.serialise(i))):s.toValue())):"object"===s.element?(s.content||[]).map(this.serialise,this):s.toValue()}serialiseEnum(i){return i.children.map((i=>this.serialise(i)))}serialiseObject(i){const s={};return i.forEach(((i,u)=>{if(i){const m=u.toValue();s[m]=this.convertKeyToRefract(m,i)}})),s}deserialiseObject(i,s){Object.keys(i).forEach((u=>{s.set(u,this.deserialise(i[u]))}))}}},28219:i=>{i.exports=class JSONSerialiser{constructor(i){this.namespace=i||new this.Namespace}serialise(i){if(!(i instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${i}\` is not an Element instance`);const s={element:i.element};i._meta&&i._meta.length>0&&(s.meta=this.serialiseObject(i.meta)),i._attributes&&i._attributes.length>0&&(s.attributes=this.serialiseObject(i.attributes));const u=this.serialiseContent(i.content);return void 0!==u&&(s.content=u),s}deserialise(i){if(!i.element)throw new Error("Given value is not an object containing an element name");const s=new(this.namespace.getElementClass(i.element));s.element!==i.element&&(s.element=i.element),i.meta&&this.deserialiseObject(i.meta,s.meta),i.attributes&&this.deserialiseObject(i.attributes,s.attributes);const u=this.deserialiseContent(i.content);return void 0===u&&null!==s.content||(s.content=u),s}serialiseContent(i){if(i instanceof this.namespace.elements.Element)return this.serialise(i);if(i instanceof this.namespace.KeyValuePair){const s={key:this.serialise(i.key)};return i.value&&(s.value=this.serialise(i.value)),s}if(i&&i.map){if(0===i.length)return;return i.map(this.serialise,this)}return i}deserialiseContent(i){if(i){if(i.element)return this.deserialise(i);if(i.key){const s=new this.namespace.KeyValuePair(this.deserialise(i.key));return i.value&&(s.value=this.deserialise(i.value)),s}if(i.map)return i.map(this.deserialise,this)}return i}serialiseObject(i){const s={};if(i.forEach(((i,u)=>{i&&(s[u.toValue()]=this.serialise(i))})),0!==Object.keys(s).length)return s}deserialiseObject(i,s){Object.keys(i).forEach((u=>{s.set(u,this.deserialise(i[u]))}))}}},27418:i=>{"use strict";var s=Object.getOwnPropertySymbols,u=Object.prototype.hasOwnProperty,m=Object.prototype.propertyIsEnumerable;i.exports=function shouldUseNative(){try{if(!Object.assign)return!1;var i=new String("abc");if(i[5]="de","5"===Object.getOwnPropertyNames(i)[0])return!1;for(var s={},u=0;u<10;u++)s["_"+String.fromCharCode(u)]=u;if("0123456789"!==Object.getOwnPropertyNames(s).map((function(i){return s[i]})).join(""))return!1;var m={};return"abcdefghijklmnopqrst".split("").forEach((function(i){m[i]=i})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},m)).join("")}catch(i){return!1}}()?Object.assign:function(i,v){for(var _,j,M=function toObject(i){if(null==i)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(i)}(i),$=1;${var m="function"==typeof Map&&Map.prototype,v=Object.getOwnPropertyDescriptor&&m?Object.getOwnPropertyDescriptor(Map.prototype,"size"):null,_=m&&v&&"function"==typeof v.get?v.get:null,j=m&&Map.prototype.forEach,M="function"==typeof Set&&Set.prototype,$=Object.getOwnPropertyDescriptor&&M?Object.getOwnPropertyDescriptor(Set.prototype,"size"):null,W=M&&$&&"function"==typeof $.get?$.get:null,X=M&&Set.prototype.forEach,Y="function"==typeof WeakMap&&WeakMap.prototype?WeakMap.prototype.has:null,Z="function"==typeof WeakSet&&WeakSet.prototype?WeakSet.prototype.has:null,ee="function"==typeof WeakRef&&WeakRef.prototype?WeakRef.prototype.deref:null,ae=Boolean.prototype.valueOf,ie=Object.prototype.toString,le=Function.prototype.toString,ce=String.prototype.match,pe=String.prototype.slice,de=String.prototype.replace,fe=String.prototype.toUpperCase,ye=String.prototype.toLowerCase,be=RegExp.prototype.test,_e=Array.prototype.concat,we=Array.prototype.join,Se=Array.prototype.slice,xe=Math.floor,Pe="function"==typeof BigInt?BigInt.prototype.valueOf:null,Ie=Object.getOwnPropertySymbols,Te="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?Symbol.prototype.toString:null,Re="function"==typeof Symbol&&"object"==typeof Symbol.iterator,qe="function"==typeof Symbol&&Symbol.toStringTag&&(typeof Symbol.toStringTag===Re||"symbol")?Symbol.toStringTag:null,ze=Object.prototype.propertyIsEnumerable,Ve=("function"==typeof Reflect?Reflect.getPrototypeOf:Object.getPrototypeOf)||([].__proto__===Array.prototype?function(i){return i.__proto__}:null);function addNumericSeparator(i,s){if(i===1/0||i===-1/0||i!=i||i&&i>-1e3&&i<1e3||be.call(/e/,s))return s;var u=/[0-9](?=(?:[0-9]{3})+(?![0-9]))/g;if("number"==typeof i){var m=i<0?-xe(-i):xe(i);if(m!==i){var v=String(m),_=pe.call(s,v.length+1);return de.call(v,u,"$&_")+"."+de.call(de.call(_,/([0-9]{3})/g,"$&_"),/_$/,"")}}return de.call(s,u,"$&_")}var We=u(24654),He=We.custom,Xe=isSymbol(He)?He:null;function wrapQuotes(i,s,u){var m="double"===(u.quoteStyle||s)?'"':"'";return m+i+m}function quote(i){return de.call(String(i),/"/g,""")}function isArray(i){return!("[object Array]"!==toStr(i)||qe&&"object"==typeof i&&qe in i)}function isRegExp(i){return!("[object RegExp]"!==toStr(i)||qe&&"object"==typeof i&&qe in i)}function isSymbol(i){if(Re)return i&&"object"==typeof i&&i instanceof Symbol;if("symbol"==typeof i)return!0;if(!i||"object"!=typeof i||!Te)return!1;try{return Te.call(i),!0}catch(i){}return!1}i.exports=function inspect_(i,s,u,m){var v=s||{};if(has(v,"quoteStyle")&&"single"!==v.quoteStyle&&"double"!==v.quoteStyle)throw new TypeError('option "quoteStyle" must be "single" or "double"');if(has(v,"maxStringLength")&&("number"==typeof v.maxStringLength?v.maxStringLength<0&&v.maxStringLength!==1/0:null!==v.maxStringLength))throw new TypeError('option "maxStringLength", if provided, must be a positive integer, Infinity, or `null`');var M=!has(v,"customInspect")||v.customInspect;if("boolean"!=typeof M&&"symbol"!==M)throw new TypeError("option \"customInspect\", if provided, must be `true`, `false`, or `'symbol'`");if(has(v,"indent")&&null!==v.indent&&"\t"!==v.indent&&!(parseInt(v.indent,10)===v.indent&&v.indent>0))throw new TypeError('option "indent" must be "\\t", an integer > 0, or `null`');if(has(v,"numericSeparator")&&"boolean"!=typeof v.numericSeparator)throw new TypeError('option "numericSeparator", if provided, must be `true` or `false`');var $=v.numericSeparator;if(void 0===i)return"undefined";if(null===i)return"null";if("boolean"==typeof i)return i?"true":"false";if("string"==typeof i)return inspectString(i,v);if("number"==typeof i){if(0===i)return 1/0/i>0?"0":"-0";var ie=String(i);return $?addNumericSeparator(i,ie):ie}if("bigint"==typeof i){var fe=String(i)+"n";return $?addNumericSeparator(i,fe):fe}var be=void 0===v.depth?5:v.depth;if(void 0===u&&(u=0),u>=be&&be>0&&"object"==typeof i)return isArray(i)?"[Array]":"[Object]";var xe=function getIndent(i,s){var u;if("\t"===i.indent)u="\t";else{if(!("number"==typeof i.indent&&i.indent>0))return null;u=we.call(Array(i.indent+1)," ")}return{base:u,prev:we.call(Array(s+1),u)}}(v,u);if(void 0===m)m=[];else if(indexOf(m,i)>=0)return"[Circular]";function inspect(i,s,_){if(s&&(m=Se.call(m)).push(s),_){var j={depth:v.depth};return has(v,"quoteStyle")&&(j.quoteStyle=v.quoteStyle),inspect_(i,j,u+1,m)}return inspect_(i,v,u+1,m)}if("function"==typeof i&&!isRegExp(i)){var Ie=function nameOf(i){if(i.name)return i.name;var s=ce.call(le.call(i),/^function\s*([\w$]+)/);if(s)return s[1];return null}(i),He=arrObjKeys(i,inspect);return"[Function"+(Ie?": "+Ie:" (anonymous)")+"]"+(He.length>0?" { "+we.call(He,", ")+" }":"")}if(isSymbol(i)){var Ye=Re?de.call(String(i),/^(Symbol\(.*\))_[^)]*$/,"$1"):Te.call(i);return"object"!=typeof i||Re?Ye:markBoxed(Ye)}if(function isElement(i){if(!i||"object"!=typeof i)return!1;if("undefined"!=typeof HTMLElement&&i instanceof HTMLElement)return!0;return"string"==typeof i.nodeName&&"function"==typeof i.getAttribute}(i)){for(var Qe="<"+ye.call(String(i.nodeName)),et=i.attributes||[],tt=0;tt"}if(isArray(i)){if(0===i.length)return"[]";var rt=arrObjKeys(i,inspect);return xe&&!function singleLineValues(i){for(var s=0;s=0)return!1;return!0}(rt)?"["+indentedJoin(rt,xe)+"]":"[ "+we.call(rt,", ")+" ]"}if(function isError(i){return!("[object Error]"!==toStr(i)||qe&&"object"==typeof i&&qe in i)}(i)){var nt=arrObjKeys(i,inspect);return"cause"in Error.prototype||!("cause"in i)||ze.call(i,"cause")?0===nt.length?"["+String(i)+"]":"{ ["+String(i)+"] "+we.call(nt,", ")+" }":"{ ["+String(i)+"] "+we.call(_e.call("[cause]: "+inspect(i.cause),nt),", ")+" }"}if("object"==typeof i&&M){if(Xe&&"function"==typeof i[Xe]&&We)return We(i,{depth:be-u});if("symbol"!==M&&"function"==typeof i.inspect)return i.inspect()}if(function isMap(i){if(!_||!i||"object"!=typeof i)return!1;try{_.call(i);try{W.call(i)}catch(i){return!0}return i instanceof Map}catch(i){}return!1}(i)){var ot=[];return j&&j.call(i,(function(s,u){ot.push(inspect(u,i,!0)+" => "+inspect(s,i))})),collectionOf("Map",_.call(i),ot,xe)}if(function isSet(i){if(!W||!i||"object"!=typeof i)return!1;try{W.call(i);try{_.call(i)}catch(i){return!0}return i instanceof Set}catch(i){}return!1}(i)){var at=[];return X&&X.call(i,(function(s){at.push(inspect(s,i))})),collectionOf("Set",W.call(i),at,xe)}if(function isWeakMap(i){if(!Y||!i||"object"!=typeof i)return!1;try{Y.call(i,Y);try{Z.call(i,Z)}catch(i){return!0}return i instanceof WeakMap}catch(i){}return!1}(i))return weakCollectionOf("WeakMap");if(function isWeakSet(i){if(!Z||!i||"object"!=typeof i)return!1;try{Z.call(i,Z);try{Y.call(i,Y)}catch(i){return!0}return i instanceof WeakSet}catch(i){}return!1}(i))return weakCollectionOf("WeakSet");if(function isWeakRef(i){if(!ee||!i||"object"!=typeof i)return!1;try{return ee.call(i),!0}catch(i){}return!1}(i))return weakCollectionOf("WeakRef");if(function isNumber(i){return!("[object Number]"!==toStr(i)||qe&&"object"==typeof i&&qe in i)}(i))return markBoxed(inspect(Number(i)));if(function isBigInt(i){if(!i||"object"!=typeof i||!Pe)return!1;try{return Pe.call(i),!0}catch(i){}return!1}(i))return markBoxed(inspect(Pe.call(i)));if(function isBoolean(i){return!("[object Boolean]"!==toStr(i)||qe&&"object"==typeof i&&qe in i)}(i))return markBoxed(ae.call(i));if(function isString(i){return!("[object String]"!==toStr(i)||qe&&"object"==typeof i&&qe in i)}(i))return markBoxed(inspect(String(i)));if(!function isDate(i){return!("[object Date]"!==toStr(i)||qe&&"object"==typeof i&&qe in i)}(i)&&!isRegExp(i)){var it=arrObjKeys(i,inspect),st=Ve?Ve(i)===Object.prototype:i instanceof Object||i.constructor===Object,lt=i instanceof Object?"":"null prototype",ct=!st&&qe&&Object(i)===i&&qe in i?pe.call(toStr(i),8,-1):lt?"Object":"",ut=(st||"function"!=typeof i.constructor?"":i.constructor.name?i.constructor.name+" ":"")+(ct||lt?"["+we.call(_e.call([],ct||[],lt||[]),": ")+"] ":"");return 0===it.length?ut+"{}":xe?ut+"{"+indentedJoin(it,xe)+"}":ut+"{ "+we.call(it,", ")+" }"}return String(i)};var Ye=Object.prototype.hasOwnProperty||function(i){return i in this};function has(i,s){return Ye.call(i,s)}function toStr(i){return ie.call(i)}function indexOf(i,s){if(i.indexOf)return i.indexOf(s);for(var u=0,m=i.length;us.maxStringLength){var u=i.length-s.maxStringLength,m="... "+u+" more character"+(u>1?"s":"");return inspectString(pe.call(i,0,s.maxStringLength),s)+m}return wrapQuotes(de.call(de.call(i,/(['\\])/g,"\\$1"),/[\x00-\x1f]/g,lowbyte),"single",s)}function lowbyte(i){var s=i.charCodeAt(0),u={8:"b",9:"t",10:"n",12:"f",13:"r"}[s];return u?"\\"+u:"\\x"+(s<16?"0":"")+fe.call(s.toString(16))}function markBoxed(i){return"Object("+i+")"}function weakCollectionOf(i){return i+" { ? }"}function collectionOf(i,s,u,m){return i+" ("+s+") {"+(m?indentedJoin(u,m):we.call(u,", "))+"}"}function indentedJoin(i,s){if(0===i.length)return"";var u="\n"+s.prev+s.base;return u+we.call(i,","+u)+"\n"+s.prev}function arrObjKeys(i,s){var u=isArray(i),m=[];if(u){m.length=i.length;for(var v=0;v{var s,u,m=i.exports={};function defaultSetTimout(){throw new Error("setTimeout has not been defined")}function defaultClearTimeout(){throw new Error("clearTimeout has not been defined")}function runTimeout(i){if(s===setTimeout)return setTimeout(i,0);if((s===defaultSetTimout||!s)&&setTimeout)return s=setTimeout,setTimeout(i,0);try{return s(i,0)}catch(u){try{return s.call(null,i,0)}catch(u){return s.call(this,i,0)}}}!function(){try{s="function"==typeof setTimeout?setTimeout:defaultSetTimout}catch(i){s=defaultSetTimout}try{u="function"==typeof clearTimeout?clearTimeout:defaultClearTimeout}catch(i){u=defaultClearTimeout}}();var v,_=[],j=!1,M=-1;function cleanUpNextTick(){j&&v&&(j=!1,v.length?_=v.concat(_):M=-1,_.length&&drainQueue())}function drainQueue(){if(!j){var i=runTimeout(cleanUpNextTick);j=!0;for(var s=_.length;s;){for(v=_,_=[];++M1)for(var u=1;u{"use strict";var m=u(50414);function emptyFunction(){}function emptyFunctionWithReset(){}emptyFunctionWithReset.resetWarningCache=emptyFunction,i.exports=function(){function shim(i,s,u,v,_,j){if(j!==m){var M=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw M.name="Invariant Violation",M}}function getShim(){return shim}shim.isRequired=shim;var i={array:shim,bigint:shim,bool:shim,func:shim,number:shim,object:shim,string:shim,symbol:shim,any:shim,arrayOf:getShim,element:shim,elementType:shim,instanceOf:getShim,node:shim,objectOf:getShim,oneOf:getShim,oneOfType:getShim,shape:getShim,exact:getShim,checkPropTypes:emptyFunctionWithReset,resetWarningCache:emptyFunction};return i.PropTypes=i,i}},45697:(i,s,u)=>{i.exports=u(92703)()},50414:i=>{"use strict";i.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},55798:i=>{"use strict";var s=String.prototype.replace,u=/%20/g,m="RFC1738",v="RFC3986";i.exports={default:v,formatters:{RFC1738:function(i){return s.call(i,u,"+")},RFC3986:function(i){return String(i)}},RFC1738:m,RFC3986:v}},80129:(i,s,u)=>{"use strict";var m=u(58261),v=u(55235),_=u(55798);i.exports={formats:_,parse:v,stringify:m}},55235:(i,s,u)=>{"use strict";var m=u(12769),v=Object.prototype.hasOwnProperty,_=Array.isArray,j={allowDots:!1,allowPrototypes:!1,allowSparse:!1,arrayLimit:20,charset:"utf-8",charsetSentinel:!1,comma:!1,decoder:m.decode,delimiter:"&",depth:5,ignoreQueryPrefix:!1,interpretNumericEntities:!1,parameterLimit:1e3,parseArrays:!0,plainObjects:!1,strictNullHandling:!1},interpretNumericEntities=function(i){return i.replace(/&#(\d+);/g,(function(i,s){return String.fromCharCode(parseInt(s,10))}))},parseArrayValue=function(i,s){return i&&"string"==typeof i&&s.comma&&i.indexOf(",")>-1?i.split(","):i},M=function parseQueryStringKeys(i,s,u,m){if(i){var _=u.allowDots?i.replace(/\.([^.[]+)/g,"[$1]"):i,j=/(\[[^[\]]*])/g,M=u.depth>0&&/(\[[^[\]]*])/.exec(_),$=M?_.slice(0,M.index):_,W=[];if($){if(!u.plainObjects&&v.call(Object.prototype,$)&&!u.allowPrototypes)return;W.push($)}for(var X=0;u.depth>0&&null!==(M=j.exec(_))&&X=0;--_){var j,M=i[_];if("[]"===M&&u.parseArrays)j=[].concat(v);else{j=u.plainObjects?Object.create(null):{};var $="["===M.charAt(0)&&"]"===M.charAt(M.length-1)?M.slice(1,-1):M,W=parseInt($,10);u.parseArrays||""!==$?!isNaN(W)&&M!==$&&String(W)===$&&W>=0&&u.parseArrays&&W<=u.arrayLimit?(j=[])[W]=v:"__proto__"!==$&&(j[$]=v):j={0:v}}v=j}return v}(W,s,u,m)}};i.exports=function(i,s){var u=function normalizeParseOptions(i){if(!i)return j;if(null!==i.decoder&&void 0!==i.decoder&&"function"!=typeof i.decoder)throw new TypeError("Decoder has to be a function.");if(void 0!==i.charset&&"utf-8"!==i.charset&&"iso-8859-1"!==i.charset)throw new TypeError("The charset option must be either utf-8, iso-8859-1, or undefined");var s=void 0===i.charset?j.charset:i.charset;return{allowDots:void 0===i.allowDots?j.allowDots:!!i.allowDots,allowPrototypes:"boolean"==typeof i.allowPrototypes?i.allowPrototypes:j.allowPrototypes,allowSparse:"boolean"==typeof i.allowSparse?i.allowSparse:j.allowSparse,arrayLimit:"number"==typeof i.arrayLimit?i.arrayLimit:j.arrayLimit,charset:s,charsetSentinel:"boolean"==typeof i.charsetSentinel?i.charsetSentinel:j.charsetSentinel,comma:"boolean"==typeof i.comma?i.comma:j.comma,decoder:"function"==typeof i.decoder?i.decoder:j.decoder,delimiter:"string"==typeof i.delimiter||m.isRegExp(i.delimiter)?i.delimiter:j.delimiter,depth:"number"==typeof i.depth||!1===i.depth?+i.depth:j.depth,ignoreQueryPrefix:!0===i.ignoreQueryPrefix,interpretNumericEntities:"boolean"==typeof i.interpretNumericEntities?i.interpretNumericEntities:j.interpretNumericEntities,parameterLimit:"number"==typeof i.parameterLimit?i.parameterLimit:j.parameterLimit,parseArrays:!1!==i.parseArrays,plainObjects:"boolean"==typeof i.plainObjects?i.plainObjects:j.plainObjects,strictNullHandling:"boolean"==typeof i.strictNullHandling?i.strictNullHandling:j.strictNullHandling}}(s);if(""===i||null==i)return u.plainObjects?Object.create(null):{};for(var $="string"==typeof i?function parseQueryStringValues(i,s){var u,M={},$=s.ignoreQueryPrefix?i.replace(/^\?/,""):i,W=s.parameterLimit===1/0?void 0:s.parameterLimit,X=$.split(s.delimiter,W),Y=-1,Z=s.charset;if(s.charsetSentinel)for(u=0;u-1&&(ae=_(ae)?[ae]:ae),v.call(M,ee)?M[ee]=m.combine(M[ee],ae):M[ee]=ae}return M}(i,u):i,W=u.plainObjects?Object.create(null):{},X=Object.keys($),Y=0;Y{"use strict";var m=u(37478),v=u(12769),_=u(55798),j=Object.prototype.hasOwnProperty,M={brackets:function brackets(i){return i+"[]"},comma:"comma",indices:function indices(i,s){return i+"["+s+"]"},repeat:function repeat(i){return i}},$=Array.isArray,W=String.prototype.split,X=Array.prototype.push,pushToArray=function(i,s){X.apply(i,$(s)?s:[s])},Y=Date.prototype.toISOString,Z=_.default,ee={addQueryPrefix:!1,allowDots:!1,charset:"utf-8",charsetSentinel:!1,delimiter:"&",encode:!0,encoder:v.encode,encodeValuesOnly:!1,format:Z,formatter:_.formatters[Z],indices:!1,serializeDate:function serializeDate(i){return Y.call(i)},skipNulls:!1,strictNullHandling:!1},ae={},ie=function stringify(i,s,u,_,j,M,X,Y,Z,ie,le,ce,pe,de,fe,ye){for(var be=i,_e=ye,we=0,Se=!1;void 0!==(_e=_e.get(ae))&&!Se;){var xe=_e.get(i);if(we+=1,void 0!==xe){if(xe===we)throw new RangeError("Cyclic object value");Se=!0}void 0===_e.get(ae)&&(we=0)}if("function"==typeof Y?be=Y(s,be):be instanceof Date?be=le(be):"comma"===u&&$(be)&&(be=v.maybeMap(be,(function(i){return i instanceof Date?le(i):i}))),null===be){if(j)return X&&!de?X(s,ee.encoder,fe,"key",ce):s;be=""}if(function isNonNullishPrimitive(i){return"string"==typeof i||"number"==typeof i||"boolean"==typeof i||"symbol"==typeof i||"bigint"==typeof i}(be)||v.isBuffer(be)){if(X){var Pe=de?s:X(s,ee.encoder,fe,"key",ce);if("comma"===u&&de){for(var Ie=W.call(String(be),","),Te="",Re=0;Re0?be.join(",")||null:void 0}];else if($(Y))qe=Y;else{var Ve=Object.keys(be);qe=Z?Ve.sort(Z):Ve}for(var We=_&&$(be)&&1===be.length?s+"[]":s,He=0;He0?fe+de:""}},12769:(i,s,u)=>{"use strict";var m=u(55798),v=Object.prototype.hasOwnProperty,_=Array.isArray,j=function(){for(var i=[],s=0;s<256;++s)i.push("%"+((s<16?"0":"")+s.toString(16)).toUpperCase());return i}(),M=function arrayToObject(i,s){for(var u=s&&s.plainObjects?Object.create(null):{},m=0;m1;){var s=i.pop(),u=s.obj[s.prop];if(_(u)){for(var m=[],v=0;v=48&&X<=57||X>=65&&X<=90||X>=97&&X<=122||_===m.RFC1738&&(40===X||41===X)?$+=M.charAt(W):X<128?$+=j[X]:X<2048?$+=j[192|X>>6]+j[128|63&X]:X<55296||X>=57344?$+=j[224|X>>12]+j[128|X>>6&63]+j[128|63&X]:(W+=1,X=65536+((1023&X)<<10|1023&M.charCodeAt(W)),$+=j[240|X>>18]+j[128|X>>12&63]+j[128|X>>6&63]+j[128|63&X])}return $},isBuffer:function isBuffer(i){return!(!i||"object"!=typeof i)&&!!(i.constructor&&i.constructor.isBuffer&&i.constructor.isBuffer(i))},isRegExp:function isRegExp(i){return"[object RegExp]"===Object.prototype.toString.call(i)},maybeMap:function maybeMap(i,s){if(_(i)){for(var u=[],m=0;m{"use strict";var u=Object.prototype.hasOwnProperty;function decode(i){try{return decodeURIComponent(i.replace(/\+/g," "))}catch(i){return null}}function encode(i){try{return encodeURIComponent(i)}catch(i){return null}}s.stringify=function querystringify(i,s){s=s||"";var m,v,_=[];for(v in"string"!=typeof s&&(s="?"),i)if(u.call(i,v)){if((m=i[v])||null!=m&&!isNaN(m)||(m=""),v=encode(v),m=encode(m),null===v||null===m)continue;_.push(v+"="+m)}return _.length?s+_.join("&"):""},s.parse=function querystring(i){for(var s,u=/([^=?#&]+)=?([^&]*)/g,m={};s=u.exec(i);){var v=decode(s[1]),_=decode(s[2]);null===v||null===_||v in m||(m[v]=_)}return m}},14419:(i,s,u)=>{const m=u(60697),v=u(69450),_=m.types;i.exports=class RandExp{constructor(i,s){if(this._setDefaults(i),i instanceof RegExp)this.ignoreCase=i.ignoreCase,this.multiline=i.multiline,i=i.source;else{if("string"!=typeof i)throw new Error("Expected a regexp or string");this.ignoreCase=s&&-1!==s.indexOf("i"),this.multiline=s&&-1!==s.indexOf("m")}this.tokens=m(i)}_setDefaults(i){this.max=null!=i.max?i.max:null!=RandExp.prototype.max?RandExp.prototype.max:100,this.defaultRange=i.defaultRange?i.defaultRange:this.defaultRange.clone(),i.randInt&&(this.randInt=i.randInt)}gen(){return this._gen(this.tokens,[])}_gen(i,s){var u,m,v,j,M;switch(i.type){case _.ROOT:case _.GROUP:if(i.followedBy||i.notFollowedBy)return"";for(i.remember&&void 0===i.groupNumber&&(i.groupNumber=s.push(null)-1),m="",j=0,M=(u=i.options?this._randSelect(i.options):i.stack).length;j{"use strict";var m=u(34155),v=65536,_=4294967295;var j=u(89509).Buffer,M=u.g.crypto||u.g.msCrypto;M&&M.getRandomValues?i.exports=function randomBytes(i,s){if(i>_)throw new RangeError("requested too many random bytes");var u=j.allocUnsafe(i);if(i>0)if(i>v)for(var $=0;${"use strict";function _typeof(i){return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(i){return typeof i}:function(i){return i&&"function"==typeof Symbol&&i.constructor===Symbol&&i!==Symbol.prototype?"symbol":typeof i},_typeof(i)}Object.defineProperty(s,"__esModule",{value:!0}),s.CopyToClipboard=void 0;var m=_interopRequireDefault(u(67294)),v=_interopRequireDefault(u(20640)),_=["text","onCopy","options","children"];function _interopRequireDefault(i){return i&&i.__esModule?i:{default:i}}function ownKeys(i,s){var u=Object.keys(i);if(Object.getOwnPropertySymbols){var m=Object.getOwnPropertySymbols(i);s&&(m=m.filter((function(s){return Object.getOwnPropertyDescriptor(i,s).enumerable}))),u.push.apply(u,m)}return u}function _objectSpread(i){for(var s=1;s=0||(v[u]=i[u]);return v}(i,s);if(Object.getOwnPropertySymbols){var _=Object.getOwnPropertySymbols(i);for(m=0;m<_.length;m++)u=_[m],s.indexOf(u)>=0||Object.prototype.propertyIsEnumerable.call(i,u)&&(v[u]=i[u])}return v}function _defineProperties(i,s){for(var u=0;u{"use strict";var m=u(74300).CopyToClipboard;m.CopyToClipboard=m,i.exports=m},53441:(i,s,u)=>{"use strict";function _typeof(i){return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(i){return typeof i}:function(i){return i&&"function"==typeof Symbol&&i.constructor===Symbol&&i!==Symbol.prototype?"symbol":typeof i},_typeof(i)}Object.defineProperty(s,"__esModule",{value:!0}),s.DebounceInput=void 0;var m=_interopRequireDefault(u(67294)),v=_interopRequireDefault(u(91296)),_=["element","onChange","value","minLength","debounceTimeout","forceNotifyByEnter","forceNotifyOnBlur","onKeyDown","onBlur","inputRef"];function _interopRequireDefault(i){return i&&i.__esModule?i:{default:i}}function _objectWithoutProperties(i,s){if(null==i)return{};var u,m,v=function _objectWithoutPropertiesLoose(i,s){if(null==i)return{};var u,m,v={},_=Object.keys(i);for(m=0;m<_.length;m++)u=_[m],s.indexOf(u)>=0||(v[u]=i[u]);return v}(i,s);if(Object.getOwnPropertySymbols){var _=Object.getOwnPropertySymbols(i);for(m=0;m<_.length;m++)u=_[m],s.indexOf(u)>=0||Object.prototype.propertyIsEnumerable.call(i,u)&&(v[u]=i[u])}return v}function ownKeys(i,s){var u=Object.keys(i);if(Object.getOwnPropertySymbols){var m=Object.getOwnPropertySymbols(i);s&&(m=m.filter((function(s){return Object.getOwnPropertyDescriptor(i,s).enumerable}))),u.push.apply(u,m)}return u}function _objectSpread(i){for(var s=1;s=m?u.notify(i):s.length>v.length&&u.notify(_objectSpread(_objectSpread({},i),{},{target:_objectSpread(_objectSpread({},i.target),{},{value:""})}))}))})),_defineProperty(_assertThisInitialized(u),"onKeyDown",(function(i){"Enter"===i.key&&u.forceNotify(i);var s=u.props.onKeyDown;s&&(i.persist(),s(i))})),_defineProperty(_assertThisInitialized(u),"onBlur",(function(i){u.forceNotify(i);var s=u.props.onBlur;s&&(i.persist(),s(i))})),_defineProperty(_assertThisInitialized(u),"createNotifier",(function(i){if(i<0)u.notify=function(){return null};else if(0===i)u.notify=u.doNotify;else{var s=(0,v.default)((function(i){u.isDebouncing=!1,u.doNotify(i)}),i);u.notify=function(i){u.isDebouncing=!0,s(i)},u.flush=function(){return s.flush()},u.cancel=function(){u.isDebouncing=!1,s.cancel()}}})),_defineProperty(_assertThisInitialized(u),"doNotify",(function(){u.props.onChange.apply(void 0,arguments)})),_defineProperty(_assertThisInitialized(u),"forceNotify",(function(i){var s=u.props.debounceTimeout;if(u.isDebouncing||!(s>0)){u.cancel&&u.cancel();var m=u.state.value,v=u.props.minLength;m.length>=v?u.doNotify(i):u.doNotify(_objectSpread(_objectSpread({},i),{},{target:_objectSpread(_objectSpread({},i.target),{},{value:m})}))}})),u.isDebouncing=!1,u.state={value:void 0===i.value||null===i.value?"":i.value};var m=u.props.debounceTimeout;return u.createNotifier(m),u}return function _createClass(i,s,u){return s&&_defineProperties(i.prototype,s),u&&_defineProperties(i,u),Object.defineProperty(i,"prototype",{writable:!1}),i}(DebounceInput,[{key:"componentDidUpdate",value:function componentDidUpdate(i){if(!this.isDebouncing){var s=this.props,u=s.value,m=s.debounceTimeout,v=i.debounceTimeout,_=i.value,j=this.state.value;void 0!==u&&_!==u&&j!==u&&this.setState({value:u}),m!==v&&this.createNotifier(m)}}},{key:"componentWillUnmount",value:function componentWillUnmount(){this.flush&&this.flush()}},{key:"render",value:function render(){var i,s,u=this.props,v=u.element,j=(u.onChange,u.value,u.minLength,u.debounceTimeout,u.forceNotifyByEnter),M=u.forceNotifyOnBlur,$=u.onKeyDown,W=u.onBlur,X=u.inputRef,Y=_objectWithoutProperties(u,_),Z=this.state.value;i=j?{onKeyDown:this.onKeyDown}:$?{onKeyDown:$}:{},s=M?{onBlur:this.onBlur}:W?{onBlur:W}:{};var ee=X?{ref:X}:{};return m.default.createElement(v,_objectSpread(_objectSpread(_objectSpread(_objectSpread({},Y),{},{onChange:this.onChange,value:Z},i),s),ee))}}]),DebounceInput}(m.default.PureComponent);s.DebounceInput=j,_defineProperty(j,"defaultProps",{element:"input",type:"text",onKeyDown:void 0,onBlur:void 0,value:void 0,minLength:0,debounceTimeout:100,forceNotifyByEnter:!0,forceNotifyOnBlur:!0,inputRef:void 0})},775:(i,s,u)=>{"use strict";var m=u(53441).DebounceInput;m.DebounceInput=m,i.exports=m},64448:(i,s,u)=>{"use strict";var m=u(67294),v=u(27418),_=u(63840);function y(i){for(var s="https://reactjs.org/docs/error-decoder.html?invariant="+i,u=1;u