diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index 46adaad..0000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[env] -MTP_TYPE_MAPS = { value = "mtp-type-maps/type-maps.yaml", relative = true } diff --git a/.envrc b/.envrc deleted file mode 100644 index 3550a30..0000000 --- a/.envrc +++ /dev/null @@ -1 +0,0 @@ -use flake diff --git a/.fallowrc.json b/.fallowrc.json deleted file mode 100644 index ba50fb5..0000000 --- a/.fallowrc.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/schema.json", - "entry": [ - "src/index.{ts,tsx,js,jsx}", - "src/main.{ts,tsx,js,jsx}", - "apps/pwa/src/serviceWorker.ts", - "apps/tauri/render-version.ts", - "packages/**/*.test.ts" - ], - "workspaces": { - "packages": ["packages/*", "apps/*"] - }, - "duplicates": { - "minOccurrences": 3, - "ignore": ["**/scripts/**", "**.test.ts", "licenses/**"] - }, - "rules": {} -} diff --git a/.forgejo/workflows/dependency-builds.yml b/.forgejo/workflows/dependency-builds.yml deleted file mode 100644 index 2ae48fe..0000000 --- a/.forgejo/workflows/dependency-builds.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Dependency builds - -on: - pull_request: - -env: - FORGEJO_TOKEN: "" - GITHUB_TOKEN: "" - -jobs: - web: - if: ${{ github.actor == 'rasensprenger' }} - name: Build web - runs-on: nixos - steps: - - uses: https://data.forgejo.org/actions/checkout@v4 - with: - persist-credentials: false - - run: git submodule update --init --recursive - - run: nix develop .#electron --command pnpm install --frozen-lockfile - - run: nix develop .#electron --command pnpm run build:packages - - run: nix develop .#electron --command pnpm run build:web - - desktop: - if: ${{ github.actor == 'rasensprenger' }} - name: Build desktop - runs-on: nixos - steps: - - uses: https://data.forgejo.org/actions/checkout@v4 - with: - persist-credentials: false - - run: git submodule update --init --recursive - - run: nix develop .#electron --command pnpm install --frozen-lockfile - - run: nix develop .#electron --command pnpm run build:packages - - run: nix develop .#electron --command pnpm run build:desktop - - native-mtp: - if: ${{ github.actor == 'rasensprenger' }} - name: Test native MTP - runs-on: nixos - steps: - - run: nix profile add nixpkgs#nodejs_24 - - uses: https://data.forgejo.org/actions/checkout@v4 - with: - persist-credentials: false - - run: git submodule update --init --recursive - - run: nix develop .#electron --command bash -lc 'cd apps/tauri/src-tauri && cargo test' - - mobile: - if: ${{ github.actor == 'rasensprenger' }} - name: Build mobile - runs-on: nixos - steps: - - uses: https://data.forgejo.org/actions/checkout@v4 - with: - persist-credentials: false - - run: git submodule update --init --recursive - - run: nix develop .#tauri --command pnpm install --frozen-lockfile - - run: nix develop .#tauri --command pnpm run build:packages - - run: nix develop .#tauri --command pnpm --dir apps/tauri run build:mobile:ci diff --git a/.forgejo/workflows/deploy-dev.yml b/.forgejo/workflows/deploy-dev.yml index 7f59ec1..26053ff 100644 --- a/.forgejo/workflows/deploy-dev.yml +++ b/.forgejo/workflows/deploy-dev.yml @@ -1,317 +1,27 @@ on: - workflow_dispatch: push: branches: - dev - paths-ignore: - - flake.nix jobs: - build-web: - runs-on: nixos + deploy: + runs-on: docker + steps: - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 - - name: Pull git submodules - run: git submodule update --init --recursive + - name: Setup Bun + uses: oven-sh/setup-bun@v2 - name: Install dependencies - run: nix develop .#electron --command pnpm install --frozen-lockfile + run: bun install --frozen-lockfile - - name: Copy licenses - run: nix develop .#electron --command pnpm run copy-licenses + - name: Build + run: bun run build:web - - name: Build packages - run: nix develop .#electron --command pnpm run build:packages - - - name: Build web - run: nix develop .#electron --command pnpm run build:web + - name: Install rsync + run: apt-get update && apt-get install -y rsync - name: Deploy - run: nix develop .#electron --command rsync -a --delete apps/web/dist/ /var/lib/www/tensamin-web-dev/ - - build-mobile: - runs-on: nixos - steps: - - name: Check out repo - uses: https://data.forgejo.org/actions/checkout@v4 - - - name: Pull git submodules - run: git submodule update --init --recursive - - - name: Install dependencies - run: nix develop .#tauri --command pnpm install --frozen-lockfile - - - name: Copy licenses - run: nix develop .#tauri --command pnpm run copy-licenses - - - name: Build packages - run: nix develop .#tauri --command pnpm run build:packages - - - name: Setup Android Keystore - env: - KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} - KEYSTORE_PROPERTIES: ${{ secrets.ANDROID_KEYSTORE_PROPERTIES }} - run: | - set -euo pipefail - - if [ -z "$KEYSTORE_BASE64" ]; then - echo "ANDROID_KEYSTORE_BASE64 secret is missing or empty" - exit 1 - fi - - if [ -z "$KEYSTORE_PROPERTIES" ]; then - echo "ANDROID_KEYSTORE_PROPERTIES secret is missing or empty" - exit 1 - fi - - printf '%s' "$KEYSTORE_BASE64" \ - | tr -d '[:space:]' \ - | base64 -d > keystore.jks - - printf '%s' "$KEYSTORE_PROPERTIES" \ - | sed 's/\\n/\n/g' \ - | tr -d '\r' \ - | sed 's|^[[:space:]]*storeFile[[:space:]]*=.*|storeFile=keystore.jks|' \ - > keystore.properties - - grep -q '^[[:space:]]*storeFile[[:space:]]*=' keystore.properties || printf '\nstoreFile=keystore.jks\n' >> keystore.properties - - if [ ! -s keystore.jks ]; then - echo "Decoded keystore.jks is missing or empty" - exit 1 - fi - - if [ ! -s keystore.properties ]; then - echo "Generated keystore.properties is missing or empty" - exit 1 - fi - - if ! grep -q '^[[:space:]]*keyAlias[[:space:]]*=' keystore.properties; then - echo "keystore.properties is missing keyAlias" - exit 1 - fi - - if ! grep -Eq '^[[:space:]]*(keyPassword|password)[[:space:]]*=' keystore.properties; then - echo "keystore.properties is missing keyPassword or password" - exit 1 - fi - - if ! grep -Eq '^[[:space:]]*(storePassword|password)[[:space:]]*=' keystore.properties; then - echo "keystore.properties is missing storePassword or password" - exit 1 - fi - - - name: Build mobile - run: nix develop .#tauri --command pnpm run build:mobile - - - name: Upload mobile artifact - uses: https://data.forgejo.org/actions/upload-artifact@v3 - with: - name: mobile-apk - path: apps/tauri/src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk - - build-desktop: - runs-on: nixos - strategy: - matrix: - target: [linux] - steps: - - name: Check out repo - uses: https://data.forgejo.org/actions/checkout@v4 - - - name: Pull git submodules - run: git submodule update --init --recursive - - - name: Install dependencies - run: nix develop .#electron --command pnpm install --frozen-lockfile - - - name: Copy licenses - run: nix develop .#electron --command pnpm run copy-licenses - - - name: Build packages - run: nix develop .#electron --command pnpm run build:packages - - - name: Set Electron dev version - run: | - nix develop .#electron --command bash <<'EOF' - set -euo pipefail - VERSION="$(node -p "require('./package.json').version")" - SHORT_SHA="$(git rev-parse --short HEAD)" - DEV_VERSION="$VERSION-dev-$SHORT_SHA" - export DEV_VERSION - node -e ' - const fs = require("fs"); - const path = "apps/electron/package.json"; - const pkg = JSON.parse(fs.readFileSync(path, "utf8")); - pkg.version = process.env.DEV_VERSION; - fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + "\n"); - ' - EOF - - - name: Build Electron desktop - run: | - nix develop .#electron --command bash <<'EOF' - set -euo pipefail - cd apps/electron - pnpm run package:raw - EOF - - - name: Upload desktop artifacts - uses: https://data.forgejo.org/actions/upload-artifact@v3 - with: - name: electron-desktop-${{ matrix.target }} - path: apps/electron/release/ - - release: - runs-on: nixos - needs: [build-web, build-mobile, build-desktop] - steps: - - name: Check out repo - uses: https://data.forgejo.org/actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Pull git submodules - run: git submodule update --init --recursive - - - name: Install dependencies - run: nix develop .#electron --command pnpm install --frozen-lockfile - - - name: Download mobile artifact - uses: https://data.forgejo.org/actions/download-artifact@v3 - with: - name: mobile-apk - path: apps/tauri/src-tauri/gen/android/app/build/outputs/apk/universal/release/ - - - name: Download desktop artifacts - uses: https://data.forgejo.org/actions/download-artifact@v3 - with: - name: electron-desktop-linux - path: apps/electron/release/ - - - name: Read version and hash - id: version - run: | - nix develop .#electron --command bash <<'EOF' - set -euo pipefail - VERSION="$(node -p "require('./package.json').version")" - SHORT_SHA="$(git rev-parse --short HEAD)" - echo "version=$VERSION" >> "$FORGEJO_OUTPUT" - echo "short_sha=$SHORT_SHA" >> "$FORGEJO_OUTPUT" - echo "tag=${VERSION}-dev-${SHORT_SHA}" >> "$FORGEJO_OUTPUT" - echo "title=${VERSION}-dev-${SHORT_SHA}" >> "$FORGEJO_OUTPUT" - EOF - - - name: Copy releases - env: - TENSAMIN_RELEASE_VERSION: ${{ steps.version.outputs.tag }} - TENSAMIN_RELEASE_TAG: ${{ steps.version.outputs.tag }} - run: | - nix develop .#electron --command bash <<'EOF' - set -euo pipefail - ASSET_BASE_URL="${{ forgejo.api_url }}" - ASSET_BASE_URL="${ASSET_BASE_URL%/api/v1}/${{ forgejo.repository }}/releases/download/${{ steps.version.outputs.tag }}" - FORGEJO_RELEASE_ASSET_BASE_URL="$ASSET_BASE_URL" pnpm run copy-releases - EOF - - - name: Create pre-release and upload files - env: - TOKEN: ${{ forgejo.token }} - API: ${{ forgejo.api_url }} - REPO: ${{ forgejo.repository }} - SHA: ${{ forgejo.sha }} - TAG: ${{ steps.version.outputs.tag }} - TITLE: ${{ steps.version.outputs.title }} - run: | - nix develop .#electron --command bash <<'EOF' - set -eu - - test -d releases - find releases -type f | grep -q . - - LATEST_PROD_TAG="$(git for-each-ref refs/tags --sort=-creatordate --format='%(refname:short)' | awk '!/-/' | head -n 1 || true)" - - if [ -n "$LATEST_PROD_TAG" ]; then - RAW_LOG="$(git log "$LATEST_PROD_TAG"..HEAD --pretty=format:'- %s')" - else - RAW_LOG="$(git log --pretty=format:'- %s')" - fi - - export RAW_LOG LATEST_PROD_TAG API REPO TAG - BODY="$(node -e ' - const raw = process.env.RAW_LOG; - const lines = raw.split("\n"); - const commits = []; - for (const line of lines) { - const msg = line.replace(/^- /, ""); - const parts = msg.split(/(?=\([^)]+\):)/).filter(Boolean).map(s => s.trim()).filter(s => s); - for (const part of parts) { - commits.push(part); - } - } - const priority = { "(feat):": 1, "(fix):": 2, "(qol):": 3 }; - commits.sort((a, b) => { - const tagA = a.match(/^(\([^)]+\):)/)?.[1] || ""; - const tagB = b.match(/^(\([^)]+\):)/)?.[1] || ""; - return (priority[tagA] || 99) - (priority[tagB] || 99); - }); - const log = commits.map(c => "- " + c).join("\n"); - const latestTag = process.env.LATEST_PROD_TAG; - if (latestTag) { - const serverUrl = process.env.API.replace(/\/api\/v1.*/, ""); - const compareUrl = serverUrl + "/" + process.env.REPO + "/compare/" + latestTag + "..." + process.env.TAG; - console.log(log + "\n\n[View changes](" + compareUrl + ")"); - } else { - console.log(log); - } - ')" - - HTTP_STATUS=$(curl -s -w "%{http_code}" -o release_out.json -H "Authorization: token $TOKEN" "$API/repos/$REPO/releases/tags/$TAG") - - if [ "$HTTP_STATUS" = "200" ]; then - echo "Release $TAG already exists." - RELEASE_ID="$(jq -r .id release_out.json)" - else - echo "Creating new pre-release for $TAG" - RELEASE_JSON="$(curl -f -sS -X POST "$API/repos/$REPO/releases" \ - -H "Authorization: token $TOKEN" \ - -H "Content-Type: application/json" \ - -d "$(jq -n \ - --arg tag "$TAG" \ - --arg name "$TITLE" \ - --arg body "$BODY" \ - --arg target "$SHA" \ - '{ - tag_name: $tag, - name: $name, - body: $body, - target_commitish: $target, - draft: false, - prerelease: true - }')")" - RELEASE_ID="$(echo "$RELEASE_JSON" | jq -r .id)" - fi - - ASSET_BASE_URL="${API%/api/v1}/$REPO/releases/download/$TAG" - export ASSET_BASE_URL - node -e ' - const fs = require("fs"); - const path = "releases/electron-release-metadata.json"; - const metadata = JSON.parse(fs.readFileSync(path, "utf8")); - metadata.version = process.env.TAG; - metadata.tag = process.env.TAG; - for (const artifact of metadata.artifacts || []) { - artifact.url = `${process.env.ASSET_BASE_URL}/${encodeURIComponent(artifact.name)}`; - } - fs.writeFileSync(path, `${JSON.stringify(metadata, null, 2)}\n`); - ' - - find releases -type f -print0 | while IFS= read -r -d '' file; do - name="$(basename "$file")" - curl -fsS -X POST "$API/repos/$REPO/releases/$RELEASE_ID/assets?name=$name" \ - -H "Authorization: token $TOKEN" \ - -F "attachment=@$file" - done - EOF + run: rsync -a --delete apps/web/dist/ /var/lib/www/tensamin-web-dev/ diff --git a/.forgejo/workflows/deploy-prod.yml b/.forgejo/workflows/deploy-prod.yml index 621a0cd..1df5b60 100644 --- a/.forgejo/workflows/deploy-prod.yml +++ b/.forgejo/workflows/deploy-prod.yml @@ -1,213 +1,51 @@ on: - workflow_dispatch: push: branches: - main - paths-ignore: - - flake.nix jobs: - build-web: - runs-on: nixos + deploy: + runs-on: docker + steps: - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 - - name: Pull git submodules - run: git submodule update --init --recursive + - name: Install Packages + run: apt-get update && apt-get install -y sudo curl jq + + - name: Install Nix + uses: https://github.com/cachix/install-nix-action@v30 + + - name: Install Bun + uses: oven-sh/setup-bun@v2 - name: Install dependencies - run: nix develop .#electron --command pnpm install --frozen-lockfile - - - name: Copy licenses - run: nix develop .#electron --command pnpm run copy-licenses - - - name: Build packages - run: nix develop .#electron --command pnpm run build:packages - - - name: Build web - run: nix develop .#electron --command pnpm run build:web - - - name: Deploy - run: nix develop .#electron --command rsync -a --delete apps/web/dist/ /var/lib/www/tensamin-web-prod/ - - build-mobile: - runs-on: nixos - steps: - - name: Check out repo - uses: https://data.forgejo.org/actions/checkout@v4 - - - name: Pull git submodules - run: git submodule update --init --recursive - - - name: Install dependencies - run: nix develop .#tauri --command pnpm install --frozen-lockfile - - - name: Copy licenses - run: nix develop .#tauri --command pnpm run copy-licenses - - - name: Build packages - run: nix develop .#tauri --command pnpm run build:packages + run: bun install --frozen-lockfile - name: Setup Android Keystore env: KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} KEYSTORE_PROPERTIES: ${{ secrets.ANDROID_KEYSTORE_PROPERTIES }} run: | - set -euo pipefail + bun -e "require('fs').writeFileSync('keystore.jks', Buffer.from(process.env.KEYSTORE_BASE64.replace(/\s+/g, ''), 'base64'))" + bun -e "const content = process.env.KEYSTORE_PROPERTIES.replace(/\\\\n/g, '\n').replace(/\\r/g, '').split('\n').map(l => l.trim()).filter(l => l).join('\n'); require('fs').writeFileSync('keystore.properties', content)" - if [ -z "$KEYSTORE_BASE64" ]; then - echo "ANDROID_KEYSTORE_BASE64 secret is missing or empty" - exit 1 - fi + - name: Build + run: bun run build:apps - if [ -z "$KEYSTORE_PROPERTIES" ]; then - echo "ANDROID_KEYSTORE_PROPERTIES secret is missing or empty" - exit 1 - fi + - name: Install rsync + run: apt-get update && apt-get install -y rsync - printf '%s' "$KEYSTORE_BASE64" \ - | tr -d '[:space:]' \ - | base64 -d > keystore.jks - - printf '%s' "$KEYSTORE_PROPERTIES" \ - | sed 's/\\n/\n/g' \ - | tr -d '\r' \ - | sed 's|^[[:space:]]*storeFile[[:space:]]*=.*|storeFile=keystore.jks|' \ - > keystore.properties - - grep -q '^[[:space:]]*storeFile[[:space:]]*=' keystore.properties || printf '\nstoreFile=keystore.jks\n' >> keystore.properties - - if [ ! -s keystore.jks ]; then - echo "Decoded keystore.jks is missing or empty" - exit 1 - fi - - if [ ! -s keystore.properties ]; then - echo "Generated keystore.properties is missing or empty" - exit 1 - fi - - if ! grep -q '^[[:space:]]*keyAlias[[:space:]]*=' keystore.properties; then - echo "keystore.properties is missing keyAlias" - exit 1 - fi - - if ! grep -Eq '^[[:space:]]*(keyPassword|password)[[:space:]]*=' keystore.properties; then - echo "keystore.properties is missing keyPassword or password" - exit 1 - fi - - if ! grep -Eq '^[[:space:]]*(storePassword|password)[[:space:]]*=' keystore.properties; then - echo "keystore.properties is missing storePassword or password" - exit 1 - fi - - - name: Build mobile - run: nix develop .#tauri --command pnpm run build:mobile - - - name: Upload mobile artifact - uses: https://data.forgejo.org/actions/upload-artifact@v3 - with: - name: mobile-apk - path: apps/tauri/src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk - - build-desktop: - runs-on: nixos - strategy: - matrix: - target: [linux] - steps: - - name: Check out repo - uses: https://data.forgejo.org/actions/checkout@v4 - - - name: Pull git submodules - run: git submodule update --init --recursive - - - name: Install dependencies - run: nix develop .#electron --command pnpm install --frozen-lockfile - - - name: Copy licenses - run: nix develop .#electron --command pnpm run copy-licenses - - - name: Build packages - run: nix develop .#electron --command pnpm run build:packages - - - name: Set Electron prod version - run: | - nix develop .#electron --command bash <<'EOF' - set -euo pipefail - VERSION="$(node -p "require('./package.json').version")" - export VERSION - node -e ' - const fs = require("fs"); - const path = "apps/electron/package.json"; - const pkg = JSON.parse(fs.readFileSync(path, "utf8")); - pkg.version = process.env.VERSION; - fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + "\n"); - ' - EOF - - - name: Build Electron desktop - run: | - nix develop .#electron --command bash <<'EOF' - set -euo pipefail - cd apps/electron - pnpm run package:raw - EOF - - - name: Upload desktop artifacts - uses: https://data.forgejo.org/actions/upload-artifact@v3 - with: - name: electron-desktop-${{ matrix.target }} - path: apps/electron/release/ - - release: - runs-on: nixos - needs: [build-web, build-mobile, build-desktop] - steps: - - name: Check out repo - uses: https://data.forgejo.org/actions/checkout@v4 - - - name: Pull git submodules - run: git submodule update --init --recursive - - - name: Install dependencies - run: nix develop .#electron --command pnpm install --frozen-lockfile - - - name: Download mobile artifact - uses: https://data.forgejo.org/actions/download-artifact@v3 - with: - name: mobile-apk - path: apps/tauri/src-tauri/gen/android/app/build/outputs/apk/universal/release/ - - - name: Download desktop artifacts - uses: https://data.forgejo.org/actions/download-artifact@v3 - with: - name: electron-desktop-linux - path: apps/electron/release/ + - name: Deploy + run: rsync -a --delete apps/web/dist/ /var/lib/www/tensamin-web-prod/ - name: Read version id: version run: | - nix develop .#electron --command bash <<'EOF' - set -euo pipefail VERSION="$(node -p "require('./package.json').version")" echo "version=$VERSION" >> "$FORGEJO_OUTPUT" - echo "tag=$VERSION" >> "$FORGEJO_OUTPUT" - EOF - - - name: Copy releases - env: - TENSAMIN_RELEASE_VERSION: ${{ steps.version.outputs.tag }} - TENSAMIN_RELEASE_TAG: ${{ steps.version.outputs.tag }} - run: | - nix develop .#electron --command bash <<'EOF' - set -euo pipefail - ASSET_BASE_URL="${{ forgejo.api_url }}" - ASSET_BASE_URL="${ASSET_BASE_URL%/api/v1}/${{ forgejo.repository }}/releases/download/${{ steps.version.outputs.tag }}" - FORGEJO_RELEASE_ASSET_BASE_URL="$ASSET_BASE_URL" pnpm run copy-releases - EOF + echo "tag=v$VERSION" >> "$FORGEJO_OUTPUT" - name: Create release and upload files env: @@ -216,98 +54,42 @@ jobs: REPO: ${{ forgejo.repository }} SHA: ${{ forgejo.sha }} TAG: ${{ steps.version.outputs.tag }} + VERSION: ${{ steps.version.outputs.version }} run: | - nix develop .#electron --command bash <<'EOF' set -eu test -d releases find releases -type f | grep -q . - COMMIT_MSG="$(git log -1 --pretty=%B | sed 's/$/ /')" - HTTP_STATUS=$(curl -s -w "%{http_code}" -o release_out.json -H "Authorization: token $TOKEN" "$API/repos/$REPO/releases/tags/$TAG") if [ "$HTTP_STATUS" = "200" ]; then echo "Release $TAG already exists." - exit 0 + RELEASE_ID="$(jq -r .id release_out.json)" + else + echo "Creating new release for $TAG" + RELEASE_JSON="$(curl -f -sS -X POST "$API/repos/$REPO/releases" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d "$(jq -n \ + --arg tag "$TAG" \ + --arg name "$TAG" \ + --arg body "Release $VERSION" \ + --arg target "$SHA" \ + '{ + tag_name: $tag, + name: $name, + body: $body, + target_commitish: $target, + draft: false, + prerelease: false + }')")" + RELEASE_ID="$(echo "$RELEASE_JSON" | jq -r .id)" fi - echo "Creating new release for $TAG" - RELEASE_JSON="$(curl -f -sS -X POST "$API/repos/$REPO/releases" \ - -H "Authorization: token $TOKEN" \ - -H "Content-Type: application/json" \ - -d "$(jq -n \ - --arg tag "$TAG" \ - --arg name "$TAG" \ - --arg body "$COMMIT_MSG" \ - --arg target "$SHA" \ - '{ - tag_name: $tag, - name: $name, - body: $body, - target_commitish: $target, - draft: false, - prerelease: false - }')")" - RELEASE_ID="$(echo "$RELEASE_JSON" | jq -r .id)" - - ASSET_BASE_URL="${API%/api/v1}/$REPO/releases/download/$TAG" - export ASSET_BASE_URL - node -e ' - const fs = require("fs"); - const path = "releases/electron-release-metadata.json"; - const metadata = JSON.parse(fs.readFileSync(path, "utf8")); - metadata.version = process.env.TAG; - metadata.tag = process.env.TAG; - for (const artifact of metadata.artifacts || []) { - artifact.url = `${process.env.ASSET_BASE_URL}/${encodeURIComponent(artifact.name)}`; - } - fs.writeFileSync(path, `${JSON.stringify(metadata, null, 2)}\n`); - ' - find releases -type f -print0 | while IFS= read -r -d '' file; do name="$(basename "$file")" curl -fsS -X POST "$API/repos/$REPO/releases/$RELEASE_ID/assets?name=$name" \ -H "Authorization: token $TOKEN" \ -F "attachment=@$file" done - EOF - - - name: Delete dev releases - env: - TOKEN: ${{ forgejo.token }} - API: ${{ forgejo.api_url }} - REPO: ${{ forgejo.repository }} - run: | - nix develop .#electron --command bash <<'EOF' - set -eu - - PAGE=1 - DELETE_RELEASES=delete-dev-releases.tsv - - : > "$DELETE_RELEASES" - - while :; do - curl -fsS \ - -H "Authorization: token $TOKEN" \ - "$API/repos/$REPO/releases?page=$PAGE&limit=50&pre-release=true" \ - -o releases.json - - COUNT="$(jq 'length' releases.json)" - test "$COUNT" -gt 0 || break - - jq -r \ - '.[] | select(.prerelease == true) | select(.tag_name | contains("-dev-")) | [.id, .tag_name] | @tsv' releases.json \ - >> "$DELETE_RELEASES" - - PAGE="$((PAGE + 1))" - done - - while IFS="$(printf '\t')" read -r release_id release_tag; do - test -n "$release_id" || continue - echo "Deleting dev release $release_tag" - curl -fsS -X DELETE \ - -H "Authorization: token $TOKEN" \ - "$API/repos/$REPO/releases/$release_id" - done < "$DELETE_RELEASES" - EOF diff --git a/.gitignore b/.gitignore index 261f60b..a63f278 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,2 @@ node_modules releases -.fallow -.direnv -keystore.jks -keystore.properties diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 1e9ee12..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "mtp-type-maps"] - path = mtp-type-maps - url = https://git.methanium.net/tensamin/mtp-type-maps diff --git a/.prettierignore b/.prettierignore index 15e3fb5..75752bc 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,5 +5,4 @@ coverage *.tsbuildinfo bun.lock apps/tauri/src-tauri -apps/electron/release licenses diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 34ff437..5bb82f5 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,6 +2,7 @@ "recommendations": [ "tauri-apps.tauri-vscode", "rust-lang.rust-analyzer", - "bradlc.vscode-tailwindcss" + "bradlc.vscode-tailwindcss", + "antfu.vite" ] } diff --git a/LICENSE b/LICENSE index e952c84..d218eff 100644 --- a/LICENSE +++ b/LICENSE @@ -1,15 +1,16 @@ -Copyright (c) 2025 Methanium - +Copyright (c) [2025] [Methanium] All rights reserved. -No part of this software, source code, documentation, or -associated materials may be copied, reproduced, modified, -distributed, published, sublicensed, sold, or used to create -derivative works without prior written permission from the -copyright holder. +This software is protected by copyright. Copying, editing, +distributing, publicly performing, or any other use of this software +or its components, in source or binary form, is strictly prohibited without the express +written permission of the copyright holder. -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY -OF ANY KIND, EXPRESS OR IMPLIED. TO THE MAXIMUM -EXTENT PERMITTED BY LAW, THE COPYRIGHT HOLDER SHALL -NOT BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER -LIABILITY ARISING FROM THE SOFTWARE OR ITS USE. +FUTURE LICENSE ACCEPTANCE: +It is the copyright holder's intention to release this software in the future +under a license yet to be defined, which will, among other things, +allow private, non-commercial use. This statement does not constitute +a current license grant and does not alter the above +prohibition on use, copying, or modification. Until the formal +publication of such a future license, all rights remain +reserved. diff --git a/README b/README deleted file mode 100644 index ff7c637..0000000 --- a/README +++ /dev/null @@ -1,3 +0,0 @@ -# Information - -All dev releases get deleted upon creation of the latest prod release. diff --git a/TODO b/TODO deleted file mode 100644 index e4b9435..0000000 --- a/TODO +++ /dev/null @@ -1,6 +0,0 @@ -- Add a bunch of tests -- Full accessability -- Add settings saving & update onboarding to use it -- Add onboarding page to load one profile during onboarding - --> Most folders in packages/ have specific todos diff --git a/apps/electron/.gitignore b/apps/electron/.gitignore deleted file mode 100644 index b53a288..0000000 --- a/apps/electron/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -dist -release diff --git a/apps/electron/package.json b/apps/electron/package.json deleted file mode 100644 index 560ed34..0000000 --- a/apps/electron/package.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "name": "@tensamin/electron", - "private": true, - "version": "0.0.3", - "description": "Tensamin desktop client", - "author": "methanium", - "homepage": "https://git.methanium.net/tensamin/client", - "desktopName": "Tensamin", - "type": "module", - "main": "dist/main/main.js", - "scripts": { - "clean": "rm -rf dist release", - "lint": "eslint src", - "build:web": "cd ../.. && pnpm run build:web", - "build": "tsc -p tsconfig.json && esbuild src/preload/preload.ts --bundle --platform=node --format=cjs --external:electron --outfile=dist/preload/preload.cjs", - "dev:raw": "cd ../.. && pnpm run build:web && cd apps/electron && pnpm run build && electron . --verbose", - "dev": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop ../..#electron --command pnpm run dev:raw; else pnpm run dev:raw; fi", - "start:raw": "pnpm run build && electron .", - "start": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop ../..#electron --command pnpm run start:raw; else pnpm run start:raw; fi", - "package:raw": "cd ../.. && pnpm run build:web && cd apps/electron && pnpm run build && electron-builder --publish never", - "package": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop ../..#electron --command pnpm run package:raw; else pnpm run package:raw; fi", - "package:linux:raw": "cd ../.. && pnpm run build:web && cd apps/electron && pnpm run build && electron-builder --linux --publish never", - "package:linux": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop ../..#electron --command pnpm run package:linux:raw; else pnpm run package:linux:raw; fi", - "package:windows:raw": "cd ../.. && pnpm run build:web && cd apps/electron && pnpm run build && electron-builder --win --publish never", - "package:windows": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop ../..#electron --command pnpm run package:windows:raw; else pnpm run package:windows:raw; fi", - "checksum": "node scripts/generate-release-metadata.ts", - "generate-signing-key": "node scripts/generate-signing-key.ts", - "validate:raw": "pnpm run build && pnpm run package:linux:raw", - "validate": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop ../..#electron --command pnpm run validate:raw; else pnpm run validate:raw; fi" - }, - "devDependencies": { - "@types/node": "^26.1.2", - "electron": "^43.3.0", - "electron-builder": "^26.15.3", - "esbuild": "^0.28.1", - "typescript": "~6.0.3" - }, - "build": { - "appId": "net.tensamin.client", - "productName": "Tensamin", - "executableName": "tensamin", - "artifactName": "Tensamin-${version}-${os}-${arch}.${ext}", - "icon": "build/icons/icon.png", - "directories": { - "output": "release" - }, - "toolsets": { - "appimage": "1.0.3" - }, - "files": [ - "dist/**/*", - "package.json" - ], - "extraResources": [ - { - "from": "../web/dist", - "to": "web" - }, - { - "from": "build/icons", - "to": "icons", - "filter": [ - "32x32.png", - "icon.png" - ] - } - ], - "linux": { - "target": [ - "AppImage", - "deb", - "rpm" - ], - "icon": "build/icons", - "executableName": "tensamin", - "category": "Network", - "maintainer": "Methanium", - "syncDesktopName": true, - "desktop": { - "entry": { - "Name": "Tensamin", - "StartupWMClass": "Tensamin" - } - } - }, - "win": { - "target": [ - "nsis", - "portable" - ], - "icon": "build/icons/icon.ico" - }, - "mac": { - "target": [ - "dmg" - ], - "icon": "build/icons/icon.icns", - "extendInfo": { - "NSCameraUsageDescription": "Tensamin uses your camera when you choose to share it in a call.", - "NSMicrophoneUsageDescription": "Tensamin uses your microphone for calls." - } - }, - "publish": null - } -} diff --git a/apps/electron/scripts/generate-release-metadata.ts b/apps/electron/scripts/generate-release-metadata.ts deleted file mode 100644 index 7e36096..0000000 --- a/apps/electron/scripts/generate-release-metadata.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { createHash } from "node:crypto"; -import { - createReadStream, - existsSync, - readdirSync, - statSync, - writeFileSync, -} from "node:fs"; -import { basename, join } from "node:path"; -import rootPackage from "../../../package.json" with { type: "json" }; - -const releaseDir = join(import.meta.dir, "..", "release"); -const outDir = join(import.meta.dir, "..", "..", "..", "releases"); - -function sha256(filePath: string) { - const hash = createHash("sha256"); - const stream = createReadStream(filePath); - - return new Promise((resolve, reject) => { - stream.on("data", (chunk) => hash.update(chunk)); - stream.on("error", reject); - stream.on("end", () => resolve(hash.digest("hex"))); - }); -} - -function platformFor(file: string) { - if (/win|nsis|portable|\.exe$/i.test(file)) return "windows"; - if (/mac|darwin|\.dmg$/i.test(file)) return "macos"; - return "linux"; -} - -function archFor(file: string) { - if (/arm64|aarch64/i.test(file)) return "arm64"; - return "x64"; -} - -if (!existsSync(releaseDir)) { - throw new Error(`Missing Electron release directory: ${releaseDir}`); -} - -const files = readdirSync(releaseDir) - .filter((file) => !file.endsWith(".blockmap") && !file.endsWith(".yml")) - .map((file) => join(releaseDir, file)) - .filter((file) => statSync(file).isFile()); - -const artifacts = await Promise.all( - files.map(async (filePath) => ({ - name: basename(filePath), - platform: platformFor(filePath), - arch: archFor(filePath), - url: `__FORGEJO_RELEASE_ASSET_URL__/${encodeURIComponent(basename(filePath))}`, - sha256: await sha256(filePath), - size: statSync(filePath).size, - })), -); - -const metadata = { - version: rootPackage.version, - tag: rootPackage.version, - publishedAt: new Date().toISOString(), - artifacts, -}; - -writeFileSync( - join(outDir, "electron-release-metadata.json"), - `${JSON.stringify(metadata, null, 2)}\n`, -); -writeFileSync( - join(outDir, "SHA256SUMS"), - `${artifacts.map((artifact) => `${artifact.sha256} ${artifact.name}`).join("\n")}\n`, -); diff --git a/apps/electron/scripts/generate-signing-key.ts b/apps/electron/scripts/generate-signing-key.ts deleted file mode 100644 index 6ad35ef..0000000 --- a/apps/electron/scripts/generate-signing-key.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { generateKeyPairSync } from "node:crypto"; - -const { privateKey, publicKey } = generateKeyPairSync("ed25519", { - privateKeyEncoding: { type: "pkcs8", format: "pem" }, - publicKeyEncoding: { type: "spki", format: "pem" }, -}); - -console.log("TENSAMIN_UPDATE_PRIVATE_KEY_PEM="); -console.log(privateKey.trim()); -console.log("\nTENSAMIN_UPDATE_PUBLIC_KEY_PEM="); -console.log(publicKey.trim()); diff --git a/apps/electron/src/main/main.ts b/apps/electron/src/main/main.ts deleted file mode 100644 index 083d4d4..0000000 --- a/apps/electron/src/main/main.ts +++ /dev/null @@ -1,511 +0,0 @@ -import { execFile } from "node:child_process"; -import { fileURLToPath } from "node:url"; -import { dirname, join, resolve } from "node:path"; -import { - app, - BrowserWindow, - desktopCapturer, - globalShortcut, - ipcMain, - session, - shell, -} from "electron"; -import { checkForUpdates } from "./updates.js"; -import { - ipcChannels, - type DesktopCallStatus, - type DesktopGlobalHotkeyBinding, - type DesktopScreenShareAudioOutput, - type DesktopScreenShareCapabilities, -} from "../shared/ipc.js"; -import { initTray, setTrayCallStatus } from "./tray.js"; -import { - clearSecureStorage, - deleteSecureStorage, - getSecureStorageStatus, - loadSecureStorage, - saveSecureStorage, -} from "./secureStorage.js"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const verbose = process.argv.includes("--verbose"); -let mainWindow: BrowserWindow | null = null; -let selectedScreenShareSourceId: string | null = null; -let globalHotkeyBindings: DesktopGlobalHotkeyBinding[] = []; -let globalHotkeysSuspended = false; - -app.setName("tensamin"); -app.setPath("userData", join(app.getPath("appData"), "tensamin", "electron")); - -if (verbose) { - app.commandLine.appendSwitch("enable-logging", "stderr"); - app.commandLine.appendSwitch("v", "1"); - app.commandLine.appendSwitch("log-level", "0"); -} - -if ( - process.platform === "linux" && - !app.commandLine.hasSwitch("password-store") -) { - app.commandLine.appendSwitch("password-store", "gnome-libsecret"); -} - -if ( - process.platform === "linux" && - process.env.XDG_SESSION_TYPE === "wayland" -) { - app.commandLine.appendSwitch("enable-features", "GlobalShortcutsPortal"); -} - -if ( - process.platform === "linux" && - process.env.XDG_SESSION_TYPE === "wayland" && - !process.env.TENSAMIN_ENABLE_VULKAN -) { - app.commandLine.appendSwitch("disable-features", "Vulkan"); -} - -function verboseLog(...args: unknown[]) { - if (verbose) { - console.log("[tensamin:electron]", ...args); - } -} - -function getRendererIndex() { - if (!app.isPackaged) { - return resolve(__dirname, "../../../web/dist/index.html"); - } - - return join(process.resourcesPath, "web", "index.html"); -} - -function getWindowIcon() { - if (process.platform === "darwin") return undefined; - if (app.isPackaged) return join(process.resourcesPath, "icons", "icon.png"); - return resolve(__dirname, "../../build/icons/icon.png"); -} - -function getPlatform(): DesktopScreenShareCapabilities["platform"] { - if (process.platform === "linux") return "linux"; - if (process.platform === "darwin") return "macos"; - if (process.platform === "win32") return "windows"; - return "other"; -} - -function getScreenShareCapabilities(): DesktopScreenShareCapabilities { - const platform = getPlatform(); - - return { - runtime: "electron", - platform, - showAudioOutputSelector: platform === "linux", - showAudioSwitch: platform === "windows" || platform === "macos", - hasReliableSystemAudio: platform === "windows", - }; -} - -function execJson(command: string, args: string[]) { - verboseLog("exec", command, args.join(" ")); - - return new Promise((resolvePromise, reject) => { - execFile(command, args, { timeout: 3000 }, (error, stdout, stderr) => { - if (error) { - reject(new Error(stderr.trim() || error.message)); - return; - } - - resolvePromise(JSON.parse(stdout)); - }); - }); -} - -async function listAudioOutputs(): Promise { - verboseLog("listAudioOutputs", { platform: process.platform }); - - if (process.platform !== "linux") return []; - - const sinks = await execJson("pactl", ["--format=json", "list", "sinks"]); - if (!Array.isArray(sinks)) return []; - - return sinks - .map((sink) => { - if (!sink || typeof sink !== "object") return null; - const record = sink as Record; - const id = record.index == null ? undefined : String(record.index); - const name = - typeof record.description === "string" ? record.description : id; - if (!id || !name) return null; - return { id, name, isDefault: false }; - }) - .filter( - (output): output is DesktopScreenShareAudioOutput => output != null, - ); -} - -async function listScreenShareSources() { - verboseLog("listScreenShareSources"); - - const sources = await desktopCapturer.getSources({ - types: ["screen", "window"], - thumbnailSize: { width: 320, height: 180 }, - fetchWindowIcons: true, - }); - - return sources.map((source) => ({ - id: source.id, - kind: source.id.startsWith("screen:") ? "screen" : "window", - name: source.name, - subtitle: source.id, - thumbnail: source.thumbnail.isEmpty() ? null : source.thumbnail.toDataURL(), - })); -} - -function registerDisplayMediaHandler() { - session.defaultSession.setDisplayMediaRequestHandler( - async (_request, callback) => { - verboseLog("display media request", { selectedScreenShareSourceId }); - - const sources = await desktopCapturer.getSources({ - types: ["screen", "window"], - thumbnailSize: { width: 0, height: 0 }, - }); - - const selected = sources.find( - (source) => source.id === selectedScreenShareSourceId, - ); - selectedScreenShareSourceId = null; - const video = selected ?? sources[0]; - - if (!video) { - callback({}); - return; - } - - if (process.platform === "win32") { - callback({ video, audio: "loopback" }); - return; - } - - callback({ video }); - }, - ); -} - -function registerMediaPermissionHandler() { - const isTrustedRenderer = (url: string) => { - try { - const parsed = new URL(url); - return parsed.protocol === "file:"; - } catch { - return false; - } - }; - - session.defaultSession.setPermissionCheckHandler( - (_webContents, permission, requestingOrigin) => - permission === "media" && isTrustedRenderer(requestingOrigin), - ); - session.defaultSession.setPermissionRequestHandler( - (_webContents, permission, callback, details) => { - callback( - permission === "media" && isTrustedRenderer(details.requestingUrl), - ); - }, - ); -} - -function registerIpc() { - verboseLog("registering ipc handlers"); - - ipcMain.handle(ipcChannels.listScreenShareSources, listScreenShareSources); - ipcMain.handle(ipcChannels.listScreenShareAudioOutputs, listAudioOutputs); - ipcMain.handle( - ipcChannels.getScreenShareCapabilities, - getScreenShareCapabilities, - ); - ipcMain.handle( - ipcChannels.selectScreenShareSource, - (_event, sourceId: unknown) => { - if ( - typeof sourceId !== "string" || - sourceId.length === 0 || - sourceId.length > 256 - ) { - throw new Error("Invalid screen share source id."); - } - - selectedScreenShareSourceId = sourceId; - verboseLog("selected screen share source", sourceId); - return true; - }, - ); - ipcMain.handle(ipcChannels.getVersion, () => app.getVersion()); - ipcMain.handle(ipcChannels.checkForUpdates, checkForUpdates); - ipcMain.handle(ipcChannels.getSecureStorageStatus, getSecureStorageStatus); - ipcMain.handle(ipcChannels.loadSecureStorage, (_event, key: unknown) => - loadSecureStorage(key), - ); - ipcMain.handle( - ipcChannels.saveSecureStorage, - (_event, key: unknown, value: unknown) => saveSecureStorage(key, value), - ); - ipcMain.handle(ipcChannels.deleteSecureStorage, (_event, key: unknown) => - deleteSecureStorage(key), - ); - ipcMain.handle(ipcChannels.clearSecureStorage, clearSecureStorage); - ipcMain.handle( - ipcChannels.setGlobalHotkeyBindings, - (event, bindings: unknown) => { - assertTrustedRenderer(event); - return setGlobalHotkeyBindings(bindings); - }, - ); - ipcMain.handle( - ipcChannels.setGlobalHotkeysSuspended, - (event, suspended: unknown) => { - assertTrustedRenderer(event); - if (typeof suspended !== "boolean") { - throw new Error("Invalid hotkey suspension state."); - } - globalHotkeysSuspended = suspended; - return applyGlobalHotkeyBindings(); - }, - ); - ipcMain.handle(ipcChannels.setCallStatus, (_event, status: unknown) => { - if ( - typeof status !== "object" || - status === null || - typeof (status as DesktopCallStatus).inCall !== "boolean" || - typeof (status as DesktopCallStatus).speaking !== "boolean" || - ((status as DesktopCallStatus).iconDataUrl !== undefined && - (typeof (status as DesktopCallStatus).iconDataUrl !== "string" || - !(status as DesktopCallStatus).iconDataUrl?.startsWith( - "data:image/png;base64,", - ) || - (status as DesktopCallStatus).iconDataUrl!.length > 16_384)) - ) { - throw new Error("Invalid call status."); - } - - const { inCall, iconDataUrl } = status as DesktopCallStatus; - setTrayCallStatus(inCall, iconDataUrl); - }); - ipcMain.handle(ipcChannels.minimizeWindow, () => { - verboseLog("window:minimize"); - mainWindow?.minimize(); - }); - ipcMain.handle(ipcChannels.maximizeWindow, () => { - verboseLog("window:maximize"); - if (!mainWindow) return; - - if (mainWindow.isMaximized()) { - mainWindow.unmaximize(); - return; - } - - mainWindow.maximize(); - }); - ipcMain.handle(ipcChannels.closeWindow, () => { - verboseLog("window:close"); - mainWindow?.close(); - }); -} - -function assertTrustedRenderer(event: Electron.IpcMainInvokeEvent) { - const target = mainWindow; - if ( - !target || - target.isDestroyed() || - event.sender !== target.webContents || - event.senderFrame !== target.webContents.mainFrame - ) { - throw new Error("Untrusted hotkey IPC sender."); - } - - try { - if (fileURLToPath(event.senderFrame.url) === getRendererIndex()) return; - } catch { - // Fall through to the rejection below. - } - throw new Error("Untrusted hotkey IPC sender."); -} - -function validGlobalHotkeyBindings( - value: unknown, -): value is DesktopGlobalHotkeyBinding[] { - return ( - Array.isArray(value) && - value.length <= 64 && - value.every( - (binding) => - binding && - typeof binding === "object" && - typeof (binding as DesktopGlobalHotkeyBinding).id === "string" && - /^[a-z0-9.-]+$/i.test((binding as DesktopGlobalHotkeyBinding).id) && - (binding as DesktopGlobalHotkeyBinding).id.length > 0 && - (binding as DesktopGlobalHotkeyBinding).id.length <= 128 && - typeof (binding as DesktopGlobalHotkeyBinding).accelerator === - "string" && - (binding as DesktopGlobalHotkeyBinding).accelerator.length > 0 && - (binding as DesktopGlobalHotkeyBinding).accelerator.length <= 128, - ) - ); -} - -function applyGlobalHotkeyBindings() { - globalShortcut.unregisterAll(); - const statuses = Object.fromEntries( - globalHotkeyBindings.map(({ id }) => [id, false]), - ); - if (globalHotkeysSuspended) return statuses; - - const grouped = new Map(); - for (const { id, accelerator } of globalHotkeyBindings) { - const ids = grouped.get(accelerator) ?? []; - ids.push(id); - grouped.set(accelerator, ids); - } - - for (const [accelerator, ids] of grouped) { - let registered = false; - try { - registered = globalShortcut.register(accelerator, () => { - const target = mainWindow; - if (!target || target.isDestroyed()) return; - ids.forEach((id) => - target.webContents.send(ipcChannels.globalHotkeyTriggered, id), - ); - }); - } catch (error) { - console.error("Failed to register global hotkey", accelerator, error); - } - ids.forEach((id) => { - statuses[id] = registered; - }); - } - - return statuses; -} - -function setGlobalHotkeyBindings(bindings: unknown) { - if (!validGlobalHotkeyBindings(bindings)) { - throw new Error("Invalid global hotkey bindings."); - } - globalHotkeyBindings = bindings; - return applyGlobalHotkeyBindings(); -} - -async function createWindow() { - const rendererIndex = getRendererIndex(); - verboseLog("creating main window", { - appVersion: app.getVersion(), - electronVersion: process.versions.electron, - chromeVersion: process.versions.chrome, - nodeVersion: process.versions.node, - platform: process.platform, - arch: process.arch, - isPackaged: app.isPackaged, - rendererIndex, - argv: process.argv, - }); - - mainWindow = new BrowserWindow({ - width: 1200, - height: 800, - minWidth: 900, - minHeight: 600, - title: "Tensamin", - icon: getWindowIcon(), - frame: false, - autoHideMenuBar: true, - webPreferences: { - preload: join(__dirname, "../preload/preload.cjs"), - contextIsolation: true, - nodeIntegration: false, - sandbox: true, - webSecurity: true, - }, - }); - mainWindow.setMenuBarVisibility(false); - - mainWindow.webContents.setWindowOpenHandler(({ url }) => { - verboseLog("blocked window open", url); - void shell.openExternal(url); - return { action: "deny" }; - }); - - if (verbose) { - mainWindow.webContents.on( - "console-message", - (_event, level, message, line, sourceId) => { - const target = level >= 2 ? console.error : console.log; - target("[tensamin:renderer]", message, { level, line, sourceId }); - }, - ); - - mainWindow.webContents.on( - "did-fail-load", - (_event, errorCode, errorDescription, validatedURL) => { - console.error("[tensamin:electron] renderer failed to load", { - errorCode, - errorDescription, - validatedURL, - }); - }, - ); - - mainWindow.webContents.on("did-finish-load", () => { - verboseLog("renderer finished loading", mainWindow?.webContents.getURL()); - }); - - mainWindow.webContents.on("render-process-gone", (_event, details) => { - console.error("[tensamin:electron] renderer process gone", details); - }); - - mainWindow.on("unresponsive", () => { - console.error("[tensamin:electron] main window became unresponsive"); - }); - } - - await mainWindow.loadFile(rendererIndex); -} - -app.on("window-all-closed", () => { - verboseLog("window-all-closed"); - if (process.platform !== "darwin") app.quit(); -}); - -app.on("activate", () => { - verboseLog("activate"); - if (BrowserWindow.getAllWindows().length === 0) void createWindow(); -}); - -app.on("will-quit", () => { - globalShortcut.unregisterAll(); -}); - -if (verbose) { - process.on("uncaughtException", (error) => { - console.error("[tensamin:electron] uncaught exception", error); - }); - - process.on("unhandledRejection", (reason) => { - console.error("[tensamin:electron] unhandled rejection", reason); - }); -} - -async function start() { - verboseLog("waiting for app readiness"); - await app.whenReady(); - verboseLog("app ready"); - registerIpc(); - registerDisplayMediaHandler(); - registerMediaPermissionHandler(); - initTray(() => mainWindow); - await createWindow(); -} - -void start().catch((error) => { - console.error("[tensamin:electron] failed to start", error); - app.exit(1); -}); diff --git a/apps/electron/src/main/secureStorage.ts b/apps/electron/src/main/secureStorage.ts deleted file mode 100644 index 9f1f37f..0000000 --- a/apps/electron/src/main/secureStorage.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { app, safeStorage } from "electron"; -import { mkdir, readFile, rename, writeFile } from "node:fs/promises"; -import { dirname, join } from "node:path"; -import { - secureStorageLimits, - type DesktopSecureStorageStatus, -} from "../shared/ipc.js"; - -type StoredValues = Record; - -let pendingWrite = Promise.resolve(); - -function storagePath() { - return join(app.getPath("userData"), "secure-storage.json"); -} - -function validateKey(key: unknown): asserts key is string { - if ( - typeof key !== "string" || - key.length === 0 || - Buffer.byteLength(key, "utf8") > secureStorageLimits.maxKeyBytes - ) { - throw new Error("Invalid secure storage key."); - } -} - -function validateValue(value: unknown): asserts value is string { - if ( - typeof value !== "string" || - Buffer.byteLength(value, "utf8") > secureStorageLimits.maxValueBytes - ) { - throw new Error("Invalid secure storage value."); - } -} - -export function getSecureStorageStatus(): DesktopSecureStorageStatus { - const backend = - process.platform === "linux" - ? safeStorage.getSelectedStorageBackend() - : process.platform === "darwin" - ? "keychain" - : process.platform === "win32" - ? "dpapi" - : null; - - return { - available: - safeStorage.isEncryptionAvailable() && - (process.platform !== "linux" || backend !== "basic_text"), - backend, - }; -} - -function requireAvailable() { - if (!getSecureStorageStatus().available) { - throw new Error("Secure storage is unavailable."); - } -} - -async function readValues(): Promise { - try { - const parsed: unknown = JSON.parse(await readFile(storagePath(), "utf8")); - if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { - throw new Error("Invalid secure storage data."); - } - - const values = parsed as Record; - if (Object.values(values).some((value) => typeof value !== "string")) { - throw new Error("Invalid secure storage data."); - } - return values as StoredValues; - } catch (error) { - if ((error as NodeJS.ErrnoException).code === "ENOENT") return {}; - throw error; - } -} - -async function writeValues(values: StoredValues) { - const path = storagePath(); - const temporaryPath = `${path}.tmp`; - await mkdir(dirname(path), { recursive: true, mode: 0o700 }); - await writeFile(temporaryPath, JSON.stringify(values), { mode: 0o600 }); - await rename(temporaryPath, path); -} - -function mutateValues(mutation: (values: StoredValues) => void) { - const operation = pendingWrite.then(async () => { - const values = await readValues(); - mutation(values); - await writeValues(values); - }); - pendingWrite = operation.catch(() => undefined); - return operation; -} - -export async function loadSecureStorage(key: unknown): Promise { - requireAvailable(); - validateKey(key); - await pendingWrite; - const encrypted = (await readValues())[key]; - if (encrypted === undefined) return null; - return safeStorage.decryptString(Buffer.from(encrypted, "base64")); -} - -export function saveSecureStorage(key: unknown, value: unknown) { - requireAvailable(); - validateKey(key); - validateValue(value); - const encrypted = safeStorage.encryptString(value).toString("base64"); - return mutateValues((values) => { - values[key] = encrypted; - }); -} - -export function deleteSecureStorage(key: unknown) { - requireAvailable(); - validateKey(key); - return mutateValues((values) => { - delete values[key]; - }); -} - -export function clearSecureStorage() { - requireAvailable(); - return mutateValues((values) => { - for (const key of Object.keys(values)) delete values[key]; - }); -} diff --git a/apps/electron/src/main/tray.ts b/apps/electron/src/main/tray.ts deleted file mode 100644 index 5daaf1b..0000000 --- a/apps/electron/src/main/tray.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { app, BrowserWindow, Menu, nativeImage, Tray } from "electron"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -let tray: Tray | null = null; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -function getTrayIconPath(filename: string) { - if (app.isPackaged) - return path.join(process.resourcesPath, "icons", filename); - return path.resolve(__dirname, "../../build/icons", filename); -} - -export function setTrayCallStatus(inCall: boolean, iconDataUrl?: string) { - if (!tray) return; - - if (inCall && iconDataUrl) { - const image = nativeImage.createFromDataURL(iconDataUrl); - if (!image.isEmpty()) { - tray.setImage(image); - return; - } - } - - tray.setImage(getTrayIconPath("32x32.png")); -} - -export function initTray(getMainWindow: () => BrowserWindow | null) { - tray = new Tray(getTrayIconPath("32x32.png")); // keep reference alive - - const contextMenu = Menu.buildFromTemplate([ - { - label: "Restart", - type: "normal", - click: () => { - app.relaunch(); - app.quit(); - }, - }, - { label: "Quit", type: "normal", click: () => app.quit() }, - ]); - - tray.setContextMenu(contextMenu); - - tray.on("click", () => { - const mainWindow = getMainWindow(); - if (!mainWindow) return; - - if (mainWindow.isVisible()) { - mainWindow.hide(); - return; - } - - if (mainWindow.isMinimized()) mainWindow.restore(); - mainWindow.show(); - mainWindow.focus(); - }); -} diff --git a/apps/electron/src/main/updates.ts b/apps/electron/src/main/updates.ts deleted file mode 100644 index b5f7376..0000000 --- a/apps/electron/src/main/updates.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { createHash } from "node:crypto"; -import { createReadStream } from "node:fs"; -import { mkdir, rm, writeFile } from "node:fs/promises"; -import { basename, join } from "node:path"; -import { app, net } from "electron"; -import type { - ReleaseArtifact, - ReleaseMetadata, - UpdateCheckResult, -} from "../shared/ipc.js"; - -const metadataUrl = process.env.TENSAMIN_UPDATE_METADATA_URL; - -function compareSemver(left: string, right: string) { - const leftParts = left - .split(/[.-]/) - .map((part) => Number.parseInt(part, 10) || 0); - const rightParts = right - .split(/[.-]/) - .map((part) => Number.parseInt(part, 10) || 0); - const length = Math.max(leftParts.length, rightParts.length); - - for (let index = 0; index < length; index += 1) { - const diff = (leftParts[index] ?? 0) - (rightParts[index] ?? 0); - if (diff !== 0) return diff; - } - - return 0; -} - -function isDevVersion(version: string) { - return /-dev[.-]/.test(version); -} - -function platformName() { - if (process.platform === "win32") return "windows"; - if (process.platform === "darwin") return "macos"; - if (process.platform === "linux") return "linux"; - return process.platform; -} - -function archName() { - if (process.arch === "x64") return "x64"; - if (process.arch === "arm64") return "arm64"; - return process.arch; -} - -function requestText(url: string): Promise { - return new Promise((resolve, reject) => { - const request = net.request(url); - request.on("response", (response) => { - if (response.statusCode < 200 || response.statusCode >= 300) { - reject( - new Error(`Update request failed with HTTP ${response.statusCode}.`), - ); - return; - } - - const chunks: Buffer[] = []; - response.on("data", (chunk) => chunks.push(Buffer.from(chunk))); - response.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); - }); - request.on("error", reject); - request.end(); - }); -} - -function requestBuffer(url: string): Promise { - return new Promise((resolve, reject) => { - const request = net.request(url); - request.on("response", (response) => { - if (response.statusCode < 200 || response.statusCode >= 300) { - reject(new Error(`Download failed with HTTP ${response.statusCode}.`)); - return; - } - - const chunks: Buffer[] = []; - response.on("data", (chunk) => chunks.push(Buffer.from(chunk))); - response.on("end", () => resolve(Buffer.concat(chunks))); - }); - request.on("error", reject); - request.end(); - }); -} - -async function sha256File(filePath: string) { - const hash = createHash("sha256"); - - await new Promise((resolve, reject) => { - const stream = createReadStream(filePath); - stream.on("data", (chunk) => hash.update(chunk)); - stream.on("error", reject); - stream.on("end", resolve); - }); - - return hash.digest("hex"); -} - -function selectArtifact( - metadata: ReleaseMetadata, -): ReleaseArtifact | undefined { - const platform = platformName(); - const arch = archName(); - - return metadata.artifacts.find( - (artifact) => artifact.platform === platform && artifact.arch === arch, - ); -} - -export async function checkForUpdates(): Promise { - const currentVersion = app.getVersion(); - - if (!metadataUrl) { - return { available: false, currentVersion, latestVersion: currentVersion }; - } - - const metadata = JSON.parse( - await requestText(metadataUrl), - ) as ReleaseMetadata; - const artifact = selectArtifact(metadata); - - if (isDevVersion(metadata.version) !== isDevVersion(currentVersion)) { - return { - available: false, - currentVersion, - latestVersion: metadata.version, - }; - } - - if (isDevVersion(currentVersion)) { - if (!artifact || metadata.version === currentVersion) { - return { - available: false, - currentVersion, - latestVersion: metadata.version, - }; - } - - return { - available: true, - currentVersion, - latestVersion: metadata.version, - artifact, - }; - } - - if (!artifact || compareSemver(metadata.version, currentVersion) <= 0) { - return { - available: false, - currentVersion, - latestVersion: metadata.version, - }; - } - - return { - available: true, - currentVersion, - latestVersion: metadata.version, - artifact, - }; -} - -export async function downloadVerifiedArtifact(artifact: ReleaseArtifact) { - const updatesDir = join(app.getPath("userData"), "updates"); - await rm(updatesDir, { recursive: true, force: true }); - await mkdir(updatesDir, { recursive: true }); - - const destination = join(updatesDir, basename(artifact.name)); - await writeFile(destination, await requestBuffer(artifact.url), { - mode: 0o600, - }); - - const actualHash = await sha256File(destination); - if (actualHash !== artifact.sha256) { - await rm(destination, { force: true }); - throw new Error("Downloaded update failed checksum verification."); - } - - return destination; -} diff --git a/apps/electron/src/preload/preload.ts b/apps/electron/src/preload/preload.ts deleted file mode 100644 index 9f1c932..0000000 --- a/apps/electron/src/preload/preload.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { contextBridge, ipcRenderer } from "electron"; -import { - ipcChannels, - type DesktopCallStatus, - type DesktopGlobalHotkeyBinding, - type DesktopScreenShareSource, - secureStorageLimits, -} from "../shared/ipc.js"; - -function windowAction(channel: string) { - return () => ipcRenderer.invoke(channel); -} - -function validKey(key: string) { - return ( - typeof key === "string" && - key.length > 0 && - Buffer.byteLength(key, "utf8") <= secureStorageLimits.maxKeyBytes - ); -} - -const desktopApi = { - media: { - listScreenShareSources: () => - ipcRenderer.invoke(ipcChannels.listScreenShareSources), - listScreenShareAudioOutputs: () => - ipcRenderer.invoke(ipcChannels.listScreenShareAudioOutputs), - getScreenShareCapabilities: () => - ipcRenderer.invoke(ipcChannels.getScreenShareCapabilities), - selectScreenShareSource: (sourceId: DesktopScreenShareSource["id"]) => { - if (typeof sourceId !== "string" || sourceId.length === 0) { - return Promise.reject(new Error("Invalid screen share source id.")); - } - - return ipcRenderer.invoke(ipcChannels.selectScreenShareSource, sourceId); - }, - }, - app: { - getVersion: () => ipcRenderer.invoke(ipcChannels.getVersion), - }, - updates: { - checkForUpdates: () => ipcRenderer.invoke(ipcChannels.checkForUpdates), - }, - call: { - setStatus: (status: DesktopCallStatus) => { - if ( - typeof status?.inCall !== "boolean" || - typeof status?.speaking !== "boolean" || - (status.iconDataUrl !== undefined && - (typeof status.iconDataUrl !== "string" || - !status.iconDataUrl.startsWith("data:image/png;base64,"))) - ) { - return Promise.reject(new Error("Invalid call status.")); - } - - return ipcRenderer.invoke(ipcChannels.setCallStatus, status); - }, - }, - hotkeys: { - setBindings: (bindings: DesktopGlobalHotkeyBinding[]) => - ipcRenderer.invoke(ipcChannels.setGlobalHotkeyBindings, bindings), - setSuspended: (suspended: boolean) => - typeof suspended === "boolean" - ? ipcRenderer.invoke(ipcChannels.setGlobalHotkeysSuspended, suspended) - : Promise.reject(new Error("Invalid hotkey suspension state.")), - onTriggered: (callback: (id: string) => void) => { - const listener = (_event: Electron.IpcRendererEvent, id: unknown) => { - if (typeof id === "string") callback(id); - }; - ipcRenderer.on(ipcChannels.globalHotkeyTriggered, listener); - return () => { - ipcRenderer.removeListener(ipcChannels.globalHotkeyTriggered, listener); - }; - }, - }, - secureStorage: { - getStatus: () => ipcRenderer.invoke(ipcChannels.getSecureStorageStatus), - load: (key: string) => - validKey(key) - ? ipcRenderer.invoke(ipcChannels.loadSecureStorage, key) - : Promise.reject(new Error("Invalid secure storage key.")), - save: (key: string, value: string) => - validKey(key) && - typeof value === "string" && - Buffer.byteLength(value, "utf8") <= secureStorageLimits.maxValueBytes - ? ipcRenderer.invoke(ipcChannels.saveSecureStorage, key, value) - : Promise.reject(new Error("Invalid secure storage key or value.")), - delete: (key: string) => - validKey(key) - ? ipcRenderer.invoke(ipcChannels.deleteSecureStorage, key) - : Promise.reject(new Error("Invalid secure storage key.")), - clear: () => ipcRenderer.invoke(ipcChannels.clearSecureStorage), - }, - window: { - minimize: () => windowAction(ipcChannels.minimizeWindow), - maximize: () => windowAction(ipcChannels.maximizeWindow), - close: () => windowAction(ipcChannels.closeWindow), - }, -}; - -contextBridge.exposeInMainWorld("tensaminDesktop", desktopApi); -contextBridge.exposeInMainWorld( - "tensaminShowWindowControls", - process.env.TENSAMIN_HIDE_CONTROLS == null, -); - -export type TensaminDesktopApi = typeof desktopApi; diff --git a/apps/electron/src/shared/ipc.ts b/apps/electron/src/shared/ipc.ts deleted file mode 100644 index c424265..0000000 --- a/apps/electron/src/shared/ipc.ts +++ /dev/null @@ -1,88 +0,0 @@ -export type DesktopScreenShareSource = { - id: string; - kind: "screen" | "window"; - name: string; - subtitle?: string | null; - thumbnail?: string | null; -}; - -export type DesktopScreenShareAudioOutput = { - id: string; - name: string; - isDefault: boolean; -}; - -export type DesktopScreenShareCapabilities = { - runtime: "electron"; - platform: "linux" | "macos" | "windows" | "other"; - showAudioOutputSelector: boolean; - showAudioSwitch: boolean; - hasReliableSystemAudio: boolean; -}; - -export type DesktopCallStatus = { - inCall: boolean; - speaking: boolean; - iconDataUrl?: string; -}; - -export type DesktopSecureStorageStatus = { - available: boolean; - backend: string | null; -}; - -export type DesktopGlobalHotkeyBinding = { - id: string; - accelerator: string; -}; - -export const secureStorageLimits = { - maxKeyBytes: 256, - maxValueBytes: 1024 * 1024, -} as const; - -export type ReleaseArtifact = { - name: string; - platform: string; - arch: string; - url: string; - sha256: string; - size: number; -}; - -export type ReleaseMetadata = { - version: string; - tag: string; - publishedAt: string; - artifacts: ReleaseArtifact[]; -}; - -export type UpdateCheckResult = - | { available: false; currentVersion: string; latestVersion: string } - | { - available: true; - currentVersion: string; - latestVersion: string; - artifact: ReleaseArtifact; - }; - -export const ipcChannels = { - listScreenShareSources: "desktopMedia:listScreenShareSources", - listScreenShareAudioOutputs: "desktopMedia:listScreenShareAudioOutputs", - getScreenShareCapabilities: "desktopMedia:getScreenShareCapabilities", - selectScreenShareSource: "desktopMedia:selectScreenShareSource", - minimizeWindow: "window:minimize", - maximizeWindow: "window:maximize", - closeWindow: "window:close", - getVersion: "app:getVersion", - checkForUpdates: "updates:checkForUpdates", - setCallStatus: "call:setStatus", - getSecureStorageStatus: "secureStorage:getStatus", - loadSecureStorage: "secureStorage:load", - saveSecureStorage: "secureStorage:save", - deleteSecureStorage: "secureStorage:delete", - clearSecureStorage: "secureStorage:clear", - setGlobalHotkeyBindings: "hotkeys:setBindings", - setGlobalHotkeysSuspended: "hotkeys:setSuspended", - globalHotkeyTriggered: "hotkeys:triggered", -} as const; diff --git a/apps/electron/tsconfig.json b/apps/electron/tsconfig.json deleted file mode 100644 index 8d5bda2..0000000 --- a/apps/electron/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "rootDir": "src", - "outDir": "dist", - "strict": true, - "skipLibCheck": true, - "esModuleInterop": true, - "types": ["node", "electron"] - }, - "include": ["src/**/*.ts"] -} diff --git a/apps/pwa/package.json b/apps/pwa/package.json deleted file mode 100644 index 39c4ec5..0000000 --- a/apps/pwa/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "@tensamin/pwa", - "private": true, - "version": "0.0.0", - "type": "module", - "exports": { - "./vite": "./src/vite.ts", - "./runtime": "./src/runtime.tsx" - }, - "scripts": { - "format": "pnpm exec prettier --write .", - "lint": "eslint src", - "build": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.worker.json --noEmit" - }, - "dependencies": { - "@methanium/ui": "*", - "@tauri-apps/api": "^2.11.1", - "@tensamin/crypto": "workspace:*", - "@tensamin/shared": "workspace:*", - "@tensamin/storage": "workspace:*", - "mtp": "*", - "react": "^19.2.8", - "sonner": "^2.0.7", - "vite-plugin-pwa": "^1.1.0", - "workbox-core": "^7.3.0", - "workbox-precaching": "^7.3.0", - "workbox-routing": "^7.3.0", - "workbox-strategies": "^7.3.0" - }, - "devDependencies": { - "@types/node": "^26.1.2", - "@types/react": "^19.2.18", - "typescript": "~6.0.3", - "vite": "^8.2.1" - } -} diff --git a/apps/pwa/src/runtime.tsx b/apps/pwa/src/runtime.tsx deleted file mode 100644 index a1947eb..0000000 --- a/apps/pwa/src/runtime.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import { useEffect } from "react"; -import { toast } from "sonner"; - -import { setDatabaseEntry } from "@tensamin/shared/indexedDb"; -import { isTauri } from "@tauri-apps/api/core"; - -import "./style.css"; - -const launchedFiles: File[] = []; -const fileListeners = new Set<(file: File) => void>(); - -function emitLaunchedFile(file: File) { - if (fileListeners.size === 0) launchedFiles.push(file); - else for (const listener of fileListeners) listener(file); -} - -export function subscribeTuFileLaunch(listener: (file: File) => void) { - fileListeners.add(listener); - for (const file of launchedFiles.splice(0)) listener(file); - return () => { - fileListeners.delete(listener); - }; -} - -function isInstalledPwa() { - return ( - window.matchMedia("(display-mode: standalone)").matches || - window.matchMedia("(display-mode: window-controls-overlay)").matches || - (navigator as Navigator & { standalone?: boolean }).standalone === true - ); -} - -function applicationServerKey(value: string) { - const normalized = value.replace(/-/g, "+").replace(/_/g, "/"); - const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "="); - return Uint8Array.from(atob(padded), (character) => character.charCodeAt(0)); -} - -async function enablePush() { - if (!("Notification" in window)) - throw new Error("Notifications are not supported by this browser."); - const permission = await Notification.requestPermission(); - if (permission !== "granted") - throw new Error("Notification permission was not granted."); - - const publicKey = import.meta.env.VITE_WEB_PUSH_PUBLIC_KEY; - if ( - !publicKey || - !("serviceWorker" in navigator) || - !("PushManager" in window) - ) { - return; - } - const registration = await navigator.serviceWorker.ready; - const subscription = - (await registration.pushManager.getSubscription()) ?? - (await registration.pushManager.subscribe({ - userVisibleOnly: true, - applicationServerKey: applicationServerKey(publicKey), - })); - await setDatabaseEntry("keys", "push-subscription", subscription.toJSON()); -} - -function InstalledPwaRuntime() { - useEffect(() => { - if ( - !("serviceWorker" in navigator) || - !["http:", "https:"].includes(window.location.protocol) - ) { - return; - } - let reloading = false; - const handleControllerChange = () => { - if (reloading) return; - reloading = true; - // A newly activated service worker must reload the document it controls. - // eslint-disable-next-line tensamin/no-window-location-reload - window.location.reload(); - }; - navigator.serviceWorker.addEventListener( - "controllerchange", - handleControllerChange, - ); - void navigator.serviceWorker - .register( - import.meta.env.DEV ? "/dev-sw.js?dev-sw" : "/serviceWorker.js", - { - type: "module", - }, - ) - .then((registration) => { - const watchWorker = (worker: ServiceWorker) => { - worker.addEventListener("statechange", () => { - if (worker.state !== "installed") return; - if (!navigator.serviceWorker.controller) { - toast.success("Tensamin is ready for offline startup"); - return; - } - toast("A Tensamin update is ready", { - duration: Infinity, - action: { - label: "Update", - onClick: () => worker.postMessage({ type: "SKIP_WAITING" }), - }, - }); - }); - }; - if (registration.installing) watchWorker(registration.installing); - registration.addEventListener("updatefound", () => { - if (registration.installing) watchWorker(registration.installing); - }); - }) - .catch((error: unknown) => { - console.error("Failed to register the Tensamin service worker", error); - }); - return () => { - navigator.serviceWorker.removeEventListener( - "controllerchange", - handleControllerChange, - ); - }; - }, []); - - useEffect(() => { - const launchQueue = ( - window as Window & { - launchQueue?: { - setConsumer: ( - consumer: (params: { - files?: Array<{ getFile: () => Promise }>; - }) => void, - ) => void; - }; - } - ).launchQueue; - launchQueue?.setConsumer((params) => { - for (const handle of params.files ?? []) { - void handle.getFile().then(emitLaunchedFile); - } - }); - }, []); - - useEffect(() => { - if ( - !("Notification" in window) || - Notification.permission !== "default" || - localStorage.getItem("pwa-push-hint") - ) { - return; - } - localStorage.setItem("pwa-push-hint", "shown"); - toast("Enable message notifications", { - duration: Infinity, - action: { - label: "Enable", - onClick: () => { - void enablePush() - .then(() => toast.success("Notifications enabled")) - .catch((error: unknown) => - toast.error( - error instanceof Error - ? error.message - : "Could not enable notifications", - ), - ); - }, - }, - }); - }, []); - - useEffect(() => { - if ( - "Notification" in window && - Notification.permission === "granted" && - import.meta.env.VITE_WEB_PUSH_PUBLIC_KEY - ) { - void enablePush().catch((error: unknown) => { - console.error("Failed to refresh the Web Push subscription", error); - }); - } - }, []); - - return null; -} - -export default function PwaRuntime() { - if (isTauri() || !isInstalledPwa()) return null; - return ; -} diff --git a/apps/pwa/src/serviceWorker.ts b/apps/pwa/src/serviceWorker.ts deleted file mode 100644 index 554ceeb..0000000 --- a/apps/pwa/src/serviceWorker.ts +++ /dev/null @@ -1,161 +0,0 @@ -/// - -import { base64ToBytes } from "mtp"; -import { clientsClaim } from "workbox-core"; -import { cleanupOutdatedCaches, precacheAndRoute } from "workbox-precaching"; -import { NavigationRoute, registerRoute } from "workbox-routing"; -import { createHandlerBoundToURL } from "workbox-precaching"; -import { CacheFirst } from "workbox-strategies"; - -import { decryptChatText, unwrapChatSecret } from "@tensamin/crypto/chatSecret"; -import { loadSecureBrowserValue } from "@tensamin/storage/browserSecure"; - -declare let self: ServiceWorkerGlobalScope; - -type PushPayload = { - version: 1; - senderId: number; - sender: string; - avatar?: string; - message: { content: string }; - secret: { - chatId: string; - secretId: string; - version: number; - encryptedSecret: string; - kemCiphertext: string; - wrappingScheme: string; - }; -}; - -function isPushPayload(value: unknown): value is PushPayload { - if (!value || typeof value !== "object") return false; - const payload = value as Partial; - const message = payload.message as - Partial | undefined; - const secret = payload.secret as Partial | undefined; - const stringValues = [ - payload.sender, - message?.content, - secret?.chatId, - secret?.secretId, - secret?.encryptedSecret, - secret?.kemCiphertext, - secret?.wrappingScheme, - ]; - return ( - payload.version === 1 && - typeof payload.senderId === "number" && - Number.isSafeInteger(payload.senderId) && - payload.senderId > 0 && - typeof secret?.version === "number" && - stringValues.every((item) => typeof item === "string") - ); -} - -async function decryptPush(payload: PushPayload) { - const keyring = await loadSecureBrowserValue("mtp_keyring"); - if (!keyring) throw new Error("MTP credentials are unavailable."); - const chatSecret = await unwrapChatSecret({ - encryptedSecret: base64ToBytes(payload.secret.encryptedSecret), - kemCiphertext: base64ToBytes(payload.secret.kemCiphertext), - keyring, - chatId: payload.secret.chatId, - secretId: payload.secret.secretId, - version: payload.secret.version, - wrappingScheme: payload.secret.wrappingScheme, - }); - try { - return await decryptChatText(chatSecret, payload.message.content); - } finally { - chatSecret.fill(0); - } -} - -clientsClaim(); -cleanupOutdatedCaches(); -const precacheManifest = self.__WB_MANIFEST; -precacheAndRoute(precacheManifest); - -if ( - precacheManifest.some((entry) => - (typeof entry === "string" ? entry : entry.url).endsWith("index.html"), - ) -) { - registerRoute( - new NavigationRoute(createHandlerBoundToURL("index.html"), { - denylist: [/^\/api\//], - }), - ); -} -registerRoute( - ({ request, url }) => - url.origin === self.location.origin && - ["font", "image", "style"].includes(request.destination), - new CacheFirst({ cacheName: "tensamin-static-v1" }), -); - -self.addEventListener("push", (event) => { - event.waitUntil( - (async () => { - let payload: PushPayload | undefined; - try { - const value = event.data?.json() as unknown; - if (isPushPayload(value)) payload = value; - } catch { - // The generic notification below is safe for malformed payloads. - } - - let body = "Open Tensamin to view the encrypted message."; - if (payload) { - try { - body = await decryptPush(payload); - } catch { - // Do not leak credential or decryption failures in the notification. - } - } - - await self.registration.showNotification(payload?.sender ?? "Tensamin", { - body, - icon: payload?.avatar || "./icons/icon-192.png", - badge: "./icons/notification-badge.png", - tag: payload ? `message-${payload.senderId}` : "message", - data: { url: payload ? `/chat?id=${payload.senderId}` : "/" }, - }); - - const navigatorWithBadge = self.navigator as WorkerNavigator & { - setAppBadge?: (contents?: number) => Promise; - }; - await navigatorWithBadge.setAppBadge?.().catch(() => undefined); - })(), - ); -}); - -self.addEventListener("notificationclick", (event) => { - event.notification.close(); - event.waitUntil( - (async () => { - const target = new URL( - String( - (event.notification.data as { url?: string } | undefined)?.url ?? "/", - ), - self.location.origin, - ); - const windows = await self.clients.matchAll({ - type: "window", - includeUncontrolled: true, - }); - for (const client of windows) { - if ("navigate" in client) await client.navigate(target.href); - return client.focus(); - } - return self.clients.openWindow(target.href); - })(), - ); -}); - -self.addEventListener("message", (event) => { - if ((event.data as { type?: string } | undefined)?.type === "SKIP_WAITING") { - void self.skipWaiting(); - } -}); diff --git a/apps/pwa/src/style.css b/apps/pwa/src/style.css deleted file mode 100644 index 213d773..0000000 --- a/apps/pwa/src/style.css +++ /dev/null @@ -1,18 +0,0 @@ -@media (display-mode: standalone), (display-mode: fullscreen) { - [data-pwa-root] { - padding-top: env(safe-area-inset-top, 0px); - padding-right: env(safe-area-inset-right, 0px); - padding-left: env(safe-area-inset-left, 0px); - } -} - -@media (display-mode: window-controls-overlay) and (min-width: 768px) { - [data-pwa-navbar] { - min-height: env(titlebar-area-height, 3.375rem); - padding-left: max(1px, env(titlebar-area-x, 0px)); - padding-right: max( - 0px, - calc(100vw - env(titlebar-area-x, 0px) - env(titlebar-area-width, 100vw)) - ); - } -} diff --git a/apps/pwa/src/vite.ts b/apps/pwa/src/vite.ts deleted file mode 100644 index 962dce1..0000000 --- a/apps/pwa/src/vite.ts +++ /dev/null @@ -1,218 +0,0 @@ -import { readFileSync } from "node:fs"; -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -import type { Plugin } from "vite"; -import { VitePWA } from "vite-plugin-pwa"; - -const pwaDirectory = resolve(dirname(fileURLToPath(import.meta.url)), ".."); -const tauriIcons = resolve(pwaDirectory, "../tauri/src-tauri/icons"); -const androidResources = resolve( - pwaDirectory, - "../tauri/src-tauri/gen/android/app/src/main/res", -); - -function emitIcons(): Plugin { - const icons = [ - { - fileName: "icons/icon-180.png", - source: resolve(tauriIcons, "ios/AppIcon-60x60@3x.png"), - }, - { - fileName: "icons/icon-192.png", - source: resolve(androidResources, "mipmap-xxxhdpi/ic_launcher.png"), - }, - { - fileName: "icons/icon-96.png", - source: resolve(androidResources, "mipmap-xhdpi/ic_launcher.png"), - }, - { - fileName: "icons/icon-512.png", - source: resolve(tauriIcons, "icon.png"), - }, - { - fileName: "icons/icon-maskable-512.png", - source: resolve(tauriIcons, "icon.png"), - }, - { - fileName: "icons/icon-monochrome-432.png", - source: resolve( - androidResources, - "mipmap-xxxhdpi/ic_launcher_monochrome.png", - ), - }, - { - fileName: "icons/notification-badge.png", - source: resolve(androidResources, "drawable/ic_notification_small.png"), - }, - ]; - - return { - name: "tensamin-pwa-icons", - configureServer(server) { - server.middlewares.use((request, response, next) => { - const pathname = request.url - ? new URL(request.url, "http://localhost").pathname.slice(1) - : ""; - const icon = icons.find(({ fileName }) => fileName === pathname); - if (!icon) { - next(); - return; - } - response.statusCode = 200; - response.setHeader("Content-Type", "image/png"); - response.setHeader("Cache-Control", "no-cache"); - response.end(readFileSync(icon.source)); - }); - }, - generateBundle() { - for (const icon of icons) { - this.emitFile({ - type: "asset", - fileName: icon.fileName, - source: readFileSync(icon.source), - }); - } - }, - transformIndexHtml: { - order: "post", - handler() { - return [ - { - tag: "link", - attrs: { - rel: "apple-touch-icon", - sizes: "180x180", - href: "./icons/icon-180.png", - }, - injectTo: "head", - }, - { - tag: "meta", - attrs: { name: "apple-mobile-web-app-capable", content: "yes" }, - injectTo: "head", - }, - { - tag: "meta", - attrs: { - name: "apple-mobile-web-app-status-bar-style", - content: "black-translucent", - }, - injectTo: "head", - }, - { - tag: "meta", - attrs: { - name: "apple-mobile-web-app-title", - content: "Tensamin", - }, - injectTo: "head", - }, - { - tag: "meta", - attrs: { name: "theme-color", content: "#006a67" }, - injectTo: "head", - }, - ]; - }, - }, - }; -} - -export function tensaminPwa(): Plugin[] { - return [ - emitIcons(), - ...VitePWA({ - strategies: "injectManifest", - srcDir: resolve(pwaDirectory, "src"), - filename: "serviceWorker.ts", - injectRegister: null, - registerType: "prompt", - buildBase: "/", - manifestFilename: "manifest.json", - includeAssets: ["favicon.ico", "icons/*.png"], - manifest: { - id: "/", - name: "Tensamin", - short_name: "Tensamin", - description: "Private messaging and calls with Tensamin.", - start_url: "/", - scope: "/", - display: "standalone", - display_override: ["window-controls-overlay", "standalone"], - background_color: "#001f1e", - theme_color: "#006a67", - categories: ["social", "communication"], - orientation: "any", - launch_handler: { client_mode: "focus-existing" }, - icons: [ - { - src: "icons/icon-192.png", - sizes: "192x192", - type: "image/png", - purpose: "any", - }, - { - src: "icons/icon-512.png", - sizes: "512x512", - type: "image/png", - purpose: "any", - }, - { - src: "icons/icon-maskable-512.png", - sizes: "512x512", - type: "image/png", - purpose: "maskable", - }, - { - src: "icons/icon-monochrome-432.png", - sizes: "432x432", - type: "image/png", - purpose: "monochrome", - }, - ], - shortcuts: [ - { - name: "Chats", - short_name: "Chats", - url: "/", - icons: [ - { - src: "icons/icon-96.png", - sizes: "96x96", - type: "image/png", - }, - ], - }, - { - name: "Settings", - short_name: "Settings", - url: "/settings", - icons: [ - { - src: "icons/icon-96.png", - sizes: "96x96", - type: "image/png", - }, - ], - }, - ], - file_handlers: [ - { - action: "/login", - accept: { "application/x-tensamin-user": [".tu"] }, - }, - ], - }, - injectManifest: { - globPatterns: ["**/*.{js,css,html,ico,png,svg,woff2,wasm,mp3,wav}"], - globIgnores: ["assets/v2/**"], - maximumFileSizeToCacheInBytes: 15 * 1024 * 1024, - }, - devOptions: { - enabled: true, - type: "module", - }, - }), - ]; -} diff --git a/apps/pwa/todo.md b/apps/pwa/todo.md deleted file mode 100644 index b5b2055..0000000 --- a/apps/pwa/todo.md +++ /dev/null @@ -1,33 +0,0 @@ -# Web Push Backend TODO - -The client can subscribe and decrypt version 1 push payloads, but reliable delivery requires backend support. - -- Generate and securely store a VAPID key pair. Expose only the public key to the web build as `VITE_WEB_PUSH_PUBLIC_KEY`. -- Add authenticated MTP requests for registering, replacing, and deleting a browser `PushSubscription` per user and installation. -- Persist the endpoint, `p256dh`, `auth`, expiration time, stable installation ID, and last-seen time. -- Remove subscriptions when a push service returns HTTP 404 or 410 and rate-limit registrations per user. -- Send pushes when an encrypted live message cannot be delivered to an active browser client. Define duplicate suppression for clients that receive both MTP and Web Push. -- Keep the JSON payload within push-provider limits and use this version 1 shape: - -```json -{ - "version": 1, - "senderId": 123, - "sender": "Display name", - "avatar": "https://optional.example/avatar", - "message": { "content": "base64 encrypted message content" }, - "secret": { - "chatId": "123:456", - "secretId": "chat:123:456:main", - "version": 1, - "encryptedSecret": "base64 wrapped chat secret", - "kemCiphertext": "base64 KEM ciphertext", - "wrappingScheme": "mtp-chat-secret-kem-chacha20poly1305-hkdf-sha256-v1" - } -} -``` - -- Ensure the wrapped secret is intended for the receiving user's MTP keyring. The server must never receive plaintext message content or plaintext chat secrets. -- Decide how edits, deletions, reactions, calls, read states, and per-chat notification cancellation map to push events. -- Add subscription rotation handling and unregister subscriptions when a user logs out or clears application data. -- Configure production HTTPS, SPA route fallback, `application/manifest+json` for `manifest.json`, and `Cache-Control: no-cache` for the service worker. diff --git a/apps/pwa/tsconfig.json b/apps/pwa/tsconfig.json deleted file mode 100644 index 1e7cfde..0000000 --- a/apps/pwa/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "types": ["vite/client", "vite-plugin-pwa/client", "node"], - "skipLibCheck": true, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - "jsx": "react-jsx", - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["src/runtime.tsx", "src/vite.ts"] -} diff --git a/apps/pwa/tsconfig.worker.json b/apps/pwa/tsconfig.worker.json deleted file mode 100644 index a478894..0000000 --- a/apps/pwa/tsconfig.worker.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "lib": ["ES2022", "WebWorker"], - "types": ["vite-plugin-pwa/client"], - "skipLibCheck": true, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["src/serviceWorker.ts"] -} diff --git a/apps/tauri/.gitignore b/apps/tauri/.gitignore index 8952e07..dc67136 100644 --- a/apps/tauri/.gitignore +++ b/apps/tauri/.gitignore @@ -24,3 +24,6 @@ dist-ssr *.sw? .android + +/src-tauri/gen/android/keystore.properties +/src-tauri/gen/android/keystore.jks diff --git a/apps/tauri/android.png b/apps/tauri/android.png deleted file mode 100644 index cdee032..0000000 Binary files a/apps/tauri/android.png and /dev/null differ diff --git a/apps/tauri/background.png b/apps/tauri/background.png deleted file mode 100644 index e6157bd..0000000 Binary files a/apps/tauri/background.png and /dev/null differ diff --git a/apps/tauri/flake.lock b/apps/tauri/flake.lock new file mode 100644 index 0000000..c3c71cb --- /dev/null +++ b/apps/tauri/flake.lock @@ -0,0 +1,96 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1775036866, + "narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "6201e203d09599479a3b3450ed24fa81537ebc4e", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1744536153, + "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1775272153, + "narHash": "sha256-FwYb64ysv8J2TxaqsYYcDyHAHBUEaQlriPMWPMi1K7M=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "740fb0203b2852917b909a72b948d34d0b171ec0", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/apps/tauri/flake.nix b/apps/tauri/flake.nix new file mode 100644 index 0000000..86a1b36 --- /dev/null +++ b/apps/tauri/flake.nix @@ -0,0 +1,138 @@ +{ + description = "Tauri development environment"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + rust-overlay.url = "github:oxalica/rust-overlay"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { + self, + nixpkgs, + rust-overlay, + flake-utils, + }: + flake-utils.lib.eachDefaultSystem ( + system: let + overlays = [(import rust-overlay)]; + pkgs = import nixpkgs { + inherit system overlays; + config = { + allowUnfree = true; + android_sdk.accept_license = true; + }; + }; + + projectRoot = "."; + androidHome = "${projectRoot}/.android"; + sdkRoot = "${androidHome}/sdk"; + ndkVersion = "29.0.14206865"; + + android = pkgs.androidenv.composeAndroidPackages { + cmdLineToolsVersion = "8.0"; + toolsVersion = "26.1.1"; + platformToolsVersion = "35.0.2"; + buildToolsVersions = ["35.0.0"]; + platformVersions = ["35" "36"]; + includeSources = false; + includeSystemImages = false; + includeNDK = true; + ndkVersions = [ndkVersion]; + useGoogleAPIs = false; + }; + + rustToolchain = pkgs.rust-bin.stable.latest.default.override { + extensions = ["rust-src" "rust-analyzer"]; + targets = [ + "aarch64-linux-android" + "armv7-linux-androideabi" + "i686-linux-android" + "x86_64-linux-android" + "wasm32-unknown-unknown" + ]; + }; + + desktopRuntimeLibs = with pkgs; [ + glib + gtk3 + gdk-pixbuf + pango + cairo + atk + at-spi2-atk + alsa-lib + dbus + cups + expat + libdrm + libgbm + libglvnd + mesa + libxkbcommon + libx11 + libxcomposite + libxdamage + libxext + libxfixes + libxrandr + libxcb + systemd + nspr + nss + ]; + in { + devShells.default = pkgs.mkShell { + buildInputs = with pkgs; + [ + jdk17 + rustToolchain + gradle + nodejs + pkg-config + ] + ++ desktopRuntimeLibs + ++ [ + android.androidsdk + pkgs.android-studio-tools + ]; + + shellHook = '' + sdkSource="${android.androidsdk}/libexec/android-sdk" + + mkdir -p "${androidHome}" + if [ -L "${sdkRoot}" ]; then + rm -f "${sdkRoot}" + fi + mkdir -p "${sdkRoot}" + + ln -sfn "$sdkSource/build-tools" "${sdkRoot}/build-tools" + ln -sfn "$sdkSource/cmake" "${sdkRoot}/cmake" + ln -sfn "$sdkSource/licenses" "${sdkRoot}/licenses" + ln -sfn "$sdkSource/ndk" "${sdkRoot}/ndk" + ln -sfn "$sdkSource/ndk-bundle" "${sdkRoot}/ndk-bundle" + ln -sfn "$sdkSource/platforms" "${sdkRoot}/platforms" + ln -sfn "$sdkSource/platform-tools" "${sdkRoot}/platform-tools" + ln -sfn "$sdkSource/tools" "${sdkRoot}/tools" + + mkdir -p "${sdkRoot}/cmdline-tools" + ln -sfn "$sdkSource/cmdline-tools/8.0" "${sdkRoot}/cmdline-tools/8.0" + ln -sfn "8.0" "${sdkRoot}/cmdline-tools/latest" + + sdkRootAbs="$(realpath "${sdkRoot}")" + ndkRootAbs="''${sdkRootAbs}/ndk/${ndkVersion}" + + export PATH="''${sdkRootAbs}/cmdline-tools/latest/bin:''${sdkRootAbs}/platform-tools:''${ndkRootAbs}:${pkgs.android-studio-tools}/bin:$PATH" + export ANDROID_HOME="''${sdkRootAbs}" + export ANDROID_SDK_ROOT="''${sdkRootAbs}" + export ANDROID_NDK_ROOT="''${ndkRootAbs}" + export ANDROID_NDK_HOME="$ANDROID_NDK_ROOT" + export NDK_HOME="$ANDROID_NDK_ROOT" + export NDK_PATH="$ANDROID_NDK_ROOT" + export JAVA_HOME="${pkgs.jdk17}" + export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath desktopRuntimeLibs}:$LD_LIBRARY_PATH" + ''; + }; + } + ); +} diff --git a/apps/tauri/logo.json b/apps/tauri/logo.json deleted file mode 100644 index 4c85e3b..0000000 --- a/apps/tauri/logo.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "default": "./logo.svg", - - "android_fg": "./android.png", - "android_bg": "./background.png", - "android_fg_scale": 100, - "android_monochrome": "./monochrome.png" -} diff --git a/apps/tauri/logo.png b/apps/tauri/logo.png new file mode 100644 index 0000000..7cb66f6 Binary files /dev/null and b/apps/tauri/logo.png differ diff --git a/apps/tauri/logo.svg b/apps/tauri/logo.svg deleted file mode 100644 index d6b091a..0000000 --- a/apps/tauri/logo.svg +++ /dev/null @@ -1,271 +0,0 @@ - - - - diff --git a/apps/tauri/monochrome.png b/apps/tauri/monochrome.png deleted file mode 100644 index 04d3abd..0000000 Binary files a/apps/tauri/monochrome.png and /dev/null differ diff --git a/apps/tauri/monochrome_cropped.png b/apps/tauri/monochrome_cropped.png deleted file mode 100644 index 424ebd1..0000000 Binary files a/apps/tauri/monochrome_cropped.png and /dev/null differ diff --git a/apps/tauri/package.json b/apps/tauri/package.json index 36937be..db9894e 100644 --- a/apps/tauri/package.json +++ b/apps/tauri/package.json @@ -4,33 +4,48 @@ "version": "0.0.0", "type": "module", "exports": { + "./context": { + "types": "./src/context.tsx", + "default": "./src/context.tsx" + }, + "./controls": { + "types": "./src/windowControls.tsx", + "default": "./src/windowControls.tsx" + }, "./deeplinkHandler": { "types": "./src/deeplinkHandler.tsx", "default": "./src/deeplinkHandler.tsx" + }, + "./qrCodeScanner": { + "types": "./src/qrCodeScanner.tsx", + "default": "./src/qrCodeScanner.tsx" } }, "scripts": { - "dev:mobile:raw": "adb reverse tcp:3000 tcp:3000 && tauri android dev --host ${TAURI_DEV_HOST:-127.0.0.1}", - "start-adb:mobile:raw": "adb devices", + "dev:mobile:raw": "tauri android dev", "build:mobile:raw": "tauri android build", - "build:mobile:ci": "node render-version.ts && trap 'node render-version.ts --unrender' EXIT && tauri android build --debug", - "dev:mobile": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop ../..#tauri --command pnpm run dev:mobile:raw; else pnpm run dev:mobile:raw; fi", - "start-adb:mobile": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop ../..#tauri --command pnpm run start-adb:mobile:raw; else pnpm run start-adb:mobile:raw; fi", - "build:mobile": "node render-version.ts && trap 'node render-version.ts --unrender' EXIT && if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop ../..#tauri --command pnpm run build:mobile:raw; else pnpm run build:mobile:raw; fi", - "gen-icons": "tauri icon ./logo.json && node scripts/sync-electron-icons.ts", - "format": "pnpm exec prettier --write .", + "dev:mobile": "if command -v nix >/dev/null 2>&1; then nix develop --command bun dev:mobile:raw; else bun dev:mobile:raw; fi", + "build:mobile": "if command -v nix >/dev/null 2>&1; then nix develop --command bun build:mobile:raw; else bun build:mobile:raw; fi", + "dev:desktop:raw": "tauri dev", + "build:desktop:raw": "tauri build", + "dev:desktop": "if command -v nix >/dev/null 2>&1; then nix develop --command bun dev:desktop:raw; else bun dev:desktop:raw; fi", + "build:desktop": "if command -v nix >/dev/null 2>&1; then nix develop --command bun build:desktop:raw; else bun build:desktop:raw; fi", + "gen-icons": "tauri icon ./logo.png", + "format": "bunx prettier --write .", "lint": "eslint src" }, "dependencies": { - "@methanium/ui": "*", - "@tauri-apps/api": "^2.11.1", - "@tauri-apps/plugin-deep-link": "~2.4.9", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-barcode-scanner": "~2", + "@tauri-apps/plugin-deep-link": "~2", + "@tauri-apps/plugin-opener": "^2", + "@tensamin/ui": "*", "@tensamin/shared": "workspace:*", - "react": "^19.2.8", - "react-dom": "^19.2.8" + "lucide-react": "^1.14.0", + "react": "^19.2.0", + "react-dom": "^19.2.0" }, "devDependencies": { - "@tauri-apps/cli": "^2.11.4", - "@types/node": "^26.1.2" + "@tauri-apps/cli": "^2" } } diff --git a/apps/tauri/readme.md b/apps/tauri/readme.md new file mode 100644 index 0000000..38cb730 --- /dev/null +++ b/apps/tauri/readme.md @@ -0,0 +1,5 @@ +# Outputs + +- ./src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk +- ./src-tauri/target/release/bundle/deb/Tensamin*{version}*{arch}.deb +- ./src-tauri/target/release/bundle/rpm/Tensamin-{version}-1.{arch}.rpm diff --git a/apps/tauri/render-version.ts b/apps/tauri/render-version.ts deleted file mode 100644 index 7635bed..0000000 --- a/apps/tauri/render-version.ts +++ /dev/null @@ -1,68 +0,0 @@ -import fs from "fs"; -import path from "path"; -import { fileURLToPath } from "url"; - -// Config -const PLACEHOLDER_VERSION = "0.0.0"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -const rootPackageJsonPath = path.resolve(__dirname, "../../package.json"); -const cargoTomlPath = path.resolve(__dirname, "./src-tauri/Cargo.toml"); -const tauriConfigPath = path.resolve(__dirname, "./src-tauri/tauri.conf.json"); - -// Args -const isUnrender = process.argv.includes("--unrender"); - -// Version Source -const packageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, "utf8")); - -const packageVersion: string = packageJson.version; - -if (!packageVersion && !isUnrender) { - throw new Error("No version found in package.json"); -} - -const targetVersion = isUnrender ? PLACEHOLDER_VERSION : packageVersion; - -// Helpers -function updateCargoToml(content: string): string { - const regex = /^version\s*=\s*".*"$/m; - - if (!regex.test(content)) { - throw new Error("Could not find version field in Cargo.toml"); - } - - return content.replace(regex, `version = "${targetVersion}"`); -} - -function updateTauriConfig(content: string): string { - const regex = /"version"\s*:\s*".*"/; - - if (!regex.test(content)) { - throw new Error("Could not find version field in tauri.conf.json"); - } - - return content.replace(regex, `"version": "${targetVersion}"`); -} - -// Update Cargo.toml -const cargoToml = fs.readFileSync(cargoTomlPath, "utf8"); - -const updatedCargoToml = updateCargoToml(cargoToml); - -fs.writeFileSync(cargoTomlPath, updatedCargoToml, "utf8"); - -// Update tauri.conf.json -const tauriConfig = fs.readFileSync(tauriConfigPath, "utf8"); - -const updatedTauriConfig = updateTauriConfig(tauriConfig); - -fs.writeFileSync(tauriConfigPath, updatedTauriConfig, "utf8"); - -// Finished -if (isUnrender) { - console.log(`Unrendered versions back to ${PLACEHOLDER_VERSION}`); -} else { - console.log(`Rendered version ${targetVersion}`); -} diff --git a/apps/tauri/scripts/delete-mobile.ts b/apps/tauri/scripts/delete-mobile.ts deleted file mode 100644 index e41fa50..0000000 --- a/apps/tauri/scripts/delete-mobile.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { spawnSync } from "node:child_process"; -import { createInterface } from "node:readline/promises"; - -const devicesResult = spawnSync("adb", ["devices", "-l"], { - encoding: "utf8", -}); - -if (devicesResult.status !== 0) { - process.stderr.write(devicesResult.stderr); - process.exit(devicesResult.status ?? 1); -} - -const devices = devicesResult.stdout - .split("\n") - .slice(1) - .map((line) => line.trim()) - .filter((line) => /\sdevice(?:\s|$)/.test(line)); - -if (devices.length === 0) { - console.error("No connected ADB devices found."); - process.exit(1); -} - -let selectedDevice = devices[0]; - -if (devices.length > 1) { - console.log("Select a device:"); - devices.forEach((device, index) => console.log(`${index + 1}) ${device}`)); - - const readline = createInterface({ - input: process.stdin, - output: process.stdout, - }); - const answer = await readline.question("Device: "); - readline.close(); - - const selectedIndex = Number(answer) - 1; - if (!Number.isInteger(selectedIndex) || !devices[selectedIndex]) { - console.error("Invalid device selection."); - process.exit(1); - } - - selectedDevice = devices[selectedIndex]; -} - -const serial = selectedDevice.split(/\s+/, 1)[0]; -const uninstallResult = spawnSync( - "adb", - ["-s", serial, "uninstall", "net.tensamin.client.dev"], - { stdio: "inherit" }, -); - -process.exit(uninstallResult.status ?? 1); diff --git a/apps/tauri/scripts/sync-electron-icons.ts b/apps/tauri/scripts/sync-electron-icons.ts deleted file mode 100644 index 62f232b..0000000 --- a/apps/tauri/scripts/sync-electron-icons.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { copyFile, mkdir } from "node:fs/promises"; -import { dirname, join, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -const scriptDir = dirname(fileURLToPath(import.meta.url)); -const tauriDir = resolve(scriptDir, ".."); -const clientDir = resolve(tauriDir, "../.."); -const tauriIconsDir = join(tauriDir, "src-tauri", "icons"); -const electronIconsDir = join(clientDir, "apps", "electron", "build", "icons"); - -const desktopIcons = [ - "32x32.png", - "64x64.png", - "128x128.png", - "128x128@2x.png", - "icon.png", - "icon.ico", - "icon.icns", -]; - -await mkdir(electronIconsDir, { recursive: true }); - -await Promise.all( - desktopIcons.map((icon) => - copyFile(join(tauriIconsDir, icon), join(electronIconsDir, icon)), - ), -); - -console.log( - `Synced ${desktopIcons.length} desktop icons to ${electronIconsDir}`, -); diff --git a/apps/tauri/src-tauri/Cargo.lock b/apps/tauri/src-tauri/Cargo.lock index d404d61..e65d82e 100644 --- a/apps/tauri/src-tauri/Cargo.lock +++ b/apps/tauri/src-tauri/Cargo.lock @@ -8,21 +8,11 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common 0.1.7", - "generic-array", -] - [[package]] name = "aho-corasick" -version = "1.1.5" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -35,83 +25,77 @@ checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" -version = "0.2.4" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ "alloc-no-stdlib", ] -[[package]] -name = "android_log-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" - -[[package]] -name = "android_logger" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3" -dependencies = [ - "android_log-sys", - "env_filter", - "log", -] - [[package]] name = "android_system_properties" -version = "0.1.6" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" -version = "1.0.104" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" - -[[package]] -name = "asn1-rs" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" -dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", - "displaydoc", - "nom", - "num-traits", - "rusticata-macros", - "thiserror 2.0.19", - "time", -] - -[[package]] -name = "asn1-rs-derive" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure", -] - -[[package]] -name = "asn1-rs-impl" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "async-broadcast" @@ -206,14 +190,14 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] name = "async-signal" -version = "0.2.14" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" dependencies = [ "async-io", "async-lock", @@ -235,13 +219,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.91" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 2.0.117", ] [[package]] @@ -275,33 +259,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "aws-lc-rs" -version = "1.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" -dependencies = [ - "aws-lc-sys", - "untrusted 0.7.1", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.43.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", - "pkg-config", -] +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base64" @@ -315,25 +275,13 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" - -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec 0.8.0", + "bit-vec", ] [[package]] @@ -342,15 +290,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" -[[package]] -name = "bit-vec" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" -dependencies = [ - "serde", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -359,9 +298,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.13.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ "serde_core", ] @@ -375,15 +314,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-buffer" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" -dependencies = [ - "hybrid-array", -] - [[package]] name = "block2" version = "0.6.2" @@ -408,9 +338,9 @@ dependencies = [ [[package]] name = "brotli" -version = "8.0.4" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -419,34 +349,25 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "5.0.3" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "tinyvec", -] - [[package]] name = "bumpalo" -version = "3.20.3" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytemuck" -version = "1.25.2" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "byteorder" @@ -455,21 +376,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "bytes" -version = "1.12.1" +name = "byteorder-lite" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + [[package]] name = "cairo-rs" version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "cairo-sys-rs", "glib", "libc", @@ -490,9 +426,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.2.5" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ "serde_core", ] @@ -517,32 +453,53 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.19", + "thiserror 2.0.18", ] [[package]] name = "cargo_toml" -version = "1.0.0" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa61aec073ec94791433ddf3df2323ff9d1711557c2a0eefb0f99cb4f8dca520" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" dependencies = [ - "semver", "serde", - "toml 1.1.4+spec-1.1.0", + "toml 0.9.12+spec-1.1.0", ] [[package]] name = "cc" -version = "1.4.0" +version = "1.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ "find-msvc-tools", - "jobserver", - "libc", "shlex", ] +[[package]] +name = "cef" +version = "146.4.1+146.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5dad6495c583fedab04a24f6fc08274c59a28b33967ca87709398e1f11c2ebe" +dependencies = [ + "cef-dll-sys", + "libloading 0.9.0", + "objc2", + "windows-sys 0.61.2", +] + +[[package]] +name = "cef-dll-sys" +version = "146.4.1+146.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6616217417da12da57bb7650acc3c8be0fc26e7120892ab926d2ba8d84c8c9c2" +dependencies = [ + "anyhow", + "cmake", + "download-cef", + "serde_json", +] + [[package]] name = "cesu8" version = "1.1.0" @@ -576,52 +533,11 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "cfg_aliases" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" - -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures 0.2.17", -] - -[[package]] -name = "chacha20" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "rand_core 0.10.1", -] - -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20 0.9.1", - "cipher", - "poly1305", - "zeroize", -] - [[package]] name = "chrono" -version = "0.4.45" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "num-traits", @@ -630,16 +546,45 @@ dependencies = [ ] [[package]] -name = "cipher" -version = "0.4.4" +name = "clap" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ - "crypto-common 0.1.7", - "inout", - "zeroize", + "clap_builder", + "clap_derive", ] +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + [[package]] name = "cmake" version = "0.1.58" @@ -650,10 +595,16 @@ dependencies = [ ] [[package]] -name = "cmov" -version = "0.5.4" +name = "color_quant" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "combine" @@ -675,10 +626,16 @@ dependencies = [ ] [[package]] -name = "const-oid" -version = "0.10.2" +name = "console" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +dependencies = [ + "encode_unicode", + "libc", + "unicode-width", + "windows-sys 0.61.2", +] [[package]] name = "const-random" @@ -700,16 +657,41 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "cookie" version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ + "percent-encoding", "time", "version_check", ] +[[package]] +name = "cookie_store" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" +dependencies = [ + "cookie", + "document-features", + "idna", + "indexmap 2.13.1", + "log", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -732,7 +714,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "core-foundation", "core-graphics-types", "foreign-types", @@ -745,7 +727,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "core-foundation", "libc", ] @@ -759,15 +741,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - [[package]] name = "crc32fast" version = "1.5.0" @@ -779,18 +752,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.16" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.22" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" @@ -805,19 +778,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", - "rand_core 0.6.4", "typenum", ] [[package]] -name = "crypto-common" -version = "0.2.2" +name = "cssparser" +version = "0.29.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" dependencies = [ - "getrandom 0.4.3", - "hybrid-array", - "rand_core 0.10.1", + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf 0.10.1", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", ] [[package]] @@ -829,7 +807,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "phf", + "phf 0.13.1", "smallvec", ] @@ -840,80 +818,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] name = "ctor" -version = "0.8.0" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ - "ctor-proc-macro", - "dtor", -] - -[[package]] -name = "ctor" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d83cb7e7a873830708d6b02a78cd36a592c6fa14bf267b68725103b85c0d77f" - -[[package]] -name = "ctor-proc-macro" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" - -[[package]] -name = "ctutils" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" -dependencies = [ - "cmov", -] - -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "curve25519-dalek-derive", - "fiat-crypto 0.2.9", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "curve25519-dalek-derive", - "digest 0.11.3", - "fiat-crypto 0.3.0", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -936,7 +851,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -947,60 +862,43 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.119", + "syn 2.0.117", ] -[[package]] -name = "data-encoding" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" - [[package]] name = "dbus" -version = "0.9.12" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" +checksum = "b942602992bb7acfd1f51c49811c58a610ef9181b6e66f3e519d79b540a3bf73" dependencies = [ "libc", "libdbus-sys", "windows-sys 0.61.2", ] -[[package]] -name = "der" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "der-parser" -version = "10.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" -dependencies = [ - "asn1-rs", - "displaydoc", - "nom", - "num-bigint", - "num-traits", - "rusticata-macros", -] - [[package]] name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ + "powerfmt", "serde_core", ] +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", +] + [[package]] name = "derive_more" version = "2.1.1" @@ -1019,7 +917,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -1028,21 +926,15 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", - "crypto-common 0.1.7", + "block-buffer", + "crypto-common", ] [[package]] -name = "digest" -version = "0.11.3" +name = "dioxus-debug-cell" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" -dependencies = [ - "block-buffer 0.12.1", - "const-oid", - "crypto-common 0.2.2", - "ctutils", -] +checksum = "2ea539174bb236e0e7dc9c12b19b88eae3cb574dedbd0252a2d43ea7e6de13e2" [[package]] name = "dirs" @@ -1065,13 +957,25 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "dispatch2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a0d569e003ff27784e0e14e4a594048698e0c0f0b66cabcb51511be55a7caa0" +dependencies = [ + "bitflags 2.11.0", + "block2", + "libc", + "objc2", +] + [[package]] name = "dispatch2" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "block2", "libc", "objc2", @@ -1079,13 +983,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.7" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 2.0.117", ] [[package]] @@ -1108,7 +1012,7 @@ checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -1120,6 +1024,15 @@ dependencies = [ "const-random", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "dom_query" version = "0.27.0" @@ -1127,12 +1040,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" dependencies = [ "bit-set", - "cssparser", - "foldhash", - "html5ever", + "cssparser 0.36.0", + "foldhash 0.2.0", + "html5ever 0.38.0", "precomputed-hash", - "selectors", - "tendril", + "selectors 0.36.1", + "tendril 0.5.0", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "download-cef" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7471a7d5d3bd8df1b3b75871f0317d8a6dd73270ab861cc97ae7907ee63e554" +dependencies = [ + "bzip2", + "clap", + "indicatif", + "regex", + "semver", + "serde", + "serde_json", + "sha1_smol", + "tar", + "thiserror 2.0.18", + "ureq", ] [[package]] @@ -1159,21 +1097,6 @@ dependencies = [ "dtoa", ] -[[package]] -name = "dtor" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" -dependencies = [ - "dtor-proc-macro", -] - -[[package]] -name = "dtor-proc-macro" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" - [[package]] name = "dunce" version = "1.0.5" @@ -1186,41 +1109,16 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" -[[package]] -name = "ed25519" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" -dependencies = [ - "pkcs8", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" -dependencies = [ - "curve25519-dalek 5.0.0", - "ed25519", - "serde", - "sha2 0.11.0", - "signature", - "subtle", - "zeroize", -] - [[package]] name = "embed-resource" -version = "3.0.11" +version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +checksum = "63a1d0de4f2249aa0ff5884d7080814f446bb241a559af6c170a41e878ed2d45" dependencies = [ "cc", "memchr", "rustc_version", - "toml 1.1.4+spec-1.1.0", + "toml 0.9.12+spec-1.1.0", "vswhom", "winreg", ] @@ -1231,6 +1129,12 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "endi" version = "1.1.1" @@ -1255,17 +1159,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", -] - -[[package]] -name = "env_filter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" -dependencies = [ - "log", - "regex", + "syn 2.0.117", ] [[package]] @@ -1292,15 +1186,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "event-listener" -version = "5.4.2" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ + "concurrent-queue", "parking", "pin-project-lite", ] @@ -1315,23 +1210,11 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "fastbloom" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef975e30683b2d965054bb0a836f8973857c4ebf6acf274fe46617cd285060d8" -dependencies = [ - "foldhash", - "libm", - "portable-atomic", - "siphasher", -] - [[package]] name = "fastrand" -version = "2.5.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fdeflate" @@ -1342,27 +1225,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "fern" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29" -dependencies = [ - "log", -] - -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - -[[package]] -name = "fiat-crypto" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" - [[package]] name = "field-offset" version = "0.3.6" @@ -1373,6 +1235,17 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1395,6 +1268,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foldhash" version = "0.2.0" @@ -1413,13 +1292,13 @@ dependencies = [ [[package]] name = "foreign-types-macros" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 2.0.117", ] [[package]] @@ -1438,47 +1317,35 @@ dependencies = [ ] [[package]] -name = "fs_extra" -version = "1.3.0" +name = "futf" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - -[[package]] -name = "futures" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", + "mac", + "new_debug_unreachable", ] [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", - "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1487,9 +1354,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -1506,34 +1373,33 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ - "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1544,6 +1410,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "gdk" version = "0.18.2" @@ -1653,6 +1528,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -1660,10 +1546,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", - "js-sys", "libc", - "wasi", - "wasm-bindgen", + "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] @@ -1680,16 +1564,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.3" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi 6.0.0", - "rand_core 0.10.1", - "wasm-bindgen", + "wasip2", + "wasip3", ] [[package]] @@ -1730,7 +1613,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "futures-channel", "futures-core", "futures-executor", @@ -1758,7 +1641,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -1773,9 +1656,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "gobject-sys" @@ -1837,82 +1720,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.119", -] - -[[package]] -name = "h2" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap 2.14.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "h3" -version = "0.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10872b55cfb02a821b69dc7cf8dc6a71d6af25eb9a79662bec4a9d016056b3be" -dependencies = [ - "bytes", - "fastrand", - "futures-util", - "http", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "h3-datagram" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d2c9f77921668673721ae40f17c729fc48b9e38a663858097cea547484fdf0f" -dependencies = [ - "bytes", - "h3", - "pin-project-lite", -] - -[[package]] -name = "h3-quinn" -version = "0.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2e732c8d91a74731663ac8479ab505042fbf547b9a207213ab7fbcbfc4f8b4" -dependencies = [ - "bytes", - "futures", - "h3", - "h3-datagram", - "quinn", - "tokio", - "tokio-util", -] - -[[package]] -name = "h3-webtransport" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d91a50fd582a5d67b1f756fba3cd9c66367ff4f23e1017c882f664d63b350a7" -dependencies = [ - "bytes", - "futures-util", - "h3", - "h3-datagram", - "http", - "pin-project-lite", - "tokio", - "tracing", + "syn 2.0.117", ] [[package]] @@ -1929,9 +1737,18 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.17.1" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -1958,21 +1775,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "hkdf" -version = "0.13.0" +name = "html5ever" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" -dependencies = [ - "digest 0.11.3", + "log", + "mac", + "markup5ever 0.14.1", + "match_token", ] [[package]] @@ -1982,20 +1793,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" dependencies = [ "log", - "markup5ever", + "markup5ever 0.38.0", ] -[[package]] -name = "httlib-huffman" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" - [[package]] name = "http" -version = "1.5.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", "itoa", @@ -2003,9 +1808,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.1.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", @@ -2013,9 +1818,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", @@ -2030,37 +1835,19 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hybrid-array" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" -dependencies = [ - "ctutils", - "typenum", -] - [[package]] name = "hyper" -version = "1.11.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", - "h2", "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -2068,21 +1855,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.27.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "tokio", - "tokio-rustls", - "tower-service", -] - [[package]] name = "hyper-util" version = "0.1.20" @@ -2118,7 +1890,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.62.2", ] [[package]] @@ -2222,6 +1994,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -2241,14 +2019,41 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", ] +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-traits", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png 0.18.1", + "zune-core", + "zune-jpeg", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -2262,16 +2067,29 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", - "hashbrown 0.17.1", + "hashbrown 0.16.1", "serde", "serde_core", ] +[[package]] +name = "indicatif" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb" +dependencies = [ + "console", + "portable-atomic", + "unicode-width", + "unit-prefix", + "web-time", +] + [[package]] name = "infer" version = "0.19.0" @@ -2282,19 +2100,20 @@ dependencies = [ ] [[package]] -name = "inout" -version = "0.1.4" +name = "ipnet" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] -name = "ipnet" -version = "2.12.1" +name = "iri-string" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] [[package]] name = "is-docker" @@ -2315,6 +2134,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itoa" version = "1.0.18" @@ -2360,36 +2185,6 @@ dependencies = [ "windows-sys 0.45.0", ] -[[package]] -name = "jni" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" -dependencies = [ - "cfg-if", - "combine", - "jni-macros", - "jni-sys 0.4.1", - "log", - "simd_cesu8", - "thiserror 2.0.19", - "walkdir", - "windows-link 0.2.1", -] - -[[package]] -name = "jni-macros" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "simd_cesu8", - "syn 2.0.119", -] - [[package]] name = "jni-sys" version = "0.3.1" @@ -2415,27 +2210,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn 2.0.119", -] - -[[package]] -name = "jobserver" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" -dependencies = [ - "getrandom 0.4.3", - "libc", + "syn 2.0.117", ] [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ "cfg-if", "futures-util", + "once_cell", "wasm-bindgen", ] @@ -2445,24 +2231,12 @@ version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" dependencies = [ - "jsonptr 0.6.3", + "jsonptr", "serde", "serde_json", "thiserror 1.0.69", ] -[[package]] -name = "json-patch" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72" -dependencies = [ - "jsonptr 0.7.1", - "serde", - "serde_json", - "thiserror 2.0.19", -] - [[package]] name = "jsonptr" version = "0.6.3" @@ -2473,52 +2247,41 @@ dependencies = [ "serde_json", ] -[[package]] -name = "jsonptr" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "keccak" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" -dependencies = [ - "cpufeatures 0.2.17", -] - -[[package]] -name = "keccak" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd9697dc4a9a62e2da93389f34400b77a28f0287711263cabb203b3ccb9c0e4" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", -] - [[package]] name = "keyboard-types" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "serde", "unicode-segmentation", ] +[[package]] +name = "kuchikiki" +version = "0.8.8-speedreader" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +dependencies = [ + "cssparser 0.29.6", + "html5ever 0.29.1", + "indexmap 2.13.1", + "selectors 0.24.0", +] + [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libappindicator" version = "0.9.0" @@ -2539,15 +2302,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" dependencies = [ "gtk-sys", - "libloading", + "libloading 0.7.4", "once_cell", ] [[package]] -name = "libc" -version = "0.2.189" +name = "libbz2-rs-sys" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libdbus-sys" @@ -2569,18 +2338,41 @@ dependencies = [ ] [[package]] -name = "libm" -version = "0.2.16" +name = "libloading" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] [[package]] name = "libredox" -version = "0.1.19" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ + "bitflags 2.11.0", "libc", + "plain", + "redox_syscall 0.7.3", +] + +[[package]] +name = "libwayshot" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2efa01ecfd021b1e7db27f21f4e79b35b048081c9cae9d2f898eddc98444d69" +dependencies = [ + "image 0.24.9", + "log", + "memmap2", + "nix", + "thiserror 1.0.69", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", ] [[package]] @@ -2595,6 +2387,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -2606,28 +2404,28 @@ dependencies = [ [[package]] name = "log" -version = "0.4.33" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] -name = "lru-slab" -version = "0.1.2" +name = "mac" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] -name = "mac-notification-sys" -version = "0.6.15" +name = "markup5ever" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" dependencies = [ - "cc", "log", - "objc2", - "objc2-foundation", - "time", - "uuid", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache 0.8.9", + "string_cache_codegen 0.5.4", + "tendril 0.4.3", ] [[package]] @@ -2637,15 +2435,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" dependencies = [ "log", - "tendril", + "tendril 0.5.0", "web_atoms", ] [[package]] -name = "memchr" -version = "2.8.3" +name = "match_token" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] [[package]] name = "memoffset" @@ -2662,12 +2486,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2680,221 +2498,30 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.61.2", ] [[package]] -name = "ml-dsa" -version = "0.1.1" +name = "moxcms" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" dependencies = [ - "const-oid", - "crypto-common 0.2.2", - "ctutils", - "hybrid-array", - "module-lattice", - "pkcs8", - "shake", - "signature", -] - -[[package]] -name = "mlkem-rs" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b0965b8b081668ff0398dc5e9dc3f2ebb9e833393f4ab5b9f725ddce11acef8" -dependencies = [ - "rand_core 0.6.4", - "serde", - "sha3", - "subtle", - "zeroize", -] - -[[package]] -name = "mlkem-tls" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b205d031298adf904d88efd6a57862d8650a4ab754aade19a9b5e87040bf4e" -dependencies = [ - "mlkem-rs", - "rand_core 0.6.4", - "subtle", - "x25519-dalek", - "zeroize", -] - -[[package]] -name = "module-lattice" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" -dependencies = [ - "ctutils", - "hybrid-array", "num-traits", -] - -[[package]] -name = "mtp" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "mtp-client", - "mtp-codec", - "mtp-common", - "mtp-crypto", - "mtp-host", - "mtp-transport", - "mtp-type-map", - "mtp-webserver", -] - -[[package]] -name = "mtp-client" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "mtp-codec", - "mtp-common", - "mtp-crypto", - "mtp-transport", - "rand 0.10.2", - "tokio", -] - -[[package]] -name = "mtp-codec" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "base64 0.23.1", - "byteorder", - "mtp-common", - "mtp-crypto", - "mtp-type-map", - "rand 0.10.2", - "thiserror 2.0.19", -] - -[[package]] -name = "mtp-common" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "quinn", - "rustls", - "thiserror 2.0.19", - "wtransport", -] - -[[package]] -name = "mtp-crypto" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "base64 0.22.1", - "chacha20poly1305", - "ed25519-dalek", - "getrandom 0.4.3", - "hkdf", - "ml-dsa", - "mlkem-tls", - "rand 0.10.2", - "rand_core 0.6.4", - "rustls", - "serde", - "sha2 0.11.0", - "thiserror 1.0.69", - "tokio", - "zeroize", -] - -[[package]] -name = "mtp-host" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "mtp-codec", - "mtp-common", - "mtp-crypto", - "mtp-transport", - "rand 0.10.2", - "thiserror 2.0.19", - "tokio", - "tracing", - "wtransport", -] - -[[package]] -name = "mtp-transport" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "async-trait", - "mtp-codec", - "mtp-common", - "mtp-crypto", - "rand 0.10.2", - "rcgen", - "rustls", - "rustls-native-certs", - "sha2 0.11.0", - "tokio", - "tracing", - "wtransport", - "zeroize", -] - -[[package]] -name = "mtp-type-map" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "serde", - "serde_yaml", -] - -[[package]] -name = "mtp-webserver" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "async-trait", - "bytes", - "h3", - "h3-quinn", - "h3-webtransport", - "http", - "http-body-util", - "hyper", - "hyper-util", - "mtp-codec", - "mtp-common", - "mtp-crypto", - "mtp-host", - "mtp-transport", - "quinn", - "rustls", - "thiserror 2.0.19", - "tokio", - "tokio-rustls", - "tokio-stream", - "tracing", + "pxfm", ] [[package]] name = "muda" -version = "0.19.3" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" +checksum = "7c9fec5a4e89860383d778d10563a605838f8f0b2f9303868937e5ff32e86177" dependencies = [ "crossbeam-channel", "dpi", @@ -2905,10 +2532,10 @@ dependencies = [ "objc2-core-foundation", "objc2-foundation", "once_cell", - "png 0.18.1", + "png 0.17.16", "serde", - "thiserror 2.0.19", - "windows-sys 0.61.2", + "thiserror 2.0.18", + "windows-sys 0.60.2", ] [[package]] @@ -2917,7 +2544,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "jni-sys 0.3.1", "log", "ndk-sys", @@ -2926,6 +2553,12 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "ndk-sys" version = "0.6.0+11769913" @@ -2942,53 +2575,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] -name = "nom" -version = "7.1.3" +name = "nix" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "memchr", - "minimal-lexical", + "bitflags 2.11.0", + "cfg-if", + "libc", ] [[package]] -name = "notify-rust" -version = "4.18.0" +name = "nodrop" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b4c1b4f2aa9f25f63a7a49d3dd0ed567b3670da15330a66b29434be899b891" -dependencies = [ - "futures-lite", - "log", - "mac-notification-sys", - "serde", - "tauri-winrt-notification", - "zbus", -] - -[[package]] -name = "num-bigint" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" -dependencies = [ - "num-integer", - "num-traits", -] +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" [[package]] name = "num-conv" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-traits" @@ -3018,16 +2625,7 @@ dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.119", -] - -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", + "syn 2.0.117", ] [[package]] @@ -3046,10 +2644,50 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "block2", + "libc", "objc2", + "objc2-cloud-kit", + "objc2-core-data", "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-av-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478ae33fcac9df0a18db8302387c666b8ef08a3e2d62b510ca4fc278a384b6c0" +dependencies = [ + "bitflags 2.11.0", + "block2", + "dispatch2 0.3.1", + "objc2", + "objc2-avf-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-video", + "objc2-foundation", + "objc2-image-io", + "objc2-media-toolbox", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-avf-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be" +dependencies = [ + "objc2", "objc2-foundation", ] @@ -3059,17 +2697,40 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "objc2", "objc2-foundation", ] +[[package]] +name = "objc2-core-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2" +dependencies = [ + "dispatch2 0.3.1", + "objc2", + "objc2-core-audio-types", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-core-audio-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" +dependencies = [ + "bitflags 2.11.0", + "objc2", +] + [[package]] name = "objc2-core-data" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" dependencies = [ + "bitflags 2.11.0", "objc2", "objc2-foundation", ] @@ -3080,8 +2741,10 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.13.1", - "dispatch2", + "bitflags 2.11.0", + "block2", + "dispatch2 0.3.1", + "libc", "objc2", ] @@ -3091,11 +2754,14 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.13.1", - "dispatch2", + "bitflags 2.11.0", + "block2", + "dispatch2 0.3.1", + "libc", "objc2", "objc2-core-foundation", "objc2-io-surface", + "objc2-metal", ] [[package]] @@ -3109,13 +2775,19 @@ dependencies = [ ] [[package]] -name = "objc2-core-location" +name = "objc2-core-media" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +checksum = "05ec576860167a15dd9fce7fbee7512beb4e31f532159d3482d1f9c6caedf31d" dependencies = [ + "bitflags 2.11.0", + "block2", + "dispatch2 0.3.1", "objc2", - "objc2-foundation", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-video", ] [[package]] @@ -3124,12 +2796,27 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "objc2", "objc2-core-foundation", "objc2-core-graphics", ] +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.11.0", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", + "objc2-metal", +] + [[package]] name = "objc2-encode" version = "4.1.0" @@ -3151,20 +2838,31 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "block2", "libc", "objc2", "objc2-core-foundation", ] +[[package]] +name = "objc2-image-io" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b0446e98cf4a784cc7a0177715ff317eeaa8463841c616cfc78aa4f953c4ea" +dependencies = [ + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + [[package]] name = "objc2-io-surface" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "objc2", "objc2-core-foundation", ] @@ -3179,13 +2877,36 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-media-toolbox" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd9fdde720df3da7046bb9097811000c1e7ab5cd579fa89d96b27d56781fb30" +dependencies = [ + "objc2", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-media", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-foundation", +] + [[package]] name = "objc2-quartz-core" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "objc2", "objc2-core-foundation", "objc2-foundation", @@ -3197,7 +2918,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "objc2", "objc2-core-foundation", ] @@ -3208,28 +2929,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.13.1", - "block2", + "bitflags 2.11.0", "objc2", - "objc2-cloud-kit", - "objc2-core-data", "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-location", - "objc2-core-text", - "objc2-foundation", - "objc2-quartz-core", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" -dependencies = [ - "objc2", "objc2-foundation", ] @@ -3239,7 +2941,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "block2", "objc2", "objc2-app-kit", @@ -3249,21 +2951,6 @@ dependencies = [ "objc2-security", ] -[[package]] -name = "octets" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "866cb5af6f3aa3c1b44c3c2d79d22165fbb1b102e1b3fb499864bfe34736ec4b" - -[[package]] -name = "oid-registry" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" -dependencies = [ - "asn1-rs", -] - [[package]] name = "once_cell" version = "1.21.4" @@ -3271,28 +2958,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "opaque-debug" -version = "0.3.1" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "open" -version = "5.4.1" +version = "5.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cfef937e9c486488c7e3d949ae31c0f1d06bdacd75b99c086cb35356e30408" +checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" dependencies = [ "dunce", "is-wsl", "libc", + "pathdiff", ] -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - [[package]] name = "option-ext" version = "0.2.0" @@ -3368,29 +3050,16 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link 0.2.1", ] [[package]] -name = "pem" -version = "3.0.6" +name = "pathdiff" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" -dependencies = [ - "base64 0.22.1", - "serde_core", -] - -[[package]] -name = "pem-rfc7468" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" -dependencies = [ - "base64ct", -] +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "percent-encoding" @@ -3398,25 +3067,105 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared 0.8.0", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + [[package]] name = "phf" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_macros", - "phf_shared", + "phf_macros 0.13.1", + "phf_shared 0.13.1", "serde", ] +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + [[package]] name = "phf_codegen" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.13.1", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", ] [[package]] @@ -3426,7 +3175,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ "fastrand", - "phf_shared", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -3435,11 +3211,38 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.2", ] [[package]] @@ -3448,7 +3251,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ - "siphasher", + "siphasher 1.0.2", ] [[package]] @@ -3469,30 +3272,26 @@ dependencies = [ ] [[package]] -name = "pkcs8" -version = "0.11.0" +name = "pkg-config" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" -dependencies = [ - "der", - "spki", -] +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] -name = "pkg-config" -version = "0.3.33" +name = "plain" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "plist" -version = "1.10.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ "base64 0.22.1", - "indexmap 2.14.0", - "quick-xml", + "indexmap 2.13.1", + "quick-xml 0.38.4", "serde", "time", ] @@ -3516,7 +3315,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "crc32fast", "fdeflate", "flate2", @@ -3537,22 +3336,11 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash", -] - [[package]] name = "portable-atomic" -version = "1.14.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "potential_utf" @@ -3584,6 +3372,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -3610,7 +3408,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.13+spec-1.1.0", + "toml_edit 0.25.10+spec-1.1.0", ] [[package]] @@ -3638,88 +3436,58 @@ dependencies = [ ] [[package]] -name = "proc-macro2" -version = "1.0.107" +name = "proc-macro-hack" +version = "0.5.20+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] -name = "quick-xml" -version = "0.41.0" +name = "pxfm" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" + +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" dependencies = [ "memchr", ] [[package]] -name = "quinn" -version = "0.11.11" +name = "quick-xml" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ - "bytes", - "cfg_aliases", - "futures-io", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror 2.0.19", - "tokio", - "tracing", - "web-time", + "memchr", ] [[package]] -name = "quinn-proto" -version = "0.11.16" +name = "quick-xml" +version = "0.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" dependencies = [ - "aws-lc-rs", - "bytes", - "fastbloom", - "getrandom 0.4.3", - "lru-slab", - "rand 0.10.2", - "rand_pcg", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", - "slab", - "thiserror 2.0.19", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.52.0", + "memchr", ] [[package]] name = "quote" -version = "1.0.47" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -3738,33 +3506,56 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.9.5" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "rand_chacha", - "rand_core 0.9.5", + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", ] [[package]] name = "rand" -version = "0.10.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "chacha20 0.10.1", - "getrandom 0.4.3", - "rand_core 0.10.1", + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", ] [[package]] name = "rand_chacha" -version = "0.9.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", - "rand_core 0.9.5", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", ] [[package]] @@ -3777,27 +3568,21 @@ dependencies = [ ] [[package]] -name = "rand_core" -version = "0.9.5" +name = "rand_hc" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "getrandom 0.3.4", + "rand_core 0.5.1", ] -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - [[package]] name = "rand_pcg" -version = "0.10.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" dependencies = [ - "rand_core 0.10.1", + "rand_core 0.5.1", ] [[package]] @@ -3806,28 +3591,22 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" -[[package]] -name = "rcgen" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" -dependencies = [ - "aws-lc-rs", - "pem", - "ring", - "rustls-pki-types", - "time", - "x509-parser", - "yasna", -] - [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags 2.11.0", ] [[package]] @@ -3838,34 +3617,34 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror 2.0.19", + "thiserror 2.0.18", ] [[package]] name = "ref-cast" -version = "1.0.26" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.26" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 2.0.117", ] [[package]] name = "regex" -version = "1.13.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -3875,9 +3654,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.18" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -3886,15 +3665,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.11" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.13.4" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64 0.22.1", "bytes", @@ -3904,21 +3683,15 @@ dependencies = [ "http-body", "http-body-util", "hyper", - "hyper-rustls", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", "serde", "serde_json", "sync_wrapper", "tokio", - "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -3940,7 +3713,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted 0.9.0", + "untrusted", "windows-sys 0.52.0", ] @@ -3956,9 +3729,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.3" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -3969,35 +3742,25 @@ dependencies = [ "semver", ] -[[package]] -name = "rusticata-macros" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" -dependencies = [ - "nom", -] - [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.43" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ - "aws-lc-rs", "log", "once_cell", "ring", @@ -4007,78 +3770,31 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-native-certs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", -] - [[package]] name = "rustls-pki-types" -version = "1.15.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ - "web-time", "zeroize", ] -[[package]] -name = "rustls-platform-verifier" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" -dependencies = [ - "core-foundation", - "core-foundation-sys", - "jni 0.22.4", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ - "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] name = "rustversion" -version = "1.0.23" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "same-file" @@ -4089,15 +3805,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "schemars" version = "0.8.22" @@ -4127,13 +3834,13 @@ dependencies = [ [[package]] name = "schemars" -version = "1.2.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ "dyn-clone", "ref-cast", - "schemars_derive 1.2.2", + "schemars_derive 1.2.1", "serde", "serde_json", "url", @@ -4148,20 +3855,20 @@ checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" dependencies = [ "proc-macro2", "quote", - "serde_derive_internals 0.29.1", - "syn 2.0.119", + "serde_derive_internals", + "syn 2.0.117", ] [[package]] name = "schemars_derive" -version = "1.2.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" dependencies = [ "proc-macro2", "quote", - "serde_derive_internals 0.30.0", - "syn 3.0.3", + "serde_derive_internals", + "syn 2.0.117", ] [[package]] @@ -4171,26 +3878,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "security-framework" -version = "3.7.0" +name = "selectors" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" dependencies = [ - "bitflags 2.13.1", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", + "bitflags 1.3.2", + "cssparser 0.29.6", + "derive_more 0.99.20", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc 0.2.0", + "smallvec", ] [[package]] @@ -4199,16 +3901,16 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" dependencies = [ - "bitflags 2.13.1", - "cssparser", - "derive_more", + "bitflags 2.11.0", + "cssparser 0.36.0", + "derive_more 2.1.1", "log", "new_debug_unreachable", - "phf", - "phf_codegen", + "phf 0.13.1", + "phf_codegen 0.13.1", "precomputed-hash", "rustc-hash", - "servo_arc", + "servo_arc 0.4.3", "smallvec", ] @@ -4224,9 +3926,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.229" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -4246,22 +3948,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.229" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.229" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 2.0.117", ] [[package]] @@ -4272,27 +3974,16 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", -] - -[[package]] -name = "serde_derive_internals" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", + "syn 2.0.117", ] [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.13.1", "itoa", "memchr", "serde", @@ -4302,13 +3993,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.21" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 2.0.117", ] [[package]] @@ -4331,18 +4022,17 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.21.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" dependencies = [ "base64 0.22.1", - "bs58", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.14.0", + "indexmap 2.13.1", "schemars 0.9.0", - "schemars 1.2.2", + "schemars 1.2.1", "serde_core", "serde_json", "serde_with_macros", @@ -4351,27 +4041,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.21.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.119", -] - -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap 2.14.0", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", + "syn 2.0.117", ] [[package]] @@ -4393,7 +4070,17 @@ checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", +] + +[[package]] +name = "servo_arc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +dependencies = [ + "nodrop", + "stable_deref_trait", ] [[package]] @@ -4405,6 +4092,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sha2" version = "0.10.9" @@ -4412,47 +4105,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "digest 0.11.3", -] - -[[package]] -name = "sha3" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" -dependencies = [ - "digest 0.10.7", - "keccak 0.1.6", -] - -[[package]] -name = "shake" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" -dependencies = [ - "digest 0.11.3", - "keccak 0.2.1", - "sponge-cursor", + "cpufeatures", + "digest", ] [[package]] name = "shlex" -version = "2.0.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" @@ -4464,43 +4125,23 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" -dependencies = [ - "digest 0.11.3", - "rand_core 0.10.1", -] - [[package]] name = "simd-adler32" -version = "0.3.10" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" - -[[package]] -name = "simd_cesu8" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" -dependencies = [ - "rustc_version", - "simdutf8", -] - -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "siphasher" -version = "1.0.3" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "slab" @@ -4510,20 +4151,31 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.5" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", "windows-sys 0.61.2", ] +[[package]] +name = "socks" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" +dependencies = [ + "byteorder", + "libc", + "winapi", +] + [[package]] name = "softbuffer" version = "0.4.8" @@ -4539,7 +4191,7 @@ dependencies = [ "objc2-foundation", "objc2-quartz-core", "raw-window-handle", - "redox_syscall", + "redox_syscall 0.5.18", "tracing", "wasm-bindgen", "web-sys", @@ -4572,28 +4224,25 @@ dependencies = [ "system-deps", ] -[[package]] -name = "spki" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "sponge-cursor" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" - [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + [[package]] name = "string_cache" version = "0.9.0" @@ -4602,18 +4251,30 @@ checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared", + "phf_shared 0.13.1", "precomputed-hash", ] +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + [[package]] name = "string_cache_codegen" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", ] @@ -4646,16 +4307,6 @@ name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ "proc-macro2", "quote", @@ -4664,9 +4315,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.3" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -4690,7 +4341,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -4708,53 +4359,62 @@ dependencies = [ [[package]] name = "tao" -version = "0.35.3" +version = "0.34.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" +checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "block2", "core-foundation", "core-graphics", "crossbeam-channel", - "dbus", - "dispatch2", + "dispatch2 0.3.1", "dlopen2", "dpi", "gdkwayland-sys", "gdkx11-sys", "gtk", - "jni 0.21.1", + "jni", "libc", "log", "ndk", + "ndk-context", "ndk-sys", "objc2", "objc2-app-kit", "objc2-foundation", - "objc2-ui-kit", "once_cell", "parking_lot", - "percent-encoding", "raw-window-handle", "tao-macros", "unicode-segmentation", "url", - "windows", - "windows-core", + "windows 0.61.3", + "windows-core 0.61.2", "windows-version", "x11-dl", ] [[package]] name = "tao-macros" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7eeb6d99155545da6150a1795945f16ac9c178deb2a5f2e74d776107bd5849" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", +] + +[[package]] +name = "tar" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +dependencies = [ + "filetime", + "libc", + "xattr", ] [[package]] @@ -4765,8 +4425,8 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.11.5" -source = "git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41#4af26a3f7f8b692d62cca549bbacd93f5ce90b41" +version = "2.10.3" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#37deabe7f13f1d32f4076723610de6b6b62865fc" dependencies = [ "anyhow", "bytes", @@ -4779,7 +4439,7 @@ dependencies = [ "gtk", "heck 0.5.0", "http", - "jni 0.21.1", + "jni", "libc", "log", "mime", @@ -4798,50 +4458,74 @@ dependencies = [ "serde_repr", "serialize-to-javascript", "swift-rs", - "tauri-build", + "tauri-build 2.5.6 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", "tauri-macros", "tauri-runtime", + "tauri-runtime-cef", "tauri-runtime-wry", - "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41)", - "thiserror 2.0.19", + "tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", + "thiserror 2.0.18", "tokio", "tray-icon", "url", "webkit2gtk", "webview2-com", "window-vibrancy", - "windows", + "windows 0.61.3", ] [[package]] name = "tauri-build" -version = "2.6.3" -source = "git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41#4af26a3f7f8b692d62cca549bbacd93f5ce90b41" +version = "2.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bbc990d1dbf57a8e1c7fa2327f2a614d8b757805603c1b9ba5c81bade09fd4d" dependencies = [ "anyhow", "cargo_toml", "dirs", "glob", "heck 0.5.0", - "json-patch 4.2.0", - "schemars 1.2.2", + "json-patch", + "schemars 0.8.22", "semver", "serde", "serde_json", - "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41)", + "tauri-utils 2.8.3 (registry+https://github.com/rust-lang/crates.io-index)", "tauri-winres", + "toml 0.9.12+spec-1.1.0", + "walkdir", +] + +[[package]] +name = "tauri-build" +version = "2.5.6" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#37deabe7f13f1d32f4076723610de6b6b62865fc" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 1.2.1", + "semver", + "serde", + "serde_json", + "tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", + "tauri-winres", + "toml 0.9.12+spec-1.1.0", "walkdir", ] [[package]] name = "tauri-codegen" -version = "2.6.3" -source = "git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41#4af26a3f7f8b692d62cca549bbacd93f5ce90b41" +version = "2.5.5" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#37deabe7f13f1d32f4076723610de6b6b62865fc" dependencies = [ "base64 0.22.1", "brotli", "ico", - "json-patch 4.2.0", + "json-patch", "plist", "png 0.17.16", "proc-macro2", @@ -4849,10 +4533,10 @@ dependencies = [ "semver", "serde", "serde_json", - "sha2 0.10.9", - "syn 2.0.119", - "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41)", - "thiserror 2.0.19", + "sha2", + "syn 2.0.117", + "tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", + "thiserror 2.0.18", "time", "url", "uuid", @@ -4861,22 +4545,22 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.6.3" -source = "git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41#4af26a3f7f8b692d62cca549bbacd93f5ce90b41" +version = "2.5.5" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#37deabe7f13f1d32f4076723610de6b6b62865fc" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", "tauri-codegen", - "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41)", + "tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", ] [[package]] name = "tauri-plugin" -version = "2.6.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74be5dd4bed9afbd145e5716b5fa2ec28cbc29c34ffa61c258c9273d896c8020" +checksum = "ddde7d51c907b940fb573006cdda9a642d6a7c8153657e88f8a5c3c9290cd4aa" dependencies = [ "anyhow", "glob", @@ -4884,15 +4568,42 @@ dependencies = [ "schemars 0.8.22", "serde", "serde_json", - "tauri-utils 2.9.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tauri-utils 2.8.3 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.9.12+spec-1.1.0", "walkdir", ] [[package]] -name = "tauri-plugin-deep-link" -version = "2.4.9" +name = "tauri-plugin-app-events" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ee75bc5627f77bfdf40c913255ebc258117b10ebe2b2239a1a1cf40b0b58aa" +checksum = "4e625f35abcfc422c7c4af8b348d2069cdf387a39c12429fdce570137d02f069" +dependencies = [ + "serde", + "tauri", + "tauri-plugin", + "thiserror 1.0.69", +] + +[[package]] +name = "tauri-plugin-barcode-scanner" +version = "2.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "485cbcf227f04117e930be748ea71d835900466dcd1d455d5ec284d36107a305" +dependencies = [ + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", +] + +[[package]] +name = "tauri-plugin-deep-link" +version = "2.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db49816aee496a9b200d55b55ab6ae73fd50847c79f2fabc7ee20871fa75c95" dependencies = [ "dunce", "plist", @@ -4901,59 +4612,19 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "tauri-utils 2.9.3 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 2.0.19", + "tauri-utils 2.8.3 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 2.0.18", "tracing", "url", "windows-registry", - "windows-result", -] - -[[package]] -name = "tauri-plugin-log" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6792296e6f389268016c77db21ebae1fc0568f2fccf88b1ec7e2ea71330afb4c" -dependencies = [ - "android_logger", - "fern", - "log", - "objc2", - "objc2-foundation", - "serde", - "serde_json", - "serde_repr", - "swift-rs", - "tauri", - "tauri-plugin", - "thiserror 2.0.19", - "time", -] - -[[package]] -name = "tauri-plugin-notification" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01fc2c5ff41105bd1f7242d8201fdf3efd70749b82fa013a17f2126357d194cc" -dependencies = [ - "log", - "notify-rust", - "rand 0.9.5", - "serde", - "serde_json", - "serde_repr", - "tauri", - "tauri-plugin", - "thiserror 2.0.19", - "time", - "url", + "windows-result 0.3.4", ] [[package]] name = "tauri-plugin-opener" -version = "2.5.4" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17e1bea14edce6b793a04e2417e3fd924b9bc4faae83cdee7d714156cceeed29" +checksum = "fc624469b06f59f5a29f874bbc61a2ed737c0f9c23ef09855a292c389c42e83f" dependencies = [ "dunce", "glob", @@ -4965,41 +4636,69 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "thiserror 2.0.19", + "thiserror 2.0.18", "url", - "windows", + "windows 0.61.3", "zbus", ] [[package]] name = "tauri-runtime" -version = "2.11.3" -source = "git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41#4af26a3f7f8b692d62cca549bbacd93f5ce90b41" +version = "2.10.1" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#37deabe7f13f1d32f4076723610de6b6b62865fc" dependencies = [ "cookie", "dpi", "gtk", "http", - "jni 0.21.1", + "jni", "objc2", "objc2-ui-kit", "raw-window-handle", "serde", "serde_json", - "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41)", - "thiserror 2.0.19", + "tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", + "thiserror 2.0.18", "url", - "windows", + "windows 0.61.3", +] + +[[package]] +name = "tauri-runtime-cef" +version = "0.1.0" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#37deabe7f13f1d32f4076723610de6b6b62865fc" +dependencies = [ + "base64 0.22.1", + "cef", + "cef-dll-sys", + "dioxus-debug-cell", + "dirs", + "gtk", + "html5ever 0.29.1", + "http", + "kuchikiki", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "raw-window-handle", + "serde", + "serde_json", + "sha2", + "tauri-runtime", + "tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", + "url", + "windows 0.61.3", + "x11-dl", ] [[package]] name = "tauri-runtime-wry" -version = "2.11.4" -source = "git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41#4af26a3f7f8b692d62cca549bbacd93f5ce90b41" +version = "2.10.1" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#37deabe7f13f1d32f4076723610de6b6b62865fc" dependencies = [ "gtk", "http", - "jni 0.21.1", + "jni", "log", "objc2", "objc2-app-kit", @@ -5010,33 +4709,33 @@ dependencies = [ "softbuffer", "tao", "tauri-runtime", - "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41)", + "tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.61.3", "wry", ] [[package]] name = "tauri-utils" -version = "2.9.3" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" +checksum = "219a1f983a2af3653f75b5747f76733b0da7ff03069c7a41901a5eb3ace4557d" dependencies = [ "anyhow", "cargo_metadata", - "ctor 0.8.0", - "dom_query", + "ctor", "dunce", "glob", + "html5ever 0.29.1", "http", "infer", - "json-patch 3.0.1", + "json-patch", + "kuchikiki", "log", "memchr", - "phf", - "plist", + "phf 0.11.3", "proc-macro2", "quote", "regex", @@ -5047,70 +4746,60 @@ dependencies = [ "serde_json", "serde_with", "swift-rs", - "thiserror 2.0.19", - "toml 1.1.4+spec-1.1.0", + "thiserror 2.0.18", + "toml 0.9.12+spec-1.1.0", "url", - "urlpattern 0.3.0", + "urlpattern", "uuid", + "walkdir", ] [[package]] name = "tauri-utils" -version = "2.9.3" -source = "git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41#4af26a3f7f8b692d62cca549bbacd93f5ce90b41" +version = "2.8.3" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#37deabe7f13f1d32f4076723610de6b6b62865fc" dependencies = [ "anyhow", "brotli", "cargo_metadata", - "ctor 1.0.12", - "dom_query", + "ctor", "dunce", "glob", + "html5ever 0.29.1", "http", "infer", - "json-patch 4.2.0", + "json-patch", + "kuchikiki", "log", "memchr", - "phf", - "plist", + "phf 0.11.3", "proc-macro2", "quote", "regex", - "schemars 1.2.2", + "schemars 1.2.1", "semver", "serde", "serde-untagged", "serde_json", "serde_with", "swift-rs", - "thiserror 2.0.19", - "toml 1.1.4+spec-1.1.0", + "thiserror 2.0.18", + "toml 0.9.12+spec-1.1.0", "url", - "urlpattern 0.6.0", + "urlpattern", "uuid", "walkdir", ] [[package]] name = "tauri-winres" -version = "0.3.6" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" +checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" dependencies = [ "dunce", "embed-resource", - "toml 1.1.4+spec-1.1.0", -] - -[[package]] -name = "tauri-winrt-notification" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed071c670382e85fc2f48ae706492d8c338f4f89bf72520d32f8abfe880aade" -dependencies = [ - "thiserror 2.0.19", - "windows", - "windows-version", + "toml 0.9.12+spec-1.1.0", ] [[package]] @@ -5120,39 +4809,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "tendril" -version = "0.5.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "tendril" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4790fc369d5a530f4b544b094e31388b9b3a37c0f4652ade4505945f5660d24" dependencies = [ "new_debug_unreachable", + "utf-8", ] [[package]] name = "tensamin" -version = "0.0.0" +version = "0.1.0" dependencies = [ "base64 0.22.1", - "jni 0.22.4", - "mtp", - "reqwest", + "image 0.25.10", "serde", "serde_json", "tauri", - "tauri-build", + "tauri-build 2.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tauri-plugin-app-events", + "tauri-plugin-barcode-scanner", "tauri-plugin-deep-link", - "tauri-plugin-log", - "tauri-plugin-notification", "tauri-plugin-opener", - "tokio", - "webpki-root-certs", + "xcap", ] [[package]] @@ -5166,11 +4864,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.19", + "thiserror-impl 2.0.18", ] [[package]] @@ -5181,30 +4879,29 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 2.0.117", ] [[package]] name = "time" -version = "0.3.55" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", - "libc", + "itoa", "num-conv", - "num_threads", "powerfmt", "serde_core", "time-core", @@ -5213,15 +4910,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.9" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.32" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -5246,80 +4943,29 @@ dependencies = [ "zerovec", ] -[[package]] -name = "tinyvec" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" -version = "1.53.1" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" dependencies = [ "bytes", "libc", "mio", - "parking_lot", "pin-project-lite", - "signal-hook-registry", "socket2", - "tokio-macros", "windows-sys 0.61.2", ] -[[package]] -name = "tokio-macros" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" -version = "0.7.19" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", "futures-sink", - "libc", "pin-project-lite", "tokio", ] @@ -5338,17 +4984,17 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.4+spec-1.1.0" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.13.1", "serde_core", "serde_spanned 1.1.1", - "toml_datetime 1.1.1+spec-1.1.0", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 1.0.4", + "winnow 0.7.15", ] [[package]] @@ -5360,6 +5006,15 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -5375,7 +5030,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.13.1", "toml_datetime 0.6.3", "winnow 0.5.40", ] @@ -5386,7 +5041,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.13.1", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.3", @@ -5395,30 +5050,30 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.13+spec-1.1.0" +version = "0.25.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +checksum = "a82418ca169e235e6c399a84e395ab6debeb3bc90edc959bf0f48647c6a32d1b" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.13.1", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.4", + "winnow 1.0.1", ] [[package]] name = "toml_parser" -version = "1.1.3+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.4", + "winnow 1.0.1", ] [[package]] name = "toml_writer" -version = "1.1.2+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tower" @@ -5437,20 +5092,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.11" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.11.0", "bytes", "futures-util", "http", "http-body", + "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", - "url", ] [[package]] @@ -5471,7 +5126,6 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -5485,7 +5139,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -5499,9 +5153,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.24.2" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045979e3f037cd18ad1cb2a419dfda133c5c29c9f3453370079f2255d46c257e" +checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" dependencies = [ "crossbeam-channel", "dirs", @@ -5513,10 +5167,10 @@ dependencies = [ "objc2-core-graphics", "objc2-foundation", "once_cell", - "png 0.18.1", + "png 0.17.16", "serde", - "thiserror 2.0.19", - "windows-sys 0.61.2", + "thiserror 2.0.18", + "windows-sys 0.60.2", ] [[package]] @@ -5533,9 +5187,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.20.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "uds_windows" @@ -5597,31 +5251,27 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.3" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] -name = "universal-hash" -version = "0.5.1" +name = "unicode-width" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common 0.1.7", - "subtle", -] +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] -name = "unsafe-libyaml" -version = "0.2.11" +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] -name = "untrusted" -version = "0.7.1" +name = "unit-prefix" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" [[package]] name = "untrusted" @@ -5629,6 +5279,39 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" +dependencies = [ + "base64 0.22.1", + "cookie_store", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "socks", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +dependencies = [ + "base64 0.22.1", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -5655,16 +5338,16 @@ dependencies = [ ] [[package]] -name = "urlpattern" -version = "0.6.0" +name = "utf-8" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df16f50ef4cc145211879a3867ba757076b25dfee812040dcb0658bd9ae7904b" -dependencies = [ - "icu_properties", - "regex", - "serde", - "url", -] +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" [[package]] name = "utf8_iter" @@ -5673,12 +5356,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] -name = "uuid" -version = "1.24.0" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ - "getrandom 0.4.3", + "getrandom 0.4.2", "js-sys", "serde_core", "wasm-bindgen", @@ -5735,6 +5424,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -5743,18 +5438,27 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.4+wasi-0.2.12" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" dependencies = [ "cfg-if", "once_cell", @@ -5765,9 +5469,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.76" +version = "0.4.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" dependencies = [ "js-sys", "wasm-bindgen", @@ -5775,9 +5479,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5785,26 +5489,48 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.1", + "wasm-encoder", + "wasmparser", +] + [[package]] name = "wasm-streams" version = "0.5.0" @@ -5819,10 +5545,92 @@ dependencies = [ ] [[package]] -name = "web-sys" -version = "0.3.103" +name = "wasmparser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap 2.13.1", + "semver", +] + +[[package]] +name = "wayland-backend" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +dependencies = [ + "bitflags 2.11.0", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +dependencies = [ + "proc-macro2", + "quick-xml 0.39.2", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" dependencies = [ "js-sys", "wasm-bindgen", @@ -5840,14 +5648,14 @@ dependencies = [ [[package]] name = "web_atoms" -version = "0.2.5" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "075474b12bcb3d2e3d4546580e9de478eeeead668a1761e2a8860c836b7ef297" +checksum = "57a9779e9f04d2ac1ce317aee707aa2f6b773afba7b931222bff6983843b1576" dependencies = [ - "phf", - "phf_codegen", - "string_cache", - "string_cache_codegen", + "phf 0.13.1", + "phf_codegen 0.13.1", + "string_cache 0.9.0", + "string_cache_codegen 0.6.1", ] [[package]] @@ -5895,10 +5703,10 @@ dependencies = [ ] [[package]] -name = "webpki-root-certs" -version = "1.0.9" +name = "webpki-roots" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] @@ -5911,9 +5719,9 @@ checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows", - "windows-core", - "windows-implement", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-implement 0.60.2", "windows-interface", ] @@ -5925,7 +5733,7 @@ checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -5934,11 +5742,17 @@ version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" dependencies = [ - "thiserror 2.0.19", - "windows", - "windows-core", + "thiserror 2.0.18", + "windows 0.61.3", + "windows-core 0.61.2", ] +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "winapi" version = "0.3.9" @@ -5961,7 +5775,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5985,17 +5799,39 @@ dependencies = [ "windows-version", ] +[[package]] +name = "windows" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf874e74c7a99773e62b1c671427abf01a425e77c3d3fb9fb1e4883ea934529" +dependencies = [ + "windows-collections 0.1.1", + "windows-core 0.60.1", + "windows-future 0.1.1", + "windows-link 0.1.3", + "windows-numerics 0.1.1", +] + [[package]] name = "windows" version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-collections", - "windows-core", - "windows-future", + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", "windows-link 0.1.3", - "windows-numerics", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows-collections" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5467f79cc1ba3f52ebb2ed41dbb459b8e7db636cc3429458d9a852e15bc24dec" +dependencies = [ + "windows-core 0.60.1", ] [[package]] @@ -6004,7 +5840,20 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core", + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.60.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247" +dependencies = [ + "windows-implement 0.59.0", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.3.1", ] [[package]] @@ -6013,11 +5862,34 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement", + "windows-implement 0.60.2", "windows-interface", "windows-link 0.1.3", - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a787db4595e7eb80239b74ce8babfb1363d8e343ab072f2ffe901400c03349f0" +dependencies = [ + "windows-core 0.60.1", + "windows-link 0.1.3", ] [[package]] @@ -6026,11 +5898,22 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", "windows-threading", ] +[[package]] +name = "windows-implement" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "windows-implement" version = "0.60.2" @@ -6039,7 +5922,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -6050,7 +5933,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] @@ -6065,13 +5948,23 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005dea54e2f6499f2cee279b8f703b3cf3b5734a2d8d21867c8f44003182eeed" +dependencies = [ + "windows-core 0.60.1", + "windows-link 0.1.3", +] + [[package]] name = "windows-numerics" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", ] @@ -6082,8 +5975,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ "windows-link 0.1.3", - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -6095,6 +5988,24 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -6104,6 +6015,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -6131,6 +6051,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -6164,13 +6093,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + [[package]] name = "windows-threading" version = "0.1.0" @@ -6201,6 +6147,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -6213,6 +6165,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -6225,12 +6183,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -6243,6 +6213,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -6255,6 +6231,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -6267,6 +6249,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -6279,6 +6267,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.5.40" @@ -6290,9 +6284,18 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.4" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" dependencies = [ "memchr", ] @@ -6309,9 +6312,91 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.57.1" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.13.1", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap 2.13.1", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.1", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" @@ -6321,9 +6406,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wry" -version = "0.55.1" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" +checksum = "e5a8135d8676225e5744de000d4dff5a082501bf7db6a1c1495034f8c314edbc" dependencies = [ "base64 0.22.1", "block2", @@ -6337,7 +6422,7 @@ dependencies = [ "gtk", "http", "javascriptcore-rs", - "jni 0.21.1", + "jni", "libc", "ndk", "objc2", @@ -6349,56 +6434,20 @@ dependencies = [ "once_cell", "percent-encoding", "raw-window-handle", - "sha2 0.10.9", + "sha2", "soup3", "tao-macros", - "thiserror 2.0.19", + "thiserror 2.0.18", "url", "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows", - "windows-core", + "windows 0.61.3", + "windows-core 0.61.2", "windows-version", "x11-dl", ] -[[package]] -name = "wtransport" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4aacf790813ee1956751491800537f4e04af7557b7b370501ccbfbc85963e4" -dependencies = [ - "bytes", - "pem", - "quinn", - "rcgen", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "sha2 0.11.0", - "socket2", - "thiserror 2.0.19", - "time", - "tokio", - "tracing", - "url", - "wtransport-proto", - "x509-parser", -] - -[[package]] -name = "wtransport-proto" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5867c629e4252f7439d82315923daaf27f4fa442410d51b78ab93ef4c432a11" -dependencies = [ - "httlib-huffman", - "octets", - "thiserror 2.0.19", - "url", -] - [[package]] name = "x11" version = "2.21.0" @@ -6421,51 +6470,59 @@ dependencies = [ ] [[package]] -name = "x25519-dalek" -version = "2.0.1" +name = "xattr" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ - "curve25519-dalek 4.1.3", - "rand_core 0.6.4", - "serde", - "zeroize", + "libc", + "rustix", ] [[package]] -name = "x509-parser" -version = "0.18.1" +name = "xcap" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +checksum = "eabd25cdb442bb7f63f13fdee2f59d991b04668d37c69aee00dc2a1cc9d0e9a1" dependencies = [ - "asn1-rs", - "aws-lc-rs", - "data-encoding", - "der-parser", + "dbus", + "dispatch2 0.2.0", + "image 0.25.10", "lazy_static", - "nom", - "oid-registry", - "ring", - "rusticata-macros", - "thiserror 2.0.19", - "time", + "libwayshot", + "log", + "objc2", + "objc2-app-kit", + "objc2-av-foundation", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-media", + "objc2-core-video", + "objc2-foundation", + "percent-encoding", + "scopeguard", + "thiserror 2.0.18", + "widestring", + "windows 0.60.0", + "xcb", ] [[package]] -name = "yasna" -version = "0.6.0" +name = "xcb" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" +checksum = "ee4c580d8205abb0a5cf4eb7e927bd664e425b6c3263f9c5310583da96970cf6" dependencies = [ - "bit-vec 0.9.1", - "time", + "bitflags 1.3.2", + "libc", + "quick-xml 0.30.0", ] [[package]] name = "yoke" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -6480,15 +6537,15 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", "synstructure", ] [[package]] name = "zbus" -version = "5.18.0" +version = "5.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe18fb60dc696039e738717b76eaea21e7a4489bbb1885020b43c94236d7e98a" +checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc" dependencies = [ "async-broadcast", "async-executor", @@ -6513,7 +6570,7 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow 1.0.4", + "winnow 0.7.15", "zbus_macros", "zbus_names", "zvariant", @@ -6521,14 +6578,14 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.18.0" +version = "5.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe96480bed92df2b442a1a30df364e12d08eed03aeb061f2b8dc6afb2be91119" +checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222" dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", "zbus_names", "zvariant", "zvariant_utils", @@ -6536,40 +6593,40 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.3.4" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" dependencies = [ "serde", - "winnow 1.0.4", + "winnow 0.7.15", "zvariant", ] [[package]] name = "zerocopy" -version = "0.8.55" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.55" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] name = "zerofrom" -version = "0.1.8" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] @@ -6582,29 +6639,15 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", "synstructure", ] [[package]] name = "zeroize" -version = "1.9.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" @@ -6636,51 +6679,66 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", ] [[package]] name = "zmij" -version = "1.0.23" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] [[package]] name = "zvariant" -version = "5.13.1" +version = "5.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee2a0bcd2a907786a456fff45aaaaf54c9ba5f50b71ae9ec1a4edd200c94911" +checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b" dependencies = [ "endi", "enumflags2", "serde", - "winnow 1.0.4", + "winnow 0.7.15", "zvariant_derive", "zvariant_utils", ] [[package]] name = "zvariant_derive" -version = "5.13.1" +version = "5.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38a708216a18780796770bfe3f4739c7c83a3e8f789b755534bbbc06e4e23e12" +checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c" dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.117", "zvariant_utils", ] [[package]] name = "zvariant_utils" -version = "3.5.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90cb9383f9b45290407a1258b202d3f8f01db719eb60b4e4055c6375af4fc7c7" +checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.119", - "winnow 1.0.4", + "syn 2.0.117", + "winnow 0.7.15", ] diff --git a/apps/tauri/src-tauri/Cargo.toml b/apps/tauri/src-tauri/Cargo.toml index 2a68e95..90b9504 100644 --- a/apps/tauri/src-tauri/Cargo.toml +++ b/apps/tauri/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tensamin" -version = "0.0.0" +version = "0.1.0" description = "Privacy focused messanger" authors = ["methanium"] edition = "2021" @@ -15,34 +15,43 @@ name = "mobile_lib" crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] -tauri-build = { git = "https://github.com/tauri-apps/tauri", rev = "4af26a3f7f8b692d62cca549bbacd93f5ce90b41", features = [] } +tauri-build = { version = "2", features = [] } [dependencies] +base64 = "0.22" +image = { version = "0.25", default-features = false, features = ["jpeg"] } tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" -base64 = "0.22" -reqwest = { version = "0.13", default-features = false, features = ["json", "rustls"] } -tokio = { version = "1", features = ["rt-multi-thread", "sync", "time"] } -mtp = { git = "https://git.methanium.net/Methanium/mtp.git", rev = "a5c8d4f0c898c78351e9d54124886c86e789a22a", features = ["client", "crypto"] } -webpki-root-certs = "1" tauri-plugin-deep-link = "2" -tauri-plugin-notification = "2" -tauri-plugin-log = "2" + +[target.'cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))'.dependencies] +xcap = "0.4.1" [target.'cfg(target_os = "android")'.dependencies.tauri] version = "2" features = [] default-features = true -[target.'cfg(not(target_os = "android"))'.dependencies.tauri] +[target.'cfg(target_os = "windows")'.dependencies.tauri] version = "2" -features = [] +features = ["compression", "common-controls-v6", "dynamic-acl"] default-features = true -[target.'cfg(target_os = "android")'.dependencies] -jni = "0.22" +[target.'cfg(target_os = "linux")'.dependencies.tauri] +version = "2" +features = ["common-controls-v6", "cef", "compression", "dynamic-acl", "x11"] +default-features = false + +[target.'cfg(target_os = "macos")'.dependencies.tauri] +version = "2" +features = ["x11", "common-controls-v6", "cef", "compression", "dynamic-acl"] +default-features = false + +[target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] +tauri-plugin-barcode-scanner = "2" +tauri-plugin-app-events = "0.2" [patch.crates-io.tauri] git = "https://github.com/tauri-apps/tauri" -rev = "4af26a3f7f8b692d62cca549bbacd93f5ce90b41" +branch = "feat/cef" diff --git a/apps/tauri/src-tauri/capabilities/default.json b/apps/tauri/src-tauri/capabilities/default.json index 83f7620..1f5ace4 100644 --- a/apps/tauri/src-tauri/capabilities/default.json +++ b/apps/tauri/src-tauri/capabilities/default.json @@ -6,9 +6,14 @@ "main" ], "permissions": [ + "core:default", "opener:default", - "deep-link:default", - "notification:default", - "log:default" + "core:window:default", + "core:window:allow-start-dragging", + "core:window:allow-close", + "core:window:allow-toggle-maximize", + "core:window:allow-minimize", + "core:event:default", + "deep-link:default" ] -} +} \ No newline at end of file diff --git a/apps/tauri/src-tauri/capabilities/mobile.json b/apps/tauri/src-tauri/capabilities/mobile.json index 91f1d4e..83f76e0 100644 --- a/apps/tauri/src-tauri/capabilities/mobile.json +++ b/apps/tauri/src-tauri/capabilities/mobile.json @@ -1,11 +1,17 @@ { "identifier": "mobile-capability", - "platforms": ["android", "iOS"], - "windows": ["main"], + "platforms": [ + "android", + "iOS" + ], + "windows": [ + "main" + ], "permissions": [ - "core:event:default", "deep-link:default", - "notification:default", - "log:default" + "app-events:default", + "barcode-scanner:default", + "barcode-scanner:allow-scan", + "barcode-scanner:allow-cancel" ] } diff --git a/apps/tauri/src-tauri/gen/android/app/build.gradle.kts b/apps/tauri/src-tauri/gen/android/app/build.gradle.kts index 66d6289..f13d1b3 100644 --- a/apps/tauri/src-tauri/gen/android/app/build.gradle.kts +++ b/apps/tauri/src-tauri/gen/android/app/build.gradle.kts @@ -1,4 +1,3 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget import java.util.Properties import java.io.FileInputStream @@ -41,7 +40,6 @@ android { } buildTypes { getByName("debug") { - applicationIdSuffix = ".dev" manifestPlaceholders["usesCleartextTraffic"] = "true" isDebuggable = true isJniDebuggable = true @@ -62,29 +60,26 @@ android { ) } } + kotlinOptions { + jvmTarget = "1.8" + } buildFeatures { buildConfig = true } } -kotlin { - compilerOptions { - jvmTarget = JvmTarget.JVM_1_8 - } -} - rust { rootDirRel = "../../../" } dependencies { - implementation("androidx.webkit:webkit:1.16.0") + implementation("androidx.webkit:webkit:1.14.0") implementation("androidx.appcompat:appcompat:1.7.1") - implementation("androidx.activity:activity-ktx:1.13.0") - implementation("com.google.android.material:material:1.14.0") + implementation("androidx.activity:activity-ktx:1.10.1") + implementation("com.google.android.material:material:1.12.0") testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.3.0") - androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") + androidTestImplementation("androidx.test.ext:junit:1.1.4") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") } -apply(from = "tauri.build.gradle.kts") +apply(from = "tauri.build.gradle.kts") \ No newline at end of file diff --git a/apps/tauri/src-tauri/gen/android/app/proguard-rules.pro b/apps/tauri/src-tauri/gen/android/app/proguard-rules.pro index f1b4245..481bb43 100644 --- a/apps/tauri/src-tauri/gen/android/app/proguard-rules.pro +++ b/apps/tauri/src-tauri/gen/android/app/proguard-rules.pro @@ -18,4 +18,4 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/AndroidManifest.xml b/apps/tauri/src-tauri/gen/android/app/src/main/AndroidManifest.xml index 7d05c07..697b23b 100644 --- a/apps/tauri/src-tauri/gen/android/app/src/main/AndroidManifest.xml +++ b/apps/tauri/src-tauri/gen/android/app/src/main/AndroidManifest.xml @@ -1,16 +1,6 @@ - - - - - - - - - - @@ -49,30 +39,6 @@ - - - - - - - - - - - - - val includeAudio = pendingScreenAudio - pendingScreenAudio = null - - if (includeAudio == null) return@registerForActivityResult - val data = result.data - if (result.resultCode != Activity.RESULT_OK || data == null) { - MobileMediaEvents.emitError("Screen capture permission was denied") - return@registerForActivityResult - } - - MediaProjectionService.start(this, result.resultCode, data, includeAudio) - } - - private val cameraPermissionLauncher = registerForActivityResult( - ActivityResultContracts.RequestMultiplePermissions(), - ) { - emitCameraPermission() - } - - private val screenAudioPermissionLauncher = registerForActivityResult( - ActivityResultContracts.RequestPermission(), - ) { - launchScreenCaptureIntent() - } - - override fun onWebViewCreate(webView: WebView) { - webView.settings.apply { - setSupportZoom(false) - builtInZoomControls = false - displayZoomControls = false - } - - var blockingMultiTouch = false - webView.setOnTouchListener { _, event -> - val shouldBlock = blockingMultiTouch || event.pointerCount > 1 - - when (event.actionMasked) { - MotionEvent.ACTION_POINTER_DOWN -> { - // Cancel the one-finger gesture before consuming the rest of the pinch. - MotionEvent.obtain(event).let { cancelEvent -> - cancelEvent.action = MotionEvent.ACTION_CANCEL - webView.onTouchEvent(cancelEvent) - cancelEvent.recycle() - } - blockingMultiTouch = true - } - MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> blockingMultiTouch = false - } - - shouldBlock - } - - NativeAccessibilityBridge.attach(webView) - mediaWebView = webView - MobileMediaEvents.attach(webView) - webView.addJavascriptInterface(MobileMediaJavascriptInterface(), "tensaminMobileMedia") - } override fun onCreate(savedInstanceState: Bundle?) { WindowCompat.setDecorFitsSystemWindows(window, true) window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) super.onCreate(savedInstanceState) - NativeMtpBridge.nativeAttach(applicationContext) - if (MtpSecureStore.isEnabled(this) && MtpSecureStore.hasConfig(this)) { - NativeMtpBridge.startService(this) - } - NativeAccessibilityBridge.nativeAttach(applicationContext) installKeyboardResizeWorkaround() } - override fun onResume() { - super.onResume() - NativeMtpBridge.nativeSetUiState(true) - } - - override fun onPause() { - NativeMtpBridge.nativeSetUiState(false) - super.onPause() - } - override fun onDestroy() { attachLayoutListener?.let { listener -> contentRoot?.viewTreeObserver?.removeOnGlobalLayoutListener(listener) @@ -116,87 +30,9 @@ class MainActivity : TauriActivity() { attachLayoutListener = null contentRoot = null contentChild = null - mediaWebView?.let { - NativeAccessibilityBridge.detach(it) - it.removeJavascriptInterface("tensaminMobileMedia") - } - mediaWebView = null - MobileMediaEvents.detach() super.onDestroy() } - private fun startScreenShare(includeAudio: Boolean) { - runOnUiThread { - if (pendingScreenAudio != null) { - MobileMediaEvents.emitError("Screen capture permission is already pending") - return@runOnUiThread - } - - pendingScreenAudio = includeAudio - if ( - includeAudio && - ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != - PackageManager.PERMISSION_GRANTED - ) { - screenAudioPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO) - } else { - launchScreenCaptureIntent() - } - } - } - - private fun launchScreenCaptureIntent() { - val manager = getSystemService(MediaProjectionManager::class.java) - screenCaptureLauncher.launch(manager.createScreenCaptureIntent()) - } - - private fun stopScreenShare() { - runOnUiThread { - pendingScreenAudio = null - MediaProjectionService.stop(this) - } - } - - private fun requestCameraPermission() { - runOnUiThread { - cameraPermissionLauncher.launch( - arrayOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO), - ) - } - } - - private fun emitCameraPermission() { - val detail = JSONObject() - .put( - "camera", - ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == - PackageManager.PERMISSION_GRANTED, - ) - .put( - "microphone", - ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == - PackageManager.PERMISSION_GRANTED, - ) - MobileMediaEvents.emit("tensamin-mobile-camera-permission", detail) - } - - private inner class MobileMediaJavascriptInterface { - @JavascriptInterface - fun startScreenShare(includeAudio: Boolean) { - this@MainActivity.startScreenShare(includeAudio) - } - - @JavascriptInterface - fun stopScreenShare() { - this@MainActivity.stopScreenShare() - } - - @JavascriptInterface - fun requestCameraPermission() { - this@MainActivity.requestCameraPermission() - } - } - private fun installKeyboardResizeWorkaround() { val content = window.decorView.findViewById(android.R.id.content) contentRoot = content @@ -232,20 +68,16 @@ class MainActivity : TauriActivity() { child: android.view.View, insets: WindowInsetsCompat? = ViewCompat.getRootWindowInsets(child), ) { + val visibleFrame = Rect() + child.getWindowVisibleDisplayFrame(visibleFrame) + val rootHeight = child.rootView.height if (rootHeight <= 0) return - val imeVisible = insets?.isVisible(WindowInsetsCompat.Type.ime()) == true - val imeHeight = if (imeVisible) { - insets.getInsets(WindowInsetsCompat.Type.ime()).bottom - } else { - 0 - } - val usableHeight = if (imeHeight in 1 until rootHeight) { - rootHeight - imeHeight - } else { - WindowManager.LayoutParams.MATCH_PARENT - } + val imeHeight = insets?.getInsets(WindowInsetsCompat.Type.ime())?.bottom ?: 0 + val keyboardHeight = maxOf(imeHeight, rootHeight - visibleFrame.bottom) + val keyboardVisible = keyboardHeight > rootHeight * 0.15 + val usableHeight = if (keyboardVisible) rootHeight - keyboardHeight else ViewGroup.LayoutParams.MATCH_PARENT if (previousUsableHeight == usableHeight) return diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MediaProjectionService.kt b/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MediaProjectionService.kt deleted file mode 100644 index 3e961df..0000000 --- a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MediaProjectionService.kt +++ /dev/null @@ -1,345 +0,0 @@ -package net.tensamin.client - -import android.Manifest -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.app.Service -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.content.pm.ServiceInfo -import android.graphics.Bitmap -import android.graphics.PixelFormat -import android.hardware.display.DisplayManager -import android.hardware.display.VirtualDisplay -import android.media.AudioAttributes -import android.media.AudioFormat -import android.media.AudioPlaybackCaptureConfiguration -import android.media.AudioRecord -import android.media.projection.MediaProjection -import android.media.projection.MediaProjectionManager -import android.media.ImageReader -import android.os.Build -import android.os.Handler -import android.os.HandlerThread -import android.os.IBinder -import android.util.Base64 -import android.util.DisplayMetrics -import androidx.core.app.NotificationCompat -import androidx.core.content.ContextCompat -import java.io.ByteArrayOutputStream -import java.util.concurrent.atomic.AtomicBoolean -import org.json.JSONObject - -class MediaProjectionService : Service() { - private var projection: MediaProjection? = null - private var virtualDisplay: VirtualDisplay? = null - private var imageReader: ImageReader? = null - private var captureThread: HandlerThread? = null - private var audioRecord: AudioRecord? = null - private var audioThread: Thread? = null - private val captureActive = AtomicBoolean(false) - private var lastFrameAt = 0L - - private val projectionCallback = object : MediaProjection.Callback() { - override fun onStop() { - stopCapture(stopProjection = false, emitStopped = true) - stopSelf() - } - } - - override fun onBind(intent: Intent?): IBinder? = null - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - if (intent?.action == ACTION_STOP) { - stopCapture(stopProjection = true, emitStopped = true) - stopSelf() - return START_NOT_STICKY - } - - val permissionData = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - intent?.getParcelableExtra(EXTRA_PERMISSION_DATA, Intent::class.java) - } else { - @Suppress("DEPRECATION") - intent?.getParcelableExtra(EXTRA_PERMISSION_DATA) - } - val resultCode = intent?.getIntExtra(EXTRA_RESULT_CODE, Int.MIN_VALUE) ?: Int.MIN_VALUE - if (permissionData == null || resultCode == Int.MIN_VALUE) { - MobileMediaEvents.emitError("Screen capture permission data is missing") - stopSelf() - return START_NOT_STICKY - } - - val includeAudio = intent?.getBooleanExtra(EXTRA_INCLUDE_AUDIO, false) ?: false - try { - startForegroundNotification() - startCapture(resultCode, permissionData, includeAudio) - } catch (error: Throwable) { - stopCapture(stopProjection = true, emitStopped = false) - stopForeground(STOP_FOREGROUND_REMOVE) - stopSelf() - MobileMediaEvents.emitError(error.message ?: "Unable to start screen capture") - } - return START_NOT_STICKY - } - - override fun onDestroy() { - stopCapture(stopProjection = true, emitStopped = true) - super.onDestroy() - } - - private fun startForegroundNotification() { - val notificationManager = getSystemService(NotificationManager::class.java) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - notificationManager.createNotificationChannel( - NotificationChannel( - NOTIFICATION_CHANNEL_ID, - "Screen sharing", - NotificationManager.IMPORTANCE_LOW, - ), - ) - } - - val stopIntent = Intent(this, MediaProjectionService::class.java).setAction(ACTION_STOP) - val stopPendingIntent = PendingIntent.getService( - this, - 0, - stopIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, - ) - val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) - .setSmallIcon(android.R.drawable.ic_menu_share) - .setContentTitle("Tensamin is sharing your screen") - .setContentText("Tap Stop to end screen sharing") - .setOngoing(true) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .addAction(android.R.drawable.ic_media_pause, "Stop", stopPendingIntent) - .build() - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - startForeground( - NOTIFICATION_ID, - notification, - ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION, - ) - } else { - startForeground(NOTIFICATION_ID, notification) - } - } - - @Suppress("DEPRECATION") - private fun startCapture(resultCode: Int, permissionData: Intent, includeAudio: Boolean) { - stopCapture(stopProjection = true, emitStopped = false) - - val manager = getSystemService(MediaProjectionManager::class.java) - val newProjection = manager.getMediaProjection(resultCode, permissionData) - ?: error("Android did not provide a screen capture session") - projection = newProjection - - val thread = HandlerThread("tensamin-screen-capture").also { it.start() } - captureThread = thread - val handler = Handler(thread.looper) - newProjection.registerCallback(projectionCallback, handler) - - val metrics = DisplayMetrics() - getSystemService(android.view.WindowManager::class.java).defaultDisplay.getRealMetrics(metrics) - val displayWidth = metrics.widthPixels - val displayHeight = metrics.heightPixels - val width = minOf(displayWidth, MAX_FRAME_WIDTH) - val height = (displayHeight.toLong() * width / displayWidth).toInt() - val density = metrics.densityDpi - val reader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 2) - imageReader = reader - reader.setOnImageAvailableListener({ source -> captureFrame(source, width, height) }, handler) - - virtualDisplay = newProjection.createVirtualDisplay( - "Tensamin screen sharing", - width, - height, - density, - DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, - reader.surface, - null, - handler, - ) - captureActive.set(true) - - val audioStarted = includeAudio && startAudioCapture(newProjection) - MobileMediaEvents.emit( - "tensamin-mobile-screen-started", - JSONObject().put("includeAudio", audioStarted), - ) - } - - private fun captureFrame(source: ImageReader, width: Int, height: Int) { - val image = source.acquireLatestImage() ?: return - try { - val now = System.currentTimeMillis() - if (!captureActive.get() || now - lastFrameAt < FRAME_INTERVAL_MS) return - lastFrameAt = now - - val plane = image.planes[0] - val paddedWidth = plane.rowStride / plane.pixelStride - val paddedBitmap = Bitmap.createBitmap(paddedWidth, height, Bitmap.Config.ARGB_8888) - paddedBitmap.copyPixelsFromBuffer(plane.buffer) - val croppedBitmap = if (paddedWidth == width) { - paddedBitmap - } else { - Bitmap.createBitmap(paddedBitmap, 0, 0, width, height).also { paddedBitmap.recycle() } - } - val outputBitmap = croppedBitmap - - val bytes = ByteArrayOutputStream() - outputBitmap.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, bytes) - val outputWidth = outputBitmap.width - val outputHeight = outputBitmap.height - outputBitmap.recycle() - MobileMediaEvents.emit( - "tensamin-mobile-screen-frame", - JSONObject() - .put("data", Base64.encodeToString(bytes.toByteArray(), Base64.NO_WRAP)) - .put("mimeType", "image/jpeg") - .put("width", outputWidth) - .put("height", outputHeight), - ) - } catch (error: Throwable) { - if (captureActive.get()) { - MobileMediaEvents.emitError(error.message ?: "Unable to read a screen frame") - } - } finally { - image.close() - } - } - - private fun startAudioCapture(activeProjection: MediaProjection): Boolean { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - MobileMediaEvents.emitError("System audio capture requires Android 10 or newer") - return false - } - if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != - PackageManager.PERMISSION_GRANTED - ) { - MobileMediaEvents.emitError("Microphone permission is required for system audio capture") - return false - } - - return try { - val format = AudioFormat.Builder() - .setEncoding(AudioFormat.ENCODING_PCM_16BIT) - .setSampleRate(AUDIO_SAMPLE_RATE) - .setChannelMask(AudioFormat.CHANNEL_IN_MONO) - .build() - val configuration = AudioPlaybackCaptureConfiguration.Builder(activeProjection) - .addMatchingUsage(AudioAttributes.USAGE_MEDIA) - .addMatchingUsage(AudioAttributes.USAGE_GAME) - .build() - val minimumBuffer = AudioRecord.getMinBufferSize( - AUDIO_SAMPLE_RATE, - AudioFormat.CHANNEL_IN_MONO, - AudioFormat.ENCODING_PCM_16BIT, - ) - check(minimumBuffer > 0) { "Android could not allocate a system audio buffer" } - val bufferSize = maxOf(minimumBuffer * 2, AUDIO_BATCH_BYTES) - val record = AudioRecord.Builder() - .setAudioFormat(format) - .setAudioPlaybackCaptureConfig(configuration) - .setBufferSizeInBytes(bufferSize) - .build() - check(record.state == AudioRecord.STATE_INITIALIZED) { - "Android could not initialize system audio capture" - } - audioRecord = record - record.startRecording() - audioThread = Thread({ readAudio(record) }, "tensamin-audio-capture").also { it.start() } - true - } catch (error: Throwable) { - audioRecord?.release() - audioRecord = null - MobileMediaEvents.emitError(error.message ?: "Unable to capture system audio") - false - } - } - - private fun readAudio(record: AudioRecord) { - val buffer = ByteArray(AUDIO_BATCH_BYTES) - while (captureActive.get() && !Thread.currentThread().isInterrupted) { - val read = record.read(buffer, 0, buffer.size, AudioRecord.READ_BLOCKING) - if (read > 0 && captureActive.get()) { - MobileMediaEvents.emit( - "tensamin-mobile-screen-audio", - JSONObject() - .put("data", Base64.encodeToString(buffer, 0, read, Base64.NO_WRAP)) - .put("sampleRate", AUDIO_SAMPLE_RATE) - .put("channelCount", 1) - .put("encoding", "pcm16le"), - ) - } else if (read < 0 && captureActive.get()) { - MobileMediaEvents.emitError("System audio capture stopped with code $read") - return - } - } - } - - @Synchronized - private fun stopCapture(stopProjection: Boolean, emitStopped: Boolean) { - val wasActive = captureActive.getAndSet(false) - - val record = audioRecord - audioRecord = null - try { - record?.stop() - } catch (_: IllegalStateException) { - } - record?.release() - audioThread?.interrupt() - audioThread = null - - imageReader?.setOnImageAvailableListener(null, null) - virtualDisplay?.release() - virtualDisplay = null - imageReader?.close() - imageReader = null - - val oldProjection = projection - projection = null - oldProjection?.unregisterCallback(projectionCallback) - if (stopProjection) oldProjection?.stop() - - captureThread?.quitSafely() - captureThread = null - lastFrameAt = 0L - - if (wasActive && emitStopped) { - MobileMediaEvents.emit("tensamin-mobile-screen-stopped") - } - } - - companion object { - private const val ACTION_STOP = "net.tensamin.client.STOP_SCREEN_SHARE" - private const val EXTRA_RESULT_CODE = "resultCode" - private const val EXTRA_PERMISSION_DATA = "permissionData" - private const val EXTRA_INCLUDE_AUDIO = "includeAudio" - private const val NOTIFICATION_CHANNEL_ID = "screen-sharing" - private const val NOTIFICATION_ID = 7314 - private const val FRAME_INTERVAL_MS = 75L - private const val MAX_FRAME_WIDTH = 1280 - private const val JPEG_QUALITY = 72 - private const val AUDIO_SAMPLE_RATE = 48_000 - private const val AUDIO_BATCH_BYTES = 9_600 - - fun start(context: Context, resultCode: Int, data: Intent, includeAudio: Boolean) { - val intent = Intent(context, MediaProjectionService::class.java) - .putExtra(EXTRA_RESULT_CODE, resultCode) - .putExtra(EXTRA_PERMISSION_DATA, data) - .putExtra(EXTRA_INCLUDE_AUDIO, includeAudio) - ContextCompat.startForegroundService(context, intent) - } - - fun stop(context: Context) { - context.startService( - Intent(context, MediaProjectionService::class.java).setAction(ACTION_STOP), - ) - } - } -} diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MobileMediaEvents.kt b/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MobileMediaEvents.kt deleted file mode 100644 index c8c377c..0000000 --- a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MobileMediaEvents.kt +++ /dev/null @@ -1,29 +0,0 @@ -package net.tensamin.client - -import android.webkit.WebView -import java.lang.ref.WeakReference -import org.json.JSONObject - -object MobileMediaEvents { - private var webView = WeakReference(null) - - fun attach(value: WebView) { - webView = WeakReference(value) - } - - fun detach() { - webView.clear() - } - - fun emitError(message: String) { - emit("tensamin-mobile-screen-error", JSONObject().put("message", message)) - } - - fun emit(name: String, detail: JSONObject = JSONObject()) { - val view = webView.get() ?: return - val script = "window.dispatchEvent(new CustomEvent(" + - JSONObject.quote(name) + - ", { detail: " + detail.toString() + " }));" - view.post { view.evaluateJavascript(script, null) } - } -} diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpBootReceiver.kt b/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpBootReceiver.kt deleted file mode 100644 index b96ae35..0000000 --- a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpBootReceiver.kt +++ /dev/null @@ -1,17 +0,0 @@ -package net.tensamin.client - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent - -class MtpBootReceiver : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if ( - intent.action == Intent.ACTION_BOOT_COMPLETED && - MtpSecureStore.isEnabled(context) && - MtpSecureStore.hasConfig(context) - ) { - NativeMtpBridge.startService(context) - } - } -} diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpForegroundService.kt b/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpForegroundService.kt deleted file mode 100644 index 56cca97..0000000 --- a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpForegroundService.kt +++ /dev/null @@ -1,167 +0,0 @@ -package net.tensamin.client - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.app.Service -import android.content.Context -import android.content.Intent -import android.content.pm.ServiceInfo -import android.net.ConnectivityManager -import android.net.Network -import android.net.NetworkCapabilities -import android.os.Build -import android.os.IBinder -import android.os.Process -import android.os.SystemClock -import androidx.core.app.NotificationCompat - -class MtpForegroundService : Service() { - private var started = false - private val networkCallback = object : ConnectivityManager.NetworkCallback() { - override fun onAvailable(network: Network) = refreshNotification() - override fun onLost(network: Network) = refreshNotification() - override fun onCapabilitiesChanged(network: Network, capabilities: NetworkCapabilities) = - refreshNotification() - } - - override fun onBind(intent: Intent?): IBinder? = null - - override fun onCreate() { - super.onCreate() - getSystemService(ConnectivityManager::class.java) - .registerDefaultNetworkCallback(networkCallback) - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - if (intent?.action == ACTION_STOP) { - MtpSecureStore.setEnabled(this, false) - stopForeground(STOP_FOREGROUND_REMOVE) - stopSelf() - return START_NOT_STICKY - } - - if (started) return START_STICKY - - createChannel(this) - connectionStatus = "Connecting" - val notification = buildNotification(this, displayedStatus(this)) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - startForeground( - NOTIFICATION_ID, - notification, - ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE, - ) - } else { - startForeground(NOTIFICATION_ID, notification) - } - val config = MtpSecureStore.loadConfig(this) - if (config == null || !MtpSecureStore.isEnabled(this)) { - stopForeground(STOP_FOREGROUND_REMOVE) - stopSelf() - return START_NOT_STICKY - } - - try { - NativeMtpBridge.nativeAttach(applicationContext) - NativeMtpBridge.nativeStart(config) - started = true - NativeMtpBridge.log(2, "Started MTP foreground service") - } catch (error: Throwable) { - NativeMtpBridge.log(0, "Failed to start MTP foreground service", error) - updateNotification(this, "Connection failed") - } - return START_STICKY - } - - override fun onTaskRemoved(rootIntent: Intent?) { - val preferences = getSharedPreferences(SERVICE_PREFERENCES, Context.MODE_PRIVATE) - val now = SystemClock.elapsedRealtime() - if (now - preferences.getLong(LAST_TASK_RESTART, 0) < TASK_RESTART_COOLDOWN_MS) { - super.onTaskRemoved(rootIntent) - return - } - preferences.edit().putLong(LAST_TASK_RESTART, now).commit() - if (MtpSecureStore.isEnabled(this) && MtpSecureStore.hasConfig(this)) { - startService(Intent(this, MtpForegroundService::class.java)) - } - super.onTaskRemoved(rootIntent) - // Tauri cannot recreate its WebView after the UI task is removed while this process survives. - Process.killProcess(Process.myPid()) - } - - override fun onDestroy() { - getSystemService(ConnectivityManager::class.java).unregisterNetworkCallback(networkCallback) - if (!MtpSecureStore.isEnabled(this)) NativeMtpBridge.nativeStop() - super.onDestroy() - } - - private fun refreshNotification() { - updateNotification(this, connectionStatus) - } - - companion object { - private const val CHANNEL_ID = "tensamin-connection" - private const val NOTIFICATION_ID = 2201 - private const val ACTION_STOP = "net.tensamin.client.STOP_MTP" - private const val SERVICE_PREFERENCES = "tensamin-service" - private const val LAST_TASK_RESTART = "last-task-restart" - private const val TASK_RESTART_COOLDOWN_MS = 15_000L - @Volatile private var connectionStatus = "Connecting" - - fun updateNotification(context: Context, status: String) { - if (!MtpSecureStore.isEnabled(context)) return - connectionStatus = status - createChannel(context) - context.getSystemService(NotificationManager::class.java) - .notify(NOTIFICATION_ID, buildNotification(context, displayedStatus(context))) - } - - private fun displayedStatus(context: Context): String { - val connectivity = context.getSystemService(ConnectivityManager::class.java) - val network = connectivity.activeNetwork ?: return "No network" - val capabilities = connectivity.getNetworkCapabilities(network) ?: return "No network" - return if ( - capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && - capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) - ) connectionStatus else "No network" - } - - private fun createChannel(context: Context) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return - context.getSystemService(NotificationManager::class.java).createNotificationChannel( - NotificationChannel( - CHANNEL_ID, - "Background connection", - NotificationManager.IMPORTANCE_LOW, - ).apply { description = "Keeps Tensamin connected for incoming messages" }, - ) - } - - private fun buildNotification(context: Context, status: String): Notification { - val openIntent = PendingIntent.getActivity( - context, - 0, - Intent(context, MainActivity::class.java), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, - ) - val stopIntent = PendingIntent.getService( - context, - 1, - Intent(context, MtpForegroundService::class.java).setAction(ACTION_STOP), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, - ) - return NotificationCompat.Builder(context, CHANNEL_ID) - .setSmallIcon(android.R.drawable.stat_notify_sync) - .setContentTitle("Tensamin") - .setContentText(status) - .setContentIntent(openIntent) - .setOngoing(true) - .setCategory(Notification.CATEGORY_SERVICE) - .setPriority(NotificationCompat.PRIORITY_LOW) - .addAction(android.R.drawable.ic_menu_close_clear_cancel, "Stop", stopIntent) - .build() - } - } -} diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpSecureStore.kt b/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpSecureStore.kt deleted file mode 100644 index bb03688..0000000 --- a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpSecureStore.kt +++ /dev/null @@ -1,74 +0,0 @@ -package net.tensamin.client - -import android.content.Context -import android.security.keystore.KeyGenParameterSpec -import android.security.keystore.KeyProperties -import android.util.Base64 -import java.security.KeyStore -import javax.crypto.Cipher -import javax.crypto.KeyGenerator -import javax.crypto.SecretKey -import javax.crypto.spec.GCMParameterSpec - -object MtpSecureStore { - private const val KEY_ALIAS = "tensamin-mtp-config" - private const val PREFS = "tensamin-mtp" - private const val CONFIG = "config" - private const val ENABLED = "enabled" - - fun saveConfig(context: Context, config: String) { - val cipher = Cipher.getInstance("AES/GCM/NoPadding") - cipher.init(Cipher.ENCRYPT_MODE, getOrCreateKey()) - val encrypted = cipher.doFinal(config.toByteArray(Charsets.UTF_8)) - val payload = Base64.encodeToString(cipher.iv + encrypted, Base64.NO_WRAP) - preferences(context).edit().putString(CONFIG, payload).apply() - } - - fun loadConfig(context: Context): String? { - val payload = preferences(context).getString(CONFIG, null) ?: return null - return runCatching { - val bytes = Base64.decode(payload, Base64.NO_WRAP) - require(bytes.size > 12) - val cipher = Cipher.getInstance("AES/GCM/NoPadding") - cipher.init( - Cipher.DECRYPT_MODE, - getOrCreateKey(), - GCMParameterSpec(128, bytes.copyOfRange(0, 12)), - ) - String(cipher.doFinal(bytes.copyOfRange(12, bytes.size)), Charsets.UTF_8) - }.getOrNull() - } - - fun hasConfig(context: Context): Boolean = loadConfig(context) != null - - fun setEnabled(context: Context, enabled: Boolean) { - preferences(context).edit().putBoolean(ENABLED, enabled).apply() - } - - fun isEnabled(context: Context): Boolean = - preferences(context).getBoolean(ENABLED, false) - - private fun preferences(context: Context) = - context.getSharedPreferences(PREFS, Context.MODE_PRIVATE) - - private fun getOrCreateKey(): SecretKey { - val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } - (keyStore.getKey(KEY_ALIAS, null) as? SecretKey)?.let { return it } - - val generator = KeyGenerator.getInstance( - KeyProperties.KEY_ALGORITHM_AES, - "AndroidKeyStore", - ) - generator.init( - KeyGenParameterSpec.Builder( - KEY_ALIAS, - KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT, - ) - .setBlockModes(KeyProperties.BLOCK_MODE_GCM) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) - .setKeySize(256) - .build(), - ) - return generator.generateKey() - } -} diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/NativeAccessibilityBridge.kt b/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/NativeAccessibilityBridge.kt deleted file mode 100644 index e04365a..0000000 --- a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/NativeAccessibilityBridge.kt +++ /dev/null @@ -1,51 +0,0 @@ -package net.tensamin.client - -import android.content.Context -import android.webkit.WebView -import androidx.annotation.Keep -import java.lang.ref.WeakReference - -@Keep -object NativeAccessibilityBridge { - const val DEFAULT_INITIAL_SCALE = 290 - private const val MIN_INITIAL_SCALE = 210 - private const val MAX_INITIAL_SCALE = 500 - private const val PREFERENCES = "tensamin-accessibility" - private const val INITIAL_SCALE = "initial-scale" - - private var webView = WeakReference(null) - - init { - System.loadLibrary("mobile_lib") - } - - @JvmStatic external fun nativeAttach(context: Context) - - fun attach(webView: WebView) { - this.webView = WeakReference(webView) - webView.setInitialScale(getInitialScale(webView.context)) - } - - fun detach(webView: WebView) { - if (this.webView.get() === webView) this.webView.clear() - } - - fun getInitialScale(context: Context): Int { - val preferences = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE) - val storedScale = preferences.getInt(INITIAL_SCALE, DEFAULT_INITIAL_SCALE) - val scale = storedScale.coerceIn(MIN_INITIAL_SCALE, MAX_INITIAL_SCALE) - if (scale != storedScale) preferences.edit().putInt(INITIAL_SCALE, scale).apply() - return scale - } - - fun setInitialScale(context: Context, initialScale: Int) { - val nextScale = initialScale.coerceIn(MIN_INITIAL_SCALE, MAX_INITIAL_SCALE) - context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE) - .edit() - .putInt(INITIAL_SCALE, nextScale) - .apply() - webView.get()?.let { currentWebView -> - currentWebView.post { currentWebView.setInitialScale(nextScale) } - } - } -} diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/NativeMtpBridge.kt b/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/NativeMtpBridge.kt deleted file mode 100644 index ba9422d..0000000 --- a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/NativeMtpBridge.kt +++ /dev/null @@ -1,161 +0,0 @@ -package net.tensamin.client - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.graphics.BitmapFactory -import android.net.Uri -import android.os.Build -import android.os.PowerManager -import android.provider.Settings -import android.util.Log -import androidx.annotation.Keep -import androidx.core.app.NotificationCompat -import androidx.core.app.Person -import androidx.core.content.LocusIdCompat -import androidx.core.content.ContextCompat -import androidx.core.content.pm.ShortcutInfoCompat -import androidx.core.content.pm.ShortcutManagerCompat -import androidx.core.graphics.drawable.IconCompat - -@Keep -object NativeMtpBridge { - private const val MESSAGE_CHANNEL = "tensamin-messages" - - init { - System.loadLibrary("mobile_lib") - } - - @JvmStatic external fun nativeAttach(context: Context) - @JvmStatic external fun nativeStart(config: String) - @JvmStatic external fun nativeStop() - @JvmStatic external fun nativeSetUiState(visible: Boolean) - @JvmStatic external fun nativeLog(level: Int, message: String, details: String) - - fun log(level: Int, message: String, error: Throwable? = null) { - val details = error?.stackTraceToString().orEmpty() - Log.println(if (level == 0) Log.ERROR else Log.INFO, "TensaminAndroid", "$message $details") - nativeLog(level, message, details) - } - - fun storeConfig(context: Context, config: String) { - try { - MtpSecureStore.saveConfig(context, config) - if (MtpSecureStore.isEnabled(context)) startService(context) - log(2, "Stored native MTP credentials") - } catch (error: Throwable) { - log(0, "Failed to store native MTP credentials", error) - throw error - } - } - - fun hasConfig(context: Context): Boolean = MtpSecureStore.hasConfig(context) - - fun setServiceEnabled(context: Context, enabled: Boolean) { - MtpSecureStore.setEnabled(context, enabled) - if (enabled && MtpSecureStore.hasConfig(context)) startService(context) else stopService(context) - } - - fun isIgnoringBatteryOptimizations(context: Context): Boolean = - context.getSystemService(PowerManager::class.java) - .isIgnoringBatteryOptimizations(context.packageName) - - fun requestBatteryExemption(context: Context) { - val intent = Intent( - Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, - Uri.parse("package:${context.packageName}"), - ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - context.startActivity(intent) - } - - fun startService(context: Context) { - ContextCompat.startForegroundService( - context, - Intent(context, MtpForegroundService::class.java), - ) - } - - fun stopService(context: Context) { - if (!context.stopService(Intent(context, MtpForegroundService::class.java))) nativeStop() - } - - fun updateServiceStatus(context: Context, status: String) { - MtpForegroundService.updateNotification(context, status) - } - - fun postMessageNotification( - context: Context, - senderId: Long, - sender: String, - body: String, - avatar: ByteArray, - ) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - context.getSystemService(NotificationManager::class.java).createNotificationChannel( - NotificationChannel( - MESSAGE_CHANNEL, - "Messages", - NotificationManager.IMPORTANCE_HIGH, - ).apply { description = "Incoming Tensamin messages" }, - ) - } - val openIntent = Intent( - Intent.ACTION_VIEW, - Uri.parse("tensamin://chat?id=$senderId"), - context, - MainActivity::class.java, - ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP) - val pendingIntent = PendingIntent.getActivity( - context, - senderId.hashCode(), - openIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, - ) - val avatarBitmap = avatar.takeIf { it.isNotEmpty() }?.let { - BitmapFactory.decodeByteArray(it, 0, it.size) - } - val avatarIcon = avatarBitmap?.let(IconCompat::createWithAdaptiveBitmap) - val person = Person.Builder() - .setName(sender) - .setKey(senderId.toString()) - .setIcon(avatarIcon) - .build() - val shortcutId = "chat-$senderId" - val shortcut = ShortcutInfoCompat.Builder(context, shortcutId) - .setShortLabel(sender) - .setLongLived(true) - .setPerson(person) - .setIntent(openIntent) - .apply { if (avatarIcon != null) setIcon(avatarIcon) } - .build() - ShortcutManagerCompat.pushDynamicShortcut(context, shortcut) - - val style = NotificationCompat.MessagingStyle( - Person.Builder().setName("You").build(), - ).addMessage(body, System.currentTimeMillis(), person) - val notification = NotificationCompat.Builder(context, MESSAGE_CHANNEL) - .setSmallIcon(R.drawable.ic_notification_small) - .setContentTitle(sender) - .setContentText(body) - .setStyle(style) - .setShortcutId(shortcutId) - .setLocusId(LocusIdCompat(shortcutId)) - .setLargeIcon(avatarBitmap) - .setCategory(Notification.CATEGORY_MESSAGE) - .setAutoCancel(true) - .setContentIntent(pendingIntent) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .build() - context.getSystemService(NotificationManager::class.java) - .notify(senderId.hashCode(), notification) - } - - fun cancelMessageNotification(context: Context, senderId: Long) { - context.getSystemService(NotificationManager::class.java) - .cancel(senderId.hashCode()) - } - -} diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/drawable/ic_notification_small.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/drawable/ic_notification_small.png deleted file mode 100644 index bec055d..0000000 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/drawable/ic_notification_small.png and /dev/null differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 0343c28..2ffbf24 100644 --- a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,5 @@ - - + \ No newline at end of file diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index cd21ec7..6de2c44 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png deleted file mode 100644 index b038821..0000000 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png and /dev/null differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png index fcd876f..c6da8ae 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png deleted file mode 100644 index b1a2c84..0000000 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 772d941..c42517c 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 22e9714..1d61eb2 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png deleted file mode 100644 index 5635294..0000000 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png and /dev/null differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png index 3de42b8..eceb132 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png deleted file mode 100644 index b13236e..0000000 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index 6823287..be24593 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index de98c38..5aeb1dc 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png deleted file mode 100644 index 16908ba..0000000 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png and /dev/null differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png index cab8a74..cebe007 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png deleted file mode 100644 index c929b14..0000000 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index fe68af3..1b7aa3d 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 0f90d7b..14abb66 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png deleted file mode 100644 index 15cf062..0000000 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png and /dev/null differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png index 8fca244..a918033 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png deleted file mode 100644 index 21f0293..0000000 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index 2f8677f..5bd12fa 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 93598e4..4583924 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png deleted file mode 100644 index 0b02649..0000000 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png and /dev/null differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png index b3b35ee..f480384 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png deleted file mode 100644 index 8c3f92e..0000000 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index 94582d8..17279d6 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/values/strings.xml b/apps/tauri/src-tauri/gen/android/app/src/main/res/values/strings.xml index 2551e9e..e9951d1 100644 --- a/apps/tauri/src-tauri/gen/android/app/src/main/res/values/strings.xml +++ b/apps/tauri/src-tauri/gen/android/app/src/main/res/values/strings.xml @@ -1,4 +1,4 @@ - Tensamin - Tensamin + tensamin + tensamin \ No newline at end of file diff --git a/apps/tauri/src-tauri/gen/android/build.gradle.kts b/apps/tauri/src-tauri/gen/android/build.gradle.kts index 1488b27..dddb93c 100644 --- a/apps/tauri/src-tauri/gen/android/build.gradle.kts +++ b/apps/tauri/src-tauri/gen/android/build.gradle.kts @@ -1,4 +1,4 @@ -import com.android.build.api.dsl.LibraryExtension +import com.android.build.gradle.LibraryExtension buildscript { repositories { @@ -7,7 +7,7 @@ buildscript { } dependencies { classpath("com.android.tools.build:gradle:8.11.0") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.20") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25") } } diff --git a/apps/tauri/src-tauri/gen/android/buildSrc/build.gradle.kts b/apps/tauri/src-tauri/gen/android/buildSrc/build.gradle.kts index 09776c6..5c55bba 100644 --- a/apps/tauri/src-tauri/gen/android/buildSrc/build.gradle.kts +++ b/apps/tauri/src-tauri/gen/android/buildSrc/build.gradle.kts @@ -20,3 +20,4 @@ dependencies { compileOnly(gradleApi()) implementation("com.android.tools.build:gradle:8.11.0") } + diff --git a/apps/tauri/src-tauri/gen/android/buildSrc/src/main/java/net/tensamin/client/kotlin/BuildTask.kt b/apps/tauri/src-tauri/gen/android/buildSrc/src/main/java/net/tensamin/client/kotlin/BuildTask.kt index e2be2d1..a7e39eb 100644 --- a/apps/tauri/src-tauri/gen/android/buildSrc/src/main/java/net/tensamin/client/kotlin/BuildTask.kt +++ b/apps/tauri/src-tauri/gen/android/buildSrc/src/main/java/net/tensamin/client/kotlin/BuildTask.kt @@ -5,12 +5,8 @@ import org.gradle.api.GradleException import org.gradle.api.logging.LogLevel import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction -import org.gradle.process.ExecOperations -import javax.inject.Inject -open class BuildTask @Inject constructor( - private val execOperations: ExecOperations, -) : DefaultTask() { +open class BuildTask : DefaultTask() { @Input var rootDirRel: String? = null @Input @@ -54,7 +50,7 @@ open class BuildTask @Inject constructor( val release = release ?: throw GradleException("release cannot be null") val args = listOf("tauri", "android", "android-studio-script"); - execOperations.exec { + project.exec { workingDir(File(project.projectDir, rootDirRel)) executable(executable) args(args) @@ -69,4 +65,4 @@ open class BuildTask @Inject constructor( args(listOf("--target", target)) }.assertNormalExitValue() } -} +} \ No newline at end of file diff --git a/apps/tauri/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties b/apps/tauri/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties index 8e0bb4b..c5f9a53 100644 --- a/apps/tauri/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties +++ b/apps/tauri/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue May 10 19:22:52 CST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/apps/tauri/src-tauri/icons/128x128.png b/apps/tauri/src-tauri/icons/128x128.png index 357e1c2..2e64037 100644 Binary files a/apps/tauri/src-tauri/icons/128x128.png and b/apps/tauri/src-tauri/icons/128x128.png differ diff --git a/apps/tauri/src-tauri/icons/128x128@2x.png b/apps/tauri/src-tauri/icons/128x128@2x.png index be55e24..d379bfa 100644 Binary files a/apps/tauri/src-tauri/icons/128x128@2x.png and b/apps/tauri/src-tauri/icons/128x128@2x.png differ diff --git a/apps/tauri/src-tauri/icons/32x32.png b/apps/tauri/src-tauri/icons/32x32.png index bc4fba4..c98f4e9 100644 Binary files a/apps/tauri/src-tauri/icons/32x32.png and b/apps/tauri/src-tauri/icons/32x32.png differ diff --git a/apps/tauri/src-tauri/icons/64x64.png b/apps/tauri/src-tauri/icons/64x64.png index 567f4ad..e579c32 100644 Binary files a/apps/tauri/src-tauri/icons/64x64.png and b/apps/tauri/src-tauri/icons/64x64.png differ diff --git a/apps/tauri/src-tauri/icons/Square107x107Logo.png b/apps/tauri/src-tauri/icons/Square107x107Logo.png index 48dd0e1..32c273f 100644 Binary files a/apps/tauri/src-tauri/icons/Square107x107Logo.png and b/apps/tauri/src-tauri/icons/Square107x107Logo.png differ diff --git a/apps/tauri/src-tauri/icons/Square142x142Logo.png b/apps/tauri/src-tauri/icons/Square142x142Logo.png index be8a336..b344198 100644 Binary files a/apps/tauri/src-tauri/icons/Square142x142Logo.png and b/apps/tauri/src-tauri/icons/Square142x142Logo.png differ diff --git a/apps/tauri/src-tauri/icons/Square150x150Logo.png b/apps/tauri/src-tauri/icons/Square150x150Logo.png index 78b70fe..a055b76 100644 Binary files a/apps/tauri/src-tauri/icons/Square150x150Logo.png and b/apps/tauri/src-tauri/icons/Square150x150Logo.png differ diff --git a/apps/tauri/src-tauri/icons/Square284x284Logo.png b/apps/tauri/src-tauri/icons/Square284x284Logo.png index c896c52..322f113 100644 Binary files a/apps/tauri/src-tauri/icons/Square284x284Logo.png and b/apps/tauri/src-tauri/icons/Square284x284Logo.png differ diff --git a/apps/tauri/src-tauri/icons/Square30x30Logo.png b/apps/tauri/src-tauri/icons/Square30x30Logo.png index f8f8e7a..563a719 100644 Binary files a/apps/tauri/src-tauri/icons/Square30x30Logo.png and b/apps/tauri/src-tauri/icons/Square30x30Logo.png differ diff --git a/apps/tauri/src-tauri/icons/Square310x310Logo.png b/apps/tauri/src-tauri/icons/Square310x310Logo.png index 5fe0250..2c05099 100644 Binary files a/apps/tauri/src-tauri/icons/Square310x310Logo.png and b/apps/tauri/src-tauri/icons/Square310x310Logo.png differ diff --git a/apps/tauri/src-tauri/icons/Square44x44Logo.png b/apps/tauri/src-tauri/icons/Square44x44Logo.png index fbfc485..9e7d8cb 100644 Binary files a/apps/tauri/src-tauri/icons/Square44x44Logo.png and b/apps/tauri/src-tauri/icons/Square44x44Logo.png differ diff --git a/apps/tauri/src-tauri/icons/Square71x71Logo.png b/apps/tauri/src-tauri/icons/Square71x71Logo.png index 091f375..949270a 100644 Binary files a/apps/tauri/src-tauri/icons/Square71x71Logo.png and b/apps/tauri/src-tauri/icons/Square71x71Logo.png differ diff --git a/apps/tauri/src-tauri/icons/Square89x89Logo.png b/apps/tauri/src-tauri/icons/Square89x89Logo.png index 861261f..2e7bf91 100644 Binary files a/apps/tauri/src-tauri/icons/Square89x89Logo.png and b/apps/tauri/src-tauri/icons/Square89x89Logo.png differ diff --git a/apps/tauri/src-tauri/icons/StoreLogo.png b/apps/tauri/src-tauri/icons/StoreLogo.png index 7cc6b2d..4b82c14 100644 Binary files a/apps/tauri/src-tauri/icons/StoreLogo.png and b/apps/tauri/src-tauri/icons/StoreLogo.png differ diff --git a/apps/tauri/src-tauri/icons/icon.icns b/apps/tauri/src-tauri/icons/icon.icns index a7f0a1e..dbdc5e3 100644 Binary files a/apps/tauri/src-tauri/icons/icon.icns and b/apps/tauri/src-tauri/icons/icon.icns differ diff --git a/apps/tauri/src-tauri/icons/icon.ico b/apps/tauri/src-tauri/icons/icon.ico index c5c68f8..7b2783e 100644 Binary files a/apps/tauri/src-tauri/icons/icon.ico and b/apps/tauri/src-tauri/icons/icon.ico differ diff --git a/apps/tauri/src-tauri/icons/icon.png b/apps/tauri/src-tauri/icons/icon.png index dad3edb..33853b3 100644 Binary files a/apps/tauri/src-tauri/icons/icon.png and b/apps/tauri/src-tauri/icons/icon.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@1x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@1x.png index e18a9c8..294193c 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@1x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@2x-1.png index 0861c9d..02a5874 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@2x-1.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@2x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@2x.png index 0861c9d..02a5874 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@2x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@3x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@3x.png index 20e1a6e..3453672 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@3x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@1x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@1x.png index 1720996..10af386 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@1x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@2x-1.png index a534e4c..a13e395 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@2x-1.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@2x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@2x.png index a534e4c..a13e395 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@2x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@3x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@3x.png index e1cd9f0..f4f2ccf 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@3x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@1x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@1x.png index 0861c9d..02a5874 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@1x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@2x-1.png index 4fe3372..5414b1d 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@2x-1.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@2x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@2x.png index 4fe3372..5414b1d 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@2x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@3x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@3x.png index a9e0a6c..aa8cbd8 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@3x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-512@2x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-512@2x.png index 7ebf2f2..1beaa86 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-512@2x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-60x60@2x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-60x60@2x.png index a9e0a6c..aa8cbd8 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-60x60@2x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-60x60@3x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-60x60@3x.png index 6e946d2..17aa19b 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-60x60@3x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-76x76@1x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-76x76@1x.png index 22f1af4..67faaf8 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-76x76@1x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-76x76@2x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-76x76@2x.png index 828c395..c00d463 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-76x76@2x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png index ee9fc5d..f3ca0dd 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/apps/tauri/src-tauri/src/accessibility_backend.rs b/apps/tauri/src-tauri/src/accessibility_backend.rs deleted file mode 100644 index 692b42e..0000000 --- a/apps/tauri/src-tauri/src/accessibility_backend.rs +++ /dev/null @@ -1,135 +0,0 @@ -#[cfg(not(target_os = "android"))] -const DEFAULT_INITIAL_SCALE: i32 = 290; -const MIN_INITIAL_SCALE: i32 = 210; -const MAX_INITIAL_SCALE: i32 = 500; - -#[tauri::command] -pub fn accessibility_get_initial_scale() -> Result { - android_get_initial_scale() -} - -#[tauri::command] -pub fn accessibility_set_initial_scale(initial_scale: i32) -> Result<(), String> { - if !(MIN_INITIAL_SCALE..=MAX_INITIAL_SCALE).contains(&initial_scale) { - return Err(format!( - "Initial scale must be between {MIN_INITIAL_SCALE} and {MAX_INITIAL_SCALE}" - )); - } - - android_set_initial_scale(initial_scale) -} - -#[cfg(not(target_os = "android"))] -fn android_get_initial_scale() -> Result { - Ok(DEFAULT_INITIAL_SCALE) -} - -#[cfg(not(target_os = "android"))] -fn android_set_initial_scale(_: i32) -> Result<(), String> { - Ok(()) -} - -#[cfg(target_os = "android")] -mod android { - use std::sync::OnceLock; - - use jni::{ - jni_sig, jni_str, - objects::{Global, JClass, JObject, JValue}, - Env, EnvUnowned, JavaVM, - }; - - struct Host { - vm: JavaVM, - context: Global>, - bridge: Global>, - } - - static HOST: OnceLock = OnceLock::new(); - - fn attach(env: &mut Env, context: JObject) -> Result<(), String> { - if HOST.get().is_some() { - return Ok(()); - } - - let class = env - .find_class(jni_str!("net/tensamin/client/NativeAccessibilityBridge")) - .map_err(|error| error.to_string())?; - let bridge = env - .get_static_field( - class, - jni_str!("INSTANCE"), - jni_sig!("Lnet/tensamin/client/NativeAccessibilityBridge;"), - ) - .and_then(|value| value.l()) - .map_err(|error| error.to_string())?; - - HOST.set(Host { - vm: env.get_java_vm().map_err(|error| error.to_string())?, - context: env - .new_global_ref(context) - .map_err(|error| error.to_string())?, - bridge: env - .new_global_ref(bridge) - .map_err(|error| error.to_string())?, - }) - .map_err(|_| "Android accessibility host is already attached".to_string()) - } - - fn with_env(call: impl FnOnce(&mut Env, &Host) -> Result) -> Result { - let host = HOST - .get() - .ok_or("Android accessibility host is not attached")?; - host.vm - .attach_current_thread(|env| Ok::<_, jni::errors::Error>(call(env, host))) - .map_err(|error| error.to_string())? - } - - pub fn get_initial_scale() -> Result { - with_env(|env, host| { - env.call_method( - host.bridge.as_obj(), - jni_str!("getInitialScale"), - jni_sig!("(Landroid/content/Context;)I"), - &[JValue::Object(host.context.as_obj())], - ) - .and_then(|value| value.i()) - .map_err(|error| error.to_string()) - }) - } - - pub fn set_initial_scale(initial_scale: i32) -> Result<(), String> { - with_env(|env, host| { - env.call_method( - host.bridge.as_obj(), - jni_str!("setInitialScale"), - jni_sig!("(Landroid/content/Context;I)V"), - &[ - JValue::Object(host.context.as_obj()), - JValue::Int(initial_scale), - ], - ) - .map_err(|error| error.to_string())?; - Ok(()) - }) - } - - #[no_mangle] - pub extern "system" fn Java_net_tensamin_client_NativeAccessibilityBridge_nativeAttach< - 'caller, - >( - mut env: EnvUnowned<'caller>, - _class: JClass, - context: JObject<'caller>, - ) { - let _ = env.with_env(|env| { - let _ = attach(env, context); - Ok::<_, jni::errors::Error>(()) - }); - } -} - -#[cfg(target_os = "android")] -use android::{ - get_initial_scale as android_get_initial_scale, set_initial_scale as android_set_initial_scale, -}; diff --git a/apps/tauri/src-tauri/src/lib.rs b/apps/tauri/src-tauri/src/lib.rs index 6636682..9eef4be 100644 --- a/apps/tauri/src-tauri/src/lib.rs +++ b/apps/tauri/src-tauri/src/lib.rs @@ -1,37 +1,216 @@ -mod accessibility_backend; -mod mtp_backend; +use serde::Serialize; + +#[derive(Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct ScreenShareSource { + id: String, + kind: String, + name: String, + subtitle: Option, +} + +#[derive(Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct ScreenShareAudioOutput { + id: String, + name: String, + is_default: bool, +} + +#[derive(Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct ScreenShareCapabilities { + platform: String, + show_audio_output_selector: bool, + show_audio_switch: bool, + has_reliable_system_audio: bool, +} + +#[tauri::command] +fn list_screen_share_sources() -> Result, String> { + #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] + { + use xcap::{Monitor, Window}; + + let mut sources = Vec::new(); + let mut errors = Vec::new(); + + match Monitor::all() { + Ok(monitors) => { + for (index, monitor) in monitors.into_iter().enumerate() { + let mut subtitle = None; + + if monitor.is_primary().unwrap_or(false) { + subtitle = Some("Primary display".to_string()); + } + + sources.push(ScreenShareSource { + id: format!("screen:{index}"), + kind: "screen".to_string(), + name: monitor + .name() + .unwrap_or_else(|_| format!("Display {}", index + 1)), + subtitle, + }); + } + } + Err(error) => errors.push(format!("display listing failed: {error}")), + } + + match Window::all() { + Ok(windows) => { + for (index, window) in windows.into_iter().enumerate() { + if window.is_minimized().unwrap_or(false) { + continue; + } + + let title = window.title().unwrap_or_default(); + + if title.trim().is_empty() { + continue; + } + + sources.push(ScreenShareSource { + id: format!("window:{index}"), + kind: "window".to_string(), + name: title, + subtitle: None, + }); + } + } + Err(error) => errors.push(format!("window listing failed: {error}")), + } + + if sources.is_empty() { + if errors.is_empty() { + return Ok(sources); + } + + return Err(errors.join("; ")); + } + + return Ok(sources); + } + + #[allow(unreachable_code)] + Ok(Vec::new()) +} + +#[tauri::command] +fn list_audio_outputs() -> Result, String> { + #[cfg(target_os = "linux")] + { + use serde_json::Value; + use std::process::Command; + + let output = Command::new("pactl") + .args(["--format=json", "list", "sinks"]) + .output() + .map_err(|error| error.to_string())?; + + if !output.status.success() { + return Err(String::from_utf8_lossy(&output.stderr).trim().to_string()); + } + + let default_sink = Command::new("pactl") + .arg("get-default-sink") + .output() + .ok() + .filter(|result| result.status.success()) + .map(|result| String::from_utf8_lossy(&result.stdout).trim().to_string()); + + let sinks: Value = serde_json::from_slice(&output.stdout).map_err(|error| error.to_string())?; + let sink_entries = sinks + .as_array() + .ok_or_else(|| "Unexpected pactl sink response".to_string())?; + + let mut outputs = Vec::new(); + + for sink in sink_entries { + let Some(index) = sink.get("index").and_then(Value::as_i64) else { + continue; + }; + + let Some(name) = sink.get("name").and_then(Value::as_str) else { + continue; + }; + + let description = sink + .get("description") + .and_then(Value::as_str) + .or_else(|| { + sink.get("properties") + .and_then(|properties| properties.get("device.description")) + .and_then(Value::as_str) + }) + .unwrap_or(name) + .to_string(); + + let is_default = default_sink.as_deref() == Some(name); + + outputs.push(ScreenShareAudioOutput { + id: index.to_string(), + name: description, + is_default, + }); + } + + outputs.sort_by_key(|output| !output.is_default); + + return Ok(outputs); + } + + #[allow(unreachable_code)] + Ok(Vec::new()) +} + +#[tauri::command] +fn get_screen_share_capabilities() -> ScreenShareCapabilities { + ScreenShareCapabilities { + platform: if cfg!(target_os = "linux") { + "linux" + } else if cfg!(target_os = "windows") { + "windows" + } else if cfg!(target_os = "macos") { + "macos" + } else { + "other" + } + .to_string(), + show_audio_output_selector: cfg!(target_os = "linux"), + show_audio_switch: cfg!(any(target_os = "windows", target_os = "macos")), + has_reliable_system_audio: cfg!(target_os = "windows"), + } +} #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { - let builder = tauri::Builder::default() - .plugin( - tauri_plugin_log::Builder::new() - .level(tauri_plugin_log::log::LevelFilter::Info) - .build(), - ) - .plugin(tauri_plugin_notification::init()); + let builder = tauri::Builder::default(); + + #[cfg(any(target_os = "linux", target_os = "macos"))] + let builder = builder.command_line_args([ + ("enable-media-stream", None::), + ("enable-usermedia-screen-capturing", None::), + ("allow-http-screen-capture", None::), + #[cfg(target_os = "linux")] + ( + "enable-features", + Some("WebRTCPipeWireCapturer".to_string()), + ), + ]); let builder = builder .plugin(tauri_plugin_deep_link::init()) .plugin(tauri_plugin_opener::init()); - let app = builder - .invoke_handler(tauri::generate_handler![ - accessibility_backend::accessibility_get_initial_scale, - accessibility_backend::accessibility_set_initial_scale, - mtp_backend::mtp_request, - mtp_backend::mtp_status, - mtp_backend::mtp_store_credentials, - mtp_backend::mtp_has_credentials, - mtp_backend::mtp_load_keyring, - mtp_backend::mtp_set_enabled, - mtp_backend::mtp_set_ui_visible, - mtp_backend::mtp_post_message_notification, - mtp_backend::mtp_is_ignoring_battery_optimizations, - mtp_backend::mtp_request_battery_exemption, - ]) + #[cfg(any(target_os = "ios", target_os = "android"))] + let builder = builder.plugin(tauri_plugin_barcode_scanner::init()); + + #[cfg(any(target_os = "ios", target_os = "android"))] + let builder = builder.plugin(tauri_plugin_app_events::init()); + + if let Err(error) = builder .setup(|_app| { - mtp_backend::manager().attach_app(_app.handle().clone()); #[cfg(any(target_os = "linux", windows))] { use tauri_plugin_deep_link::DeepLinkExt; @@ -46,18 +225,14 @@ pub fn run() { } Ok(()) }) - .build(tauri::generate_context!()) - .expect("error while building tauri application"); - - app.run(|_app, event| { - #[cfg(target_os = "android")] - if let tauri::RunEvent::ExitRequested { - api, code: None, .. - } = event - { - if mtp_backend::manager().is_enabled() { - api.prevent_exit(); - } - } - }); + .invoke_handler(tauri::generate_handler![ + list_screen_share_sources, + list_audio_outputs, + get_screen_share_capabilities + ]) + .run(tauri::generate_context!()) + { + eprintln!("error while running tauri application: {error}"); + panic!("error while running tauri application: {error}"); + } } diff --git a/apps/tauri/src-tauri/src/main.rs b/apps/tauri/src-tauri/src/main.rs index 272583a..a9346de 100644 --- a/apps/tauri/src-tauri/src/main.rs +++ b/apps/tauri/src-tauri/src/main.rs @@ -2,5 +2,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - mobile_lib::run(); + mobile_lib::run() } diff --git a/apps/tauri/src-tauri/src/mtp_backend.rs b/apps/tauri/src-tauri/src/mtp_backend.rs deleted file mode 100644 index fa28597..0000000 --- a/apps/tauri/src-tauri/src/mtp_backend.rs +++ /dev/null @@ -1,1563 +0,0 @@ -use std::sync::{ - atomic::{AtomicBool, AtomicU64, Ordering}, - Arc, Mutex, OnceLock, RwLock, -}; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -use base64::{ - engine::general_purpose::{STANDARD, STANDARD_NO_PAD}, - Engine as _, -}; -use mtp::client::{ClientConfig, MTPClient, MTPConnection, Policy, SendMode}; -use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue, TypeMap}; -use mtp::crypto::{ - derive_encryption_key, AeadDecrypt, ChaCha20Poly1305, HybridKem, Keyring, PublicKeyBundle, -}; -use serde::{Deserialize, Serialize}; -use serde_json::{Map, Value}; -use tauri::{AppHandle, Emitter}; -use tokio::sync::mpsc; - -const EVENT_NAME: &str = "mtp://event"; -const DISCONNECTED: u8 = 0; -const CONNECTING: u8 = 1; -const CONNECTED: u8 = 2; -const CHAT_SECRET_SALT: &[u8] = b"tensamin-chat-secret-v1"; -const CHAT_MESSAGE_SALT: &[u8] = b"tensamin-chat-message-v1"; -const CHAT_SECRET_SCHEME: &str = "mtp-chat-secret-kem-chacha20poly1305-hkdf-sha256-v1"; -const INITIAL_SYNC_TIMEOUT: Duration = Duration::from_secs(30); -const MAX_BUFFERED_INITIAL_FRAMES: usize = 1_000; -const NOTIFICATION_QUEUE_CAPACITY: usize = 32; -#[cfg(target_os = "android")] -const ROOT_YE_PEM: &[u8] = b"-----BEGIN CERTIFICATE-----\n\ -MIIB2TCCAWCgAwIBAgIRAKQCa6LvbHwg1AR+XmWmk4AwCgYIKoZIzj0EAwMwLjEL\n\ -MAkGA1UEBhMCVVMxDTALBgNVBAoTBElTUkcxEDAOBgNVBAMTB1Jvb3QgWUUwHhcN\n\ -MjUwOTAzMDAwMDAwWhcNNDUwOTAyMjM1OTU5WjAuMQswCQYDVQQGEwJVUzENMAsG\n\ -A1UEChMESVNSRzEQMA4GA1UEAxMHUm9vdCBZRTB2MBAGByqGSM49AgEGBSuBBAAi\n\ -A2IABDwS/6vhrcVqcbBo+wgdI3fwn9x7DNJJOY/lTOti0vkwuRN87RhEhTH17E7X\n\ -yFjWsPYhIPt/wzOqxTd2b+4ZJNy9ID04YywF9U5zasDVyGSNErVNtz8uSGh5izW8\n\ -7j77GaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O\n\ -BBYEFKPIJlqOoUzQNWP8myPIOq5W809WMAoGCCqGSM49BAMDA2cAMGQCMHhMr8N9\n\ -LdL1VQKs9BdV81r76eXRB6mtjuNjzk6/lBsPNToWLTDzGYgtQKO1jl63uAIwGV7m\n\ -onyF377c+MM1oqVNs17sgu7F9YKZwgLmVbeOMDbKAXHtKMDLbiGllCcs8f47\n\ ------END CERTIFICATE-----\n"; -#[cfg(target_os = "android")] -const YE1_PEM: &[u8] = b"-----BEGIN CERTIFICATE-----\n\ -MIICizCCAhGgAwIBAgIQXd1w3TH4AchcGGp6BLgK/jAKBggqhkjOPQQDAzAuMQsw\n\ -CQYDVQQGEwJVUzENMAsGA1UEChMESVNSRzEQMA4GA1UEAxMHUm9vdCBZRTAeFw0y\n\ -NTA5MDMwMDAwMDBaFw0yODA5MDIyMzU5NTlaMDMxCzAJBgNVBAYTAlVTMRYwFAYD\n\ -VQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQDEwNZRTEwdjAQBgcqhkjOPQIBBgUr\n\ -gQQAIgNiAAQHZVB1/mimla2hfSurylScjPMZaOJXLz/NnAc2sylm8WDyhU9Ccp+z\n\ -ASQi5vSwGGJjSGklkD9fdPR8GpyDIOIjCEfrnbt/v+ZSEPLLEGbaM6EccDbN7p9x\n\ -teIm2Avf+ryjge4wgeswDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUF\n\ -BwMBMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLsgykcL/tflnPmPCSqj\n\ -jDdFsbzYMB8GA1UdIwQYMBaAFKPIJlqOoUzQNWP8myPIOq5W809WMDIGCCsGAQUF\n\ -BwEBBCYwJDAiBggrBgEFBQcwAoYWaHR0cDovL3llLmkubGVuY3Iub3JnLzATBgNV\n\ -HSAEDDAKMAgGBmeBDAECATAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8veWUuYy5s\n\ -ZW5jci5vcmcvMAoGCCqGSM49BAMDA2gAMGUCMQDgjUEahFT/h3DRakqiPZpLvPgf\n\ -Zwkt6K2EOMmh1nvEzl83eMLYcod4GCl3b0J1Nn0CMBNYmEQJb4CEG5WoOe7aRn/L\n\ -VKu6saHmHEynI7ysIPd8zQsK1HdmhlHKlw9Z5GpGvA==\n\ ------END CERTIFICATE-----\n"; - -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct MtpConfig { - pub user_id: u64, - pub keyring: String, - pub omega_url: String, - pub forced_omikron_url: Option, - pub forced_omikron_public_key: Option, -} - -#[derive(Clone, Debug, Default, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct MtpSnapshot { - pub generation: u64, - pub ready_state: u8, - pub identified: bool, - pub state: Option, - pub error: Option, -} - -#[derive(Clone, Serialize)] -#[serde(tag = "kind", rename_all = "camelCase")] -enum MtpEvent { - State { - snapshot: MtpSnapshot, - }, - Message { - generation: u64, - message: Value, - }, - Log { - level: u8, - message: String, - details: Option, - }, -} - -pub struct MtpManager { - runtime: tokio::runtime::Runtime, - config: RwLock>, - connection: RwLock>>, - snapshot: RwLock, - generation: AtomicU64, - enabled: AtomicBool, - ui_visible: AtomicBool, - app: RwLock>, - start_lock: Mutex<()>, -} - -struct RequestIdAllocator { - next: AtomicU64, -} - -impl RequestIdAllocator { - fn new() -> Self { - Self { - next: AtomicU64::new(1), - } - } - - fn next(&self) -> Result { - let value = self.next.fetch_add(1, Ordering::Relaxed); - u32::try_from(value) - .map_err(|_| "MTP request ID space exhausted for this connection".to_string()) - } -} - -struct ManagedConnection { - mtp: Arc, - request_ids: RequestIdAllocator, -} - -impl ManagedConnection { - async fn next_request_id(&self) -> Result { - let id = self.request_ids.next(); - if id.is_err() { - self.mtp.sender.close().await; - } - id - } -} - -struct PreparedConnection { - connection: MTPConnection, - request_ids: RequestIdAllocator, - initial_state: Value, - buffered_frames: Vec, -} - -static MANAGER: OnceLock = OnceLock::new(); - -pub fn manager() -> &'static MtpManager { - MANAGER.get_or_init(|| MtpManager { - runtime: tokio::runtime::Builder::new_multi_thread() - .enable_all() - .thread_name("tensamin-mtp") - .build() - .expect("failed to create MTP runtime"), - config: RwLock::new(None), - connection: RwLock::new(None), - snapshot: RwLock::new(MtpSnapshot { - ready_state: DISCONNECTED, - ..MtpSnapshot::default() - }), - generation: AtomicU64::new(0), - enabled: AtomicBool::new(false), - ui_visible: AtomicBool::new(false), - app: RwLock::new(None), - start_lock: Mutex::new(()), - }) -} - -impl MtpManager { - pub fn attach_app(&self, app: AppHandle) { - *self.app.write().expect("app lock poisoned") = Some(app); - } - - pub fn configure_and_start(&'static self, config: MtpConfig) { - let _guard = self.start_lock.lock().expect("start lock poisoned"); - if self.enabled.load(Ordering::SeqCst) - && self.config.read().expect("config lock poisoned").as_ref() == Some(&config) - { - return; - } - *self.config.write().expect("config lock poisoned") = Some(config); - self.enabled.store(true, Ordering::SeqCst); - let generation = self.generation.fetch_add(1, Ordering::SeqCst) + 1; - if let Some(connection) = self - .connection - .write() - .expect("connection lock poisoned") - .take() - { - self.runtime - .spawn(async move { connection.mtp.sender.close().await }); - } - self.set_snapshot(MtpSnapshot { - generation, - ready_state: CONNECTING, - identified: false, - state: None, - error: None, - }); - self.runtime.spawn(supervise(generation)); - } - - pub fn stop(&self) { - self.enabled.store(false, Ordering::SeqCst); - self.generation.fetch_add(1, Ordering::SeqCst); - if let Some(connection) = self - .connection - .write() - .expect("connection lock poisoned") - .take() - { - self.runtime - .spawn(async move { connection.mtp.sender.close().await }); - } - self.set_snapshot(MtpSnapshot { - generation: self.generation.load(Ordering::SeqCst), - ready_state: DISCONNECTED, - identified: false, - state: None, - error: None, - }); - } - - pub fn set_ui_visible(&self, visible: bool) { - self.ui_visible.store(visible, Ordering::SeqCst); - } - - pub fn is_enabled(&self) -> bool { - self.enabled.load(Ordering::SeqCst) - } - - pub fn snapshot(&self) -> MtpSnapshot { - self.snapshot - .read() - .expect("snapshot lock poisoned") - .clone() - } - - fn set_snapshot(&self, snapshot: MtpSnapshot) { - if let Some(error) = snapshot.error.as_ref() { - eprintln!("android: MTP connection failed: {error}"); - } - *self.snapshot.write().expect("snapshot lock poisoned") = snapshot.clone(); - self.emit(MtpEvent::State { snapshot }); - } - - fn emit(&self, event: MtpEvent) { - if let Some(app) = self.app.read().expect("app lock poisoned").as_ref() { - let _ = app.emit(EVENT_NAME, event); - } - } - - fn log(&self, level: u8, message: impl Into, details: Option) { - let message = message.into(); - eprintln!( - "android: {message}{}", - details - .as_ref() - .map(|value| format!(": {value}")) - .unwrap_or_default() - ); - self.emit(MtpEvent::Log { - level, - message, - details, - }); - } - - fn is_current(&self, generation: u64) -> bool { - self.enabled.load(Ordering::SeqCst) && self.generation.load(Ordering::SeqCst) == generation - } - - async fn request(&self, type_name: &str, data: Value) -> Result { - let connection = self - .connection - .read() - .map_err(|_| "MTP connection lock is unavailable")? - .clone() - .ok_or_else(|| "MTP is not connected".to_string())?; - let request = json_to_frame(type_name, data, connection.next_request_id().await?)?; - let response = connection - .mtp - .request(&request, None) - .await - .map_err(|error| error.to_string())?; - frame_to_json(&response) - } -} - -async fn supervise(generation: u64) { - let manager = manager(); - let mut delay = Duration::from_secs(1); - while manager.is_current(generation) { - let config = manager.config.read().expect("config lock poisoned").clone(); - let Some(config) = config else { return }; - manager.log(2, "Starting native MTP connection", None); - android_status("Connecting"); - match connect(&config).await { - Ok(prepared) => { - delay = Duration::from_secs(1); - let connection = Arc::new(ManagedConnection { - mtp: Arc::new(prepared.connection), - request_ids: prepared.request_ids, - }); - let stale = { - let _guard = manager.start_lock.lock().expect("start lock poisoned"); - let mut current = manager - .connection - .write() - .expect("connection lock poisoned"); - if !manager.is_current(generation) { - true - } else { - *current = Some(connection.clone()); - drop(current); - manager.set_snapshot(MtpSnapshot { - generation, - ready_state: CONNECTED, - identified: true, - state: Some(prepared.initial_state), - error: None, - }); - false - } - }; - if stale { - connection.mtp.sender.close().await; - break; - } - android_status("Connected"); - manager.log(2, "Native MTP connection established", None); - - let (notification_tx, mut notification_rx) = - mpsc::channel(NOTIFICATION_QUEUE_CAPACITY); - let notification_connection = connection.clone(); - let notification_config = config.clone(); - let notification_worker = tokio::spawn(async move { - while let Some(frame) = notification_rx.recv().await { - if !manager.is_current(generation) { - break; - } - if let Err(error) = notify_message( - ¬ification_config, - notification_connection.clone(), - &frame, - ) - .await - { - eprintln!("failed to create background message notification: {error}"); - } - } - }); - - for frame in prepared.buffered_frames { - handle_push(generation, ¬ification_tx, frame).await; - } - - while manager.is_current(generation) { - match connection.mtp.receive().await { - Ok(frame) => handle_push(generation, ¬ification_tx, frame).await, - Err(error) => { - let _guard = manager.start_lock.lock().expect("start lock poisoned"); - if manager.is_current(generation) { - manager.set_snapshot(MtpSnapshot { - generation, - ready_state: DISCONNECTED, - identified: false, - state: None, - error: Some(error.to_string()), - }); - } - break; - } - } - } - drop(notification_tx); - notification_worker.abort(); - if let Err(error) = notification_worker.await { - if !error.is_cancelled() { - eprintln!("background notification worker failed: {error}"); - } - } - let mut current = manager - .connection - .write() - .expect("connection lock poisoned"); - if current - .as_ref() - .is_some_and(|stored| Arc::ptr_eq(stored, &connection)) - { - current.take(); - } - } - Err(error) => { - let _guard = manager.start_lock.lock().expect("start lock poisoned"); - if manager.is_current(generation) { - manager.set_snapshot(MtpSnapshot { - generation, - ready_state: DISCONNECTED, - identified: false, - state: None, - error: Some(error), - }); - } - } - } - - if !manager.is_current(generation) { - break; - } - android_status("Reconnecting"); - tokio::time::sleep(jittered_retry_delay(delay)).await; - delay = (delay * 2).min(Duration::from_secs(60)); - } -} - -fn jittered_retry_delay(delay: Duration) -> Duration { - let entropy = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|duration| duration.subsec_nanos()) - .unwrap_or_default(); - let percent = 80 + entropy % 41; - delay.mul_f64(percent as f64 / 100.0) -} - -async fn connect(config: &MtpConfig) -> Result { - let (url, public_key) = resolve_endpoint(config) - .await - .map_err(|error| format!("endpoint discovery failed: {error}"))?; - manager().log( - 2, - "Resolved Omikron endpoint", - Some(Value::String(url.clone())), - ); - let keyring_bytes = decode_browser_base64(&config.keyring) - .map_err(|error| format!("invalid MTP keyring: {error}"))?; - let keyring = Keyring::from_bytes(&keyring_bytes) - .map_err(|error| format!("invalid MTP keyring: {error}"))?; - let host_key_bytes = decode_sdk_bytes(&public_key) - .map_err(|error| format!("invalid Omikron public key: {error}"))?; - let host_key = PublicKeyBundle::from_bytes(&host_key_bytes) - .map_err(|error| format!("invalid Omikron public key: {error}"))?; - host_key - .validate() - .map_err(|error| format!("invalid Omikron public key: {error}"))?; - let client_config = ClientConfig::new(url) - .with_client_id(config.user_id) - .with_description("client") - .with_policy(Policy::default().with_send_mode(SendMode::SingleStreamPerMessage)) - .with_ping_interval(Duration::from_secs(30)) - .with_max_missed_pings(3); - #[cfg(target_os = "android")] - let client_config = client_config.with_pinned_pem(android_root_certificates().clone()); - let connection = tokio::time::timeout( - Duration::from_secs(30), - MTPClient::auth_connect(client_config, &keyring, &host_key), - ) - .await - .map_err(|_| "transport authentication timed out".to_string())? - .map_err(|error| format!("transport authentication failed: {error}"))?; - manager().log(2, "Native MTP authentication completed", None); - - let (state, buffered_frames) = await_initial_state(&connection).await?; - let request_ids = RequestIdAllocator::new(); - let (initial_state, ack) = prepare_initial_state_ack(&state, &request_ids)?; - let response = tokio::time::timeout(INITIAL_SYNC_TIMEOUT, connection.request(&ack, None)) - .await - .map_err(|_| "state acknowledgement timed out".to_string())? - .map_err(|error| format!("state acknowledgement failed: {error}"))?; - if response - .get_type_name() - .is_some_and(|name| name.starts_with("Error")) - { - return Err(format!("ClientStateAck failed: {response}")); - } - Ok(PreparedConnection { - connection, - request_ids, - initial_state, - buffered_frames, - }) -} - -fn prepare_initial_state_ack( - state: &CommunicationValue, - request_ids: &RequestIdAllocator, -) -> Result<(Value, CommunicationValue), String> { - let initial_state = frame_data_to_json(state)?; - validate_client_state_sync(&initial_state)?; - let data = initial_state - .as_object() - .ok_or("ClientStateSync payload is not an object")?; - let session_id = required_integer(data, "SessionId")?; - let version = required_integer(data, "VersionNumber")?; - let ack = CommunicationValue::new(communication_type("ClientStateAck")?) - .with_id(request_ids.next()?) - .add_typed_default(DataType::SessionId, number_to_data(session_id)) - .add_typed_default(DataType::VersionNumber, number_to_data(version)); - Ok((initial_state, ack)) -} - -fn validate_client_state_sync(state: &Value) -> Result<(), String> { - let data = state - .as_object() - .ok_or("ClientStateSync payload is not an object")?; - if required_integer(data, "SessionId")? <= 0 { - return Err("ClientStateSync SessionId must be a positive integer".into()); - } - for field in ["VersionNumber", "CacheSchemaVersion"] { - if required_integer(data, field)? < 0 { - return Err(format!("ClientStateSync {field} must be nonnegative")); - } - } - match data.get("SyncMode").and_then(Value::as_str) { - Some("full" | "delta") => {} - _ => return Err("ClientStateSync SyncMode must be 'full' or 'delta'".into()), - } - for field in ["Contacts", "Communities", "Calls", "Messages"] { - if !data.get(field).is_some_and(Value::is_array) { - return Err(format!("ClientStateSync {field} must be an array")); - } - } - for field in ["DeletedMessageIds", "DeletedContactIds"] { - if let Some(value) = data.get(field) { - let values = value - .as_array() - .ok_or_else(|| format!("ClientStateSync {field} must be an array"))?; - if values.iter().any(|value| !value.is_number()) { - return Err(format!("ClientStateSync {field} must contain numbers")); - } - } - } - validate_object_array(data, "Communities", |_| Ok(()))?; - validate_object_array(data, "Contacts", validate_contact)?; - validate_object_array(data, "Calls", validate_call)?; - validate_object_array(data, "Messages", validate_message)?; - Ok(()) -} - -fn validate_object_array( - data: &Map, - field: &str, - validate: impl Fn(&Map) -> Result<(), String>, -) -> Result<(), String> { - let values = data - .get(field) - .and_then(Value::as_array) - .ok_or_else(|| format!("ClientStateSync {field} must be an array"))?; - for value in values { - let object = value - .as_object() - .ok_or_else(|| format!("ClientStateSync {field} entries must be objects"))?; - validate(object)?; - } - Ok(()) -} - -fn validate_contact(contact: &Map) -> Result<(), String> { - if !contact.get("UserId").is_some_and(Value::is_number) { - return Err("ClientStateSync contact omitted numeric UserId".into()); - } - if let Some(messages) = contact.get("Messages") { - let messages = messages - .as_array() - .ok_or("ClientStateSync contact Messages must be an array")?; - for message in messages { - validate_message( - message - .as_object() - .ok_or("ClientStateSync contact message must be an object")?, - )?; - } - } - Ok(()) -} - -fn validate_call(call: &Map) -> Result<(), String> { - if !call.get("CallId").is_some_and(Value::is_string) { - return Err("ClientStateSync call omitted string CallId".into()); - } - let members = call - .get("CallMembers") - .and_then(Value::as_array) - .ok_or("ClientStateSync call omitted CallMembers array")?; - if members.iter().any(|member| !member.is_number()) { - return Err("ClientStateSync CallMembers must contain numbers".into()); - } - Ok(()) -} - -fn validate_message(message: &Map) -> Result<(), String> { - for field in ["SenderId", "SendTime"] { - if !message.get(field).is_some_and(Value::is_number) { - return Err(format!("ClientStateSync message omitted numeric {field}")); - } - } - let content = message - .get("Content") - .and_then(Value::as_str) - .ok_or("ClientStateSync message omitted string Content")?; - STANDARD - .decode(content) - .or_else(|_| STANDARD_NO_PAD.decode(content)) - .map_err(|_| "ClientStateSync message Content must be base64".to_string())?; - if let Some(state) = message.get("MessageState") { - match state.as_str() { - Some("read" | "received" | "sent" | "sending" | "awaiting") => {} - _ => return Err("ClientStateSync message has invalid MessageState".into()), - } - } - Ok(()) -} - -fn required_integer(data: &Map, field: &str) -> Result { - let value = data - .get(field) - .ok_or_else(|| format!("ClientStateSync omitted {field}"))?; - if let Some(value) = value.as_i64() { - return Ok(value as i128); - } - value - .as_u64() - .map(|value| value as i128) - .ok_or_else(|| format!("ClientStateSync {field} must be an integer")) -} - -async fn await_initial_state( - connection: &MTPConnection, -) -> Result<(CommunicationValue, Vec), String> { - let mut buffered = Vec::new(); - let deadline = tokio::time::Instant::now() + INITIAL_SYNC_TIMEOUT; - - loop { - let frame = tokio::time::timeout_at(deadline, connection.receive()) - .await - .map_err(|_| "initial state synchronization timed out".to_string())? - .map_err(|error| format!("initial state synchronization failed: {error}"))?; - - if frame.is_type(CommunicationType::ErrorNoIota) { - return Err("No Iota is currently connected".into()); - } - - if frame.get_type_name() == Some("ClientStateSync") { - return Ok((frame, buffered)); - } - - if buffered.len() == MAX_BUFFERED_INITIAL_FRAMES { - return Err("initial state synchronization buffered too many frames".into()); - } - buffered.push(frame); - } -} - -async fn resolve_endpoint(config: &MtpConfig) -> Result<(String, String), String> { - if let (Some(url), Some(key)) = ( - config.forced_omikron_url.as_ref(), - config.forced_omikron_public_key.as_ref(), - ) { - return Ok((url.clone(), key.clone())); - } - #[derive(Deserialize)] - struct Omikron { - ip_address: String, - port: u16, - public_key: String, - } - let root = config.omega_url.trim_end_matches('/'); - let client = reqwest::Client::builder() - .connect_timeout(Duration::from_secs(10)) - .timeout(Duration::from_secs(20)); - #[cfg(target_os = "android")] - let client = client.tls_certs_only( - reqwest::Certificate::from_pem_bundle(android_root_certificates()) - .map_err(|error| format!("invalid bundled root certificates: {error}"))?, - ); - let response = client - .build() - .map_err(|error| error.to_string())? - .get(format!("{root}/api/get/omikron/{}", config.user_id)) - .send() - .await - .map_err(|error| error.to_string())?; - if !response.status().is_success() { - return Err(format!("Omikron lookup failed: {}", response.status())); - } - let data: Omikron = response.json().await.map_err(|error| error.to_string())?; - Ok(( - format!("https://{}:{}", data.ip_address, data.port), - data.public_key, - )) -} - -async fn handle_push( - generation: u64, - notification_tx: &mpsc::Sender, - frame: CommunicationValue, -) { - let manager = manager(); - if let Ok(message) = frame_to_json(&frame) { - manager.emit(MtpEvent::Message { - generation, - message, - }); - } - if frame.is_type(CommunicationType::MessageState) - && frame.get_str(DataType::MessageState) == Some("read") - { - if let Some(partner_id) = frame - .get_data(DataType::ChatPartnerId) - .and_then(DataValue::as_number) - .and_then(|value| u64::try_from(value).ok()) - { - if let Err(error) = android_cancel_notification(partner_id) { - eprintln!("failed to clear read message notification: {error}"); - } - } - } - if frame.is_type(CommunicationType::MessageLive) && !manager.ui_visible.load(Ordering::SeqCst) { - if notification_tx.try_send(frame).is_err() { - eprintln!("background message notification queue is full"); - } - } -} - -async fn notify_message( - config: &MtpConfig, - connection: Arc, - frame: &CommunicationValue, -) -> Result<(), String> { - let sender_id = frame - .get_data(DataType::SenderId) - .and_then(DataValue::as_number) - .and_then(|value| u64::try_from(value).ok()) - .ok_or("MessageLive omitted SenderId")?; - let message = frame - .get_data(DataType::Message) - .ok_or("MessageLive omitted Message")?; - let content = container_value_by_name(message, "Content") - .and_then(DataValue::as_str) - .ok_or("MessageLive omitted Content")?; - let keyring_bytes = decode_browser_base64(&config.keyring)?; - let keyring = Keyring::from_bytes(&keyring_bytes).map_err(|error| error.to_string())?; - let chat_id = derive_chat_id(config.user_id, sender_id); - let secret_id = format!("chat:{chat_id}:main"); - let secret_request = CommunicationValue::new(CommunicationType::GetChatSecret) - .with_id(connection.next_request_id().await?) - .add_typed_default(DataType::UserId, DataValue::Str(config.user_id.to_string())) - .add_typed_default(DataType::ChatId, DataValue::Str(chat_id.clone())) - .add_typed_default(DataType::SecretId, DataValue::Str(secret_id.clone())); - let secret = connection - .mtp - .request(&secret_request, None) - .await - .map_err(|error| error.to_string())?; - if !secret.is_type(CommunicationType::ChatSecretResponse) { - return Err(format!("GetChatSecret failed: {secret}")); - } - if secret.get_str(DataType::WrappingScheme) != Some(CHAT_SECRET_SCHEME) { - return Err("unsupported chat secret wrapping scheme".into()); - } - let version = secret - .get_data(DataType::VersionNumber) - .and_then(DataValue::as_number) - .ok_or("missing secret version")?; - let encrypted_secret = secret - .get_bytes(DataType::EncryptedSecret) - .ok_or("missing encrypted secret")?; - let kem_ciphertext = secret - .get_bytes(DataType::KemCiphertext) - .ok_or("missing KEM ciphertext")?; - let shared = HybridKem::decapsulate(&keyring.kem_secret_key, kem_ciphertext) - .map_err(|error| error.to_string())?; - let wrapping_key = derive_encryption_key( - &shared, - CHAT_SECRET_SALT, - format!("{chat_id}:{secret_id}:{version}").as_bytes(), - ) - .map_err(|error| error.to_string())?; - let chat_secret = ChaCha20Poly1305::new(wrapping_key) - .decrypt(encrypted_secret, b"") - .map_err(|error| error.to_string())?; - let message_key = derive_encryption_key(&chat_secret, CHAT_MESSAGE_SALT, b"message-content") - .map_err(|error| error.to_string())?; - let ciphertext = STANDARD - .decode(content) - .map_err(|error| error.to_string())?; - let plaintext = ChaCha20Poly1305::new(message_key) - .decrypt(&ciphertext, b"") - .map_err(|error| error.to_string())?; - - let user_request = CommunicationValue::new(CommunicationType::GetUserData) - .with_id(connection.next_request_id().await?) - .add_typed_default( - DataType::UserId, - DataValue::UnsignedNumber(sender_id as u128), - ); - let user = connection - .mtp - .request(&user_request, None) - .await - .map_err(|error| error.to_string())?; - let sender = user - .get_str(DataType::Display) - .or_else(|| user.get_str(DataType::Username)) - .map(str::to_owned) - .unwrap_or_else(|| format!("User {sender_id}")); - let avatar = user - .get_str(DataType::Avatar) - .and_then(|avatar| decode_browser_base64(avatar).ok()); - android_notify( - sender_id, - &sender, - &String::from_utf8_lossy(&plaintext), - avatar.as_deref(), - )?; - Ok(()) -} - -fn derive_chat_id(own: u64, peer: u64) -> String { - format!("{}:{}", own.min(peer), own.max(peer)) -} - -fn decode_browser_base64(value: &str) -> Result, String> { - let normalized: String = value - .chars() - .filter(|character| !character.is_ascii_whitespace()) - .collect(); - if normalized.is_empty() { - return Err("value is empty".into()); - } - STANDARD - .decode(&normalized) - .or_else(|_| STANDARD_NO_PAD.decode(&normalized)) - .map_err(|_| "invalid base64 encoding".into()) -} - -fn decode_sdk_bytes(value: &str) -> Result, String> { - let trimmed = value.trim(); - let hex: String = trimmed - .strip_prefix("0x") - .or_else(|| trimmed.strip_prefix("0X")) - .unwrap_or(trimmed) - .chars() - .filter(|character| !matches!(character, ' ' | '\t' | '\r' | '\n' | ':' | '_' | '-')) - .collect(); - if !hex.is_empty() && hex.chars().all(|character| character.is_ascii_hexdigit()) { - if hex.len() % 2 != 0 { - return Err("hex string has an odd length".into()); - } - return (0..hex.len()) - .step_by(2) - .map(|offset| { - u8::from_str_radix(&hex[offset..offset + 2], 16) - .map_err(|_| "invalid hex encoding".to_string()) - }) - .collect(); - } - decode_browser_base64(trimmed) -} - -#[cfg(target_os = "android")] -fn android_root_certificates() -> &'static Vec { - static ROOTS: OnceLock> = OnceLock::new(); - ROOTS.get_or_init(|| { - let mut pem = Vec::new(); - for certificate in webpki_root_certs::TLS_SERVER_ROOT_CERTS { - pem.extend_from_slice(b"-----BEGIN CERTIFICATE-----\n"); - let encoded = STANDARD.encode(certificate.as_ref()); - for line in encoded.as_bytes().chunks(64) { - pem.extend_from_slice(line); - pem.push(b'\n'); - } - pem.extend_from_slice(b"-----END CERTIFICATE-----\n"); - } - pem.extend_from_slice(ROOT_YE_PEM); - pem.extend_from_slice(YE1_PEM); - pem - }) -} - -fn container_value(value: &DataValue, field: DataType) -> Option<&DataValue> { - let id = field.try_to_id(&TypeMap::latest())?; - value.get_field(id) -} - -fn container_value_by_name<'a>(value: &'a DataValue, field: &str) -> Option<&'a DataValue> { - container_value(value, DataType::from_name(field)?) -} - -fn communication_type(name: &str) -> Result { - CommunicationType::from_name(name).ok_or_else(|| format!("unknown communication type: {name}")) -} - -fn json_to_frame(type_name: &str, data: Value, id: u32) -> Result { - let comm_type = communication_type(type_name)?; - let mut frame = CommunicationValue::new(comm_type).with_id(id); - let Value::Object(fields) = data else { - return Err("MTP request data must be an object".into()); - }; - for (name, value) in fields { - let data_type = - DataType::from_name(&name).ok_or_else(|| format!("unknown data type: {name}"))?; - frame = frame.add_typed_default(data_type, json_to_data(&name, value)?); - } - Ok(frame) -} - -fn json_to_data(field: &str, value: Value) -> Result { - Ok(match value { - Value::Null => DataValue::Null, - Value::Bool(value) => DataValue::from(value), - Value::Number(value) => { - if let Some(integer) = value.as_u64() { - DataValue::UnsignedNumber(integer as u128) - } else if let Some(integer) = value.as_i64() { - DataValue::SignedNumber(integer as i128) - } else { - DataValue::Float(value.as_f64().ok_or("invalid number")?) - } - } - Value::String(value) => DataValue::Str(value), - Value::Array(values) if is_bytes_field(field) => DataValue::Bytes( - values - .into_iter() - .map(|value| { - value - .as_u64() - .and_then(|n| u8::try_from(n).ok()) - .ok_or("invalid byte") - }) - .collect::, _>>()?, - ), - Value::Array(values) => DataValue::Array( - values - .into_iter() - .map(|value| json_to_data(field, value)) - .collect::>()?, - ), - Value::Object(fields) => { - let mut entries = Vec::with_capacity(fields.len()); - for (name, value) in fields { - let data_type = DataType::from_name(&name) - .ok_or_else(|| format!("unknown nested data type: {name}"))?; - let id = data_type - .try_to_id(&TypeMap::latest()) - .ok_or_else(|| format!("unmapped data type: {name}"))?; - entries.push((id, json_to_data(&name, value)?)); - } - DataValue::Container(entries) - } - }) -} - -fn number_to_data(value: i128) -> DataValue { - if value >= 0 { - DataValue::UnsignedNumber(value as u128) - } else { - DataValue::SignedNumber(value) - } -} - -fn is_bytes_field(field: &str) -> bool { - matches!( - field, - "EncryptedSecret" | "KemCiphertext" | "Payload" | "PublicKeys" - ) -} - -fn frame_to_json(frame: &CommunicationValue) -> Result { - let mut result = Map::new(); - if let Some(id) = frame.id() { - result.insert("id".into(), Value::from(id)); - } - result.insert( - "type".into(), - Value::String(frame.get_type_name().unwrap_or("Unknown").to_owned()), - ); - result.insert("data".into(), frame_data_to_json(frame)?); - Ok(Value::Object(result)) -} - -fn frame_data_to_json(frame: &CommunicationValue) -> Result { - let map = frame.type_map().cloned().unwrap_or_else(TypeMap::latest); - let mut result = Map::new(); - let entries = frame - .data() - .ok_or("MTP frame payload is not a data container")?; - for (id, value) in entries { - let name = map - .data_type_name(id.0) - .ok_or_else(|| format!("unknown data type id: {}", id.0))?; - result.insert(name.to_owned(), data_to_json(value, &map)?); - } - Ok(Value::Object(result)) -} - -fn data_to_json(value: &DataValue, map: &TypeMap) -> Result { - Ok(match value { - DataValue::BoolTrue => Value::Bool(true), - DataValue::BoolFalse => Value::Bool(false), - DataValue::Bool(value) => Value::Bool(*value), - DataValue::SignedNumber(value) => number_to_json(*value)?, - DataValue::UnsignedNumber(value) => { - number_to_json(i128::try_from(*value).map_err(|_| "number exceeds JSON range")?)? - } - DataValue::Float(value) => serde_json::Number::from_f64(*value) - .map(Value::Number) - .ok_or("invalid float")?, - DataValue::Str(value) => Value::String(value.clone()), - DataValue::Bytes(value) => Value::Array(value.iter().copied().map(Value::from).collect()), - DataValue::Array(values) => Value::Array( - values - .iter() - .map(|value| data_to_json(value, map)) - .collect::>()?, - ), - DataValue::Container(entries) => { - let mut object = Map::new(); - for (id, value) in entries { - let name = map - .data_type_name(id.0) - .ok_or_else(|| format!("unknown nested data type id: {}", id.0))?; - object.insert(name.to_owned(), data_to_json(value, map)?); - } - Value::Object(object) - } - DataValue::Null => Value::Null, - _ => return Err("encrypted protocol values cannot cross the frontend bridge".into()), - }) -} - -fn number_to_json(value: i128) -> Result { - i64::try_from(value) - .map(Value::from) - .map_err(|_| "number exceeds JSON range".into()) -} - -#[cfg(test)] -mod tests { - use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; - use serde_json::json; - - use super::{ - container_value_by_name, decode_browser_base64, decode_sdk_bytes, frame_to_json, - jittered_retry_delay, json_to_frame, prepare_initial_state_ack, RequestIdAllocator, - }; - - #[test] - fn browser_base64_accepts_file_whitespace_and_missing_padding() { - assert_eq!(decode_browser_base64(" AQID\r\n").unwrap(), [1, 2, 3]); - assert_eq!(decode_browser_base64("AQI").unwrap(), [1, 2]); - } - - #[test] - fn sdk_bytes_accept_hex_and_base64() { - assert_eq!(decode_sdk_bytes("0x01:02-ff").unwrap(), [1, 2, 255]); - assert_eq!(decode_sdk_bytes("AQI=").unwrap(), [1, 2]); - } - - #[test] - fn request_ids_are_nonzero_and_monotonic() { - let ids = RequestIdAllocator::new(); - - assert_eq!(ids.next().unwrap(), 1); - assert_eq!(ids.next().unwrap(), 2); - } - - #[test] - fn retry_jitter_stays_within_policy_bounds() { - let delay = jittered_retry_delay(std::time::Duration::from_secs(10)); - - assert!(delay >= std::time::Duration::from_secs(8)); - assert!(delay <= std::time::Duration::from_secs(12)); - } - - #[test] - fn json_content_uses_content_wire_type() { - let frame = json_to_frame( - "MessageEdit", - json!({ - "Content": "ciphertext", - "ChatPartnerId": 42, - "SendTime": 10, - }), - 1, - ) - .unwrap(); - - assert_eq!( - frame - .get_data(DataType::Content) - .and_then(DataValue::as_str), - Some("ciphertext") - ); - } - - #[test] - fn nested_json_content_uses_content_wire_type() { - let frame = json_to_frame( - "MessageEdit", - json!({ - "Message": { "Content": "ciphertext" }, - }), - 1, - ) - .unwrap(); - let message = frame.get_data(DataType::Message).unwrap(); - - assert_eq!( - container_value_by_name(message, "Content").and_then(DataValue::as_str), - Some("ciphertext") - ); - } - - #[test] - fn content_is_exposed_to_frontend() { - let frame = CommunicationValue::new(CommunicationType::MessageEditLive) - .with_id(1) - .add_typed_default(DataType::Content, DataValue::Str("ciphertext".into())); - - let json = frame_to_json(&frame).unwrap(); - - assert_eq!(json["data"]["Content"], "ciphertext"); - } - - fn valid_initial_state() -> CommunicationValue { - CommunicationValue::new(communication_type("ClientStateSync").unwrap()) - .add_typed_default(DataType::SessionId, DataValue::UnsignedNumber(1)) - .add_typed_default(DataType::VersionNumber, DataValue::UnsignedNumber(0)) - .add_typed_default(DataType::CacheSchemaVersion, DataValue::UnsignedNumber(0)) - .add_typed_default(DataType::SyncMode, DataValue::Str("full".into())) - .add_typed_default(DataType::Contacts, DataValue::Array(vec![])) - .add_typed_default(DataType::Communities, DataValue::Array(vec![])) - .add_typed_default(DataType::Calls, DataValue::Array(vec![])) - .add_typed_default(DataType::Messages, DataValue::Array(vec![])) - } - - #[test] - fn valid_initial_state_is_prepared_before_ack() { - let ids = RequestIdAllocator::new(); - let (state, ack) = prepare_initial_state_ack(&valid_initial_state(), &ids).unwrap(); - - assert_eq!(state["SyncMode"], "full"); - assert_eq!(ack.get_type_name(), Some("ClientStateAck")); - assert_eq!(ack.id(), Some(1)); - } - - #[test] - fn malformed_initial_state_does_not_prepare_ack() { - let ids = RequestIdAllocator::new(); - let malformed = valid_initial_state() - .add_typed_default(DataType::SyncMode, DataValue::Str("invalid".into())); - - assert!(prepare_initial_state_ack(&malformed, &ids).is_err()); - assert_eq!( - ids.next().unwrap(), - 1, - "no acknowledgement ID was allocated" - ); - } - - #[test] - fn malformed_nested_initial_state_does_not_prepare_ack() { - let ids = RequestIdAllocator::new(); - let malformed = CommunicationValue::new(communication_type("ClientStateSync").unwrap()) - .add_typed_default(DataType::SessionId, DataValue::UnsignedNumber(1)) - .add_typed_default(DataType::VersionNumber, DataValue::UnsignedNumber(0)) - .add_typed_default(DataType::CacheSchemaVersion, DataValue::UnsignedNumber(0)) - .add_typed_default(DataType::SyncMode, DataValue::Str("full".into())) - .add_typed_default( - DataType::Contacts, - DataValue::Array(vec![DataValue::Container(vec![])]), - ) - .add_typed_default(DataType::Communities, DataValue::Array(vec![])) - .add_typed_default(DataType::Calls, DataValue::Array(vec![])) - .add_typed_default(DataType::Messages, DataValue::Array(vec![])); - - assert!(prepare_initial_state_ack(&malformed, &ids).is_err()); - assert_eq!( - ids.next().unwrap(), - 1, - "no acknowledgement ID was allocated" - ); - } -} - -#[tauri::command] -pub async fn mtp_request(type_name: String, data: Value) -> Result { - manager().request(&type_name, data).await -} - -#[tauri::command] -pub fn mtp_status() -> MtpSnapshot { - manager().snapshot() -} - -#[tauri::command] -pub fn mtp_store_credentials(config: MtpConfig) -> Result<(), String> { - let serialized = serde_json::to_string(&config).map_err(|error| error.to_string())?; - android_store_config(&serialized)?; - *manager().config.write().expect("config lock poisoned") = Some(config); - Ok(()) -} - -#[tauri::command] -pub fn mtp_has_credentials() -> Result { - android_has_config() -} - -#[tauri::command] -pub fn mtp_load_keyring() -> Option { - manager() - .config - .read() - .expect("config lock poisoned") - .as_ref() - .map(|config| config.keyring.clone()) -} - -#[tauri::command] -pub fn mtp_set_enabled(enabled: bool) -> Result<(), String> { - android_set_enabled(enabled)?; - if !enabled { - manager().stop(); - } - Ok(()) -} - -#[tauri::command] -pub fn mtp_is_ignoring_battery_optimizations() -> Result { - android_is_ignoring_battery_optimizations() -} - -#[tauri::command] -pub fn mtp_request_battery_exemption() -> Result<(), String> { - android_request_battery_exemption() -} - -#[tauri::command] -pub fn mtp_set_ui_visible(visible: bool) { - manager().set_ui_visible(visible); -} - -#[tauri::command] -pub fn mtp_post_message_notification( - sender_id: u64, - sender: String, - body: String, - avatar: Option, -) -> Result { - #[cfg(target_os = "android")] - { - let avatar = avatar.as_deref().map(decode_browser_base64).transpose()?; - android_notify(sender_id, &sender, &body, avatar.as_deref())?; - Ok(true) - } - #[cfg(not(target_os = "android"))] - { - let _ = (sender_id, sender, body, avatar); - Ok(false) - } -} - -#[cfg(not(target_os = "android"))] -fn android_store_config(_: &str) -> Result<(), String> { - Ok(()) -} -#[cfg(not(target_os = "android"))] -fn android_has_config() -> Result { - Ok(manager() - .config - .read() - .expect("config lock poisoned") - .is_some()) -} -#[cfg(not(target_os = "android"))] -fn android_set_enabled(_: bool) -> Result<(), String> { - Ok(()) -} -#[cfg(not(target_os = "android"))] -fn android_status(_: &str) {} -#[cfg(not(target_os = "android"))] -fn android_notify(_: u64, _: &str, _: &str, _: Option<&[u8]>) -> Result<(), String> { - Ok(()) -} -#[cfg(not(target_os = "android"))] -fn android_cancel_notification(_: u64) -> Result<(), String> { - Ok(()) -} -#[cfg(not(target_os = "android"))] -fn android_is_ignoring_battery_optimizations() -> Result { - Ok(true) -} -#[cfg(not(target_os = "android"))] -fn android_request_battery_exemption() -> Result<(), String> { - Ok(()) -} - -#[cfg(target_os = "android")] -mod android { - use std::sync::OnceLock; - - use jni::{ - jni_sig, jni_str, - objects::{Global, JClass, JObject, JString, JValue}, - Env, EnvUnowned, JavaVM, - }; - use serde_json::Value; - - pub struct Host { - vm: JavaVM, - context: Global>, - bridge: Global>, - } - - static HOST: OnceLock = OnceLock::new(); - - pub fn attach(env: &mut Env, context: JObject) -> Result<(), String> { - if HOST.get().is_some() { - return Ok(()); - } - let class = env - .find_class(jni_str!("net/tensamin/client/NativeMtpBridge")) - .map_err(|e| e.to_string())?; - let bridge = env - .get_static_field( - class, - jni_str!("INSTANCE"), - jni_sig!("Lnet/tensamin/client/NativeMtpBridge;"), - ) - .and_then(|value| value.l()) - .map_err(|e| e.to_string())?; - HOST.set(Host { - vm: env.get_java_vm().map_err(|e| e.to_string())?, - context: env.new_global_ref(context).map_err(|e| e.to_string())?, - bridge: env.new_global_ref(bridge).map_err(|e| e.to_string())?, - }) - .map_err(|_| "Android MTP host is already attached".to_string()) - } - - fn with_env(call: impl FnOnce(&mut Env, &Host) -> Result) -> Result { - let host = HOST.get().ok_or("Android MTP host is not attached")?; - host.vm - .attach_current_thread(|env| Ok::<_, jni::errors::Error>(call(env, host))) - .map_err(|e| e.to_string())? - } - - pub fn store_config(config: &str) -> Result<(), String> { - with_env(|env, host| { - let value = env.new_string(config).map_err(|e| e.to_string())?; - env.call_method( - host.bridge.as_obj(), - jni_str!("storeConfig"), - jni_sig!("(Landroid/content/Context;Ljava/lang/String;)V"), - &[ - JValue::Object(host.context.as_obj()), - JValue::Object(&value), - ], - ) - .map_err(|e| e.to_string())?; - Ok(()) - }) - } - - pub fn has_config() -> Result { - with_env(|env, host| { - env.call_method( - host.bridge.as_obj(), - jni_str!("hasConfig"), - jni_sig!("(Landroid/content/Context;)Z"), - &[JValue::Object(host.context.as_obj())], - ) - .and_then(|value| value.z()) - .map_err(|e| e.to_string()) - }) - } - - pub fn set_enabled(enabled: bool) -> Result<(), String> { - with_env(|env, host| { - env.call_method( - host.bridge.as_obj(), - jni_str!("setServiceEnabled"), - jni_sig!("(Landroid/content/Context;Z)V"), - &[ - JValue::Object(host.context.as_obj()), - JValue::Bool(enabled.into()), - ], - ) - .map_err(|e| e.to_string())?; - Ok(()) - }) - } - - pub fn status(status: &str) { - let _ = with_env(|env, host| { - let status = env.new_string(status).map_err(|e| e.to_string())?; - env.call_method( - host.bridge.as_obj(), - jni_str!("updateServiceStatus"), - jni_sig!("(Landroid/content/Context;Ljava/lang/String;)V"), - &[ - JValue::Object(host.context.as_obj()), - JValue::Object(&status), - ], - ) - .map_err(|e| e.to_string())?; - Ok(()) - }); - } - - pub fn notify( - sender_id: u64, - sender: &str, - body: &str, - avatar: Option<&[u8]>, - ) -> Result<(), String> { - with_env(|env, host| { - let sender = env.new_string(sender).map_err(|e| e.to_string())?; - let body = env.new_string(body).map_err(|e| e.to_string())?; - let avatar = env - .byte_array_from_slice(avatar.unwrap_or_default()) - .map_err(|e| e.to_string())?; - env.call_method( - host.bridge.as_obj(), - jni_str!("postMessageNotification"), - jni_sig!("(Landroid/content/Context;JLjava/lang/String;Ljava/lang/String;[B)V"), - &[ - JValue::Object(host.context.as_obj()), - JValue::Long(sender_id as i64), - JValue::Object(&sender), - JValue::Object(&body), - JValue::Object(&avatar), - ], - ) - .map_err(|e| e.to_string())?; - Ok(()) - }) - } - - pub fn cancel_notification(sender_id: u64) -> Result<(), String> { - with_env(|env, host| { - env.call_method( - host.bridge.as_obj(), - jni_str!("cancelMessageNotification"), - jni_sig!("(Landroid/content/Context;J)V"), - &[ - JValue::Object(host.context.as_obj()), - JValue::Long(sender_id as i64), - ], - ) - .map_err(|e| e.to_string())?; - Ok(()) - }) - } - - pub fn is_ignoring_battery_optimizations() -> Result { - with_env(|env, host| { - env.call_method( - host.bridge.as_obj(), - jni_str!("isIgnoringBatteryOptimizations"), - jni_sig!("(Landroid/content/Context;)Z"), - &[JValue::Object(host.context.as_obj())], - ) - .and_then(|value| value.z()) - .map_err(|e| e.to_string()) - }) - } - - pub fn request_battery_exemption() -> Result<(), String> { - with_env(|env, host| { - env.call_method( - host.bridge.as_obj(), - jni_str!("requestBatteryExemption"), - jni_sig!("(Landroid/content/Context;)V"), - &[JValue::Object(host.context.as_obj())], - ) - .map_err(|e| e.to_string())?; - Ok(()) - }) - } - - #[no_mangle] - pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeAttach<'caller>( - mut env: EnvUnowned<'caller>, - _class: JClass, - context: JObject<'caller>, - ) { - let _ = env.with_env(|env| { - let _ = attach(env, context); - Ok::<_, jni::errors::Error>(()) - }); - } - - #[no_mangle] - pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeStart<'caller>( - mut env: EnvUnowned<'caller>, - _class: JClass, - config: JString<'caller>, - ) { - let _ = env.with_env(|env| { - if let Ok(config) = config.mutf8_chars(env) { - if let Ok(config) = serde_json::from_str(config.to_str().as_ref()) { - super::manager().configure_and_start(config); - } - } - Ok::<_, jni::errors::Error>(()) - }); - } - - #[no_mangle] - pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeStop( - _env: EnvUnowned, - _class: JClass, - ) { - let _ = std::panic::catch_unwind(|| super::manager().stop()); - } - - #[no_mangle] - pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeSetUiState( - _env: EnvUnowned, - _class: JClass, - visible: jni::sys::jboolean, - ) { - super::manager().set_ui_visible(visible); - } - - #[no_mangle] - pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeLog<'caller>( - mut env: EnvUnowned<'caller>, - _class: JClass, - level: jni::sys::jint, - message: JString<'caller>, - details: JString<'caller>, - ) { - let _ = env.with_env(|env| { - if let (Ok(message), Ok(details)) = (message.mutf8_chars(env), details.mutf8_chars(env)) - { - let message = message.to_str(); - let details = details.to_str(); - super::manager().log( - level.clamp(0, 3) as u8, - message.into_owned(), - (!details.is_empty()).then(|| Value::String(details.into_owned())), - ); - } - Ok::<_, jni::errors::Error>(()) - }); - } -} - -#[cfg(target_os = "android")] -use android::{ - cancel_notification as android_cancel_notification, has_config as android_has_config, - is_ignoring_battery_optimizations as android_is_ignoring_battery_optimizations, - notify as android_notify, request_battery_exemption as android_request_battery_exemption, - set_enabled as android_set_enabled, status as android_status, - store_config as android_store_config, -}; diff --git a/apps/tauri/src-tauri/tauri.conf.json b/apps/tauri/src-tauri/tauri.conf.json index 3fcf7bc..c416f81 100644 --- a/apps/tauri/src-tauri/tauri.conf.json +++ b/apps/tauri/src-tauri/tauri.conf.json @@ -1,13 +1,13 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Tensamin", - "version": "0.0.0", + "version": "0.1.0", "mainBinaryName": "tensamin", "identifier": "net.tensamin.client", "build": { - "beforeDevCommand": "cd ../web && pnpm run dev && cd ../tauri", + "beforeDevCommand": "cd ../web && bun run dev && cd ../tauri", "devUrl": "http://localhost:3000", - "beforeBuildCommand": "cd ../.. && pnpm run build:web && cd apps/tauri", + "beforeBuildCommand": "cd ../.. && bun run build:web && cd apps/tauri", "frontendDist": "../../web/dist" }, "app": { @@ -25,11 +25,8 @@ } }, "bundle": { - "android": { - "debugApplicationIdSuffix": ".dev" - }, "active": true, - "targets": [], + "targets": ["app", "deb", "rpm", "nsis", "msi", "dmg"], "icon": [ "icons/32x32.png", "icons/128x128.png", @@ -40,6 +37,9 @@ }, "plugins": { "deep-link": { + "desktop": { + "schemes": ["tensamin"] + }, "mobile": [ { "scheme": ["tensamin"], diff --git a/packages/shared/src/desktopMedia.tsx b/apps/tauri/src/context.tsx similarity index 51% rename from packages/shared/src/desktopMedia.tsx rename to apps/tauri/src/context.tsx index 6799966..7d86f00 100644 --- a/packages/shared/src/desktopMedia.tsx +++ b/apps/tauri/src/context.tsx @@ -1,11 +1,11 @@ import { createContext, useContext, useMemo, type ReactNode } from "react"; +import { invoke, isTauri } from "@tauri-apps/api/core"; export type DesktopScreenShareSource = { id: string; kind: "screen" | "window"; name: string; subtitle?: string | null; - thumbnail?: string | null; }; export type DesktopScreenShareAudioOutput = { @@ -15,51 +15,12 @@ export type DesktopScreenShareAudioOutput = { }; export type DesktopScreenShareCapabilities = { - runtime?: "electron" | "tauri"; platform: "linux" | "macos" | "windows" | "other"; showAudioOutputSelector: boolean; showAudioSwitch: boolean; hasReliableSystemAudio: boolean; }; - - -declare global { - interface Window { - tensaminDesktop?: { - media?: { - getScreenShareCapabilities?: () => Promise; - listScreenShareSources?: () => Promise; - listScreenShareAudioOutputs?: () => Promise< - DesktopScreenShareAudioOutput[] - >; - selectScreenShareSource?: (sourceId: string) => Promise; - }; - call?: { - setStatus?: (status: { - inCall: boolean; - speaking: boolean; - iconDataUrl?: string; - }) => Promise; - }; - hotkeys?: { - setBindings?: ( - bindings: Array<{ id: string; accelerator: string }>, - ) => Promise>; - setSuspended?: (suspended: boolean) => Promise>; - onTriggered?: (callback: (id: string) => void) => () => void; - }; - secureStorage?: { - getStatus?: () => Promise<{ available: boolean; backend: string | null }>; - load?: (key: string) => Promise; - save?: (key: string, value: string) => Promise; - delete?: (key: string) => Promise; - clear?: () => Promise; - }; -}; - } -} - type DesktopMediaContextValue = { getScreenShareCapabilities: () => Promise; listScreenShareSources: () => Promise; @@ -67,7 +28,6 @@ type DesktopMediaContextValue = { }; const defaultCapabilities: DesktopScreenShareCapabilities = { - runtime: undefined, platform: "other", showAudioOutputSelector: false, showAudioSwitch: false, @@ -79,29 +39,33 @@ const desktopMediaContext = createContext( ); async function listScreenShareSources(): Promise { - if (window.tensaminDesktop?.media?.listScreenShareSources) { - return window.tensaminDesktop.media.listScreenShareSources(); + if (!isTauri()) { + return []; } - return []; + return invoke("list_screen_share_sources"); } async function listScreenShareAudioOutputs(): Promise< DesktopScreenShareAudioOutput[] > { - if (window.tensaminDesktop?.media?.listScreenShareAudioOutputs) { - return window.tensaminDesktop.media.listScreenShareAudioOutputs(); + if (!isTauri()) { + return []; } - return []; + return invoke( + "list_screen_share_audio_outputs", + ); } async function getScreenShareCapabilities(): Promise { - if (window.tensaminDesktop?.media?.getScreenShareCapabilities) { - return window.tensaminDesktop.media.getScreenShareCapabilities(); + if (!isTauri()) { + return defaultCapabilities; } - return defaultCapabilities; + return invoke( + "get_screen_share_capabilities", + ); } export function useDesktopMedia() { @@ -114,11 +78,7 @@ export function useDesktopMedia() { return value; } -export default function DesktopMediaProvider({ - children, -}: { - children: ReactNode; -}) { +export default function Provider({ children }: { children: ReactNode }) { const value = useMemo( () => ({ getScreenShareCapabilities, diff --git a/apps/tauri/src/deeplinkHandler.tsx b/apps/tauri/src/deeplinkHandler.tsx index dcbb5c7..36def6d 100644 --- a/apps/tauri/src/deeplinkHandler.tsx +++ b/apps/tauri/src/deeplinkHandler.tsx @@ -7,13 +7,12 @@ import { } from "react"; import { getCurrent, onOpenUrl } from "@tauri-apps/plugin-deep-link"; import { isTauri } from "@tauri-apps/api/core"; -import { useIsMobile } from "@methanium/ui"; - - -export const deeplinkContext = createContext<{ +type DeeplinkContextValue = { deeplinks: readonly string[]; -} | undefined>( +}; + +export const deeplinkContext = createContext( undefined, ); @@ -32,10 +31,10 @@ export default function DeeplinkProvider({ children: ReactNode; }) { const [deeplinks, setDeeplinks] = useState([]); - const isMobile = useIsMobile(); + const isTauriEnv = isTauri(); useEffect(() => { - if (!isTauri() || !isMobile) return; + if (!isTauriEnv) return; let mounted = true; let unlisten: (() => void) | undefined; @@ -58,7 +57,7 @@ export default function DeeplinkProvider({ mounted = false; unlisten?.(); }; - }, [isMobile]); + }, [isTauriEnv]); return ( void; +}) { + return ( + + ); +} diff --git a/apps/tauri/tsconfig.json b/apps/tauri/tsconfig.json index b54d14e..9714625 100644 --- a/apps/tauri/tsconfig.json +++ b/apps/tauri/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", @@ -9,5 +8,5 @@ "skipLibCheck": true, "noEmit": true }, - "include": ["src", "./render-version.ts"] + "include": ["src"] } diff --git a/apps/web/.gitignore b/apps/web/.gitignore index 8952e07..a547bf3 100644 --- a/apps/web/.gitignore +++ b/apps/web/.gitignore @@ -22,5 +22,3 @@ dist-ssr *.njsproj *.sln *.sw? - -.android diff --git a/apps/web/index.html b/apps/web/index.html index 0c0644f..eef61d9 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -2,11 +2,8 @@ - - + + Tensamin diff --git a/apps/web/package.json b/apps/web/package.json index a6b3951..07bfd07 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -4,60 +4,59 @@ "version": "0.0.0", "type": "module", "scripts": { - "format": "pnpm exec prettier --write .", + "format": "bunx prettier --write .", "lint": "eslint src", - "dev": "vite", - "test": "vitest run --passWithNoTests", - "build": "pnpm --filter @tensamin/pwa build && pnpm run test && tsc -b && vite build", + "dev": "vite --port 3000 --host 0.0.0.0", + "test": "bun test --pass-with-no-tests", + "build": "bun run test && tsc -b && vite build", "preview": "cd dist && nix-shell -p python3 --run 'python3 -m http.server 3000' && cd .." }, "dependencies": { - "@fontsource-variable/public-sans": "^5.3.0", - "@methanium/ui": "*", - "@tailwindcss/vite": "^4.3.3", - "@tanstack/react-router": "^1.170.21", - "@tanstack/react-virtual": "^3.14.9", - "@tauri-apps/api": "^2.11.1", - "@tensamin/pwa": "workspace:*", - "@tensamin/cache": "workspace:*", + "@fontsource-variable/inter": "^5.2.8", + "@noble/curves": "^2.2.0", + "@tailwindcss/vite": "^4.2.4", + "@tanstack/react-router": "^1.169.1", + "@tanstack/react-virtual": "^3.13.24", + "@tauri-apps/api": "^2", "@tensamin/call": "workspace:*", "@tensamin/chat": "workspace:*", "@tensamin/crypto": "workspace:*", - "@tensamin/hotkeys": "workspace:*", - "@tensamin/mtp": "workspace:*", - "@tensamin/notifications": "workspace:*", - "@tensamin/onboarding": "workspace:*", - "@tensamin/settings": "workspace:*", "@tensamin/shared": "workspace:*", "@tensamin/storage": "workspace:*", "@tensamin/tauri": "workspace:*", + "@tensamin/ttp": "workspace:*", "@tensamin/tauth": "workspace:*", + "@tensamin/ui": "*", "@tensamin/user": "workspace:*", - "decimal.js-light": "^2.5.1", - "eventemitter3": "^5.0.4", - "lucide-react": "^1.29.0", - "react": "^19.2.8", - "react-dom": "^19.2.8", - "react-is": "^19.2.8", - "react-redux": "^9.3.0", + "@tensamin/notifications": "workspace:*", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "comlink": "^4.4.2", + "framer-motion": "^12.38.0", + "lucide-react": "^1.14.0", + "qrcode": "^1.5.4", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "shadcn": "^4.6.0", + "sonner": "^2.0.7", + "tailwind-merge": "^3.5.0", "tailwind-scrollbar-hide": "^4.0.0", - "tailwindcss": "^4.3.3", + "tailwindcss": "^4.2.4", + "tauri-plugin-app-events-api": "^0.2.0", "tw-animate-css": "^1.4.0", - "use-sync-external-store": "^1.6.0", - "zod": "^4.4.3" + "zod": "^4.3.6" }, "devDependencies": { "@eslint/js": "^10.0.1", "@types/qrcode": "^1.5.6", - "@types/react": "^19.2.18", - "@types/react-dom": "^19.2.4", - "@vitejs/plugin-react": "^6.0.5", - "esbuild": "^0.28.1", - "eslint": "^10.8.0", - "globals": "^17.9.0", - "mtp": "*", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-react": "^6.0.1", + "esbuild": "^0.25.11", + "eslint": "^10.0.3", + "globals": "^17.4.0", "typescript": "~6.0.3", - "typescript-eslint": "^8.66.0", - "vite": "^8.2.1" + "typescript-eslint": "^8.57.0", + "vite": "^8.0.10" } } diff --git a/apps/web/public/favicon.ico b/apps/web/public/favicon.ico index 30de873..278aa2e 100644 Binary files a/apps/web/public/favicon.ico and b/apps/web/public/favicon.ico differ diff --git a/apps/web/public/sounds/call_jingle_1.wav b/apps/web/public/sounds/call_jingle_1.wav deleted file mode 100644 index 93a23b5..0000000 Binary files a/apps/web/public/sounds/call_jingle_1.wav and /dev/null differ diff --git a/apps/web/public/sounds/call_jingle_2.wav b/apps/web/public/sounds/call_jingle_2.wav deleted file mode 100644 index 33d9efd..0000000 Binary files a/apps/web/public/sounds/call_jingle_2.wav and /dev/null differ diff --git a/apps/web/public/sounds/call_join.wav b/apps/web/public/sounds/call_join.wav deleted file mode 100644 index b135ab7..0000000 Binary files a/apps/web/public/sounds/call_join.wav and /dev/null differ diff --git a/apps/web/public/sounds/call_leave.wav b/apps/web/public/sounds/call_leave.wav deleted file mode 100644 index eb7f53b..0000000 Binary files a/apps/web/public/sounds/call_leave.wav and /dev/null differ diff --git a/apps/web/public/sounds/message.wav b/apps/web/public/sounds/message.wav deleted file mode 100644 index 59ecedf..0000000 Binary files a/apps/web/public/sounds/message.wav and /dev/null differ diff --git a/apps/web/public/sounds/stream_end_other.wav b/apps/web/public/sounds/stream_end_other.wav deleted file mode 100644 index 4177d12..0000000 Binary files a/apps/web/public/sounds/stream_end_other.wav and /dev/null differ diff --git a/apps/web/public/sounds/stream_end_self.wav b/apps/web/public/sounds/stream_end_self.wav deleted file mode 100644 index 8a78493..0000000 Binary files a/apps/web/public/sounds/stream_end_self.wav and /dev/null differ diff --git a/apps/web/public/sounds/stream_start_other.wav b/apps/web/public/sounds/stream_start_other.wav deleted file mode 100644 index bca4724..0000000 Binary files a/apps/web/public/sounds/stream_start_other.wav and /dev/null differ diff --git a/apps/web/public/sounds/stream_start_self.wav b/apps/web/public/sounds/stream_start_self.wav deleted file mode 100644 index 5e57021..0000000 Binary files a/apps/web/public/sounds/stream_start_self.wav and /dev/null differ diff --git a/apps/web/public/sounds/stream_watch_end.wav b/apps/web/public/sounds/stream_watch_end.wav deleted file mode 100644 index e81557a..0000000 Binary files a/apps/web/public/sounds/stream_watch_end.wav and /dev/null differ diff --git a/apps/web/public/sounds/stream_watch_start.wav b/apps/web/public/sounds/stream_watch_start.wav deleted file mode 100644 index acf863d..0000000 Binary files a/apps/web/public/sounds/stream_watch_start.wav and /dev/null differ diff --git a/apps/web/src/components/modals/basic.tsx b/apps/web/src/components/modals/basic.tsx index dc4e48a..6105d7a 100644 --- a/apps/web/src/components/modals/basic.tsx +++ b/apps/web/src/components/modals/basic.tsx @@ -1,74 +1,25 @@ import type { User } from "@tensamin/user/context"; -import { - Avatar, - AvatarImage, - AvatarFallback, - Tooltip, - TooltipTrigger, - TooltipContent, - Button, -} from "@methanium/ui"; -import { Skeleton } from "@methanium/ui"; -import { getStatusColor } from "@tensamin/shared/data"; - -export function Basic({ - user, - extra, -}: { - user: Pick< - User, - "Display" | "Username" | "Avatar" | "OnlineStatus" | "Status" - >; - extra?: React.ReactNode; -}) { - const display = user.Display || user.Username || "Unknown"; - const onlineStatus = user.OnlineStatus || "user_borked"; - const avatar = user.Avatar - ? `data:image/webp;base64,${user.Avatar}` - : undefined; +import { Avatar, AvatarImage, AvatarFallback } from "@tensamin/ui"; +import { reduceDisplay } from "./utils"; +import { Card, CardHeader } from "@tensamin/ui"; +import { Skeleton } from "@tensamin/ui"; +export function Basic(props: { user: User }) { return ( - +
+

{props.user.display}

+
+ + ); } export function Loading() { - return ; + return ; } diff --git a/apps/web/src/components/modals/profile.tsx b/apps/web/src/components/modals/profile.tsx deleted file mode 100644 index a4bddb2..0000000 --- a/apps/web/src/components/modals/profile.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import type { User } from "@tensamin/user/context"; -import { Avatar, AvatarFallback, AvatarImage, Button } from "@methanium/ui"; -import { Text } from "@methanium/ui/markdown"; -import { ChevronDown, ChevronUp } from "lucide-react"; -import { useState } from "react"; - -export default function Profile({ - user, -}: { - user: Pick< - User, - | "UserId" - | "Display" - | "Username" - | "Avatar" - | "About" - | "IotaId" - | "PublicKey" - >; -}) { - const [showAdvancedInformation, setShowAdvancedInformation] = useState(false); - - return ( -
-
- - - - {user.Display.slice(0, 2).toUpperCase()} - - -
-

{user.Display}

-

{user.Username}

-
-
- - - {showAdvancedInformation && ( -
-

- Iota ID: {user.IotaId} -

-

- User ID: {user.UserId} -

-

- Public Key: {user.PublicKey} -

-
- )} -
- ); -} diff --git a/apps/web/src/components/modals/utils.ts b/apps/web/src/components/modals/utils.ts new file mode 100644 index 0000000..248dcd5 --- /dev/null +++ b/apps/web/src/components/modals/utils.ts @@ -0,0 +1,13 @@ +/** + * Executes reduceDisplay. + * @param display Parameter display. + * @returns unknown. + */ +export function reduceDisplay(display: string) { + const words = display.split(" "); + if (words.length === 1) { + return display.slice(0, 2).toUpperCase(); + } else { + return words[0].charAt(0).toUpperCase() + words[1].charAt(0).toUpperCase(); + } +} diff --git a/apps/web/src/components/navbar.tsx b/apps/web/src/components/navbar.tsx index 122f188..162a057 100644 --- a/apps/web/src/components/navbar.tsx +++ b/apps/web/src/components/navbar.tsx @@ -1,34 +1,15 @@ -import { - Button, - Popover, - PopoverContent, - PopoverTrigger, - useIsMobile, -} from "@methanium/ui"; -import { - ArrowLeft, - EllipsisVertical, - House, - Phone, - Settings, - User, -} from "lucide-react"; +import { Button } from "@tensamin/ui"; +import { ArrowLeft, House, Phone, Settings, User } from "lucide-react"; import { useLocation, useNavigate, useSearch } from "@tanstack/react-router"; import { joinCall, useCall } from "@tensamin/call/store"; import Wrapper from "@tensamin/user/wrapper"; -import { Skeleton } from "@methanium/ui"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, -} from "@methanium/ui"; +import { Skeleton } from "@tensamin/ui"; +import { Select, SelectContent, SelectItem, SelectTrigger } from "@tensamin/ui"; import { displayCallId } from "@tensamin/call/utils"; import { useState } from "react"; -import { SidebarTrigger, useSidebar } from "@methanium/ui"; -import { WindowControls as Controls } from "@methanium/ui"; +import { SidebarTrigger, useSidebar } from "@tensamin/ui"; +import { WindowControls as Controls } from "@tensamin/ui"; import { useSession } from "@tensamin/storage/session"; -import Profile from "./modals/profile"; export default function Navbar({ forMobile }: { forMobile: boolean }) { const navigate = useNavigate(); @@ -40,95 +21,51 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) { const { id } = useSearch({ strict: false }); const currentCalls = calls.filter((call) => - call.CallMembers.some((member) => member === id), + call.call_members.some((member) => member === id), ); + //const currentCalls = ["leck", "schleck", "und", "eck"]; - const isMobile = useIsMobile(); - - const [userInfoOpen, setUserInfoOpen] = useState(false); - - const { callId } = useCall(); + const [selectOpen, setSelectOpen] = useState(false); return (
{forMobile ? ( ( - - )} - /> + className="w-9 h-9 aspect-square rounded-lg ml-2" + variant="outline" + > + + ) : ( <> )} - {isMobile && pathname === "/call" && callId && ( -

{displayCallId(callId)}

- )} {pathname === "/chat" && id && ( - isMobile ? ( -

{user?.Display}

- ) : ( - - ( - - )} - /> - - - - - ) - } - loading={} + component={(user) => ( +

{user?.display}

+ )} + loading={} /> )}
@@ -142,7 +79,7 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) { onClick={() => { void joinCall(id); }} - className="w-9 h-9! aspect-square rounded-lg" + className="w-9 h-9 aspect-square rounded-lg" variant="outline" > @@ -153,39 +90,34 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) { onClick={() => { void joinCall( id, - currentCalls[0].CallSecret, - currentCalls[0].CallId, + currentCalls[0].call_secret, + currentCalls[0].call_id, ); }} - className="w-9 h-9! aspect-square rounded-lg" + className="w-9 h-9 aspect-square rounded-lg" > ) : ( <> - +
); @@ -213,7 +140,7 @@ export function MobileNavbar() {
@@ -224,7 +151,7 @@ export function MobileNavbar() { navigate({ to: "/" }); setOpenMobile(false); }} - className="text-foreground w-12 h-12 aspect-square rounded-xl flex flex-col gap-1 border-0! bg-none! bg-transparent!" + className="text-foreground w-12 h-12 aspect-square rounded-xl flex flex-col gap-1" variant="link" > @@ -232,10 +159,11 @@ export function MobileNavbar() { +
+ {isTauriEnv ? ( + <> + { + if (!data.startsWith("tensamin://tu::")) { + toast("error", "Invalid QR code"); + return; + } else { + const decoded = data.replace("tensamin://tu::", ""); + + try { + const { userId, privateKey } = parseTuFileContent(decoded); + save("session_id", Date.now()); + save("user_id", userId); + save("private_key", privateKey); + location.href = "/"; + } catch (error) { + log(0, "login", "red", error); + toast("error", "Failed to parse QR code data"); + } + } + }} + /> + + ) : (
uploadRef.current?.click()} - className={cn( - "flex flex-col gap-3 cursor-pointer w-55 aspect-square", - "border-3 items-center justify-center rounded-lg", - "transition-all duration-300 ease-in-out", - "border-input", - "bg-input/13 hover:bg-input/30", - isDragging ? "animate-wiggle" : "", - )} + className="flex flex-col gap-3 cursor-pointer w-55 aspect-square bg-input/13 hover:bg-input/30 transition-all duration-300 ease-in-out border-3 items-center justify-center rounded-lg" > - - -

- Select .tu file -

+ +

Select .tu file

)}
- - + +
} /> - - - - - ); -} +import { useShowMobileNavbar } from "@/routes/app/layout"; +/** + * Renders the conversation sidebar with account summary and conversation list. + * @returns Sidebar JSX. + */ export default function Sidebar() { const isMobile = useIsMobile(); const showMobileNavbar = useShowMobileNavbar(); - const { openMobile, setOpenMobile } = useSidebar(); - const [draftStatus, setDraftStatus] = useState(""); - const [draftOnlineStatus, setDraftOnlineStatus] = - useState("user_online"); - const [dialogOpen, setDialogOpen] = useState(false); - const [statusErrorMessage, setStatusErrorMessage] = useState(""); - const [statusSaveSucceeded, setStatusSaveSucceeded] = useState(false); - const { send } = useMTP(); - - const content = ( - <> - + return ( + +
} userId={"own"} - fields={[ - "UserId", - "Display", - "Username", - "Avatar", - "OnlineStatus", - "Status", - ]} - component={(user) => ( - <> - - - - - } - /> - - - { - setDraftStatus(user.Status ?? ""); - setDraftOnlineStatus( - accountPreference(user.OnlineStatus), - ); - setStatusErrorMessage(""); - setStatusSaveSucceeded(false); - setDialogOpen(true); - }} - > - Set Status - - - - - } - /> - { - if (!nextOpen) { - setDraftStatus(user.Status ?? ""); - setDraftOnlineStatus( - accountPreference(user.OnlineStatus), - ); - setStatusErrorMessage(""); - setStatusSaveSucceeded(false); - } - - setDialogOpen(nextOpen); - }} - send={send} - draftStatus={draftStatus} - setDraftStatus={setDraftStatus} - draftOnlineStatus={draftOnlineStatus} - setDraftOnlineStatus={setDraftOnlineStatus} - errorMessage={statusErrorMessage} - setErrorMessage={setStatusErrorMessage} - saveSucceeded={statusSaveSucceeded} - setSaveSucceeded={setStatusSaveSucceeded} - /> - - )} + component={(user) => } />
@@ -288,34 +52,6 @@ export default function Sidebar() { )} - + ); - - if (isMobile) { - return ( - <> - {openMobile && ( -
setOpenMobile(false)} - /> - )} -
-
{content}
-
- - ); - } - - return {content}; } diff --git a/apps/web/src/components/status-options.ts b/apps/web/src/components/status-options.ts deleted file mode 100644 index da1ebb5..0000000 --- a/apps/web/src/components/status-options.ts +++ /dev/null @@ -1,13 +0,0 @@ -export const onlineStatusOptions = [ - { label: "Online", value: "user_online" }, - { label: "Idle", value: "user_idle" }, - { label: "Do not disturb", value: "user_dnd" }, - { label: "On the toilet", value: "user_wc" }, - { label: "Offline", value: "user_invisible" }, -] as const; - -export type OnlineStatus = (typeof onlineStatusOptions)[number]["value"]; - -export const onlineStatusLabels = Object.fromEntries( - onlineStatusOptions.map((option) => [option.value, option.label]), -) as Record; diff --git a/apps/web/src/features/conversation/list/body.tsx b/apps/web/src/features/conversation/list/body.tsx index cc3baaf..7092b6f 100644 --- a/apps/web/src/features/conversation/list/body.tsx +++ b/apps/web/src/features/conversation/list/body.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from "react"; +import * as React from "react"; import { useVirtualizer } from "@tanstack/react-virtual"; import Switch from "./switch"; @@ -8,14 +8,14 @@ import { Loader2 } from "lucide-react"; import { useSession } from "@tensamin/storage/session"; export default function List() { - const [category, setCategory] = useState<"conversations" | "communities">( - "conversations", - ); + const [category, setCategory] = React.useState< + "conversations" | "communities" + >("conversations"); const { contacts, communities } = useSession(); const items = category === "conversations" ? contacts : communities; - const scrollRef = useRef(null); + const scrollRef = React.useRef(null); // TanStack Virtual is intentionally used here; React Compiler memoization is skipped. // eslint-disable-next-line react-hooks/incompatible-library @@ -31,7 +31,7 @@ export default function List() {
{items === null ? (
@@ -65,7 +65,7 @@ export default function List() { {category === "conversations" ? ( contacts?.[virtualItem.index] ? ( ) : null ) : communities?.[virtualItem.index] ? ( @@ -76,16 +76,6 @@ export default function List() {
); })} -
diff --git a/apps/web/src/features/conversation/list/switch.tsx b/apps/web/src/features/conversation/list/switch.tsx index be3e10e..1839e6a 100644 --- a/apps/web/src/features/conversation/list/switch.tsx +++ b/apps/web/src/features/conversation/list/switch.tsx @@ -23,10 +23,10 @@ export default function Switch(props: { return (
} userId={userId} - fields={[ - "Display", - "Username", - "Avatar", - "OnlineStatus", - "Status", - ]} component={(user) => } />
diff --git a/apps/web/src/features/legal/screen.tsx b/apps/web/src/features/legal/screen.tsx new file mode 100644 index 0000000..6d4062c --- /dev/null +++ b/apps/web/src/features/legal/screen.tsx @@ -0,0 +1,247 @@ +import { useState, useCallback, useEffect } from "react"; +import { useStorage } from "@tensamin/storage/context"; +import { Button } from "@tensamin/ui"; +import { Checkbox } from "@tensamin/ui"; +import { z } from "zod"; + +import { ErrorScreen } from "@tensamin/ui"; + +import { legalDocsSchema } from "@tensamin/shared/features/legal/schema"; +import { log } from "@tensamin/shared/log"; +import { Link } from "@tensamin/ui"; +import { Label } from "@tensamin/ui"; +import { CreateScreen } from "@tensamin/ui"; + +// Prevents the user from using Tensamin without accepting the privacy policy and terms of service. +export default function Screen(props: { children: React.ReactNode }) { + const { load, save } = useStorage(); + + const [error, setError] = useState(""); + const [errorDescription, setErrorDescription] = useState(""); + + const [remoteDocs, setRemoteDocs] = useState< + z.infer | undefined + >(undefined); + const [localDocs, setLocalDocs] = useState< + z.infer | undefined + >(undefined); + + const [loading, setLoading] = useState(true); + + const [acceptedPP, acceptPP] = useState(false); + const [acceptedTOS, acceptTOS] = useState(false); + const [hasContinued, setHasContinued] = useState(false); + + const [userId, setUserId] = useState(undefined); + + // Saves + const handleContinueLegal = useCallback((): void => { + const currentDocs = remoteDocs; + if (!currentDocs) { + return; + } + + save("accepted_privacy_policy", true); + save("accepted_terms_of_service", true); + save("legal_docs", currentDocs); + setHasContinued(true); + }, [remoteDocs, save]); + + useEffect(() => { + let active = true; + + void (async () => { + try { + const id = await load("user_id"); + if (!active) { + return; + } + + setUserId(id); + + if (id === 0) { + return; + } + + const current = await fetch("https://legal.tensamin.net/api/current") + .then((res) => res.json()) + .catch((err) => { + if (!active) { + return undefined; + } + + setError("Failed to load legal documents"); + setErrorDescription( + "An error occurred while fetching the legal documents from the server. Please try again later.", + ); + log(0, "Legal", "red", "Failed to fetch legal documents", err); + return undefined; + }); + + if (!active || current === undefined) { + return; + } + + const safeCurrent = legalDocsSchema.safeParse(current); + if (!safeCurrent.success) { + setError("Failed to load legal documents"); + setErrorDescription( + "The legal documents data received from the server is invalid. Please try again later.", + ); + log( + 0, + "Legal", + "red", + "Invalid legal documents data", + safeCurrent.error, + ); + return; + } + + setRemoteDocs(safeCurrent.data); + + const currentLocalDocs = await load("legal_docs"); + setLocalDocs(currentLocalDocs); + + const [loadedAcceptedPP, loadedAcceptedTOS] = await Promise.all([ + load("accepted_privacy_policy"), + load("accepted_terms_of_service"), + ]); + + if (!active) { + return; + } + + acceptPP( + loadedAcceptedPP && + !!currentLocalDocs && + currentLocalDocs.pp.hash === safeCurrent.data.pp.hash, + ); + acceptTOS( + loadedAcceptedTOS && + !!currentLocalDocs && + currentLocalDocs.tos.hash === safeCurrent.data.tos.hash, + ); + } finally { + if (active) { + setLoading(false); + } + } + })(); + + return () => { + active = false; + }; + }, [load]); + + if (error !== "" && errorDescription !== "") { + return ; + } + + if (loading || userId === undefined) { + return null; + } + + const docsMatch = + localDocs !== undefined && + remoteDocs !== undefined && + localDocs.pp.hash === remoteDocs.pp.hash && + localDocs.tos.hash === remoteDocs.tos.hash; + + if ( + (acceptedPP && acceptedTOS && (docsMatch || hasContinued)) || + userId === 0 + ) { + return <>{props.children}; + } + + return ( + +
+

+ Privacy Policy & ToS +

+ {remoteDocs?.pp.version} / {remoteDocs?.tos.version} +

+

+
+
+ + +
+ + +
+
+ +
+ + ); +} + +// Components +function ContinueButton({ + onClick, + disabled, +}: { + onClick: () => void; + disabled?: boolean; +}) { + return ( +
+ +
+ ); +} + +export function BigCheckbox({ + id, + label, + checked, + onChange, +}: { + id: string; + label: string; + checked: boolean; + onChange: (checked: boolean) => void; +}) { + return ( +
+ + +
+ ); +} diff --git a/apps/web/src/features/settings/components.tsx b/apps/web/src/features/settings/components.tsx new file mode 100644 index 0000000..ff1d7f1 --- /dev/null +++ b/apps/web/src/features/settings/components.tsx @@ -0,0 +1,108 @@ +import { Label } from "@tensamin/ui"; +import { Switch as UISwitch, Input as UIInput } from "@tensamin/ui"; +import { useEffect, useState } from "react"; + +import type { Storage } from "@tensamin/shared/data"; +import { settingsStorageDefaults } from "@tensamin/shared/settings"; +import { useStorage } from "@tensamin/storage/context"; + +type BooleanStorageKey = { + [K in keyof Storage]: Storage[K] extends boolean ? K : never; +}[keyof Storage]; + +type StringStorageKey = { + [K in keyof Storage]: Storage[K] extends string ? K : never; +}[keyof Storage]; + +type NumberStorageKey = { + [K in keyof Storage]: Storage[K] extends number ? K : never; +}[keyof Storage]; + +type InputProps = + | { + label: string; + id: StringStorageKey; + placeholder?: string; + type?: "text"; + } + | { + label: string; + id: NumberStorageKey; + placeholder?: string; + type: "number"; + }; + +export function Switch({ + label, + id, +}: { + label: string; + id: keyof typeof settingsStorageDefaults & BooleanStorageKey; +}) { + const { save, load } = useStorage(); + const [value, setValue] = useState(settingsStorageDefaults[id]); + + useEffect(() => { + load(id).then((value) => setValue(value)); + }, [id, load]); + + return ( +
+ { + setValue(value); + save(id, value); + }} + /> + +
+ ); +} + +export function Input({ label, id, placeholder, type }: InputProps) { + const { save, load } = useStorage(); + const [value, setValue] = useState( + type === "number" ? 0 : "", + ); + + useEffect(() => { + load(id).then((loadedValue) => { + if (type === "number") { + if (typeof loadedValue === "number") { + setValue(loadedValue); + } + return; + } + + if (typeof loadedValue === "string") { + setValue(loadedValue); + } + }); + }, [id, load, type]); + + return ( +
+ + { + if (type === "number") { + const nextValue = Number(e.target.value); + setValue(nextValue); + save(id, nextValue); + return; + } + + const nextValue = e.target.value; + setValue(nextValue); + save(id, nextValue); + }} + /> +
+ ); +} diff --git a/apps/web/src/features/settings/layout.tsx b/apps/web/src/features/settings/layout.tsx new file mode 100644 index 0000000..fa27444 --- /dev/null +++ b/apps/web/src/features/settings/layout.tsx @@ -0,0 +1,74 @@ +import { Button } from "@tensamin/ui"; + +import options from "@tensamin/shared/settings"; +import { cn, useIsMobile } from "@tensamin/ui"; +import { Outlet, useLocation, useNavigate } from "@tanstack/react-router"; + +export default function Screen() { + const isMobile = useIsMobile(); + const location = useLocation(); + + return ( +
+ {!isMobile && } + + {/* Page */} +
+

+ {location.pathname + .split("/") + .pop() + ?.replace(/-/g, " ") + .replace(/\b\w/g, (l) => l.toUpperCase())} +

+ +
+
+ ); +} + +export function SettingsSidebar() { + const settingsOptions = options as Record< + string, + Record> + >; + + const isMobile = useIsMobile(); + const navigate = useNavigate(); + + return ( +
+ {/* Settings */} + {Object.keys(settingsOptions).map((category) => ( + // Category +
+

{category}

+ {Object.keys(settingsOptions[category]).map((page) => ( + // Page +
+ +
+ ))} +
+ ))} +
+ ); +} diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 3037fec..d243481 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -1,38 +1,11 @@ @import "tailwindcss"; @import "tailwind-scrollbar-hide/v4"; @import "tw-animate-css"; -@import "@fontsource-variable/public-sans"; +@import "@fontsource-variable/inter"; @source "./**/*.{ts,tsx}"; @source "../../../packages/**/src/**/*.{ts,tsx}"; -@theme { - --font-sans: "Public Sans Variable", sans-serif; - - --animate-wiggle: wiggle 0.5s ease-in-out infinite; - - @keyframes wiggle { - 0%, - 100% { - transform: rotate(-2deg); - } - 50% { - transform: rotate(2deg); - } - } -} - -@theme inline { - --radius-sm: calc(var(--radius) * 0.6); - --radius-md: calc(var(--radius) * 0.8); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) * 1.4); - --radius-2xl: calc(var(--radius) * 1.8); - --radius-3xl: calc(var(--radius) * 2.2); - --radius-4xl: calc(var(--radius) * 2.6); - --radius-full: calc(var(--radius) * 9999); -} - html, body, #root { @@ -41,39 +14,6 @@ body, overflow: hidden; } -body { - font-family: "Public Sans Variable", sans-serif; -} - #root { min-height: 0; } - -@layer base { - * { - scrollbar-width: thin; - scrollbar-color: var(--border) transparent; - } - - ::-webkit-scrollbar { - width: 6px; - height: 6px; - } - - ::-webkit-scrollbar-track { - background: transparent; - } - - ::-webkit-scrollbar-thumb { - background-color: var(--border); - border-radius: 9999px; - } - - ::-webkit-scrollbar-thumb:hover { - opacity: 0.8; - } - - ::-webkit-scrollbar-corner { - background: transparent; - } -} diff --git a/apps/web/src/index.tsx b/apps/web/src/index.tsx index 460e92b..0dfffed 100644 --- a/apps/web/src/index.tsx +++ b/apps/web/src/index.tsx @@ -4,18 +4,16 @@ import { RouterProvider, createRootRoute, createRoute, - createHashHistory, createRouter, } from "@tanstack/react-router"; import "./index.css"; -import "@methanium/ui/index.css"; +import "@tensamin/ui/index.css"; import NotFound from "@/routes/404"; import AppLayout from "@/routes/app/layout"; -import { createSettingsRoute } from "@tensamin/settings"; -import OnboardingGate from "@tensamin/onboarding"; +import SettingsLayout from "@/features/settings/layout"; import Home from "@/routes/app/home"; import ChatScreen from "@tensamin/chat/screen"; @@ -23,33 +21,30 @@ import CallScreen from "@tensamin/call/screen"; import Login from "@/routes/screens/login"; import ChatContext from "@tensamin/chat/context"; -import { useCall, useInitializeCall } from "@tensamin/call/store"; -import { useIsSpeaking } from "@tensamin/call/speakingState"; -import { Provider as MTPProvider } from "@tensamin/mtp"; -import UserProvider from "@tensamin/user/context"; -import DeeplinkContext, { useDeeplinks } from "@tensamin/tauri/deeplinkHandler"; +import { useInitializeCall } from "@tensamin/call/store"; +import { Provider as TTPProvider } from "@tensamin/ttp"; +import UserContext from "@tensamin/user/context"; +import DeeplinkContext from "@tensamin/tauri/deeplinkHandler"; import NotificationsProvider from "@tensamin/notifications/context"; -import PwaRuntime from "@tensamin/pwa/runtime"; import TAuthWrapper from "@tensamin/tauth/context"; -import { ErrorScreen, ThemeProvider, useTheme } from "@methanium/ui"; +import { ThemeProvider } from "@tensamin/ui"; import z from "zod"; -import { useEffect, useRef, useState, type ReactNode } from "react"; +import { useEffect, useState, type ReactNode } from "react"; import Storage from "@tensamin/storage/context"; import Session from "@tensamin/storage/session"; import Crypto from "@tensamin/crypto/context"; -import DesktopMediaProvider from "@tensamin/shared/desktopMedia"; -import { log } from "@tensamin/shared/log"; +import Mobile from "@tensamin/tauri/context"; -import CacheSync from "@tensamin/cache/sync"; +import LegalWrapper from "@/features/legal/screen"; import { useStorage } from "@tensamin/storage/context"; import { useLocation, useNavigate } from "@tanstack/react-router"; -import { useIsMobile, Toaster, TooltipProvider } from "@methanium/ui"; -import { HotkeysProvider } from "@tensamin/hotkeys"; +import { useIsMobile, Toaster, TooltipProvider } from "@tensamin/ui"; +import { isTauri } from "@tauri-apps/api/core"; const wrapper = document.getElementById("root"); @@ -73,39 +68,34 @@ window.setLogLevelToMax = () => { function LoginWrapper({ children }: { children: ReactNode }) { const [loggedIn, setLoggedIn] = useState(null); - const { load, secureStorage } = useStorage(); + const { load } = useStorage(); const navigate = useNavigate(); const location = useLocation(); useEffect(() => { - if (secureStorage === null) return; let active = true; - Promise.all([load("user_id"), load("mtp_keyring")]) - .then(([userId, keyring]) => { - if (!active) return; - if (userId !== 0 && keyring !== "") { - setLoggedIn(true); - if (location.pathname === "/login") { - void navigate({ to: "/", replace: true }); - } - return; - } - setLoggedIn(false); - void navigate({ - to: "/login", - replace: true, - }); - }) - .catch((error) => { - log(0, "login", "red", "Failed to load login state", error); + load("user_id").then((userId) => { + if (!active) { + return; + } + + if (userId !== 0) { + setLoggedIn(true); + return; + } + + setLoggedIn(false); + navigate({ + to: "/login", }); + }); return () => { active = false; }; - }, [load, location.pathname, navigate, secureStorage]); + }, [load, navigate]); if (loggedIn !== true && location.pathname !== "/login") { return null; @@ -114,160 +104,34 @@ function LoginWrapper({ children }: { children: ReactNode }) { return children; } -function ThemeStorageBridge() { - const { load, save } = useStorage(); - const { - themeColor, - setThemeColor, - themePalette, - setThemePalette, - themePrimaryColor, - setThemePrimaryColor, - themePolarity, - setThemePolarity, - themeTint, - setThemeTint, - themeBorderRadius, - setThemeBorderRadius, - themeCustomCss, - setThemeCustomCss, - parentThemeId, - setParentThemeId, - applyThemePreset, - themeDesign, - setThemeDesign, - } = useTheme(); - const loadedRef = useRef(false); - - useEffect(() => { - let active = true; - - Promise.all([ - load("theme_color"), - load("theme_palette"), - load("theme_primary_color"), - load("theme_polarity"), - load("theme_tint"), - load("theme_border_radius"), - load("theme_custom_css"), - load("theme_parent"), - load("theme_design"), - ]).then( - ([ - color, - palette, - primaryColor, - polarity, - tint, - borderRadius, - customCss, - parent, - design, - ]) => { - if (!active) { - return; - } - - if (customCss === "" && parent) applyThemePreset(parent); - else setParentThemeId(parent || null); - setThemeColor(color); - setThemePalette(palette); - setThemePrimaryColor(primaryColor); - setThemePolarity(polarity); - setThemeTint(tint); - setThemeBorderRadius(borderRadius); - if (customCss !== "") setThemeCustomCss(customCss); - setThemeDesign(design); - loadedRef.current = true; - }, - ); - - return () => { - active = false; - }; - }, [ - load, - applyThemePreset, - setThemeBorderRadius, - setThemeColor, - setThemeCustomCss, - setParentThemeId, - setThemeDesign, - setThemePalette, - setThemePolarity, - setThemePrimaryColor, - setThemeTint, - ]); - - useEffect(() => { - if (loadedRef.current) save("theme_color", themeColor); - }, [save, themeColor]); - - useEffect(() => { - if (loadedRef.current) save("theme_palette", themePalette); - }, [save, themePalette]); - - useEffect(() => { - if (loadedRef.current) save("theme_primary_color", themePrimaryColor); - }, [save, themePrimaryColor]); - - useEffect(() => { - if (loadedRef.current) save("theme_polarity", themePolarity); - }, [save, themePolarity]); - - useEffect(() => { - if (loadedRef.current) save("theme_tint", themeTint); - }, [save, themeTint]); - - useEffect(() => { - if (loadedRef.current) save("theme_border_radius", themeBorderRadius); - }, [save, themeBorderRadius]); - - useEffect(() => { - if (loadedRef.current) save("theme_custom_css", themeCustomCss); - }, [save, themeCustomCss]); - - useEffect(() => { - if (loadedRef.current) save("theme_parent", parentThemeId ?? ""); - }, [parentThemeId, save]); - - useEffect(() => { - if (loadedRef.current) save("theme_design", themeDesign); - }, [save, themeDesign]); - - return null; -} - function RootShell() { const isMobile = useIsMobile(); return ( - -
- + +
+ - - - - - - - + + + + + + + + +
@@ -278,161 +142,33 @@ function RootShell() { function AppShell() { return ( - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + ); } -function DeeplinkNavigator() { - const { deeplinks } = useDeeplinks(); - const navigate = useNavigate(); - const handledCount = useRef(0); - - useEffect(() => { - const links = deeplinks.slice(handledCount.current); - handledCount.current = deeplinks.length; - - for (const link of links) { - try { - const url = new URL(link); - const id = Number(url.searchParams.get("id")); - if ( - url.protocol === "tensamin:" && - url.hostname === "chat" && - Number.isSafeInteger(id) && - id > 0 - ) { - void navigate({ to: "/chat", search: { id } }); - } - } catch { - // Ignore malformed URLs delivered by the platform. - } - } - }, [deeplinks, navigate]); +function CallInit() { + useInitializeCall(); return null; } -function createCallTrayIcon(color: string, speaking: boolean) { - const canvas = document.createElement("canvas"); - canvas.width = 32; - canvas.height = 32; - - const context = canvas.getContext("2d"); - if (!context) return undefined; - - context.globalAlpha = speaking ? 1 : 0.55; - context.fillStyle = color; - context.beginPath(); - context.arc(16, 16, 13, 0, Math.PI * 2); - context.fill(); - - if (speaking) { - context.globalAlpha = 0.3; - context.fillStyle = "#ffffff"; - context.fill(); - } - - return canvas.toDataURL("image/png"); -} - -function CallInit() { - const callInvitePopup = useInitializeCall(); - - const { load } = useStorage(); - const { - themeColor, - themePalette, - themePrimaryColor, - themePolarity, - themeTint, - themeCustomCss, - } = useTheme(); - const [localUserId, setLocalUserId] = useState(-1); - const [primaryColor, setPrimaryColor] = useState(""); - const inCall = useCall((state) => state.state === "open"); - const speaking = useIsSpeaking(localUserId); - - useEffect(() => { - let active = true; - - load("user_id").then((userId) => { - if (active) setLocalUserId(userId); - }); - - return () => { - active = false; - }; - }, [load]); - - useEffect(() => { - const frame = requestAnimationFrame(() => { - setPrimaryColor( - getComputedStyle(document.documentElement) - .getPropertyValue("--primary") - .trim(), - ); - }); - - return () => cancelAnimationFrame(frame); - }, [ - themeColor, - themeCustomCss, - themePalette, - themePolarity, - themePrimaryColor, - themeTint, - ]); - - useEffect(() => { - const iconDataUrl = primaryColor - ? createCallTrayIcon(primaryColor, speaking) - : undefined; - - void window.tensaminDesktop?.call - ?.setStatus?.({ - inCall, - speaking: inCall && speaking, - iconDataUrl: inCall ? iconDataUrl : undefined, - }) - .catch((error: unknown) => { - console.error("Failed to update desktop call status", error); - }); - }, [inCall, primaryColor, speaking]); - - return callInvitePopup; -} - const rootRoute = createRootRoute({ component: RootShell, - errorComponent: ({ error }: { error: Error }) => ( - - ), }); const appRoute = createRoute({ @@ -442,7 +178,47 @@ const appRoute = createRoute({ notFoundComponent: NotFound, }); -const settingsRoute = createSettingsRoute(appRoute); +const settingsRoute = createRoute({ + getParentRoute: () => appRoute, + path: "settings", + component: SettingsLayout, + staticData: { + showMobileNavbar: true, + }, +}); + +type SettingsRouteModule = { + default?: () => React.JSX.Element; + component?: () => React.JSX.Element; +}; + +const settingsRouteModules = import.meta.glob( + "./routes/settings/*.tsx", + { eager: true }, +); + +const settingsChildren = Object.entries(settingsRouteModules).map( + ([filePath, module]) => { + const fileName = filePath.split("/").pop()?.replace(".tsx", "") ?? ""; + const path = fileName === "index" ? "/" : fileName; + const component = module.component ?? module.default; + + if (!component) { + throw new Error( + `Settings route module "${filePath}" must export a default component`, + ); + } + + return createRoute({ + getParentRoute: () => settingsRoute, + path, + component, + staticData: { + showMobileNavbar: true, + }, + }); + }, +); const homeRoute = createRoute({ getParentRoute: () => appRoute, @@ -484,14 +260,17 @@ const loginRoute = createRoute({ }); const routeTree = rootRoute.addChildren([ - appRoute.addChildren([homeRoute, chatRoute, callRoute, settingsRoute]), + appRoute.addChildren([ + homeRoute, + chatRoute, + callRoute, + settingsRoute.addChildren(settingsChildren), + ]), loginRoute, ]); const router = createRouter({ routeTree, - history: - window.location.protocol === "file:" ? createHashHistory() : undefined, defaultNotFoundComponent: NotFound, }); diff --git a/apps/web/src/routes/app/home.tsx b/apps/web/src/routes/app/home.tsx index cd4f662..90f6a26 100644 --- a/apps/web/src/routes/app/home.tsx +++ b/apps/web/src/routes/app/home.tsx @@ -9,59 +9,45 @@ import { Input, Button, useIsMobile, -} from "@methanium/ui"; +} from "@tensamin/ui"; import z from "zod"; -import { useMTP } from "@tensamin/mtp"; +import { toast } from "@tensamin/shared/log"; +import { useTTP } from "@tensamin/ttp"; import { useState } from "react"; import { Loader2 } from "lucide-react"; import { isTauri } from "@tauri-apps/api/core"; -import { useSession } from "@tensamin/storage/session"; import { useStorage } from "@tensamin/storage/context"; -import { ShieldAlert } from "lucide-react"; // The page export default function Page() { + const { clear } = useStorage(); const isMobile = useIsMobile(); - const { secureStorage } = useStorage(); return ( -
-
- - -
- {secureStorage && !secureStorage.secure && ( -
- -
-

Secure storage is unavailable

-

{secureStorage.reason}

-

- Your keyring and cached messages will get saved in regular - storage. -

-
-
- )} +
+ + + +
); } // Add Conversation Button Component function AddConversationButton() { - const { send } = useMTP(); - const { contacts, insertContact } = useSession(); + const { send } = useTTP(); const [loading, setLoading] = useState(false); - const [open, setOpen] = useState(false); - const [error, setError] = useState(null); - async function submit(username: string | null) { + function submit(username: string | null) { if (loading) return; - setError(null); - // username check const schema = z .string() .min(1, "Username is too short") @@ -70,50 +56,26 @@ function AddConversationButton() { const result = schema.safeParse(username?.toLowerCase().trim()); if (!result.success) { - setError(result.error.issues[0].message); + toast("error", result.error.issues[0].message); return; } - // user existence check - const user = await send("GetUserData", { - Username: result.data, - }) - .then((data) => { - if (data.type === "ErrorNotFound" || data.data.UserId === 0) { - throw new Error(); - } - - return data; - }) - .catch(() => { - setError("User not found"); - return; - }); - if (!user) return; - - // alrady added check - if (contacts.some((contact) => contact.UserId === user.data.UserId)) { - setError("Conversation already exists"); - return; - } - - // add the conv const timeout = setTimeout(() => setLoading(true), 500); - send("AddConversation", { - ChatPartnerId: user.data.UserId, + send("add_conversation", { + chat_partner_name: result.data, }) .then(() => { - insertContact(user.data.UserId); - setOpen(false); + toast("success", "Conversation added"); }) .catch((error) => { if (String(error).includes("error_not_found")) { - setError("User not found"); + toast("error", "User not found"); return; } - setError(String(error)); + toast("error", "Failed to add conversation, check console for details"); + console.error("Failed to add conversation", error); }) .finally(() => { clearTimeout(timeout); @@ -122,21 +84,9 @@ function AddConversationButton() { } return ( - { - if (value) { - setError(null); - } - setOpen(value); - }} - > - ( - - )} - /> - + + Add Conversation} /> + New Conversation @@ -160,17 +110,8 @@ function AddConversationButton() { name="username" placeholder="Enter username..." /> -
- {error && ( -

{error}

- )} - ( - - )} - /> +
+ Cancel} /> diff --git a/apps/web/src/routes/app/layout.tsx b/apps/web/src/routes/app/layout.tsx index 9f165b4..a937d2f 100644 --- a/apps/web/src/routes/app/layout.tsx +++ b/apps/web/src/routes/app/layout.tsx @@ -2,13 +2,17 @@ import { type ReactNode } from "react"; import Sidebar from "@/components/sidebar"; import Navbar, { MobileNavbar } from "@/components/navbar"; -import { useShowMobileNavbar } from "./useShowMobileNavbar"; -import CallPopout from "@tensamin/call/popout"; -import { useIsMobile, cn, SidebarProvider } from "@methanium/ui"; +import { useIsMobile, cn, SidebarProvider } from "@tensamin/ui"; import { isTauri } from "@tauri-apps/api/core"; +import { useLocation, useMatches } from "@tanstack/react-router"; +/** + * Executes Layout. + * @param props Parameter props. + * @returns unknown. + */ export default function Layout({ children }: { children: ReactNode }) { const isMobile = useIsMobile(); const showMobileNavbar = useShowMobileNavbar(); @@ -17,9 +21,7 @@ export default function Layout({ children }: { children: ReactNode }) {
-
); } + +export function useShowMobileNavbar(): boolean { + const matches = useMatches(); + const location = useLocation(); + + const value = matches.some( + (match) => + match.pathname === location.pathname && + // @ts-expect-error Stuff + match.staticData?.showMobileNavbar === true, + ); + + return value; +} diff --git a/apps/web/src/routes/app/useShowMobileNavbar.ts b/apps/web/src/routes/app/useShowMobileNavbar.ts deleted file mode 100644 index 5fb039e..0000000 --- a/apps/web/src/routes/app/useShowMobileNavbar.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useLocation, useMatches } from "@tanstack/react-router"; - -export function useShowMobileNavbar(): boolean { - const matches = useMatches(); - const location = useLocation(); - - return matches.some( - (match) => - match.pathname === location.pathname && - // Router staticData is app-defined and not typed by the route matcher here. - // @ts-expect-error App route metadata - match.staticData?.showMobileNavbar === true, - ); -} diff --git a/apps/web/src/routes/screens/login.tsx b/apps/web/src/routes/screens/login.tsx index f5f746e..dc85810 100644 --- a/apps/web/src/routes/screens/login.tsx +++ b/apps/web/src/routes/screens/login.tsx @@ -1,5 +1,5 @@ import Form from "@/components/screens/login/form"; -import { CreateScreen } from "@methanium/ui"; +import { CreateScreen } from "@tensamin/ui"; /** * Executes Page. diff --git a/apps/web/src/routes/settings/chat.tsx b/apps/web/src/routes/settings/chat.tsx new file mode 100644 index 0000000..3dafa45 --- /dev/null +++ b/apps/web/src/routes/settings/chat.tsx @@ -0,0 +1,12 @@ +import { Switch } from "@/features/settings/components"; + +export default function Page() { + return ( +
+ +
+ ); +} diff --git a/apps/web/src/routes/settings/index.tsx b/apps/web/src/routes/settings/index.tsx new file mode 100644 index 0000000..568d53f --- /dev/null +++ b/apps/web/src/routes/settings/index.tsx @@ -0,0 +1,7 @@ +import { SettingsSidebar } from "@/features/settings/layout"; +import { useIsMobile } from "@tensamin/ui"; + +export default function Page() { + const isMobile = useIsMobile(); + return isMobile && ; +} diff --git a/packages/settings/src/pages/licenses.tsx b/apps/web/src/routes/settings/licenses.tsx similarity index 85% rename from packages/settings/src/pages/licenses.tsx rename to apps/web/src/routes/settings/licenses.tsx index ebf3578..31293b1 100644 --- a/packages/settings/src/pages/licenses.tsx +++ b/apps/web/src/routes/settings/licenses.tsx @@ -1,35 +1,38 @@ import { - Badge, - Button, Card, CardContent, + CardTitle, + CardHeader, CardDescription, CardFooter, - CardHeader, - CardTitle, + Button, + Badge, Dialog, DialogContent, DialogTrigger, -} from "@methanium/ui"; +} from "@tensamin/ui"; import { - generatedAt, - packageCount, packages, -} from "../../../../licenses/third-party-credits.json"; + packageCount, + generatedAt, +} from "../../../../../licenses/third-party-credits.json"; -const licenseTexts = import.meta.glob("../../../../licenses/**/*", { +const licenseTexts = import.meta.glob("../../../../../licenses/**/*", { eager: true, import: "default", query: "?raw", }) as Record; function getLicenseFiles(licensePackage: (typeof packages)[number]) { - return licensePackage.files.map((fileName) => ({ - fileName, - text: licenseTexts[ - "../../../../" + licensePackage.licenseFolder + "/" + fileName - ], - })); + return licensePackage.files.map((fileName) => { + const path = + "../../../../../" + licensePackage.licenseFolder + "/" + fileName; + + return { + fileName, + text: licenseTexts[path], + }; + }); } export default function Page() { @@ -40,10 +43,9 @@ export default function Page() {

Package Count: {packageCount}

-
+
{packages.map((licensePackage) => ( @@ -67,7 +69,7 @@ export default function Page() { target="_blank" rel="noreferrer" href={licensePackage.repository - .replace("git+", "") + ?.replace("git+", "") .replace(".git", "")} > - )} + } />
diff --git a/apps/web/src/routes/settings/profile.tsx b/apps/web/src/routes/settings/profile.tsx new file mode 100644 index 0000000..0b98f2d --- /dev/null +++ b/apps/web/src/routes/settings/profile.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
; +} diff --git a/apps/web/src/routes/settings/security.tsx b/apps/web/src/routes/settings/security.tsx new file mode 100644 index 0000000..15a6155 --- /dev/null +++ b/apps/web/src/routes/settings/security.tsx @@ -0,0 +1,114 @@ +import { useStorage } from "@tensamin/storage/context"; +import { useEffect, useState } from "react"; +import QRCode from "qrcode"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, + Button, +} from "@tensamin/ui"; + +export default function Page() { + return ( +
+ +
+ ); +} + +// QR Code Login +const generateQR = async (text: string): Promise => { + try { + const url = await QRCode.toDataURL(text, { + errorCorrectionLevel: "H", + margin: 1, + color: { + dark: "#000000FF", + light: "#FFFFFFFF", + }, + }); + return url; + } catch (err) { + console.error(err); + throw err; + } +}; +function QrCodeLogin() { + const { load } = useStorage(); + const [userId, setUserId] = useState(0); + const [privateKey, setPrivateKey] = useState(""); + const [qrCodeBase64, setQrCodeBase64] = useState( + undefined, + ); + + useEffect(() => { + load("private_key").then((value) => { + if (value) { + setPrivateKey(value); + } + }); + load("user_id").then((value) => { + if (value) { + setUserId(value); + } + }); + }, [load]); + + useEffect(() => { + if (userId && privateKey) { + generateQR(`tensamin://tu::${userId}::${privateKey}`).then( + setQrCodeBase64, + ); + } + }, [userId, privateKey]); + + const [qrCodeVisible, setQrCodeVisible] = useState(false); + + return ( +
+

Login QR Code

+
+ {qrCodeBase64 && ( + QR Code + )} + {!qrCodeVisible && ( + <> +
+
+ + Show QR Code} /> + + + Are you sure? + + Exposing this QR code is the same as sharing your .tu + file. Since changing your private key is quite tedious, + only do this when you're confident the key won't be + compromised. + + + + Cancel + setQrCodeVisible(true)}> + Show QR Code + + } + /> + + + +
+ + )} +
+
+ ); +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index b2304a9..92f0d91 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -1,4 +1,4 @@ -import { createReadStream, statSync } from "node:fs"; +import { createReadStream, realpathSync, statSync } from "node:fs"; import type { IncomingMessage, ServerResponse } from "node:http"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -6,12 +6,14 @@ import { fileURLToPath } from "node:url"; import { defineConfig, type Plugin } from "vite"; import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; -import { mtp } from "mtp/vite"; -import { methaniumUi } from "@methanium/ui/vite"; -import { tensaminPwa } from "@tensamin/pwa/vite"; const host = process.env.TAURI_DEV_HOST; const appDir = dirname(fileURLToPath(import.meta.url)); +const markdownPackageDir = resolve(appDir, "../../packages/markdown"); + +function resolveMarkdownDependency(packageName: string): string { + return realpathSync(resolve(markdownPackageDir, "node_modules", packageName)); +} function deepFilterAssetHeaders(rootDir: string): Plugin { const serveModel = ( @@ -49,98 +51,56 @@ function deepFilterAssetHeaders(rootDir: string): Plugin { } export default defineConfig({ - base: "./", clearScreen: false, resolve: { tsconfigPaths: true, + alias: [ + { + find: "@codemirror/commands", + replacement: resolveMarkdownDependency("@codemirror/commands"), + }, + { + find: "@codemirror/lang-markdown", + replacement: resolveMarkdownDependency("@codemirror/lang-markdown"), + }, + { + find: "@codemirror/state", + replacement: resolveMarkdownDependency("@codemirror/state"), + }, + { + find: "@codemirror/view", + replacement: resolveMarkdownDependency("@codemirror/view"), + }, + ], dedupe: [ - "react", - "react-dom", - "react-redux", - "use-sync-external-store", - "@tanstack/history", - "@tanstack/react-router", - "@tanstack/router-core", - "@tensamin/crypto", - "@tensamin/settings", - "@tensamin/storage", - "@tensamin/mtp", - "@tensamin/user", - "@tensamin/tauri", - "@tensamin/chat", + "@codemirror/commands", + "@codemirror/lang-markdown", + "@codemirror/state", + "@codemirror/view", ], }, server: { - port: 3000, + port: 5173, strictPort: true, - host: "0.0.0.0", + host: host || "0.0.0.0", hmr: host ? { protocol: "ws", host, - clientPort: 3000, + port: 1421, } : undefined, watch: { ignored: ["**/src-tauri/**"], - usePolling: true, - interval: 100, }, }, envPrefix: ["VITE_", "TAURI_ENV_*"], - optimizeDeps: { - include: [ - "react-redux", - "use-sync-external-store/shim/with-selector", - "use-sync-external-store/with-selector", - "decimal.js-light", - "eventemitter3", - "react-is", - ], - exclude: [ - "mtp", - "mtp/raw", - "mtp/type-map", - "@tensamin/call", - "@tensamin/call/utils", - "@tensamin/chat", - "@tensamin/crypto", - "@tensamin/crypto/context", - "@tensamin/hotkeys", - "@tensamin/mtp", - "@tensamin/notifications", - "@tensamin/onboarding", - "@tensamin/shared", - "@tensamin/shared/data", - "@tensamin/shared/log", - "@tensamin/settings", - "@tensamin/storage", - "@tensamin/storage/context", - "@tensamin/tauri", - "@tensamin/tauth", - "@tensamin/user", - ], - }, build: { minify: !process.env.TAURI_ENV_DEBUG ? "esbuild" : false, sourcemap: !!process.env.TAURI_ENV_DEBUG, }, plugins: [ - ...tensaminPwa(), - methaniumUi({ defaultThemeId: "tensamin" }), deepFilterAssetHeaders(resolve(appDir, "public")), - mtp({ typeMaps: resolve(appDir, "../../mtp-type-maps/type-maps.yaml") }), - { - name: "workspace-realpath-resolution", - enforce: "post", - config() { - return { - resolve: { - preserveSymlinks: false, - }, - }; - }, - }, react(), tailwindcss(), ], diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..5b4b4cd --- /dev/null +++ b/bun.lock @@ -0,0 +1,1753 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "tensamin", + "dependencies": { + "@tensamin/ttp-core": "*", + "@tensamin/ui": "*", + "sonner": "^2.0.7", + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^25.6.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@typescript-eslint/parser": "^8.59.1", + "eslint": "^10.2.1", + "eslint-plugin-react-hooks": "^7.1.1", + "globals": "^17.5.0", + "jsonc-parser": "^3.3.1", + "prettier": "^3.8.3", + "typescript": "^6.0.3", + "typescript-eslint": "^8.59.1", + }, + }, + "apps/tauri": { + "name": "@tensamin/tauri", + "version": "0.0.0", + "dependencies": { + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-barcode-scanner": "~2", + "@tauri-apps/plugin-deep-link": "~2", + "@tauri-apps/plugin-opener": "^2", + "@tensamin/shared": "workspace:*", + "@tensamin/ui": "*", + "lucide-react": "^1.14.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + }, + "devDependencies": { + "@tauri-apps/cli": "^2", + }, + }, + "apps/web": { + "name": "@tensamin/web", + "version": "0.0.0", + "dependencies": { + "@fontsource-variable/inter": "^5.2.8", + "@noble/curves": "^2.2.0", + "@tailwindcss/vite": "^4.2.4", + "@tanstack/react-router": "^1.169.1", + "@tanstack/react-virtual": "^3.13.24", + "@tauri-apps/api": "^2", + "@tensamin/call": "workspace:*", + "@tensamin/chat": "workspace:*", + "@tensamin/crypto": "workspace:*", + "@tensamin/notifications": "workspace:*", + "@tensamin/shared": "workspace:*", + "@tensamin/storage": "workspace:*", + "@tensamin/tauri": "workspace:*", + "@tensamin/tauth": "workspace:*", + "@tensamin/ttp": "workspace:*", + "@tensamin/ui": "*", + "@tensamin/user": "workspace:*", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "comlink": "^4.4.2", + "framer-motion": "^12.38.0", + "lucide-react": "^1.14.0", + "qrcode": "^1.5.4", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "shadcn": "^4.6.0", + "sonner": "^2.0.7", + "tailwind-merge": "^3.5.0", + "tailwind-scrollbar-hide": "^4.0.0", + "tailwindcss": "^4.2.4", + "tauri-plugin-app-events-api": "^0.2.0", + "tw-animate-css": "^1.4.0", + "zod": "^4.3.6", + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/qrcode": "^1.5.6", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-react": "^6.0.1", + "esbuild": "^0.25.11", + "eslint": "^10.0.3", + "globals": "^17.4.0", + "typescript": "~6.0.3", + "typescript-eslint": "^8.57.0", + "vite": "^8.0.10", + }, + }, + "packages/call": { + "name": "@tensamin/call", + "version": "0.0.0", + "dependencies": { + "@livekit/components-react": "^2.9.20", + "@tanstack/react-query": "^5.100.7", + "@tanstack/react-router": "^1.169.1", + "@tanstack/react-virtual": "^3.13.24", + "@tauri-apps/api": "^2", + "@tensamin/crypto": "workspace:*", + "@tensamin/markdown": "workspace:*", + "@tensamin/shared": "workspace:*", + "@tensamin/storage": "workspace:*", + "@tensamin/tauri": "workspace:*", + "@tensamin/ttp": "workspace:*", + "@tensamin/ui": "*", + "@tensamin/user": "workspace:*", + "deepfilternet3-noise-filter": "^1.2.1", + "livekit-client": "^2.18.8", + "lucide-react": "^1.14.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "recharts": "^3.8.1", + "sonner": "^2.0.7", + "zod": "^4.3.6", + "zustand": "^5.0.8", + }, + }, + "packages/chat": { + "name": "@tensamin/chat", + "version": "0.0.0", + "dependencies": { + "@tanstack/react-query": "^5.0.0", + "@tanstack/react-router": "^1.0.0", + "@tanstack/react-virtual": "^3.0.0", + "@tensamin/crypto": "workspace:*", + "@tensamin/markdown": "workspace:*", + "@tensamin/shared": "workspace:*", + "@tensamin/storage": "workspace:*", + "@tensamin/ttp": "workspace:*", + "@tensamin/ui": "*", + "@tensamin/user": "workspace:*", + "lucide-react": "^1.14.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "zod": "^4.3.6", + }, + }, + "packages/crypto": { + "name": "@tensamin/crypto", + "version": "0.0.0", + "dependencies": { + "@noble/curves": "^2.0.1", + "comlink": "^4.4.2", + "react": "^19.2.0", + "react-dom": "^19.2.0", + }, + }, + "packages/markdown": { + "name": "@tensamin/markdown", + "version": "0.0.0", + "dependencies": { + "@codemirror/commands": "^6.10.2", + "@codemirror/lang-markdown": "^6.5.0", + "@codemirror/state": "^6.5.4", + "@codemirror/view": "^6.41.1", + "@tensamin/ui": "*", + "react": "^19.2.0", + "react-dom": "^19.2.0", + }, + }, + "packages/notifications": { + "name": "@tensamin/notifications", + "version": "0.0.0", + "dependencies": { + "@tauri-apps/api": "^2.11.0", + "@tensamin/chat": "workspace:*", + "@tensamin/crypto": "workspace:*", + "@tensamin/shared": "workspace:*", + "@tensamin/storage": "workspace:*", + "@tensamin/ttp": "workspace:*", + "@tensamin/user": "workspace:*", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "zod": "^4.3.6", + }, + }, + "packages/shared": { + "name": "@tensamin/shared", + "version": "0.0.0", + "dependencies": { + "@tensamin/ui": "*", + "lucide-react": "^1.14.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "sonner": "^2.0.7", + "zod": "^4.3.6", + }, + }, + "packages/storage": { + "name": "@tensamin/storage", + "version": "0.0.0", + "dependencies": { + "@tensamin/shared": "workspace:*", + "@tensamin/ttp": "workspace:*", + "@tensamin/ui": "*", + "react": "^19.2.0", + "react-dom": "^19.2.0", + }, + }, + "packages/tauth": { + "name": "@tensamin/tauth", + "version": "0.0.0", + "dependencies": { + "@tanstack/react-router": "^1.0.0", + "@tauri-apps/api": "^2.10.1", + "@tensamin/crypto": "workspace:*", + "@tensamin/shared": "workspace:*", + "@tensamin/storage": "workspace:*", + "@tensamin/tauri": "workspace:*", + "@tensamin/ttp": "workspace:*", + "@tensamin/ui": "*", + "@tensamin/user": "workspace:*", + "lucide-react": "^1.8.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "sonner": "^2.0.7", + "zod": "^4.3.6", + }, + }, + "packages/ttp": { + "name": "@tensamin/ttp", + "version": "0.0.0", + "dependencies": { + "@tanstack/react-router": "^1.0.0", + "@tauri-apps/api": "^2", + "@tensamin/crypto": "workspace:*", + "@tensamin/shared": "workspace:*", + "@tensamin/storage": "workspace:*", + "@tensamin/ttp-core": "*", + "@tensamin/ui": "*", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "tauri-plugin-app-events-api": "^0.2.0", + "zod": "^4.3.6", + }, + "devDependencies": { + "eslint": "^10.0.3", + }, + }, + "packages/user": { + "name": "@tensamin/user", + "version": "0.0.0", + "dependencies": { + "@tensamin/shared": "workspace:*", + "@tensamin/storage": "workspace:*", + "@tensamin/ttp": "workspace:*", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "zod": "^4.3.6", + }, + }, + }, + "overrides": { + "@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.17.tar.gz", + "@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.31.tar.gz", + }, + "packages": { + "@antfu/ni": ["@antfu/ni@25.0.0", "", { "dependencies": { "ansis": "^4.0.0", "fzf": "^0.5.2", "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" }, "bin": { "na": "bin/na.mjs", "ni": "bin/ni.mjs", "nr": "bin/nr.mjs", "nci": "bin/nci.mjs", "nlx": "bin/nlx.mjs", "nun": "bin/nun.mjs", "nup": "bin/nup.mjs" } }, "sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA=="], + + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], + + "@babel/compat-data": ["@babel/compat-data@7.29.3", "", {}, "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg=="], + + "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], + + "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], + + "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + + "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.3", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.29.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], + + "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.28.6", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg=="], + + "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], + + "@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], + + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="], + + "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="], + + "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="], + + "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw=="], + + "@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="], + + "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], + + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], + + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@base-ui/react": ["@base-ui/react@1.4.1", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@base-ui/utils": "0.2.8", "@floating-ui/react-dom": "^2.1.8", "@floating-ui/utils": "^0.2.11", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@date-fns/tz": "^1.2.0", "@types/react": "^17 || ^18 || ^19", "date-fns": "^4.0.0", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@date-fns/tz", "@types/react", "date-fns"] }, "sha512-Ab5/LIhcmL8BQcsBUYiOfkSDRdLpvgUBzMK30cu684JPcLclYlztharvCZyNNgzJtbAiREzI9q0pI5erHCMgCw=="], + + "@base-ui/utils": ["@base-ui/utils@0.2.8", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@floating-ui/utils": "^0.2.11", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-jvOi+c+ftGlGotNcKnzPVg2IhCaDTB6/6R3JeqdjdXktuAJi3wKH9T7+svuaKh1mmfVU11UWzUZVH74JDfi/wQ=="], + + "@bufbuild/protobuf": ["@bufbuild/protobuf@1.10.1", "", {}, "sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ=="], + + "@codemirror/autocomplete": ["@codemirror/autocomplete@6.20.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A=="], + + "@codemirror/commands": ["@codemirror/commands@6.10.3", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q=="], + + "@codemirror/lang-css": ["@codemirror/lang-css@6.3.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.2", "@lezer/css": "^1.1.7" } }, "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg=="], + + "@codemirror/lang-html": ["@codemirror/lang-html@6.4.11", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-css": "^6.0.0", "@codemirror/lang-javascript": "^6.0.0", "@codemirror/language": "^6.4.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0", "@lezer/css": "^1.1.0", "@lezer/html": "^1.3.12" } }, "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw=="], + + "@codemirror/lang-javascript": ["@codemirror/lang-javascript@6.2.5", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.6.0", "@codemirror/lint": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0", "@lezer/javascript": "^1.0.0" } }, "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A=="], + + "@codemirror/lang-markdown": ["@codemirror/lang-markdown@6.5.0", "", { "dependencies": { "@codemirror/autocomplete": "^6.7.1", "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.3.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.2.1", "@lezer/markdown": "^1.0.0" } }, "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw=="], + + "@codemirror/language": ["@codemirror/language@6.12.3", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA=="], + + "@codemirror/lint": ["@codemirror/lint@6.9.5", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.35.0", "crelt": "^1.0.5" } }, "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA=="], + + "@codemirror/state": ["@codemirror/state@6.6.0", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ=="], + + "@codemirror/view": ["@codemirror/view@6.41.1", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-ToDnWKbBnke+ZLrP6vgTTDScGi5H37YYuZGniQaBzxMVdtCxMrslsmtnOvbPZk4RX9bvkQqnWR/WS/35tJA0qg=="], + + "@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="], + + "@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.64.0", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.4", "which": "^4.0.0", "yocto-spinner": "^1.1.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-6+xRpZaWuHXEqnhBjae+VmQI9Uaqw5Uzu/ScpO+W7ww9Zp3lHSNBoNjFcUxhrCyc7pRGQzyDjhKzloqrPHERiQ=="], + + "@ecies/ciphers": ["@ecies/ciphers@0.2.6", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g=="], + + "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@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-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "@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.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.5.5", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w=="], + + "@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="], + + "@eslint/js": ["@eslint/js@10.0.1", "", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="], + + "@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.1", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ=="], + + "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="], + + "@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="], + + "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.8", "", { "dependencies": { "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A=="], + + "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], + + "@fontsource-variable/inter": ["@fontsource-variable/inter@5.2.8", "", {}, "sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ=="], + + "@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="], + + "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], + + "@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], + + "@humanfs/types": ["@humanfs/types@0.15.0", "", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + + "@inquirer/ansi": ["@inquirer/ansi@2.0.5", "", {}, "sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw=="], + + "@inquirer/confirm": ["@inquirer/confirm@6.0.12", "", { "dependencies": { "@inquirer/core": "^11.1.9", "@inquirer/type": "^4.0.5" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-h9FgGun3QwVYNj5TWIZZ+slii73bMoBFjPfVIGtnFuL4t8gBiNDV9PcSfIzkuxvgquJKt9nr1QzszpBzTbH8Og=="], + + "@inquirer/core": ["@inquirer/core@11.1.9", "", { "dependencies": { "@inquirer/ansi": "^2.0.5", "@inquirer/figures": "^2.0.5", "@inquirer/type": "^4.0.5", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-BDE4fG22uYh1bGSifcj7JSx119TVYNViMhMu85usp4Fswrzh6M0DV3yld64jA98uOAa2GSQ4Bg4bZRm2d2cwSg=="], + + "@inquirer/figures": ["@inquirer/figures@2.0.5", "", {}, "sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ=="], + + "@inquirer/type": ["@inquirer/type@4.0.5", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@lezer/common": ["@lezer/common@1.5.2", "", {}, "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ=="], + + "@lezer/css": ["@lezer/css@1.3.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.0" } }, "sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg=="], + + "@lezer/highlight": ["@lezer/highlight@1.2.3", "", { "dependencies": { "@lezer/common": "^1.3.0" } }, "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g=="], + + "@lezer/html": ["@lezer/html@1.3.13", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg=="], + + "@lezer/javascript": ["@lezer/javascript@1.5.4", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.1.3", "@lezer/lr": "^1.3.0" } }, "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA=="], + + "@lezer/lr": ["@lezer/lr@1.4.10", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A=="], + + "@lezer/markdown": ["@lezer/markdown@1.6.3", "", { "dependencies": { "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0" } }, "sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw=="], + + "@livekit/components-core": ["@livekit/components-core@0.12.13", "", { "dependencies": { "@floating-ui/dom": "1.7.4", "loglevel": "1.9.1", "rxjs": "7.8.2" }, "peerDependencies": { "livekit-client": "^2.17.2", "tslib": "^2.6.2" } }, "sha512-DQmi84afHoHjZ62wm8y+XPNIDHTwFHAltjd3lmyXj8UZHOY7wcza4vFt1xnghJOD5wLRY58L1dkAgAw59MgWvw=="], + + "@livekit/components-react": ["@livekit/components-react@2.9.20", "", { "dependencies": { "@livekit/components-core": "0.12.13", "clsx": "2.1.1", "events": "^3.3.0", "jose": "^6.0.12", "usehooks-ts": "3.1.1" }, "peerDependencies": { "@livekit/krisp-noise-filter": "^0.2.12 || ^0.3.0", "livekit-client": "^2.17.2", "react": ">=18", "react-dom": ">=18", "tslib": "^2.6.2" }, "optionalPeers": ["@livekit/krisp-noise-filter"] }, "sha512-hjkYOsJj9Jbghb7wM5cI8HoVisKeL6Zcy1VnRWTLm0sqVbto8GJp/17T4Udx85mCPY6Jgh8I1Cv0yVzgz7CQtg=="], + + "@livekit/mutex": ["@livekit/mutex@1.1.1", "", {}, "sha512-EsshAucklmpuUAfkABPxJNhzj9v2sG7JuzFDL4ML1oJQSV14sqrpTYnsaOudMAw9yOaW53NU3QQTlUQoRs4czw=="], + + "@livekit/protocol": ["@livekit/protocol@1.45.3", "", { "dependencies": { "@bufbuild/protobuf": "^1.10.0" } }, "sha512-WmMxBTsy4dRBqcrswFwUUlgq3Z0nnhOqKR6tX749Rb/PcB1yBMUtrHxZvcsS6qi3/5+86zHeVG+exmu1sZqfJg=="], + + "@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="], + + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="], + + "@mswjs/interceptors": ["@mswjs/interceptors@0.41.8", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-pRLMNKTSGRoLq+KnEB/7OY5vijw1XmcheAAOiv6pj7W1FG32kAGqj1C/RK/cqxRGr1Fh+zBi8sDur8kj3EQv6A=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + + "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], + + "@noble/curves": ["@noble/curves@2.2.0", "", { "dependencies": { "@noble/hashes": "2.2.0" } }, "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ=="], + + "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@open-draft/deferred-promise": ["@open-draft/deferred-promise@3.0.0", "", {}, "sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA=="], + + "@open-draft/logger": ["@open-draft/logger@0.3.0", "", { "dependencies": { "is-node-process": "^1.2.0", "outvariant": "^1.4.0" } }, "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ=="], + + "@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="], + + "@oxc-project/types": ["@oxc-project/types@0.127.0", "", {}, "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ=="], + + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], + + "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], + + "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], + + "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="], + + "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="], + + "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="], + + "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], + + "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], + + "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], + + "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="], + + "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], + + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], + + "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], + + "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="], + + "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], + + "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], + + "@reduxjs/toolkit": ["@reduxjs/toolkit@2.11.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^11.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.17", "", { "os": "android", "cpu": "arm64" }, "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm" }, "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "s390x" }, "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.17", "", { "os": "none", "cpu": "arm64" }, "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.17", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "x64" }, "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.7", "", {}, "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA=="], + + "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], + + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], + + "@tabby_ai/hijri-converter": ["@tabby_ai/hijri-converter@1.0.5", "", {}, "sha512-r5bClKrcIusDoo049dSL8CawnHR6mRdDwhlQuIgZRNty68q0x8k3Lf1BtPAMxRf/GgnHBnIO4ujd3+GQdLWzxQ=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.2.4", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.4" } }, "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.4", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.4", "@tailwindcss/oxide-darwin-arm64": "4.2.4", "@tailwindcss/oxide-darwin-x64": "4.2.4", "@tailwindcss/oxide-freebsd-x64": "4.2.4", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", "@tailwindcss/oxide-linux-x64-musl": "4.2.4", "@tailwindcss/oxide-wasm32-wasi": "4.2.4", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" } }, "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.4", "", { "os": "android", "cpu": "arm64" }, "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.4", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.2.4", "", { "dependencies": { "@tailwindcss/node": "4.2.4", "@tailwindcss/oxide": "4.2.4", "tailwindcss": "4.2.4" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw=="], + + "@tanstack/history": ["@tanstack/history@1.161.6", "", {}, "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg=="], + + "@tanstack/query-core": ["@tanstack/query-core@5.100.8", "", {}, "sha512-ceYwSFOqjPwET5TA6IOYxzxlGc0ekyH/gfOtWkP0PX43rzX9bxW48Iuw8KAduKCToi4rJAQ6nRy2kAe8gszdmg=="], + + "@tanstack/react-query": ["@tanstack/react-query@5.100.8", "", { "dependencies": { "@tanstack/query-core": "5.100.8" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-iNNEekixXU5vtAGKKZX2lx3jTooG5yNY+kv0wSgEdEYG0Mj0JM5bcuQtC35ZAP3nDopT6jciUK3xeX65U7AnfA=="], + + "@tanstack/react-router": ["@tanstack/react-router@1.169.1", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/react-store": "^0.9.3", "@tanstack/router-core": "1.169.1", "isbot": "^5.1.22" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-MBtQKSvac3OCcsSa6oBpDrrN90IV47I6Gtv05NxhbFVh+gVjtqvs6HSU4XM9+y5sHZPgS+35eArflX4vM8GEnQ=="], + + "@tanstack/react-store": ["@tanstack/react-store@0.9.3", "", { "dependencies": { "@tanstack/store": "0.9.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg=="], + + "@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.24", "", { "dependencies": { "@tanstack/virtual-core": "3.14.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-aIJvz5OSkhNIhZIpYivrxrPTKYsjW9Uzy+sP/mx0S3sev2HyvPb7xmjbYvokzEpfgYHy/HjzJ2zFAETuUfgCpg=="], + + "@tanstack/router-core": ["@tanstack/router-core@1.169.1", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-x+2gIGKTTE1qAn7tLieGfrB5ciOviDmmi2ox9fAWUubRV+yTU5ruGFXocoCIWF+lB+SOtnHjo2E9BLSWyYoEmA=="], + + "@tanstack/store": ["@tanstack/store@0.9.3", "", {}, "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw=="], + + "@tanstack/virtual-core": ["@tanstack/virtual-core@3.14.0", "", {}, "sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q=="], + + "@tauri-apps/api": ["@tauri-apps/api@2.11.0", "", {}, "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA=="], + + "@tauri-apps/cli": ["@tauri-apps/cli@2.11.0", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.11.0", "@tauri-apps/cli-darwin-x64": "2.11.0", "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.0", "@tauri-apps/cli-linux-arm64-gnu": "2.11.0", "@tauri-apps/cli-linux-arm64-musl": "2.11.0", "@tauri-apps/cli-linux-riscv64-gnu": "2.11.0", "@tauri-apps/cli-linux-x64-gnu": "2.11.0", "@tauri-apps/cli-linux-x64-musl": "2.11.0", "@tauri-apps/cli-win32-arm64-msvc": "2.11.0", "@tauri-apps/cli-win32-ia32-msvc": "2.11.0", "@tauri-apps/cli-win32-x64-msvc": "2.11.0" }, "bin": { "tauri": "tauri.js" } }, "sha512-W5Wbuqsb2pHFPTj4TaRNKTj5rwXhDShPiLSY9T18y4ouSR/NNCptAEFxFsBtyNRgL6Vs1a/q9LzfqqYzEwC+Jw=="], + + "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.11.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UfMeDNlgIP252rm/KSTuu8yHatPua5TjtUEUf+jyIzVwBNcIl7Ywkdpfj+e5jVVg3EfCTp+4gwuL1dNpgF8clg=="], + + "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.11.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-lY1+aPlgyMN7vgjtCdQ3+WODfZkebAcxnrCrO0HjqDpKSXieDkrJbimqeaoM4RwhTSrCLRHfVYiYrfE5E131tg=="], + + "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.11.0", "", { "os": "linux", "cpu": "arm" }, "sha512-5uCP0AusgN3NrKC8EpkuJwjek1k8pEffBdugJSpXPey/QGbPEb8vZ542n/giJ2mZPjMSllDkdhG2QIDpBY4PpQ=="], + + "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.11.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-loDPqtRHMSbIcrH2VBd4GgHoQlF7jJnrZj7MxA2lj1cixS/jEgMAPFqj83U6Wvjete4HfYplbE/gCpSFifA9jw=="], + + "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.11.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-DtSE8ZBlB9H+L+eHkfZ3myt00EVEyAB3e41juEHoE2qT88fgVlJvyrwa9SZYc/xTwCS9TnmK+R84tpg+ZsAg7Q=="], + + "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.11.0", "", { "os": "linux", "cpu": "none" }, "sha512-5QdgS4LD+kntClI1aj2JmwjW38LosNXxwCe8viIHEwqYIWuMPdNEIau6/cLogI38Yzx9DnfCPRfEWLyI+5li8Q=="], + + "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.11.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5UynPXo3Zq9khjVdAbD+YogeLltdVUeOah2ioSIM3tu6H7wY9vMy6rgGJhv9r5R8ZXmk9GttMippdqYJWrnLnA=="], + + "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.11.0", "", { "os": "linux", "cpu": "x64" }, "sha512-CNz7fHbApz1Zyhhq73jtGn9JqgNEV/lIWnTnUo6h6ujw+mHsTmkLszvJSM8W6JBaDjNpTTFr/RSNoVL5FMwcTg=="], + + "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.11.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-K+br+VXZ+Xx0n/9FdWohpW5Ugq+2FQUpJScqcPl1hTxXfh3fgjYgt4qA2NgrjlJo+zZPNrmUMl+NLvm0ufEqBQ=="], + + "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.11.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-OFV+s3MLZnd75zl0ZAFU5riMpGK4waUEA8ZDuijDsnkU0btz/gHhqh5jVlOn8thyvgdtT3Xyoxqo099MMifH3g=="], + + "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.11.0", "", { "os": "win32", "cpu": "x64" }, "sha512-AeDTWBd2cOZ6TX133BWsoo+LutG9o0JRcgjMsIfLE13ZugpgCMv/2dJbUiBGeRvbPOGin5A3aYmsArPVV6ZSHQ=="], + + "@tauri-apps/plugin-barcode-scanner": ["@tauri-apps/plugin-barcode-scanner@2.4.4", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-uXvyMI8UgQjSrGxzTU5isNoQarMGRxFmTmb4TsgiWZHf/g7LsIyAQCwoFShjax0fXCK5mdVKDOvlkfOr21fo6g=="], + + "@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.4.9", "", { "dependencies": { "@tauri-apps/api": "^2.11.0" } }, "sha512-u0SKOUHnJ1wqeqXsDFq2+kASCBj9xxbG0g9XZWPy9SOmU4wXtp6b/wiYpm6oH6/5fBTQsLqnLhIvqLBRpgHJlA=="], + + "@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.4", "", { "dependencies": { "@tauri-apps/api": "^2.11.0" } }, "sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ=="], + + "@tensamin/call": ["@tensamin/call@workspace:packages/call"], + + "@tensamin/chat": ["@tensamin/chat@workspace:packages/chat"], + + "@tensamin/crypto": ["@tensamin/crypto@workspace:packages/crypto"], + + "@tensamin/markdown": ["@tensamin/markdown@workspace:packages/markdown"], + + "@tensamin/notifications": ["@tensamin/notifications@workspace:packages/notifications"], + + "@tensamin/shared": ["@tensamin/shared@workspace:packages/shared"], + + "@tensamin/storage": ["@tensamin/storage@workspace:packages/storage"], + + "@tensamin/tauri": ["@tensamin/tauri@workspace:apps/tauri"], + + "@tensamin/tauth": ["@tensamin/tauth@workspace:packages/tauth"], + + "@tensamin/ttp": ["@tensamin/ttp@workspace:packages/ttp"], + + "@tensamin/ttp-core": ["@tensamin/ttp-core@https://git.methanium.net/tensamin/ttp/archive/0.0.17.tar.gz", { "dependencies": { "@eslint/js": "^10.0.1", "@typescript-eslint/parser": "^8.59.1", "@webtransport-bun/webtransport": "^0.3.0", "globals": "^17.5.0", "typescript": "^6.0.3", "typescript-eslint": "^8.59.1", "zod": "^4.4.1" } }, "sha512-smyx+04hSnWM1oyWJfJrRWmxu7OInwyHeIlaD1h3tUrkrSxiKWHl1qCmxVO1bC+cDwyUXhJ5HUnNCbx1aWx7Dg=="], + + "@tensamin/ui": ["@tensamin/ui@https://git.methanium.net/tensamin/ui/archive/0.0.31.tar.gz", { "dependencies": { "@base-ui/react": "^1.3.0", "@fontsource-variable/inter": "^5.2.6", "@tauri-apps/api": "^2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", "embla-carousel-react": "^8.6.0", "input-otp": "^1.4.2", "lucide-react": "^1.8.0", "next-themes": "^0.4.6", "react-day-picker": "^9.14.0", "react-resizable-panels": "^4.10.0", "recharts": "3.8.0", "shadcn": "^3.5.0", "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.3.0", "vaul": "^1.1.2" }, "peerDependencies": { "react": "^19.2.0", "react-dom": "^19.2.0" } }, "sha512-I2FhsDtR5ElHwCasf6CfFCiyr5T2Cp9MQ2uAhcMnoNZ0tsK0mJhs+Rz9nUDmysydNyqOj8j/uC0CYXnnG1pCxQ=="], + + "@tensamin/user": ["@tensamin/user@workspace:packages/user"], + + "@tensamin/web": ["@tensamin/web@workspace:apps/web"], + + "@ts-morph/common": ["@ts-morph/common@0.27.0", "", { "dependencies": { "fast-glob": "^3.3.3", "minimatch": "^10.0.1", "path-browserify": "^1.0.1" } }, "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], + + "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], + + "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="], + + "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], + + "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], + + "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], + + "@types/d3-shape": ["@types/d3-shape@3.1.8", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w=="], + + "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], + + "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], + + "@types/dom-mediacapture-record": ["@types/dom-mediacapture-record@1.0.22", "", {}, "sha512-mUMZLK3NvwRLcAAT9qmcK+9p7tpU2FHdDsntR3YI4+GY88XrgG4XiE7u1Q2LAN2/FZOz/tdMDC3GQCR4T8nFuw=="], + + "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], + + "@types/qrcode": ["@types/qrcode@1.5.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw=="], + + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + + "@types/set-cookie-parser": ["@types/set-cookie-parser@2.4.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw=="], + + "@types/statuses": ["@types/statuses@2.0.6", "", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="], + + "@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="], + + "@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.59.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/type-utils": "8.59.1", "@typescript-eslint/utils": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.59.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.59.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/types": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.59.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.59.1", "@typescript-eslint/types": "^8.59.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.1", "", { "dependencies": { "@typescript-eslint/types": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1" } }, "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.59.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.59.1", "", { "dependencies": { "@typescript-eslint/types": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1", "@typescript-eslint/utils": "8.59.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.59.1", "", {}, "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.1", "@typescript-eslint/tsconfig-utils": "8.59.1", "@typescript-eslint/types": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.59.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/types": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.1", "", { "dependencies": { "@typescript-eslint/types": "8.59.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg=="], + + "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="], + + "@webtransport-bun/webtransport": ["@webtransport-bun/webtransport@0.3.0", "", { "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ] }, "sha512-/OX/TCgBD64n/0BjMNytObq5NK2pAM0KOoXBEw49l3sTmbVXNTPP1ZRPIdIyV43agM+mKtZBaV3Er3k3YoB6sw=="], + + "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], + + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], + + "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], + + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.25", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-QO/VHsXCQdnzADMfmkeOPvHdIAkoB7i0/rGjINPJEetLx75hNttVWGQ/jycHUDP9zZ9rupbm60WRxcwViB0MiA=="], + + "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], + + "brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], + + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], + + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001791", "", {}, "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ=="], + + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], + + "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], + + "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + + "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], + + "cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "cmdk": ["cmdk@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg=="], + + "code-block-writer": ["code-block-writer@13.0.3", "", {}, "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "comlink": ["comlink@4.4.2", "", {}, "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g=="], + + "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], + + "content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="], + + "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + + "cookie-es": ["cookie-es@3.1.1", "", {}, "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg=="], + + "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], + + "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], + + "cosmiconfig": ["cosmiconfig@9.0.1", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ=="], + + "crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], + + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + + "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], + + "d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="], + + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + + "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], + + "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], + + "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], + + "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], + + "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], + + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + + "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="], + + "date-fns-jalali": ["date-fns-jalali@4.1.0-0", "", {}, "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], + + "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="], + + "dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "deepfilternet3-noise-filter": ["deepfilternet3-noise-filter@1.2.1", "", { "peerDependencies": { "livekit-client": "^2.0.0" } }, "sha512-OAyrHTDlUHH+AhfpVNKYEOhVqb9cZpu0fdNThplA/tB/Ts4PF/UsI+abl2n1IbSxUkhiF0OqDejEhk1n42Oqpw=="], + + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + + "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], + + "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], + + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], + + "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], + + "dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="], + + "dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "eciesjs": ["eciesjs@0.4.18", "", { "dependencies": { "@ecies/ciphers": "^0.2.5", "@noble/ciphers": "^1.3.0", "@noble/curves": "^1.9.7", "@noble/hashes": "^1.8.0" } }, "sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ=="], + + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.349", "", {}, "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A=="], + + "embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="], + + "embla-carousel-react": ["embla-carousel-react@8.6.0", "", { "dependencies": { "embla-carousel": "8.6.0", "embla-carousel-reactive-utils": "8.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA=="], + + "embla-carousel-reactive-utils": ["embla-carousel-reactive-utils@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + + "enhanced-resolve": ["enhanced-resolve@5.21.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA=="], + + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-toolkit": ["es-toolkit@1.46.1", "", {}, "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ=="], + + "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=="], + + "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@10.3.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.5.5", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw=="], + + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.1.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g=="], + + "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], + + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + + "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], + + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + + "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], + + "eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="], + + "execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="], + + "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], + + "express-rate-limit": ["express-rate-limit@8.4.1", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="], + + "fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="], + + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + + "fast-wrap-ansi": ["fast-wrap-ansi@0.2.0", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w=="], + + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + + "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], + + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], + + "framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="], + + "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + + "fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "fuzzysort": ["fuzzysort@3.1.0", "", {}, "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="], + + "fzf": ["fzf@0.5.2", "", {}, "sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], + + "get-own-enumerable-keys": ["get-own-enumerable-keys@1.0.0", "", {}, "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@17.6.0", "", {}, "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "graphql": ["graphql@16.13.2", "", {}, "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], + + "headers-polyfill": ["headers-polyfill@5.0.1", "", { "dependencies": { "@types/set-cookie-parser": "^2.4.10", "set-cookie-parser": "^3.0.1" } }, "sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA=="], + + "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], + + "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], + + "hono": ["hono@4.12.16", "", {}, "sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg=="], + + "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], + + "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "immer": ["immer@10.2.0", "", {}, "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "input-otp": ["input-otp@1.4.2", "", { "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA=="], + + "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], + + "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], + + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + + "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], + + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-in-ssh": ["is-in-ssh@1.0.0", "", {}, "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw=="], + + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + + "is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="], + + "is-node-process": ["is-node-process@1.2.0", "", {}, "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-obj": ["is-obj@3.0.0", "", {}, "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + + "is-regexp": ["is-regexp@3.1.0", "", {}, "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA=="], + + "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="], + + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + + "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], + + "isbot": ["isbot@5.1.39", "", {}, "sha512-obH0yYahGXdzNxo+djmHhBYThUKDkz565cxkIlt2L9hXfv1NlaLKoDBHo6KxXsYrIXx2RK3x5vY36CfZcobxEw=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + + "jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "livekit-client": ["livekit-client@2.18.8", "", { "dependencies": { "@livekit/mutex": "1.1.1", "@livekit/protocol": "1.45.3", "events": "^3.3.0", "jose": "^6.1.0", "loglevel": "^1.9.2", "sdp-transform": "^2.15.0", "tslib": "2.8.1", "typed-emitter": "^2.1.0", "webrtc-adapter": "9.0.5" }, "peerDependencies": { "@types/dom-mediacapture-record": "^1" } }, "sha512-E+bSpnBVng/1xG4RfL1Q51dHUpBwL14Wix4sR5bS0djEzKMEtrxcUyhWLltdwQ0USf1t0PaxW6WL4oVb2s4Fsw=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], + + "log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], + + "loglevel": ["loglevel@1.9.2", "", {}, "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "lucide-react": ["lucide-react@1.14.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], + + "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], + + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + + "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + + "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "motion-dom": ["motion-dom@12.38.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="], + + "motion-utils": ["motion-utils@12.36.0", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "msw": ["msw@2.14.2", "", { "dependencies": { "@inquirer/confirm": "^6.0.11", "@mswjs/interceptors": "^0.41.3", "@open-draft/deferred-promise": "^3.0.0", "@types/statuses": "^2.0.6", "cookie": "^1.1.1", "graphql": "^16.13.2", "headers-polyfill": "^5.0.1", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.11.7", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.1", "type-fest": "^5.5.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-D2bTe0tpuf9nw4DA39wFaqUD/hRPKj0DKpo2lAqu+A47Ifg4+h0hbfn6QxVOsiUY2uhgEN6TTpGSHDsc+ysYNg=="], + + "mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="], + + "nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + + "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="], + + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + + "node-releases": ["node-releases@2.0.38", "", {}, "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="], + + "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "object-treeify": ["object-treeify@1.1.33", "", {}, "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A=="], + + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], + + "outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + + "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + + "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], + + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + + "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], + + "pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="], + + "postcss": ["postcss@8.5.13", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag=="], + + "postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], + + "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prettier": ["prettier@3.8.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="], + + "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], + + "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], + + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="], + + "qs": ["qs@6.15.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], + + "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], + + "react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="], + + "react-day-picker": ["react-day-picker@9.14.0", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "@tabby_ai/hijri-converter": "1.0.5", "date-fns": "^4.1.0", "date-fns-jalali": "4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-tBaoDWjPwe0M5pGrum4H0SR6Lyk+BO9oHnp9JbKpGKW2mlraNPgP9BMfsg5pWpwrssARmeqk7YBl2oXutZTaHA=="], + + "react-dom": ["react-dom@19.2.5", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="], + + "react-is": ["react-is@19.2.5", "", {}, "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ=="], + + "react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="], + + "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], + + "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], + + "react-resizable-panels": ["react-resizable-panels@4.10.0", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-frjewRQt7TCv/vCH1pJfjZ7RxAhr5pKuqVQtVgzFq/vherxBFOWyC3xMbryx5Ti2wylViGUFc93Etg4rB3E0UA=="], + + "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], + + "recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="], + + "recharts": ["recharts@3.8.1", "", { "dependencies": { "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg=="], + + "redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="], + + "redux-thunk": ["redux-thunk@3.1.0", "", { "peerDependencies": { "redux": "^5.0.0" } }, "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="], + + "reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], + + "rettime": ["rettime@0.11.8", "", {}, "sha512-0fERGXktJTyJ+h8fBEiPxHPEFOu0h15JY7JtwrOVqR5K+vb99ho6IyOo7ekLS3h4sJCzIDy4VWKIbZUfe9njmg=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rolldown": ["rolldown@1.0.0-rc.17", "", { "dependencies": { "@oxc-project/types": "=0.127.0", "@rolldown/pluginutils": "1.0.0-rc.17" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-x64": "1.0.0-rc.17", "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA=="], + + "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], + + "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "sdp": ["sdp@3.2.2", "", {}, "sha512-xZocWwfyp4hkbN4hLWxMjmv2Q8aNa9MhmOZ7L9aCZPT+dZsgRr6wZRrSYE3HTdyk/2pZKPSgqI7ns7Een1xMSA=="], + + "sdp-transform": ["sdp-transform@2.15.0", "", { "bin": { "sdp-verify": "checker.js" } }, "sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw=="], + + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], + + "seroval": ["seroval@1.5.2", "", {}, "sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q=="], + + "seroval-plugins": ["seroval-plugins@1.5.2", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg=="], + + "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], + + "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], + + "set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="], + + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], + + "shadcn": ["shadcn@4.6.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-4XeMwFf8ZZxmqQQp+U+Nsq2M+cY4Da8Joo/EaMdHVc4uVuWSTJoeidlZ3gDjyxXCjYB1FLcxYwR4lYQAH8emOg=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + + "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], + + "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], + + "strict-event-emitter": ["strict-event-emitter@0.5.1", "", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "stringify-object": ["stringify-object@5.0.0", "", { "dependencies": { "get-own-enumerable-keys": "^1.0.0", "is-obj": "^3.0.0", "is-regexp": "^3.1.0" } }, "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg=="], + + "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], + + "strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="], + + "style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="], + + "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], + + "tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="], + + "tailwind-scrollbar-hide": ["tailwind-scrollbar-hide@4.0.0", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || >= 4.0.0 || >= 4.0.0-beta.8 || >= 4.0.0-alpha.20" } }, "sha512-gobtvVcThB2Dxhy0EeYSS1RKQJ5baDFkamkhwBvzvevwX6L4XQfpZ3me9s25Ss1ecFVT5jPYJ50n+7xTBJG9WQ=="], + + "tailwindcss": ["tailwindcss@4.2.4", "", {}, "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA=="], + + "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + + "tauri-plugin-app-events-api": ["tauri-plugin-app-events-api@0.2.0", "", { "dependencies": { "@tauri-apps/api": "^2.0.3" } }, "sha512-CnlAeucWhT+Rgx6CCsqUpq2D3pqgx0fhjSPKomH7O2t1rGVu5kGLMkYvaCX6gqVTUYpIWX5BLV7LGY2DRri0QA=="], + + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + + "tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], + + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + + "tldts": ["tldts@7.0.30", "", { "dependencies": { "tldts-core": "^7.0.30" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw=="], + + "tldts-core": ["tldts-core@7.0.30", "", {}, "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + + "tough-cookie": ["tough-cookie@6.0.1", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw=="], + + "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], + + "ts-morph": ["ts-morph@26.0.0", "", { "dependencies": { "@ts-morph/common": "~0.27.0", "code-block-writer": "^13.0.3" } }, "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug=="], + + "tsconfig-paths": ["tsconfig-paths@4.2.0", "", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], + + "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], + + "typed-emitter": ["typed-emitter@2.1.0", "", { "optionalDependencies": { "rxjs": "*" } }, "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA=="], + + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], + + "typescript-eslint": ["typescript-eslint@8.59.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.59.1", "@typescript-eslint/parser": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1", "@typescript-eslint/utils": "8.59.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ=="], + + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], + + "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], + + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + + "until-async": ["until-async@3.0.2", "", {}, "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], + + "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], + + "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + + "usehooks-ts": ["usehooks-ts@3.1.1", "", { "dependencies": { "lodash.debounce": "^4.0.8" }, "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="], + + "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + + "vaul": ["vaul@1.1.2", "", { "dependencies": { "@radix-ui/react-dialog": "^1.1.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA=="], + + "victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="], + + "vite": ["vite@8.0.10", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.10", "rolldown": "1.0.0-rc.17", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw=="], + + "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], + + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + + "webrtc-adapter": ["webrtc-adapter@9.0.5", "", { "dependencies": { "sdp": "^3.2.0" } }, "sha512-U9vjByy/sK2OMXu5mmfuZFKTMIUQe34c0JXRO+oDrxJTsntdYT2iIFwYMOV7HhMTuktcZLGf2W1N/OcSf9ssWg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], + + "y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], + + "yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "yocto-spinner": ["yocto-spinner@1.1.0", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-/BY0AUXnS7IKO354uLLA2eRcWiqDifEbd6unXCsOxkFDAkhgUL3PH9X2bFoaU0YchnDXsF+iKleeTLJGckbXfA=="], + + "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], + + "zod": ["zod@4.4.2", "", {}, "sha512-IynmDyxsEsb9RKzO3J9+4SxXnl2FTFSzNBaKKaMV6tsSk0rw9gYw9gs+JFCq/qk2LCZ78KDwyj+Z289TijSkUw=="], + + "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], + + "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], + + "zustand": ["zustand@5.0.12", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g=="], + + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@dotenvx/dotenvx/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], + + "@dotenvx/dotenvx/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], + + "@dotenvx/dotenvx/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@floating-ui/react-dom/@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="], + + "@livekit/components-core/loglevel": ["loglevel@1.9.1", "", {}, "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg=="], + + "@modelcontextprotocol/sdk/ajv": ["ajv@8.20.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-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "@mswjs/interceptors/@open-draft/deferred-promise": ["@open-draft/deferred-promise@2.2.0", "", {}, "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA=="], + + "@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-focus-scope/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-portal/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], + + "@reduxjs/toolkit/immer": ["immer@11.1.4", "", {}, "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@tensamin/ui/recharts": ["recharts@3.8.0", "", { "dependencies": { "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Z/m38DX3L73ExO4Tpc9/iZWHmHnlzWG4njQbxsF5aSjwqmHNDDIm0rdEBArkwsBvR8U6EirlEHiQNYWCVh9sGQ=="], + + "@tensamin/ui/shadcn": ["shadcn@3.8.5", "", { "dependencies": { "@antfu/ni": "^25.0.0", "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-jPRx44e+eyeV7xwY3BLJXcfrks00+M0h5BGB9l6DdcBW4BpAj4x3lVmVy0TXPEs2iHEisxejr62sZAAw6B1EVA=="], + + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "ajv-formats/ajv": ["ajv@8.20.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-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "eciesjs/@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="], + + "eciesjs/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + + "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], + + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "msw/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=="], + + "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], + + "restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + + "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.17", "", {}, "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg=="], + + "router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], + + "shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + + "@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + + "@dotenvx/dotenvx/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + + "@dotenvx/dotenvx/execa/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "@dotenvx/dotenvx/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + + "@dotenvx/dotenvx/execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "@dotenvx/dotenvx/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + + "@dotenvx/dotenvx/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], + + "@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "@tensamin/ui/shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "msw/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=="], + + "msw/yargs/y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "msw/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "yargs/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + + "msw/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "msw/yargs/cliui/wrap-ansi": ["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=="], + + "yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "msw/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + } +} diff --git a/eslint.config.ts b/eslint.config.ts index f9bc102..2209bf9 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -5,17 +5,12 @@ import reactHooks from "eslint-plugin-react-hooks"; import * as tsParser from "@typescript-eslint/parser"; import { dirname } from "node:path"; import { fileURLToPath } from "node:url"; -import { - inlineSingleUseDeclarations, - noReactNamespaceImport, - noWindowLocationReload, -} from "./utils/eslint-rules/index.js"; const tsconfigRootDir = dirname(fileURLToPath(import.meta.url)); export default [ { - ignores: ["**/dist/**", "**/node_modules/**"], + ignores: ["**/dist/**", "**/node_modules/**", "**/.tmp/**"], }, js.configs.recommended, ...tseslint.configs.recommended, @@ -33,20 +28,9 @@ export default [ }, plugins: { "react-hooks": reactHooks, - tensamin: { - rules: { - "inline-single-use-declarations": inlineSingleUseDeclarations, - "no-react-namespace-import": noReactNamespaceImport, - "no-window-location-reload": noWindowLocationReload, - }, - }, }, rules: { ...reactHooks.configs.recommended.rules, - "react-hooks/set-state-in-effect": "off", - "tensamin/inline-single-use-declarations": "error", - "tensamin/no-react-namespace-import": "error", - "tensamin/no-window-location-reload": "error", }, }, ]; diff --git a/flake.lock b/flake.lock deleted file mode 100644 index aeb3d70..0000000 --- a/flake.lock +++ /dev/null @@ -1,62 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1782467914, - "narHash": "sha256-pGvFkM8N0xEkIIXDe5YYfbEAvHrk4IxBrjB/x8OomhE=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "e73de5be04e0eff4190a1432b946d469c794e7b4", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_2": { - "locked": { - "lastModified": 1744536153, - "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs", - "rust-overlay": "rust-overlay" - } - }, - "rust-overlay": { - "inputs": { - "nixpkgs": "nixpkgs_2" - }, - "locked": { - "lastModified": 1782616745, - "narHash": "sha256-NN5B1cKBXF6h1Ec681gMGZ2o/99d7vKXAAfFNEvyOKA=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "b916d014dc57eb555f84e51172a82f7f6fb560d7", - "type": "github" - }, - "original": { - "owner": "oxalica", - "repo": "rust-overlay", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index 0b0d63c..0000000 --- a/flake.nix +++ /dev/null @@ -1,441 +0,0 @@ -{ - description = "Tensamin Client"; - - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - rust-overlay.url = "github:oxalica/rust-overlay"; - }; - - outputs = - { - self, - nixpkgs, - rust-overlay, - ... - }: - let - systems = [ "x86_64-linux" ]; - forAllSystems = nixpkgs.lib.genAttrs systems; - version = "0.0.11"; - in - { - packages = forAllSystems ( - system: - let - pkgs = import nixpkgs { - inherit system; - overlays = [ (import rust-overlay) ]; - config = { - allowUnfree = true; - android_sdk.accept_license = true; - }; - }; - electronRuntimeLibs = with pkgs; [ - alsa-lib - at-spi2-atk - at-spi2-core - atk - cairo - cups - dbus - expat - fontconfig - freetype - gdk-pixbuf - glib - gtk3 - libdrm - libgbm - libglvnd - libnotify - libpulseaudio - libsecret - libuuid - libxkbcommon - mesa - nspr - nss - pango - pipewire - systemd - wayland - libX11 - libXScrnSaver - libXcomposite - libXcursor - libXdamage - libXext - libXfixes - libXi - libXrandr - libXtst - libxcb - ]; - electron = pkgs.electron; - pnpm = pkgs.pnpm; - mtpTypeMaps = pkgs.fetchgit { - url = "https://git.methanium.net/tensamin/mtp-type-maps"; - rev = "6e5122fe44f793c0e0d3229b3d34145ce17c2d31"; - hash = "sha256-/4n8F0YLJaLncefKL907P5l+en1CTjpgdFmLBF3gbiQ="; - }; - mtpSource = pkgs.fetchzip { - url = "https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz"; - hash = "sha256-XLTa8DxP93Q4hBHRCLUzCPOqkbdb4V3aZxE5Iuq+kW0="; - }; - mtpCargoDeps = pkgs.rustPlatform.fetchCargoVendor { - src = mtpSource; - hash = "sha256-8MZ65N/EtWPAggal0JkGDx3WSn+LxWQORinkqVbsrys="; - }; - wasmBindgenCliSource = pkgs.fetchCrate { - pname = "wasm-bindgen-cli"; - version = "0.2.127"; - hash = "sha256-di+qBAdd7pENLiIB9CoZoab+W5xeDoByMREcCGTSzWo="; - }; - wasmBindgenCli = pkgs.buildWasmBindgenCli { - src = wasmBindgenCliSource; - cargoDeps = pkgs.rustPlatform.fetchCargoVendor { - src = wasmBindgenCliSource; - hash = "sha256-FTv2GZIAQs0ePdIZXIXil7JbZ6kIT05VG6vqC1qNFxQ="; - }; - }; - desktopItem = pkgs.makeDesktopItem { - name = "tensamin"; - desktopName = "Tensamin"; - exec = "tensamin"; - icon = "tensamin"; - startupWMClass = "Tensamin"; - categories = [ "Network" ]; - }; - defaultPackage = pkgs.stdenv.mkDerivation (finalAttrs: { - pname = "tensamin"; - inherit version; - src = self; - - pnpmDeps = pkgs.fetchPnpmDeps { - inherit (finalAttrs) pname version src; - inherit pnpm; - fetcherVersion = 4; - hash = "sha256-imP3MTr1YLc28Z9n617m0Wt/6vPirFznzoriKzojlEg="; - }; - - nativeBuildInputs = with pkgs; [ - copyDesktopItems - makeWrapper - nodejs_22 - pnpm - pnpmConfigHook - cargo - lld - rustc - wasm-pack - wasmBindgenCli - binaryen - ]; - - env.ELECTRON_SKIP_BINARY_DOWNLOAD = 1; - - postPatch = '' - rm -rf mtp-type-maps - ln -s ${mtpTypeMaps} mtp-type-maps - - node -e ' - const fs = require("fs"); - const path = "apps/electron/package.json"; - const pkg = JSON.parse(fs.readFileSync(path, "utf8")); - pkg.version = "${version}"; - fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + "\n"); - ' - ''; - - buildPhase = '' - runHook preBuild - - mkdir -p "$HOME/.cargo" - substitute ${mtpCargoDeps}/.cargo/config.toml "$HOME/.cargo/config.toml" \ - --replace-fail @vendor@ ${mtpCargoDeps} - - pnpm run copy-licenses - pnpm run build:packages - pnpm run build:web - pnpm --dir apps/tauri run gen-icons - pnpm --dir apps/electron run build - pnpm --dir apps/electron exec electron-builder --dir --linux --publish never \ - --config.electronDist=${electron.dist} \ - --config.electronVersion=${electron.version} - - runHook postBuild - ''; - - installPhase = '' - runHook preInstall - - mkdir -p "$out/lib/tensamin" "$out/bin" - cp -r apps/electron/release/linux-unpacked/. "$out/lib/tensamin/" - - makeWrapper "$out/lib/tensamin/tensamin" "$out/bin/tensamin" \ - --prefix LD_LIBRARY_PATH : "${pkgs.lib.makeLibraryPath electronRuntimeLibs}" - - install -Dm644 apps/electron/build/icons/icon.png \ - "$out/share/icons/hicolor/512x512/apps/tensamin.png" - - runHook postInstall - ''; - - desktopItems = [ desktopItem ]; - - meta = { - description = "Tensamin desktop client"; - homepage = "https://git.methanium.net/tensamin/client"; - mainProgram = "tensamin"; - platforms = pkgs.lib.platforms.linux; - }; - }); - in - { - default = defaultPackage; - tensamin = defaultPackage; - electron = defaultPackage; - } - ); - - devShells = forAllSystems ( - system: - let - pkgs = import nixpkgs { - inherit system; - overlays = [ (import rust-overlay) ]; - config = { - allowUnfree = true; - android_sdk.accept_license = true; - }; - }; - - electronRuntimeLibs = with pkgs; [ - alsa-lib - at-spi2-atk - at-spi2-core - atk - cairo - cups - dbus - expat - fontconfig - freetype - gdk-pixbuf - glib - gtk3 - libdrm - libgbm - libglvnd - libnotify - libpulseaudio - libsecret - libuuid - libxkbcommon - mesa - nspr - nss - pango - pipewire - systemd - wayland - libX11 - libXScrnSaver - libXcomposite - libXcursor - libXdamage - libXext - libXfixes - libXi - libXrandr - libXtst - libxcb - ]; - - buildToolsVersion = "35.0.0"; - ndkVersion = "29.0.14206865"; - android = pkgs.androidenv.composeAndroidPackages { - cmdLineToolsVersion = "8.0"; - toolsVersion = "26.1.1"; - platformToolsVersion = "35.0.2"; - buildToolsVersions = [ buildToolsVersion ]; - platformVersions = [ - "35" - "36" - ]; - includeSources = false; - includeSystemImages = false; - includeNDK = true; - ndkVersions = [ ndkVersion ]; - useGoogleAPIs = false; - }; - rustToolchain = pkgs.rust-bin.stable.latest.default.override { - extensions = [ - "rust-src" - "rust-analyzer" - ]; - targets = [ - "aarch64-linux-android" - "armv7-linux-androideabi" - "i686-linux-android" - "x86_64-linux-android" - "wasm32-unknown-unknown" - ]; - }; - - commonDeps = [ - rustToolchain - pkgs.wasm-pack - pkgs.lld - ]; - appImageToolsArchive = pkgs.fetchurl { - url = "https://github.com/electron-userland/electron-builder-binaries/releases/download/appimage@1.0.3/appimage-tools-runtime-20251108.tar.gz"; - hash = "sha256-hAIaeO4hSub9M6LWKpK6JVQt0QvIa/EXqbLQu6ROdmU="; - }; - appImageTools = - pkgs.runCommand "electron-builder-appimage-tools-nix" - { - nativeBuildInputs = [ - pkgs.gnutar - pkgs.gzip - ]; - } - '' - mkdir -p "$out" - tar -xzf ${appImageToolsArchive} --strip-components=1 -C "$out" - rm -f "$out/mksquashfs" "$out/desktop-file-validate" - ln -s ${pkgs.squashfsTools}/bin/mksquashfs "$out/mksquashfs" - ln -s ${pkgs.desktop-file-utils}/bin/desktop-file-validate "$out/desktop-file-validate" - ''; - in - rec { - default = electron; - - electron = pkgs.mkShell { - packages = - with pkgs; - [ - nodejs_22 - corepack_22 - coreutils - pnpm - pkgs.electron - pkg-config - python3 - gcc - gnumake - git - jq - patchelf - dpkg - rpm - fpm - curl - fakeroot - rsync - xz - p7zip - ] - ++ electronRuntimeLibs - ++ commonDeps; - - shellHook = '' - export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath electronRuntimeLibs}:$LD_LIBRARY_PATH" - export ELECTRON_ENABLE_LOGGING=1 - export ELECTRON_OZONE_PLATFORM_HINT="''${ELECTRON_OZONE_PLATFORM_HINT:-auto}" - export NPM_CONFIG_TARGET_ARCH="''${NPM_CONFIG_TARGET_ARCH:-x64}" - export npm_config_build_from_source=true - export USE_SYSTEM_FPM=true - export ELECTRON_BUILDER_7ZIP_PATH="${pkgs.p7zip}/bin/7za" - export APPIMAGE_TOOLS_PATH="${appImageTools}" - - alias electron-install='cd "$(git rev-parse --show-toplevel 2>/dev/null || pwd)" && pnpm install' - alias electron-build-web='cd "$(git rev-parse --show-toplevel 2>/dev/null || pwd)" && pnpm run build:web' - alias electron-dev='pnpm run dev' - alias electron-package='pnpm run package:linux' - alias electron-validate='pnpm run validate' - ''; - }; - - tauri = pkgs.mkShell { - buildInputs = - with pkgs; - [ - jdk17 - gradle - pnpm - bun - nodejs - corepack_22 - coreutils - pkg-config - git - jq - curl - ] - ++ [ - android.androidsdk - android-studio-tools - ] - ++ commonDeps; - - shellHook = '' - sdkSource="${android.androidsdk}/libexec/android-sdk" - if [ -d apps/tauri/src-tauri ]; then - projectRoot="$PWD/apps/tauri" - else - projectRoot="$PWD" - fi - androidHome="$projectRoot/.android" - sdkRoot="$androidHome/sdk" - - mkdir -p "$androidHome" - if [ -L "$sdkRoot" ]; then - rm -f "$sdkRoot" - fi - mkdir -p "$sdkRoot" - - ln -sfn "$sdkSource/build-tools" "$sdkRoot/build-tools" - ln -sfn "$sdkSource/cmake" "$sdkRoot/cmake" - ln -sfn "$sdkSource/licenses" "$sdkRoot/licenses" - ln -sfn "$sdkSource/ndk" "$sdkRoot/ndk" - ln -sfn "$sdkSource/ndk-bundle" "$sdkRoot/ndk-bundle" - ln -sfn "$sdkSource/platforms" "$sdkRoot/platforms" - ln -sfn "$sdkSource/platform-tools" "$sdkRoot/platform-tools" - ln -sfn "$sdkSource/tools" "$sdkRoot/tools" - - mkdir -p "$sdkRoot/cmdline-tools" - ln -sfn "$sdkSource/cmdline-tools/8.0" "$sdkRoot/cmdline-tools/8.0" - ln -sfn "8.0" "$sdkRoot/cmdline-tools/latest" - - sdkRootAbs="$(realpath "$sdkRoot")" - ndkRootAbs="''${sdkRootAbs}/ndk/${ndkVersion}" - - export PATH="''${sdkRootAbs}/cmdline-tools/latest/bin:''${sdkRootAbs}/platform-tools:''${ndkRootAbs}:${pkgs.android-studio-tools}/bin:$PATH" - export ANDROID_HOME="''${sdkRootAbs}" - export ANDROID_SDK_ROOT="''${sdkRootAbs}" - export ANDROID_NDK_ROOT="''${ndkRootAbs}" - export ANDROID_NDK_HOME="$ANDROID_NDK_ROOT" - export NDK_HOME="$ANDROID_NDK_ROOT" - export NDK_PATH="$ANDROID_NDK_ROOT" - export JAVA_HOME="${pkgs.jdk17}" - - aapt2Path="''${sdkRootAbs}/build-tools/${buildToolsVersion}/aapt2" - if [ -x "$aapt2Path" ]; then - mkdir -p "$HOME/.gradle" - gradleProperties="$HOME/.gradle/gradle.properties" - touch "$gradleProperties" - if grep -q '^android\.aapt2FromMavenOverride=' "$gradleProperties"; then - sed -i "s|^android\.aapt2FromMavenOverride=.*|android.aapt2FromMavenOverride=$aapt2Path|" "$gradleProperties" - else - printf '\nandroid.aapt2FromMavenOverride=%s\n' "$aapt2Path" >> "$gradleProperties" - fi - fi - - adb devices - ''; - }; - } - ); - }; -} diff --git a/licenses/react@19.2.8/LICENSE b/licenses/@codemirror_commands@6.10.3/LICENSE similarity index 84% rename from licenses/react@19.2.8/LICENSE rename to licenses/@codemirror_commands@6.10.3/LICENSE index b93be90..9a91f48 100644 --- a/licenses/react@19.2.8/LICENSE +++ b/licenses/@codemirror_commands@6.10.3/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) Meta Platforms, Inc. and affiliates. +Copyright (C) 2018-2021 by Marijn Haverbeke and others Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/licenses/use-sync-external-store@1.6.0/LICENSE b/licenses/@codemirror_lang-markdown@6.5.0/LICENSE similarity index 84% rename from licenses/use-sync-external-store@1.6.0/LICENSE rename to licenses/@codemirror_lang-markdown@6.5.0/LICENSE index b93be90..9a91f48 100644 --- a/licenses/use-sync-external-store@1.6.0/LICENSE +++ b/licenses/@codemirror_lang-markdown@6.5.0/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) Meta Platforms, Inc. and affiliates. +Copyright (C) 2018-2021 by Marijn Haverbeke and others Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/licenses/@codemirror_state@6.6.0/LICENSE b/licenses/@codemirror_state@6.6.0/LICENSE new file mode 100644 index 0000000..9a91f48 --- /dev/null +++ b/licenses/@codemirror_state@6.6.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/licenses/@codemirror_view@6.41.1/LICENSE b/licenses/@codemirror_view@6.41.1/LICENSE new file mode 100644 index 0000000..9a91f48 --- /dev/null +++ b/licenses/@codemirror_view@6.41.1/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/licenses/@fontsource-variable_public-sans@5.3.0/LICENSE b/licenses/@fontsource-variable_inter@5.2.8/LICENSE similarity index 95% rename from licenses/@fontsource-variable_public-sans@5.3.0/LICENSE rename to licenses/@fontsource-variable_inter@5.2.8/LICENSE index a6cb63a..40589da 100644 --- a/licenses/@fontsource-variable_public-sans@5.3.0/LICENSE +++ b/licenses/@fontsource-variable_inter@5.2.8/LICENSE @@ -1,4 +1,4 @@ -Copyright 2015 The Public Sans Project Authors (https://github.com/uswds/public-sans) PublicSans-Italic[wght].ttf: Copyright 2015 The Public Sans Project Authors (https://github.com/uswds/public-sans) +Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter) Inter-Italic[opsz,wght].ttf: Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter) This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: diff --git a/licenses/@livekit_components-react@2.9.23/LICENSE b/licenses/@livekit_components-react@2.9.20/LICENSE similarity index 100% rename from licenses/@livekit_components-react@2.9.23/LICENSE rename to licenses/@livekit_components-react@2.9.20/LICENSE diff --git a/licenses/@methanium_ui@0.0.28/LICENSE b/licenses/@methanium_ui@0.0.28/LICENSE deleted file mode 100644 index e952c84..0000000 --- a/licenses/@methanium_ui@0.0.28/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -Copyright (c) 2025 Methanium - -All rights reserved. - -No part of this software, source code, documentation, or -associated materials may be copied, reproduced, modified, -distributed, published, sublicensed, sold, or used to create -derivative works without prior written permission from the -copyright holder. - -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY -OF ANY KIND, EXPRESS OR IMPLIED. TO THE MAXIMUM -EXTENT PERMITTED BY LAW, THE COPYRIGHT HOLDER SHALL -NOT BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER -LIABILITY ARISING FROM THE SOFTWARE OR ITS USE. diff --git a/licenses/electron-builder@26.15.3/LICENSE b/licenses/@noble_curves@2.2.0/LICENSE similarity index 72% rename from licenses/electron-builder@26.15.3/LICENSE rename to licenses/@noble_curves@2.2.0/LICENSE index 7d8fa01..9297a04 100644 --- a/licenses/electron-builder@26.15.3/LICENSE +++ b/licenses/@noble_curves@2.2.0/LICENSE @@ -1,22 +1,21 @@ The MIT License (MIT) -Copyright (c) 2015 Loopline Systems +Copyright (c) 2022 Paul Miller (https://paulmillr.com) Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal +of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/licenses/@tailwindcss_vite@4.3.3/LICENSE b/licenses/@tailwindcss_vite@4.2.4/LICENSE similarity index 100% rename from licenses/@tailwindcss_vite@4.3.3/LICENSE rename to licenses/@tailwindcss_vite@4.2.4/LICENSE diff --git a/licenses/@tanstack_react-query@5.101.4/LICENSE b/licenses/@tanstack_react-query@5.100.8/LICENSE similarity index 100% rename from licenses/@tanstack_react-query@5.101.4/LICENSE rename to licenses/@tanstack_react-query@5.100.8/LICENSE diff --git a/licenses/@tanstack_react-router@1.170.23/LICENSE b/licenses/@tanstack_react-router@1.169.1/LICENSE similarity index 100% rename from licenses/@tanstack_react-router@1.170.23/LICENSE rename to licenses/@tanstack_react-router@1.169.1/LICENSE diff --git a/licenses/@tanstack_react-virtual@3.14.9/LICENSE b/licenses/@tanstack_react-virtual@3.13.24/LICENSE similarity index 100% rename from licenses/@tanstack_react-virtual@3.14.9/LICENSE rename to licenses/@tanstack_react-virtual@3.13.24/LICENSE diff --git a/licenses/@tauri-apps_api@2.11.1/LICENSE_APACHE-2.0 b/licenses/@tauri-apps_api@2.11.0/LICENSE_APACHE-2.0 similarity index 100% rename from licenses/@tauri-apps_api@2.11.1/LICENSE_APACHE-2.0 rename to licenses/@tauri-apps_api@2.11.0/LICENSE_APACHE-2.0 diff --git a/licenses/@tauri-apps_api@2.11.1/LICENSE_MIT b/licenses/@tauri-apps_api@2.11.0/LICENSE_MIT similarity index 100% rename from licenses/@tauri-apps_api@2.11.1/LICENSE_MIT rename to licenses/@tauri-apps_api@2.11.0/LICENSE_MIT diff --git a/licenses/@tauri-apps_cli@2.11.4/LICENSE_APACHE-2.0 b/licenses/@tauri-apps_cli@2.11.0/LICENSE_APACHE-2.0 similarity index 100% rename from licenses/@tauri-apps_cli@2.11.4/LICENSE_APACHE-2.0 rename to licenses/@tauri-apps_cli@2.11.0/LICENSE_APACHE-2.0 diff --git a/licenses/@tauri-apps_cli@2.11.4/LICENSE_MIT b/licenses/@tauri-apps_cli@2.11.0/LICENSE_MIT similarity index 100% rename from licenses/@tauri-apps_cli@2.11.4/LICENSE_MIT rename to licenses/@tauri-apps_cli@2.11.0/LICENSE_MIT diff --git a/licenses/@tauri-apps_plugin-notification@2.3.3/LICENSE.spdx b/licenses/@tauri-apps_plugin-barcode-scanner@2.4.4/LICENSE.spdx similarity index 100% rename from licenses/@tauri-apps_plugin-notification@2.3.3/LICENSE.spdx rename to licenses/@tauri-apps_plugin-barcode-scanner@2.4.4/LICENSE.spdx diff --git a/licenses/@tauri-apps_plugin-opener@2.5.4/LICENSE.spdx b/licenses/@tauri-apps_plugin-opener@2.5.4/LICENSE.spdx new file mode 100644 index 0000000..cdd0df5 --- /dev/null +++ b/licenses/@tauri-apps_plugin-opener@2.5.4/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/licenses/@tensamin_ttp-core@0.0.15/LICENSE b/licenses/@tensamin_ttp-core@0.0.15/LICENSE new file mode 100644 index 0000000..7ba2513 --- /dev/null +++ b/licenses/@tensamin_ttp-core@0.0.15/LICENSE @@ -0,0 +1,16 @@ +Copyright (c) [2026] [Methanium] +All rights reserved. + +This software is protected by copyright. Copying, editing, +distributing, publicly performing, or any other use of this software +or its components, in source or binary form, is strictly prohibited without the express +written permission of the copyright holder. + +FUTURE LICENSE ACCEPTANCE: +It is the copyright holder's intention to release this software in the future +under a license yet to be defined, which will, among other things, +allow private, non-commercial use. This statement does not constitute +a current license grant and does not alter the above +prohibition on use, copying, or modification. Until the formal +publication of such a future license, all rights remain +reserved. diff --git a/licenses/@types_node@26.2.0/LICENSE b/licenses/@types_node@25.6.0/LICENSE similarity index 100% rename from licenses/@types_node@26.2.0/LICENSE rename to licenses/@types_node@25.6.0/LICENSE diff --git a/licenses/@types_react-dom@19.2.4/LICENSE b/licenses/@types_react-dom@19.2.3/LICENSE similarity index 100% rename from licenses/@types_react-dom@19.2.4/LICENSE rename to licenses/@types_react-dom@19.2.3/LICENSE diff --git a/licenses/@types_react@19.2.18/LICENSE b/licenses/@types_react@19.2.14/LICENSE similarity index 100% rename from licenses/@types_react@19.2.18/LICENSE rename to licenses/@types_react@19.2.14/LICENSE diff --git a/licenses/@typescript-eslint_parser@8.66.0/LICENSE b/licenses/@typescript-eslint_parser@8.59.1/LICENSE similarity index 100% rename from licenses/@typescript-eslint_parser@8.66.0/LICENSE rename to licenses/@typescript-eslint_parser@8.59.1/LICENSE diff --git a/licenses/@vitejs_plugin-react@6.0.5/LICENSE b/licenses/@vitejs_plugin-react@6.0.1/LICENSE similarity index 100% rename from licenses/@vitejs_plugin-react@6.0.5/LICENSE rename to licenses/@vitejs_plugin-react@6.0.1/LICENSE diff --git a/licenses/THIRD_PARTY_NOTICES.md b/licenses/THIRD_PARTY_NOTICES.md index 3d61ec4..51009a7 100644 --- a/licenses/THIRD_PARTY_NOTICES.md +++ b/licenses/THIRD_PARTY_NOTICES.md @@ -1,6 +1,42 @@ # Third-Party Notices -Generated from pnpm-lock.yaml and installed packages in workspace node_modules folders. +Generated from bun.lock and installed packages in workspace node_modules folders. + +## @codemirror/commands@6.10.3 + +- License: MIT +- Repository: git+https://github.com/codemirror/commands.git +- Description: Collection of editing commands for the CodeMirror code editor +- Included files: LICENSE +- Folder: `licenses/@codemirror_commands@6.10.3` +- Source package dir: `packages/markdown/node_modules/@codemirror/commands` + +## @codemirror/lang-markdown@6.5.0 + +- License: MIT +- Repository: https://github.com/codemirror/lang-markdown.git +- Description: Markdown language support for the CodeMirror code editor +- Included files: LICENSE +- Folder: `licenses/@codemirror_lang-markdown@6.5.0` +- Source package dir: `packages/markdown/node_modules/@codemirror/lang-markdown` + +## @codemirror/state@6.6.0 + +- License: MIT +- Repository: git+https://github.com/codemirror/state.git +- Description: Editor state data structures for the CodeMirror code editor +- Included files: LICENSE +- Folder: `licenses/@codemirror_state@6.6.0` +- Source package dir: `packages/markdown/node_modules/@codemirror/state` + +## @codemirror/view@6.41.1 + +- License: MIT +- Repository: git+https://code.haverbeke.berlin/codemirror/view.git +- Description: DOM view component for the CodeMirror code editor +- Included files: LICENSE +- Folder: `licenses/@codemirror_view@6.41.1` +- Source package dir: `packages/markdown/node_modules/@codemirror/view` ## @eslint/js@10.0.1 @@ -12,111 +48,103 @@ Generated from pnpm-lock.yaml and installed packages in workspace node_modules f - Folder: `licenses/@eslint_js@10.0.1` - Source package dir: `apps/web/node_modules/@eslint/js` -## @fontsource-variable/public-sans@5.3.0 +## @fontsource-variable/inter@5.2.8 - License: OFL-1.1 -- Homepage: https://fontsource.org/fonts/public-sans +- Homepage: https://fontsource.org/fonts/inter - Repository: git+https://github.com/fontsource/font-files.git -- Description: Self-host the Public Sans font in a neatly bundled NPM package. +- Description: Self-host the Inter font in a neatly bundled NPM package. - Included files: LICENSE -- Folder: `licenses/@fontsource-variable_public-sans@5.3.0` -- Source package dir: `apps/web/node_modules/@fontsource-variable/public-sans` +- Folder: `licenses/@fontsource-variable_inter@5.2.8` +- Source package dir: `apps/web/node_modules/@fontsource-variable/inter` -## @livekit/components-react@2.9.23 +## @livekit/components-react@2.9.20 - License: Apache-2.0 - Repository: https://github.com/livekit/components-js.git - Included files: LICENSE -- Folder: `licenses/@livekit_components-react@2.9.23` +- Folder: `licenses/@livekit_components-react@2.9.20` - Source package dir: `packages/call/node_modules/@livekit/components-react` -## @methanium/ui@0.0.28 +## @noble/curves@2.2.0 -- License: UNKNOWN +- License: MIT +- Homepage: https://paulmillr.com/noble/ +- Repository: git+https://github.com/paulmillr/noble-curves.git +- Description: Audited & minimal JS implementation of elliptic curve cryptography - Included files: LICENSE -- Folder: `licenses/@methanium_ui@0.0.28` -- Source package dir: `apps/tauri/node_modules/@methanium/ui` +- Folder: `licenses/@noble_curves@2.2.0` +- Source package dir: `apps/web/node_modules/@noble/curves` -## @tailwindcss/vite@4.3.3 +## @tailwindcss/vite@4.2.4 - License: MIT - Homepage: https://tailwindcss.com - Repository: https://github.com/tailwindlabs/tailwindcss.git - Description: A utility-first CSS framework for rapidly building custom user interfaces. - Included files: LICENSE -- Folder: `licenses/@tailwindcss_vite@4.3.3` +- Folder: `licenses/@tailwindcss_vite@4.2.4` - Source package dir: `apps/web/node_modules/@tailwindcss/vite` -## @tanstack/pacer@0.21.1 - -- License: MIT -- Homepage: https://tanstack.com/pacer -- Repository: git+https://github.com/TanStack/pacer.git -- Description: Utilities for debouncing, throttling, rate-limiting, queuing, and more. -- Included files: LICENSE -- Folder: `licenses/@tanstack_pacer@0.21.1` -- Source package dir: `packages/chat/node_modules/@tanstack/pacer` - -## @tanstack/react-hotkeys@0.10.0 - -- License: MIT -- Homepage: https://tanstack.com/hotkeys -- Repository: git+https://github.com/TanStack/hotkeys.git -- Description: React adapter for TanStack Hotkeys -- Included files: LICENSE -- Folder: `licenses/@tanstack_react-hotkeys@0.10.0` -- Source package dir: `packages/hotkeys/node_modules/@tanstack/react-hotkeys` - -## @tanstack/react-query@5.101.4 +## @tanstack/react-query@5.100.8 - License: MIT - Homepage: https://tanstack.com/query - Repository: git+https://github.com/TanStack/query.git - Description: Hooks for managing, caching and syncing asynchronous and remote data in React - Included files: LICENSE -- Folder: `licenses/@tanstack_react-query@5.101.4` -- Source package dir: `packages/chat/node_modules/@tanstack/react-query` +- Folder: `licenses/@tanstack_react-query@5.100.8` +- Source package dir: `packages/call/node_modules/@tanstack/react-query` -## @tanstack/react-router@1.170.23 +## @tanstack/react-router@1.169.1 - License: MIT - Homepage: https://tanstack.com/router - Repository: git+https://github.com/TanStack/router.git - Description: Modern and scalable routing for React applications - Included files: LICENSE -- Folder: `licenses/@tanstack_react-router@1.170.23` +- Folder: `licenses/@tanstack_react-router@1.169.1` - Source package dir: `apps/web/node_modules/@tanstack/react-router` -## @tanstack/react-virtual@3.14.9 +## @tanstack/react-virtual@3.13.24 - License: MIT - Homepage: https://tanstack.com/virtual - Repository: git+https://github.com/TanStack/virtual.git - Description: Headless UI for virtualizing scrollable elements in React - Included files: LICENSE -- Folder: `licenses/@tanstack_react-virtual@3.14.9` +- Folder: `licenses/@tanstack_react-virtual@3.13.24` - Source package dir: `apps/web/node_modules/@tanstack/react-virtual` -## @tauri-apps/api@2.11.1 +## @tauri-apps/api@2.11.0 - License: Apache-2.0 OR MIT - Homepage: https://github.com/tauri-apps/tauri#readme - Repository: git+https://github.com/tauri-apps/tauri.git - Description: Tauri API definitions -- Included files: LICENSE_APACHE-2.0, LICENSE_MIT -- Folder: `licenses/@tauri-apps_api@2.11.1` +- Included files: LICENSE_MIT, LICENSE_APACHE-2.0 +- Folder: `licenses/@tauri-apps_api@2.11.0` - Source package dir: `apps/tauri/node_modules/@tauri-apps/api` -## @tauri-apps/cli@2.11.4 +## @tauri-apps/cli@2.11.0 - License: Apache-2.0 OR MIT - Homepage: https://github.com/tauri-apps/tauri#readme - Repository: git+https://github.com/tauri-apps/tauri.git - Description: Command line interface for building Tauri apps -- Included files: LICENSE_APACHE-2.0, LICENSE_MIT -- Folder: `licenses/@tauri-apps_cli@2.11.4` +- Included files: LICENSE_MIT, LICENSE_APACHE-2.0 +- Folder: `licenses/@tauri-apps_cli@2.11.0` - Source package dir: `apps/tauri/node_modules/@tauri-apps/cli` +## @tauri-apps/plugin-barcode-scanner@2.4.4 + +- License: MIT OR Apache-2.0 +- Repository: https://github.com/tauri-apps/plugins-workspace +- Description: Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS +- Included files: LICENSE.spdx +- Folder: `licenses/@tauri-apps_plugin-barcode-scanner@2.4.4` +- Source package dir: `apps/tauri/node_modules/@tauri-apps/plugin-barcode-scanner` + ## @tauri-apps/plugin-deep-link@2.4.9 - License: MIT OR Apache-2.0 @@ -126,23 +154,38 @@ Generated from pnpm-lock.yaml and installed packages in workspace node_modules f - Folder: `licenses/@tauri-apps_plugin-deep-link@2.4.9` - Source package dir: `apps/tauri/node_modules/@tauri-apps/plugin-deep-link` -## @tauri-apps/plugin-notification@2.3.3 +## @tauri-apps/plugin-opener@2.5.4 - License: MIT OR Apache-2.0 - Repository: https://github.com/tauri-apps/plugins-workspace +- Description: Open files and URLs using their default application. - Included files: LICENSE.spdx -- Folder: `licenses/@tauri-apps_plugin-notification@2.3.3` -- Source package dir: `packages/notifications/node_modules/@tauri-apps/plugin-notification` +- Folder: `licenses/@tauri-apps_plugin-opener@2.5.4` +- Source package dir: `apps/tauri/node_modules/@tauri-apps/plugin-opener` -## @types/node@26.2.0 +## @tensamin/ttp-core@0.0.15 + +- License: UNKNOWN +- Included files: LICENSE +- Folder: `licenses/@tensamin_ttp-core@0.0.15` +- Source package dir: `packages/ttp/node_modules/@tensamin/ttp-core` + +## @tensamin/ui@0.0.30 + +- License: UNKNOWN +- Included files: none found +- Folder: `licenses/@tensamin_ui@0.0.30` +- Source package dir: `apps/tauri/node_modules/@tensamin/ui` + +## @types/node@25.6.0 - License: MIT - Homepage: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node - Repository: https://github.com/DefinitelyTyped/DefinitelyTyped.git - Description: TypeScript definitions for node - Included files: LICENSE -- Folder: `licenses/@types_node@26.2.0` -- Source package dir: `apps/electron/node_modules/@types/node` +- Folder: `licenses/@types_node@25.6.0` +- Source package dir: `node_modules/@types/node` ## @types/qrcode@1.5.6 @@ -154,101 +197,101 @@ Generated from pnpm-lock.yaml and installed packages in workspace node_modules f - Folder: `licenses/@types_qrcode@1.5.6` - Source package dir: `apps/web/node_modules/@types/qrcode` -## @types/react@19.2.18 +## @types/react@19.2.14 - License: MIT - Homepage: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react - Repository: https://github.com/DefinitelyTyped/DefinitelyTyped.git - Description: TypeScript definitions for react - Included files: LICENSE -- Folder: `licenses/@types_react@19.2.18` +- Folder: `licenses/@types_react@19.2.14` - Source package dir: `apps/web/node_modules/@types/react` -## @types/react-dom@19.2.4 +## @types/react-dom@19.2.3 - License: MIT - Homepage: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react-dom - Repository: https://github.com/DefinitelyTyped/DefinitelyTyped.git - Description: TypeScript definitions for react-dom - Included files: LICENSE -- Folder: `licenses/@types_react-dom@19.2.4` +- Folder: `licenses/@types_react-dom@19.2.3` - Source package dir: `apps/web/node_modules/@types/react-dom` -## @typescript-eslint/parser@8.66.0 +## @typescript-eslint/parser@8.59.1 - License: MIT - Homepage: https://typescript-eslint.io/packages/parser - Repository: https://github.com/typescript-eslint/typescript-eslint.git - Description: An ESLint custom parser which leverages TypeScript ESTree - Included files: LICENSE -- Folder: `licenses/@typescript-eslint_parser@8.66.0` +- Folder: `licenses/@typescript-eslint_parser@8.59.1` - Source package dir: `node_modules/@typescript-eslint/parser` -## @vitejs/plugin-react@6.0.5 +## @vitejs/plugin-react@6.0.1 - License: MIT - Homepage: https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme - Repository: git+https://github.com/vitejs/vite-plugin-react.git - Description: The default Vite plugin for React projects - Included files: LICENSE -- Folder: `licenses/@vitejs_plugin-react@6.0.5` +- Folder: `licenses/@vitejs_plugin-react@6.0.1` - Source package dir: `apps/web/node_modules/@vitejs/plugin-react` -## decimal.js-light@2.5.1 +## class-variance-authority@0.7.1 + +- License: Apache-2.0 +- Homepage: https://github.com/joe-bell/cva#readme +- Repository: https://github.com/joe-bell/cva.git +- Description: Class Variance Authority 🧬 +- Included files: LICENSE +- Folder: `licenses/class-variance-authority@0.7.1` +- Source package dir: `apps/web/node_modules/class-variance-authority` + +## clsx@2.1.1 - License: MIT -- Repository: https://github.com/MikeMcl/decimal.js-light.git -- Description: An arbitrary-precision Decimal type for JavaScript. -- Included files: LICENCE.md -- Folder: `licenses/decimal.js-light@2.5.1` -- Source package dir: `apps/web/node_modules/decimal.js-light` +- Repository: lukeed/clsx +- Description: A tiny (239B) utility for constructing className strings conditionally. +- Included files: license +- Folder: `licenses/clsx@2.1.1` +- Source package dir: `apps/web/node_modules/clsx` -## deepfilternet3-noise-filter@1.3.0 +## comlink@4.4.2 + +- License: Apache-2.0 +- Repository: https://github.com/GoogleChromeLabs/comlink.git +- Description: Comlink makes WebWorkers enjoyable +- Included files: LICENSE +- Folder: `licenses/comlink@4.4.2` +- Source package dir: `apps/web/node_modules/comlink` + +## deepfilternet3-noise-filter@1.2.1 - License: (Apache-2.0 OR MIT) - Homepage: https://github.com/mezonai/mezon-noise-suppression#readme - Repository: git+https://github.com/mezonai/mezon-noise-suppression.git - Description: Custom audio processor with DeepFilterNet3 noise filtering integrated with LiveKit client - Included files: LICENSE-APACHE, LICENSE-MIT -- Folder: `licenses/deepfilternet3-noise-filter@1.3.0` +- Folder: `licenses/deepfilternet3-noise-filter@1.2.1` - Source package dir: `packages/call/node_modules/deepfilternet3-noise-filter` -## electron@43.3.0 - -- License: MIT -- Repository: https://github.com/electron/electron -- Description: Build cross platform desktop apps with JavaScript, HTML, and CSS -- Included files: LICENSE -- Folder: `licenses/electron@43.3.0` -- Source package dir: `apps/electron/node_modules/electron` - -## electron-builder@26.15.3 - -- License: MIT -- Homepage: https://github.com/electron-userland/electron-builder -- Repository: git+https://github.com/electron-userland/electron-builder.git -- Description: A complete solution to package and build a ready for distribution Electron app for MacOS, Windows and Linux with “auto update” support out of the box -- Included files: LICENSE -- Folder: `licenses/electron-builder@26.15.3` -- Source package dir: `apps/electron/node_modules/electron-builder` - -## esbuild@0.28.1 +## esbuild@0.25.12 - License: MIT - Repository: git+https://github.com/evanw/esbuild.git - Description: An extremely fast JavaScript and CSS bundler and minifier. - Included files: LICENSE.md -- Folder: `licenses/esbuild@0.28.1` -- Source package dir: `apps/electron/node_modules/esbuild` +- Folder: `licenses/esbuild@0.25.12` +- Source package dir: `apps/web/node_modules/esbuild` -## eslint@10.8.1 +## eslint@10.3.0 - License: MIT - Homepage: https://eslint.org - Repository: eslint/eslint - Description: An AST-based pattern checker for JavaScript. - Included files: LICENSE -- Folder: `licenses/eslint@10.8.1` +- Folder: `licenses/eslint@10.3.0` - Source package dir: `apps/web/node_modules/eslint` ## eslint-plugin-react-hooks@7.1.1 @@ -261,130 +304,111 @@ Generated from pnpm-lock.yaml and installed packages in workspace node_modules f - Folder: `licenses/eslint-plugin-react-hooks@7.1.1` - Source package dir: `node_modules/eslint-plugin-react-hooks` -## eventemitter3@5.0.4 +## framer-motion@12.38.0 - License: MIT -- Repository: git://github.com/primus/eventemitter3.git -- Description: EventEmitter3 focuses on performance while maintaining a Node.js AND browser compatible interface. -- Included files: LICENSE -- Folder: `licenses/eventemitter3@5.0.4` -- Source package dir: `apps/web/node_modules/eventemitter3` +- Repository: https://github.com/motiondivision/motion/ +- Description: A simple and powerful JavaScript animation library +- Included files: LICENSE.md +- Folder: `licenses/framer-motion@12.38.0` +- Source package dir: `apps/web/node_modules/framer-motion` -## fallow@3.14.0 - -- License: MIT -- Homepage: https://docs.fallow.tools -- Repository: git+https://github.com/fallow-rs/fallow.git -- Description: Codebase intelligence for TypeScript and JavaScript. Free static analysis of code and styles, optional paid runtime intelligence (Fallow Runtime). Quality, risk, architecture, dependencies, duplication, and design-system drift for humans, CI, and the agents writing your code. Zero-config framework support. -- Included files: none found -- Folder: `licenses/fallow@3.14.0` -- Source package dir: `node_modules/fallow` - -## globals@17.9.0 +## globals@17.6.0 - License: MIT - Repository: sindresorhus/globals - Description: Global identifiers from different JavaScript environments - Included files: license -- Folder: `licenses/globals@17.9.0` +- Folder: `licenses/globals@17.6.0` - Source package dir: `apps/web/node_modules/globals` -## livekit-client@2.21.0 +## jsonc-parser@3.3.1 + +- License: MIT +- Repository: https://github.com/microsoft/node-jsonc-parser +- Description: Scanner and parser for JSON with comments. +- Included files: LICENSE.md +- Folder: `licenses/jsonc-parser@3.3.1` +- Source package dir: `node_modules/jsonc-parser` + +## livekit-client@2.18.8 - License: Apache-2.0 - Repository: git@github.com:livekit/client-sdk-js.git - Description: JavaScript/TypeScript client SDK for LiveKit - Included files: LICENSE -- Folder: `licenses/livekit-client@2.21.0` +- Folder: `licenses/livekit-client@2.18.8` - Source package dir: `packages/call/node_modules/livekit-client` -## lucide-react@1.30.0 +## lucide-react@1.14.0 - License: ISC - Homepage: https://lucide.dev - Repository: https://github.com/lucide-icons/lucide.git - Description: A Lucide icon library package for React applications. - Included files: LICENSE -- Folder: `licenses/lucide-react@1.30.0` -- Source package dir: `apps/web/node_modules/lucide-react` +- Folder: `licenses/lucide-react@1.14.0` +- Source package dir: `apps/tauri/node_modules/lucide-react` -## motion@13.0.0 - -- License: MIT -- Repository: https://github.com/motiondivision/motion -- Description: An animation library for JavaScript and React. -- Included files: LICENSE.md -- Folder: `licenses/motion@13.0.0` -- Source package dir: `packages/chat/node_modules/motion` - -## mtp@0.2.0 - -- License: UNKNOWN -- Description: MTP TypeScript SDK -- Included files: none found -- Folder: `licenses/mtp@0.2.0` -- Source package dir: `packages/mtp/node_modules/mtp` - -## prettier@3.9.6 +## prettier@3.8.3 - License: MIT - Homepage: https://prettier.io - Repository: prettier/prettier - Description: Prettier is an opinionated code formatter - Included files: LICENSE -- Folder: `licenses/prettier@3.9.6` +- Folder: `licenses/prettier@3.8.3` - Source package dir: `node_modules/prettier` -## react@19.2.8 +## qrcode@1.5.4 + +- License: MIT +- Homepage: http://github.com/soldair/node-qrcode +- Repository: git://github.com/soldair/node-qrcode.git +- Description: QRCode / 2d Barcode api with both server side and client side support using canvas +- Included files: license +- Folder: `licenses/qrcode@1.5.4` +- Source package dir: `apps/web/node_modules/qrcode` + +## react@19.2.5 - License: MIT - Homepage: https://react.dev/ -- Repository: https://github.com/react/react.git +- Repository: https://github.com/facebook/react.git - Description: React is a JavaScript library for building user interfaces. - Included files: LICENSE -- Folder: `licenses/react@19.2.8` +- Folder: `licenses/react@19.2.5` - Source package dir: `apps/tauri/node_modules/react` -## react-dom@19.2.8 +## react-dom@19.2.5 - License: MIT - Homepage: https://react.dev/ -- Repository: https://github.com/react/react.git +- Repository: https://github.com/facebook/react.git - Description: React package for working with the DOM. - Included files: LICENSE -- Folder: `licenses/react-dom@19.2.8` +- Folder: `licenses/react-dom@19.2.5` - Source package dir: `apps/tauri/node_modules/react-dom` -## react-is@19.2.8 - -- License: MIT -- Homepage: https://react.dev/ -- Repository: https://github.com/react/react.git -- Description: Brand checking of React Elements. -- Included files: LICENSE -- Folder: `licenses/react-is@19.2.8` -- Source package dir: `apps/web/node_modules/react-is` - -## react-redux@9.3.0 - -- License: MIT -- Homepage: https://github.com/reduxjs/react-redux -- Repository: github:reduxjs/react-redux -- Description: Official React bindings for Redux -- Included files: LICENSE.md -- Folder: `licenses/react-redux@9.3.0` -- Source package dir: `apps/web/node_modules/react-redux` - -## recharts@3.10.1 +## recharts@3.8.1 - License: MIT - Homepage: https://github.com/recharts/recharts - Repository: git+https://github.com/recharts/recharts.git - Description: React charts - Included files: LICENSE -- Folder: `licenses/recharts@3.10.1` +- Folder: `licenses/recharts@3.8.1` - Source package dir: `packages/call/node_modules/recharts` +## shadcn@4.6.0 + +- License: MIT +- Repository: https://github.com/shadcn-ui/ui.git +- Description: Add components to your apps. +- Included files: LICENSE.md +- Folder: `licenses/shadcn@4.6.0` +- Source package dir: `apps/web/node_modules/shadcn` + ## sonner@2.0.7 - License: MIT @@ -393,7 +417,17 @@ Generated from pnpm-lock.yaml and installed packages in workspace node_modules f - Description: An opinionated toast component for React. - Included files: LICENSE.md - Folder: `licenses/sonner@2.0.7` -- Source package dir: `packages/notifications/node_modules/sonner` +- Source package dir: `apps/web/node_modules/sonner` + +## tailwind-merge@3.5.0 + +- License: MIT +- Homepage: https://github.com/dcastil/tailwind-merge +- Repository: https://github.com/dcastil/tailwind-merge.git +- Description: Merge Tailwind CSS classes without style conflicts +- Included files: LICENSE.md +- Folder: `licenses/tailwind-merge@3.5.0` +- Source package dir: `apps/web/node_modules/tailwind-merge` ## tailwind-scrollbar-hide@4.0.0 @@ -405,14 +439,14 @@ Generated from pnpm-lock.yaml and installed packages in workspace node_modules f - Folder: `licenses/tailwind-scrollbar-hide@4.0.0` - Source package dir: `apps/web/node_modules/tailwind-scrollbar-hide` -## tailwindcss@4.3.3 +## tailwindcss@4.2.4 - License: MIT - Homepage: https://tailwindcss.com - Repository: https://github.com/tailwindlabs/tailwindcss.git - Description: A utility-first CSS framework for rapidly building custom user interfaces. - Included files: LICENSE -- Folder: `licenses/tailwindcss@4.3.3` +- Folder: `licenses/tailwindcss@4.2.4` - Source package dir: `apps/web/node_modules/tailwindcss` ## tw-animate-css@1.4.0 @@ -433,63 +467,44 @@ Generated from pnpm-lock.yaml and installed packages in workspace node_modules f - Description: TypeScript is a language for application scale JavaScript development - Included files: LICENSE.txt - Folder: `licenses/typescript@6.0.3` -- Source package dir: `apps/electron/node_modules/typescript` +- Source package dir: `apps/web/node_modules/typescript` -## typescript-eslint@8.66.0 +## typescript-eslint@8.59.1 - License: MIT - Homepage: https://typescript-eslint.io/packages/typescript-eslint - Repository: https://github.com/typescript-eslint/typescript-eslint.git - Description: Tooling which enables you to use TypeScript with ESLint - Included files: LICENSE -- Folder: `licenses/typescript-eslint@8.66.0` +- Folder: `licenses/typescript-eslint@8.59.1` - Source package dir: `apps/web/node_modules/typescript-eslint` -## use-sync-external-store@1.6.0 - -- License: MIT -- Repository: https://github.com/facebook/react.git -- Description: Backwards compatible shim for React's useSyncExternalStore. Works with any React that supports hooks. -- Included files: LICENSE -- Folder: `licenses/use-sync-external-store@1.6.0` -- Source package dir: `apps/web/node_modules/use-sync-external-store` - -## vite@8.2.1 +## vite@8.0.10 - License: MIT - Homepage: https://vite.dev - Repository: git+https://github.com/vitejs/vite.git - Description: Native-ESM powered web dev build tool - Included files: LICENSE.md -- Folder: `licenses/vite@8.2.1` +- Folder: `licenses/vite@8.0.10` - Source package dir: `apps/web/node_modules/vite` -## vitest@4.1.10 - -- License: MIT -- Homepage: https://vitest.dev -- Repository: git+https://github.com/vitest-dev/vitest.git -- Description: Next generation testing framework powered by Vite -- Included files: LICENSE.md -- Folder: `licenses/vitest@4.1.10` -- Source package dir: `node_modules/vitest` - -## zod@4.4.3 +## zod@4.4.2 - License: MIT - Homepage: https://zod.dev - Repository: git+https://github.com/colinhacks/zod.git - Description: TypeScript-first schema declaration and validation library with static type inference - Included files: LICENSE -- Folder: `licenses/zod@4.4.3` +- Folder: `licenses/zod@4.4.2` - Source package dir: `apps/web/node_modules/zod` -## zustand@5.0.14 +## zustand@5.0.12 - License: MIT - Homepage: https://github.com/pmndrs/zustand - Repository: git+https://github.com/pmndrs/zustand.git - Description: 🐻 Bear necessities for state management in React - Included files: LICENSE -- Folder: `licenses/zustand@5.0.14` +- Folder: `licenses/zustand@5.0.12` - Source package dir: `packages/call/node_modules/zustand` diff --git a/licenses/class-variance-authority@0.7.1/LICENSE b/licenses/class-variance-authority@0.7.1/LICENSE new file mode 100644 index 0000000..380ac71 --- /dev/null +++ b/licenses/class-variance-authority@0.7.1/LICENSE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2022 Joe Bell + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licenses/clsx@2.1.1/license b/licenses/clsx@2.1.1/license new file mode 100644 index 0000000..fa6089f --- /dev/null +++ b/licenses/clsx@2.1.1/license @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) Luke Edwards (lukeed.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licenses/comlink@4.4.2/LICENSE b/licenses/comlink@4.4.2/LICENSE new file mode 100644 index 0000000..12b597d --- /dev/null +++ b/licenses/comlink@4.4.2/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/licenses/decimal.js-light@2.5.1/LICENCE.md b/licenses/decimal.js-light@2.5.1/LICENCE.md deleted file mode 100644 index 538bf23..0000000 --- a/licenses/decimal.js-light@2.5.1/LICENCE.md +++ /dev/null @@ -1,23 +0,0 @@ -The MIT Expat Licence. - -Copyright (c) 2020 Michael Mclaughlin - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/licenses/deepfilternet3-noise-filter@1.3.0/LICENSE-APACHE b/licenses/deepfilternet3-noise-filter@1.2.1/LICENSE-APACHE similarity index 100% rename from licenses/deepfilternet3-noise-filter@1.3.0/LICENSE-APACHE rename to licenses/deepfilternet3-noise-filter@1.2.1/LICENSE-APACHE diff --git a/licenses/deepfilternet3-noise-filter@1.3.0/LICENSE-MIT b/licenses/deepfilternet3-noise-filter@1.2.1/LICENSE-MIT similarity index 100% rename from licenses/deepfilternet3-noise-filter@1.3.0/LICENSE-MIT rename to licenses/deepfilternet3-noise-filter@1.2.1/LICENSE-MIT diff --git a/licenses/electron@43.3.0/LICENSE b/licenses/electron@43.3.0/LICENSE deleted file mode 100644 index 536d54e..0000000 --- a/licenses/electron@43.3.0/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (c) Electron contributors -Copyright (c) 2013-2020 GitHub Inc. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licenses/esbuild@0.28.1/LICENSE.md b/licenses/esbuild@0.25.12/LICENSE.md similarity index 100% rename from licenses/esbuild@0.28.1/LICENSE.md rename to licenses/esbuild@0.25.12/LICENSE.md diff --git a/licenses/eslint@10.8.1/LICENSE b/licenses/eslint@10.3.0/LICENSE similarity index 100% rename from licenses/eslint@10.8.1/LICENSE rename to licenses/eslint@10.3.0/LICENSE diff --git a/licenses/eventemitter3@5.0.4/LICENSE b/licenses/framer-motion@12.38.0/LICENSE.md similarity index 96% rename from licenses/eventemitter3@5.0.4/LICENSE rename to licenses/framer-motion@12.38.0/LICENSE.md index abcbd54..b5b8d6a 100644 --- a/licenses/eventemitter3@5.0.4/LICENSE +++ b/licenses/framer-motion@12.38.0/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 Arnout Kazemier +Copyright (c) 2018 Framer B.V. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/licenses/globals@17.9.0/license b/licenses/globals@17.6.0/license similarity index 100% rename from licenses/globals@17.9.0/license rename to licenses/globals@17.6.0/license diff --git a/licenses/react-redux@9.3.0/LICENSE.md b/licenses/jsonc-parser@3.3.1/LICENSE.md similarity index 95% rename from licenses/react-redux@9.3.0/LICENSE.md rename to licenses/jsonc-parser@3.3.1/LICENSE.md index 55bc8df..1c65de1 100644 --- a/licenses/react-redux@9.3.0/LICENSE.md +++ b/licenses/jsonc-parser@3.3.1/LICENSE.md @@ -1,21 +1,21 @@ -The MIT License (MIT) - -Copyright (c) 2015-present Dan Abramov - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The MIT License (MIT) + +Copyright (c) Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licenses/livekit-client@2.21.0/LICENSE b/licenses/livekit-client@2.18.8/LICENSE similarity index 100% rename from licenses/livekit-client@2.21.0/LICENSE rename to licenses/livekit-client@2.18.8/LICENSE diff --git a/licenses/lucide-react@1.30.0/LICENSE b/licenses/lucide-react@1.14.0/LICENSE similarity index 100% rename from licenses/lucide-react@1.30.0/LICENSE rename to licenses/lucide-react@1.14.0/LICENSE diff --git a/licenses/motion@13.0.0/LICENSE.md b/licenses/motion@13.0.0/LICENSE.md deleted file mode 100644 index 8111044..0000000 --- a/licenses/motion@13.0.0/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2024 [Motion](https://motion.dev) B.V. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/licenses/prettier@3.9.6/LICENSE b/licenses/prettier@3.8.3/LICENSE similarity index 100% rename from licenses/prettier@3.9.6/LICENSE rename to licenses/prettier@3.8.3/LICENSE diff --git a/licenses/qrcode@1.5.4/license b/licenses/qrcode@1.5.4/license new file mode 100644 index 0000000..4e2b3b9 --- /dev/null +++ b/licenses/qrcode@1.5.4/license @@ -0,0 +1,10 @@ +The MIT License (MIT) + +Copyright (c) 2012 Ryan Day + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/licenses/react-dom@19.2.8/LICENSE b/licenses/react-dom@19.2.5/LICENSE similarity index 100% rename from licenses/react-dom@19.2.8/LICENSE rename to licenses/react-dom@19.2.5/LICENSE diff --git a/licenses/react-is@19.2.8/LICENSE b/licenses/react@19.2.5/LICENSE similarity index 100% rename from licenses/react-is@19.2.8/LICENSE rename to licenses/react@19.2.5/LICENSE diff --git a/licenses/recharts@3.10.1/LICENSE b/licenses/recharts@3.8.1/LICENSE similarity index 100% rename from licenses/recharts@3.10.1/LICENSE rename to licenses/recharts@3.8.1/LICENSE diff --git a/licenses/sbom.cyclonedx.json b/licenses/sbom.cyclonedx.json index 2fca23f..6e10a91 100644 --- a/licenses/sbom.cyclonedx.json +++ b/licenses/sbom.cyclonedx.json @@ -3,11 +3,11 @@ "specVersion": "1.5", "version": 1, "metadata": { - "timestamp": "2026-08-09T22:09:04.469Z", + "timestamp": "2026-05-04T13:05:17.472Z", "tools": [ { "vendor": "OpenAI", - "name": "custom pnpm license generator" + "name": "custom bun license generator" } ], "component": { @@ -16,6 +16,130 @@ } }, "components": [ + { + "type": "library", + "bomRef": "pkg:npm/%40codemirror/commands@6.10.3", + "name": "@codemirror/commands", + "version": "6.10.3", + "purl": "pkg:npm/%40codemirror/commands@6.10.3", + "description": "Collection of editing commands for the CodeMirror code editor", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "externalReferences": [ + { + "type": "vcs", + "url": "git+https://github.com/codemirror/commands.git" + } + ], + "properties": [ + { + "name": "local:licenseFolder", + "value": "licenses/@codemirror_commands@6.10.3" + }, + { + "name": "local:sourcePackageDir", + "value": "packages/markdown/node_modules/@codemirror/commands" + } + ] + }, + { + "type": "library", + "bomRef": "pkg:npm/%40codemirror/lang-markdown@6.5.0", + "name": "@codemirror/lang-markdown", + "version": "6.5.0", + "purl": "pkg:npm/%40codemirror/lang-markdown@6.5.0", + "description": "Markdown language support for the CodeMirror code editor", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/codemirror/lang-markdown.git" + } + ], + "properties": [ + { + "name": "local:licenseFolder", + "value": "licenses/@codemirror_lang-markdown@6.5.0" + }, + { + "name": "local:sourcePackageDir", + "value": "packages/markdown/node_modules/@codemirror/lang-markdown" + } + ] + }, + { + "type": "library", + "bomRef": "pkg:npm/%40codemirror/state@6.6.0", + "name": "@codemirror/state", + "version": "6.6.0", + "purl": "pkg:npm/%40codemirror/state@6.6.0", + "description": "Editor state data structures for the CodeMirror code editor", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "externalReferences": [ + { + "type": "vcs", + "url": "git+https://github.com/codemirror/state.git" + } + ], + "properties": [ + { + "name": "local:licenseFolder", + "value": "licenses/@codemirror_state@6.6.0" + }, + { + "name": "local:sourcePackageDir", + "value": "packages/markdown/node_modules/@codemirror/state" + } + ] + }, + { + "type": "library", + "bomRef": "pkg:npm/%40codemirror/view@6.41.1", + "name": "@codemirror/view", + "version": "6.41.1", + "purl": "pkg:npm/%40codemirror/view@6.41.1", + "description": "DOM view component for the CodeMirror code editor", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "externalReferences": [ + { + "type": "vcs", + "url": "git+https://code.haverbeke.berlin/codemirror/view.git" + } + ], + "properties": [ + { + "name": "local:licenseFolder", + "value": "licenses/@codemirror_view@6.41.1" + }, + { + "name": "local:sourcePackageDir", + "value": "packages/markdown/node_modules/@codemirror/view" + } + ] + }, { "type": "library", "bomRef": "pkg:npm/%40eslint/js@10.0.1", @@ -53,11 +177,11 @@ }, { "type": "library", - "bomRef": "pkg:npm/%40fontsource-variable/public-sans@5.3.0", - "name": "@fontsource-variable/public-sans", - "version": "5.3.0", - "purl": "pkg:npm/%40fontsource-variable/public-sans@5.3.0", - "description": "Self-host the Public Sans font in a neatly bundled NPM package.", + "bomRef": "pkg:npm/%40fontsource-variable/inter@5.2.8", + "name": "@fontsource-variable/inter", + "version": "5.2.8", + "purl": "pkg:npm/%40fontsource-variable/inter@5.2.8", + "description": "Self-host the Inter font in a neatly bundled NPM package.", "licenses": [ { "license": { @@ -68,7 +192,7 @@ "externalReferences": [ { "type": "website", - "url": "https://fontsource.org/fonts/public-sans" + "url": "https://fontsource.org/fonts/inter" }, { "type": "vcs", @@ -78,20 +202,20 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/@fontsource-variable_public-sans@5.3.0" + "value": "licenses/@fontsource-variable_inter@5.2.8" }, { "name": "local:sourcePackageDir", - "value": "apps/web/node_modules/@fontsource-variable/public-sans" + "value": "apps/web/node_modules/@fontsource-variable/inter" } ] }, { "type": "library", - "bomRef": "pkg:npm/%40livekit/components-react@2.9.23", + "bomRef": "pkg:npm/%40livekit/components-react@2.9.20", "name": "@livekit/components-react", - "version": "2.9.23", - "purl": "pkg:npm/%40livekit/components-react@2.9.23", + "version": "2.9.20", + "purl": "pkg:npm/%40livekit/components-react@2.9.20", "licenses": [ { "license": { @@ -108,7 +232,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/@livekit_components-react@2.9.23" + "value": "licenses/@livekit_components-react@2.9.20" }, { "name": "local:sourcePackageDir", @@ -118,28 +242,45 @@ }, { "type": "library", - "bomRef": "pkg:npm/%40methanium/ui@0.0.28", - "name": "@methanium/ui", - "version": "0.0.28", - "purl": "pkg:npm/%40methanium/ui@0.0.28", - "externalReferences": [], + "bomRef": "pkg:npm/%40noble/curves@2.2.0", + "name": "@noble/curves", + "version": "2.2.0", + "purl": "pkg:npm/%40noble/curves@2.2.0", + "description": "Audited & minimal JS implementation of elliptic curve cryptography", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "externalReferences": [ + { + "type": "website", + "url": "https://paulmillr.com/noble/" + }, + { + "type": "vcs", + "url": "git+https://github.com/paulmillr/noble-curves.git" + } + ], "properties": [ { "name": "local:licenseFolder", - "value": "licenses/@methanium_ui@0.0.28" + "value": "licenses/@noble_curves@2.2.0" }, { "name": "local:sourcePackageDir", - "value": "apps/tauri/node_modules/@methanium/ui" + "value": "apps/web/node_modules/@noble/curves" } ] }, { "type": "library", - "bomRef": "pkg:npm/%40tailwindcss/vite@4.3.3", + "bomRef": "pkg:npm/%40tailwindcss/vite@4.2.4", "name": "@tailwindcss/vite", - "version": "4.3.3", - "purl": "pkg:npm/%40tailwindcss/vite@4.3.3", + "version": "4.2.4", + "purl": "pkg:npm/%40tailwindcss/vite@4.2.4", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "licenses": [ { @@ -161,7 +302,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/@tailwindcss_vite@4.3.3" + "value": "licenses/@tailwindcss_vite@4.2.4" }, { "name": "local:sourcePackageDir", @@ -171,80 +312,10 @@ }, { "type": "library", - "bomRef": "pkg:npm/%40tanstack/pacer@0.21.1", - "name": "@tanstack/pacer", - "version": "0.21.1", - "purl": "pkg:npm/%40tanstack/pacer@0.21.1", - "description": "Utilities for debouncing, throttling, rate-limiting, queuing, and more.", - "licenses": [ - { - "license": { - "id": "MIT" - } - } - ], - "externalReferences": [ - { - "type": "website", - "url": "https://tanstack.com/pacer" - }, - { - "type": "vcs", - "url": "git+https://github.com/TanStack/pacer.git" - } - ], - "properties": [ - { - "name": "local:licenseFolder", - "value": "licenses/@tanstack_pacer@0.21.1" - }, - { - "name": "local:sourcePackageDir", - "value": "packages/chat/node_modules/@tanstack/pacer" - } - ] - }, - { - "type": "library", - "bomRef": "pkg:npm/%40tanstack/react-hotkeys@0.10.0", - "name": "@tanstack/react-hotkeys", - "version": "0.10.0", - "purl": "pkg:npm/%40tanstack/react-hotkeys@0.10.0", - "description": "React adapter for TanStack Hotkeys", - "licenses": [ - { - "license": { - "id": "MIT" - } - } - ], - "externalReferences": [ - { - "type": "website", - "url": "https://tanstack.com/hotkeys" - }, - { - "type": "vcs", - "url": "git+https://github.com/TanStack/hotkeys.git" - } - ], - "properties": [ - { - "name": "local:licenseFolder", - "value": "licenses/@tanstack_react-hotkeys@0.10.0" - }, - { - "name": "local:sourcePackageDir", - "value": "packages/hotkeys/node_modules/@tanstack/react-hotkeys" - } - ] - }, - { - "type": "library", - "bomRef": "pkg:npm/%40tanstack/react-query@5.101.4", + "bomRef": "pkg:npm/%40tanstack/react-query@5.100.8", "name": "@tanstack/react-query", - "version": "5.101.4", - "purl": "pkg:npm/%40tanstack/react-query@5.101.4", + "version": "5.100.8", + "purl": "pkg:npm/%40tanstack/react-query@5.100.8", "description": "Hooks for managing, caching and syncing asynchronous and remote data in React", "licenses": [ { @@ -266,20 +337,20 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/@tanstack_react-query@5.101.4" + "value": "licenses/@tanstack_react-query@5.100.8" }, { "name": "local:sourcePackageDir", - "value": "packages/chat/node_modules/@tanstack/react-query" + "value": "packages/call/node_modules/@tanstack/react-query" } ] }, { "type": "library", - "bomRef": "pkg:npm/%40tanstack/react-router@1.170.23", + "bomRef": "pkg:npm/%40tanstack/react-router@1.169.1", "name": "@tanstack/react-router", - "version": "1.170.23", - "purl": "pkg:npm/%40tanstack/react-router@1.170.23", + "version": "1.169.1", + "purl": "pkg:npm/%40tanstack/react-router@1.169.1", "description": "Modern and scalable routing for React applications", "licenses": [ { @@ -301,7 +372,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/@tanstack_react-router@1.170.23" + "value": "licenses/@tanstack_react-router@1.169.1" }, { "name": "local:sourcePackageDir", @@ -311,10 +382,10 @@ }, { "type": "library", - "bomRef": "pkg:npm/%40tanstack/react-virtual@3.14.9", + "bomRef": "pkg:npm/%40tanstack/react-virtual@3.13.24", "name": "@tanstack/react-virtual", - "version": "3.14.9", - "purl": "pkg:npm/%40tanstack/react-virtual@3.14.9", + "version": "3.13.24", + "purl": "pkg:npm/%40tanstack/react-virtual@3.13.24", "description": "Headless UI for virtualizing scrollable elements in React", "licenses": [ { @@ -336,7 +407,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/@tanstack_react-virtual@3.14.9" + "value": "licenses/@tanstack_react-virtual@3.13.24" }, { "name": "local:sourcePackageDir", @@ -346,10 +417,10 @@ }, { "type": "library", - "bomRef": "pkg:npm/%40tauri-apps/api@2.11.1", + "bomRef": "pkg:npm/%40tauri-apps/api@2.11.0", "name": "@tauri-apps/api", - "version": "2.11.1", - "purl": "pkg:npm/%40tauri-apps/api@2.11.1", + "version": "2.11.0", + "purl": "pkg:npm/%40tauri-apps/api@2.11.0", "description": "Tauri API definitions", "licenses": [ { @@ -371,7 +442,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/@tauri-apps_api@2.11.1" + "value": "licenses/@tauri-apps_api@2.11.0" }, { "name": "local:sourcePackageDir", @@ -381,10 +452,10 @@ }, { "type": "library", - "bomRef": "pkg:npm/%40tauri-apps/cli@2.11.4", + "bomRef": "pkg:npm/%40tauri-apps/cli@2.11.0", "name": "@tauri-apps/cli", - "version": "2.11.4", - "purl": "pkg:npm/%40tauri-apps/cli@2.11.4", + "version": "2.11.0", + "purl": "pkg:npm/%40tauri-apps/cli@2.11.0", "description": "Command line interface for building Tauri apps", "licenses": [ { @@ -406,7 +477,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/@tauri-apps_cli@2.11.4" + "value": "licenses/@tauri-apps_cli@2.11.0" }, { "name": "local:sourcePackageDir", @@ -414,6 +485,37 @@ } ] }, + { + "type": "library", + "bomRef": "pkg:npm/%40tauri-apps/plugin-barcode-scanner@2.4.4", + "name": "@tauri-apps/plugin-barcode-scanner", + "version": "2.4.4", + "purl": "pkg:npm/%40tauri-apps/plugin-barcode-scanner@2.4.4", + "description": "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS", + "licenses": [ + { + "license": { + "name": "MIT OR Apache-2.0" + } + } + ], + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/tauri-apps/plugins-workspace" + } + ], + "properties": [ + { + "name": "local:licenseFolder", + "value": "licenses/@tauri-apps_plugin-barcode-scanner@2.4.4" + }, + { + "name": "local:sourcePackageDir", + "value": "apps/tauri/node_modules/@tauri-apps/plugin-barcode-scanner" + } + ] + }, { "type": "library", "bomRef": "pkg:npm/%40tauri-apps/plugin-deep-link@2.4.9", @@ -447,10 +549,11 @@ }, { "type": "library", - "bomRef": "pkg:npm/%40tauri-apps/plugin-notification@2.3.3", - "name": "@tauri-apps/plugin-notification", - "version": "2.3.3", - "purl": "pkg:npm/%40tauri-apps/plugin-notification@2.3.3", + "bomRef": "pkg:npm/%40tauri-apps/plugin-opener@2.5.4", + "name": "@tauri-apps/plugin-opener", + "version": "2.5.4", + "purl": "pkg:npm/%40tauri-apps/plugin-opener@2.5.4", + "description": "Open files and URLs using their default application.", "licenses": [ { "license": { @@ -467,20 +570,56 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/@tauri-apps_plugin-notification@2.3.3" + "value": "licenses/@tauri-apps_plugin-opener@2.5.4" }, { "name": "local:sourcePackageDir", - "value": "packages/notifications/node_modules/@tauri-apps/plugin-notification" + "value": "apps/tauri/node_modules/@tauri-apps/plugin-opener" } ] }, { "type": "library", - "bomRef": "pkg:npm/%40types/node@26.2.0", + "bomRef": "pkg:npm/%40tensamin/ttp-core@0.0.15", + "name": "@tensamin/ttp-core", + "version": "0.0.15", + "purl": "pkg:npm/%40tensamin/ttp-core@0.0.15", + "externalReferences": [], + "properties": [ + { + "name": "local:licenseFolder", + "value": "licenses/@tensamin_ttp-core@0.0.15" + }, + { + "name": "local:sourcePackageDir", + "value": "packages/ttp/node_modules/@tensamin/ttp-core" + } + ] + }, + { + "type": "library", + "bomRef": "pkg:npm/%40tensamin/ui@0.0.30", + "name": "@tensamin/ui", + "version": "0.0.30", + "purl": "pkg:npm/%40tensamin/ui@0.0.30", + "externalReferences": [], + "properties": [ + { + "name": "local:licenseFolder", + "value": "licenses/@tensamin_ui@0.0.30" + }, + { + "name": "local:sourcePackageDir", + "value": "apps/tauri/node_modules/@tensamin/ui" + } + ] + }, + { + "type": "library", + "bomRef": "pkg:npm/%40types/node@25.6.0", "name": "@types/node", - "version": "26.2.0", - "purl": "pkg:npm/%40types/node@26.2.0", + "version": "25.6.0", + "purl": "pkg:npm/%40types/node@25.6.0", "description": "TypeScript definitions for node", "licenses": [ { @@ -502,11 +641,11 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/@types_node@26.2.0" + "value": "licenses/@types_node@25.6.0" }, { "name": "local:sourcePackageDir", - "value": "apps/electron/node_modules/@types/node" + "value": "node_modules/@types/node" } ] }, @@ -547,10 +686,10 @@ }, { "type": "library", - "bomRef": "pkg:npm/%40types/react@19.2.18", + "bomRef": "pkg:npm/%40types/react@19.2.14", "name": "@types/react", - "version": "19.2.18", - "purl": "pkg:npm/%40types/react@19.2.18", + "version": "19.2.14", + "purl": "pkg:npm/%40types/react@19.2.14", "description": "TypeScript definitions for react", "licenses": [ { @@ -572,7 +711,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/@types_react@19.2.18" + "value": "licenses/@types_react@19.2.14" }, { "name": "local:sourcePackageDir", @@ -582,10 +721,10 @@ }, { "type": "library", - "bomRef": "pkg:npm/%40types/react-dom@19.2.4", + "bomRef": "pkg:npm/%40types/react-dom@19.2.3", "name": "@types/react-dom", - "version": "19.2.4", - "purl": "pkg:npm/%40types/react-dom@19.2.4", + "version": "19.2.3", + "purl": "pkg:npm/%40types/react-dom@19.2.3", "description": "TypeScript definitions for react-dom", "licenses": [ { @@ -607,7 +746,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/@types_react-dom@19.2.4" + "value": "licenses/@types_react-dom@19.2.3" }, { "name": "local:sourcePackageDir", @@ -617,10 +756,10 @@ }, { "type": "library", - "bomRef": "pkg:npm/%40typescript-eslint/parser@8.66.0", + "bomRef": "pkg:npm/%40typescript-eslint/parser@8.59.1", "name": "@typescript-eslint/parser", - "version": "8.66.0", - "purl": "pkg:npm/%40typescript-eslint/parser@8.66.0", + "version": "8.59.1", + "purl": "pkg:npm/%40typescript-eslint/parser@8.59.1", "description": "An ESLint custom parser which leverages TypeScript ESTree", "licenses": [ { @@ -642,7 +781,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/@typescript-eslint_parser@8.66.0" + "value": "licenses/@typescript-eslint_parser@8.59.1" }, { "name": "local:sourcePackageDir", @@ -652,10 +791,10 @@ }, { "type": "library", - "bomRef": "pkg:npm/%40vitejs/plugin-react@6.0.5", + "bomRef": "pkg:npm/%40vitejs/plugin-react@6.0.1", "name": "@vitejs/plugin-react", - "version": "6.0.5", - "purl": "pkg:npm/%40vitejs/plugin-react@6.0.5", + "version": "6.0.1", + "purl": "pkg:npm/%40vitejs/plugin-react@6.0.1", "description": "The default Vite plugin for React projects", "licenses": [ { @@ -677,7 +816,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/@vitejs_plugin-react@6.0.5" + "value": "licenses/@vitejs_plugin-react@6.0.1" }, { "name": "local:sourcePackageDir", @@ -687,11 +826,46 @@ }, { "type": "library", - "bomRef": "pkg:npm/decimal.js-light@2.5.1", - "name": "decimal.js-light", - "version": "2.5.1", - "purl": "pkg:npm/decimal.js-light@2.5.1", - "description": "An arbitrary-precision Decimal type for JavaScript.", + "bomRef": "pkg:npm/class-variance-authority@0.7.1", + "name": "class-variance-authority", + "version": "0.7.1", + "purl": "pkg:npm/class-variance-authority@0.7.1", + "description": "Class Variance Authority 🧬", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/joe-bell/cva#readme" + }, + { + "type": "vcs", + "url": "https://github.com/joe-bell/cva.git" + } + ], + "properties": [ + { + "name": "local:licenseFolder", + "value": "licenses/class-variance-authority@0.7.1" + }, + { + "name": "local:sourcePackageDir", + "value": "apps/web/node_modules/class-variance-authority" + } + ] + }, + { + "type": "library", + "bomRef": "pkg:npm/clsx@2.1.1", + "name": "clsx", + "version": "2.1.1", + "purl": "pkg:npm/clsx@2.1.1", + "description": "A tiny (239B) utility for constructing className strings conditionally.", "licenses": [ { "license": { @@ -702,26 +876,57 @@ "externalReferences": [ { "type": "vcs", - "url": "https://github.com/MikeMcl/decimal.js-light.git" + "url": "lukeed/clsx" } ], "properties": [ { "name": "local:licenseFolder", - "value": "licenses/decimal.js-light@2.5.1" + "value": "licenses/clsx@2.1.1" }, { "name": "local:sourcePackageDir", - "value": "apps/web/node_modules/decimal.js-light" + "value": "apps/web/node_modules/clsx" } ] }, { "type": "library", - "bomRef": "pkg:npm/deepfilternet3-noise-filter@1.3.0", + "bomRef": "pkg:npm/comlink@4.4.2", + "name": "comlink", + "version": "4.4.2", + "purl": "pkg:npm/comlink@4.4.2", + "description": "Comlink makes WebWorkers enjoyable", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/GoogleChromeLabs/comlink.git" + } + ], + "properties": [ + { + "name": "local:licenseFolder", + "value": "licenses/comlink@4.4.2" + }, + { + "name": "local:sourcePackageDir", + "value": "apps/web/node_modules/comlink" + } + ] + }, + { + "type": "library", + "bomRef": "pkg:npm/deepfilternet3-noise-filter@1.2.1", "name": "deepfilternet3-noise-filter", - "version": "1.3.0", - "purl": "pkg:npm/deepfilternet3-noise-filter@1.3.0", + "version": "1.2.1", + "purl": "pkg:npm/deepfilternet3-noise-filter@1.2.1", "description": "Custom audio processor with DeepFilterNet3 noise filtering integrated with LiveKit client", "licenses": [ { @@ -743,7 +948,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/deepfilternet3-noise-filter@1.3.0" + "value": "licenses/deepfilternet3-noise-filter@1.2.1" }, { "name": "local:sourcePackageDir", @@ -753,76 +958,10 @@ }, { "type": "library", - "bomRef": "pkg:npm/electron@43.3.0", - "name": "electron", - "version": "43.3.0", - "purl": "pkg:npm/electron@43.3.0", - "description": "Build cross platform desktop apps with JavaScript, HTML, and CSS", - "licenses": [ - { - "license": { - "id": "MIT" - } - } - ], - "externalReferences": [ - { - "type": "vcs", - "url": "https://github.com/electron/electron" - } - ], - "properties": [ - { - "name": "local:licenseFolder", - "value": "licenses/electron@43.3.0" - }, - { - "name": "local:sourcePackageDir", - "value": "apps/electron/node_modules/electron" - } - ] - }, - { - "type": "library", - "bomRef": "pkg:npm/electron-builder@26.15.3", - "name": "electron-builder", - "version": "26.15.3", - "purl": "pkg:npm/electron-builder@26.15.3", - "description": "A complete solution to package and build a ready for distribution Electron app for MacOS, Windows and Linux with “auto update” support out of the box", - "licenses": [ - { - "license": { - "id": "MIT" - } - } - ], - "externalReferences": [ - { - "type": "website", - "url": "https://github.com/electron-userland/electron-builder" - }, - { - "type": "vcs", - "url": "git+https://github.com/electron-userland/electron-builder.git" - } - ], - "properties": [ - { - "name": "local:licenseFolder", - "value": "licenses/electron-builder@26.15.3" - }, - { - "name": "local:sourcePackageDir", - "value": "apps/electron/node_modules/electron-builder" - } - ] - }, - { - "type": "library", - "bomRef": "pkg:npm/esbuild@0.28.1", + "bomRef": "pkg:npm/esbuild@0.25.12", "name": "esbuild", - "version": "0.28.1", - "purl": "pkg:npm/esbuild@0.28.1", + "version": "0.25.12", + "purl": "pkg:npm/esbuild@0.25.12", "description": "An extremely fast JavaScript and CSS bundler and minifier.", "licenses": [ { @@ -840,20 +979,20 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/esbuild@0.28.1" + "value": "licenses/esbuild@0.25.12" }, { "name": "local:sourcePackageDir", - "value": "apps/electron/node_modules/esbuild" + "value": "apps/web/node_modules/esbuild" } ] }, { "type": "library", - "bomRef": "pkg:npm/eslint@10.8.1", + "bomRef": "pkg:npm/eslint@10.3.0", "name": "eslint", - "version": "10.8.1", - "purl": "pkg:npm/eslint@10.8.1", + "version": "10.3.0", + "purl": "pkg:npm/eslint@10.3.0", "description": "An AST-based pattern checker for JavaScript.", "licenses": [ { @@ -875,7 +1014,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/eslint@10.8.1" + "value": "licenses/eslint@10.3.0" }, { "name": "local:sourcePackageDir", @@ -920,11 +1059,11 @@ }, { "type": "library", - "bomRef": "pkg:npm/eventemitter3@5.0.4", - "name": "eventemitter3", - "version": "5.0.4", - "purl": "pkg:npm/eventemitter3@5.0.4", - "description": "EventEmitter3 focuses on performance while maintaining a Node.js AND browser compatible interface.", + "bomRef": "pkg:npm/framer-motion@12.38.0", + "name": "framer-motion", + "version": "12.38.0", + "purl": "pkg:npm/framer-motion@12.38.0", + "description": "A simple and powerful JavaScript animation library", "licenses": [ { "license": { @@ -935,61 +1074,26 @@ "externalReferences": [ { "type": "vcs", - "url": "git://github.com/primus/eventemitter3.git" + "url": "https://github.com/motiondivision/motion/" } ], "properties": [ { "name": "local:licenseFolder", - "value": "licenses/eventemitter3@5.0.4" + "value": "licenses/framer-motion@12.38.0" }, { "name": "local:sourcePackageDir", - "value": "apps/web/node_modules/eventemitter3" + "value": "apps/web/node_modules/framer-motion" } ] }, { "type": "library", - "bomRef": "pkg:npm/fallow@3.14.0", - "name": "fallow", - "version": "3.14.0", - "purl": "pkg:npm/fallow@3.14.0", - "description": "Codebase intelligence for TypeScript and JavaScript. Free static analysis of code and styles, optional paid runtime intelligence (Fallow Runtime). Quality, risk, architecture, dependencies, duplication, and design-system drift for humans, CI, and the agents writing your code. Zero-config framework support.", - "licenses": [ - { - "license": { - "id": "MIT" - } - } - ], - "externalReferences": [ - { - "type": "website", - "url": "https://docs.fallow.tools" - }, - { - "type": "vcs", - "url": "git+https://github.com/fallow-rs/fallow.git" - } - ], - "properties": [ - { - "name": "local:licenseFolder", - "value": "licenses/fallow@3.14.0" - }, - { - "name": "local:sourcePackageDir", - "value": "node_modules/fallow" - } - ] - }, - { - "type": "library", - "bomRef": "pkg:npm/globals@17.9.0", + "bomRef": "pkg:npm/globals@17.6.0", "name": "globals", - "version": "17.9.0", - "purl": "pkg:npm/globals@17.9.0", + "version": "17.6.0", + "purl": "pkg:npm/globals@17.6.0", "description": "Global identifiers from different JavaScript environments", "licenses": [ { @@ -1007,7 +1111,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/globals@17.9.0" + "value": "licenses/globals@17.6.0" }, { "name": "local:sourcePackageDir", @@ -1017,10 +1121,41 @@ }, { "type": "library", - "bomRef": "pkg:npm/livekit-client@2.21.0", + "bomRef": "pkg:npm/jsonc-parser@3.3.1", + "name": "jsonc-parser", + "version": "3.3.1", + "purl": "pkg:npm/jsonc-parser@3.3.1", + "description": "Scanner and parser for JSON with comments.", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/microsoft/node-jsonc-parser" + } + ], + "properties": [ + { + "name": "local:licenseFolder", + "value": "licenses/jsonc-parser@3.3.1" + }, + { + "name": "local:sourcePackageDir", + "value": "node_modules/jsonc-parser" + } + ] + }, + { + "type": "library", + "bomRef": "pkg:npm/livekit-client@2.18.8", "name": "livekit-client", - "version": "2.21.0", - "purl": "pkg:npm/livekit-client@2.21.0", + "version": "2.18.8", + "purl": "pkg:npm/livekit-client@2.18.8", "description": "JavaScript/TypeScript client SDK for LiveKit", "licenses": [ { @@ -1038,7 +1173,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/livekit-client@2.21.0" + "value": "licenses/livekit-client@2.18.8" }, { "name": "local:sourcePackageDir", @@ -1048,10 +1183,10 @@ }, { "type": "library", - "bomRef": "pkg:npm/lucide-react@1.30.0", + "bomRef": "pkg:npm/lucide-react@1.14.0", "name": "lucide-react", - "version": "1.30.0", - "purl": "pkg:npm/lucide-react@1.30.0", + "version": "1.14.0", + "purl": "pkg:npm/lucide-react@1.14.0", "description": "A Lucide icon library package for React applications.", "licenses": [ { @@ -1073,70 +1208,20 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/lucide-react@1.30.0" + "value": "licenses/lucide-react@1.14.0" }, { "name": "local:sourcePackageDir", - "value": "apps/web/node_modules/lucide-react" + "value": "apps/tauri/node_modules/lucide-react" } ] }, { "type": "library", - "bomRef": "pkg:npm/motion@13.0.0", - "name": "motion", - "version": "13.0.0", - "purl": "pkg:npm/motion@13.0.0", - "description": "An animation library for JavaScript and React.", - "licenses": [ - { - "license": { - "id": "MIT" - } - } - ], - "externalReferences": [ - { - "type": "vcs", - "url": "https://github.com/motiondivision/motion" - } - ], - "properties": [ - { - "name": "local:licenseFolder", - "value": "licenses/motion@13.0.0" - }, - { - "name": "local:sourcePackageDir", - "value": "packages/chat/node_modules/motion" - } - ] - }, - { - "type": "library", - "bomRef": "pkg:npm/mtp@0.2.0", - "name": "mtp", - "version": "0.2.0", - "purl": "pkg:npm/mtp@0.2.0", - "description": "MTP TypeScript SDK", - "externalReferences": [], - "properties": [ - { - "name": "local:licenseFolder", - "value": "licenses/mtp@0.2.0" - }, - { - "name": "local:sourcePackageDir", - "value": "packages/mtp/node_modules/mtp" - } - ] - }, - { - "type": "library", - "bomRef": "pkg:npm/prettier@3.9.6", + "bomRef": "pkg:npm/prettier@3.8.3", "name": "prettier", - "version": "3.9.6", - "purl": "pkg:npm/prettier@3.9.6", + "version": "3.8.3", + "purl": "pkg:npm/prettier@3.8.3", "description": "Prettier is an opinionated code formatter", "licenses": [ { @@ -1158,7 +1243,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/prettier@3.9.6" + "value": "licenses/prettier@3.8.3" }, { "name": "local:sourcePackageDir", @@ -1168,10 +1253,45 @@ }, { "type": "library", - "bomRef": "pkg:npm/react@19.2.8", + "bomRef": "pkg:npm/qrcode@1.5.4", + "name": "qrcode", + "version": "1.5.4", + "purl": "pkg:npm/qrcode@1.5.4", + "description": "QRCode / 2d Barcode api with both server side and client side support using canvas", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "externalReferences": [ + { + "type": "website", + "url": "http://github.com/soldair/node-qrcode" + }, + { + "type": "vcs", + "url": "git://github.com/soldair/node-qrcode.git" + } + ], + "properties": [ + { + "name": "local:licenseFolder", + "value": "licenses/qrcode@1.5.4" + }, + { + "name": "local:sourcePackageDir", + "value": "apps/web/node_modules/qrcode" + } + ] + }, + { + "type": "library", + "bomRef": "pkg:npm/react@19.2.5", "name": "react", - "version": "19.2.8", - "purl": "pkg:npm/react@19.2.8", + "version": "19.2.5", + "purl": "pkg:npm/react@19.2.5", "description": "React is a JavaScript library for building user interfaces.", "licenses": [ { @@ -1187,13 +1307,13 @@ }, { "type": "vcs", - "url": "https://github.com/react/react.git" + "url": "https://github.com/facebook/react.git" } ], "properties": [ { "name": "local:licenseFolder", - "value": "licenses/react@19.2.8" + "value": "licenses/react@19.2.5" }, { "name": "local:sourcePackageDir", @@ -1203,10 +1323,10 @@ }, { "type": "library", - "bomRef": "pkg:npm/react-dom@19.2.8", + "bomRef": "pkg:npm/react-dom@19.2.5", "name": "react-dom", - "version": "19.2.8", - "purl": "pkg:npm/react-dom@19.2.8", + "version": "19.2.5", + "purl": "pkg:npm/react-dom@19.2.5", "description": "React package for working with the DOM.", "licenses": [ { @@ -1222,13 +1342,13 @@ }, { "type": "vcs", - "url": "https://github.com/react/react.git" + "url": "https://github.com/facebook/react.git" } ], "properties": [ { "name": "local:licenseFolder", - "value": "licenses/react-dom@19.2.8" + "value": "licenses/react-dom@19.2.5" }, { "name": "local:sourcePackageDir", @@ -1238,80 +1358,10 @@ }, { "type": "library", - "bomRef": "pkg:npm/react-is@19.2.8", - "name": "react-is", - "version": "19.2.8", - "purl": "pkg:npm/react-is@19.2.8", - "description": "Brand checking of React Elements.", - "licenses": [ - { - "license": { - "id": "MIT" - } - } - ], - "externalReferences": [ - { - "type": "website", - "url": "https://react.dev/" - }, - { - "type": "vcs", - "url": "https://github.com/react/react.git" - } - ], - "properties": [ - { - "name": "local:licenseFolder", - "value": "licenses/react-is@19.2.8" - }, - { - "name": "local:sourcePackageDir", - "value": "apps/web/node_modules/react-is" - } - ] - }, - { - "type": "library", - "bomRef": "pkg:npm/react-redux@9.3.0", - "name": "react-redux", - "version": "9.3.0", - "purl": "pkg:npm/react-redux@9.3.0", - "description": "Official React bindings for Redux", - "licenses": [ - { - "license": { - "id": "MIT" - } - } - ], - "externalReferences": [ - { - "type": "website", - "url": "https://github.com/reduxjs/react-redux" - }, - { - "type": "vcs", - "url": "github:reduxjs/react-redux" - } - ], - "properties": [ - { - "name": "local:licenseFolder", - "value": "licenses/react-redux@9.3.0" - }, - { - "name": "local:sourcePackageDir", - "value": "apps/web/node_modules/react-redux" - } - ] - }, - { - "type": "library", - "bomRef": "pkg:npm/recharts@3.10.1", + "bomRef": "pkg:npm/recharts@3.8.1", "name": "recharts", - "version": "3.10.1", - "purl": "pkg:npm/recharts@3.10.1", + "version": "3.8.1", + "purl": "pkg:npm/recharts@3.8.1", "description": "React charts", "licenses": [ { @@ -1333,7 +1383,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/recharts@3.10.1" + "value": "licenses/recharts@3.8.1" }, { "name": "local:sourcePackageDir", @@ -1341,6 +1391,37 @@ } ] }, + { + "type": "library", + "bomRef": "pkg:npm/shadcn@4.6.0", + "name": "shadcn", + "version": "4.6.0", + "purl": "pkg:npm/shadcn@4.6.0", + "description": "Add components to your apps.", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/shadcn-ui/ui.git" + } + ], + "properties": [ + { + "name": "local:licenseFolder", + "value": "licenses/shadcn@4.6.0" + }, + { + "name": "local:sourcePackageDir", + "value": "apps/web/node_modules/shadcn" + } + ] + }, { "type": "library", "bomRef": "pkg:npm/sonner@2.0.7", @@ -1372,7 +1453,42 @@ }, { "name": "local:sourcePackageDir", - "value": "packages/notifications/node_modules/sonner" + "value": "apps/web/node_modules/sonner" + } + ] + }, + { + "type": "library", + "bomRef": "pkg:npm/tailwind-merge@3.5.0", + "name": "tailwind-merge", + "version": "3.5.0", + "purl": "pkg:npm/tailwind-merge@3.5.0", + "description": "Merge Tailwind CSS classes without style conflicts", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/dcastil/tailwind-merge" + }, + { + "type": "vcs", + "url": "https://github.com/dcastil/tailwind-merge.git" + } + ], + "properties": [ + { + "name": "local:licenseFolder", + "value": "licenses/tailwind-merge@3.5.0" + }, + { + "name": "local:sourcePackageDir", + "value": "apps/web/node_modules/tailwind-merge" } ] }, @@ -1413,10 +1529,10 @@ }, { "type": "library", - "bomRef": "pkg:npm/tailwindcss@4.3.3", + "bomRef": "pkg:npm/tailwindcss@4.2.4", "name": "tailwindcss", - "version": "4.3.3", - "purl": "pkg:npm/tailwindcss@4.3.3", + "version": "4.2.4", + "purl": "pkg:npm/tailwindcss@4.2.4", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "licenses": [ { @@ -1438,7 +1554,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/tailwindcss@4.3.3" + "value": "licenses/tailwindcss@4.2.4" }, { "name": "local:sourcePackageDir", @@ -1512,16 +1628,16 @@ }, { "name": "local:sourcePackageDir", - "value": "apps/electron/node_modules/typescript" + "value": "apps/web/node_modules/typescript" } ] }, { "type": "library", - "bomRef": "pkg:npm/typescript-eslint@8.66.0", + "bomRef": "pkg:npm/typescript-eslint@8.59.1", "name": "typescript-eslint", - "version": "8.66.0", - "purl": "pkg:npm/typescript-eslint@8.66.0", + "version": "8.59.1", + "purl": "pkg:npm/typescript-eslint@8.59.1", "description": "Tooling which enables you to use TypeScript with ESLint", "licenses": [ { @@ -1543,7 +1659,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/typescript-eslint@8.66.0" + "value": "licenses/typescript-eslint@8.59.1" }, { "name": "local:sourcePackageDir", @@ -1553,41 +1669,10 @@ }, { "type": "library", - "bomRef": "pkg:npm/use-sync-external-store@1.6.0", - "name": "use-sync-external-store", - "version": "1.6.0", - "purl": "pkg:npm/use-sync-external-store@1.6.0", - "description": "Backwards compatible shim for React's useSyncExternalStore. Works with any React that supports hooks.", - "licenses": [ - { - "license": { - "id": "MIT" - } - } - ], - "externalReferences": [ - { - "type": "vcs", - "url": "https://github.com/facebook/react.git" - } - ], - "properties": [ - { - "name": "local:licenseFolder", - "value": "licenses/use-sync-external-store@1.6.0" - }, - { - "name": "local:sourcePackageDir", - "value": "apps/web/node_modules/use-sync-external-store" - } - ] - }, - { - "type": "library", - "bomRef": "pkg:npm/vite@8.2.1", + "bomRef": "pkg:npm/vite@8.0.10", "name": "vite", - "version": "8.2.1", - "purl": "pkg:npm/vite@8.2.1", + "version": "8.0.10", + "purl": "pkg:npm/vite@8.0.10", "description": "Native-ESM powered web dev build tool", "licenses": [ { @@ -1609,7 +1694,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/vite@8.2.1" + "value": "licenses/vite@8.0.10" }, { "name": "local:sourcePackageDir", @@ -1619,45 +1704,10 @@ }, { "type": "library", - "bomRef": "pkg:npm/vitest@4.1.10", - "name": "vitest", - "version": "4.1.10", - "purl": "pkg:npm/vitest@4.1.10", - "description": "Next generation testing framework powered by Vite", - "licenses": [ - { - "license": { - "id": "MIT" - } - } - ], - "externalReferences": [ - { - "type": "website", - "url": "https://vitest.dev" - }, - { - "type": "vcs", - "url": "git+https://github.com/vitest-dev/vitest.git" - } - ], - "properties": [ - { - "name": "local:licenseFolder", - "value": "licenses/vitest@4.1.10" - }, - { - "name": "local:sourcePackageDir", - "value": "node_modules/vitest" - } - ] - }, - { - "type": "library", - "bomRef": "pkg:npm/zod@4.4.3", + "bomRef": "pkg:npm/zod@4.4.2", "name": "zod", - "version": "4.4.3", - "purl": "pkg:npm/zod@4.4.3", + "version": "4.4.2", + "purl": "pkg:npm/zod@4.4.2", "description": "TypeScript-first schema declaration and validation library with static type inference", "licenses": [ { @@ -1679,7 +1729,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/zod@4.4.3" + "value": "licenses/zod@4.4.2" }, { "name": "local:sourcePackageDir", @@ -1689,10 +1739,10 @@ }, { "type": "library", - "bomRef": "pkg:npm/zustand@5.0.14", + "bomRef": "pkg:npm/zustand@5.0.12", "name": "zustand", - "version": "5.0.14", - "purl": "pkg:npm/zustand@5.0.14", + "version": "5.0.12", + "purl": "pkg:npm/zustand@5.0.12", "description": "🐻 Bear necessities for state management in React", "licenses": [ { @@ -1714,7 +1764,7 @@ "properties": [ { "name": "local:licenseFolder", - "value": "licenses/zustand@5.0.14" + "value": "licenses/zustand@5.0.12" }, { "name": "local:sourcePackageDir", @@ -1723,4 +1773,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/licenses/@tanstack_pacer@0.21.1/LICENSE b/licenses/shadcn@4.6.0/LICENSE.md similarity index 96% rename from licenses/@tanstack_pacer@0.21.1/LICENSE rename to licenses/shadcn@4.6.0/LICENSE.md index 308cb68..fad4d88 100644 --- a/licenses/@tanstack_pacer@0.21.1/LICENSE +++ b/licenses/shadcn@4.6.0/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Tanner Linsley +Copyright (c) 2023 shadcn Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/licenses/@tanstack_react-hotkeys@0.10.0/LICENSE b/licenses/tailwind-merge@3.5.0/LICENSE.md similarity index 96% rename from licenses/@tanstack_react-hotkeys@0.10.0/LICENSE rename to licenses/tailwind-merge@3.5.0/LICENSE.md index 8ce4739..0d2b96a 100644 --- a/licenses/@tanstack_react-hotkeys@0.10.0/LICENSE +++ b/licenses/tailwind-merge@3.5.0/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 Tanner Linsley +Copyright (c) 2021 Dany Castillo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/licenses/tailwindcss@4.3.3/LICENSE b/licenses/tailwindcss@4.2.4/LICENSE similarity index 100% rename from licenses/tailwindcss@4.3.3/LICENSE rename to licenses/tailwindcss@4.2.4/LICENSE diff --git a/licenses/third-party-credits.json b/licenses/third-party-credits.json index 6188b40..a1d7968 100644 --- a/licenses/third-party-credits.json +++ b/licenses/third-party-credits.json @@ -1,7 +1,59 @@ { - "generatedAt": "2026-08-09T22:09:04.466Z", - "packageCount": 52, + "generatedAt": "2026-05-04T13:05:17.471Z", + "packageCount": 53, "packages": [ + { + "name": "@codemirror/commands", + "version": "6.10.3", + "license": "MIT", + "homepage": null, + "repository": "git+https://github.com/codemirror/commands.git", + "description": "Collection of editing commands for the CodeMirror code editor", + "files": [ + "LICENSE" + ], + "licenseFolder": "licenses/@codemirror_commands@6.10.3", + "sourcePackageDir": "packages/markdown/node_modules/@codemirror/commands" + }, + { + "name": "@codemirror/lang-markdown", + "version": "6.5.0", + "license": "MIT", + "homepage": null, + "repository": "https://github.com/codemirror/lang-markdown.git", + "description": "Markdown language support for the CodeMirror code editor", + "files": [ + "LICENSE" + ], + "licenseFolder": "licenses/@codemirror_lang-markdown@6.5.0", + "sourcePackageDir": "packages/markdown/node_modules/@codemirror/lang-markdown" + }, + { + "name": "@codemirror/state", + "version": "6.6.0", + "license": "MIT", + "homepage": null, + "repository": "git+https://github.com/codemirror/state.git", + "description": "Editor state data structures for the CodeMirror code editor", + "files": [ + "LICENSE" + ], + "licenseFolder": "licenses/@codemirror_state@6.6.0", + "sourcePackageDir": "packages/markdown/node_modules/@codemirror/state" + }, + { + "name": "@codemirror/view", + "version": "6.41.1", + "license": "MIT", + "homepage": null, + "repository": "git+https://code.haverbeke.berlin/codemirror/view.git", + "description": "DOM view component for the CodeMirror code editor", + "files": [ + "LICENSE" + ], + "licenseFolder": "licenses/@codemirror_view@6.41.1", + "sourcePackageDir": "packages/markdown/node_modules/@codemirror/view" + }, { "name": "@eslint/js", "version": "10.0.1", @@ -16,21 +68,21 @@ "sourcePackageDir": "apps/web/node_modules/@eslint/js" }, { - "name": "@fontsource-variable/public-sans", - "version": "5.3.0", + "name": "@fontsource-variable/inter", + "version": "5.2.8", "license": "OFL-1.1", - "homepage": "https://fontsource.org/fonts/public-sans", + "homepage": "https://fontsource.org/fonts/inter", "repository": "git+https://github.com/fontsource/font-files.git", - "description": "Self-host the Public Sans font in a neatly bundled NPM package.", + "description": "Self-host the Inter font in a neatly bundled NPM package.", "files": [ "LICENSE" ], - "licenseFolder": "licenses/@fontsource-variable_public-sans@5.3.0", - "sourcePackageDir": "apps/web/node_modules/@fontsource-variable/public-sans" + "licenseFolder": "licenses/@fontsource-variable_inter@5.2.8", + "sourcePackageDir": "apps/web/node_modules/@fontsource-variable/inter" }, { "name": "@livekit/components-react", - "version": "2.9.23", + "version": "2.9.20", "license": "Apache-2.0", "homepage": null, "repository": "https://github.com/livekit/components-js.git", @@ -38,25 +90,25 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/@livekit_components-react@2.9.23", + "licenseFolder": "licenses/@livekit_components-react@2.9.20", "sourcePackageDir": "packages/call/node_modules/@livekit/components-react" }, { - "name": "@methanium/ui", - "version": "0.0.28", - "license": "UNKNOWN", - "homepage": null, - "repository": null, - "description": null, + "name": "@noble/curves", + "version": "2.2.0", + "license": "MIT", + "homepage": "https://paulmillr.com/noble/", + "repository": "git+https://github.com/paulmillr/noble-curves.git", + "description": "Audited & minimal JS implementation of elliptic curve cryptography", "files": [ "LICENSE" ], - "licenseFolder": "licenses/@methanium_ui@0.0.28", - "sourcePackageDir": "apps/tauri/node_modules/@methanium/ui" + "licenseFolder": "licenses/@noble_curves@2.2.0", + "sourcePackageDir": "apps/web/node_modules/@noble/curves" }, { "name": "@tailwindcss/vite", - "version": "4.3.3", + "version": "4.2.4", "license": "MIT", "homepage": "https://tailwindcss.com", "repository": "https://github.com/tailwindlabs/tailwindcss.git", @@ -64,38 +116,12 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/@tailwindcss_vite@4.3.3", + "licenseFolder": "licenses/@tailwindcss_vite@4.2.4", "sourcePackageDir": "apps/web/node_modules/@tailwindcss/vite" }, - { - "name": "@tanstack/pacer", - "version": "0.21.1", - "license": "MIT", - "homepage": "https://tanstack.com/pacer", - "repository": "git+https://github.com/TanStack/pacer.git", - "description": "Utilities for debouncing, throttling, rate-limiting, queuing, and more.", - "files": [ - "LICENSE" - ], - "licenseFolder": "licenses/@tanstack_pacer@0.21.1", - "sourcePackageDir": "packages/chat/node_modules/@tanstack/pacer" - }, - { - "name": "@tanstack/react-hotkeys", - "version": "0.10.0", - "license": "MIT", - "homepage": "https://tanstack.com/hotkeys", - "repository": "git+https://github.com/TanStack/hotkeys.git", - "description": "React adapter for TanStack Hotkeys", - "files": [ - "LICENSE" - ], - "licenseFolder": "licenses/@tanstack_react-hotkeys@0.10.0", - "sourcePackageDir": "packages/hotkeys/node_modules/@tanstack/react-hotkeys" - }, { "name": "@tanstack/react-query", - "version": "5.101.4", + "version": "5.100.8", "license": "MIT", "homepage": "https://tanstack.com/query", "repository": "git+https://github.com/TanStack/query.git", @@ -103,12 +129,12 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/@tanstack_react-query@5.101.4", - "sourcePackageDir": "packages/chat/node_modules/@tanstack/react-query" + "licenseFolder": "licenses/@tanstack_react-query@5.100.8", + "sourcePackageDir": "packages/call/node_modules/@tanstack/react-query" }, { "name": "@tanstack/react-router", - "version": "1.170.23", + "version": "1.169.1", "license": "MIT", "homepage": "https://tanstack.com/router", "repository": "git+https://github.com/TanStack/router.git", @@ -116,12 +142,12 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/@tanstack_react-router@1.170.23", + "licenseFolder": "licenses/@tanstack_react-router@1.169.1", "sourcePackageDir": "apps/web/node_modules/@tanstack/react-router" }, { "name": "@tanstack/react-virtual", - "version": "3.14.9", + "version": "3.13.24", "license": "MIT", "homepage": "https://tanstack.com/virtual", "repository": "git+https://github.com/TanStack/virtual.git", @@ -129,37 +155,50 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/@tanstack_react-virtual@3.14.9", + "licenseFolder": "licenses/@tanstack_react-virtual@3.13.24", "sourcePackageDir": "apps/web/node_modules/@tanstack/react-virtual" }, { "name": "@tauri-apps/api", - "version": "2.11.1", + "version": "2.11.0", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/tauri-apps/tauri#readme", "repository": "git+https://github.com/tauri-apps/tauri.git", "description": "Tauri API definitions", "files": [ - "LICENSE_APACHE-2.0", - "LICENSE_MIT" + "LICENSE_MIT", + "LICENSE_APACHE-2.0" ], - "licenseFolder": "licenses/@tauri-apps_api@2.11.1", + "licenseFolder": "licenses/@tauri-apps_api@2.11.0", "sourcePackageDir": "apps/tauri/node_modules/@tauri-apps/api" }, { "name": "@tauri-apps/cli", - "version": "2.11.4", + "version": "2.11.0", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/tauri-apps/tauri#readme", "repository": "git+https://github.com/tauri-apps/tauri.git", "description": "Command line interface for building Tauri apps", "files": [ - "LICENSE_APACHE-2.0", - "LICENSE_MIT" + "LICENSE_MIT", + "LICENSE_APACHE-2.0" ], - "licenseFolder": "licenses/@tauri-apps_cli@2.11.4", + "licenseFolder": "licenses/@tauri-apps_cli@2.11.0", "sourcePackageDir": "apps/tauri/node_modules/@tauri-apps/cli" }, + { + "name": "@tauri-apps/plugin-barcode-scanner", + "version": "2.4.4", + "license": "MIT OR Apache-2.0", + "homepage": null, + "repository": "https://github.com/tauri-apps/plugins-workspace", + "description": "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS", + "files": [ + "LICENSE.spdx" + ], + "licenseFolder": "licenses/@tauri-apps_plugin-barcode-scanner@2.4.4", + "sourcePackageDir": "apps/tauri/node_modules/@tauri-apps/plugin-barcode-scanner" + }, { "name": "@tauri-apps/plugin-deep-link", "version": "2.4.9", @@ -174,21 +213,45 @@ "sourcePackageDir": "apps/tauri/node_modules/@tauri-apps/plugin-deep-link" }, { - "name": "@tauri-apps/plugin-notification", - "version": "2.3.3", + "name": "@tauri-apps/plugin-opener", + "version": "2.5.4", "license": "MIT OR Apache-2.0", "homepage": null, "repository": "https://github.com/tauri-apps/plugins-workspace", - "description": null, + "description": "Open files and URLs using their default application.", "files": [ "LICENSE.spdx" ], - "licenseFolder": "licenses/@tauri-apps_plugin-notification@2.3.3", - "sourcePackageDir": "packages/notifications/node_modules/@tauri-apps/plugin-notification" + "licenseFolder": "licenses/@tauri-apps_plugin-opener@2.5.4", + "sourcePackageDir": "apps/tauri/node_modules/@tauri-apps/plugin-opener" + }, + { + "name": "@tensamin/ttp-core", + "version": "0.0.15", + "license": "UNKNOWN", + "homepage": null, + "repository": null, + "description": null, + "files": [ + "LICENSE" + ], + "licenseFolder": "licenses/@tensamin_ttp-core@0.0.15", + "sourcePackageDir": "packages/ttp/node_modules/@tensamin/ttp-core" + }, + { + "name": "@tensamin/ui", + "version": "0.0.30", + "license": "UNKNOWN", + "homepage": null, + "repository": null, + "description": null, + "files": [], + "licenseFolder": "licenses/@tensamin_ui@0.0.30", + "sourcePackageDir": "apps/tauri/node_modules/@tensamin/ui" }, { "name": "@types/node", - "version": "26.2.0", + "version": "25.6.0", "license": "MIT", "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node", "repository": "https://github.com/DefinitelyTyped/DefinitelyTyped.git", @@ -196,8 +259,8 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/@types_node@26.2.0", - "sourcePackageDir": "apps/electron/node_modules/@types/node" + "licenseFolder": "licenses/@types_node@25.6.0", + "sourcePackageDir": "node_modules/@types/node" }, { "name": "@types/qrcode", @@ -214,7 +277,7 @@ }, { "name": "@types/react", - "version": "19.2.18", + "version": "19.2.14", "license": "MIT", "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react", "repository": "https://github.com/DefinitelyTyped/DefinitelyTyped.git", @@ -222,12 +285,12 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/@types_react@19.2.18", + "licenseFolder": "licenses/@types_react@19.2.14", "sourcePackageDir": "apps/web/node_modules/@types/react" }, { "name": "@types/react-dom", - "version": "19.2.4", + "version": "19.2.3", "license": "MIT", "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react-dom", "repository": "https://github.com/DefinitelyTyped/DefinitelyTyped.git", @@ -235,12 +298,12 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/@types_react-dom@19.2.4", + "licenseFolder": "licenses/@types_react-dom@19.2.3", "sourcePackageDir": "apps/web/node_modules/@types/react-dom" }, { "name": "@typescript-eslint/parser", - "version": "8.66.0", + "version": "8.59.1", "license": "MIT", "homepage": "https://typescript-eslint.io/packages/parser", "repository": "https://github.com/typescript-eslint/typescript-eslint.git", @@ -248,12 +311,12 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/@typescript-eslint_parser@8.66.0", + "licenseFolder": "licenses/@typescript-eslint_parser@8.59.1", "sourcePackageDir": "node_modules/@typescript-eslint/parser" }, { "name": "@vitejs/plugin-react", - "version": "6.0.5", + "version": "6.0.1", "license": "MIT", "homepage": "https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme", "repository": "git+https://github.com/vitejs/vite-plugin-react.git", @@ -261,25 +324,51 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/@vitejs_plugin-react@6.0.5", + "licenseFolder": "licenses/@vitejs_plugin-react@6.0.1", "sourcePackageDir": "apps/web/node_modules/@vitejs/plugin-react" }, { - "name": "decimal.js-light", - "version": "2.5.1", + "name": "class-variance-authority", + "version": "0.7.1", + "license": "Apache-2.0", + "homepage": "https://github.com/joe-bell/cva#readme", + "repository": "https://github.com/joe-bell/cva.git", + "description": "Class Variance Authority 🧬", + "files": [ + "LICENSE" + ], + "licenseFolder": "licenses/class-variance-authority@0.7.1", + "sourcePackageDir": "apps/web/node_modules/class-variance-authority" + }, + { + "name": "clsx", + "version": "2.1.1", "license": "MIT", "homepage": null, - "repository": "https://github.com/MikeMcl/decimal.js-light.git", - "description": "An arbitrary-precision Decimal type for JavaScript.", + "repository": "lukeed/clsx", + "description": "A tiny (239B) utility for constructing className strings conditionally.", "files": [ - "LICENCE.md" + "license" ], - "licenseFolder": "licenses/decimal.js-light@2.5.1", - "sourcePackageDir": "apps/web/node_modules/decimal.js-light" + "licenseFolder": "licenses/clsx@2.1.1", + "sourcePackageDir": "apps/web/node_modules/clsx" + }, + { + "name": "comlink", + "version": "4.4.2", + "license": "Apache-2.0", + "homepage": null, + "repository": "https://github.com/GoogleChromeLabs/comlink.git", + "description": "Comlink makes WebWorkers enjoyable", + "files": [ + "LICENSE" + ], + "licenseFolder": "licenses/comlink@4.4.2", + "sourcePackageDir": "apps/web/node_modules/comlink" }, { "name": "deepfilternet3-noise-filter", - "version": "1.3.0", + "version": "1.2.1", "license": "(Apache-2.0 OR MIT)", "homepage": "https://github.com/mezonai/mezon-noise-suppression#readme", "repository": "git+https://github.com/mezonai/mezon-noise-suppression.git", @@ -288,38 +377,12 @@ "LICENSE-APACHE", "LICENSE-MIT" ], - "licenseFolder": "licenses/deepfilternet3-noise-filter@1.3.0", + "licenseFolder": "licenses/deepfilternet3-noise-filter@1.2.1", "sourcePackageDir": "packages/call/node_modules/deepfilternet3-noise-filter" }, - { - "name": "electron", - "version": "43.3.0", - "license": "MIT", - "homepage": null, - "repository": "https://github.com/electron/electron", - "description": "Build cross platform desktop apps with JavaScript, HTML, and CSS", - "files": [ - "LICENSE" - ], - "licenseFolder": "licenses/electron@43.3.0", - "sourcePackageDir": "apps/electron/node_modules/electron" - }, - { - "name": "electron-builder", - "version": "26.15.3", - "license": "MIT", - "homepage": "https://github.com/electron-userland/electron-builder", - "repository": "git+https://github.com/electron-userland/electron-builder.git", - "description": "A complete solution to package and build a ready for distribution Electron app for MacOS, Windows and Linux with “auto update” support out of the box", - "files": [ - "LICENSE" - ], - "licenseFolder": "licenses/electron-builder@26.15.3", - "sourcePackageDir": "apps/electron/node_modules/electron-builder" - }, { "name": "esbuild", - "version": "0.28.1", + "version": "0.25.12", "license": "MIT", "homepage": null, "repository": "git+https://github.com/evanw/esbuild.git", @@ -327,12 +390,12 @@ "files": [ "LICENSE.md" ], - "licenseFolder": "licenses/esbuild@0.28.1", - "sourcePackageDir": "apps/electron/node_modules/esbuild" + "licenseFolder": "licenses/esbuild@0.25.12", + "sourcePackageDir": "apps/web/node_modules/esbuild" }, { "name": "eslint", - "version": "10.8.1", + "version": "10.3.0", "license": "MIT", "homepage": "https://eslint.org", "repository": "eslint/eslint", @@ -340,7 +403,7 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/eslint@10.8.1", + "licenseFolder": "licenses/eslint@10.3.0", "sourcePackageDir": "apps/web/node_modules/eslint" }, { @@ -357,32 +420,21 @@ "sourcePackageDir": "node_modules/eslint-plugin-react-hooks" }, { - "name": "eventemitter3", - "version": "5.0.4", + "name": "framer-motion", + "version": "12.38.0", "license": "MIT", "homepage": null, - "repository": "git://github.com/primus/eventemitter3.git", - "description": "EventEmitter3 focuses on performance while maintaining a Node.js AND browser compatible interface.", + "repository": "https://github.com/motiondivision/motion/", + "description": "A simple and powerful JavaScript animation library", "files": [ - "LICENSE" + "LICENSE.md" ], - "licenseFolder": "licenses/eventemitter3@5.0.4", - "sourcePackageDir": "apps/web/node_modules/eventemitter3" - }, - { - "name": "fallow", - "version": "3.14.0", - "license": "MIT", - "homepage": "https://docs.fallow.tools", - "repository": "git+https://github.com/fallow-rs/fallow.git", - "description": "Codebase intelligence for TypeScript and JavaScript. Free static analysis of code and styles, optional paid runtime intelligence (Fallow Runtime). Quality, risk, architecture, dependencies, duplication, and design-system drift for humans, CI, and the agents writing your code. Zero-config framework support.", - "files": [], - "licenseFolder": "licenses/fallow@3.14.0", - "sourcePackageDir": "node_modules/fallow" + "licenseFolder": "licenses/framer-motion@12.38.0", + "sourcePackageDir": "apps/web/node_modules/framer-motion" }, { "name": "globals", - "version": "17.9.0", + "version": "17.6.0", "license": "MIT", "homepage": null, "repository": "sindresorhus/globals", @@ -390,12 +442,25 @@ "files": [ "license" ], - "licenseFolder": "licenses/globals@17.9.0", + "licenseFolder": "licenses/globals@17.6.0", "sourcePackageDir": "apps/web/node_modules/globals" }, + { + "name": "jsonc-parser", + "version": "3.3.1", + "license": "MIT", + "homepage": null, + "repository": "https://github.com/microsoft/node-jsonc-parser", + "description": "Scanner and parser for JSON with comments.", + "files": [ + "LICENSE.md" + ], + "licenseFolder": "licenses/jsonc-parser@3.3.1", + "sourcePackageDir": "node_modules/jsonc-parser" + }, { "name": "livekit-client", - "version": "2.21.0", + "version": "2.18.8", "license": "Apache-2.0", "homepage": null, "repository": "git@github.com:livekit/client-sdk-js.git", @@ -403,12 +468,12 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/livekit-client@2.21.0", + "licenseFolder": "licenses/livekit-client@2.18.8", "sourcePackageDir": "packages/call/node_modules/livekit-client" }, { "name": "lucide-react", - "version": "1.30.0", + "version": "1.14.0", "license": "ISC", "homepage": "https://lucide.dev", "repository": "https://github.com/lucide-icons/lucide.git", @@ -416,36 +481,12 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/lucide-react@1.30.0", - "sourcePackageDir": "apps/web/node_modules/lucide-react" - }, - { - "name": "motion", - "version": "13.0.0", - "license": "MIT", - "homepage": null, - "repository": "https://github.com/motiondivision/motion", - "description": "An animation library for JavaScript and React.", - "files": [ - "LICENSE.md" - ], - "licenseFolder": "licenses/motion@13.0.0", - "sourcePackageDir": "packages/chat/node_modules/motion" - }, - { - "name": "mtp", - "version": "0.2.0", - "license": "UNKNOWN", - "homepage": null, - "repository": null, - "description": "MTP TypeScript SDK", - "files": [], - "licenseFolder": "licenses/mtp@0.2.0", - "sourcePackageDir": "packages/mtp/node_modules/mtp" + "licenseFolder": "licenses/lucide-react@1.14.0", + "sourcePackageDir": "apps/tauri/node_modules/lucide-react" }, { "name": "prettier", - "version": "3.9.6", + "version": "3.8.3", "license": "MIT", "homepage": "https://prettier.io", "repository": "prettier/prettier", @@ -453,64 +494,51 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/prettier@3.9.6", + "licenseFolder": "licenses/prettier@3.8.3", "sourcePackageDir": "node_modules/prettier" }, + { + "name": "qrcode", + "version": "1.5.4", + "license": "MIT", + "homepage": "http://github.com/soldair/node-qrcode", + "repository": "git://github.com/soldair/node-qrcode.git", + "description": "QRCode / 2d Barcode api with both server side and client side support using canvas", + "files": [ + "license" + ], + "licenseFolder": "licenses/qrcode@1.5.4", + "sourcePackageDir": "apps/web/node_modules/qrcode" + }, { "name": "react", - "version": "19.2.8", + "version": "19.2.5", "license": "MIT", "homepage": "https://react.dev/", - "repository": "https://github.com/react/react.git", + "repository": "https://github.com/facebook/react.git", "description": "React is a JavaScript library for building user interfaces.", "files": [ "LICENSE" ], - "licenseFolder": "licenses/react@19.2.8", + "licenseFolder": "licenses/react@19.2.5", "sourcePackageDir": "apps/tauri/node_modules/react" }, { "name": "react-dom", - "version": "19.2.8", + "version": "19.2.5", "license": "MIT", "homepage": "https://react.dev/", - "repository": "https://github.com/react/react.git", + "repository": "https://github.com/facebook/react.git", "description": "React package for working with the DOM.", "files": [ "LICENSE" ], - "licenseFolder": "licenses/react-dom@19.2.8", + "licenseFolder": "licenses/react-dom@19.2.5", "sourcePackageDir": "apps/tauri/node_modules/react-dom" }, - { - "name": "react-is", - "version": "19.2.8", - "license": "MIT", - "homepage": "https://react.dev/", - "repository": "https://github.com/react/react.git", - "description": "Brand checking of React Elements.", - "files": [ - "LICENSE" - ], - "licenseFolder": "licenses/react-is@19.2.8", - "sourcePackageDir": "apps/web/node_modules/react-is" - }, - { - "name": "react-redux", - "version": "9.3.0", - "license": "MIT", - "homepage": "https://github.com/reduxjs/react-redux", - "repository": "github:reduxjs/react-redux", - "description": "Official React bindings for Redux", - "files": [ - "LICENSE.md" - ], - "licenseFolder": "licenses/react-redux@9.3.0", - "sourcePackageDir": "apps/web/node_modules/react-redux" - }, { "name": "recharts", - "version": "3.10.1", + "version": "3.8.1", "license": "MIT", "homepage": "https://github.com/recharts/recharts", "repository": "git+https://github.com/recharts/recharts.git", @@ -518,9 +546,22 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/recharts@3.10.1", + "licenseFolder": "licenses/recharts@3.8.1", "sourcePackageDir": "packages/call/node_modules/recharts" }, + { + "name": "shadcn", + "version": "4.6.0", + "license": "MIT", + "homepage": null, + "repository": "https://github.com/shadcn-ui/ui.git", + "description": "Add components to your apps.", + "files": [ + "LICENSE.md" + ], + "licenseFolder": "licenses/shadcn@4.6.0", + "sourcePackageDir": "apps/web/node_modules/shadcn" + }, { "name": "sonner", "version": "2.0.7", @@ -532,7 +573,20 @@ "LICENSE.md" ], "licenseFolder": "licenses/sonner@2.0.7", - "sourcePackageDir": "packages/notifications/node_modules/sonner" + "sourcePackageDir": "apps/web/node_modules/sonner" + }, + { + "name": "tailwind-merge", + "version": "3.5.0", + "license": "MIT", + "homepage": "https://github.com/dcastil/tailwind-merge", + "repository": "https://github.com/dcastil/tailwind-merge.git", + "description": "Merge Tailwind CSS classes without style conflicts", + "files": [ + "LICENSE.md" + ], + "licenseFolder": "licenses/tailwind-merge@3.5.0", + "sourcePackageDir": "apps/web/node_modules/tailwind-merge" }, { "name": "tailwind-scrollbar-hide", @@ -549,7 +603,7 @@ }, { "name": "tailwindcss", - "version": "4.3.3", + "version": "4.2.4", "license": "MIT", "homepage": "https://tailwindcss.com", "repository": "https://github.com/tailwindlabs/tailwindcss.git", @@ -557,7 +611,7 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/tailwindcss@4.3.3", + "licenseFolder": "licenses/tailwindcss@4.2.4", "sourcePackageDir": "apps/web/node_modules/tailwindcss" }, { @@ -584,11 +638,11 @@ "LICENSE.txt" ], "licenseFolder": "licenses/typescript@6.0.3", - "sourcePackageDir": "apps/electron/node_modules/typescript" + "sourcePackageDir": "apps/web/node_modules/typescript" }, { "name": "typescript-eslint", - "version": "8.66.0", + "version": "8.59.1", "license": "MIT", "homepage": "https://typescript-eslint.io/packages/typescript-eslint", "repository": "https://github.com/typescript-eslint/typescript-eslint.git", @@ -596,25 +650,12 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/typescript-eslint@8.66.0", + "licenseFolder": "licenses/typescript-eslint@8.59.1", "sourcePackageDir": "apps/web/node_modules/typescript-eslint" }, - { - "name": "use-sync-external-store", - "version": "1.6.0", - "license": "MIT", - "homepage": null, - "repository": "https://github.com/facebook/react.git", - "description": "Backwards compatible shim for React's useSyncExternalStore. Works with any React that supports hooks.", - "files": [ - "LICENSE" - ], - "licenseFolder": "licenses/use-sync-external-store@1.6.0", - "sourcePackageDir": "apps/web/node_modules/use-sync-external-store" - }, { "name": "vite", - "version": "8.2.1", + "version": "8.0.10", "license": "MIT", "homepage": "https://vite.dev", "repository": "git+https://github.com/vitejs/vite.git", @@ -622,25 +663,12 @@ "files": [ "LICENSE.md" ], - "licenseFolder": "licenses/vite@8.2.1", + "licenseFolder": "licenses/vite@8.0.10", "sourcePackageDir": "apps/web/node_modules/vite" }, - { - "name": "vitest", - "version": "4.1.10", - "license": "MIT", - "homepage": "https://vitest.dev", - "repository": "git+https://github.com/vitest-dev/vitest.git", - "description": "Next generation testing framework powered by Vite", - "files": [ - "LICENSE.md" - ], - "licenseFolder": "licenses/vitest@4.1.10", - "sourcePackageDir": "node_modules/vitest" - }, { "name": "zod", - "version": "4.4.3", + "version": "4.4.2", "license": "MIT", "homepage": "https://zod.dev", "repository": "git+https://github.com/colinhacks/zod.git", @@ -648,12 +676,12 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/zod@4.4.3", + "licenseFolder": "licenses/zod@4.4.2", "sourcePackageDir": "apps/web/node_modules/zod" }, { "name": "zustand", - "version": "5.0.14", + "version": "5.0.12", "license": "MIT", "homepage": "https://github.com/pmndrs/zustand", "repository": "git+https://github.com/pmndrs/zustand.git", @@ -661,8 +689,8 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/zustand@5.0.14", + "licenseFolder": "licenses/zustand@5.0.12", "sourcePackageDir": "packages/call/node_modules/zustand" } ] -} +} \ No newline at end of file diff --git a/licenses/typescript-eslint@8.66.0/LICENSE b/licenses/typescript-eslint@8.59.1/LICENSE similarity index 100% rename from licenses/typescript-eslint@8.66.0/LICENSE rename to licenses/typescript-eslint@8.59.1/LICENSE diff --git a/licenses/vite@8.2.1/LICENSE.md b/licenses/vite@8.0.10/LICENSE.md similarity index 96% rename from licenses/vite@8.2.1/LICENSE.md rename to licenses/vite@8.0.10/LICENSE.md index 6d24a5e..0aabee5 100644 --- a/licenses/vite@8.2.1/LICENSE.md +++ b/licenses/vite@8.0.10/LICENSE.md @@ -369,34 +369,6 @@ Repository: https://github.com/vitest-dev/vitest --------------------------------------- -## @voidzero-dev/vite-task-client -License: MIT -Repository: https://github.com/voidzero-dev/vite-task - -> MIT License -> -> Copyright (c) 2026-present, VoidZero Inc. -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - ## anymatch License: ISC By: Elan Shanker @@ -990,35 +962,6 @@ Repository: https://github.com/follow-redirects/follow-redirects --------------------------------------- -## fresh-import -License: MIT -By: sapphi-red -Repository: https://github.com/sapphi-red/fresh-import - -> MIT License -> -> Copyright (c) 2026 sapphi-red -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - ## generic-names License: MIT By: Alexey Litvinov @@ -1219,13 +1162,6 @@ Repository: https://github.com/micromatch/is-glob --------------------------------------- -## is-reference -License: MIT -By: Rich Harris -Repository: https://github.com/Rich-Harris/is-reference - ---------------------------------------- - ## isexe, which License: ISC By: Isaac Z. Schlueter @@ -1256,7 +1192,7 @@ Repository: https://github.com/lydell/js-tokens > The MIT License (MIT) > -> Copyright (c) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025 Simon Lydell +> Copyright (c) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024 Simon Lydell > > Permission is hereby granted, free of charge, to any person obtaining a copy > of this software and associated documentation files (the "Software"), to deal @@ -1281,7 +1217,7 @@ Repository: https://github.com/lydell/js-tokens ## launch-editor, launch-editor-middleware License: MIT By: Evan You -Repositories: https://github.com/vitejs/launch-editor, https://github.com/vitejs/launch-editor +Repositories: https://github.com/yyx990803/launch-editor, https://github.com/yyx990803/launch-editor > The MIT License (MIT) > @@ -2292,31 +2228,3 @@ Repository: https://github.com/websockets/ws > COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER > IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN > CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------- - -## zimmerframe -License: MIT -Repository: https://github.com/sveltejs/zimmerframe - -> MIT License -> -> Copyright (c) 2023 [these people](https://github.com/Rich-Harris/zimmerframe/graphs/contributors) -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. diff --git a/licenses/vitest@4.1.10/LICENSE.md b/licenses/vitest@4.1.10/LICENSE.md deleted file mode 100644 index d2883c9..0000000 --- a/licenses/vitest@4.1.10/LICENSE.md +++ /dev/null @@ -1,811 +0,0 @@ -# Vitest core license -Vitest is released under the MIT license: - -MIT License - -Copyright (c) 2021-Present VoidZero Inc. and Vitest contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -# Licenses of bundled dependencies -The published Vitest artifact additionally contains code with the following licenses: -BSD-3-Clause, ISC, MIT - -# Bundled dependencies: -## @antfu/install-pkg -License: MIT -By: Anthony Fu -Repository: git+https://github.com/antfu/install-pkg.git - -> MIT License -> -> Copyright (c) 2021 Anthony Fu -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - -## @bomb.sh/tab -License: MIT -By: Bombshell Authors -Repository: git+https://github.com/bombshell-dev/tab.git - ---------------------------------------- - -## @jridgewell/resolve-uri -License: MIT -By: Justin Ridgewell -Repository: https://github.com/jridgewell/resolve-uri - -> Copyright 2019 Justin Ridgewell -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in -> all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - -## @jridgewell/sourcemap-codec -License: MIT -By: Justin Ridgewell -Repository: git+https://github.com/jridgewell/sourcemaps.git - -> Copyright 2024 Justin Ridgewell -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in -> all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - -## @jridgewell/trace-mapping -License: MIT -By: Justin Ridgewell -Repository: git+https://github.com/jridgewell/sourcemaps.git - -> Copyright 2024 Justin Ridgewell -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in -> all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - -## @sinonjs/commons -License: BSD-3-Clause -Repository: git+https://github.com/sinonjs/commons.git - -> BSD 3-Clause License -> -> Copyright (c) 2018, Sinon.JS -> All rights reserved. -> -> Redistribution and use in source and binary forms, with or without -> modification, are permitted provided that the following conditions are met: -> -> * Redistributions of source code must retain the above copyright notice, this -> list of conditions and the following disclaimer. -> -> * Redistributions in binary form must reproduce the above copyright notice, -> this list of conditions and the following disclaimer in the documentation -> and/or other materials provided with the distribution. -> -> * Neither the name of the copyright holder nor the names of its -> contributors may be used to endorse or promote products derived from -> this software without specific prior written permission. -> -> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -> DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -> FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -> DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -> SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -> OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -> OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------- - -## @sinonjs/fake-timers -License: BSD-3-Clause -By: Christian Johansen -Repository: git+https://github.com/sinonjs/fake-timers.git - -> Copyright (c) 2010-2014, Christian Johansen, christian@cjohansen.no. All rights reserved. -> -> Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -> -> 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -> -> 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -> -> 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -> -> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------- - -## acorn -License: MIT -By: Marijn Haverbeke, Ingvar Stepanyan, Adrian Heine -Repository: https://github.com/acornjs/acorn.git - -> MIT License -> -> Copyright (C) 2012-2022 by various contributors (see AUTHORS) -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in -> all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -> THE SOFTWARE. - ---------------------------------------- - -## acorn-walk -License: MIT -By: Marijn Haverbeke, Ingvar Stepanyan, Adrian Heine -Repository: https://github.com/acornjs/acorn.git - -> MIT License -> -> Copyright (C) 2012-2020 by various contributors (see AUTHORS) -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in -> all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -> THE SOFTWARE. - ---------------------------------------- - -## birpc -License: MIT -By: Anthony Fu -Repository: git+https://github.com/antfu-collective/birpc.git - -> MIT License -> -> Copyright (c) 2021 Anthony Fu -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - -## cac -License: MIT -By: egoist -Repository: egoist/cac - -> The MIT License (MIT) -> -> Copyright (c) EGOIST <0x142857@gmail.com> (https://github.com/egoist) -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in -> all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -> THE SOFTWARE. - ---------------------------------------- - -## empathic -License: MIT -By: Luke Edwards -Repository: lukeed/empathic - -> MIT License -> -> Copyright (c) Luke Edwards (lukeed.com) -> -> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------- - -## flatted -License: ISC -By: Andrea Giammarchi -Repository: git+https://github.com/WebReflection/flatted.git - -> ISC License -> -> Copyright (c) 2018-2020, Andrea Giammarchi, @WebReflection -> -> Permission to use, copy, modify, and/or distribute this software for any -> purpose with or without fee is hereby granted, provided that the above -> copyright notice and this permission notice appear in all copies. -> -> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -> REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -> AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -> INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -> LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -> OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -> PERFORMANCE OF THIS SOFTWARE. - ---------------------------------------- - -## js-tokens -License: MIT -By: Simon Lydell -Repository: lydell/js-tokens - -> The MIT License (MIT) -> -> Copyright (c) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Simon Lydell -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in -> all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -> THE SOFTWARE. - ---------------------------------------- - -## kleur -License: MIT -By: Luke Edwards -Repository: lukeed/kleur - -> The MIT License (MIT) -> -> Copyright (c) Luke Edwards (lukeed.com) -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in -> all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -> THE SOFTWARE. - ---------------------------------------- - -## local-pkg -License: MIT -By: Anthony Fu -Repository: git+https://github.com/antfu-collective/local-pkg.git - -> MIT License -> -> Copyright (c) 2021 Anthony Fu -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - -## mime -License: MIT -By: Robert Kieffer -Repository: https://github.com/broofa/mime - -> MIT License -> -> Copyright (c) 2023 Robert Kieffer -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - -## mlly -License: MIT -Repository: unjs/mlly - -> MIT License -> -> Copyright (c) Pooya Parsa -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - -## package-manager-detector -License: MIT -By: Anthony Fu -Repository: git+https://github.com/antfu-collective/package-manager-detector.git - -> MIT License -> -> Copyright (c) 2020-PRESENT Anthony Fu -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - -## prompts -License: MIT -By: Terkel Gjervig -Repository: terkelg/prompts - -> MIT License -> -> Copyright (c) 2018 Terkel Gjervig Nielsen -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - -## quansync -License: MIT -By: Anthony Fu, 三咲智子 Kevin Deng -Repository: git+https://github.com/quansync-dev/quansync.git - -> MIT License -> -> Copyright (c) 2025-PRESENT Anthony Fu and Kevin Deng -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - -## sisteransi -License: MIT -By: Terkel Gjervig -Repository: https://github.com/terkelg/sisteransi - -> MIT License -> -> Copyright (c) 2018 Terkel Gjervig Nielsen -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - -## strip-literal -License: MIT -By: Anthony Fu -Repository: git+https://github.com/antfu/strip-literal.git - -> MIT License -> -> Copyright (c) 2022 Anthony Fu -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - -## tinyhighlight -License: MIT -Repository: git+https://github.com/tinylibs/tinyhighlight.git - -> # Tinyhighlight core license -> -> MIT License -> -> Copyright (c) 2023 Tinylibs -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. -> -> # Additionaly Tinyhighlight modifies code with the following licenses: -> -> MIT -> -> ## @babel/highlight -> -> MIT License -> -> Copyright (c) 2014-present Sebastian McKenzie and other contributors -> -> Permission is hereby granted, free of charge, to any person obtaining -> a copy of this software and associated documentation files (the -> "Software"), to deal in the Software without restriction, including -> without limitation the rights to use, copy, modify, merge, publish, -> distribute, sublicense, and/or sell copies of the Software, and to -> permit persons to whom the Software is furnished to do so, subject to -> the following conditions: -> -> The above copyright notice and this permission notice shall be -> included in all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -> -> ## @babel/helper-validator-identifier -> -> MIT License -> -> Copyright (c) 2014-present Sebastian McKenzie and other contributors -> -> Permission is hereby granted, free of charge, to any person obtaining -> a copy of this software and associated documentation files (the -> "Software"), to deal in the Software without restriction, including -> without limitation the rights to use, copy, modify, merge, publish, -> distribute, sublicense, and/or sell copies of the Software, and to -> permit persons to whom the Software is furnished to do so, subject to -> the following conditions: -> -> The above copyright notice and this permission notice shall be -> included in all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------- - -## type-detect -License: MIT -By: Jake Luer, Keith Cirkel, David Losert, Aleksey Shvayka, Lucas Fernandes da Costa, Grant Snodgrass, Jeremy Tice, Edward Betts, dvlsg, Amila Welihinda, Jake Champion, Miroslav Bajtoš -Repository: git+ssh://git@github.com/chaijs/type-detect.git - -> Copyright (c) 2013 Jake Luer (http://alogicalparadox.com) -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in -> all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -> THE SOFTWARE. - ---------------------------------------- - -## ufo -License: MIT -Repository: unjs/ufo - -> MIT License -> -> Copyright (c) Pooya Parsa -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - -## ws -License: MIT -By: Einar Otto Stangvik -Repository: git+https://github.com/websockets/ws.git - -> Copyright (c) 2011 Einar Otto Stangvik -> Copyright (c) 2013 Arnout Kazemier and contributors -> Copyright (c) 2016 Luigi Pinca and contributors -> -> Permission is hereby granted, free of charge, to any person obtaining a copy of -> this software and associated documentation files (the "Software"), to deal in -> the Software without restriction, including without limitation the rights to -> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -> the Software, and to permit persons to whom the Software is furnished to do so, -> subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licenses/zod@4.4.3/LICENSE b/licenses/zod@4.4.2/LICENSE similarity index 100% rename from licenses/zod@4.4.3/LICENSE rename to licenses/zod@4.4.2/LICENSE diff --git a/licenses/zustand@5.0.14/LICENSE b/licenses/zustand@5.0.12/LICENSE similarity index 100% rename from licenses/zustand@5.0.14/LICENSE rename to licenses/zustand@5.0.12/LICENSE diff --git a/mtp-type-maps b/mtp-type-maps deleted file mode 160000 index f4e45aa..0000000 --- a/mtp-type-maps +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f4e45aa3a3ad0e3c3a257f66857b904a1af7901c diff --git a/package.json b/package.json index 1975028..40e19a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tensamin", - "version": "0.0.11", + "version": "0.0.1", "private": true, "workspaces": [ "packages/*", @@ -8,44 +8,44 @@ ], "type": "module", "scripts": { - "format": "pnpm exec prettier --write .", - "lint": "node utils/scripts/lint-packages.ts", - "check": "fallow && pnpm run lint", - "copy-licenses": "node utils/scripts/copy-licenses.ts", - "copy-releases": "node utils/scripts/copy-releases.ts", - "pre-build": "pnpm run lint && pnpm run build:packages", - "build:apps": "pnpm run copy-licenses && pnpm run pre-build && pnpm run build:mobile && pnpm run build:desktop && pnpm run copy-releases", - "build:packages": "node utils/scripts/build-packages.ts", - "update:packages": "node utils/scripts/update-packages.ts", - "dev": "cd apps/web && pnpm dev", - "build:web": "cd apps/web && pnpm run build", - "preview:web": "cd apps/web && pnpm run preview", - "dev:mobile": "pnpm run delete:mobile || true && cd apps/tauri && pnpm dev:mobile && pnpm run delete:mobile", - "build:mobile": "cd apps/tauri && pnpm run build:mobile", - "start-adb:mobile": "cd apps/tauri && pnpm run start-adb:mobile", - "dev:desktop": "cd apps/electron && pnpm run dev", - "build:desktop": "cd apps/electron && pnpm run package", - "delete:mobile": "nix develop .#tauri --command node apps/tauri/scripts/delete-mobile.ts", - "update-submodules": "git submodule update --remote --force --recursive" + "format": "bunx prettier --write .", + "lint": "bun scripts/lint-packages.ts", + "copy-licenses": "bun scripts/copy-licenses.ts", + "copy-releases": "bun scripts/copy-releases.ts", + "pre-build": "bun run lint && bun run build:packages", + "build:apps": "bun run copy-licenses && bun run pre-build && bun run build:mobile && bun run build:desktop && bun --bun run copy-releases", + "build:packages": "bun scripts/build-packages.ts", + "update:packages": "bun scripts/update-packages.ts", + "dev:web": "cd apps/web && bun dev", + "build:web": "cd apps/web && bun run build", + "preview:web": "cd apps/web && bun run preview", + "dev:mobile": "cd apps/tauri && bun dev:mobile", + "build:mobile": "cd apps/tauri && bun run build:mobile", + "dev:desktop": "cd apps/tauri && bun dev:desktop", + "build:desktop": "cd apps/tauri && bun run build:desktop", + "delete:mobile": "cd apps/tauri && nix develop --command adb uninstall net.tensamin.client" }, "devDependencies": { "@eslint/js": "^10.0.1", - "@types/node": "^26.2.0", - "@types/react": "^19.2.18", - "@types/react-dom": "^19.2.4", - "@typescript-eslint/parser": "^8.67.0", - "eslint": "^10.8.1", + "@types/node": "^25.6.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@typescript-eslint/parser": "^8.59.1", + "eslint": "^10.2.1", "eslint-plugin-react-hooks": "^7.1.1", - "fallow": "^3.17.0", - "globals": "^17.11.0", - "prettier": "^3.9.6", + "globals": "^17.5.0", + "prettier": "^3.8.3", "typescript": "^6.0.3", - "typescript-eslint": "^8.67.0", - "vitest": "^4.1.11" + "typescript-eslint": "^8.59.1", + "jsonc-parser": "^3.3.1" + }, + "overrides": { + "@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.31.tar.gz", + "@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.17.tar.gz" }, "dependencies": { - "@methanium/ui": "https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz", - "mtp": "https://git.methanium.net/methanium/mtp/releases/download/0.3.0-b331b9f6a3/mtp-0.3.0.tgz", - "sonner": "^2.0.8" + "@tensamin/ttp-core": "*", + "@tensamin/ui": "*", + "sonner": "^2.0.7" } } diff --git a/packages/cache/package.json b/packages/cache/package.json deleted file mode 100644 index 2a69aa9..0000000 --- a/packages/cache/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@tensamin/cache", - "private": true, - "version": "0.0.0", - "type": "module", - "exports": { - ".": "./src/index.ts", - "./helpers": "./src/helpers.ts", - "./schemas": "./src/schemas.ts", - "./sync": "./src/sync.tsx" - }, - "scripts": { - "format": "pnpm exec prettier --write .", - "lint": "eslint src", - "build": "tsc -p tsconfig.json --noEmit" - }, - "dependencies": { - "@tensamin/mtp": "workspace:*", - "@tensamin/shared": "workspace:*", - "@tensamin/storage": "workspace:*", - "react": "^19.2.8", - "zod": "^4.4.3" - } -} diff --git a/packages/cache/src/helpers.ts b/packages/cache/src/helpers.ts deleted file mode 100644 index b7ec58a..0000000 --- a/packages/cache/src/helpers.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { CachedMessage, ConversationWindow } from "./schemas"; - -export function trimMessages( - messages: readonly CachedMessage[], - limit: number, -): CachedMessage[] { - return [...messages] - .sort((a, b) => b.SendTime - a.SendTime) - .slice(0, limit) - .sort((a, b) => a.SendTime - b.SendTime); -} - -export function replaceConversation( - windows: readonly ConversationWindow[], - replacement: ConversationWindow, -): ConversationWindow[] { - return [ - replacement, - ...windows.filter((item) => item.UserId !== replacement.UserId), - ]; -} - -export function selectConversationWindows( - windows: readonly ConversationWindow[], - limit: number, -): ConversationWindow[] { - return [...windows] - .sort((a, b) => b.LastMessageAt - a.LastMessageAt || a.UserId - b.UserId) - .slice(0, limit); -} diff --git a/packages/cache/src/index.ts b/packages/cache/src/index.ts deleted file mode 100644 index 135d85b..0000000 --- a/packages/cache/src/index.ts +++ /dev/null @@ -1,269 +0,0 @@ -import type { z } from "zod"; -import { storageDefaults } from "@tensamin/shared/data"; -import { - deleteDatabaseEntry, - getDatabaseEntry, - listDatabaseEntries, - setDatabaseEntry, -} from "@tensamin/shared/indexedDb"; -import { - replaceConversation, - selectConversationWindows, - trimMessages, -} from "./helpers"; -import { - accountIdSchema, - chatDraftSchema, - contactsSchema, - conversationWindowSchema, - userProfileSchema, - type ChatDraft, - type Contact, - type ConversationWindow, - type UserProfile, -} from "./schemas"; - -export * from "./helpers"; -export * from "./schemas"; - -type CacheStore = "contacts" | "profiles" | "conversations" | "drafts"; - -export interface SecureValueCodec { - encode(value: unknown): unknown | Promise; - decode(value: unknown): unknown | Promise; -} - -export interface CacheOptions { - codec?: SecureValueCodec; - contacts?: number; - messagesPerChat?: number; -} - -export interface CacheLifecycle { - clearAccount(): Promise; - close(): Promise; -} - -const identityCodec: SecureValueCodec = { - encode: (value) => value, - decode: (value) => value, -}; - -export function createCache(accountId: string, options: CacheOptions = {}) { - const account = accountIdSchema.parse(accountId); - const codec = options.codec ?? identityCodec; - const getLimits = async () => { - const [storedContacts, storedMessagesPerChat] = await Promise.all([ - getDatabaseEntry("storage", "cache_contacts"), - getDatabaseEntry("storage", "cache_messages_per_chat"), - ]); - return { - contacts: Math.max( - 0, - Math.floor( - options.contacts ?? - (typeof storedContacts === "number" - ? storedContacts - : storageDefaults.cache_contacts), - ), - ), - messagesPerChat: Math.max( - 0, - Math.floor( - options.messagesPerChat ?? - (typeof storedMessagesPerChat === "number" - ? storedMessagesPerChat - : storageDefaults.cache_messages_per_chat), - ), - ), - }; - }; - const prefix = `${account}:`; - const storedPrefix = (store: CacheStore) => `${store}:${prefix}`; - const storedKey = (store: CacheStore, key: string) => - `${storedPrefix(store)}${key}`; - const entries = async (store: CacheStore) => { - const storePrefix = storedPrefix(store); - return (await listDatabaseEntries("cache", storePrefix)).map( - ([key, value]) => [key.slice(storePrefix.length), value] as const, - ); - }; - let closed = false; - const ensureOpen = () => { - if (closed) throw new Error("Cache is closed"); - }; - const read = async ( - store: CacheStore, - key: string, - schema: z.ZodType, - ) => { - ensureOpen(); - const value = await getDatabaseEntry("cache", storedKey(store, key)); - return value === undefined - ? undefined - : schema.parse(await codec.decode(value)); - }; - const write = async ( - store: CacheStore, - key: string, - schema: z.ZodType, - value: T, - ) => { - ensureOpen(); - await setDatabaseEntry( - "cache", - storedKey(store, key), - await codec.encode(schema.parse(value)), - ); - }; - const remove = async (store: CacheStore, key: string) => { - ensureOpen(); - await deleteDatabaseEntry("cache", storedKey(store, key)); - }; - const listConversations = async () => { - ensureOpen(); - const storedEntries = await entries("conversations"); - const windows = await Promise.all( - storedEntries.map(async ([, value]) => - conversationWindowSchema.parse(await codec.decode(value)), - ), - ); - const { contacts } = await getLimits(); - return selectConversationWindows(windows, contacts); - }; - - return { - contacts: { - get: () => read("contacts", "authoritative", contactsSchema), - replace: (contacts: Contact[]) => - write("contacts", "authoritative", contactsSchema, contacts), - clear: () => remove("contacts", "authoritative"), - }, - profiles: { - get: (userId: number) => - read("profiles", String(userId), userProfileSchema), - put: (profile: UserProfile) => - write("profiles", String(profile.UserId), userProfileSchema, profile), - delete: (userId: number) => remove("profiles", String(userId)), - }, - conversations: { - list: listConversations, - get: (userId: number) => - read("conversations", String(userId), conversationWindowSchema), - replace: async (window: ConversationWindow) => { - const { contacts, messagesPerChat } = await getLimits(); - const candidate = conversationWindowSchema.parse({ - ...window, - Messages: trimMessages(window.Messages, messagesPerChat), - }); - const selected = selectConversationWindows( - replaceConversation(await listConversations(), candidate), - contacts, - ); - await Promise.all( - selected.map((item) => - write( - "conversations", - String(item.UserId), - conversationWindowSchema, - item, - ), - ), - ); - const retained = new Set(selected.map((item) => item.UserId)); - const storedEntries = await entries("conversations"); - await Promise.all( - storedEntries.flatMap(([key]) => { - const userId = Number(key); - return retained.has(userId) - ? [] - : [deleteDatabaseEntry("cache", storedKey("conversations", key))]; - }), - ); - }, - replaceSelected: async (windows: ConversationWindow[]) => { - const { contacts, messagesPerChat } = await getLimits(); - const selected = selectConversationWindows( - windows.map((window) => ({ - ...window, - Messages: trimMessages(window.Messages, messagesPerChat), - })), - contacts, - ); - await Promise.all( - selected.map((item) => - write( - "conversations", - String(item.UserId), - conversationWindowSchema, - item, - ), - ), - ); - const retained = new Set(selected.map((item) => item.UserId)); - const storedEntries = await entries("conversations"); - await Promise.all( - storedEntries.flatMap(([key]) => { - const userId = Number(key); - return retained.has(userId) - ? [] - : [deleteDatabaseEntry("cache", storedKey("conversations", key))]; - }), - ); - }, - prune: async () => { - const { messagesPerChat } = await getLimits(); - const windows = await listConversations(); - await Promise.all( - windows.map((window) => - write( - "conversations", - String(window.UserId), - conversationWindowSchema, - { - ...window, - Messages: trimMessages(window.Messages, messagesPerChat), - }, - ), - ), - ); - const retained = new Set(windows.map((window) => window.UserId)); - const storedEntries = await entries("conversations"); - await Promise.all( - storedEntries.flatMap(([key]) => - retained.has(Number(key)) - ? [] - : [deleteDatabaseEntry("cache", storedKey("conversations", key))], - ), - ); - }, - delete: (userId: number) => remove("conversations", String(userId)), - }, - drafts: { - get: (userId: number) => read("drafts", String(userId), chatDraftSchema), - put: (userId: number, draft: ChatDraft) => - write("drafts", String(userId), chatDraftSchema, draft), - delete: (userId: number) => remove("drafts", String(userId)), - }, - clearAccount: async () => { - ensureOpen(); - await Promise.all( - ( - ["contacts", "profiles", "conversations", "drafts"] as CacheStore[] - ).map(async (store) => { - const storedEntries = await entries(store); - await Promise.all( - storedEntries.map(([key]) => - deleteDatabaseEntry("cache", storedKey(store, key)), - ), - ); - }), - ); - }, - close: async () => { - closed = true; - }, - }; -} - -export type Cache = ReturnType; diff --git a/packages/cache/src/schemas.ts b/packages/cache/src/schemas.ts deleted file mode 100644 index 152b471..0000000 --- a/packages/cache/src/schemas.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { mtp } from "@tensamin/shared/data"; -import { z } from "zod"; - -export const accountIdSchema = z.string().min(1); -export const contactSchema = z.object({ - LastMessageAt: z.number(), - UserId: z.number(), - LastMessage: z - .object({ Content: z.base64(), SenderId: z.number() }) - .optional(), - Messages: z.array(mtp.MessageGet.response), -}); -export const contactsSchema = z.array(contactSchema); -export const userProfileSchema = mtp.GetUserData.response; - -// Content remains the protocol base64 ciphertext. This package never decrypts messages. -export const cachedMessageSchema = mtp.MessageGet.response; -export const conversationWindowSchema = z.object({ - UserId: z.number(), - LastMessageAt: z.number(), - Messages: z.array(cachedMessageSchema), -}); -// Unlike cached messages, draft content is plaintext and must use a secure codec. -export const chatDraftSchema = z.object({ - Content: z.string(), - ReplyId: z.number().optional(), -}); - -export type Contact = z.infer; -export type UserProfile = z.infer; -export type CachedMessage = z.infer; -export type ConversationWindow = z.infer; -export type ChatDraft = z.infer; diff --git a/packages/cache/src/sync.tsx b/packages/cache/src/sync.tsx deleted file mode 100644 index 8928e23..0000000 --- a/packages/cache/src/sync.tsx +++ /dev/null @@ -1,338 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from "react"; -import { - createCache, - type CachedMessage, - type UserProfile, -} from "@tensamin/cache"; -import { useMTP, type MTPExchange, type ProtocolMessage } from "@tensamin/mtp"; -import { useStorage } from "@tensamin/storage/context"; -import { secureValueCodec } from "@tensamin/storage/secure"; - -function isError(message: ProtocolMessage) { - return message.type.startsWith("Error"); -} - -export function removeMissingContactSnapshots( - contacts: T[], - missingUserIds: readonly number[], -): T[] { - const missing = new Set(missingUserIds); - return contacts.filter((contact) => !missing.has(contact.UserId)); -} - -export default function CacheSync() { - const { addInterceptor, contextReady, freshContacts, subscribe } = useMTP(); - const { load } = useStorage(); - const [accountId, setAccountId] = useState(0); - const queueRef = useRef(Promise.resolve()); - - useEffect(() => { - void load("user_id").then(setAccountId); - }, [load]); - - const enqueue = useCallback((operation: () => Promise) => { - const next = queueRef.current.then(operation); - queueRef.current = next.catch(() => undefined); - return next; - }, []); - - const secureCache = useCallback( - () => - createCache(String(accountId), { - codec: secureValueCodec, - }), - [accountId], - ); - - const removeMissingContacts = useCallback( - async (userIds: number[]) => { - if (userIds.length === 0) return; - const cache = secureCache(); - const contacts = await cache.contacts.get(); - if (!contacts) return; - const remaining = removeMissingContactSnapshots(contacts, userIds); - if (remaining.length === contacts.length) return; - await cache.contacts.replace(remaining); - await cache.conversations.replaceSelected(remaining); - }, - [secureCache], - ); - - const replaceMessage = useCallback( - async ( - partnerId: number, - sendTime: number, - edit: Partial, - ) => { - const cache = secureCache(); - const window = await cache.conversations.get(partnerId); - if (!window) return; - await cache.conversations.replace({ - ...window, - Messages: window.Messages.map((message) => - message.SendTime === sendTime ? { ...message, ...edit } : message, - ), - }); - }, - [secureCache], - ); - - const insertMessage = useCallback( - async (partnerId: number, message: CachedMessage) => { - const cache = secureCache(); - const window = await cache.conversations.get(partnerId); - await cache.conversations.replace({ - UserId: partnerId, - LastMessageAt: Math.max(window?.LastMessageAt ?? 0, message.SendTime), - Messages: [ - ...(window?.Messages ?? []).filter( - (cached) => cached.SendTime !== message.SendTime, - ), - message, - ], - }); - }, - [secureCache], - ); - - const removeMessage = useCallback( - async (partnerId: number, sendTime: number) => { - const cache = secureCache(); - const window = await cache.conversations.get(partnerId); - if (!window) return; - await cache.conversations.replace({ - ...window, - Messages: window.Messages.filter( - (message) => message.SendTime !== sendTime, - ), - }); - }, - [secureCache], - ); - - useEffect(() => { - if (!accountId || !contextReady) return; - void enqueue(async () => { - const cache = secureCache(); - await cache.contacts.replace(freshContacts); - await cache.conversations.replaceSelected( - freshContacts.map((contact) => ({ - UserId: contact.UserId, - LastMessageAt: contact.LastMessageAt, - Messages: contact.Messages, - })), - ); - }); - }, [accountId, contextReady, enqueue, freshContacts, secureCache]); - - const synchronizeExchange = useCallback( - async ({ type, data, response }: MTPExchange) => { - if (!accountId || isError(response)) return; - const request = (data ?? {}) as Record; - const result = response.data as Record; - - if (type === "GetStates" && Array.isArray(result.MissingUserIds)) { - await removeMissingContacts( - result.MissingUserIds.filter( - (userId): userId is number => typeof userId === "number", - ), - ); - return; - } - - if (type === "GetUserData") { - await createCache(String(accountId)).profiles.put( - result as unknown as UserProfile, - ); - return; - } - - if (type === "MessagesGet" && Number(request.Offset) === 0) { - const partnerId = Number(request.UserId); - const messages = result.Messages as CachedMessage[]; - const cache = secureCache(); - const previous = await cache.conversations.get(partnerId); - await cache.conversations.replace({ - UserId: partnerId, - LastMessageAt: Math.max( - previous?.LastMessageAt ?? 0, - ...messages.map((message) => message.SendTime), - ), - // This replacement is authoritative: absent server messages are deleted. - Messages: messages, - }); - return; - } - - if (type === "MessageSend") { - const partnerId = Number(request.ReceiverId); - await insertMessage(partnerId, { - Content: String(request.Content), - Files: request.Files as CachedMessage["Files"], - MessageState: "sent", - SenderId: accountId, - SendTime: Number(request.SendTime), - ReplyId: request.ReplyId ? Number(request.ReplyId) : undefined, - }); - return; - } - - if (type === "MessageEdit") { - await replaceMessage( - Number(request.ChatPartnerId), - Number(request.SendTime), - { Content: String(request.Content), Edited: true }, - ); - return; - } - - if (type === "MessageDelete") { - await removeMessage( - Number(request.ChatPartnerId), - Number(request.SendTime), - ); - return; - } - - if (type === "MessageReactionAdd" || type === "MessageReactionRemove") { - const partnerId = Number(request.ChatPartnerId); - const sendTime = Number(request.SendTime); - const reaction = String(request.Reaction); - const cache = secureCache(); - const window = await cache.conversations.get(partnerId); - const message = window?.Messages.find( - (candidate) => candidate.SendTime === sendTime, - ); - if (!message) return; - const reactions = (message.Reactions ?? []).filter( - (candidate) => - candidate.SenderId !== accountId || candidate.Reaction !== reaction, - ); - if (type === "MessageReactionAdd") { - reactions.push({ SenderId: accountId, Reaction: reaction }); - } - await replaceMessage(partnerId, sendTime, { Reactions: reactions }); - return; - } - - if (type === "MessageState") { - await replaceMessage( - Number(result.ChatPartnerId), - Number(result.SendTime), - { - MessageState: result.MessageState as CachedMessage["MessageState"], - }, - ); - return; - } - }, - [ - accountId, - insertMessage, - removeMissingContacts, - removeMessage, - replaceMessage, - secureCache, - ], - ); - - useEffect(() => { - if (!accountId) return; - return addInterceptor((exchange) => - enqueue(() => synchronizeExchange(exchange)), - ); - }, [accountId, addInterceptor, enqueue, synchronizeExchange]); - - const synchronizePush = useCallback( - async (message: ProtocolMessage) => { - if (!accountId || isError(message)) return; - const data = message.data as Record; - if (message.type === "GetStates" && Array.isArray(data.MissingUserIds)) { - await removeMissingContacts( - data.MissingUserIds.filter( - (userId): userId is number => typeof userId === "number", - ), - ); - return; - } - if (message.type === "MessageLive") { - await insertMessage( - Number(data.SenderId), - data.Message as CachedMessage, - ); - return; - } - if (message.type === "MessageEditLive") { - await replaceMessage( - Number(data.ChatPartnerId), - Number(data.SendTime), - { Content: String(data.Content), Edited: true }, - ); - return; - } - if (message.type === "MessageDeleteLive") { - const deleteData = message.data as { - ChatPartnerId: number; - SendTime: number; - }; - await removeMessage(deleteData.ChatPartnerId, deleteData.SendTime); - return; - } - if (message.type === "MessageState") { - await replaceMessage( - Number(data.ChatPartnerId), - Number(data.SendTime), - { MessageState: data.MessageState as CachedMessage["MessageState"] }, - ); - return; - } - if (message.type === "MessageReactionLive") { - const partnerId = Number(data.ChatPartnerId); - const sendTime = Number(data.SendTime); - const senderId = Number(data.SenderId); - const reaction = String(data.Reaction); - const cache = secureCache(); - const window = await cache.conversations.get(partnerId); - const target = window?.Messages.find( - (candidate) => candidate.SendTime === sendTime, - ); - if (!window || !target) return; - - const reactions = (target.Reactions ?? []).filter( - (candidate) => - candidate.SenderId !== senderId || candidate.Reaction !== reaction, - ); - if (data.Accepted === true) { - reactions.push({ SenderId: senderId, Reaction: reaction }); - } - await replaceMessage(partnerId, sendTime, { Reactions: reactions }); - } - }, - [ - accountId, - insertMessage, - removeMessage, - removeMissingContacts, - replaceMessage, - secureCache, - ], - ); - - useEffect(() => { - if (!accountId || !contextReady) return; - const handleMessage = (message: ProtocolMessage) => { - void enqueue(() => synchronizePush(message)); - }; - const unsubscribers = [ - subscribe("GetStates", handleMessage), - subscribe("MessageLive", handleMessage), - subscribe("MessageEditLive", handleMessage), - subscribe("MessageDeleteLive", handleMessage), - subscribe("MessageState", handleMessage), - subscribe("MessageReactionLive", handleMessage), - ]; - return () => unsubscribers.forEach((unsubscribe) => unsubscribe()); - }, [accountId, contextReady, enqueue, subscribe, synchronizePush]); - - return null; -} diff --git a/packages/cache/tsconfig.json b/packages/cache/tsconfig.json deleted file mode 100644 index 2206f33..0000000 --- a/packages/cache/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "jsx": "react-jsx", - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "skipLibCheck": true, - "noEmit": true, - "lib": ["ES2022", "DOM"] - }, - "include": ["src"] -} diff --git a/packages/call/package.json b/packages/call/package.json index 7171f50..c60a47d 100644 --- a/packages/call/package.json +++ b/packages/call/package.json @@ -5,34 +5,37 @@ "type": "module", "exports": { "./store": "./src/store.tsx", - "./speakingState": "./src/speakingState.ts", "./screen": "./src/screen.tsx", "./utils": "./src/utils.ts", - "./sidebarBox": "./src/components/sidebarBox.tsx", - "./popout": "./src/components/popout.tsx" + "./sidebarBox": "./src/components/sidebarBox.tsx" }, "scripts": { - "format": "pnpm exec prettier --write .", + "format": "bunx prettier --write .", "lint": "eslint src", "build": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@livekit/components-react": "^2.9.23", - "@methanium/ui": "*", - "@tanstack/react-router": "^1.170.21", + "@livekit/components-react": "^2.9.20", + "@tanstack/react-query": "^5.100.7", + "@tanstack/react-router": "^1.169.1", + "@tanstack/react-virtual": "^3.13.24", + "@tauri-apps/api": "^2", "@tensamin/crypto": "workspace:*", - "@tensamin/mtp": "workspace:*", + "@tensamin/markdown": "workspace:*", "@tensamin/shared": "workspace:*", "@tensamin/storage": "workspace:*", + "@tensamin/tauri": "workspace:*", + "@tensamin/ttp": "workspace:*", + "@tensamin/ui": "*", "@tensamin/user": "workspace:*", - "deepfilternet3-noise-filter": "1.3.0", - "livekit-client": "^2.21.0", - "lucide-react": "^1.29.0", - "mtp": "*", - "react": "^19.2.8", - "react-dom": "^19.2.8", - "recharts": "^3.10.1", - "zod": "^4.4.3", - "zustand": "^5.0.14" + "deepfilternet3-noise-filter": "^1.2.1", + "livekit-client": "^2.18.8", + "lucide-react": "^1.14.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "recharts": "^3.8.1", + "sonner": "^2.0.7", + "zod": "^4.3.6", + "zustand": "^5.0.8" } } diff --git a/packages/call/src/components/actions.tsx b/packages/call/src/components/actions.tsx index 7856d7e..0aa7220 100644 --- a/packages/call/src/components/actions.tsx +++ b/packages/call/src/components/actions.tsx @@ -5,13 +5,11 @@ import { Tooltip, TooltipContent, TooltipTrigger, - useIsMobile, - cn, -} from "@methanium/ui"; +} from "@tensamin/ui"; import { useEffect, useState } from "react"; import MuteButton from "./buttons/mute"; import DeafButton from "./buttons/deaf"; -import MediaShareButton from "./buttons/mediaShare"; +import ScreenshareButton from "./buttons/screenshare"; import LeaveButton from "./buttons/leave"; import { setCallIsPopout, @@ -30,10 +28,9 @@ import { SquareArrowOutDownLeft, SquareArrowOutUpRight, } from "lucide-react"; -import { useStorage } from "@tensamin/storage/context"; export default function Actions() { - const sharedClasses = "w-14! h-10!"; + const sharedClasses = "w-14 h-10"; const sharedIconSize = 15; const view = useCall((state) => state.view); const focusedParticipantId = useCall((state) => state.focusedParticipantId); @@ -45,12 +42,6 @@ export default function Actions() { focusedParticipantId != null && watchedStreamParticipantIds.includes(focusedParticipantId); - const { load } = useStorage(); - const [ownId, setOwnId] = useState(0); - useEffect(() => { - load("user_id").then(setOwnId); - }, [load]); - // Fullscreen stuff const callIsPopout = useCall((state) => state.callIsPopout); const callIsFullscreen = useCall((state) => state.callIsFullscreen); @@ -62,13 +53,10 @@ export default function Actions() { }, [screenRef]); const toggleFullscreen = async () => { - const screen = screenRef?.current; - const fullscreenDocument = screen?.ownerDocument ?? document; - if (callIsFullscreen) { - await fullscreenDocument.exitFullscreen().catch(() => undefined); + await document.exitFullscreen().catch(() => undefined); } else { - await screen?.requestFullscreen().catch(() => undefined); + await screenRef?.current?.requestFullscreen().catch(() => undefined); } triggerCallLayoutCalculation(); @@ -79,45 +67,34 @@ export default function Actions() { (state) => state.usersInFocusedViewHidden, ); - const isMobile = useIsMobile(); - return ( -
- {!isMobile && ( -
- {view === "focused" && ( - - ( - - )} - /> - - Hide users - - - )} -
- )} +
+
+ {view === "focused" && ( + + + setUsersInFocusedViewHidden(!usersInFocusedViewHidden) + } + variant="link" + className="w-11 h-11 p-0! ml-3 text-foreground" + > + {usersInFocusedViewHidden ? ( + + ) : ( + + )} + + } + /> + + Hide users + + + )} +
- - {isWatchingFocusedStream && focusedParticipantId !== ownId ? ( + {isWatchingFocusedStream ? ( ( + render={ - )} + } /> Stop watching @@ -178,58 +151,50 @@ export default function Actions() { )} - {!isMobile && ( -
- - ( - - )} - /> - - Popout - - - - ( - - )} - /> - - Fullscreen - - -
- )} +
+ + setCallIsPopout(!callIsPopout)} + variant="link" + className="w-11 h-11 p-0! text-foreground" + > + {callIsPopout ? ( + + ) : ( + + )} + + } + /> + + Popout + + + + { + void toggleFullscreen(); + }} + variant="link" + className="w-11 h-11 p-0! mr-3 text-foreground" + > + {callIsFullscreen ? ( + + ) : ( + + )} + + } + /> + + Fullscreen + + +
); } diff --git a/packages/call/src/components/buttons/deaf.tsx b/packages/call/src/components/buttons/deaf.tsx index 417e3f4..42dd3f4 100644 --- a/packages/call/src/components/buttons/deaf.tsx +++ b/packages/call/src/components/buttons/deaf.tsx @@ -1,18 +1,18 @@ -import { Button } from "@methanium/ui"; +import { Button, Tooltip, TooltipContent, TooltipTrigger } from "@tensamin/ui"; import { toggleDeaf, useCall } from "../../store"; import { HeadphoneOff, Headphones } from "lucide-react"; -import { - type CallButtonProps, - iconScale, - withButtonTooltip, -} from "./tooltipButton"; export default function DeafButton({ className, iconSize, tooltip, portalContainer, -}: CallButtonProps) { +}: { + className?: string; + iconSize?: number; + tooltip?: string; + portalContainer?: HTMLElement; +}) { const deaf = useCall((state) => state.deaf); const button = ( @@ -22,12 +22,27 @@ export default function DeafButton({ className={className} > {deaf ? ( - + ) : ( - + )} ); - return withButtonTooltip(button, tooltip, portalContainer); + if (!tooltip) { + return button; + } + + return ( + + + + {tooltip} + + + ); } diff --git a/packages/call/src/components/buttons/invite.tsx b/packages/call/src/components/buttons/invite.tsx index d49a643..d3a6d8e 100644 --- a/packages/call/src/components/buttons/invite.tsx +++ b/packages/call/src/components/buttons/invite.tsx @@ -7,7 +7,7 @@ import { Tooltip, TooltipContent, TooltipTrigger, -} from "@methanium/ui"; +} from "@tensamin/ui"; import Wrapper from "@tensamin/user/wrapper"; import { Mail } from "lucide-react"; import { sendCallInvite, useCall } from "../../store"; @@ -38,13 +38,13 @@ export default function InviteButton({ render={ tooltip ? ( ( - - )} + } /> ) : (
} component={(user) => ( )} /> diff --git a/packages/call/src/components/buttons/leave.tsx b/packages/call/src/components/buttons/leave.tsx index b7e63de..364b4be 100644 --- a/packages/call/src/components/buttons/leave.tsx +++ b/packages/call/src/components/buttons/leave.tsx @@ -1,18 +1,18 @@ import { LeaveIcon } from "@livekit/components-react"; -import { Button } from "@methanium/ui"; +import { Button, Tooltip, TooltipContent, TooltipTrigger } from "@tensamin/ui"; import { disconnect, useCall } from "../../store"; -import { - type CallButtonProps, - iconScale, - withButtonTooltip, -} from "./tooltipButton"; export default function LeaveButton({ className, iconSize, tooltip, portalContainer, -}: CallButtonProps) { +}: { + className?: string; + iconSize?: number; + tooltip?: string; + portalContainer?: HTMLElement; +}) { const state = useCall((store) => store.state); const button = ( @@ -22,9 +22,20 @@ export default function LeaveButton({ disabled={state === "closing" || state === "closed"} variant="destructive" > - + ); - return withButtonTooltip(button, tooltip, portalContainer); + if (!tooltip) { + return button; + } + + return ( + + + + {tooltip} + + + ); } diff --git a/packages/call/src/components/buttons/mediaShare.tsx b/packages/call/src/components/buttons/mediaShare.tsx deleted file mode 100644 index 510fbef..0000000 --- a/packages/call/src/components/buttons/mediaShare.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import { useEffect, useState } from "react"; -import { - Button, - Popover, - PopoverContent, - PopoverTrigger, - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@methanium/ui"; -import { MonitorDot, ScreenShare } from "lucide-react"; -import { toast } from "@tensamin/shared/log"; -import { - startScreenShare, - stopCameraShare, - stopScreenShare, - useCall, -} from "../../store"; -import { getMediaShareAdapter, type MediaShareKind } from "../../mediaShare"; -import MediaShareDialog from "../mediaShareDialog"; - -export default function MediaShareButton({ - className, - iconSize, - tooltip, - defaultPortal, -}: { - className?: string; - iconSize?: number; - tooltip?: string; - defaultPortal?: boolean; -}) { - const isScreensharing = useCall((state) => state.screenShareEnabled); - const cameraEnabled = useCall((state) => state.cameraEnabled); - const screenRef = useCall((state) => state.screenRef); - const [portalContainer, setPortalContainer] = useState(); - const [dialogKind, setDialogKind] = useState(null); - const [menuOpen, setMenuOpen] = useState(false); - - useEffect(() => { - if (!defaultPortal) setPortalContainer(screenRef?.current ?? undefined); - }, [screenRef, defaultPortal]); - - async function beginScreenShare() { - try { - const capabilities = await getMediaShareAdapter().getCapabilities(); - if (capabilities.screenPicker === "sources") { - setDialogKind("screen"); - } else { - await startScreenShare({ includeAudio: true }); - } - } catch (error) { - console.error("Failed to start screen sharing", error); - toast( - "error", - error instanceof Error - ? error.message - : "Failed to start screen sharing.", - ); - } - } - - async function stop(kind: MediaShareKind) { - try { - await (kind === "screen" ? stopScreenShare() : stopCameraShare()); - } catch (error) { - console.error(`Failed to stop ${kind} sharing`, error); - toast("error", `Failed to stop ${kind} sharing.`); - } - } - - const trigger = ( - - ); - - return ( - <> - - - - tooltip ? ( - ( - } - onClick={onClick} - className={className} - > - {trigger} - - )} - /> - ) : ( - - {trigger} - - ) - } - /> - - - - {isScreensharing ? ( - - ) : null} - {cameraEnabled ? ( - - ) : null} - - - {tooltip ? ( - - {tooltip} - - ) : null} - - - {dialogKind ? ( - !open && setDialogKind(null)} - portalContainer={portalContainer} - /> - ) : null} - - ); -} diff --git a/packages/call/src/components/buttons/mute.tsx b/packages/call/src/components/buttons/mute.tsx index 025f2ca..c8f2ba0 100644 --- a/packages/call/src/components/buttons/mute.tsx +++ b/packages/call/src/components/buttons/mute.tsx @@ -1,18 +1,18 @@ -import { Button } from "@methanium/ui"; +import { Button, Tooltip, TooltipContent, TooltipTrigger } from "@tensamin/ui"; import { toggleMute, useCall } from "../../store"; import { Mic, MicOff } from "lucide-react"; -import { - type CallButtonProps, - iconScale, - withButtonTooltip, -} from "./tooltipButton"; export default function MuteButton({ className, iconSize, tooltip, portalContainer, -}: CallButtonProps) { +}: { + className?: string; + iconSize?: number; + tooltip?: string; + portalContainer?: HTMLElement; +}) { const micEnabled = useCall((state) => state.micEnabled); const button = ( @@ -22,12 +22,23 @@ export default function MuteButton({ onClick={() => void toggleMute()} > {micEnabled ? ( - + ) : ( - + )} ); - return withButtonTooltip(button, tooltip, portalContainer); + if (!tooltip) { + return button; + } + + return ( + + + + {tooltip} + + + ); } diff --git a/packages/call/src/components/buttons/screenshare.tsx b/packages/call/src/components/buttons/screenshare.tsx new file mode 100644 index 0000000..4471539 --- /dev/null +++ b/packages/call/src/components/buttons/screenshare.tsx @@ -0,0 +1,160 @@ +import { useEffect, useState } from "react"; +import { + Button, + Popover, + PopoverContent, + PopoverTrigger, + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@tensamin/ui"; +import { MonitorDot, ScreenShare } from "lucide-react"; +import { toast } from "@tensamin/shared/log"; +import { isTauri } from "@tauri-apps/api/core"; +import { setScreenShareEnabled, useCall } from "../../store"; +import ScreenShareDialog from "../screenshareDialog"; + +export default function ScreenshareButton({ + className, + iconSize, + tooltip, +}: { + className?: string; + iconSize?: number; + tooltip?: string; +}) { + const isScreensharing = useCall((state) => state.screenShareEnabled); + const screenRef = useCall((state) => state.screenRef); + const [portalContainer, setPortalContainer] = useState(); + const [dialogOpen, setDialogOpen] = useState(false); + const [menuOpen, setMenuOpen] = useState(false); + + useEffect(() => { + setPortalContainer(screenRef?.current ?? undefined); + }, [screenRef]); + + async function startWebShare() { + try { + await setScreenShareEnabled(true, { + audio: true, + systemAudio: "include", + surfaceSwitching: "include", + video: true, + }); + } catch (error) { + console.error("Failed to start web screen share", error); + toast( + "error", + error instanceof Error + ? error.message + : "Failed to start screen sharing.", + ); + } + } + + async function stopShare() { + try { + await setScreenShareEnabled(false); + } catch (error) { + console.error("Failed to stop screen share", error); + toast("error", "Failed to stop screen sharing."); + } + } + + return ( + <> + + + + {isScreensharing ? ( + + ) : ( + + )} + + } + /> + ) : ( + + ) + } + /> + + + + + + + {tooltip && ( + + {tooltip} + + )} + + + {isTauri() && ( + + )} + + ); +} diff --git a/packages/call/src/components/buttons/tooltipButton.tsx b/packages/call/src/components/buttons/tooltipButton.tsx deleted file mode 100644 index 3b3d04e..0000000 --- a/packages/call/src/components/buttons/tooltipButton.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { cloneElement } from "react"; -import type { MouseEvent as ReactMouseEvent, ReactElement, Ref } from "react"; -import { Tooltip, TooltipContent, TooltipTrigger } from "@methanium/ui"; - -export type CallButtonProps = { - className?: string; - iconSize?: number; - tooltip?: string; - portalContainer?: HTMLElement; -}; - -export function iconScale(iconSize?: number) { - return { scale: (iconSize ? iconSize + 100 : 100) + "%" }; -} - -export function withButtonTooltip( - trigger: ReactElement<{ - onClick?: (event: ReactMouseEvent) => void; - ref?: Ref; - }>, - content?: string, - portalContainer?: HTMLElement, -) { - if (!content) return trigger; - - return ( - - - cloneElement(trigger, { - ref, - onClick: (event) => { - onClick?.(event); - trigger.props.onClick?.(event); - }, - }) - } - /> - - {content} - - - ); -} diff --git a/packages/call/src/components/invitePopup.tsx b/packages/call/src/components/invitePopup.tsx deleted file mode 100644 index 583c57d..0000000 --- a/packages/call/src/components/invitePopup.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { - Avatar, - AvatarFallback, - AvatarImage, - Button, - Dialog, - DialogContent, -} from "@methanium/ui"; -import Wrapper from "@tensamin/user/wrapper"; -import { PhoneIncoming, X } from "lucide-react"; - -export default function InvitePopup({ - open, - setOpen, - onAccept, - user, -}: { - open: boolean; - setOpen: (value: boolean) => void; - onAccept: (value: boolean) => void; - user: number; -}) { - return ( - ( - - - - - - {user.Display.slice(0, 2).toUpperCase()} - - -

{user.Display}

-
- - -
-
-
- )} - /> - ); -} diff --git a/packages/call/src/components/mediaShareDialog.tsx b/packages/call/src/components/mediaShareDialog.tsx deleted file mode 100644 index 8e7b493..0000000 --- a/packages/call/src/components/mediaShareDialog.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import { useEffect, useRef, useState } from "react"; -import { - Button, - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, - Label, - Switch, -} from "@methanium/ui"; -import { toast } from "@tensamin/shared/log"; -import { Loader2 } from "lucide-react"; -import { - getMediaShareAdapter, - type MediaShareKind, - type MediaShareSource, -} from "../mediaShare"; -import { startCameraShare, startScreenShare } from "../store"; -import MediaSourceCard from "./mediaSourceCard"; - -export default function MediaShareDialog({ - kind, - open, - onOpenChange, - portalContainer, -}: { - kind: MediaShareKind; - open: boolean; - onOpenChange: (open: boolean) => void; - portalContainer?: HTMLElement; -}) { - const [loading, setLoading] = useState(false); - const [sources, setSources] = useState([]); - const [selectedSourceId, setSelectedSourceId] = useState(null); - const [shareAudio, setShareAudio] = useState(true); - const [canShareAudio, setCanShareAudio] = useState(false); - const [cameraPreview, setCameraPreview] = useState(null); - const [cameraPreviewVersion, setCameraPreviewVersion] = useState(0); - const cameraPreviewRef = useRef(null); - - function stopCameraPreview() { - cameraPreviewRef.current?.getTracks().forEach((track) => track.stop()); - cameraPreviewRef.current = null; - setCameraPreview(null); - } - - useEffect(() => { - if (!open) return; - let active = true; - setLoading(true); - setSelectedSourceId(null); - - Promise.all([ - getMediaShareAdapter().listSources(kind), - getMediaShareAdapter().getCapabilities(), - ]) - .then(([nextSources, capabilities]) => { - if (!active) return; - const availableSources = - kind === "camera" && nextSources.length === 0 - ? [ - { - id: "__default_camera__", - kind: "camera" as const, - name: "Default camera", - }, - ] - : nextSources; - setSources(availableSources); - setSelectedSourceId(availableSources[0]?.id ?? null); - setCanShareAudio(kind === "screen" && capabilities.canShareScreenAudio); - }) - .catch((error) => { - console.error("Failed to load media share sources", error); - toast("error", "Failed to load media sources."); - }) - .finally(() => active && setLoading(false)); - - return () => { - active = false; - }; - }, [kind, open]); - - useEffect(() => { - if (kind !== "camera" || !open || !selectedSourceId) { - stopCameraPreview(); - return; - } - - let active = true; - stopCameraPreview(); - const sourceId = - selectedSourceId === "__default_camera__" ? undefined : selectedSourceId; - - void navigator.mediaDevices - .getUserMedia({ - audio: false, - video: sourceId ? { deviceId: { exact: sourceId } } : true, - }) - .then((stream) => { - if (!active) { - stream.getTracks().forEach((track) => track.stop()); - return; - } - cameraPreviewRef.current = stream; - setCameraPreview(stream); - }) - .catch((error) => { - console.error("Failed to preview camera", error); - }); - - return () => { - active = false; - stopCameraPreview(); - }; - }, [cameraPreviewVersion, kind, open, selectedSourceId]); - - async function startSharing() { - setLoading(true); - try { - if (kind === "camera") { - stopCameraPreview(); - await startCameraShare( - selectedSourceId === "__default_camera__" - ? undefined - : (selectedSourceId ?? undefined), - ); - } else { - await startScreenShare({ - sourceId: selectedSourceId ?? undefined, - includeAudio: canShareAudio && shareAudio, - }); - } - onOpenChange(false); - } catch (error) { - console.error(`Failed to share ${kind}`, error); - if (kind === "camera") { - setCameraPreviewVersion((version) => version + 1); - } - toast( - "error", - error instanceof Error ? error.message : `Failed to share ${kind}.`, - ); - } finally { - setLoading(false); - } - } - - return ( - - - - - {kind === "camera" ? "Share a camera" : "Share your screen"} - - - -
-
- {sources.map((source) => { - const selected = source.id === selectedSourceId; - return ( - setSelectedSourceId(source.id)} - /> - ); - })} -
- - {!loading && sources.length === 0 ? ( -

- No {kind === "camera" ? "cameras" : "windows or displays"} found. -

- ) : null} - - {canShareAudio ? ( -
-
- -

- Some apps and protected media do not allow audio capture. -

-
- -
- ) : null} - - {loading ? ( -
- - Loading sources... -
- ) : null} -
- - - - - -
-
- ); -} diff --git a/packages/call/src/components/mediaSourceCard.tsx b/packages/call/src/components/mediaSourceCard.tsx deleted file mode 100644 index f740dbc..0000000 --- a/packages/call/src/components/mediaSourceCard.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { cn } from "@methanium/ui"; -import { AppWindow, Camera, MonitorUp } from "lucide-react"; -import { useEffect, useRef } from "react"; -import type { MediaShareSource } from "../mediaShare"; - -function SourceIcon({ source }: { source: MediaShareSource }) { - const className = "size-8 text-muted-foreground"; - - if (source.kind === "camera") return ; - if (source.kind === "window") return ; - return ; -} - -function VideoPreview({ stream }: { stream: MediaStream }) { - const videoRef = useRef(null); - - useEffect(() => { - const video = videoRef.current; - if (!video) return; - video.srcObject = stream; - void video.play().catch(() => undefined); - - return () => { - video.srcObject = null; - }; - }, [stream]); - - return ( -
); @@ -165,56 +98,42 @@ export default function Base({ fill?: boolean; flush?: boolean; }) { - const { load } = useStorage(); + const { get } = useUser(); const focusedParticipantId = useCall((state) => state.focusedParticipantId); const view = useCall((state) => state.view); - const participantId = Number(participant?.identity); - const validParticipantId = - participant && Number.isInteger(participantId) && participantId > 0 - ? participantId - : null; - const { data: user } = useUserFields(validParticipantId, USER_FIELDS); - const [avatarBackgroundColor, setAvatarBackgroundColor] = useState< - string | undefined - >(undefined); - const isSpeaking = useIsSpeaking(user?.UserId ?? -1); + const [user, setUser] = useState(null); const screenSharePublication = getTrackPublicationBySource( participant, Track.Source.ScreenShare, ); const screenSharePreview = participant?.attributes["screenSharePreview"]; - const cameraPublication = getTrackPublicationBySource( - participant, - Track.Source.Camera, - ); - const cameraDisabled = useCall((state) => - user ? state.disabledCameraParticipantIds.includes(user.UserId) : false, - ); - - const [ownId, setOwnId] = useState(0); - useEffect(() => { - load("user_id").then(setOwnId); - }, [load]); useEffect(() => { - if (type !== "user" || !user?.Avatar) { - setAvatarBackgroundColor(undefined); + const participantId = Number(participant?.identity); + + if ( + !participant || + !Number.isInteger(participantId) || + participantId <= 0 + ) { + // eslint-disable-next-line + setUser(null); return; } let active = true; - void getAverageImageColor(user.Avatar).then((color) => { + void get(participantId).then((nextUser) => { if (active) { - setAvatarBackgroundColor(color); + setUser(nextUser); } }); return () => { active = false; }; - }, [type, user?.Avatar]); + }, [participant, get]); // Avatar calc const currentCard = useRef(null); @@ -231,12 +150,12 @@ export default function Base({ const onClick = () => { if (view === "grid") { - focusParticipant(user.UserId, type); + focusParticipant(user.user_id); } else { - if (user.UserId === focusedParticipantId) { + if (user.user_id === focusedParticipantId) { setCallView("grid"); } else { - focusParticipant(user.UserId, type); + focusParticipant(user.user_id); } } }; @@ -245,22 +164,17 @@ export default function Base({ screenSharePublication?.isSubscribed && screenSharePublication.track; const isFocusedInFocusedView = - view === "focused" && user.UserId === focusedParticipantId; + view === "focused" && user.user_id === focusedParticipantId; return ( - +
<> @@ -270,7 +184,7 @@ export default function Base({ variant="outline" onClick={(event) => { if (isFocusedInFocusedView) event.stopPropagation(); - startWatchingStream(user.UserId); + startWatchingStream(user.user_id); }} > Watch Stream @@ -280,7 +194,7 @@ export default function Base({ variant="outline" onClick={(event) => { event.stopPropagation(); - startWatchingStream(user.UserId); + startWatchingStream(user.user_id); }} > @@ -289,20 +203,18 @@ export default function Base({
)} {view === "grid" || - (view === "focused" && user.UserId !== focusedParticipantId) ? ( + (view === "focused" && + user.user_id !== focusedParticipantId) ? ( ) : null}
{/* Detect video / user and place here */} @@ -329,36 +241,30 @@ export default function Base({
) : null)} - {type === "user" && - (cameraPublication?.track && !cameraDisabled ? ( - - ) : ( - + + - - - {user.Display.slice(0, 2).toUpperCase()} - - - ))} + {user.display.slice(0, 2).toUpperCase()} + + + )}
} /> - - + + Stop Watching + + ); } diff --git a/packages/call/src/components/modals/contextMenu.tsx b/packages/call/src/components/modals/contextMenu.tsx deleted file mode 100644 index d063cee..0000000 --- a/packages/call/src/components/modals/contextMenu.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { - ContextMenuCheckboxItem, - ContextMenuContent, - ContextMenuItem, - ContextMenuSeparator, - Slider, -} from "@methanium/ui"; -import { setParticipantCameraDisabled, useCall } from "../../store"; -import { useState } from "react"; - -function EmptyCheckboxIndicator({ checked }: { checked: boolean }) { - if (checked) { - return null; - } - - return ( - - ); -} - -export default function ContextMenu({ - user, - ownId, -}: { - user: Readonly<{ UserId: number }>; - ownId: number; -}) { - const [muted, setMuted] = useState(false); - const [soundboardMuted, setSoundboardMuted] = useState(false); - const [serverDeafened, setServerDeafened] = useState(false); - const [serverMuted, setServerMuted] = useState(false); - const watchedStreamParticipantIds = useCall( - (state) => state.watchedStreamParticipantIds, - ); - const cameraDisabled = useCall((state) => - state.disabledCameraParticipantIds.includes(user.UserId), - ); - - return ( - - {watchedStreamParticipantIds.includes(user.UserId) && - user.UserId !== ownId ? ( - Stop Watching - ) : null} - Profile - Change Nickname - - e.preventDefault()} - className="flex flex-col items-start pb-2" - > -

Volume

- e.stopPropagation()} - onPointerDown={(e) => e.stopPropagation()} - /> -
- e.preventDefault()} - className="flex justify-between" - > -

Mute

- -
- e.preventDefault()} - className="flex justify-between" - > -

Mute Soundboard

- -
- {user.UserId !== ownId ? ( - - setParticipantCameraDisabled(user.UserId, checked) - } - onSelect={(e) => e.preventDefault()} - className="flex justify-between" - > -

Disable camera

- -
- ) : null} - e.preventDefault()} - className="flex justify-between text-destructive focus:text-destructive" - > -

Server Deaf

- -
- e.preventDefault()} - className="flex justify-between text-destructive focus:text-destructive" - > -

Server Mute

- -
- Disconnect -
- ); -} diff --git a/packages/call/src/components/popout.tsx b/packages/call/src/components/popout.tsx deleted file mode 100644 index 54f76f1..0000000 --- a/packages/call/src/components/popout.tsx +++ /dev/null @@ -1,721 +0,0 @@ -import { Participant, Track } from "livekit-client"; -import VideoViewer from "./videoViewer"; -import { useLocation } from "@tanstack/react-router"; -import { getRoom, openCallPage, stopWatchingStream, useCall } from "../store"; -import { useState, useRef, useEffect, useCallback } from "react"; -import { - Avatar, - AvatarFallback, - AvatarImage, - Button, - Card, - cn, - useIsMobile, - useSidebar, -} from "@methanium/ui"; -import { ScreenShareOff } from "lucide-react"; -import { useUserFields } from "@tensamin/user/context"; -import { useIsSpeaking, useLastSpeakingParticipantId } from "../speakingState"; -import { getAverageImageColor } from "./modals/base"; - -const USER_FIELDS = ["Avatar", "Display"] as const; - -function getTrackPublicationBySource( - participant: Participant | undefined, - source: Track.Source, -) { - if (!participant) { - return undefined; - } - - return [...participant.trackPublications.values()].find( - (publication) => publication.source === source, - ); -} - -type Positions = "top-left" | "top-right" | "bottom-left" | "bottom-right"; -type ResizeEdge = - | "top" - | "right" - | "bottom" - | "left" - | "top-left" - | "top-right" - | "bottom-right" - | "bottom-left"; -type Point = { - x: number; - y: number; -}; - -const MARGIN = 40; -const MIN_SIZE = 240; -const MAX_SIZE = 1000; -const ASPECT_RATIO = 9 / 16; -const MOBILE_MARGIN = 16; -const MOBILE_PILL_WIDTH = 128; -const MOBILE_PILL_HEIGHT = 48; - -function MobileCallPill({ - active, - callId, -}: { - active: boolean; - callId: string; -}) { - const { setOpenMobile } = useSidebar(); - const lastSpeakingParticipantId = useLastSpeakingParticipantId(); - const isSpeaking = useIsSpeaking(lastSpeakingParticipantId ?? -1); - const { data: lastSpeakingUser } = useUserFields( - lastSpeakingParticipantId, - USER_FIELDS, - ); - const [avatarBackgroundColor, setAvatarBackgroundColor] = useState< - string | undefined - >(undefined); - const safeAreaRef = useRef(null); - const pillRef = useRef(null); - const initialCoords = { - x: window.innerWidth - MOBILE_PILL_WIDTH - MOBILE_MARGIN, - y: MOBILE_MARGIN, - }; - const coordsRef = useRef(initialCoords); - const dragOffsetRef = useRef({ x: 0, y: 0 }); - const dragStartRef = useRef({ x: 0, y: 0 }); - const movedRef = useRef(false); - const [position, setPosition] = useState("top-right"); - const [coords, setCoords] = useState(initialCoords); - const [isDragging, setIsDragging] = useState(false); - - useEffect(() => { - if (!lastSpeakingUser?.Avatar) { - setAvatarBackgroundColor(undefined); - return; - } - - let mounted = true; - - void getAverageImageColor(lastSpeakingUser.Avatar).then((color) => { - if (mounted) { - setAvatarBackgroundColor(color); - } - }); - - return () => { - mounted = false; - }; - }, [lastSpeakingUser?.Avatar]); - - const getSafeArea = useCallback(() => { - const element = safeAreaRef.current; - if (!element) return { top: 0, right: 0, bottom: 0, left: 0 }; - const style = getComputedStyle(element); - return { - top: parseFloat(style.paddingTop) || 0, - right: parseFloat(style.paddingRight) || 0, - bottom: parseFloat(style.paddingBottom) || 0, - left: parseFloat(style.paddingLeft) || 0, - }; - }, []); - - const getCoordsForPosition = useCallback( - (nextPosition: Positions): Point => { - const bounds = pillRef.current?.getBoundingClientRect(); - const width = bounds?.width ?? MOBILE_PILL_WIDTH; - const height = bounds?.height ?? MOBILE_PILL_HEIGHT; - const safeArea = getSafeArea(); - - return { - x: nextPosition.endsWith("right") - ? window.innerWidth - safeArea.right - width - MOBILE_MARGIN - : safeArea.left + MOBILE_MARGIN, - y: nextPosition.startsWith("bottom") - ? window.innerHeight - safeArea.bottom - height - MOBILE_MARGIN - : safeArea.top + MOBILE_MARGIN, - }; - }, - [getSafeArea], - ); - - const setCoordsSafe = useCallback((next: Point) => { - coordsRef.current = next; - setCoords(next); - }, []); - - const snapToPosition = useCallback( - (nextPosition: Positions) => { - setPosition(nextPosition); - setCoordsSafe(getCoordsForPosition(nextPosition)); - }, - [getCoordsForPosition, setCoordsSafe], - ); - - useEffect(() => { - if (active && !isDragging) { - snapToPosition(position); - } - }, [active, isDragging, position, snapToPosition]); - - useEffect(() => { - const handleResize = () => snapToPosition(position); - - window.addEventListener("resize", handleResize); - return () => window.removeEventListener("resize", handleResize); - }, [position, snapToPosition]); - - if (!active) { - return null; - } - - return ( -
- { - if (movedRef.current) { - movedRef.current = false; - return; - } - - setOpenMobile(false); - void openCallPage(callId); - }} - onPointerDown={(event) => { - if (event.button !== 0) return; - - event.currentTarget.setPointerCapture(event.pointerId); - dragOffsetRef.current = { - x: event.clientX - coordsRef.current.x, - y: event.clientY - coordsRef.current.y, - }; - dragStartRef.current = { x: event.clientX, y: event.clientY }; - movedRef.current = false; - setIsDragging(true); - }} - onPointerMove={(event) => { - if (!isDragging) return; - - const bounds = pillRef.current?.getBoundingClientRect(); - const width = bounds?.width ?? MOBILE_PILL_WIDTH; - const height = bounds?.height ?? MOBILE_PILL_HEIGHT; - const safeArea = getSafeArea(); - const next = { - x: Math.min( - window.innerWidth - safeArea.right - width - MOBILE_MARGIN, - Math.max( - safeArea.left + MOBILE_MARGIN, - event.clientX - dragOffsetRef.current.x, - ), - ), - y: Math.min( - window.innerHeight - safeArea.bottom - height - MOBILE_MARGIN, - Math.max( - safeArea.top + MOBILE_MARGIN, - event.clientY - dragOffsetRef.current.y, - ), - ), - }; - - if ( - Math.abs(event.clientX - dragStartRef.current.x) > 3 || - Math.abs(event.clientY - dragStartRef.current.y) > 3 - ) { - movedRef.current = true; - } - - setCoordsSafe(next); - }} - onPointerUp={(event) => { - if (!isDragging) return; - - const nextPosition = `${ - event.clientY < window.innerHeight / 2 ? "top" : "bottom" - }-${event.clientX < window.innerWidth / 2 ? "left" : "right"}` as Positions; - - setIsDragging(false); - snapToPosition(nextPosition); - }} - onPointerCancel={() => { - setIsDragging(false); - snapToPosition(position); - }} - className={cn( - "pointer-events-auto fixed left-0 top-0 z-200 flex w-23 h-23! touch-none select-none shadow-xl rounded-2xl flex items-center justify-center", - isSpeaking && "border-3! border-(--primary-foreground-alt)/75!", - isDragging ? "cursor-grabbing" : "cursor-grab", - )} - style={{ - backgroundColor: avatarBackgroundColor, - transform: `translate3d(${coords.x}px, ${coords.y}px, 0)`, - transition: isDragging - ? "none" - : "transform 420ms cubic-bezier(0.34, 1.56, 0.64, 1)", - willChange: "transform", - }} - > - - - - {lastSpeakingUser?.Display.slice(0, 2).toUpperCase() ?? "..."} - - - -
- ); -} - -export function Popout({ participant }: { participant: Participant }) { - const screenSharePublication = getTrackPublicationBySource( - participant, - Track.Source.ScreenShare, - ); - - const popoutRef = useRef(null); - const coordsRef = useRef({ x: MARGIN, y: MARGIN }); - const dragOffsetRef = useRef({ x: 0, y: 0 }); - const sizeRef = useRef(400); - const hideOverlayTimeoutRef = useRef | null>( - null, - ); - const resizeRef = useRef<{ - size: number; - clientX: number; - clientY: number; - edge: ResizeEdge; - }>({ size: 400, clientX: 0, clientY: 0, edge: "right" }); - - const [size, setSize] = useState(400); - const [isDragging, setIsDragging] = useState(false); - const [isResizing, setIsResizing] = useState(false); - const [isOverlayVisible, setIsOverlayVisible] = useState(false); - const [position, setPosition] = useState("top-left"); - const [coords, setCoords] = useState({ x: MARGIN, y: MARGIN }); - - const setCoordsSafe = (next: Point) => { - coordsRef.current = next; - setCoords(next); - }; - - const setSizeSafe = (next: number) => { - sizeRef.current = next; - setSize(next); - }; - - const getMaxSize = useCallback(() => { - const maxWidth = window.innerWidth - MARGIN * 2; - const maxHeightWidth = (window.innerHeight - MARGIN * 2) / ASPECT_RATIO; - - return Math.max(MIN_SIZE, Math.min(MAX_SIZE, maxWidth, maxHeightWidth)); - }, []); - - const clampSize = useCallback( - (nextSize: number) => { - return Math.min(getMaxSize(), Math.max(MIN_SIZE, nextSize)); - }, - [getMaxSize], - ); - - const getPositionFromPoint = ( - clientX: number, - clientY: number, - ): Positions => { - const height = window.innerHeight / 2 > clientY ? "top" : "bottom"; - const width = window.innerWidth / 2 > clientX ? "left" : "right"; - - return `${height}-${width}` as Positions; - }; - - const getCoordsForPosition = useCallback( - (nextPosition: Positions, nextSize = size): Point => { - const width = nextSize; - const height = nextSize * ASPECT_RATIO; - - return { - x: nextPosition.endsWith("right") - ? window.innerWidth - width - MARGIN - : MARGIN, - y: nextPosition.startsWith("bottom") - ? window.innerHeight - height - MARGIN - : MARGIN, - }; - }, - [size], - ); - - useEffect(() => { - if (isDragging) return; - - setCoordsSafe(getCoordsForPosition(position)); - }, [position, size, isDragging, getCoordsForPosition]); - - useEffect(() => { - const handleResize = () => { - if (isDragging) return; - - const nextSize = clampSize(size); - - setSizeSafe(nextSize); - setCoordsSafe(getCoordsForPosition(position, nextSize)); - }; - - window.addEventListener("resize", handleResize); - return () => window.removeEventListener("resize", handleResize); - }, [position, size, isDragging, getCoordsForPosition, clampSize]); - - useEffect(() => { - return () => { - if (hideOverlayTimeoutRef.current) { - clearTimeout(hideOverlayTimeoutRef.current); - } - }; - }, []); - - const scheduleOverlayHide = () => { - if (hideOverlayTimeoutRef.current) { - clearTimeout(hideOverlayTimeoutRef.current); - } - - hideOverlayTimeoutRef.current = setTimeout(() => { - setIsOverlayVisible(false); - hideOverlayTimeoutRef.current = null; - }, 3000); - }; - - const showOverlay = () => { - setIsOverlayVisible(true); - scheduleOverlayHide(); - }; - - const hideOverlay = () => { - if (hideOverlayTimeoutRef.current) { - clearTimeout(hideOverlayTimeoutRef.current); - hideOverlayTimeoutRef.current = null; - } - - setIsOverlayVisible(false); - }; - - const getCornerResizeDelta = ( - horizontalDelta: number, - verticalDelta: number, - ) => { - return Math.abs(horizontalDelta) > Math.abs(verticalDelta) - ? horizontalDelta - : verticalDelta; - }; - - const getResizeDelta = (e: React.PointerEvent, edge: ResizeEdge) => { - switch (edge) { - case "left": - return resizeRef.current.clientX - e.clientX; - case "right": - return e.clientX - resizeRef.current.clientX; - case "top": - return (resizeRef.current.clientY - e.clientY) / ASPECT_RATIO; - case "bottom": - return (e.clientY - resizeRef.current.clientY) / ASPECT_RATIO; - case "top-left": - return getCornerResizeDelta( - resizeRef.current.clientX - e.clientX, - (resizeRef.current.clientY - e.clientY) / ASPECT_RATIO, - ); - case "top-right": - return getCornerResizeDelta( - e.clientX - resizeRef.current.clientX, - (resizeRef.current.clientY - e.clientY) / ASPECT_RATIO, - ); - case "bottom-right": - return getCornerResizeDelta( - e.clientX - resizeRef.current.clientX, - (e.clientY - resizeRef.current.clientY) / ASPECT_RATIO, - ); - case "bottom-left": - return getCornerResizeDelta( - resizeRef.current.clientX - e.clientX, - (e.clientY - resizeRef.current.clientY) / ASPECT_RATIO, - ); - } - }; - - const startResize = ( - e: React.PointerEvent, - edge: ResizeEdge, - ) => { - if (e.button !== 0) return; - - e.stopPropagation(); - e.currentTarget.setPointerCapture(e.pointerId); - - resizeRef.current = { - size, - clientX: e.clientX, - clientY: e.clientY, - edge, - }; - - setIsResizing(true); - }; - - const resizePopout = (e: React.PointerEvent) => { - if (!isResizing) return; - - const nextSize = clampSize( - resizeRef.current.size + getResizeDelta(e, resizeRef.current.edge), - ); - - setSizeSafe(nextSize); - setCoordsSafe(getCoordsForPosition(position, nextSize)); - }; - - const stopResize = (e: React.PointerEvent) => { - if (!isResizing) return; - - e.stopPropagation(); - setIsResizing(false); - setCoordsSafe(getCoordsForPosition(position, sizeRef.current)); - }; - - const resizeEdgeClassName = "absolute z-10 bg-transparent"; - const resizeCornerClassName = "absolute z-20 h-4 w-4 bg-transparent"; - - if (!screenSharePublication) { - return null; - } - - return ( -
{ - if (e.button !== 0) return; - if (isResizing) return; - - e.currentTarget.setPointerCapture(e.pointerId); - - const current = coordsRef.current; - - dragOffsetRef.current = { - x: e.clientX - current.x, - y: e.clientY - current.y, - }; - - setIsDragging(true); - }} - onPointerMove={(e) => { - if (!isDragging) return; - - setCoordsSafe({ - x: e.clientX - dragOffsetRef.current.x, - y: e.clientY - dragOffsetRef.current.y, - }); - }} - onPointerUp={(e) => { - if (!isDragging) return; - - const nextPosition = getPositionFromPoint(e.clientX, e.clientY); - - setPosition(nextPosition); - setIsDragging(false); - - requestAnimationFrame(() => { - setCoordsSafe(getCoordsForPosition(nextPosition)); - }); - }} - onPointerCancel={() => { - setIsDragging(false); - - requestAnimationFrame(() => { - setCoordsSafe(getCoordsForPosition(position)); - }); - }} - onMouseEnter={showOverlay} - onMouseMove={showOverlay} - onMouseLeave={hideOverlay} - className={cn( - "fixed left-0 top-0 aspect-video z-200 rounded-lg border-2 border-muted-foreground bg-black", - "select-none touch-none", - isDragging ? "cursor-grabbing" : "cursor-grab", - )} - style={{ - width: size, - transform: `translate3d(${coords.x}px, ${coords.y}px, 0)`, - transition: - isDragging || isResizing - ? "none" - : "transform 420ms cubic-bezier(0.34, 1.56, 0.64, 1)", - willChange: "transform", - }} - > - -
-
-
-
-
-
- -
-
-
startResize(e, "top")} - onPointerMove={resizePopout} - onPointerUp={stopResize} - onPointerCancel={stopResize} - /> -
startResize(e, "right")} - onPointerMove={resizePopout} - onPointerUp={stopResize} - onPointerCancel={stopResize} - /> -
startResize(e, "bottom")} - onPointerMove={resizePopout} - onPointerUp={stopResize} - onPointerCancel={stopResize} - /> -
startResize(e, "left")} - onPointerMove={resizePopout} - onPointerUp={stopResize} - onPointerCancel={stopResize} - /> -
startResize(e, "top-left")} - onPointerMove={resizePopout} - onPointerUp={stopResize} - onPointerCancel={stopResize} - /> -
startResize(e, "top-right")} - onPointerMove={resizePopout} - onPointerUp={stopResize} - onPointerCancel={stopResize} - /> -
startResize(e, "bottom-right")} - onPointerMove={resizePopout} - onPointerUp={stopResize} - onPointerCancel={stopResize} - /> -
startResize(e, "bottom-left")} - onPointerMove={resizePopout} - onPointerUp={stopResize} - onPointerCancel={stopResize} - /> -
- ); -} - -export default function Wrapper() { - const room = getRoom(); - const { pathname } = useLocation(); - const { openMobile } = useSidebar(); - const isMobile = useIsMobile(); - const state = useCall((state) => state.state); - const callId = useCall((state) => state.callId); - const watchedStreamParticipantIds = useCall( - (state) => state.watchedStreamParticipantIds, - ); - const lastFocusedParticipantId = useCall( - (state) => state.lastFocusedParticipantId, - ); - const participant = room.getParticipantByIdentity( - String(lastFocusedParticipantId), - ); - - if (isMobile) { - return ( - - ); - } - - if (!participant || !lastFocusedParticipantId) { - return null; - } - - const active = - !pathname.startsWith("/call") && - state === "open" && - watchedStreamParticipantIds.includes(Number(participant.identity ?? 0)); - - return active && ; -} diff --git a/packages/call/src/components/screenshareDialog.tsx b/packages/call/src/components/screenshareDialog.tsx new file mode 100644 index 0000000..180c63e --- /dev/null +++ b/packages/call/src/components/screenshareDialog.tsx @@ -0,0 +1,335 @@ +import { useEffect, useState } from "react"; +import { + Button, + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + Label, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + Switch, +} from "@tensamin/ui"; +import { + type DesktopScreenShareAudioOutput, + type DesktopScreenShareCapabilities, + type DesktopScreenShareSource, + useDesktopMedia, +} from "@tensamin/tauri/context"; +import { toast } from "@tensamin/shared/log"; +import { AppWindow, Loader2, MonitorUp } from "lucide-react"; +import type { ScreenShareCaptureOptions } from "livekit-client"; +import { setScreenShareEnabled, startLinuxDesktopScreenShare } from "../store"; + +const NONE_AUDIO_OUTPUT = "__none__"; + +function buildScreenShareOptions( + source: DesktopScreenShareSource, + capabilities: DesktopScreenShareCapabilities, + selectedAudioOutputId: string, + shareAudio: boolean, +): ScreenShareCaptureOptions { + const wantsAudio = capabilities.showAudioOutputSelector + ? selectedAudioOutputId !== NONE_AUDIO_OUTPUT + : capabilities.hasReliableSystemAudio && shareAudio; + + return { + audio: wantsAudio + ? { + autoGainControl: false, + echoCancellation: false, + noiseSuppression: false, + } + : false, + video: { + displaySurface: source.kind === "window" ? "window" : "monitor", + }, + systemAudio: wantsAudio ? "include" : "exclude", + surfaceSwitching: "exclude", + selfBrowserSurface: "exclude", + contentHint: "detail", + }; +} + +export default function ScreenShareDialog({ + open, + onOpenChange, + isScreensharing, + portalContainer, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + isScreensharing: boolean; + portalContainer?: HTMLElement; +}) { + const { + getScreenShareCapabilities, + listScreenShareAudioOutputs, + listScreenShareSources, + } = useDesktopMedia(); + + const [loading, setLoading] = useState(false); + const [sources, setSources] = useState([]); + const [audioOutputs, setAudioOutputs] = useState< + DesktopScreenShareAudioOutput[] + >([]); + const [capabilities, setCapabilities] = + useState(null); + const [selectedSourceId, setSelectedSourceId] = useState(null); + const [selectedAudioOutputId, setSelectedAudioOutputId] = + useState(NONE_AUDIO_OUTPUT); + const [shareAudio, setShareAudio] = useState(false); + + useEffect(() => { + if (!open) { + return; + } + + let active = true; + + // eslint-disable-next-line + setLoading(true); + setSelectedSourceId(null); + setSelectedAudioOutputId(NONE_AUDIO_OUTPUT); + setShareAudio(false); + + Promise.all([listScreenShareSources(), getScreenShareCapabilities()]) + .then(async ([nextSources, nextCapabilities]) => { + if (!active) { + return; + } + + setSources(nextSources); + setCapabilities(nextCapabilities); + + if (nextCapabilities.showAudioOutputSelector) { + const nextOutputs = await listScreenShareAudioOutputs(); + + if (!active) { + return; + } + + setAudioOutputs(nextOutputs); + } else { + setAudioOutputs([]); + } + }) + .catch((error) => { + console.error("Failed to load desktop share sources", error); + toast("error", "Failed to load screen share sources."); + }) + .finally(() => { + if (active) { + setLoading(false); + } + }); + + return () => { + active = false; + }; + }, [ + getScreenShareCapabilities, + listScreenShareAudioOutputs, + listScreenShareSources, + open, + ]); + + const selectedSource = + sources.find((source) => source.id === selectedSourceId) ?? null; + + async function startSharing() { + if (!selectedSource || !capabilities) { + return; + } + + setLoading(true); + + try { + if (capabilities.platform === "linux") { + await startLinuxDesktopScreenShare(selectedSource.id); + } else { + await setScreenShareEnabled( + true, + buildScreenShareOptions( + selectedSource, + capabilities, + selectedAudioOutputId, + shareAudio, + ), + ); + } + onOpenChange(false); + } catch (error) { + console.error("Failed to start screen share", error); + toast( + "error", + error instanceof Error + ? error.message + : "Failed to start screen sharing.", + ); + } finally { + setLoading(false); + } + } + + async function stopSharing() { + setLoading(true); + + try { + await setScreenShareEnabled(false); + onOpenChange(false); + } catch (error) { + console.error("Failed to stop screen share", error); + toast("error", "Failed to stop screen sharing."); + } finally { + setLoading(false); + } + } + + return ( + + + + Share your screen + + Choose a window or display you want to share. + + + +
+
+ {sources.map((source) => { + const selected = source.id === selectedSourceId; + + return ( + + ); + })} +
+ + {!loading && sources.length === 0 && ( +

+ No windows or displays found. +

+ )} + + {capabilities?.showAudioOutputSelector ? ( +
+ + +
+ ) : capabilities?.showAudioSwitch ? ( +
+
+
+ +

+ Share system audio alongside your screen when the runtime + can provide it. +

+
+ +
+ {!capabilities.hasReliableSystemAudio && ( +

+ System audio sharing is not available on this platform. +

+ )} +
+ ) : null} + + {loading && ( +
+ + Loading sources... +
+ )} +
+ + + + {isScreensharing && ( + + )} + + +
+
+ ); +} diff --git a/packages/call/src/components/sidebarBox.tsx b/packages/call/src/components/sidebarBox.tsx index 1f1840d..294d051 100644 --- a/packages/call/src/components/sidebarBox.tsx +++ b/packages/call/src/components/sidebarBox.tsx @@ -1,5 +1,5 @@ import { Lock, LockOpen } from "lucide-react"; -import { openCallPage, useCall, getRoom } from "../store"; +import { openCallPage, useCall } from "../store"; import { Button, Card, @@ -9,8 +9,8 @@ import { TooltipContent, TooltipTrigger, useIsMobile, -} from "@methanium/ui"; -import MediaShareButton from "./buttons/mediaShare"; +} from "@tensamin/ui"; +import ScreenshareButton from "./buttons/screenshare"; import MuteButton from "./buttons/mute"; import DeafButton from "./buttons/deaf"; import { Room, Track } from "livekit-client"; @@ -18,39 +18,34 @@ import { useEffect, useState } from "react"; import { AreaChart, Area } from "recharts"; import LeaveButton from "./buttons/leave"; -async function getPing(room: Room): Promise { - const report = await room.localParticipant - .getTrackPublication(Track.Source.Microphone) - ?.track?.getRTCStatsReport(); +export default function SidebarBox() { + const state = useCall((store) => store.state); + const screenRef = useCall((store) => store.screenRef); + const isMobile = useIsMobile(); + const [portalContainer, setPortalContainer] = useState(); - if (!report) return; + useEffect(() => { + setPortalContainer(screenRef?.current ?? undefined); + }, [screenRef]); - let bestRtt: number | undefined; - - report.forEach((stat) => { - if ( - stat.type === "candidate-pair" && - stat.state === "succeeded" && - stat.currentRoundTripTime != null - ) { - bestRtt = stat.currentRoundTripTime * 1000; - } - - if (stat.type === "remote-inbound-rtp" && stat.roundTripTime != null) { - bestRtt = stat.roundTripTime * 1000; - } - }); - - if (bestRtt == null || bestRtt <= 0) return; - - const roundedRtt = Math.round(bestRtt); - - if (roundedRtt <= 0) return; - - return roundedRtt; + return state === "closed" ? null : ( + + ); } -function ConnectionBar() { +function ConnectionBar({ portalContainer }: { portalContainer?: HTMLElement }) { const state = useCall((store) => store.state); const isEncrypted = useCall((store) => store.isEncrypted); const callId = useCall((store) => store.callId); @@ -58,18 +53,14 @@ function ConnectionBar() { return ( ( + render={ - )} + } /> - Click to open call page + + Click to open call page + ); } -export function TinyPingGraph() { - const room = getRoom(); +export function TinyPingGraph({ + portalContainer, +}: { + portalContainer?: HTMLElement; +}) { + const room = useCall((store) => store.room); const [mapData, setMapData] = useState>(() => new Map()); @@ -179,30 +176,41 @@ export function TinyPingGraph() {
} /> - + {data.length > 0 ? `${data.at(-1)?.ping} ms` : "Measuring ping..."} ); } -export default function SidebarBox() { - const state = useCall((store) => store.state); - const isMobile = useIsMobile(); +async function getPing(room: Room): Promise { + const report = await room.localParticipant + .getTrackPublication(Track.Source.Microphone) + ?.track?.getRTCStatsReport(); - return state === "closed" ? null : ( - - ); + if (!report) return; + + let bestRtt: number | undefined; + + report.forEach((stat) => { + if ( + stat.type === "candidate-pair" && + stat.state === "succeeded" && + stat.currentRoundTripTime != null + ) { + bestRtt = stat.currentRoundTripTime * 1000; + } + + if (stat.type === "remote-inbound-rtp" && stat.roundTripTime != null) { + bestRtt = stat.roundTripTime * 1000; + } + }); + + if (bestRtt == null || bestRtt <= 0) return; + + const roundedRtt = Math.round(bestRtt); + + if (roundedRtt <= 0) return; + + return roundedRtt; } diff --git a/packages/call/src/components/top.tsx b/packages/call/src/components/top.tsx index d634662..62e8bfc 100644 --- a/packages/call/src/components/top.tsx +++ b/packages/call/src/components/top.tsx @@ -1,5 +1,5 @@ -import { useUserFields } from "@tensamin/user/context"; -import { useCall, getRoom } from "../store"; +import { useUser, type User } from "@tensamin/user/context"; +import { useCall } from "../store"; import { useEffect, useState } from "react"; import { useStorage } from "@tensamin/storage/context"; import { @@ -9,47 +9,12 @@ import { Tooltip, TooltipContent, TooltipTrigger, -} from "@methanium/ui"; - -const USER_FIELDS = ["Avatar", "Display"] as const; - -function ParticipantAvatar({ - userId, - portalContainer, -}: { - userId: number; - portalContainer?: HTMLElement; -}) { - const { data: user } = useUserFields(userId, USER_FIELDS); - if (!user) return null; - - return ( -
- - - - - {user.Display.slice(0, 2).toUpperCase()} - - - } - /> - - {user.Display} - - -
- ); -} +} from "@tensamin/ui"; export default function TopBar() { + const { get } = useUser(); const { load } = useStorage(); - const room = getRoom(); + const room = useCall((state) => state.room); const screenRef = useCall((state) => state.screenRef); const [portalContainer, setPortalContainer] = useState(); @@ -66,23 +31,65 @@ export default function TopBar() { : null; }, ).filter((participantId): participantId is number => participantId != null); - const [ownId, setOwnId] = useState(); + const userIdsKey = userIds.join(","); + + const [users, setUsers] = useState([]); useEffect(() => { - void load("user_id").then(setOwnId); - }, [load]); + let active = true; + const ids = userIdsKey === "" ? [] : userIdsKey.split(",").map(Number); - const participantIds = ownId === undefined ? userIds : [ownId, ...userIds]; + void Promise.all(ids.map((id) => get(id))) + .then(async (users) => { + if (!active) { + return; + } + + const ownId = await load("user_id"); + const ownUser = await get(ownId); + + setUsers([ownUser, ...users]); + }) + .catch(async () => { + if (!active) { + return; + } + + const ownId = await load("user_id"); + const ownUser = await get(ownId); + + setUsers([ownUser]); + }); + + return () => { + active = false; + }; + }, [get, userIdsKey, load]); return (
- {participantIds.map((userId) => ( - + {users.map((user) => ( +
+ + + + + {user.display.slice(0, 2).toUpperCase()} + + + } + /> + + {user.display} + + +
))}
diff --git a/packages/call/src/components/videoViewer.tsx b/packages/call/src/components/videoViewer.tsx index 2323fd8..40cf6ac 100644 --- a/packages/call/src/components/videoViewer.tsx +++ b/packages/call/src/components/videoViewer.tsx @@ -1,7 +1,7 @@ import { VideoTrack, useParticipantTracks } from "@livekit/components-react"; import { TrackPublication } from "livekit-client"; -import { getRoom } from "../store"; -import { cn } from "@methanium/ui"; +import { useCall } from "../store"; +import { cn } from "@tensamin/ui"; import { Loader2 } from "lucide-react"; export default function VideoViewer({ @@ -17,7 +17,7 @@ export default function VideoViewer({ publication: TrackPublication; participantId: string; }) { - const room = getRoom(); + const room = useCall((state) => state.room); const tracks = useParticipantTracks([publication.source], { participantIdentity: participantId, room, diff --git a/packages/call/src/mediaShare/browser.ts b/packages/call/src/mediaShare/browser.ts deleted file mode 100644 index 2815b60..0000000 --- a/packages/call/src/mediaShare/browser.ts +++ /dev/null @@ -1,84 +0,0 @@ -import type { - MediaShareAdapter, - MediaShareCapabilities, - MediaShareKind, - MediaShareRequest, - MediaShareSession, - MediaShareSource, -} from "./types"; - -export async function listCameraSources(): Promise { - const permissionStream = await navigator.mediaDevices.getUserMedia({ - audio: false, - video: true, - }); - let devices: MediaDeviceInfo[]; - try { - devices = await navigator.mediaDevices.enumerateDevices(); - } finally { - permissionStream.getTracks().forEach((track) => track.stop()); - } - let cameraIndex = 0; - - return devices - .filter((device) => device.kind === "videoinput") - .map((device) => ({ - id: device.deviceId, - kind: "camera" as const, - name: device.label || `Camera ${++cameraIndex}`, - })); -} - -export async function startCamera( - sourceId?: string, -): Promise { - const stream = await navigator.mediaDevices.getUserMedia({ - audio: false, - video: sourceId ? { deviceId: { exact: sourceId } } : true, - }); - - return streamSession(stream); -} - -function streamSession(stream: MediaStream): MediaShareSession { - return { - tracks: stream.getTracks(), - stop: async () => { - stream.getTracks().forEach((track) => track.stop()); - }, - }; -} - -export class BrowserMediaShareAdapter implements MediaShareAdapter { - async getCapabilities(): Promise { - return { - runtime: "browser", - screenPicker: "native", - canShareScreenAudio: true, - canSelectScreenAudioOutput: false, - }; - } - - async listSources(kind: MediaShareKind): Promise { - return kind === "camera" ? listCameraSources() : []; - } - - async start(request: MediaShareRequest): Promise { - if (request.kind === "camera") { - return startCamera(request.sourceId); - } - - const options: DisplayMediaStreamOptions & { - systemAudio: "include" | "exclude"; - surfaceSwitching: "include" | "exclude"; - } = { - audio: request.includeAudio ?? true, - video: true, - systemAudio: request.includeAudio === false ? "exclude" : "include", - surfaceSwitching: "include", - }; - const stream = await navigator.mediaDevices.getDisplayMedia(options); - - return streamSession(stream); - } -} diff --git a/packages/call/src/mediaShare/controller.ts b/packages/call/src/mediaShare/controller.ts deleted file mode 100644 index f123834..0000000 --- a/packages/call/src/mediaShare/controller.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { log } from "@tensamin/shared/log"; -import { type LocalTrack, Room, Track } from "livekit-client"; -import { - getMediaShareAdapter, - type MediaShareKind, - type MediaShareRequest, - type MediaShareSession, -} from "."; - -export type LocalMediaShareSession = { - tracks: Array; - capture: MediaShareSession; -}; - -type MediaShareStoreState = { - screenShareSession: LocalMediaShareSession | null; - cameraSession: LocalMediaShareSession | null; -}; - -export function createMediaShareController({ - room, - getState, - setState, - getLocalParticipantId, - startWatching, - stopWatching, - syncParticipantState, -}: { - room: Room; - getState: () => MediaShareStoreState; - setState: ( - updater: - | Partial - | ((state: MediaShareStoreState) => Partial), - ) => void; - getLocalParticipantId: () => number | null; - startWatching: (participantId: number) => void; - stopWatching: (participantId: number) => void; - syncParticipantState: () => void; -}) { - function getSession(kind: MediaShareKind) { - return kind === "screen" - ? getState().screenShareSession - : getState().cameraSession; - } - - function setSession( - kind: MediaShareKind, - session: LocalMediaShareSession | null, - ) { - setState( - kind === "screen" - ? { screenShareSession: session } - : { cameraSession: session }, - ); - } - - async function clearPublishedShare(kind: MediaShareKind) { - const session = getSession(kind); - if (!session) return; - - setSession(kind, null); - await Promise.all( - session.tracks.map((track) => - room.localParticipant.unpublishTrack(track, true).catch((error) => { - log(1, "call", "red", `Failed to unpublish ${kind} track`, error); - }), - ), - ); - await session.capture.stop().catch((error) => { - log(1, "call", "red", `Failed to stop ${kind} capture`, error); - }); - - if (kind === "screen") { - const localParticipantId = getLocalParticipantId(); - if (localParticipantId != null) stopWatching(localParticipantId); - } - } - - async function publishShare( - kind: MediaShareKind, - capture: MediaShareSession, - ) { - if (capture.tracks.length === 0) { - await capture.stop(); - throw new Error(`No ${kind} tracks were created.`); - } - - const published: MediaStreamTrack[] = []; - try { - for (const track of capture.tracks) { - await room.localParticipant.publishTrack(track, { - source: - track.kind === Track.Kind.Audio - ? Track.Source.ScreenShareAudio - : kind === "screen" - ? Track.Source.ScreenShare - : Track.Source.Camera, - }); - published.push(track); - } - } catch (error) { - await Promise.all( - published.map((track) => - room.localParticipant.unpublishTrack(track, true), - ), - ); - await capture.stop(); - throw error; - } - - for (const track of capture.tracks) { - track.addEventListener( - "ended", - () => { - void stop(kind); - }, - { once: true }, - ); - } - - setSession(kind, { tracks: capture.tracks, capture }); - syncParticipantState(); - - if (kind === "screen") { - const localParticipantId = getLocalParticipantId(); - if (localParticipantId != null) startWatching(localParticipantId); - } - } - - async function start(request: MediaShareRequest) { - await clearPublishedShare(request.kind); - const capture = await getMediaShareAdapter().start(request); - await publishShare(request.kind, capture); - } - - async function stop(kind: MediaShareKind) { - await clearPublishedShare(kind); - syncParticipantState(); - } - - async function clearAll() { - await Promise.all([ - clearPublishedShare("screen"), - clearPublishedShare("camera"), - ]); - } - - return { clearAll, start, stop }; -} diff --git a/packages/call/src/mediaShare/electron.ts b/packages/call/src/mediaShare/electron.ts deleted file mode 100644 index 2e30dc3..0000000 --- a/packages/call/src/mediaShare/electron.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type {} from "@tensamin/shared/desktopMedia"; -import { BrowserMediaShareAdapter, listCameraSources } from "./browser"; -import type { - MediaShareCapabilities, - MediaShareKind, - MediaShareRequest, - MediaShareSession, - MediaShareSource, -} from "./types"; - -export class ElectronMediaShareAdapter extends BrowserMediaShareAdapter { - override async getCapabilities(): Promise { - const capabilities = - await window.tensaminDesktop?.media?.getScreenShareCapabilities?.(); - - return { - runtime: "electron", - screenPicker: "sources", - canShareScreenAudio: capabilities?.hasReliableSystemAudio ?? false, - canSelectScreenAudioOutput: - capabilities?.showAudioOutputSelector ?? false, - }; - } - - override async listSources( - kind: MediaShareKind, - ): Promise { - if (kind === "camera") { - return listCameraSources(); - } - - return ( - (await window.tensaminDesktop?.media?.listScreenShareSources?.()) ?? [] - ); - } - - override async start(request: MediaShareRequest): Promise { - if (request.kind === "camera") { - return super.start(request); - } - - if (!request.sourceId) { - throw new Error("Choose a screen or window to share."); - } - - const select = window.tensaminDesktop?.media?.selectScreenShareSource; - if (!select) { - throw new Error("Electron screen capture is unavailable."); - } - - await select(request.sourceId); - return super.start({ ...request, sourceId: undefined }); - } -} diff --git a/packages/call/src/mediaShare/index.ts b/packages/call/src/mediaShare/index.ts deleted file mode 100644 index 9706d4d..0000000 --- a/packages/call/src/mediaShare/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { BrowserMediaShareAdapter } from "./browser"; -import { ElectronMediaShareAdapter } from "./electron"; -import { TauriMediaShareAdapter } from "./tauri"; -import type { MediaShareAdapter } from "./types"; - -let adapter: MediaShareAdapter | null = null; - -export function getMediaShareAdapter(): MediaShareAdapter { - if (!adapter) { - adapter = window.tensaminMobileMedia - ? new TauriMediaShareAdapter() - : window.tensaminDesktop?.media - ? new ElectronMediaShareAdapter() - : new BrowserMediaShareAdapter(); - } - - return adapter; -} - -export type { - MediaShareAdapter, - MediaShareKind, - MediaShareRequest, - MediaShareSession, - MediaShareSource, -} from "./types"; diff --git a/packages/call/src/mediaShare/tauri.ts b/packages/call/src/mediaShare/tauri.ts deleted file mode 100644 index 7263599..0000000 --- a/packages/call/src/mediaShare/tauri.ts +++ /dev/null @@ -1,266 +0,0 @@ -import { listCameraSources, startCamera } from "./browser"; -import type { - MediaShareAdapter, - MediaShareCapabilities, - MediaShareKind, - MediaShareRequest, - MediaShareSession, - MediaShareSource, -} from "./types"; - -declare global { - interface Window { - tensaminMobileMedia?: { - startScreenShare: (includeAudio: boolean) => void; - stopScreenShare: () => void; - requestCameraPermission: () => void; - }; - } -} - -function eventDetail(event: Event): T { - return (event as CustomEvent).detail; -} - -function decodeBase64(value: string): Uint8Array { - const decoded = atob(value); - const bytes = new Uint8Array(decoded.length); - for (let index = 0; index < decoded.length; index += 1) { - bytes[index] = decoded.charCodeAt(index); - } - return bytes; -} - -async function requestCameraPermission() { - const bridge = window.tensaminMobileMedia; - if (!bridge) throw new Error("Tauri mobile media bridge is unavailable."); - - await new Promise((resolve, reject) => { - const timeout = window.setTimeout(() => { - cleanup(); - reject(new Error("Timed out waiting for camera permission.")); - }, 30_000); - const onPermission = (event: Event) => { - cleanup(); - const permission = eventDetail<{ camera: boolean }>(event); - if (permission.camera) { - resolve(); - } else { - reject(new Error("Camera permission was denied.")); - } - }; - const cleanup = () => { - window.clearTimeout(timeout); - window.removeEventListener( - "tensamin-mobile-camera-permission", - onPermission, - ); - }; - - window.addEventListener("tensamin-mobile-camera-permission", onPermission); - bridge.requestCameraPermission(); - }); -} - -async function startMobileScreen( - includeAudio: boolean, -): Promise { - const bridge = window.tensaminMobileMedia; - if (!bridge) throw new Error("Tauri mobile media bridge is unavailable."); - - return new Promise((resolve, reject) => { - const canvas = document.createElement("canvas"); - const context = canvas.getContext("2d"); - if (!context) { - reject(new Error("Unable to create the mobile capture canvas.")); - return; - } - - const stream = canvas.captureStream(15); - let audioContext: AudioContext | null = null; - let audioNode: ScriptProcessorNode | null = null; - let audioDestination: MediaStreamAudioDestinationNode | null = null; - const audioQueue: Float32Array[] = []; - let audioQueueOffset = 0; - let queuedAudioSamples = 0; - let started = false; - let resolved = false; - let firstFrame = false; - let lastError: Error | null = null; - let errorTimer = 0; - - const timeout = window.setTimeout(() => { - cleanup(); - bridge.stopScreenShare(); - reject( - lastError ?? new Error("Timed out starting mobile screen sharing."), - ); - }, 60_000); - - const complete = () => { - if (!started || !firstFrame || resolved) return; - resolved = true; - window.clearTimeout(timeout); - resolve({ - tracks: stream.getTracks(), - stop: async () => { - bridge.stopScreenShare(); - cleanup(); - }, - }); - }; - - const onStarted = (event: Event) => { - started = true; - const detail = eventDetail<{ includeAudio: boolean }>(event); - if (detail.includeAudio) { - audioContext = new AudioContext({ sampleRate: 48_000 }); - audioNode = audioContext.createScriptProcessor(2048, 0, 1); - audioDestination = audioContext.createMediaStreamDestination(); - audioNode.onaudioprocess = ({ outputBuffer }) => { - const output = outputBuffer.getChannelData(0); - output.fill(0); - let outputOffset = 0; - while (outputOffset < output.length && audioQueue.length > 0) { - const chunk = audioQueue[0]; - const available = chunk.length - audioQueueOffset; - const count = Math.min(available, output.length - outputOffset); - output.set( - chunk.subarray(audioQueueOffset, audioQueueOffset + count), - outputOffset, - ); - outputOffset += count; - audioQueueOffset += count; - queuedAudioSamples -= count; - if (audioQueueOffset === chunk.length) { - audioQueue.shift(); - audioQueueOffset = 0; - } - } - }; - audioNode.connect(audioDestination); - void audioContext.resume(); - for (const track of audioDestination.stream.getAudioTracks()) { - stream.addTrack(track); - } - } - complete(); - }; - - const onFrame = (event: Event) => { - const detail = eventDetail<{ - data: string; - mimeType: string; - width: number; - height: number; - }>(event); - const image = new Image(); - image.onload = () => { - if (canvas.width !== detail.width || canvas.height !== detail.height) { - canvas.width = detail.width; - canvas.height = detail.height; - } - context.drawImage(image, 0, 0, canvas.width, canvas.height); - firstFrame = true; - complete(); - }; - image.src = `data:${detail.mimeType};base64,${detail.data}`; - }; - - const onAudio = (event: Event) => { - const bytes = decodeBase64( - eventDetail<{ - data: string; - sampleRate: number; - channelCount: number; - encoding: "pcm16le"; - }>(event).data, - ); - const samples = new Int16Array( - bytes.buffer, - bytes.byteOffset, - Math.floor(bytes.byteLength / 2), - ); - const chunk = new Float32Array(samples.length); - for (let index = 0; index < samples.length; index += 1) { - chunk[index] = samples[index] / 32768; - } - audioQueue.push(chunk); - queuedAudioSamples += chunk.length; - const maximumQueuedSamples = 48_000 * 2; - while (queuedAudioSamples > maximumQueuedSamples && audioQueue.length) { - const dropped = audioQueue.shift(); - if (!dropped) break; - queuedAudioSamples -= dropped.length - audioQueueOffset; - audioQueueOffset = 0; - } - }; - - const onStopped = () => { - cleanup(); - if (!resolved) reject(new Error("Mobile screen sharing was stopped.")); - }; - - const onError = (event: Event) => { - lastError = new Error(eventDetail<{ message: string }>(event).message); - window.clearTimeout(errorTimer); - errorTimer = window.setTimeout(() => { - if (!started && !resolved) { - cleanup(); - reject(lastError ?? new Error("Mobile screen sharing failed.")); - } - }, 300); - }; - - const listeners: Array<[string, EventListener]> = [ - ["tensamin-mobile-screen-started", onStarted], - ["tensamin-mobile-screen-frame", onFrame], - ["tensamin-mobile-screen-audio", onAudio], - ["tensamin-mobile-screen-stopped", onStopped], - ["tensamin-mobile-screen-error", onError], - ]; - const cleanup = () => { - window.clearTimeout(timeout); - window.clearTimeout(errorTimer); - listeners.forEach(([name, listener]) => - window.removeEventListener(name, listener), - ); - stream.getTracks().forEach((track) => track.stop()); - audioNode?.disconnect(); - audioNode = null; - void audioContext?.close(); - audioContext = null; - }; - - listeners.forEach(([name, listener]) => - window.addEventListener(name, listener), - ); - bridge.startScreenShare(includeAudio); - }); -} - -export class TauriMediaShareAdapter implements MediaShareAdapter { - async getCapabilities(): Promise { - return { - runtime: "tauri", - screenPicker: "system", - canShareScreenAudio: true, - canSelectScreenAudioOutput: false, - }; - } - - async listSources(kind: MediaShareKind): Promise { - if (kind !== "camera") return []; - await requestCameraPermission(); - return listCameraSources(); - } - - async start(request: MediaShareRequest): Promise { - if (request.kind === "screen") { - return startMobileScreen(request.includeAudio ?? true); - } - - await requestCameraPermission(); - return startCamera(request.sourceId); - } -} diff --git a/packages/call/src/mediaShare/types.ts b/packages/call/src/mediaShare/types.ts deleted file mode 100644 index 39d1d98..0000000 --- a/packages/call/src/mediaShare/types.ts +++ /dev/null @@ -1,33 +0,0 @@ -export type MediaShareKind = "screen" | "camera"; - -export type MediaShareSource = { - id: string; - kind: "screen" | "window" | "camera"; - name: string; - subtitle?: string | null; - thumbnail?: string | null; -}; - -export type MediaShareCapabilities = { - runtime: "browser" | "electron" | "tauri"; - screenPicker: "native" | "sources" | "system"; - canShareScreenAudio: boolean; - canSelectScreenAudioOutput: boolean; -}; - -export type MediaShareRequest = { - kind: MediaShareKind; - sourceId?: string; - includeAudio?: boolean; -}; - -export type MediaShareSession = { - tracks: MediaStreamTrack[]; - stop: () => Promise; -}; - -export interface MediaShareAdapter { - getCapabilities(): Promise; - listSources(kind: MediaShareKind): Promise; - start(request: MediaShareRequest): Promise; -} diff --git a/packages/call/src/screen.tsx b/packages/call/src/screen.tsx index 67511dd..49c6a11 100644 --- a/packages/call/src/screen.tsx +++ b/packages/call/src/screen.tsx @@ -1,18 +1,11 @@ -import { useEffect, useRef, useState } from "react"; -import { createPortal } from "react-dom"; -import { - setCallIsFullscreen, - setCallIsPopout, - triggerCallLayoutCalculation, - useCall, -} from "./store"; +import { useCall } from "./store"; import MainLayout from "./views/main/layout"; import MainGrid from "./views/main/grid"; import MainFocused from "./views/main/focused"; import Preview from "./views/preview"; -function ScreenContent() { +export default function Screen() { const view = useCall((state) => state.view); return view === "preview" ? ( @@ -21,117 +14,3 @@ function ScreenContent() { {view === "grid" ? : } ); } - -// Popout Window -function copyDocumentStyles(targetDocument: Document) { - for (const node of document.querySelectorAll( - 'link[rel="stylesheet"], style', - )) { - targetDocument.head.appendChild(node.cloneNode(true)); - } -} - -function syncDocumentAttributes(targetDocument: Document) { - for (const attribute of document.documentElement.attributes) { - targetDocument.documentElement.setAttribute( - attribute.name, - attribute.value, - ); - } - targetDocument.body.className = document.body.className; -} - -function PopoutScreen() { - const closeCheckRef = useRef(null); - const [container, setContainer] = useState(null); - - useEffect(() => { - const popoutWindow = window.open( - "", - "tensamin-call-popout", - "popup,width=1280,height=720", - ); - - if (!popoutWindow) { - setCallIsFullscreen(false); - setCallIsPopout(false); - return; - } - - let isMounted = true; - - popoutWindow.document.title = document.title; - popoutWindow.document.body.innerHTML = ""; - popoutWindow.document.body.style.margin = "0"; - copyDocumentStyles(popoutWindow.document); - syncDocumentAttributes(popoutWindow.document); - - popoutWindow.document.documentElement.style.height = "100%"; - popoutWindow.document.body.style.height = "100%"; - - const containerElement = popoutWindow.document.createElement("div"); - containerElement.style.width = "100%"; - containerElement.style.height = "100%"; - popoutWindow.document.body.appendChild(containerElement); - queueMicrotask(() => { - if (isMounted) { - setContainer(containerElement); - } - }); - - const closePopout = () => { - setCallIsFullscreen(false); - setCallIsPopout(false); - }; - const syncLayout = () => triggerCallLayoutCalculation(); - - popoutWindow.addEventListener("beforeunload", closePopout); - popoutWindow.addEventListener("resize", syncLayout); - popoutWindow.focus(); - - closeCheckRef.current = window.setInterval(() => { - if (popoutWindow.closed) { - closePopout(); - } - }, 500); - - triggerCallLayoutCalculation(); - - return () => { - isMounted = false; - popoutWindow.removeEventListener("beforeunload", closePopout); - popoutWindow.removeEventListener("resize", syncLayout); - - if (closeCheckRef.current) { - window.clearInterval(closeCheckRef.current); - closeCheckRef.current = null; - } - - if (!popoutWindow.closed) { - popoutWindow.close(); - } - - setCallIsFullscreen(false); - triggerCallLayoutCalculation(); - }; - }, []); - - if (!container) { - return null; - } - - return createPortal(, container); -} - -export default function Screen() { - const callIsPopout = useCall((state) => state.callIsPopout); - - return callIsPopout ? ( -
-

The window is popped out.

- -
- ) : ( - - ); -} diff --git a/packages/call/src/screenshare.ts b/packages/call/src/screenshare.ts new file mode 100644 index 0000000..21b8b71 --- /dev/null +++ b/packages/call/src/screenshare.ts @@ -0,0 +1,235 @@ +import { invoke } from "@tauri-apps/api/core"; +import { log } from "@tensamin/shared/log"; +import { + type LocalTrack, + Room, + type ScreenShareCaptureOptions, + Track, +} from "livekit-client"; + +export type ScreenShareSession = { + tracks: Array; + cleanup?: () => void; +}; + +type ScreenShareStoreState = { + screenShareSession: ScreenShareSession | null; +}; + +type ScreenShareStoreSetState = ( + updater: + | Partial + | ((state: ScreenShareStoreState) => Partial), +) => void; + +type ScreenShareControllerOptions = { + room: Room; + getState: () => ScreenShareStoreState; + setState: ScreenShareStoreSetState; + getLocalParticipantId: () => number | null; + startWatching: (participantId: number, options?: { focus?: boolean }) => void; + stopWatching: (participantId: number) => void; + syncParticipantState: () => void; +}; + +export function createScreenShareController({ + room, + getState, + setState, + getLocalParticipantId, + startWatching, + stopWatching, + syncParticipantState, +}: ScreenShareControllerOptions) { + async function clearPublishedScreenShare() { + const screenShareSession = getState().screenShareSession; + + if (!screenShareSession) { + return; + } + + await Promise.all( + screenShareSession.tracks.map((track) => + room.localParticipant.unpublishTrack(track, true).catch((error) => { + log( + 1, + "call", + "red", + "Failed to unpublish screen share track", + error, + ); + }), + ), + ); + + screenShareSession.cleanup?.(); + + const localParticipantId = getLocalParticipantId(); + + if (localParticipantId != null) { + stopWatching(localParticipantId); + } + + setState({ screenShareSession: null }); + } + + async function publishScreenShareTracks( + tracks: Array, + cleanup?: () => void, + ) { + if (tracks.length === 0) { + throw new Error("No screen share tracks were created."); + } + + await Promise.all( + tracks.map((track) => + room.localParticipant.publishTrack(track, { + source: + track.kind === Track.Kind.Video + ? Track.Source.ScreenShare + : Track.Source.ScreenShareAudio, + }), + ), + ); + + for (const track of tracks) { + const mediaStreamTrack = + track instanceof MediaStreamTrack ? track : track.mediaStreamTrack; + + mediaStreamTrack.addEventListener( + "ended", + () => { + void stopScreenShare(); + }, + { once: true }, + ); + } + + setState({ screenShareSession: { tracks, cleanup } }); + syncParticipantState(); + + const localParticipantId = getLocalParticipantId(); + + if (localParticipantId != null) { + startWatching(localParticipantId, { focus: true }); + } + } + + async function startScreenShare(options?: ScreenShareCaptureOptions) { + await clearPublishedScreenShare(); + + const tracks = await room.localParticipant.createScreenTracks(options); + + await publishScreenShareTracks(tracks, () => { + tracks.forEach((track) => track.stop()); + }); + } + + async function startLinuxDesktopScreenShare(sourceId: string) { + await clearPublishedScreenShare(); + + const canvas = document.createElement("canvas"); + canvas.width = 1280; + canvas.height = 720; + canvas.style.display = "none"; + document.body.appendChild(canvas); + + const context = canvas.getContext("2d"); + + if (!context) { + canvas.remove(); + throw new Error("Failed to initialize the screen share canvas."); + } + + const stream = canvas.captureStream(8); + const videoTrack = stream.getVideoTracks()[0]; + + if (!videoTrack) { + canvas.remove(); + throw new Error("Failed to create a video track for screen sharing."); + } + + const image = new Image(); + let stopped = false; + let frameRequestInFlight = false; + + const renderFrame = async () => { + if (stopped || frameRequestInFlight) { + return; + } + + frameRequestInFlight = true; + + try { + const dataUrl = await invoke("capture_screen_share_frame", { + sourceId, + }); + + await new Promise((resolve, reject) => { + image.onload = () => resolve(); + image.onerror = () => + reject(new Error("Failed to decode screen share frame.")); + image.src = dataUrl; + }); + + if ( + canvas.width !== image.naturalWidth || + canvas.height !== image.naturalHeight + ) { + canvas.width = image.naturalWidth; + canvas.height = image.naturalHeight; + } + + context.drawImage(image, 0, 0, canvas.width, canvas.height); + } finally { + frameRequestInFlight = false; + } + }; + + await renderFrame(); + + const interval = window.setInterval(() => { + void renderFrame().catch((error) => { + log( + 1, + "call", + "red", + "Failed to capture Linux screen share frame", + error, + ); + }); + }, 125); + + await publishScreenShareTracks([videoTrack], () => { + stopped = true; + window.clearInterval(interval); + stream.getTracks().forEach((track) => track.stop()); + canvas.remove(); + }); + } + + async function stopScreenShare() { + await clearPublishedScreenShare(); + syncParticipantState(); + } + + async function setScreenShareEnabled( + enabled: boolean, + options?: ScreenShareCaptureOptions, + ) { + if (enabled) { + await startScreenShare(options); + return; + } + + await stopScreenShare(); + } + + return { + clearPublishedScreenShare, + startLinuxDesktopScreenShare, + startScreenShare, + stopScreenShare, + setScreenShareEnabled, + }; +} diff --git a/packages/call/src/speakingIndicator.ts b/packages/call/src/speakingIndicator.ts deleted file mode 100644 index 94ff030..0000000 --- a/packages/call/src/speakingIndicator.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { - clearSpeakingParticipants, - removeSpeakingParticipant, - setMicGated, - updateSpeakingParticipants, -} from "./speakingState"; - -const SPEAKING_THRESHOLD = 0.01; -const SPEAKING_HANGTIME_MS = 500; -const ANALYSIS_INTERVAL_MS = 30; -const FFT_SIZE = 256; - -class SpeakingDetector { - private audioContext: AudioContext | null = null; - private entries = new Map< - number, - { - source: MediaStreamAudioSourceNode; - analyser: AnalyserNode; - track: MediaStreamTrack; - originalTrack?: MediaStreamTrack; - lastSpeakingTime: number; - isSpeaking: boolean; - } - >(); - private intervalId: ReturnType | null = null; - private deaf = false; - private gateThresholdStart = -50; - private gateThresholdEnd = -40; - private localParticipantId: number | null = null; - private localMicGateClosed = false; - - private ensureAudioContext(): AudioContext { - if (!this.audioContext) { - this.audioContext = new AudioContext(); - } - if (this.audioContext.state === "suspended") { - void this.audioContext.resume(); - } - return this.audioContext; - } - - setLocalParticipantId(id: number) { - this.localParticipantId = id; - } - - setGateThresholds(start: number, end: number) { - this.gateThresholdStart = start; - this.gateThresholdEnd = end; - } - - addTrack( - participantId: number, - track: MediaStreamTrack, - originalTrack?: MediaStreamTrack, - ) { - if (track.kind !== "audio") return; - - this.removeParticipant(participantId); - - const ctx = this.ensureAudioContext(); - const stream = new MediaStream([track]); - const source = ctx.createMediaStreamSource(stream); - const analyser = ctx.createAnalyser(); - analyser.fftSize = FFT_SIZE; - source.connect(analyser); - - this.entries.set(participantId, { - source, - analyser, - track, - originalTrack, - lastSpeakingTime: 0, - isSpeaking: false, - }); - - if (!this.intervalId) { - this.startLoop(); - } - } - - removeParticipant(participantId: number) { - const entry = this.entries.get(participantId); - if (!entry) return; - - try { - entry.source.disconnect(); - } catch { - // ignore - } - this.entries.delete(participantId); - - removeSpeakingParticipant(participantId); - - if (participantId === this.localParticipantId && this.localMicGateClosed) { - this.muteLocalTrack(false); - } - } - - setDeaf(deaf: boolean) { - this.deaf = deaf; - if (deaf) { - for (const entry of this.entries.values()) { - entry.isSpeaking = false; - entry.lastSpeakingTime = 0; - } - clearSpeakingParticipants(); - } - } - - private startLoop() { - if (this.intervalId) return; - this.intervalId = setInterval(() => this.analyse(), ANALYSIS_INTERVAL_MS); - } - - private stopLoop() { - if (this.intervalId) { - clearInterval(this.intervalId); - this.intervalId = null; - } - } - - private muteLocalTrack(muted: boolean) { - const entry = this.localParticipantId - ? this.entries.get(this.localParticipantId) - : undefined; - const target = entry?.originalTrack ?? entry?.track; - - if (target && target.enabled === muted) { - target.enabled = !muted; - } - - this.localMicGateClosed = muted; - setMicGated(muted); - } - - private applyNoiseGate(rms: number) { - const db = 20 * Math.log10(Math.max(rms, 0.0001)); - - if (!this.localMicGateClosed && db < this.gateThresholdStart) { - this.muteLocalTrack(true); - } else if (this.localMicGateClosed && db > this.gateThresholdEnd) { - this.muteLocalTrack(false); - } - } - - private analyse() { - if (this.deaf || this.entries.size === 0) return; - - const now = Date.now(); - const changed = new Map(); - - for (const [participantId, entry] of this.entries) { - const { analyser, track } = entry; - - if (track.muted || track.readyState === "ended" || !track.enabled) { - if (entry.isSpeaking) { - entry.isSpeaking = false; - entry.lastSpeakingTime = 0; - changed.set(participantId, false); - } - continue; - } - - const bufferLength = analyser.frequencyBinCount; - const dataArray = new Uint8Array(bufferLength); - analyser.getByteTimeDomainData(dataArray); - - let sum = 0; - for (let i = 0; i < bufferLength; i++) { - const sample = (dataArray[i] - 128) / 128.0; - sum += sample * sample; - } - const rms = Math.sqrt(sum / bufferLength); - - let nextIsSpeaking = entry.isSpeaking; - if (rms > SPEAKING_THRESHOLD) { - entry.lastSpeakingTime = now; - nextIsSpeaking = true; - } else if (now - entry.lastSpeakingTime > SPEAKING_HANGTIME_MS) { - nextIsSpeaking = false; - } - - if (participantId === this.localParticipantId) { - this.applyNoiseGate(rms); - if (this.localMicGateClosed) { - nextIsSpeaking = false; - } - } - - if (nextIsSpeaking !== entry.isSpeaking) { - entry.isSpeaking = nextIsSpeaking; - changed.set(participantId, nextIsSpeaking); - } - } - - if (changed.size > 0) { - updateSpeakingParticipants(changed); - } - } - - dispose() { - this.stopLoop(); - for (const id of Array.from(this.entries.keys())) { - this.removeParticipant(id); - } - this.entries.clear(); - if (this.audioContext) { - void this.audioContext.close(); - this.audioContext = null; - } - } -} - -let detectorInstance: SpeakingDetector | null = null; - -export function getSpeakingDetector(): SpeakingDetector { - if (!detectorInstance) { - detectorInstance = new SpeakingDetector(); - } - return detectorInstance; -} - -export function disposeSpeakingDetector(): void { - if (detectorInstance) { - detectorInstance.dispose(); - detectorInstance = null; - } -} diff --git a/packages/call/src/speakingState.ts b/packages/call/src/speakingState.ts deleted file mode 100644 index c4f5198..0000000 --- a/packages/call/src/speakingState.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { create } from "zustand"; - -const useSpeakingState = create<{ - speakingParticipantIds: Set; - lastSpeakingParticipantId: number | null; - micGated: boolean; -}>(() => ({ - speakingParticipantIds: new Set(), - lastSpeakingParticipantId: null, - micGated: false, -})); - -export function setMicGated(micGated: boolean) { - useSpeakingState.setState({ micGated }); -} - -export function clearSpeakingParticipants() { - useSpeakingState.setState({ speakingParticipantIds: new Set() }); -} - -export function removeSpeakingParticipant(participantId: number) { - useSpeakingState.setState((state) => { - const wasSpeaking = state.speakingParticipantIds.has(participantId); - const wasLastSpeaking = state.lastSpeakingParticipantId === participantId; - - if (!wasSpeaking && !wasLastSpeaking) return state; - - const next = new Set(state.speakingParticipantIds); - next.delete(participantId); - return { - speakingParticipantIds: next, - lastSpeakingParticipantId: wasLastSpeaking - ? null - : state.lastSpeakingParticipantId, - }; - }); -} - -export function updateSpeakingParticipants(changed: Map) { - useSpeakingState.setState((state) => { - let hasDiff = false; - const next = new Set(state.speakingParticipantIds); - let lastSpeakingParticipantId = state.lastSpeakingParticipantId; - - for (const [id, speaking] of changed) { - if (speaking) { - if (!next.has(id)) { - next.add(id); - lastSpeakingParticipantId = id; - hasDiff = true; - } - } else if (next.has(id)) { - next.delete(id); - hasDiff = true; - } - } - - return hasDiff - ? { speakingParticipantIds: next, lastSpeakingParticipantId } - : state; - }); -} - -export function useLastSpeakingParticipantId(): number | null { - return useSpeakingState((state) => state.lastSpeakingParticipantId); -} - -export function useIsSpeaking(participantId: number): boolean { - return useSpeakingState((state) => - state.speakingParticipantIds.has(participantId), - ); -} diff --git a/packages/call/src/store.tsx b/packages/call/src/store.tsx index bbf3f4e..e605ece 100644 --- a/packages/call/src/store.tsx +++ b/packages/call/src/store.tsx @@ -1,46 +1,33 @@ import { useCallback, useEffect, useMemo, useRef } from "react"; import { create } from "zustand"; import { useLocation, useNavigate } from "@tanstack/react-router"; -import { useMTP } from "@tensamin/mtp"; +import { useTTP } from "@tensamin/ttp"; import { log, toast } from "@tensamin/shared/log"; -import { mtp } from "@tensamin/shared/data"; -import { playSound, stopSound } from "@tensamin/shared/sounds"; -import { bytesToBase64 } from "mtp"; -import { - deriveCallSecretId, - kemPublicKeyFromPublicKeyBundle, - unwrapCallSecret, - wrapCallSecret, -} from "@tensamin/crypto/callSecret"; +import { ttp } from "@tensamin/shared/data"; +import { useCrypto } from "@tensamin/crypto/context"; import { useStorage } from "@tensamin/storage/context"; -import { useSession } from "@tensamin/storage/session"; import { useUser } from "@tensamin/user/context"; import { DeepFilterNoiseFilterProcessor } from "deepfilternet3-noise-filter"; import { ExternalE2EEKeyProvider, LocalAudioTrack, - type LocalTrackPublication, type Participant, type RemoteParticipant, type RemoteTrackPublication, Room, RoomEvent, type RemoteTrack, + type ScreenShareCaptureOptions, Track, setLogExtension, getLogger, } from "livekit-client"; import z from "zod"; +import { toast as sonnerToast } from "sonner"; import { - createMediaShareController, - type LocalMediaShareSession, -} from "./mediaShare/controller"; -import type { MediaShareRequest } from "./mediaShare"; -import { - getSpeakingDetector, - disposeSpeakingDetector, -} from "./speakingIndicator"; -import InvitePopup from "./components/invitePopup"; + createScreenShareController, + type ScreenShareSession, +} from "./screenshare"; // logging setLogExtension( @@ -51,141 +38,89 @@ setLogExtension( getLogger("tensamin"), ); +type CallState = "closed" | "closing" | "connecting" | "open" | "encrypting"; type CallView = "preview" | "focused" | "grid"; -type ProtocolCallSecret = NonNullable< - z.infer["CallSecret"] ->; -type WrappedCallSecret = { - secretId: string; - versionNumber: number; - encryptedSecret: Uint8Array; - kemCiphertext: Uint8Array; - wrappingScheme: string; -}; type CurrentCallData = - (z.infer & { exists: boolean }) | null; + | (z.infer & { exists: boolean }) + | null; +type NavigateFn = (options: { + to: string; + search?: Record; +}) => Promise; type SendFn = ( type: string, data: Record, ) => Promise<{ data: unknown }>; +type GetSharedSecretFn = ( + privateKey: unknown, + ownPublicKey: string, + remotePublicKey: string, +) => Promise; +type DecryptTextFn = (sharedSecret: string, text: string) => Promise; +type EncryptTextFn = (sharedSecret: string, text: string) => Promise; type LoadFn = (key: string) => Promise; +type GetUserFn = (userId: number) => Promise<{ public_key: string }>; type RemoteVideoTrackSelector = Track.Kind | Track.Source; type Runtime = { - navigate: (options: { - to: string; - search?: Record; - }) => Promise; + navigate: NavigateFn; send: SendFn; + getSharedSecret: GetSharedSecretFn; + decryptText: DecryptTextFn; + encryptText: EncryptTextFn; load: LoadFn; - getPublicKey: (userId: number) => Promise; + getUser: GetUserFn; }; -let _keyProvider: ExternalE2EEKeyProvider | null = null; -let _e2eeWorker: Worker | null = null; -let _room: Room | null = null; - -function getKeyProvider(): ExternalE2EEKeyProvider { - if (!_keyProvider) { - _keyProvider = new ExternalE2EEKeyProvider(); - } - return _keyProvider; -} - -function getE2EEWorker(): Worker { - if (!_e2eeWorker) { - _e2eeWorker = new Worker( - new URL("livekit-client/e2ee-worker", import.meta.url), - ); - } - return _e2eeWorker; -} - -export function getRoom(): Room { - if (!_room) { - _room = new Room({ - dynacast: true, - adaptiveStream: true, - loggerName: "tensamin", - encryption: { - keyProvider: getKeyProvider(), - worker: getE2EEWorker(), - }, - }); - } - return _room; -} +type CallStore = { + state: CallState; + view: CallView; + invitedUserId: number | null; + callId: string | null; + callSecret: string | null; + livekitToken: string | null; + currentCallData: CurrentCallData; + deaf: boolean; + micEnabled: boolean; + screenShareEnabled: boolean; + screenShareSession: ScreenShareSession | null; + focusedParticipantId: number | null; + usersInFocusedViewHidden: boolean; + watchedStreamParticipantIds: number[]; + pendingWatchedParticipantIds: number[]; + activeScreenShareParticipantIds: number[]; + isEncrypted: boolean; + callIsFullscreen: boolean; + callIsPopout: boolean; + layoutVersion: number; + screenRef: React.RefObject | null; + room: Room; + keyProvider: ExternalE2EEKeyProvider; + e2eeWorker: Worker; + runtime: Runtime | null; +}; +const keyProvider = new ExternalE2EEKeyProvider(); +const e2eeWorker = new Worker( + new URL("livekit-client/e2ee-worker", import.meta.url), +); +const room = new Room({ + dynacast: true, + adaptiveStream: true, + loggerName: "tensamin", + encryption: { + keyProvider, + worker: e2eeWorker, + }, +}); const remoteAudioElements = new Map(); -let callJingle: HTMLAudioElement | null = null; -let callJingleGeneration = 0; -const CALL_SECRET_VERSION = 1; const SCREEN_SHARE_PREVIEW_MAX_WIDTH = 320; const SCREEN_SHARE_PREVIEW_MAX_HEIGHT = 180; const SCREEN_SHARE_PREVIEW_QUALITY = 0.7; const SCREEN_SHARE_PREVIEW_TIMEOUT_MS = 5000; -async function startCallJingle(shouldPlay: () => boolean) { - const generation = ++callJingleGeneration; - stopSound(callJingle); - callJingle = null; - - const jingle = await requireRuntime(useCall.getState().runtime).load( - "settings.call_jingle", - ); - - if (generation !== callJingleGeneration || !shouldPlay()) { - return; - } - - callJingle = playSound( - jingle === "jingle_2" ? "call_jingle_2" : "call_jingle_1", - true, - ); -} - -function stopCallJingle() { - callJingleGeneration += 1; - stopSound(callJingle); - callJingle = null; -} - -function protocolBytes(bytes: Uint8Array): Uint8Array { - return new Uint8Array(bytes); -} - -function normalizeWrappedCallSecret( - callSecret: WrappedCallSecret | ProtocolCallSecret, -): WrappedCallSecret { - if ("secretId" in callSecret) { - return callSecret; - } - - return { - secretId: callSecret.SecretId, - versionNumber: callSecret.VersionNumber, - encryptedSecret: callSecret.EncryptedSecret, - kemCiphertext: callSecret.KemCiphertext, - wrappingScheme: callSecret.WrappingScheme, - }; -} - -function protocolCallSecret(callSecret: WrappedCallSecret): ProtocolCallSecret { - return { - SecretId: callSecret.secretId, - VersionNumber: callSecret.versionNumber, - EncryptedSecret: protocolBytes(callSecret.encryptedSecret), - KemCiphertext: protocolBytes(callSecret.kemCiphertext), - WrappingScheme: callSecret.wrappingScheme, - }; -} - -function randomCallSecret(): string { - return bytesToBase64(globalThis.crypto.getRandomValues(new Uint8Array(32))); -} - // audio helpers function attachRemoteAudio(trackSid: string, element: HTMLMediaElement) { const existingElement = remoteAudioElements.get(trackSid); @@ -217,7 +152,7 @@ function clearRemoteAudio() { } } -export function getParticipantId(identity: string | undefined): number | null { +function getParticipantId(identity: string | undefined): number | null { if (!identity) { return null; } @@ -227,7 +162,6 @@ export function getParticipantId(identity: string | undefined): number | null { } function getAllParticipants(): Participant[] { - const room = getRoom(); return [...room.remoteParticipants.values(), room.localParticipant]; } @@ -245,7 +179,7 @@ function getTrackPublicationBySource( } function getRemoteParticipant(participantId: number) { - return [...getRoom().remoteParticipants.values()].find( + return [...room.remoteParticipants.values()].find( (participant) => getParticipantId(participant.identity) === participantId, ); } @@ -264,26 +198,13 @@ function matchesRemoteTrackSelector( } function syncRemoteParticipantTrackSubscriptions(participantId: number) { - const state = useCall.getState(); - const watchesScreen = - state.watchedStreamParticipantIds.includes(participantId); - const cameraDisabled = - state.disabledCameraParticipantIds.includes(participantId); - for (const publication of getRemoteTrackPublications(participantId)) { - const subscribed = - publication.source === Track.Source.Camera - ? !cameraDisabled - : publication.source === Track.Source.ScreenShare || - publication.source === Track.Source.ScreenShareAudio - ? watchesScreen - : publication.kind === Track.Kind.Audio; - publication.setSubscribed(subscribed); + publication.setSubscribed(publication.kind === Track.Kind.Audio); } } function syncAllRemoteTrackSubscriptions() { - for (const participant of getRoom().remoteParticipants.values()) { + for (const participant of room.remoteParticipants.values()) { const participantId = getParticipantId(participant.identity); if (participantId != null) { @@ -324,7 +245,7 @@ function hasParticipant(participantId: number) { function getLocalScreenShareTrack(): MediaStreamTrack | null { const track = getTrackPublicationBySource( - getRoom().localParticipant, + room.localParticipant, Track.Source.ScreenShare, )?.track; @@ -343,15 +264,13 @@ async function updateLocalParticipantAttributes( screenSharePreviewLength: attributes.screenSharePreview?.length ?? 0, }); - await getRoom() - .localParticipant.setAttributes(attributes) - .catch((error) => { - log(1, "call", "red", "Failed to update local participant attributes", { - attributes: Object.keys(attributes), - error, - }); - throw error; + await room.localParticipant.setAttributes(attributes).catch((error) => { + log(1, "call", "red", "Failed to update local participant attributes", { + attributes: Object.keys(attributes), + error, }); + throw error; + }); log(2, "call", "purple", "Updated local participant attributes", { attributeKeys: Object.keys(attributes), @@ -538,8 +457,6 @@ function syncScreenShareParticipants() { watchedStreamParticipantIds, pendingWatchedParticipantIds, focusedParticipantId, - focusedParticipantType: - focusedParticipantId == null ? null : state.focusedParticipantType, view: state.view === "focused" && focusedParticipantId == null ? "grid" @@ -557,7 +474,7 @@ function requireRuntime(runtime: Runtime | null): Runtime { } export function getRoomMetadata() { - const roomMetadata = getRoom().metadata; + const roomMetadata = room.metadata; try { const data = JSON.parse(roomMetadata || '{"admins": []}'); return data as { admins: number[] }; @@ -568,13 +485,10 @@ export function getRoomMetadata() { // Sync local participant flags and screen-share derived state for the active call UI. export function syncParticipantState() { - const { cameraSession, screenShareSession } = useCall.getState(); - const room = getRoom(); + const { room, screenShareSession } = useCall.getState(); useCall.setState({ micEnabled: room.localParticipant.isMicrophoneEnabled, - cameraEnabled: - cameraSession != null || room.localParticipant.isCameraEnabled, screenShareEnabled: screenShareSession != null || room.localParticipant.isScreenShareEnabled, isEncrypted: @@ -649,16 +563,16 @@ export async function openCallPage(callId: string) { // Request the LiveKit token that authorizes this client to join a call. export async function getCallToken(callId: string): Promise { const response = await requireRuntime(useCall.getState().runtime) - .send("CallToken", { - CallId: callId, + .send("call_token", { + call_id: callId, }) .catch((err) => { log(1, "call", "red", "Failed to get call secret", err); throw err; }); - const data = response.data as { CallToken: string }; - return data.CallToken; + const data = response.data as { call_token: string }; + return data.call_token; } // Encrypt the active call secret for a recipient and send the call invite. @@ -670,45 +584,36 @@ export async function sendCallInvite(userId: number) { throw new Error("Cannot send call invite without an active call."); } - const remotePublicKey = await runtime.getPublicKey(userId); - const secretId = deriveCallSecretId(callId); - const wrapped = await wrapCallSecret({ + const ownUserId = (await runtime.load("user_id")) as number; + const privateKey = await runtime.load("private_key"); + const ownPublicKey = await runtime + .getUser(ownUserId) + .then((data) => data.public_key); + const remotePublicKey = await runtime + .getUser(userId) + .then((data) => data.public_key); + const sharedSecret = await runtime.getSharedSecret( + privateKey, + ownPublicKey, + remotePublicKey, + ); + const encryptedCallSecret = await runtime.encryptText( + sharedSecret, callSecret, - recipientKemPublicKey: kemPublicKeyFromPublicKeyBundle(remotePublicKey), - callId, - secretId, - version: CALL_SECRET_VERSION, - }); + ); - await runtime.send("CallInvite", { - ReceiverId: userId, - CallId: callId, - CallSecret: { - SecretId: secretId, - VersionNumber: CALL_SECRET_VERSION, - EncryptedSecret: protocolBytes(wrapped.encryptedSecret), - KemCiphertext: protocolBytes(wrapped.kemCiphertext), - WrappingScheme: wrapped.wrappingScheme, - }, + await runtime.send("call_invite", { + receiver_id: userId, + call_id: callId, + call_secret: encryptedCallSecret, }); } // Start tracking a participant's shared screen in the call UI. export function startWatchingStream(participantId: number) { const trackReady = getScreenShareTrackForParticipant(participantId) != null; - const alreadyWatching = useCall - .getState() - .watchedStreamParticipantIds.includes(participantId); - const localParticipantId = getParticipantId( - getRoom().localParticipant.identity, - ); - - if (!alreadyWatching && participantId !== localParticipantId) { - playSound("stream_watch_start"); - } setParticipantTrackSubscribed(participantId, Track.Source.ScreenShare); - setParticipantTrackSubscribed(participantId, Track.Source.ScreenShareAudio); useCall.setState((state) => ({ watchedStreamParticipantIds: state.watchedStreamParticipantIds.includes( @@ -722,7 +627,6 @@ export function startWatchingStream(participantId: number) { ? state.pendingWatchedParticipantIds : [...state.pendingWatchedParticipantIds, participantId], focusedParticipantId: participantId, - focusedParticipantType: "stream", })); } @@ -741,52 +645,17 @@ export function setParticipantTrackSubscribed( syncParticipantState(); } -export function setParticipantCameraDisabled( - participantId: number, - disabled: boolean, -) { - useCall.setState((state) => ({ - disabledCameraParticipantIds: disabled - ? state.disabledCameraParticipantIds.includes(participantId) - ? state.disabledCameraParticipantIds - : [...state.disabledCameraParticipantIds, participantId] - : state.disabledCameraParticipantIds.filter((id) => id !== participantId), - })); - setParticipantTrackSubscribed(participantId, Track.Source.Camera, !disabled); -} - // Focus a participant in the main call view even when they are not sharing a screen. -export function focusParticipant( - participantId: number, - type: "user" | "stream" = "user", -) { +export function focusParticipant(participantId: number) { useCall.setState({ focusedParticipantId: participantId, - lastFocusedParticipantId: participantId, - focusedParticipantType: type, view: "focused", }); } // Stop tracking a participant's shared screen and clean up related UI state. export function stopWatchingStream(participantId: number) { - const wasWatching = useCall - .getState() - .watchedStreamParticipantIds.includes(participantId); - const localParticipantId = getParticipantId( - getRoom().localParticipant.identity, - ); - - if (wasWatching && participantId !== localParticipantId) { - playSound("stream_watch_end"); - } - setParticipantTrackSubscribed(participantId, Track.Source.ScreenShare, false); - setParticipantTrackSubscribed( - participantId, - Track.Source.ScreenShareAudio, - false, - ); useCall.setState((state) => ({ watchedStreamParticipantIds: state.watchedStreamParticipantIds.filter( @@ -799,10 +668,6 @@ export function stopWatchingStream(participantId: number) { state.focusedParticipantId === participantId ? null : state.focusedParticipantId, - focusedParticipantType: - state.focusedParticipantId === participantId - ? null - : state.focusedParticipantType, view: state.view === "focused" && state.focusedParticipantId === participantId ? "grid" @@ -821,44 +686,33 @@ export function stopWatchingFocusedStream() { stopWatchingStream(focusedParticipantId); } -let mediaShareController: ReturnType | null = - null; +let screenShareController: ReturnType< + typeof createScreenShareController +> | null = null; -function getNoiseFilterAssetBaseUrl() { - if (window.location.protocol === "file:") { - return new URL("./assets", document.baseURI).href.replace(/\/$/, ""); - } - - return "/assets"; -} - -function getMediaShareController() { - if (!mediaShareController) { - mediaShareController = createMediaShareController({ - room: getRoom(), +function getScreenShareController() { + if (!screenShareController) { + screenShareController = createScreenShareController({ + room, getState: () => ({ screenShareSession: useCall.getState().screenShareSession, - cameraSession: useCall.getState().cameraSession, }), setState: (updater) => { useCall.setState((state) => typeof updater === "function" - ? updater({ - screenShareSession: state.screenShareSession, - cameraSession: state.cameraSession, - }) + ? updater({ screenShareSession: state.screenShareSession }) : updater, ); }, getLocalParticipantId: () => - getParticipantId(getRoom().localParticipant.identity), + getParticipantId(room.localParticipant.identity), startWatching: startWatchingStream, stopWatching: stopWatchingStream, syncParticipantState, }); } - return mediaShareController; + return screenShareController; } // Connect to LiveKit, enable the microphone, and move the UI into the live call. @@ -876,7 +730,7 @@ export async function connect(callId: string) { callSecret: useCall.getState().callSecret, }); - await getRoom() + await room .connect("wss://call.tensamin.net", token, { autoSubscribe: false, }) @@ -892,25 +746,21 @@ export async function connect(callId: string) { syncAllRemoteTrackSubscriptions(); - await getRoom() - .localParticipant.setMicrophoneEnabled(true) - .catch((error) => { - log(1, "call", "red", "Failed to enable microphone", error); - toast("error", "Failed to enable microphone."); - throw error; - }); + await room.localParticipant.setMicrophoneEnabled(true).catch((error) => { + log(1, "call", "red", "Failed to enable microphone", error); + toast("error", "Failed to enable microphone."); + throw error; + }); syncParticipantState(); } // Tear down the active call session and return the store to a closed state. export async function disconnect() { - stopCallJingle(); - disposeSpeakingDetector(); await clearScreenSharePreview(); try { - await getMediaShareController().clearAll(); + await getScreenShareController().clearPublishedScreenShare(); } catch (error) { log( 1, @@ -925,33 +775,25 @@ export async function disconnect() { state: "closing", invitedUserId: null, callId: null, - incomingCallInvite: null, callSecret: null, livekitToken: null, currentCallData: null, deaf: false, view: "preview", screenShareSession: null, - cameraSession: null, - cameraEnabled: false, - disabledCameraParticipantIds: [], focusedParticipantId: null, - focusedParticipantType: null, usersInFocusedViewHidden: false, watchedStreamParticipantIds: [], pendingWatchedParticipantIds: [], activeScreenShareParticipantIds: [], - ownCallSecretInvitePending: false, - callIsFullscreen: false, - lastFocusedParticipantId: null, }); - getRoom().remoteParticipants.forEach((participant) => { + room.remoteParticipants.forEach((participant) => { participant.setVolume(1); }); try { - await getRoom().disconnect(); + await room.disconnect(); } catch (error) { log(1, "call", "red", "Failed to disconnect from room", error); } finally { @@ -963,7 +805,7 @@ export async function disconnect() { // Prepare encryption and join or create a call with another user. export async function joinCall( userId: number, - callSecret?: WrappedCallSecret | ProtocolCallSecret, + callSecret?: string, existingCallId?: string, sendInvite = true, ) { @@ -975,36 +817,27 @@ export async function joinCall( } log(2, "call", "purple", "Call creation initialised"); - const isNewCall = !callSecret && !existingCallId; useCall.setState({ state: "encrypting", - invitedUserId: sendInvite && !existingCallId ? userId : null, - ownCallSecretInvitePending: isNewCall, + invitedUserId: sendInvite ? userId : null, }); - if (sendInvite && !existingCallId) { - void startCallJingle(() => useCall.getState().invitedUserId != null); - } - if (callSecret) { try { - if (!existingCallId) { - throw new Error("Cannot unwrap call secret without a call id"); - } + const sharedSecret = await runtime.getSharedSecret( + await runtime.load("private_key"), + await runtime + .getUser((await runtime.load("user_id")) as number) + .then((res) => res.public_key), + await runtime.getUser(userId).then((res) => res.public_key), + ); + const decryptedSecret = await runtime.decryptText( + sharedSecret, + callSecret, + ); - const wrappedCallSecret = normalizeWrappedCallSecret(callSecret); - const decryptedSecret = await unwrapCallSecret({ - encryptedSecret: wrappedCallSecret.encryptedSecret, - kemCiphertext: wrappedCallSecret.kemCiphertext, - keyring: String(await runtime.load("mtp_keyring")), - callId: existingCallId, - secretId: wrappedCallSecret.secretId, - version: wrappedCallSecret.versionNumber, - wrappingScheme: wrappedCallSecret.wrappingScheme, - }); - - await getKeyProvider().setKey(decryptedSecret); - await getRoom().setE2EEEnabled(true); + await keyProvider.setKey(decryptedSecret); + await room.setE2EEEnabled(true); useCall.setState({ callSecret: decryptedSecret }); } catch (err) { log(1, "call", "red", "Failed getting call secret", err); @@ -1012,10 +845,10 @@ export async function joinCall( return; } } else { - const random = randomCallSecret(); + const random = crypto.randomUUID(); - await getKeyProvider().setKey(random); - await getRoom().setE2EEEnabled(true); + await keyProvider.setKey(random); + await room.setE2EEEnabled(true); useCall.setState({ callSecret: random }); } @@ -1034,11 +867,11 @@ export async function joinCall( export async function toggleDeaf() { const nextDeaf = !useCall.getState().deaf; - getRoom().remoteParticipants.forEach((participant) => { + room.remoteParticipants.forEach((participant) => { participant.setVolume(nextDeaf ? 0 : 1); }); - if (nextDeaf && getRoom().localParticipant.isMicrophoneEnabled) { + if (nextDeaf && room.localParticipant.isMicrophoneEnabled) { await toggleMute(); } @@ -1046,7 +879,6 @@ export async function toggleDeaf() { deafened: nextDeaf ? "true" : "false", }); - getSpeakingDetector().setDeaf(nextDeaf); useCall.setState({ deaf: nextDeaf }); } @@ -1058,41 +890,41 @@ export async function toggleMute() { await toggleDeaf(); } - await getRoom().localParticipant.setMicrophoneEnabled(!micEnabled); + await room.localParticipant.setMicrophoneEnabled(!micEnabled); syncParticipantState(); } -export async function startScreenShare( - request: Omit = {}, -) { - await getMediaShareController().start({ ...request, kind: "screen" }); +// Start browser-native screen sharing for the current participant. +export async function startScreenShare(options?: ScreenShareCaptureOptions) { + await getScreenShareController().startScreenShare(options); await publishScreenSharePreview(); } -export async function startCameraShare(sourceId?: string) { - await getMediaShareController().start({ kind: "camera", sourceId }); +// Start the Linux desktop capture path that renders frames through Tauri. +export async function startLinuxDesktopScreenShare(sourceId: string) { + await getScreenShareController().startLinuxDesktopScreenShare(sourceId); + await publishScreenSharePreview(); } // Stop the local participant's active screen share and related previews. export async function stopScreenShare() { - await getMediaShareController().stop("screen"); + await getScreenShareController().stopScreenShare(); await clearScreenSharePreview(); } -export async function stopCameraShare() { - await getMediaShareController().stop("camera"); -} - // Toggle screen sharing on or off from UI controls. export async function setScreenShareEnabled( enabled: boolean, - request: Omit = {}, + options?: ScreenShareCaptureOptions, ) { + await getScreenShareController().setScreenShareEnabled(enabled, options); + if (enabled) { - await startScreenShare(request); + await publishScreenSharePreview(); return; } - await stopScreenShare(); + + await clearScreenSharePreview(); } // Reset the in-memory call store when leaving the call experience entirely. @@ -1102,22 +934,16 @@ export function resetCallState() { view: "preview", invitedUserId: null, callId: null, - incomingCallInvite: null, callSecret: null, livekitToken: null, currentCallData: null, deaf: false, - cameraEnabled: false, screenShareSession: null, - cameraSession: null, - disabledCameraParticipantIds: [], focusedParticipantId: null, - focusedParticipantType: null, usersInFocusedViewHidden: false, watchedStreamParticipantIds: [], pendingWatchedParticipantIds: [], activeScreenShareParticipantIds: [], - lastFocusedParticipantId: null, }); syncParticipantState(); @@ -1127,7 +953,7 @@ export function resetCallState() { async function ensureNoiseFilter( noiseFilter: DeepFilterNoiseFilterProcessor, ): Promise { - const microphoneTrack = getRoom().localParticipant.getTrackPublication( + const microphoneTrack = room.localParticipant.getTrackPublication( Track.Source.Microphone, )?.track; @@ -1141,96 +967,48 @@ async function ensureNoiseFilter( await microphoneTrack.setProcessor(noiseFilter).catch((err) => { log(1, "call", "red", "Failed to enable noise filter", err); }); - - const participantId = getParticipantId(getRoom().localParticipant.identity); - if (participantId != null) { - const processedTrack = microphoneTrack.mediaStreamTrack; - getSpeakingDetector().addTrack( - participantId, - processedTrack.clone(), - processedTrack, - ); - } } -export const useCall = create<{ - state: "closed" | "closing" | "connecting" | "open" | "encrypting"; - view: CallView; - invitedUserId: number | null; - callId: string | null; - incomingCallInvite: { - callId: string; - callSecret: WrappedCallSecret; - senderId: number; - } | null; - callSecret: string | null; - livekitToken: string | null; - currentCallData: CurrentCallData; - deaf: boolean; - micEnabled: boolean; - cameraEnabled: boolean; - screenShareEnabled: boolean; - screenShareSession: LocalMediaShareSession | null; - cameraSession: LocalMediaShareSession | null; - disabledCameraParticipantIds: number[]; - focusedParticipantId: number | null; - focusedParticipantType: "user" | "stream" | null; - usersInFocusedViewHidden: boolean; - watchedStreamParticipantIds: number[]; - pendingWatchedParticipantIds: number[]; - activeScreenShareParticipantIds: number[]; - isEncrypted: boolean; - ownCallSecretInvitePending: boolean; - callIsFullscreen: boolean; - callIsPopout: boolean; - layoutVersion: number; - screenRef: React.RefObject | null; - runtime: Runtime | null; - lastFocusedParticipantId: number | null; -}>(() => ({ +export const useCall = create(() => ({ state: "closed", view: "preview", invitedUserId: null, callId: null, - incomingCallInvite: null, callSecret: null, livekitToken: null, currentCallData: null, deaf: false, - micEnabled: false, - cameraEnabled: false, - screenShareEnabled: false, + micEnabled: room.localParticipant.isMicrophoneEnabled, + screenShareEnabled: room.localParticipant.isScreenShareEnabled, screenShareSession: null, - cameraSession: null, - disabledCameraParticipantIds: [], focusedParticipantId: null, - focusedParticipantType: null, usersInFocusedViewHidden: false, watchedStreamParticipantIds: [], pendingWatchedParticipantIds: [], activeScreenShareParticipantIds: [], - isEncrypted: false, - ownCallSecretInvitePending: false, + isEncrypted: + room.localParticipant.isE2EEEnabled && room.localParticipant.isEncrypted, callIsFullscreen: false, callIsPopout: false, layoutVersion: 0, screenRef: null, + room, + keyProvider, + e2eeWorker, runtime: null, - lastFocusedParticipantId: null, })); // Register app-level call listeners and wire React dependencies into the store. export function useInitializeCall() { const navigate = useNavigate(); const location = useLocation(); - const { send, subscribe } = useMTP(); + const { send, subscribePush } = useTTP(); + const { getSharedSecret, decryptText, encryptText } = useCrypto(); const { load } = useStorage(); - const { insertCall } = useSession(); const { get } = useUser(); const callId = useCall((state) => state.callId); const view = useCall((state) => state.view); - const incomingCallInvite = useCall((state) => state.incomingCallInvite); const listenersRegistered = useRef(false); const noiseFilter = useMemo( @@ -1238,10 +1016,10 @@ export function useInitializeCall() { new DeepFilterNoiseFilterProcessor({ enabled: true, enableNoiseReduction: true, - noiseReductionLevel: 60, + noiseReductionLevel: 80, sampleRate: 48000, assetConfig: { - cdnUrl: getNoiseFilterAssetBaseUrl(), + cdnUrl: "/assets", }, }), [], @@ -1251,88 +1029,55 @@ export function useInitializeCall() { setCallRuntime({ navigate, send: send as SendFn, + getSharedSecret: getSharedSecret as GetSharedSecretFn, + decryptText: decryptText as DecryptTextFn, + encryptText: encryptText as EncryptTextFn, load: load as LoadFn, - getPublicKey: (userId) => - get(userId, ["PublicKey"]).then((user) => user.PublicKey), + getUser: get as GetUserFn, }); - }, [get, load, navigate, send]); + }, [decryptText, encryptText, get, getSharedSecret, load, navigate, send]); const showCallingScreen = useCallback( - (callId: string, callSecret: WrappedCallSecret, senderId: number) => { - useCall.setState({ - incomingCallInvite: { callId, callSecret, senderId }, - }); - void startCallJingle(() => useCall.getState().incomingCallInvite != null); - }, - [], - ); + async (callId: string, callSecret: string, senderId: number) => { + const senderName = await get(senderId).then((data) => data.display); - const setInvitePopupOpen = useCallback((open: boolean) => { - if (!open) { - stopCallJingle(); - useCall.setState({ incomingCallInvite: null }); - } - }, []); - - const respondToInvite = useCallback( - (accepted: boolean) => { - const invite = useCall.getState().incomingCallInvite; - - stopCallJingle(); - useCall.setState({ incomingCallInvite: null }); - - if (!invite) { - return; - } - - insertCall({ - CallId: invite.callId, - CallSecret: protocolCallSecret(invite.callSecret), - CallMembers: [invite.senderId], - }); - - if (accepted) { - joinCall( - invite.senderId, - invite.callSecret, - invite.callId, - false, - ).catch((err) => { - log(1, "call", "red", "Failed to join call", err); - }); - return; - } - - log(2, "call", "purple", "Declined call invite", { - callId: invite.callId, - senderId: invite.senderId, + sonnerToast(`Incoming call from ${senderName}`, { + action: { + label: "Accept", + onClick: () => { + joinCall(senderId, callSecret, callId, false).catch((err) => { + log(1, "call", "red", "Failed to join call", err); + }); + }, + }, + cancel: { + label: "Decline", + onClick: () => { + log(2, "call", "purple", "Declined call invite", { + callId, + senderId, + }); + }, + }, }); }, - [insertCall], + [get], ); // listen to call invites useEffect(() => { - return subscribe("CallInvite", async ({ data }) => { - const { CallId, CallSecret, SenderId } = data; - if (!CallId || !CallSecret || !SenderId) return; + subscribePush(async (message) => { + if (message.type !== "call_invite") return; - if (SenderId === Number(await load("user_id"))) { - return; - } + const { call_id, call_secret, sender_id } = message.data as { + call_id: string; + call_secret: string; + sender_id: number; + }; - const currentCall = useCall.getState(); - if (currentCall.callId === CallId && currentCall.state !== "closed") { - return; - } - - showCallingScreen( - CallId, - normalizeWrappedCallSecret(CallSecret), - SenderId, - ); + showCallingScreen(call_id, call_secret, sender_id); }); - }, [load, subscribe, showCallingScreen]); + }, [subscribePush, showCallingScreen]); // get callId from url useEffect(() => { @@ -1392,65 +1137,11 @@ export function useInitializeCall() { listenersRegistered.current = true; - const onConnected = async () => { + const onConnected = () => { useCall.setState({ state: "open" }); - playSound("call_join"); syncParticipantState(); - const detector = getSpeakingDetector(); - const localParticipantId = getParticipantId( - room.localParticipant.identity, - ); - if (localParticipantId != null) { - detector.setLocalParticipantId(localParticipantId); - } - - const [start, end] = await Promise.all([ - load("call_mute_range_start"), - load("call_mute_range_end"), - ]); - detector.setGateThresholds(start, end); - - // Scan existing audio tracks for speaking detection - for (const participant of getAllParticipants()) { - const participantId = getParticipantId(participant.identity); - if (participantId == null) continue; - - for (const publication of participant.trackPublications.values()) { - if ( - publication.kind === Track.Kind.Audio && - publication.source === Track.Source.Microphone && - publication.track - ) { - const mediaTrack = publication.track.mediaStreamTrack; - if (participant === room.localParticipant) { - const clonedTrack = mediaTrack.clone(); - detector.addTrack(participantId, clonedTrack, mediaTrack); - } else { - detector.addTrack(participantId, mediaTrack); - } - } - } - } - const invitedUserId = useCall.getState().invitedUserId; - const ownCallSecretInvitePending = - useCall.getState().ownCallSecretInvitePending; - - if (ownCallSecretInvitePending) { - useCall.setState({ ownCallSecretInvitePending: false }); - void load("user_id") - .then((ownUserId) => sendCallInvite(Number(ownUserId))) - .catch((error) => { - log( - 1, - "call", - "red", - "Failed to send own call secret invite", - error, - ); - }); - } if (invitedUserId != null) { setTimeout(async () => { @@ -1468,8 +1159,6 @@ export function useInitializeCall() { }; const onDisconnected = () => { - stopCallJingle(); - playSound("call_leave"); useCall.setState({ state: "closed" }); syncParticipantState(); log(2, "call", "purple", "Disconnected from call", { @@ -1479,19 +1168,15 @@ export function useInitializeCall() { }; const onParticipantConnected = () => { - stopCallJingle(); - playSound("call_join"); syncAllRemoteTrackSubscriptions(); syncParticipantState(); }; const onParticipantDisconnected = (participant: Participant) => { - playSound("call_leave"); const participantId = getParticipantId(participant.identity); if (participantId != null) { stopWatchingStream(participantId); - getSpeakingDetector().removeParticipant(participantId); } syncParticipantState(); @@ -1512,88 +1197,26 @@ export function useInitializeCall() { void ensureNoiseFilter(noiseFilter); }; - const onLocalTrackPublished = (publication: LocalTrackPublication) => { - if (publication.source === Track.Source.ScreenShare) { - playSound("stream_start_self"); - } - - if ( - publication.kind === Track.Kind.Audio && - publication.source === Track.Source.Microphone && - publication.track - ) { - const participantId = getParticipantId(room.localParticipant.identity); - if (participantId != null) { - const originalTrack = publication.track.mediaStreamTrack; - const clonedTrack = originalTrack.clone(); - getSpeakingDetector().addTrack( - participantId, - clonedTrack, - originalTrack, - ); - } - } - syncParticipantState(); - void ensureNoiseFilter(noiseFilter); - }; - - const onLocalTrackUnpublished = (publication: LocalTrackPublication) => { - if (publication.source === Track.Source.ScreenShare) { - playSound("stream_end_self"); - } - - if ( - publication.kind === Track.Kind.Audio && - publication.source === Track.Source.Microphone - ) { - const participantId = getParticipantId(room.localParticipant.identity); - if (participantId != null) { - getSpeakingDetector().removeParticipant(participantId); - } - } - syncParticipantState(); - }; - const onTrackPublished = ( publication: RemoteTrackPublication, participant: RemoteParticipant, ) => { - if (publication.source === Track.Source.ScreenShare) { - playSound("stream_start_other"); - } - const participantId = getParticipantId(participant.identity); if (participantId != null) { - syncRemoteParticipantTrackSubscriptions(participantId); + if (publication.kind === Track.Kind.Audio) { + publication.setSubscribed(true); + } else { + publication.setSubscribed(false); + } } onParticipantStateChange(); }; - const onTrackUnpublished = (publication: RemoteTrackPublication) => { - if (publication.source === Track.Source.ScreenShare) { - playSound("stream_end_other"); - } - - onParticipantStateChange(); - }; - - const onTrackSubscribed = ( - track: RemoteTrack, - publication: RemoteTrackPublication, - participant: RemoteParticipant, - ) => { + const onTrackSubscribed = (track: RemoteTrack) => { if (track.kind === "audio" && track.sid) { attachRemoteAudio(track.sid, track.attach()); - - const participantId = getParticipantId(participant.identity); - if ( - participantId != null && - publication.source === Track.Source.Microphone - ) { - getSpeakingDetector().addTrack(participantId, track.mediaStreamTrack); - } } syncParticipantState(); @@ -1601,23 +1224,16 @@ export function useInitializeCall() { const onTrackUnsubscribed = ( track: RemoteTrack, - publication: RemoteTrackPublication, + _publication: unknown, participant: Participant, ) => { - const participantId = getParticipantId(participant.identity); - if (track.kind === "audio" && track.sid) { track.detach(); detachRemoteAudio(track.sid); - - if ( - participantId != null && - publication.source === Track.Source.Microphone - ) { - getSpeakingDetector().removeParticipant(participantId); - } } + const participantId = getParticipantId(participant.identity); + if ( participantId != null && getTrackPublicationBySource(participant, Track.Source.ScreenShare) @@ -1629,20 +1245,19 @@ export function useInitializeCall() { syncParticipantState(); }; - const room = getRoom(); room.on(RoomEvent.Connected, onConnected); room.on(RoomEvent.Reconnected, onConnected); room.on(RoomEvent.Disconnected, onDisconnected); room.on(RoomEvent.TrackSubscribed, onTrackSubscribed); room.on(RoomEvent.TrackUnsubscribed, onTrackUnsubscribed); room.on(RoomEvent.TrackPublished, onTrackPublished); - room.on(RoomEvent.TrackUnpublished, onTrackUnpublished); + room.on(RoomEvent.TrackUnpublished, onParticipantStateChange); room.on(RoomEvent.ParticipantConnected, onParticipantConnected); room.on(RoomEvent.ParticipantDisconnected, onParticipantDisconnected); room.on(RoomEvent.TrackMuted, onParticipantStateChange); room.on(RoomEvent.TrackUnmuted, onParticipantStateChange); - room.on(RoomEvent.LocalTrackPublished, onLocalTrackPublished); - room.on(RoomEvent.LocalTrackUnpublished, onLocalTrackUnpublished); + room.on(RoomEvent.LocalTrackPublished, onParticipantStateChange); + room.on(RoomEvent.LocalTrackUnpublished, onParticipantStateChange); room.on(RoomEvent.MediaDevicesError, onMediaDeviceFailure); room.on(RoomEvent.EncryptionError, onEncryptionError); room.on(RoomEvent.ConnectionStateChanged, onParticipantStateChange); @@ -1657,22 +1272,22 @@ export function useInitializeCall() { room.off(RoomEvent.TrackSubscribed, onTrackSubscribed); room.off(RoomEvent.TrackUnsubscribed, onTrackUnsubscribed); room.off(RoomEvent.TrackPublished, onTrackPublished); - room.off(RoomEvent.TrackUnpublished, onTrackUnpublished); + room.off(RoomEvent.TrackUnpublished, onParticipantStateChange); room.off(RoomEvent.ParticipantConnected, onParticipantConnected); room.off(RoomEvent.ParticipantDisconnected, onParticipantDisconnected); room.off(RoomEvent.TrackMuted, onParticipantStateChange); room.off(RoomEvent.TrackUnmuted, onParticipantStateChange); - room.off(RoomEvent.LocalTrackPublished, onLocalTrackPublished); - room.off(RoomEvent.LocalTrackUnpublished, onLocalTrackUnpublished); + room.off(RoomEvent.LocalTrackPublished, onParticipantStateChange); + room.off(RoomEvent.LocalTrackUnpublished, onParticipantStateChange); room.off(RoomEvent.MediaDevicesError, onMediaDeviceFailure); room.off(RoomEvent.EncryptionError, onEncryptionError); room.off(RoomEvent.ConnectionStateChanged, onParticipantStateChange); clearRemoteAudio(); listenersRegistered.current = false; room.disconnect(); - if (_e2eeWorker) _e2eeWorker.terminate(); + e2eeWorker.terminate(); }; - }, [noiseFilter, load]); + }, [noiseFilter]); // fetch call data for preview page useEffect(() => { @@ -1680,22 +1295,10 @@ export function useInitializeCall() { return; } - send("CallData", { CallId: callId }) + send("call_data", { call_id: callId }) .then((data) => { - if (data.type === "ErrorNotFound") { - setCurrentCallData({ - UserIds: [], - exists: false, - }); - return; - } - - if (data.type.startsWith("Error")) { - throw new Error(`CallData failed: ${data.type}`); - } - setCurrentCallData({ - ...mtp.CallData.response.parse(data.data), + ...(data.data as z.infer), exists: true, }); }) @@ -1705,18 +1308,9 @@ export function useInitializeCall() { error: err, }); setCurrentCallData({ - UserIds: [], + user_ids: [], exists: false, }); }); }, [callId, send, view]); - - return incomingCallInvite ? ( - - ) : null; } diff --git a/packages/call/src/values.ts b/packages/call/src/values.ts new file mode 100644 index 0000000..0564800 --- /dev/null +++ b/packages/call/src/values.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; +import { ttp } from "@tensamin/shared/data"; + +export type RawMessages = z.infer["messages"]; + +export type RawMessage = RawMessages[number]; + +export type LiveMessage = RawMessage & { + failed?: boolean; + localId: string; +}; diff --git a/packages/call/src/views/main/focused.tsx b/packages/call/src/views/main/focused.tsx index 7a7736e..d52362b 100644 --- a/packages/call/src/views/main/focused.tsx +++ b/packages/call/src/views/main/focused.tsx @@ -1,15 +1,13 @@ import { RoomEvent } from "livekit-client"; import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; -import { getRoom, setUsersInFocusedViewHidden, useCall } from "../../store"; +import { useCall } from "../../store"; import Base from "../../components/modals/base"; -import { useIsMobile } from "@methanium/ui"; const SECONDARY_ROW_HEIGHT_PX = 180; const STACK_GAP_PX = 12; export default function View() { - const isMobile = useIsMobile(); - const room = getRoom(); + const room = useCall((state) => state.room); const layoutVersion = useCall((state) => state.layoutVersion); const usersInFocusedViewHidden = useCall( @@ -18,9 +16,6 @@ export default function View() { const callIsFullscreen = useCall((state) => state.callIsFullscreen); const focusedParticipantId = useCall((state) => state.focusedParticipantId); - const focusedParticipantType = useCall( - (state) => state.focusedParticipantType, - ); const activeScreenShareParticipantIds = useCall( (state) => state.activeScreenShareParticipantIds, ); @@ -29,10 +24,6 @@ export default function View() { const [isFocusedTileFlush, setIsFocusedTileFlush] = useState(false); const [participantVersion, setParticipantVersion] = useState(0); - useEffect(() => { - if (isMobile) setUsersInFocusedViewHidden(true); - }, [isMobile]); - useEffect(() => { const syncParticipants = () => { setParticipantVersion((version) => version + 1); @@ -166,11 +157,6 @@ export default function View() { const focusedParticipant = getParticipantById(focusedParticipantId); const focusedParticipantHasActiveScreenShare = activeScreenShareParticipantIdSet.has(focusedParticipantId); - const focusedTileType: "user" | "stream" = - focusedParticipantType === "stream" && - focusedParticipantHasActiveScreenShare - ? "stream" - : "user"; const isImmersiveFocusedView = callIsFullscreen && usersInFocusedViewHidden; return ( @@ -193,7 +179,7 @@ export default function View() {
diff --git a/packages/call/src/views/main/grid.tsx b/packages/call/src/views/main/grid.tsx index de6ba9b..833d7b5 100644 --- a/packages/call/src/views/main/grid.tsx +++ b/packages/call/src/views/main/grid.tsx @@ -1,8 +1,7 @@ import { RoomEvent } from "livekit-client"; import { useEffect, useMemo, useRef, useState } from "react"; -import { useCall, getRoom } from "../../store"; +import { useCall } from "../../store"; import Base from "../../components/modals/base"; -import { cn, useIsMobile } from "@methanium/ui"; const TILE_ASPECT_RATIO = 16 / 9; const GRID_GAP = 12; @@ -83,7 +82,7 @@ function calculateOptimalGridLayout( } export default function View() { - const room = getRoom(); + const room = useCall((state) => state.room); const layoutVersion = useCall((state) => state.layoutVersion); const activeScreenShareParticipantIds = useCall( (state) => state.activeScreenShareParticipantIds, @@ -215,8 +214,6 @@ export default function View() { return room.getParticipantByIdentity(String(participantId)); } - const isMobile = useIsMobile(); - return (
{rows.map((row) => ( diff --git a/packages/call/src/views/main/layout.tsx b/packages/call/src/views/main/layout.tsx index d16ba0b..b3710da 100644 --- a/packages/call/src/views/main/layout.tsx +++ b/packages/call/src/views/main/layout.tsx @@ -1,13 +1,7 @@ import { useEffect, useLayoutEffect, useRef, useState } from "react"; import Actions from "../../components/actions"; import TopBar from "../../components/top"; -import { - setCallIsFullscreen, - setScreenRef, - triggerCallLayoutCalculation, - useCall, -} from "../../store"; -import { useIsMobile } from "@methanium/ui"; +import { setScreenRef, useCall } from "../../store"; export default function Layout({ children }: { children: React.ReactNode }) { const screenRef = useRef(null); @@ -22,30 +16,6 @@ export default function Layout({ children }: { children: React.ReactNode }) { useEffect(() => { setScreenRef(screenRef); - - const screen = screenRef.current; - const fullscreenDocument = screen?.ownerDocument; - - if (!screen || !fullscreenDocument) { - return; - } - - const handleFullscreenChange = () => { - setCallIsFullscreen(fullscreenDocument.fullscreenElement === screen); - triggerCallLayoutCalculation(); - }; - - fullscreenDocument.addEventListener( - "fullscreenchange", - handleFullscreenChange, - ); - - return () => { - fullscreenDocument.removeEventListener( - "fullscreenchange", - handleFullscreenChange, - ); - }; }, []); const usersInFocusedViewHidden = useCall( @@ -130,8 +100,6 @@ export default function Layout({ children }: { children: React.ReactNode }) { setIsImmersiveChromeVisible(false); }; - const isMobile = useIsMobile(); - return (
- {!isMobile && ( -
- -
- )} +
+ +
User: {user.Display}

: null; -} +import { useUser, type User } from "@tensamin/user/context"; export default function Preview() { + const { get } = useUser(); const currentCallData = useCall((state) => state.currentCallData); + const [data, setData] = useState([]); + + useEffect(() => { + let active = true; + + if (!currentCallData?.exists) { + return () => { + active = false; + }; + } + + void Promise.all(currentCallData.user_ids.map((id) => get(id))) + .then((users) => { + if (!active) { + return; + } + + setData(users); + }) + .catch(() => { + if (!active) { + return; + } + + setData([]); + }); + + return () => { + active = false; + }; + }, [currentCallData, get]); + return (
{currentCallData?.exists ? (
- {currentCallData.UserIds.map((userId) => ( - - ))} + {data.map((user) => { + return ( +

+ User: {user.display} +

+ ); + })}
) : (

Call expired

diff --git a/packages/call/todo.md b/packages/call/todo.md index 2a8fb74..952858f 100644 --- a/packages/call/todo.md +++ b/packages/call/todo.md @@ -1,10 +1,13 @@ +- Speaking indicator - Overlay for stream modals +- User modals + - Bg based on avatar - Mobile +- Call invite popup + - Save call invite in `calls` array +- Sounds - Admin call actions - Timeout - Disconnect -- Implement context menu features -- Add quality selection -- Good preview page -- Settings page -- Make the Three-Dots button open a dialog/context-menu to configure the call for anonymous invites or other admin stuff +- Desktop-App screenshares +- Context menus diff --git a/packages/chat/package.json b/packages/chat/package.json index d164529..3846ddd 100644 --- a/packages/chat/package.json +++ b/packages/chat/package.json @@ -7,31 +7,27 @@ "./context": "./src/context.tsx", "./screen": "./src/screen.tsx", "./behaviour-conversation": "./src/behaviour/conversation.ts", - "./behaviour-community": "./src/behaviour/community.ts", - "./values": "./src/values.ts" + "./behaviour-community": "./src/behaviour/community.ts" }, "scripts": { - "format": "pnpm exec prettier --write .", + "format": "bunx prettier --write .", "lint": "eslint src", "build": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@methanium/ui": "*", - "@tanstack/pacer": "^0.21.1", - "@tanstack/react-query": "^5.101.4", - "@tanstack/react-router": "^1.170.21", - "@tanstack/react-virtual": "^3.14.9", - "@tensamin/cache": "workspace:*", + "@tanstack/react-query": "^5.0.0", + "@tanstack/react-router": "^1.0.0", + "@tanstack/react-virtual": "^3.0.0", "@tensamin/crypto": "workspace:*", - "@tensamin/hotkeys": "workspace:*", - "@tensamin/mtp": "workspace:*", - "@tensamin/shared": "workspace:*", + "@tensamin/ttp": "workspace:*", "@tensamin/storage": "workspace:*", "@tensamin/user": "workspace:*", - "lucide-react": "^1.29.0", - "motion": "^13.0.0", - "react": "^19.2.8", - "react-dom": "^19.2.8", - "zod": "^4.4.3" + "@tensamin/markdown": "workspace:*", + "@tensamin/shared": "workspace:*", + "@tensamin/ui": "*", + "lucide-react": "^1.14.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "zod": "^4.3.6" } } diff --git a/packages/chat/src/behaviour/conversation.ts b/packages/chat/src/behaviour/conversation.ts index 1cf6bc8..67e77ee 100644 --- a/packages/chat/src/behaviour/conversation.ts +++ b/packages/chat/src/behaviour/conversation.ts @@ -1,4 +1,5 @@ -import type { BoundSendFn } from "@tensamin/mtp"; +import type { BoundSendFn } from "@tensamin/ttp"; +import type { TTP } from "@tensamin/shared/data"; import type { RawMessages } from "../values"; /** @@ -6,24 +7,24 @@ import type { RawMessages } from "../values"; * @param send Parameter send. * @param amount Parameter amount. * @param offset Parameter offset. - * @param UserId Parameter UserId. + * @param user_id Parameter user_id. * @returns Promise. */ export async function getMessages( - send: BoundSendFn, - Amount: number, - Offset: number, - UserId: number, + send: BoundSendFn, + amount: number, + offset: number, + user_id: number, ): Promise { - const messages = await send("MessagesGet", { - Amount, - Offset, - UserId, + const messages = await send("messages_get", { + amount: amount, + offset: offset, + user_id: user_id, }); if (messages.type.startsWith("error")) { throw new Error(messages.type); } - return messages.data.Messages; + return messages.data.messages; } diff --git a/packages/chat/src/components/emoji/emojiPicker.tsx b/packages/chat/src/components/emoji/emojiPicker.tsx deleted file mode 100644 index ed8f2a1..0000000 --- a/packages/chat/src/components/emoji/emojiPicker.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Button } from "@methanium/ui"; -import { Emoji } from "@methanium/ui/markdown"; -import { getRecentEmojis, useEmojiRanks } from "./emojiRanks"; - -export default function EmojiPicker({ - onSelect, -}: { - onSelect: (emoji: string) => void; -}) { - const { ranks } = useEmojiRanks(); - const emojis = getRecentEmojis(ranks, 3); - - return ( -
- {emojis.map((emoji) => ( - - ))} -
- ); -} diff --git a/packages/chat/src/components/emoji/emojiRanks.ts b/packages/chat/src/components/emoji/emojiRanks.ts deleted file mode 100644 index a30deb5..0000000 --- a/packages/chat/src/components/emoji/emojiRanks.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { useStorage } from "@tensamin/storage/context"; -import { useCallback, useEffect, useState } from "react"; -import { normalizeShortcode } from "@methanium/ui/markdown"; - -const RANKS_CHANGED_EVENT = "tensamin-reaction-ranks-changed"; -let recordQueue = Promise.resolve(); - -function normalizeRanks(ranks: Record) { - return Object.entries(ranks).reduce>( - (normalized, [value, frequency]) => { - const shortcode = normalizeShortcode(value); - if (!shortcode || !Number.isFinite(frequency) || frequency <= 0) { - return normalized; - } - - normalized[shortcode] = (normalized[shortcode] ?? 0) + frequency; - return normalized; - }, - {}, - ); -} - -function rankedEmojis(ranks: Record) { - return Object.entries(ranks) - .filter( - ([emoji, frequency]) => - normalizeShortcode(emoji) !== undefined && - Number.isFinite(frequency) && - frequency > 0, - ) - .sort(([emojiA, frequencyA], [emojiB, frequencyB]) => - frequencyB === frequencyA - ? emojiA.localeCompare(emojiB) - : frequencyB - frequencyA, - ) - .flatMap(([emoji]) => { - const shortcode = normalizeShortcode(emoji); - return shortcode ? [shortcode] : []; - }); -} - -export function getRecentEmojis(ranks: Record, amount: number) { - return rankedEmojis(ranks).slice(0, amount); -} - -export function useEmojiRanks() { - const { load, save } = useStorage(); - const [ranks, setRanks] = useState>({}); - - useEffect(() => { - void load("reactions").then((storedRanks) => { - const normalized = normalizeRanks(storedRanks); - setRanks(normalized); - if (JSON.stringify(normalized) !== JSON.stringify(storedRanks)) { - void save("reactions", normalized); - } - }); - - function handleRanksChanged(event: Event) { - setRanks((event as CustomEvent>).detail); - } - - window.addEventListener(RANKS_CHANGED_EVENT, handleRanksChanged); - return () => - window.removeEventListener(RANKS_CHANGED_EVENT, handleRanksChanged); - }, [load, save]); - - return { ranks }; -} - -export function useRecordEmojiUse() { - const { load, save } = useStorage(); - - return useCallback( - (emoji: string) => { - const shortcode = normalizeShortcode(emoji); - if (!shortcode) return; - - recordQueue = recordQueue - .catch(() => undefined) - .then(async () => { - const normalized = normalizeRanks(await load("reactions")); - const next = { - ...normalized, - [shortcode]: (normalized[shortcode] ?? 0) + 1, - }; - await save("reactions", next); - window.dispatchEvent( - new CustomEvent(RANKS_CHANGED_EVENT, { detail: next }), - ); - }); - }, - [load, save], - ); -} diff --git a/packages/chat/src/components/input.tsx b/packages/chat/src/components/input.tsx index 469c28a..487c813 100644 --- a/packages/chat/src/components/input.tsx +++ b/packages/chat/src/components/input.tsx @@ -1,402 +1,127 @@ -import { Input, type InputController } from "@methanium/ui/markdown"; -import { - Card, - CardHeader, - Drawer, - DrawerContent, - DrawerTrigger, - Popover, - PopoverContent, - PopoverTrigger, -} from "@methanium/ui"; +import Input from "@tensamin/markdown/input"; +import { Card, CardHeader } from "@tensamin/ui"; import { useStorage } from "@tensamin/storage/context"; -import React, { useCallback, useEffect, useState, useRef } from "react"; -import { Button } from "@methanium/ui"; +import * as React from "react"; +import { Button } from "@tensamin/ui"; -import { Plus, Laugh, FileVideo, SendHorizonal } from "lucide-react"; -import { useChat, useReplyMessage } from "../context"; -import { useMTP } from "@tensamin/mtp"; +import { Plus, Laugh, Clapperboard } from "lucide-react"; +import { useChat } from "../context"; +import { useTTP } from "@tensamin/ttp"; import { log, toast } from "@tensamin/shared/log"; -import { cn, useIsMobile } from "@methanium/ui"; -import { encryptChatText } from "@tensamin/crypto/chatSecret"; - +import { cn, useIsMobile } from "@tensamin/ui"; +import { encryptText } from "@tensamin/crypto/worker"; import { useSession } from "@tensamin/storage/session"; -import EmojiPicker from "./emoji/emojiPicker"; -import { useEmojiRanks, useRecordEmojiUse } from "./emoji/emojiRanks"; -import GifPicker from "./media/gifPicker"; -import ReplyBox from "./replyBox"; -import { useHotkey } from "@tensamin/hotkeys"; -import { editLastMessageHotkey } from "../hotkeys"; export default function InputComponent({ value, setValue, - onEditLastMessage, }: { value: string; setValue: (value: string) => void; - onEditLastMessage: () => void; }) { - const [invertEnterBehavior, setInvertEnterBehavior] = useState(false); + const [invertEnterBehavior, setInvertEnterBehavior] = React.useState(false); - const { send } = useMTP(); - const { - addLiveMessage, - chatSecret, - userId, - inputBoxRef, - replyTo, - setReplyTo, - } = useChat(); - const { load, save } = useStorage(); + const { send } = useTTP(); + const { addLiveMessage, sharedSecret, userId, inputBoxRef } = useChat(); + const { load } = useStorage(); const { moveUserIdToTop } = useSession(); - const gifPopoverRef = useRef(null); - const [gifPopoverOpen, setGifPopoverOpen] = useState(false); - const [emojiPopoverOpen, setEmojiPopoverOpen] = useState(false); - const recordUse = useRecordEmojiUse(); - const { ranks: emojiFrequencies } = useEmojiRanks(); - const [gifPopoverSize, setGifPopoverSize] = useState<{ - width: number; - height: number; - }>(); - const composerRef = useRef(null); - const setComposer = useCallback((controller: InputController | null) => { - composerRef.current = controller; - }, []); - useEffect(() => { - const frame = requestAnimationFrame(() => composerRef.current?.focus()); - return () => cancelAnimationFrame(frame); - }, [userId]); - - useEffect(() => { - if (replyTo === undefined) return; - - const frame = requestAnimationFrame(() => composerRef.current?.focus()); - return () => cancelAnimationFrame(frame); - }, [replyTo]); - - useEffect(() => { - const focusComposerOnType = (event: KeyboardEvent) => { - const composer = composerRef.current; - const target = event.target; - if ( - !composer || - composer.hasFocus() || - event.defaultPrevented || - event.isComposing || - event.ctrlKey || - event.metaKey || - event.altKey || - event.key.length !== 1 - ) { - return; - } - if ( - target instanceof HTMLElement && - (target.isContentEditable || - target.closest( - 'button, a[href], input, textarea, select, summary, [contenteditable], [role], [tabindex]:not([tabindex="-1"])', - )) - ) { - return; - } - - event.preventDefault(); - event.stopPropagation(); - composer.focus(); - composer.insertText(event.key); - }; - - window.addEventListener("keydown", focusComposerOnType, true); - return () => - window.removeEventListener("keydown", focusComposerOnType, true); - }, []); - - useHotkey(editLastMessageHotkey, onEditLastMessage, { - enabled: value.length === 0, - ignoreInputs: false, - target: inputBoxRef, - }); - - useEffect(() => { + React.useEffect(() => { void load("settings.reverse_enter_behavior").then((shouldInvert) => { setInvertEnterBehavior(shouldInvert); }); }, [load]); - useEffect(() => { - void load("chat_picker_size").then((size) => { - if (size) { - setGifPopoverSize(size); - } - }); - }, [load]); + /** + * Executes handleSubmit. + * @param none This function has no parameters. + * @returns unknown. + */ + async function handleSubmit() { + if (value.trim() === "") return; - async function handleSubmit(content = value, preserveContent = false) { - if (content.trim() === "") return; - - const time = new Date().getTime(); - const currentValue = content; + const time = Date.now(); + const currentValue = value; if (!Number.isSafeInteger(userId) || userId <= 0) { toast("error", "No conversation selected"); return; } - if (!chatSecret) { - toast("error", "Still getting chat secret..."); + if (!sharedSecret) { + toast("error", "Still getting shared secret..."); return; } - log(3, "chat", "purple", "Message send init, adding live message ..."); - const reference = addLiveMessage({ - NotEncrypted: true, - SendTime: time, - Content: currentValue, - SenderId: ownId, - MessageState: "awaiting", - ...(replyTo && { ReplyId: replyTo }), + height: 0, + not_encrypted: true, + send_time: time, + content: currentValue, + sent_by_self: true, + message_state: "awaiting", }); - log(3, "chat", "purple", "Live message added, encrypting..."); + const encryptedContext = await encryptText(sharedSecret, currentValue); - const encryptedContent = await encryptChatText( - chatSecret, - currentValue, - ).catch((err) => { - toast("error", "Failed to encrypt message", String(err)); - reference.setFailed(true); - }); - - if (!encryptedContent) return; - - log(3, "chat", "purple", "Content encrypted, sending message..."); - - send("MessageSend", { - Content: encryptedContent, - ReceiverId: userId, - SendTime: time, - ...(replyTo && { ReplyId: replyTo }), + send("message_send", { + height: 0, + content: encryptedContext, + receiver_id: userId, + send_time: time, }).catch((e) => { log(0, "Chat", "red", "Failed to send message", e, { - ReceiverId: userId, - SendTime: time, + content: currentValue, + encryptedContext, + receiver_id: userId, + send_time: time, }); reference.setFailed(true); toast("error", "Failed to send message"); }); - if (replyTo) { - setReplyTo(undefined); - } - - log(3, "chat", "purple", "Message sent"); - moveUserIdToTop(userId); - if (!preserveContent) { - setValue(""); - } + setValue(""); } const isMobile = useIsMobile(); - function handleGifPopoverResizeStart( - event: React.PointerEvent, - ) { - const popover = gifPopoverRef.current; - - if (!popover) return; - - event.preventDefault(); - event.stopPropagation(); - - const rect = popover.getBoundingClientRect(); - const startX = event.clientX; - const startY = event.clientY; - const startWidth = rect.width; - const startHeight = rect.height; - const minSize = 40; - const maxSize = 720; - - function clampSize(size: number) { - return Math.min(Math.max(size, minSize), maxSize); - } - - function handlePointerMove(moveEvent: PointerEvent) { - const nextSize = { - width: clampSize(startWidth + startX - moveEvent.clientX), - height: clampSize(startHeight + startY - moveEvent.clientY), - }; - - setGifPopoverSize(nextSize); - } - - function handlePointerUp() { - const popover = gifPopoverRef.current; - - if (popover) { - const rect = popover.getBoundingClientRect(); - - void save("chat_picker_size", { - width: clampSize(rect.width), - height: clampSize(rect.height), - }); - } - - window.removeEventListener("pointermove", handlePointerMove); - window.removeEventListener("pointerup", handlePointerUp); - } - - window.addEventListener("pointermove", handlePointerMove); - window.addEventListener("pointerup", handlePointerUp); - } - - const { ownId, replyMessage, replyUserId } = useReplyMessage(); - return ( -
- {/* reply */} - {replyTo !== undefined && ( - setReplyTo(undefined)} - userId={replyUserId ?? ownId} - variant="composer" - /> + - -
- - +
+

Files list

+
+
+
+ +
-
-
- -
-

Files list

-
-
-
- - ( - - )} - /> - - { - setValue(`${value}${shortcode} `); - recordUse(shortcode); - setEmojiPopoverOpen(false); - }} - /> - - - {isMobile ? ( - - -
- -
-
- -
- { - void handleSubmit(url, true); - setGifPopoverOpen(false); - }} - /> -
-
-
- ) : ( - - ( - - )} - /> - event.stopPropagation()} - onWheelCapture={(event) => event.stopPropagation()} - style={{ - ...gifPopoverSize, - maxHeight: gifPopoverSize?.height, - }} - > -
- { - void handleSubmit(url, true); - setGifPopoverOpen(false); - }} - /> - - - )} -
-
- - -
+
+ + ); } diff --git a/packages/chat/src/components/media/gifPicker.tsx b/packages/chat/src/components/media/gifPicker.tsx deleted file mode 100644 index 0907d12..0000000 --- a/packages/chat/src/components/media/gifPicker.tsx +++ /dev/null @@ -1,709 +0,0 @@ -import { Debouncer } from "@tanstack/pacer"; -import { useInfiniteQuery } from "@tanstack/react-query"; -import { - Button, - Input, - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "@methanium/ui"; -import type { ChatPickerMediaGroup } from "@tensamin/shared/data"; -import { useStorage } from "@tensamin/storage/context"; -import { ArrowLeft, Loader2, Search } from "lucide-react"; -import MediaSaveButton from "./mediaSaveButton"; -import { getUngroupedMedia } from "./mediaGroups"; -import React, { - useEffect, - useLayoutEffect, - useRef, - useState, - startTransition, -} from "react"; - -const klipyApiKey = - "KzyghnvdlaHjJuDycVx6rLIEu5eaacPyFERieoI7L8UWLt2rnJloL45UL5MVc9IG"; -const klipyBaseUrl = "https://api.klipy.com/api/v1"; -const pageSize = 24; -const minColumnWidth = 200; - -function getColumnCount(width: number, itemCount: number) { - return Math.max( - 1, - Math.min(itemCount || 1, Math.floor(width / minColumnWidth)), - ); -} - -type KlipyKind = "gif" | "meme"; - -type KlipyItem = { - id: number | string; - title?: string; - file?: Record< - string, - | Record< - string, - | { - url?: string; - width?: number; - height?: number; - } - | undefined - > - | undefined - >; - blur_preview?: string; -}; - -type PickerMedia = { - key: React.Key; - url: string; - alt: string; - width?: number; - height?: number; -}; - -function distributeByHeight( - items: T[], - columnCount: number, - getAspectRatio: (item: T) => number, -) { - const columns = Array.from({ length: columnCount }, () => [] as T[]); - const columnHeights = Array.from({ length: columnCount }, () => 0); - - for (const item of items) { - const shortestColumnIndex = columnHeights.indexOf( - Math.min(...columnHeights), - ); - - columns[shortestColumnIndex].push(item); - columnHeights[shortestColumnIndex] += getAspectRatio(item); - } - - return columns; -} - -function getKlipyPath(kind: KlipyKind) { - const resource = kind === "gif" ? "gifs" : "static-memes"; - - return `${resource}/search`; -} - -async function fetchKlipyPage({ - kind, - page, - search, -}: { - kind: KlipyKind; - page: number; - search: string; -}): Promise<{ - items: KlipyItem[]; - currentPage: number; - hasNext: boolean; -}> { - const params = new URLSearchParams({ - page: String(page), - per_page: String(pageSize), - content_filter: "medium", - }); - - if (search !== "") { - params.set("q", search); - } - - if (kind === "gif") { - params.set("format_filter", "gif,webp"); - } - - const response = await fetch( - `${klipyBaseUrl}/${klipyApiKey}/${getKlipyPath(kind)}?${params}`, - ); - - if (!response.ok) { - throw new Error(`Klipy request failed with status ${response.status}`); - } - - const body = (await response.json()) as { - data?: { - data?: KlipyItem[]; - current_page?: number; - has_next?: boolean; - }; - }; - const data = body.data; - - return { - items: data?.data ?? [], - currentPage: data?.current_page ?? page, - hasNext: data?.has_next ?? false, - }; -} - -function getFormat(item: KlipyItem, size: string, format: string) { - return item.file?.[size]?.[format]; -} - -function pickDisplayMedia(kind: KlipyKind, item: KlipyItem) { - if (kind === "gif") { - return ( - getFormat(item, "sm", "webp") ?? - getFormat(item, "md", "webp") ?? - getFormat(item, "sm", "gif") ?? - getFormat(item, "md", "gif") ?? - getFormat(item, "hd", "webp") ?? - getFormat(item, "hd", "gif") - ); - } - - return ( - getFormat(item, "sm", "webp") ?? - getFormat(item, "md", "webp") ?? - getFormat(item, "sm", "png") ?? - getFormat(item, "md", "png") ?? - getFormat(item, "hd", "webp") ?? - getFormat(item, "hd", "png") - ); -} - -function pickSelectionUrl(kind: KlipyKind, item: KlipyItem) { - if (kind === "gif") { - return ( - getFormat(item, "md", "gif")?.url ?? - getFormat(item, "sm", "gif")?.url ?? - getFormat(item, "hd", "gif")?.url - ); - } - - return ( - getFormat(item, "md", "png")?.url ?? - getFormat(item, "md", "webp")?.url ?? - getFormat(item, "sm", "png")?.url ?? - getFormat(item, "sm", "webp")?.url ?? - getFormat(item, "hd", "png")?.url ?? - getFormat(item, "hd", "webp")?.url - ); -} - -function useMeasuredWidth() { - const scrollRef = useRef(null); - const [scrollWidth, setScrollWidth] = useState(0); - - useLayoutEffect(() => { - const element = scrollRef.current; - - if (!element || typeof ResizeObserver === "undefined") return; - - const updateWidth = () => setScrollWidth(element.clientWidth); - const observer = new ResizeObserver(updateWidth); - - updateWidth(); - observer.observe(element); - - return () => observer.disconnect(); - }, []); - - return [scrollRef, scrollWidth] as const; -} - -function useImageSizes(urls: string[]) { - const [sizes, setSizes] = useState< - Record - >({}); - - useEffect(() => { - let active = true; - - for (const url of urls) { - if (sizes[url]) continue; - - const image = new Image(); - - image.onload = () => { - if (!active) return; - - setSizes((current) => ({ - ...current, - [url]: { - width: image.naturalWidth, - height: image.naturalHeight, - }, - })); - }; - image.src = url; - } - - return () => { - active = false; - }; - }, [sizes, urls]); - - return sizes; -} - -function MediaGrid({ - hasNextPage = false, - isFetchingNextPage = false, - items, - onLoadMore, - onSelect, - resizeWidth, -}: { - hasNextPage?: boolean; - isFetchingNextPage?: boolean; - items: PickerMedia[]; - onLoadMore?: () => void; - onSelect: (url: string) => void; - resizeWidth?: number; -}) { - const [scrollRef, measuredWidth] = useMeasuredWidth(); - const columnWidth = resizeWidth ?? measuredWidth; - const columnCount = getColumnCount(columnWidth, items.length); - const columns = distributeByHeight(items, columnCount, (item) => { - if (!item.width || !item.height) return 1; - - return item.height / item.width; - }); - - function handleScroll(event: React.UIEvent) { - const element = event.currentTarget; - const distanceFromBottom = - element.scrollHeight - element.scrollTop - element.clientHeight; - - if ( - distanceFromBottom > 240 || - !hasNextPage || - isFetchingNextPage || - !onLoadMore - ) { - return; - } - - onLoadMore(); - } - - useEffect(() => { - const element = scrollRef.current; - - if (!element || !hasNextPage || isFetchingNextPage || !onLoadMore) return; - - const distanceFromBottom = - element.scrollHeight - element.scrollTop - element.clientHeight; - - if (distanceFromBottom <= 240) { - onLoadMore(); - } - }, [ - columnCount, - hasNextPage, - isFetchingNextPage, - items.length, - onLoadMore, - scrollRef, - ]); - - return ( -
-
- {columns.map((column, columnIndex) => ( -
- {column.map((item) => ( - - ))} -
- ))} -
-
- {hasNextPage ? ( -
- - Loading more... -
- ) : null} -
-
- ); -} - -function KlipyPanel({ - kind, - onSelect, - resizeWidth, - searchString, -}: { - kind: KlipyKind; - onSelect: (url: string) => void; - resizeWidth?: number; - searchString: string; -}) { - const query = useInfiniteQuery({ - queryKey: ["klipy", kind, searchString], - initialPageParam: 1, - enabled: searchString !== "", - queryFn: ({ pageParam }) => - fetchKlipyPage({ - kind, - page: Number(pageParam), - search: searchString, - }), - getNextPageParam: (lastPage) => - lastPage.hasNext ? lastPage.currentPage + 1 : undefined, - }); - const items: PickerMedia[] = - query.data?.pages.flatMap((page) => - page.items.flatMap((item) => { - const displayMedia = pickDisplayMedia(kind, item); - const selectionUrl = pickSelectionUrl(kind, item); - - if (!displayMedia?.url || !selectionUrl) return []; - - return [ - { - key: item.id, - url: selectionUrl, - alt: item.title ?? "Klipy result", - width: displayMedia.width, - height: displayMedia.height, - }, - ]; - }), - ) ?? []; - - if (searchString === "") { - return ( -
- -

- {kind === "gif" && 'Try searching for "Funny cat"'} - {kind === "meme" && - 'Try searching for "Spiderman pointing at Spiderman"'} -

-
- ); - } - - if (query.isLoading) { - return ( -
- - Loading... -
- ); - } - - if (query.isError) { - return ( -
-

Failed to load {kind === "gif" ? "GIFs" : "memes"}.

- -
- ); - } - - if (items.length === 0) { - return ( -
- No {kind === "gif" ? "GIFs" : "memes"} found. -
- ); - } - - return ( - void query.fetchNextPage()} - onSelect={onSelect} - resizeWidth={resizeWidth} - /> - ); -} - -function SavedPanel({ - groups, - onSelect, - onSavedMediaChange, - onSavedMediaGroupsChange, - resizeWidth, - urls, -}: { - groups: ChatPickerMediaGroup[]; - onSelect: (url: string) => void; - onSavedMediaChange: (savedMedia: string[]) => void; - onSavedMediaGroupsChange: (groups: ChatPickerMediaGroup[]) => void; - resizeWidth?: number; - urls: string[]; -}) { - const [selectedGroupId, setSelectedGroupId] = useState(null); - const [scrollRef, measuredWidth] = useMeasuredWidth(); - const selectedGroup = groups.find((group) => group.id === selectedGroupId); - const sortedGroups = [...groups].sort((left, right) => - left.name.localeCompare(right.name), - ); - const visibleUrls = selectedGroup - ? selectedGroup.media.filter((url) => urls.includes(url)) - : getUngroupedMedia(urls, groups); - const imageSizes = useImageSizes(visibleUrls); - const columnWidth = resizeWidth ?? measuredWidth; - const columnCount = getColumnCount(columnWidth, visibleUrls.length); - const groupColumnCount = getColumnCount(columnWidth, groups.length); - const columns = distributeByHeight(visibleUrls, columnCount, (url) => { - const size = imageSizes[url]; - - if (!size?.width || !size.height) return 1; - - return size.height / size.width; - }); - - if (urls.length === 0) { - return ( -
- Saved media will appear here. -
- ); - } - - return ( -
- {selectedGroup ? ( -
- - {selectedGroup.name} -
- ) : groups.length > 0 ? ( -
- {sortedGroups.map((group) => ( - - ))} -
- ) : null} - - {visibleUrls.length === 0 ? ( -
- {selectedGroup ? "This group is empty." : "No ungrouped media."} -
- ) : null} - -
- {columns.map((column, columnIndex) => ( -
- {column.map((url) => { - const size = imageSizes[url]; - - return ( -
- - -
- ); - })} -
- ))} -
-
- ); -} - -export default function GifPicker({ - onSelect, - resizeHeight, - resizeWidth, -}: { - onSelect: (url: string) => void; - resizeHeight?: number; - resizeWidth?: number; -}) { - const { load, save } = useStorage(); - const [tab, setTab] = useState("gif"); - const [searchString, setSearchString] = useState(""); - const [debouncedSearchString, setDebouncedSearchString] = useState(""); - const [savedMedia, setSavedMedia] = useState([]); - const [savedMediaGroups, setSavedMediaGroups] = useState< - ChatPickerMediaGroup[] - >([]); - const didLoadLastTabRef = useRef(false); - const searchDebouncerRef = useRef void> | null>( - null, - ); - - if (searchDebouncerRef.current === null) { - searchDebouncerRef.current = new Debouncer( - (value) => { - startTransition(() => { - setDebouncedSearchString(value.trim()); - }); - }, - { wait: 300 }, - ); - } - - useEffect(() => { - return () => { - searchDebouncerRef.current?.cancel(); - }; - }, []); - - useEffect(() => { - if (tab !== "saved") return; - - void Promise.all([ - load("chat_picker_saved_media"), - load("chat_picker_saved_media_groups"), - ]).then(([nextSavedMedia, nextGroups]) => { - setSavedMedia(nextSavedMedia); - setSavedMediaGroups(nextGroups); - }); - }, [load, tab]); - - useEffect(() => { - void load("chat_picker_last_tab").then((savedTab) => { - setTab(savedTab); - didLoadLastTabRef.current = true; - }); - }, [load]); - - useEffect(() => { - if (!didLoadLastTabRef.current) return; - - void save("chat_picker_last_tab", tab); - }, [save, tab]); - - function handleTabChange(nextTab: string) { - if (nextTab !== "gif" && nextTab !== "meme" && nextTab !== "saved") return; - - setTab(nextTab); - } - - function handleSearchChange(event: React.ChangeEvent) { - const nextValue = event.target.value; - - setSearchString(nextValue); - searchDebouncerRef.current?.maybeExecute(nextValue); - } - - return ( - - - GIFs - Memes - Saved - - - - - - - - - - - - - - ); -} diff --git a/packages/chat/src/components/media/media.tsx b/packages/chat/src/components/media/media.tsx deleted file mode 100644 index 1072308..0000000 --- a/packages/chat/src/components/media/media.tsx +++ /dev/null @@ -1,301 +0,0 @@ -import { Text } from "@methanium/ui/markdown"; -import { useStorage } from "@tensamin/storage/context"; -import { - Avatar, - AvatarFallback, - AvatarImage, - Button, - Dialog, - DialogClose, - DialogContent, - DialogTrigger, - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@methanium/ui"; -import { toast } from "@tensamin/shared/log"; -import { - Check, - Copy, - ExternalLink, - TriangleAlert, - X, - ZoomIn, -} from "lucide-react"; -import { useState, useMemo, useEffect, useRef, type WheelEvent } from "react"; -import MediaSaveButton from "./mediaSaveButton"; -import { useUserFields } from "@tensamin/user/context"; - -const zoomLevels = [1, 1.5, 2, 3]; - -export default function Media({ - link, - senderId, - time, -}: { - link: string; - senderId: number; - time: string; -}) { - const { load } = useStorage(); - - const [trustedDomains, setTrustedDomains] = useState([]); - const [hidden, setHidden] = useState(true); - const [dialogOpen, setDialogOpen] = useState(false); - const [zoomLevel, setZoomLevel] = useState(0); - const wheelDelta = useRef(0); - const hostname = useMemo(() => new URL(link).hostname, [link]); - - const { data: user } = useUserFields(senderId, ["Avatar", "Display"]); - const avatar = user?.Avatar - ? `data:image/webp;base64,${user.Avatar}` - : undefined; - - useEffect(() => { - load("chat_trusted_domains").then(setTrustedDomains); - }, [load]); - - const changeZoom = (change: number) => { - setZoomLevel((level) => - Math.max(0, Math.min(zoomLevels.length - 1, level + change)), - ); - }; - - const handleWheel = (event: WheelEvent) => { - wheelDelta.current += event.deltaY; - - if (Math.abs(wheelDelta.current) < 50) return; - - changeZoom(wheelDelta.current < 0 ? 1 : -1); - wheelDelta.current = 0; - }; - - const [copied, setCopied] = useState(false); - useEffect(() => { - let cancelled = false; - - if (copied) { - setTimeout(() => { - if (cancelled) return; - setCopied(false); - }, 1000); - } - - return () => { - cancelled = true; - }; - }, [copied]); - const copyImage = async () => { - try { - if (!navigator.clipboard?.write || typeof ClipboardItem === "undefined") { - throw new Error("Image clipboard access is not supported."); - } - - const image = (async () => { - const response = await fetch(link); - if (!response.ok) { - throw new Error(`Failed to download image (${response.status}).`); - } - - const blob = await response.blob(); - if (blob.type === "image/png") return blob; - - const bitmap = await createImageBitmap(blob); - try { - const canvas = document.createElement("canvas"); - canvas.width = bitmap.width; - canvas.height = bitmap.height; - const context = canvas.getContext("2d"); - - if (!context) { - throw new Error("Failed to prepare the image for copying."); - } - - context.drawImage(bitmap, 0, 0); - return await new Promise((resolve, reject) => { - canvas.toBlob( - (convertedBlob) => - convertedBlob - ? resolve(convertedBlob) - : reject(new Error("Failed to convert the image to PNG.")), - "image/png", - ); - }); - } finally { - bitmap.close(); - } - })(); - - await navigator.clipboard.write([ - new ClipboardItem({ "image/png": image }), - ]); - setCopied(true); - } catch (error) { - toast("error", "Failed to copy image", String(error)); - } - }; - - return trustedDomains.includes(hostname) ? ( -
- {hidden ? ( - - ) : ( - - )} - { - setDialogOpen(open); - if (!open) { - setZoomLevel(0); - wheelDelta.current = 0; - } - }} - > - ( - setHidden(false)} - className="max-h-70 max-w-70 py-1 rounded-lg cursor-pointer" - /> - )} - /> - -
-
- - - - {user?.Display.slice(0, 2).toUpperCase()} - - -
-

{user?.Display}

-

{time}

-
-
-
-
- - ( - - )} - /> - Zoom {zoomLevel + 1}/4 - -
- - ( - - )} - /> - - {copied ? "Copied!" : "Copy image"} - - - - ( - - - - )} - /> - Open in browser - -
-
-
- ( - - ( - - )} - /> - Close - - )} - /> -
-
-
- setHidden(false)} - style={{ - transform: `translate(-50%, -50%) scale(${zoomLevels[zoomLevel]})`, - }} - className="h-[80vh] rounded-lg object-cover absolute top-1/2 left-1/2 transition-transform duration-200" - /> -
-
-
- ) : ( -
- - } - /> - - Link embeds can get your IP-Address! You can configure trusted domains - in the settings. - - - -
- ); -} diff --git a/packages/chat/src/components/media/mediaGroups.ts b/packages/chat/src/components/media/mediaGroups.ts deleted file mode 100644 index 1103781..0000000 --- a/packages/chat/src/components/media/mediaGroups.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { ChatPickerMediaGroup } from "@tensamin/shared/data"; - -function withoutMedia( - groups: ChatPickerMediaGroup[], - url: string, - keepGroupId?: string, -) { - return groups - .map((group) => ({ - ...group, - media: group.media.filter((item) => item !== url), - })) - .filter((group) => group.id === keepGroupId || group.media.length > 0); -} - -export function assignMediaToGroup( - groups: ChatPickerMediaGroup[], - groupId: string | null, - url: string, -) { - const nextGroups = withoutMedia(groups, url, groupId ?? undefined); - - if (groupId === null) return nextGroups; - - return nextGroups.map((group) => - group.id === groupId ? { ...group, media: [url, ...group.media] } : group, - ); -} - -export function createMediaGroup( - groups: ChatPickerMediaGroup[], - id: string, - name: string, - url: string, -) { - const trimmedName = name.trim(); - - if ( - !trimmedName || - groups.some( - (group) => - group.name.toLocaleLowerCase() === trimmedName.toLocaleLowerCase(), - ) - ) { - return groups; - } - - return [ - ...withoutMedia(groups, url), - { id, name: trimmedName, media: [url] }, - ]; -} - -export function getMediaGroupId(groups: ChatPickerMediaGroup[], url: string) { - return groups.find((group) => group.media.includes(url))?.id ?? null; -} - -export function getUngroupedMedia( - savedMedia: string[], - groups: ChatPickerMediaGroup[], -) { - const groupedMedia = new Set(groups.flatMap((group) => group.media)); - - return savedMedia.filter((url) => !groupedMedia.has(url)); -} diff --git a/packages/chat/src/components/media/mediaSaveButton.tsx b/packages/chat/src/components/media/mediaSaveButton.tsx deleted file mode 100644 index e585eae..0000000 --- a/packages/chat/src/components/media/mediaSaveButton.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import { - Button, - cn, - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, - Input, - Label, - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, - useIsMobile, -} from "@methanium/ui"; -import type { ChatPickerMediaGroup } from "@tensamin/shared/data"; -import { useStorage } from "@tensamin/storage/context"; -import { Ellipsis, Star } from "lucide-react"; -import { useEffect, useState } from "react"; -import { - assignMediaToGroup, - createMediaGroup, - getMediaGroupId, -} from "./mediaGroups"; - -export default function MediaSaveButton({ - ariaLabel, - className, - defaultSaved = false, - onSavedMediaChange, - onSavedMediaGroupsChange, - url, -}: { - ariaLabel?: string; - className?: string; - defaultSaved?: boolean; - onSavedMediaChange?: (savedMedia: string[]) => void; - onSavedMediaGroupsChange?: (groups: ChatPickerMediaGroup[]) => void; - url: string; -}) { - const { load, save } = useStorage(); - const isMobile = useIsMobile(); - const [savedMedia, setSavedMedia] = useState(null); - const [groups, setGroups] = useState(null); - const [dialogOpen, setDialogOpen] = useState(false); - const [selectedGroupId, setSelectedGroupId] = useState(null); - const [newGroupName, setNewGroupName] = useState(""); - const isSaved = savedMedia ? savedMedia.includes(url) : defaultSaved; - const sortedGroups = [...(groups ?? [])].sort((left, right) => - left.name.localeCompare(right.name), - ); - const normalizedNewGroupName = newGroupName.trim().toLocaleLowerCase(); - const canCreateGroup = - normalizedNewGroupName.length > 0 && - !(groups ?? []).some( - (group) => group.name.toLocaleLowerCase() === normalizedNewGroupName, - ); - - useEffect(() => { - void Promise.all([ - load("chat_picker_saved_media"), - load("chat_picker_saved_media_groups"), - ]).then(([nextSavedMedia, nextGroups]) => { - setSavedMedia(nextSavedMedia); - setGroups(nextGroups); - }); - }, [load]); - - async function handleSaveClick(event: React.MouseEvent) { - event.stopPropagation(); - - const [currentSavedMedia, currentGroups] = await Promise.all([ - load("chat_picker_saved_media"), - load("chat_picker_saved_media_groups"), - ]); - const willUnsave = currentSavedMedia.includes(url); - const nextSavedMedia = willUnsave - ? currentSavedMedia.filter((item) => item !== url) - : [url, ...currentSavedMedia]; - - setSavedMedia(nextSavedMedia); - onSavedMediaChange?.(nextSavedMedia); - if (willUnsave) { - const nextGroups = assignMediaToGroup(currentGroups, null, url); - setGroups(nextGroups); - onSavedMediaGroupsChange?.(nextGroups); - await Promise.all([ - save("chat_picker_saved_media", nextSavedMedia), - save("chat_picker_saved_media_groups", nextGroups), - ]); - return; - } - - await save("chat_picker_saved_media", nextSavedMedia); - } - - async function handleGroupClick(event: React.MouseEvent) { - event.stopPropagation(); - - const currentGroups = await load("chat_picker_saved_media_groups"); - setGroups(currentGroups); - setSelectedGroupId(getMediaGroupId(currentGroups, url)); - setNewGroupName(""); - setDialogOpen(true); - } - - async function handleGroupSave() { - const currentGroups = await load("chat_picker_saved_media_groups"); - const nextGroups = assignMediaToGroup(currentGroups, selectedGroupId, url); - - setGroups(nextGroups); - onSavedMediaGroupsChange?.(nextGroups); - await save("chat_picker_saved_media_groups", nextGroups); - setDialogOpen(false); - } - - async function handleCreateGroup() { - if (!canCreateGroup) return; - - const currentGroups = await load("chat_picker_saved_media_groups"); - const groupId = crypto.randomUUID(); - const nextGroups = createMediaGroup( - currentGroups, - groupId, - newGroupName, - url, - ); - - setGroups(nextGroups); - setSelectedGroupId(groupId); - setNewGroupName(""); - onSavedMediaGroupsChange?.(nextGroups); - await save("chat_picker_saved_media_groups", nextGroups); - setDialogOpen(false); - } - - return ( - <> -
- - {isSaved ? ( - - ) : null} -
- - - event.stopPropagation()}> - - Organize saved media - - -
-
- - -
- -
- -
- setNewGroupName(event.target.value)} - onKeyDown={(event) => { - if (event.key === "Enter") void handleCreateGroup(); - }} - /> - -
-
-
- - - - - -
-
- - ); -} diff --git a/packages/chat/src/components/message.tsx b/packages/chat/src/components/message.tsx index cbcd37e..e85d890 100644 --- a/packages/chat/src/components/message.tsx +++ b/packages/chat/src/components/message.tsx @@ -1,496 +1,76 @@ +import * as React from "react"; import type { RawMessage } from "../values"; -import { AlertTriangle, Check, CheckLine, RefreshCw } from "lucide-react"; -import { memo, useCallback, useEffect, useRef, useState } from "react"; -import { type SelectedUser, useUserFields } from "@tensamin/user/context"; +import Text from "@tensamin/markdown/text"; +import { AlertTriangle } from "lucide-react"; +import { cn, Tooltip, TooltipContent, TooltipTrigger } from "@tensamin/ui"; +import { useIsMobile } from "@tensamin/ui"; import { - Avatar, - AvatarFallback, - AvatarImage, - Button, - Card, - cn, - Skeleton, -} from "@methanium/ui"; -import MessageContextMenu from "./messageContextMenu"; -import Media from "./media/media"; -import { useStorage } from "@tensamin/storage/context"; -import { useMTP } from "@tensamin/mtp"; -import { getMessage, useChat } from "../context"; -import { decryptChatText, encryptChatText } from "@tensamin/crypto/chatSecret"; -import { log, toast } from "@tensamin/shared/log"; -import { Emoji, Input, normalizeShortcode, Text } from "@methanium/ui/markdown"; -import { useRecordEmojiUse } from "./emoji/emojiRanks"; -import ReplyBox from "./replyBox"; -import { useHotkey } from "@tensamin/hotkeys"; -import { cancelMessageEditHotkey } from "../hotkeys"; + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuTrigger, +} from "@tensamin/ui"; function MessageComponent({ - editing, - grouped, message, - onSetEditing, - user, }: { - editing: boolean; - grouped: boolean; message: RawMessage & { failed?: boolean; - decryptionFailed?: boolean; }; - onSetEditing: (editing: boolean) => void; - user: SelectedUser | null; }) { - const actuallyFailed = - (message.failed && message.MessageState === "awaiting") || - message.decryptionFailed; - - // Fade-in - const [hasFadedIn, setHasFadedIn] = useState(false); - const opacityClass = !hasFadedIn - ? "opacity-0" - : message.MessageState === "awaiting" - ? "opacity-50" - : "opacity-100"; - const messageRef = useRef(null); - useEffect(() => { - const timeout = window.setTimeout(() => { - setHasFadedIn(true); - }, 100); - - return () => { - window.clearTimeout(timeout); - }; - }, []); - - // Message states - const { load } = useStorage(); - const { send } = useMTP(); - const [ownId, setOwnId] = useState(0); - useEffect(() => { - load("user_id").then(setOwnId); - }, [load]); - const messageStateReadUpdate = useCallback(async () => { - const readConfirmations = await load("settings.read_confirmations"); - - if (readConfirmations) { - if (!user?.UserId) return; - - await send("MessageState", { - ChatPartnerId: user?.UserId, - SendTime: message.SendTime, - MessageState: "read", - }); - } else { - await send("MessageState", { - ChatPartnerId: user?.UserId, - SendTime: message.SendTime, - MessageState: "received", - }); - } - }, [load, message.SendTime, user?.UserId, send]); - useEffect(() => { - if (message.SenderId === ownId || ownId === 0) return; - - let cancelled = false; - - async function run() { - const receiveConfirmations = await load("settings.receive_confirmations"); - if (cancelled) return; - - switch (message.MessageState) { - case "sent": - if (receiveConfirmations) { - messageStateReadUpdate(); - } - break; - - case "received": - messageStateReadUpdate(); - break; - - default: - break; - } - } - - run(); - - return () => { - cancelled = true; - }; - }, [ - message.MessageState, - load, - messageStateReadUpdate, - message.SenderId, - ownId, - ]); - - // Check if message is a url - const [isValidURL, setIsValidURL] = useState(false); - useEffect(() => { - try { - if (message.Content.split(" ").length > 1) throw new Error(); - - new URL(message.Content); - setIsValidURL(true); - } catch { - setIsValidURL(false); - } - }, [message.Content]); - - // Message editing - const { - addReaction, - chatSecret, - editMessage, - userId, - removeReaction, - replyTo, - } = useChat(); - const [replyMessage, setReplyMessage] = useState(null); - const { data: replyUser } = useUserFields(replyMessage?.SenderId ?? null, [ - "Avatar", - "Display", - ]); - useEffect(() => { - if (!message.ReplyId || !ownId || !chatSecret) { - setReplyMessage(null); - return; - } - - let active = true; - void getMessage({ - sendTime: message.ReplyId, - ownId, - chatPartnerId: userId, - send, - }) - .then(async (reply) => { - const Content = await decryptChatText(chatSecret, reply.Content); - if (!active) return; - setReplyMessage({ ...reply, Content }); - }) - .catch((err) => { - if (!active) return; - setReplyMessage(null); - log(1, "chat", "red", "Failed to get replied-to message", err); - }); - - return () => { - active = false; - }; - }, [chatSecret, message.ReplyId, ownId, send, userId]); - const recordUse = useRecordEmojiUse(); - const editingRef = useRef(editing); - const onSetEditingRef = useRef(onSetEditing); - useEffect(() => { - editingRef.current = editing; - onSetEditingRef.current = onSetEditing; - }, [editing, onSetEditing]); - useEffect( - () => () => { - if (editingRef.current) onSetEditingRef.current(false); - }, - [], - ); - const [editDraft, setEditDraft] = useState(message.Content); - const cancelEditing = useCallback(() => { - setEditDraft(message.Content); - onSetEditing(false); - }, [message.Content, onSetEditing]); - useHotkey(cancelMessageEditHotkey, cancelEditing, { - enabled: editing, - target: messageRef, - }); - useEffect(() => { - if (!editing) { - setEditDraft(message.Content); - } - }, [editing, message.Content]); - const submitEditMessage = useCallback( - async (newContent: string) => { - if (!chatSecret) return; - - const encryptedContent = await encryptChatText( - chatSecret, - newContent, - ).catch((err) => { - toast("error", "Failed to encrypt edit", String(err)); - }); - - if (!encryptedContent) return; - - const previousContent = message.Content; - editMessage(message.SendTime, { Content: newContent, Edited: true }); - - try { - const response = await send("MessageEdit", { - ChatPartnerId: userId, - Content: encryptedContent, - SendTime: message.SendTime, - }); - - if (response.type.startsWith("Error")) { - throw new Error(response.type); - } - } catch (err) { - editMessage(message.SendTime, { - Content: previousContent, - Edited: message.Edited ?? false, - }); - log(1, "chat", "red", "Failed to edit message", err); - toast("error", "Failed to edit message", String(err)); - } - }, - [ - chatSecret, - editMessage, - userId, - message.Content, - message.Edited, - message.SendTime, - send, - ], - ); - - const groupedReactions = Object.entries( - (message.Reactions ?? []).reduce< - Record - >((groups, item) => { - const reaction = normalizeShortcode(item.Reaction); - if (!reaction) return groups; - - const group = groups[reaction] ?? { - count: 0, - reactedByMe: false, - }; - group.count += 1; - group.reactedByMe ||= item.SenderId === ownId; - groups[reaction] = group; - return groups; - }, {}), - ); - - function toggleReaction(reaction: string) { - const reactedByMe = message.Reactions?.some( - (item) => - normalizeShortcode(item.Reaction) === reaction && - item.SenderId === ownId, - ); - - if (reactedByMe) { - return removeReaction(message.SendTime, reaction); - } - - recordUse(reaction); - return addReaction(message.SendTime, reaction); - } + const isMobile = useIsMobile(); + const actuallyFailed = message.failed && message.message_state === "awaiting"; return (
- {/* reply */} - {replyMessage && replyUser && ( - - )} - {user && message.Content ? ( - - <> + + - <> - {grouped ? ( -

- {new Date(message.SendTime).toLocaleString([], { - hour: "2-digit", - minute: "2-digit", - })} -

- ) : ( - - - - {user.Display.slice(0, 2).toUpperCase()} - - - )} -
- {!grouped && ( -
-

{user.Display}

-

- {new Date(message.SendTime).toLocaleString([], { - hour: "2-digit", - minute: "2-digit", - })} -

-
- {message.SenderId !== - ownId ? null : message.MessageState === "read" ? ( - - ) : message.MessageState === "received" ? ( - - ) : message.MessageState === "sent" ? ( - - ) : message.MessageState === "sending" ? ( - - ) : null} - {actuallyFailed && ( - - )} -
-
- )} - {editing ? ( -
- - { - if (!editDraft.trim()) return; - if (editDraft !== message.Content) { - void submitEditMessage(editDraft); - } - onSetEditing(false); - }} - /> - -
- - -
-
- ) : ( -
- {isValidURL ? ( - - ) : ( -
- -
- )} -
- )} - {groupedReactions.length > 0 && ( -
- {groupedReactions.map( - ([reaction, { count, reactedByMe }]) => ( - - ), - )} -
- )} -
- + {message.failed && message.message_state === "awaiting" && ( + + +

Failed to send message

+
+ } /> +
+ )} +
- - - ) : ( - - )} + } + /> + + + + + +
); } -export default memo(MessageComponent, (prev, next) => { +export default React.memo(MessageComponent, (prev, next) => { return ( - prev.message.SendTime === next.message.SendTime && - prev.editing === next.editing && - prev.message.Content === next.message.Content && - prev.message.SenderId === next.message.SenderId && - prev.message.ReplyId === next.message.ReplyId && - prev.message.MessageState === next.message.MessageState && - prev.message.failed === next.message.failed && - prev.message.decryptionFailed === next.message.decryptionFailed && - JSON.stringify(prev.message.Reactions) === - JSON.stringify(next.message.Reactions) && - prev.grouped === next.grouped && - prev.user === next.user + prev.message.send_time === next.message.send_time && + prev.message.content === next.message.content && + prev.message.height === next.message.height && + prev.message.sent_by_self === next.message.sent_by_self && + prev.message.message_state === next.message.message_state && + prev.message.failed === next.message.failed ); }); diff --git a/packages/chat/src/components/messageContextMenu.tsx b/packages/chat/src/components/messageContextMenu.tsx deleted file mode 100644 index 2277aa1..0000000 --- a/packages/chat/src/components/messageContextMenu.tsx +++ /dev/null @@ -1,743 +0,0 @@ -import { - Button, - Card, - cn, - ContextMenu, - ContextMenuContent, - ContextMenuGroup, - ContextMenuItem, - ContextMenuSeparator, - ContextMenuSub, - ContextMenuSubContent, - ContextMenuSubTrigger, - ContextMenuTrigger, - Drawer, - DrawerContent, - DrawerDescription, - DrawerTitle, - Popover, - PopoverContent, - PopoverTrigger, - Separator, - Tooltip, - TooltipContent, - TooltipTrigger, - useIsMobile, -} from "@methanium/ui"; -import { - Clipboard, - Ellipsis, - Forward, - Laugh, - Plus, - Pen, - Pin, - Reply, - Trash, -} from "lucide-react"; -import { AnimatePresence, motion } from "motion/react"; -import { cloneElement, useMemo, useState, useSyncExternalStore } from "react"; -import type { - MouseEvent as ReactMouseEvent, - ReactElement, - ReactNode, - Ref, -} from "react"; -import { useChat } from "../context"; -import { Emoji } from "@methanium/ui/markdown"; -import EmojiPicker from "./emoji/emojiPicker"; -import { getRecentEmojis, useEmojiRanks } from "./emoji/emojiRanks"; - -async function copyText(text: string) { - await navigator.clipboard.writeText(text); -} - -type MenuComponents = { - Content: (props: { className?: string; children: ReactNode }) => ReactElement; - Group: (props: { children: ReactNode }) => ReactElement; - Item: (props: { - children: ReactNode; - className?: string; - disabled?: boolean; - onClick?: () => void | Promise; - variant?: "default" | "destructive"; - }) => ReactElement; - Separator: (props: { className?: string }) => ReactElement; - Sub: (props: { children: ReactNode }) => ReactElement; - SubTrigger: (props: { - children: ReactNode; - onClick?: () => void | Promise; - }) => ReactElement; - SubContent: (props: { children: ReactNode }) => ReactElement; -}; - -function blurActiveElement() { - if (document.activeElement instanceof HTMLElement) { - document.activeElement.blur(); - } -} - -const desktopMenuComponents: MenuComponents = { - Content: ContextMenuContent, - Group: ContextMenuGroup, - Item: ContextMenuItem, - Separator: ContextMenuSeparator, - Sub: ContextMenuSub, - SubTrigger: ContextMenuSubTrigger, - SubContent: ContextMenuSubContent, -}; - -function getMobileMenuComponents({ - description, - onClose, - title, -}: { - description: string; - onClose: () => void; - title: string; -}): MenuComponents { - return { - Content: ({ className, children }) => ( - - {title} - {description} -
{children}
-
- ), - Group: ({ children }) =>
{children}
, - Item: ({ children, className, disabled, onClick, variant = "default" }) => { - async function handleClick() { - await onClick?.(); - blurActiveElement(); - onClose(); - } - - return ( - - ); - }, - Separator: ({ className }) => ( -
- ), - Sub: ({ children }) =>
{children}
, - SubTrigger: ({ children, onClick }) => { - async function handleClick() { - blurActiveElement(); - onClose(); - await onClick?.(); - } - - return ( - - ); - }, - SubContent: ({ children }) =>
{children}
, - }; -} - -function ReactionItems({ - Item, - emojis, - onMore, - onSelect, -}: { - Item: MenuComponents["Item"]; - emojis: string[]; - onMore: () => void; - onSelect: (emoji: string) => void; -}) { - return ( - <> - {emojis.map((emoji) => ( - onSelect(emoji)}> - - {emoji} - - ))} - - - More reactions - - - ); -} - -let shiftPressed = false; -const shiftListeners = new Set<() => void>(); - -function setShiftPressed(value: boolean) { - if (shiftPressed === value) return; - - shiftPressed = value; - shiftListeners.forEach((listener) => listener()); -} - -function subscribeToShift(listener: () => void) { - shiftListeners.add(listener); - - if (shiftListeners.size === 1) { - window.addEventListener("keydown", handleShiftKey); - window.addEventListener("keyup", handleShiftKey); - window.addEventListener("blur", handleWindowBlur); - } - - return () => { - shiftListeners.delete(listener); - if (shiftListeners.size === 0) { - window.removeEventListener("keydown", handleShiftKey); - window.removeEventListener("keyup", handleShiftKey); - window.removeEventListener("blur", handleWindowBlur); - shiftPressed = false; - } - }; -} - -function handleShiftKey(event: KeyboardEvent) { - setShiftPressed(event.shiftKey); -} - -function handleWindowBlur() { - setShiftPressed(false); -} - -function useShiftPressed() { - return useSyncExternalStore( - subscribeToShift, - () => shiftPressed, - () => false, - ); -} - -let activeMiniMenuId: number | null = null; -let clearMiniMenuTimeout: ReturnType | undefined; -const miniMenuListeners = new Set<() => void>(); - -function setActiveMiniMenu(messageId: number | null) { - if (clearMiniMenuTimeout !== undefined) { - clearTimeout(clearMiniMenuTimeout); - clearMiniMenuTimeout = undefined; - } - if (activeMiniMenuId === messageId) return; - - activeMiniMenuId = messageId; - miniMenuListeners.forEach((listener) => listener()); -} - -function scheduleMiniMenuClose() { - clearMiniMenuTimeout = setTimeout(() => { - clearMiniMenuTimeout = undefined; - setActiveMiniMenu(null); - }, 80); -} - -function useActiveMiniMenuId() { - return useSyncExternalStore( - (listener) => { - miniMenuListeners.add(listener); - return () => miniMenuListeners.delete(listener); - }, - () => activeMiniMenuId, - () => null, - ); -} - -function MiniMenuTooltip({ - children, - label, -}: { - children: ReactElement<{ - onClick?: (event: ReactMouseEvent) => void; - ref?: Ref; - }>; - label: string; -}) { - return ( - - - cloneElement(children, { - ref, - onClick: (event) => { - onClick?.(event); - children.props.onClick?.(event); - }, - }) - } - /> - {label} - - ); -} - -function MiniMessageMenu({ - fadeOut, - isOwnMessage, - onDelete, - onEdit, - onOpenMenu, - onOpenPicker, - usePickerTrigger, - onReact, - quickReactions, - onReply, - shiftIsPressed, -}: { - fadeOut: boolean; - isOwnMessage: boolean; - onDelete: () => void; - onEdit: () => void; - onOpenMenu: (event: ReactMouseEvent) => void; - onOpenPicker: () => void; - usePickerTrigger: boolean; - onReact: (emoji: string) => void; - quickReactions: string[]; - onReply: () => void; - shiftIsPressed: boolean; -}) { - return ( - - - {quickReactions.map((emoji) => ( - onReact(emoji)} - > - - - } - /> - ))} - - ( - - )} - /> - ) : ( - - ) - } - /> - - {isOwnMessage ? : } - - } - /> - - - - } - /> - - {isOwnMessage && shiftIsPressed ? ( - - - - } - /> - ) : ( - - - - } - /> - )} - - - ); -} - -function MessageMenuContent({ - components, - content, - devEnabled, - isOwnMessage, - messageId, - onAddReaction, - reactionEmojis, - onReact, - showReactionItems = true, - onSetEditing, -}: { - components: MenuComponents; - content: string; - devEnabled: boolean; - isOwnMessage: boolean; - messageId: number; - onAddReaction?: () => void | Promise; - reactionEmojis: string[]; - onReact: (emoji: string) => void; - showReactionItems?: boolean; - onSetEditing: (value: boolean) => void; -}) { - const { Content, Group, Item, Separator, Sub, SubContent, SubTrigger } = - components; - - const { deleteMessage, setReplyTo } = useChat(); - - return ( - - - - - Add Reaction - - {showReactionItems && ( - - void onAddReaction?.()} - onSelect={onReact} - /> - - )} - - -

Pin Message

-
- copyText(content)} - > -

Copy Raw

-
-
- - - {isOwnMessage && ( - { - onSetEditing(true); - }} - > -

Edit Message

-
- )} - { - setReplyTo(messageId); - }} - > -

Reply

-
- -

Forward

-
-
- {isOwnMessage && ( - <> - - - deleteMessage(messageId)} - > -

Delete Message

-
-
- - )} - {devEnabled && ( - <> - - - copyText(String(messageId))} - > -

Copy ID

-
-
- - )} -
- ); -} - -export default function MessageContextMenu({ - children, - content, - hideMiniMenu = false, - isOwnMessage, - messageId, - onReact, - onSetEditing, -}: { - children: ReactElement; - content: string; - hideMiniMenu?: boolean; - isOwnMessage: boolean; - messageId: number; - onReact: (emoji: string) => void | Promise; - onSetEditing: (value: boolean) => void; -}) { - const isMobile = useIsMobile(); - const shiftIsPressed = useShiftPressed(); - const activeMenuId = useActiveMiniMenuId(); - const { deleteMessage, setReplyTo } = useChat(); - const devEnabled = useMemo( - () => Number(localStorage.getItem("log_level")) >= 3, - [], - ); - const [mainDrawerOpen, setMainDrawerOpen] = useState(false); - const [contextMenuOpen, setContextMenuOpen] = useState(false); - const [reactionDrawerOpen, setReactionDrawerOpen] = useState(false); - const [pickerOpen, setPickerOpen] = useState(false); - const [pickerClosing, setPickerClosing] = useState(false); - const [fadeMiniMenu, setFadeMiniMenu] = useState(false); - const { ranks } = useEmojiRanks(); - const quickReactions = getRecentEmojis(ranks, 3); - const menuReactions = getRecentEmojis(ranks, 5); - - function selectReaction(emoji: string) { - void onReact(emoji); - setActiveMiniMenu(null); - setReactionDrawerOpen(false); - setPickerOpen(false); - setPickerClosing(false); - } - function setPickerVisibility(open: boolean) { - setPickerOpen(open); - setPickerClosing(!isMobile && !open); - } - const mainDrawerComponents = useMemo( - () => - getMobileMenuComponents({ - description: "Actions available for this message.", - onClose: () => setMainDrawerOpen(false), - title: "Message actions", - }), - [], - ); - const reactionDrawerComponents = useMemo( - () => - getMobileMenuComponents({ - description: "Choose a reaction to add to this message.", - onClose: () => setReactionDrawerOpen(false), - title: "Add reaction", - }), - [], - ); - - function renderMessage( - openMenu: (event: ReactMouseEvent) => void, - ) { - return ( -
setActiveMiniMenu(messageId)} - onPointerLeave={scheduleMiniMenuClose} - > - {children} - setFadeMiniMenu(false)}> - {!isMobile && - !hideMiniMenu && - (activeMenuId === messageId || - contextMenuOpen || - pickerOpen || - pickerClosing) && ( - deleteMessage(messageId)} - onEdit={() => onSetEditing(true)} - onOpenMenu={openMenu} - onOpenPicker={() => setPickerVisibility(true)} - usePickerTrigger={!isMobile} - onReact={selectReaction} - quickReactions={quickReactions} - onReply={() => setReplyTo(messageId)} - shiftIsPressed={shiftIsPressed} - /> - )} - -
- ); - } - - if (isMobile) { - const child = renderMessage(() => setMainDrawerOpen(true)); - - return ( - <> - - {cloneElement(child, { - onContextMenu: (event: ReactMouseEvent) => { - child.props.onContextMenu?.(event); - if (event.defaultPrevented) return; - - event.preventDefault(); - setMainDrawerOpen(true); - }, - })} - setReactionDrawerOpen(true)} - onReact={selectReaction} - reactionEmojis={menuReactions} - showReactionItems={false} - onSetEditing={onSetEditing} - /> - - - - Add reaction - - Choose a reaction to add to this message. - -
- { - setReactionDrawerOpen(false); - setPickerVisibility(true); - }} - onSelect={selectReaction} - /> -
-
-
- - - Choose an emoji - - Choose an emoji to react with. - -
- -
-
-
- - ); - } - - const child = renderMessage((event) => { - event.preventDefault(); - event.stopPropagation(); - event.currentTarget.dispatchEvent( - new MouseEvent("contextmenu", { - bubbles: true, - button: 2, - clientX: event.clientX, - clientY: event.clientY, - }), - ); - }); - - return ( - { - setPickerVisibility(open); - setFadeMiniMenu(!open && eventDetails.reason === "outside-press"); - }} - onOpenChangeComplete={(open) => { - if (!open) setPickerClosing(false); - }} - > - - - setPickerVisibility(true)} - onReact={selectReaction} - reactionEmojis={menuReactions} - onSetEditing={onSetEditing} - /> - - - - - - ); -} diff --git a/packages/chat/src/components/replyBox.tsx b/packages/chat/src/components/replyBox.tsx deleted file mode 100644 index fef038e..0000000 --- a/packages/chat/src/components/replyBox.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { - Avatar, - AvatarFallback, - AvatarImage, - Button, - cn, - Skeleton, -} from "@methanium/ui"; -import { Text } from "@methanium/ui/markdown"; -import type { SelectedUser } from "@tensamin/user/context"; -import Wrapper from "@tensamin/user/wrapper"; -import { Forward, X } from "lucide-react"; - -type ReplyUserData = SelectedUser; - -function ReplyUser({ user }: { user: ReplyUserData }) { - return ( -
- - - - {user.Display.slice(0, 2).toUpperCase()} - - -

- {user.Display} -

-
- ); -} - -export default function ReplyBox({ - edited, - content, - loading = false, - onDismiss, - user, - userId, - variant, -}: { - edited?: boolean; - content?: string; - loading?: boolean; - onDismiss?: () => void; - user?: ReplyUserData; - userId?: number; - variant: "composer" | "message"; -}) { - return ( -
-
- - {loading ? ( - <> - - - - ) : ( - <> - {user ? ( - - ) : userId ? ( - } - component={(resolvedUser) => } - /> - ) : null} - {content !== undefined && ( -
- -
- )} - - )} - {onDismiss && ( - - )} -
-
- ); -} diff --git a/packages/chat/src/context.tsx b/packages/chat/src/context.tsx index 7c88df1..38ee454 100644 --- a/packages/chat/src/context.tsx +++ b/packages/chat/src/context.tsx @@ -12,87 +12,35 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { useRouterState } from "@tanstack/react-router"; import type { InfiniteData } from "@tanstack/react-query"; import type { LiveMessage, RawMessage, RawMessages } from "./values"; -import { - deriveChatId, - deriveChatSecretId, - decryptChatText, - kemPublicKeyFromPublicKeyBundle, - ownKemPublicKeyFromKeyring, - randomChatSecret, - unwrapChatSecret, - wrapChatSecret, -} from "@tensamin/crypto/chatSecret"; -import { useStorage } from "@tensamin/storage/context"; -import { useMTP } from "@tensamin/mtp"; -import { log, toast } from "@tensamin/shared/log"; -import { useSession } from "@tensamin/storage/session"; +import { useCrypto } from "@tensamin/crypto/context"; import { useUser } from "@tensamin/user/context"; -import { createCache, type ChatDraft } from "@tensamin/cache"; -import { secureValueCodec } from "@tensamin/storage/secure"; +import { useStorage } from "@tensamin/storage/context"; +import { useTTP } from "@tensamin/ttp"; +import { log } from "@tensamin/shared/log"; +import { useSession } from "@tensamin/storage/session"; export const context = createContext(undefined); const queryClient = new QueryClient(); -const CHAT_SECRET_VERSION = 1; -function bytesFromProtocol(value: unknown): Uint8Array { - if (value instanceof Uint8Array) return value; - if (value instanceof ArrayBuffer) return new Uint8Array(value); - if (ArrayBuffer.isView(value)) { - return new Uint8Array(value.buffer, value.byteOffset, value.byteLength); - } - if (Array.isArray(value)) return new Uint8Array(value); - if (typeof value === "string") { - const bin = atob(value); - const out = new Uint8Array(bin.length); - for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i); - return out; - } - if (typeof value === "object" && value !== null) { - const entries = Object.entries(value); - if ( - entries.every( - ([key, item]) => /^\d+$/.test(key) && typeof item === "number", - ) - ) { - return new Uint8Array(entries.map(([, item]) => item as number)); - } - } - throw new Error("expected protocol bytes"); -} - -type EditableMessage = RawMessage & { failed?: boolean }; -type MessageEdit = Partial< - Pick< - EditableMessage, - "Content" | "Edited" | "MessageState" | "Reactions" | "failed" - > ->; - -function updateMessagesBySendTime( +function updateMessageStateBySendTime< + T extends { send_time: number; message_state: RawMessage["message_state"] }, +>( messages: T[], sendTime: number, - edit: MessageEdit, + messageState: RawMessage["message_state"], ): { next: T[]; updated: boolean } { let updated = false; const next = messages.map((item) => { - if (item.SendTime !== sendTime) { - return item; - } - - const entries = Object.entries(edit) as Array< - [keyof MessageEdit, MessageEdit[keyof MessageEdit]] - >; - - if (entries.every(([key, value]) => item[key] === value)) { + if (item.send_time !== sendTime || item.message_state === messageState) { return item; } updated = true; return { ...item, - ...edit, + message_state: messageState, }; }); @@ -102,149 +50,25 @@ function updateMessagesBySendTime( }; } -function updateMessageReaction( - message: T, - reaction: string, - senderId: number, - accepted: boolean, -): T { - const current = message.Reactions ?? []; - const withoutReaction = current.filter( - (item) => item.Reaction !== reaction || item.SenderId !== senderId, - ); - const next = accepted - ? [...withoutReaction, { Reaction: reaction, SenderId: senderId }] - : withoutReaction; - - if ( - next.length === current.length && - next.every( - (item, index) => - item.Reaction === current[index]?.Reaction && - item.SenderId === current[index]?.SenderId, - ) - ) { - return message; - } - - return { ...message, Reactions: next }; -} - -function assertProtocolSuccess(type: string, response: { type: string }) { - if (response.type.startsWith("Error")) { - throw new Error(`${type} failed: ${response.type}`); - } -} - -function protocolBytes(bytes: Uint8Array): Uint8Array { - return new Uint8Array(bytes); -} - -type SendMessageGet = ( - type: "MessageGet", - data: { SendTime: number }, -) => Promise<{ data: RawMessage }>; - -type StoredDraftState = ChatDraft & { - accountId: number; - userId: number; - loaded: boolean; - revision: number; -}; - -function draftKey(accountId: number, userId: number) { - return `${accountId}:${userId}`; -} - -export async function getMessage({ - sendTime, - ownId, - chatPartnerId, - send, -}: { - sendTime: number; - ownId: number; - chatPartnerId: number; - send: SendMessageGet; -}): Promise { - const cache = createCache(String(ownId), { - codec: secureValueCodec, - }); - const window = await cache.conversations.get(chatPartnerId); - const cachedMessage = window?.Messages.find( - (message) => message.SendTime === sendTime, - ); - const message = - cachedMessage ?? - ( - await send("MessageGet", { - SendTime: sendTime, - }) - ).data; - const messageChatPartnerId = - message.SenderId === ownId ? chatPartnerId : message.SenderId; - - if (messageChatPartnerId !== chatPartnerId) { - throw new Error("Reply message does not belong to this chat"); - } - - return message; -} - -export async function fetchReplyMessage({ - replyTo, - ownId, - chatUserId, - send, - getChatSecret, -}: { - replyTo: number; - ownId: number; - chatUserId: number; - send: SendMessageGet; - getChatSecret: (userId: number) => Promise; -}) { - const message = await getMessage({ - sendTime: replyTo, - ownId, - chatPartnerId: chatUserId, - send, - }); - const chatSecret = await getChatSecret(chatUserId); - - if (!chatSecret) { - throw new Error("Missing chat secret"); - } - - const decryptedContent = await decryptChatText(chatSecret, message.Content); - - return { - ...message, - Content: decryptedContent, - }; -} - -export default function Provider({ children }: { children: ReactNode }) { +/** + * Executes Provider. + * @param props Parameter props. + * @returns unknown. + */ +export default function Provider(props: { children: ReactNode }) { + const { getSharedSecret, decryptText } = useCrypto(); + const { get } = useUser(); const { load } = useStorage(); - const { send, subscribe } = useMTP(); - const { get: getUser } = useUser(); + const { send, subscribePush } = useTTP(); const { moveUserIdToTop } = useSession(); - const [error, setError] = useState(""); - const [errorDescription, setErrorDescription] = useState(""); - const [liveMessagesState, setLiveMessagesState] = useState([]); - const [ownId, setOwnId] = useState(0); - const [drafts, setDrafts] = useState>({}); - const draftsRef = useRef>({}); - const loadingDraftsRef = useRef(new Set()); - const draftWriteQueuesRef = useRef(new Map>()); - const [currentChatSecretState, setCurrentChatSecretState] = useState<{ + const [currentSharedSecretState, setCurrentSharedSecretState] = useState<{ userId: number; - value: Uint8Array | null; + value: string; }>({ userId: 0, - value: null, + value: "", }); const inputBoxRef = useRef(null); @@ -259,154 +83,15 @@ export default function Provider({ children }: { children: ReactNode }) { return Number(rawId ?? 0); }, [locationSearch]); - const currentChatSecret = useMemo(() => { - if (currentChatSecretState.userId !== userIdValue) { - return null; + const currentSharedSecret = useMemo(() => { + if (currentSharedSecretState.userId !== userIdValue) { + return ""; } - return currentChatSecretState.value; - }, [currentChatSecretState, userIdValue]); - - useEffect(() => { - load("user_id").then(setOwnId); - }, [load]); - - const persistDraft = useCallback( - (key: string, accountId: number, userId: number, draft: ChatDraft) => { - const previous = - draftWriteQueuesRef.current.get(key) ?? Promise.resolve(); - const next = previous - .catch(() => undefined) - .then(async () => { - const cache = createCache(String(accountId), { - codec: secureValueCodec, - }); - if (draft.Content === "" && draft.ReplyId === undefined) { - await cache.drafts.delete(userId); - } else { - await cache.drafts.put(userId, draft); - } - }) - .catch((err) => { - log(1, "chat", "red", "Failed to cache chat draft", err); - }); - draftWriteQueuesRef.current.set(key, next); - }, - [], - ); - - const updateDraft = useCallback( - ( - accountId: number, - userId: number, - update: (current: ChatDraft) => ChatDraft, - ) => { - const key = draftKey(accountId, userId); - const current = draftsRef.current[key] ?? { - accountId, - userId, - Content: "", - loaded: false, - revision: 0, - }; - const changed = update(current); - const next: StoredDraftState = { - ...current, - ...changed, - revision: current.revision + 1, - }; - const nextDrafts = { ...draftsRef.current, [key]: next }; - draftsRef.current = nextDrafts; - setDrafts(nextDrafts); - - if (next.loaded) { - persistDraft(key, accountId, userId, { - Content: next.Content, - ReplyId: next.ReplyId, - }); - } - }, - [persistDraft], - ); - - useEffect(() => { - if ( - !Number.isSafeInteger(ownId) || - ownId <= 0 || - !Number.isSafeInteger(userIdValue) || - userIdValue <= 0 - ) { - return; - } - - const key = draftKey(ownId, userIdValue); - if (draftsRef.current[key]?.loaded || loadingDraftsRef.current.has(key)) { - return; - } - loadingDraftsRef.current.add(key); - - void (async () => { - let stored: ChatDraft | undefined; - try { - stored = await createCache(String(ownId), { - codec: secureValueCodec, - }).drafts.get(userIdValue); - } catch (err) { - log(1, "chat", "red", "Failed to restore chat draft", err); - } finally { - const current = draftsRef.current[key]; - const next: StoredDraftState = - current && current.revision > 0 - ? { ...current, loaded: true } - : { - accountId: ownId, - userId: userIdValue, - Content: stored?.Content ?? "", - ReplyId: stored?.ReplyId, - loaded: true, - revision: 0, - }; - const nextDrafts = { ...draftsRef.current, [key]: next }; - draftsRef.current = nextDrafts; - setDrafts(nextDrafts); - loadingDraftsRef.current.delete(key); - - if (next.revision > 0) { - persistDraft(key, ownId, userIdValue, { - Content: next.Content, - ReplyId: next.ReplyId, - }); - } - } - })(); - }, [ownId, persistDraft, userIdValue]); - - const activeDraftKey = - ownId > 0 && userIdValue > 0 ? draftKey(ownId, userIdValue) : undefined; - const activeDraft = activeDraftKey ? drafts[activeDraftKey] : undefined; - const composerValue = activeDraft?.Content ?? ""; - const replyTo = activeDraft?.ReplyId; - const setComposerValue = useCallback( - (value: string) => { - if (ownId <= 0 || userIdValue <= 0) return; - updateDraft(ownId, userIdValue, (current) => ({ - ...current, - Content: value, - })); - }, - [ownId, updateDraft, userIdValue], - ); - const setReplyTo = useCallback( - (value: number | undefined) => { - if (ownId <= 0 || userIdValue <= 0) return; - updateDraft(ownId, userIdValue, (current) => ({ - ...current, - ReplyId: value, - })); - }, - [ownId, updateDraft, userIdValue], - ); + return currentSharedSecretState.value; + }, [currentSharedSecretState, userIdValue]); + // Load shared secret useEffect(() => { if (!userIdValue) return; @@ -414,93 +99,28 @@ export default function Provider({ children }: { children: ReactNode }) { void (async () => { try { - setError(""); - setErrorDescription(""); - - const ownUserId = Number(await load("user_id")); - const keyring = await load("mtp_keyring"); - const chatId = deriveChatId(ownUserId, userIdValue); - const secretId = deriveChatSecretId(chatId); - - const existing = await send("GetChatSecret", { - UserId: String(ownUserId), - ChatId: chatId, - SecretId: secretId, - }); - - if (!existing.type.startsWith("Error")) { - const data = existing.data as Record; - const secret = await unwrapChatSecret({ - encryptedSecret: bytesFromProtocol(data.EncryptedSecret), - kemCiphertext: bytesFromProtocol(data.KemCiphertext), - keyring, - chatId: String(data.ChatId), - secretId: String(data.SecretId), - version: Number(data.VersionNumber), - wrappingScheme: String(data.WrappingScheme), - }); - - if (active) { - setCurrentChatSecretState({ userId: userIdValue, value: secret }); - } - return; - } - - if (existing.type !== "ErrorNotSet") { - throw new Error(`GetChatSecret failed: ${existing.type}`); - } - - const rawSecret = randomChatSecret(); - const ownWrapped = await wrapChatSecret({ - chatSecret: rawSecret, - recipientKemPublicKey: ownKemPublicKeyFromKeyring(keyring), - chatId, - secretId, - version: CHAT_SECRET_VERSION, - }); - const peerUser = await getUser(userIdValue, ["PublicKey"]); - const peerWrapped = await wrapChatSecret({ - chatSecret: rawSecret, - recipientKemPublicKey: kemPublicKeyFromPublicKeyBundle( - peerUser.PublicKey, - ), - chatId, - secretId, - version: CHAT_SECRET_VERSION, - }); - - assertProtocolSuccess( - "SetChatSecret", - await send("SetChatSecret", { - ChatId: chatId, - SecretId: secretId, - VersionNumber: CHAT_SECRET_VERSION, - WrappingScheme: ownWrapped.wrappingScheme, - CreatedAt: Date.now(), - Recipients: [ - { - UserId: String(ownUserId), - EncryptedSecret: protocolBytes(ownWrapped.encryptedSecret), - KemCiphertext: protocolBytes(ownWrapped.kemCiphertext), - }, - { - UserId: String(userIdValue), - EncryptedSecret: protocolBytes(peerWrapped.encryptedSecret), - KemCiphertext: protocolBytes(peerWrapped.kemCiphertext), - }, - ], - }), + const recipientData = await get(userIdValue); + const ownId = await load("user_id"); + const privateKey = await load("private_key"); + const ownData = await get(ownId); + const sharedSecret = await getSharedSecret( + privateKey, + ownData.public_key, + recipientData.public_key, ); if (active) { - setCurrentChatSecretState({ userId: userIdValue, value: rawSecret }); + setCurrentSharedSecretState({ + userId: userIdValue, + value: sharedSecret, + }); } - } catch (err) { - log(1, "chat", "red", "Failed to initialize chat secret", err); + } catch { if (active) { - setError(err instanceof Error ? err.name : "Unknown Error"); - setErrorDescription(err instanceof Error ? err.message : String(err)); - setCurrentChatSecretState({ userId: userIdValue, value: null }); + setCurrentSharedSecretState({ + userId: userIdValue, + value: "", + }); } } })(); @@ -508,149 +128,145 @@ export default function Provider({ children }: { children: ReactNode }) { return () => { active = false; }; - }, [getUser, load, send, userIdValue]); - - const getChatSecret = useCallback( - async (userId: number): Promise => { - if (!Number.isSafeInteger(userId) || userId <= 0) { - throw new Error("Invalid chat user id"); - } - - const ownUserId = Number(await load("user_id")); - const keyring = await load("mtp_keyring"); - const chatId = deriveChatId(ownUserId, userId); - const secretId = deriveChatSecretId(chatId); - - const response = await send("GetChatSecret", { - UserId: String(ownUserId), - ChatId: chatId, - SecretId: secretId, - }); - - if (response.type === "ErrorNotSet") { - return null; - } - - if (response.type.startsWith("Error")) { - throw new Error(`GetChatSecret failed: ${response.type}`); - } - - const data = response.data as Record; - return await unwrapChatSecret({ - encryptedSecret: bytesFromProtocol(data.EncryptedSecret), - kemCiphertext: bytesFromProtocol(data.KemCiphertext), - keyring, - chatId: String(data.ChatId), - secretId: String(data.SecretId), - version: Number(data.VersionNumber), - wrappingScheme: String(data.WrappingScheme), - }); - }, - [load, send], - ); - - const decryptMessages = useCallback( - async (messages: RawMessages) => { - if (!currentChatSecret) return []; - return Promise.all( - messages.map(async (message) => { - try { - return { - ...message, - Content: await decryptChatText( - currentChatSecret, - message.Content, - ), - }; - } catch (err) { - log(1, "chat", "red", "Failed to decrypt historical message", err, { - SendTime: message.SendTime, - }); - return { - ...message, - Content: "Failed to decrypt message", - decryptionFailed: true, - }; - } - }), - ); - }, - [currentChatSecret], - ); - - const getCachedMessages = useCallback(async () => { - if (!ownId || !userIdValue || !currentChatSecret) return []; - const cache = createCache(String(ownId), { - codec: secureValueCodec, - }); - const window = await cache.conversations.get(userIdValue); - return decryptMessages(window?.Messages ?? []); - }, [currentChatSecret, decryptMessages, ownId, userIdValue]); + }, [get, getSharedSecret, load, userIdValue]); const getMessages = useCallback( async (amount: number, offset: number) => { - if (!currentChatSecret) { - return []; - } - - const messages = await send("MessagesGet", { - Amount: amount, - Offset: offset, - UserId: userIdValue, + const messages = await send("messages_get", { + amount, + offset, + user_id: userIdValue, }); if (messages.type.startsWith("error")) { throw new Error(messages.type); } - const rawMessages = messages.data.Messages; - const sorted = [...rawMessages].sort((a, b) => a.SendTime - b.SendTime); + const rawMessages = messages.data.messages; + const sorted = [...rawMessages].sort((a, b) => a.send_time - b.send_time); if (sorted.length > 0) { - const fetchedSendTimes = new Set(sorted.map((item) => item.SendTime)); + const fetchedSendTimes = new Set(sorted.map((item) => item.send_time)); setLiveMessagesState((prev) => { const filtered = prev.filter( - (liveMessage) => !fetchedSendTimes.has(liveMessage.SendTime), + (liveMessage) => !fetchedSendTimes.has(liveMessage.send_time), ); return filtered.length === prev.length ? prev : filtered; }); } - return decryptMessages(sorted); + return await Promise.all( + sorted.map(async (message) => { + try { + return { + ...message, + content: await decryptText(currentSharedSecret, message.content), + }; + } catch { + return message; + } + }), + ); }, - [currentChatSecret, decryptMessages, send, userIdValue], + [send, userIdValue, currentSharedSecret, decryptText], ); + const addLiveMessage = useCallback( + (message: RawMessage) => { + const localId = + globalThis.crypto?.randomUUID?.() ?? + `${Date.now()}-${Math.random().toString(36).slice(2)}`; + + if (!message.sent_by_self) { + moveUserIdToTop(userIdValue); + } + + setLiveMessagesState((prev) => [ + ...prev, + { + ...message, + localId, + failed: false, + }, + ]); + + return { + setFailed: (failed: boolean) => { + setLiveMessagesState((prev) => + prev.map((liveMessage) => + liveMessage.localId === localId + ? { ...liveMessage, failed } + : liveMessage, + ), + ); + }, + }; + }, + [userIdValue, moveUserIdToTop], + ); + + const clearLiveMessages = useCallback(() => { + setLiveMessagesState([]); + }, []); + + // Get live updates for message states useEffect(() => { - if (!currentChatSecret || !ownId || !userIdValue) return; - const queryKey = ["chat-messages", String(userIdValue), true] as const; - void getCachedMessages().then((messages) => { - if (messages.length === 0 || queryClient.getQueryData(queryKey)) return; - queryClient.setQueryData>(queryKey, { - pages: [messages], - pageParams: [0], - }); - }); - }, [currentChatSecret, getCachedMessages, ownId, userIdValue]); + return subscribePush((message) => { + if (message.type !== "message_state") { + return; + } + + const rawData = message.data as { + chat_partner_id: unknown; + send_time: unknown; + message_state: RawMessage["message_state"]; + }; + + const nextState = { + chat_partner_id: Number(rawData.chat_partner_id), + send_time: Number(rawData.send_time), + message_state: rawData.message_state, + }; + + if ( + !Number.isFinite(nextState.chat_partner_id) || + !Number.isFinite(nextState.send_time) + ) { + log( + 3, + "chat", + "yellow", + "Cancel message state update due to invalid data", + ); + return; + } + + if (nextState.chat_partner_id !== userIdValue) { + log( + 3, + "chat", + "yellow", + "Cancel message state update due to user ID mismatch", + { + expected: userIdValue, + received: nextState.chat_partner_id, + }, + ); + return; + } - const editMessage = useCallback( - (sendTime: number, edit: MessageEdit) => { setLiveMessagesState((prev) => { - const { next, updated } = updateMessagesBySendTime( + const { next, updated } = updateMessageStateBySendTime( prev, - sendTime, - edit, + nextState.send_time, + nextState.message_state, ); return updated ? next : prev; }); - const queryKey = [ - "chat-messages", - String(userIdValue), - currentChatSecret !== null, - ] as const; + const queryKey = ["chat-messages", String(userIdValue)] as const; queryClient.setQueryData>( queryKey, (current) => { @@ -661,7 +277,11 @@ export default function Provider({ children }: { children: ReactNode }) { let updated = false; const pages = current.pages.map((page) => { - const nextPage = updateMessagesBySendTime(page, sendTime, edit); + const nextPage = updateMessageStateBySendTime( + page, + nextState.send_time, + nextState.message_state, + ); if (nextPage.updated) { updated = true; @@ -680,336 +300,23 @@ export default function Provider({ children }: { children: ReactNode }) { }; }, ); - }, - [currentChatSecret, userIdValue], - ); - - const removeMessage = useCallback( - (sendTime: number) => { - setLiveMessagesState((prev) => - prev.filter((message) => message.SendTime !== sendTime), - ); - const queryKey = [ - "chat-messages", - String(userIdValue), - currentChatSecret !== null, - ] as const; - - queryClient.setQueryData>( - queryKey, - (current) => { - if (!current) { - return current; - } - - let updated = false; - - const pages = current.pages.map((page) => { - const nextPage = page.filter((message) => { - const keep = message.SendTime !== sendTime; - if (!keep) { - updated = true; - } - return keep; - }); - - return nextPage; - }); - - if (!updated) { - return current; - } - - return { - ...current, - pages, - }; - }, - ); - }, - [currentChatSecret, userIdValue], - ); - - const applyLiveReaction = useCallback( - ( - sendTime: number, - reaction: string, - senderId: number, - accepted: boolean, - ) => { - setLiveMessagesState((current) => - current.map((message) => - message.SendTime === sendTime - ? updateMessageReaction(message, reaction, senderId, accepted) - : message, - ), - ); - - const queryKey = [ - "chat-messages", - String(userIdValue), - currentChatSecret !== null, - ] as const; - queryClient.setQueryData>( - queryKey, - (current) => - current - ? { - ...current, - pages: current.pages.map((page) => - page.map((message) => - message.SendTime === sendTime - ? updateMessageReaction( - message, - reaction, - senderId, - accepted, - ) - : message, - ), - ), - } - : current, - ); - }, - [currentChatSecret, userIdValue], - ); - - const deleteMessage = useCallback( - async (sendTime: number) => { - try { - const response = await send("MessageDelete", { - ChatPartnerId: userIdValue, - SendTime: sendTime, - }); - - assertProtocolSuccess("MessageDelete", response); - removeMessage(sendTime); - } catch (err) { - log(1, "chat", "red", "Failed to delete message", err); - toast("error", "Failed to delete message", String(err)); - } - }, - [removeMessage, send, userIdValue], - ); - - const setReaction = useCallback( - async (sendTime: number, reaction: string, add: boolean) => { - let previousReactions: RawMessage["Reactions"]; - let foundMessage = false; - - const applyOptimisticUpdate = (message: EditableMessage) => { - const current = message.Reactions ?? []; - previousReactions = current; - foundMessage = true; - - return add - ? [...current, { Reaction: reaction, SenderId: ownId }] - : current.filter( - (item) => item.Reaction !== reaction || item.SenderId !== ownId, - ); - }; - - setLiveMessagesState((current) => - current.map((message) => - message.SendTime === sendTime - ? { ...message, Reactions: applyOptimisticUpdate(message) } - : message, - ), - ); - - const queryKey = [ - "chat-messages", - String(userIdValue), - currentChatSecret !== null, - ] as const; - queryClient.setQueryData>( - queryKey, - (current) => - current - ? { - ...current, - pages: current.pages.map((page) => - page.map((message) => - message.SendTime === sendTime - ? { - ...message, - Reactions: applyOptimisticUpdate(message), - } - : message, - ), - ), - } - : current, - ); - - try { - const response = await send( - add ? "MessageReactionAdd" : "MessageReactionRemove", - { - ChatPartnerId: userIdValue, - Reaction: reaction, - SendTime: sendTime, - }, - ); - assertProtocolSuccess( - add ? "MessageReactionAdd" : "MessageReactionRemove", - response, - ); - } catch (err) { - if (foundMessage) { - editMessage(sendTime, { Reactions: previousReactions }); - } - log(1, "chat", "red", "Failed to update reaction", err); - toast("error", "Failed to update reaction", String(err)); - } - }, - [currentChatSecret, editMessage, ownId, send, userIdValue], - ); - - const addReaction = useCallback( - (sendTime: number, reaction: string) => - setReaction(sendTime, reaction, true), - [setReaction], - ); - const removeReaction = useCallback( - (sendTime: number, reaction: string) => - setReaction(sendTime, reaction, false), - [setReaction], - ); - - const addLiveMessage = useCallback( - (message: RawMessage) => { - const localId = - globalThis.crypto?.randomUUID?.() ?? - `${Date.now()}-${Math.random().toString(36).slice(2)}`; - - if (message.SenderId !== ownId) { - moveUserIdToTop(userIdValue); - } - - setLiveMessagesState((prev) => [ - ...prev, - { - ...message, - localId, - failed: false, - }, - ]); - - return { - setFailed: (failed: boolean) => { - editMessage(message.SendTime, { failed }); - }, - }; - }, - [editMessage, userIdValue, moveUserIdToTop, ownId], - ); - - const clearLiveMessages = useCallback(() => { - setLiveMessagesState([]); - }, []); - - // Get live updates for message states - useEffect(() => { - const unsubscribeEdit = subscribe("MessageEditLive", ({ data }) => { - if (!currentChatSecret) return; - if (data.ChatPartnerId !== userIdValue) { - log( - 3, - "chat", - "yellow", - "Cancel message edit update due to user ID mismatch", - { - expected: userIdValue, - received: data.ChatPartnerId, - }, - ); - return; - } - - void decryptChatText(currentChatSecret, data.Content) - .then((content) => { - editMessage(data.SendTime, { Content: content, Edited: true }); - }) - .catch((err) => { - log(1, "chat", "red", "Failed to decrypt message edit", err, { - SendTime: data.SendTime, - }); - }); }); - const unsubscribeReaction = subscribe("MessageReactionLive", ({ data }) => { - if (data.ChatPartnerId !== userIdValue) return; - applyLiveReaction( - data.SendTime, - data.Reaction, - data.SenderId, - data.Accepted, - ); - }); - const unsubscribeDelete = subscribe("MessageDeleteLive", ({ data }) => { - if (data.ChatPartnerId !== userIdValue) return; - removeMessage(data.SendTime); - }); - const unsubscribeState = subscribe("MessageState", ({ data }) => { - if (data.ChatPartnerId !== userIdValue) { - log( - 3, - "chat", - "yellow", - "Cancel message state update due to user ID mismatch", - { - expected: userIdValue, - received: data.ChatPartnerId, - }, - ); - return; - } - editMessage(data.SendTime, { - MessageState: data.MessageState, - }); - }); - return () => { - unsubscribeEdit(); - unsubscribeReaction(); - unsubscribeDelete(); - unsubscribeState(); - }; - }, [ - currentChatSecret, - applyLiveReaction, - editMessage, - removeMessage, - subscribe, - userIdValue, - ]); + }, [subscribePush, userIdValue]); return ( liveMessagesState, addLiveMessage, - editMessage, - deleteMessage, - addReaction, - removeReaction, clearLiveMessages, - chatSecret: currentChatSecret, - ownId, + sharedSecret: currentSharedSecret, userId: userIdValue, inputBoxRef, - error, - errorDescription, - composerValue, - setComposerValue, - replyTo, - setReplyTo, }} > - {children} + {props.children} ); @@ -1017,28 +324,21 @@ export default function Provider({ children }: { children: ReactNode }) { type contextType = { getMessages: (amount: number, offset: number) => Promise; - getChatSecret: (userId: number) => Promise; liveMessages: () => LiveMessage[]; addLiveMessage: (message: RawMessage) => { setFailed: (failed: boolean) => void; }; - editMessage: (sendTime: number, edit: MessageEdit) => void; - deleteMessage: (sendTime: number) => void; - addReaction: (sendTime: number, reaction: string) => Promise; - removeReaction: (sendTime: number, reaction: string) => Promise; clearLiveMessages: () => void; - chatSecret: Uint8Array | null; - ownId: number; + sharedSecret: string; userId: number; inputBoxRef: React.RefObject; - error: string; - errorDescription: string; - composerValue: string; - setComposerValue: (value: string) => void; - replyTo: number | undefined; - setReplyTo: (value: number | undefined) => void; }; +/** + * Executes useChat. + * @param none This function has no parameters. + * @returns contextType. + */ export function useChat(): contextType { const ctx = useContext(context); if (!ctx) { @@ -1046,66 +346,3 @@ export function useChat(): contextType { } return ctx; } - -export function useReplyMessage() { - const { send } = useMTP(); - const { load } = useStorage(); - const { replyTo, setReplyTo, getChatSecret, userId } = useChat(); - const [ownId, setOwnId] = useState(0); - const [replyMessage, setReplyMessage] = useState(); - - const handleReplyError = useCallback( - (err: unknown) => { - log(1, "chat", "red", "Failed to get reply message", err); - toast("error", "Failed to get reply message", String(err)); - setReplyTo(undefined); - setReplyMessage(undefined); - }, - [setReplyTo], - ); - - useEffect(() => { - void load("user_id").then((value) => { - setOwnId(Number(value)); - }); - }, [load]); - - useEffect(() => { - if (!replyTo || !ownId) { - setReplyMessage(undefined); - return; - } - - let active = true; - setReplyMessage(undefined); - - void fetchReplyMessage({ - replyTo, - ownId, - chatUserId: userId, - send, - getChatSecret, - }) - .then((message) => { - if (active) { - setReplyMessage(message); - } - }) - .catch((err) => { - if (active) { - handleReplyError(err); - } - }); - - return () => { - active = false; - }; - }, [replyTo, ownId, userId, send, getChatSecret, handleReplyError]); - - return { - ownId, - replyTo, - replyMessage, - replyUserId: replyMessage?.SenderId, - }; -} diff --git a/packages/chat/src/hotkeys.ts b/packages/chat/src/hotkeys.ts deleted file mode 100644 index 039d1fb..0000000 --- a/packages/chat/src/hotkeys.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { defineHotkey } from "@tensamin/hotkeys"; - -export const editLastMessageHotkey = defineHotkey({ - id: "chat.edit-last-message", - name: "Edit last message", - description: "Edit your most recent message when the composer is empty.", - category: "Chat", - defaultBinding: "ArrowUp", -}); - -export const cancelMessageEditHotkey = defineHotkey({ - id: "chat.cancel-message-edit", - name: "Cancel message edit", - description: "Close the message editor without saving changes.", - category: "Chat", - defaultBinding: "Escape", -}); diff --git a/packages/chat/src/screen.tsx b/packages/chat/src/screen.tsx index 685b140..a23760d 100644 --- a/packages/chat/src/screen.tsx +++ b/packages/chat/src/screen.tsx @@ -1,129 +1,19 @@ -import { - useCallback, - useEffect, - useLayoutEffect, - useMemo, - useRef, - useState, -} from "react"; +import * as React from "react"; import { useInfiniteQuery } from "@tanstack/react-query"; import { useVirtualizer } from "@tanstack/react-virtual"; +import { useChat } from "./context"; import InputComponent from "./components/input"; import Message from "./components/message"; -import { useChat } from "./context"; -import { - PAGE_SIZE, - FALLBACK_MESSAGE_HEIGHT, - MESSAGES_PER_VIRTUAL_ROW, - type LiveMessage, - type RawMessage, -} from "./values"; -import Wrapper from "@tensamin/user/wrapper"; -function shouldFetchPreviousPage({ - entry, - hasNextPage, - isFetchingNextPage, - userScrolledUp, -}: { - entry: IntersectionObserverEntry | undefined; - hasNextPage: boolean; - isFetchingNextPage: boolean; - userScrolledUp: boolean; -}) { - return [ - entry?.isIntersecting === true, - userScrolledUp, - hasNextPage, - !isFetchingNextPage, - ].every(Boolean); +import { PAGE_SIZE } from "./values"; +import { useIsMobile } from "@tensamin/ui"; + +function getDistanceFromBottom(element: HTMLDivElement) { + return element.scrollHeight - (element.scrollTop + element.clientHeight); } -function getMessageRenderKey(message: RawMessage | LiveMessage) { - return "localId" in message ? message.localId : String(message.SendTime); -} - -function isSameDay(first: number | Date, second: number | Date) { - const firstDate = new Date(first); - const secondDate = new Date(second); - - return ( - firstDate.getFullYear() === secondDate.getFullYear() && - firstDate.getMonth() === secondDate.getMonth() && - firstDate.getDate() === secondDate.getDate() - ); -} - -function formatMessageDate(sendTime: number) { - const date = new Date(sendTime); - const today = new Date(); - const yesterday = new Date(today); - yesterday.setDate(yesterday.getDate() - 1); - - if (isSameDay(date, today)) { - return "Today"; - } - - if (isSameDay(date, yesterday)) { - return "Yesterday"; - } - - const day = String(date.getDate()).padStart(2, "0"); - const month = date.toLocaleString([], { month: "long" }); - return `${day} ${month} ${date.getFullYear()}`; -} - -function DateSeparator({ label }: { label: string }) { - const [visible, setVisible] = useState(false); - - useEffect(() => { - const timeout = window.setTimeout(() => setVisible(true), 100); - return () => window.clearTimeout(timeout); - }, []); - - return ( -
-
- - {label} - -
-
- ); -} - -function buildMessageChunks( - messages: Array, - keyPrefix: string, - startOffset = 0, -) { - const chunks: { - key: string; - messages: Array; - startIndex: number; - }[] = []; - - for (let end = messages.length; end > 0; end -= MESSAGES_PER_VIRTUAL_ROW) { - const start = Math.max(0, end - MESSAGES_PER_VIRTUAL_ROW); - const chunkMessages = messages.slice(start, end); - const firstMessage = chunkMessages[0]; - - if (firstMessage) { - chunks.push({ - key: `${keyPrefix}-${getMessageRenderKey(firstMessage)}`, - messages: chunkMessages, - startIndex: startOffset + start, - }); - } - } - - return chunks; -} +const FALLBACK_MESSAGE_HEIGHT = 56; /** * Renders the chat screen with virtualized history and live message updates. @@ -135,54 +25,40 @@ export default function Screen() { liveMessages, clearLiveMessages, userId, - chatSecret, - ownId, + sharedSecret, inputBoxRef, - error, - errorDescription, - composerValue, - setComposerValue, } = useChat(); - const scrollRef = useRef(null); - const composerRef = useRef(null); - const topSentinelRef = useRef(null); - const didInitialScrollRef = useRef(false); - const userScrolledUpRef = useRef(false); - const isAtBottomRef = useRef(true); - const smoothScrollFrameRef = useRef(null); - const smoothScrollTargetRef = useRef(0); - const hasNextPageRef = useRef(false); - const isFetchingNextPageRef = useRef(false); - const fetchNextPageRef = useRef<(() => void) | null>(null); - const [lastLiveMessageCount, setLastLiveMessageCount] = useState(0); - const [didInitialScroll, setDidInitialScroll] = useState(false); - const [viewportHeight, setViewportHeight] = useState(0); - const [composerHeight, setComposerHeight] = useState(0); - const [editingMessageId, setEditingMessageId] = useState(null); - const previousEditingMessageIdRef = useRef(null); + const scrollRef = React.useRef(null); + const isMobile = useIsMobile(); + const stickyBottomThreshold = isMobile ? 220 : 140; + + const [hasScrolledToBottomInitially, setHasScrolledToBottomInitially] = + React.useState(false); + const [lastLiveMessageCount, setLastLiveMessageCount] = React.useState(0); + const [prependAnchor, setPrependAnchor] = React.useState<{ + totalSize: number; + scrollTop: number; + } | null>(null); + const [scrollWidth, setScrollWidth] = React.useState(0); + const [scrollHeight, setScrollHeight] = React.useState(0); + const [inputHeight, setInputHeight] = React.useState(0); + const [measuredHeights, setMeasuredHeights] = React.useState< + Record + >({}); + const measurementRefs = React.useRef(new Map()); + const shouldRestoreBottomOnResizeRef = React.useRef(false); + const isAtBottomRef = React.useRef(true); + const shouldRestoreBottomOnFocusResizeRef = React.useRef(false); const hasValidChatUser = Number.isSafeInteger(userId) && userId > 0; - const hasChatSecret = chatSecret !== null; - - useEffect(() => { - const previousEditingMessageId = previousEditingMessageIdRef.current; - previousEditingMessageIdRef.current = editingMessageId; - if (previousEditingMessageId === null || editingMessageId !== null) return; - - const frame = requestAnimationFrame(() => { - inputBoxRef.current - ?.querySelector(".cm-content") - ?.focus({ preventScroll: true }); - }); - return () => cancelAnimationFrame(frame); - }, [editingMessageId, inputBoxRef]); + const hasSharedSecret = sharedSecret.length > 0; const messagesQuery = useInfiniteQuery({ - queryKey: ["chat-messages", String(userId), hasChatSecret], + queryKey: ["chat-messages", String(userId), hasSharedSecret], initialPageParam: 0, queryFn: ({ pageParam }) => getMessages(PAGE_SIZE, Number(pageParam)), - enabled: hasValidChatUser && hasChatSecret, + enabled: hasValidChatUser && hasSharedSecret, getNextPageParam: (lastPage, allPages) => { if (lastPage.length < PAGE_SIZE) { return undefined; @@ -191,142 +67,111 @@ export default function Screen() { return allPages.length * PAGE_SIZE; }, }); - const { - fetchNextPage: fetchMessagesNextPage, - hasNextPage: hasMessagesNextPage, - isFetchingNextPage: isFetchingMessagesNextPage, - } = messagesQuery; - useEffect(() => { - hasNextPageRef.current = hasMessagesNextPage; - isFetchingNextPageRef.current = isFetchingMessagesNextPage; - fetchNextPageRef.current = () => { - void fetchMessagesNextPage(); - }; - }, [fetchMessagesNextPage, hasMessagesNextPage, isFetchingMessagesNextPage]); - - useEffect(() => { - clearLiveMessages(); - didInitialScrollRef.current = false; - userScrolledUpRef.current = false; - isAtBottomRef.current = true; - setDidInitialScroll(false); - setLastLiveMessageCount(0); - setEditingMessageId(null); - }, [clearLiveMessages, userId]); - - const historicalMessages = useMemo(() => { + const historicalMessages = React.useMemo(() => { const pages = messagesQuery.data?.pages ?? []; - const seenSendTimes = new Set(); - const dedupedMessages: RawMessage[] = []; - - for (const message of [...pages].reverse().flat()) { - if (seenSendTimes.has(message.SendTime)) { - continue; - } - - seenSendTimes.add(message.SendTime); - dedupedMessages.push(message); - } - - return dedupedMessages; + return [...pages].reverse().flat(); }, [messagesQuery.data]); + React.useEffect(() => { + clearLiveMessages(); + setHasScrolledToBottomInitially(false); + setLastLiveMessageCount(0); + setPrependAnchor(null); + }, [userId, clearLiveMessages]); + const liveMessagesSnapshot = liveMessages(); - const liveWithoutDuplicates = useMemo(() => { + const messages = React.useMemo(() => { + if (historicalMessages.length === 0) { + return liveMessagesSnapshot; + } + const historicalSendTimes = new Set( - historicalMessages.map((message) => message.SendTime), + historicalMessages.map((message) => message.send_time), + ); + const liveWithoutDuplicates = liveMessagesSnapshot.filter( + (message) => !historicalSendTimes.has(message.send_time), ); - return liveMessagesSnapshot.filter( - (message) => !historicalSendTimes.has(message.SendTime), - ); - }, [historicalMessages, liveMessagesSnapshot]); - - const messages = useMemo(() => { return [...historicalMessages, ...liveWithoutDuplicates]; - }, [historicalMessages, liveWithoutDuplicates]); + }, [historicalMessages, liveMessagesSnapshot]); + const shouldShowConversationStart = + !!messagesQuery.data && !messagesQuery.hasNextPage; + const virtualRowCount = + messages.length + (shouldShowConversationStart ? 1 : 0); - const historicalMessageChunks = useMemo(() => { - return buildMessageChunks(historicalMessages, "history"); - }, [historicalMessages]); + const messagesRef = React.useRef(messages); - const liveMessageChunks = useMemo(() => { - return buildMessageChunks( - liveWithoutDuplicates, - "live", - historicalMessages.length, - ); - }, [historicalMessages.length, liveWithoutDuplicates]); + React.useEffect(() => { + messagesRef.current = messages; + }, [messages]); - const messageChunks = useMemo(() => { - return [...liveMessageChunks, ...historicalMessageChunks]; - }, [historicalMessageChunks, liveMessageChunks]); + const getItemKey = React.useCallback( + (index: number) => { + if (shouldShowConversationStart && index === 0) { + return "conversation-start"; + } - const virtualRowCount = messageChunks.length; - - const getItemKey = useCallback( - (index: number) => messageChunks[index]?.key ?? index, - [messageChunks], + const messageIndex = shouldShowConversationStart ? index - 1 : index; + return messagesRef.current[messageIndex]?.send_time ?? index; + }, + [shouldShowConversationStart], ); - const estimateSize = useCallback(() => FALLBACK_MESSAGE_HEIGHT, []); + const setMeasurementRef = React.useCallback( + (sendTime: number, element: HTMLDivElement | null) => { + if (!element) { + measurementRefs.current.delete(sendTime); + return; + } + + measurementRefs.current.set(sendTime, element); + }, + [], + ); + + const estimateMessageSize = React.useCallback( + (index: number) => { + if (shouldShowConversationStart && index === 0) { + return FALLBACK_MESSAGE_HEIGHT; + } + + const messageIndex = shouldShowConversationStart ? index - 1 : index; + const sendTime = messages[messageIndex]?.send_time; + + if (sendTime === undefined) { + return FALLBACK_MESSAGE_HEIGHT; + } + + return measuredHeights[sendTime] ?? FALLBACK_MESSAGE_HEIGHT; + }, + [measuredHeights, messages, shouldShowConversationStart], + ); // eslint-disable-next-line react-hooks/incompatible-library const virtualizer = useVirtualizer({ count: virtualRowCount, getScrollElement: () => scrollRef.current, getItemKey, - estimateSize, - overscan: 2, - paddingStart: composerHeight + 20, + estimateSize: estimateMessageSize, + overscan: isMobile ? 10 : 6, }); - const totalSize = virtualizer.getTotalSize(); - const contentHeight = Math.max(totalSize, viewportHeight); - const verticalOffset = Math.max(0, viewportHeight - totalSize); - const editLastMessage = useCallback(() => { - if (editingMessageId !== null) return; - const message = [...messages] - .reverse() - .find( - (candidate) => - candidate.SenderId === ownId && - candidate.Content.length > 0 && - candidate.MessageState !== "awaiting" && - !("failed" in candidate && candidate.failed) && - !("decryptionFailed" in candidate && candidate.decryptionFailed), - ); - if (!message) return; - - const chunkIndex = messageChunks.findIndex((chunk) => - chunk.messages.some( - (candidate) => candidate.SendTime === message.SendTime, - ), - ); - const chunkIsRendered = virtualizer - .getVirtualItems() - .some(({ index }) => index === chunkIndex); - if (chunkIndex >= 0 && !chunkIsRendered) { - virtualizer.scrollToIndex(chunkIndex, { align: "center" }); - } - setEditingMessageId(message.SendTime); - }, [editingMessageId, messageChunks, messages, ownId, virtualizer]); - - useLayoutEffect(() => { + React.useLayoutEffect(() => { const element = scrollRef.current; if (!element || typeof ResizeObserver === "undefined") { return; } - const updateViewportHeight = () => { - setViewportHeight(element.clientHeight); + const updateDimensions = () => { + setScrollWidth(element.clientWidth); + setScrollHeight(element.clientHeight); }; - updateViewportHeight(); + updateDimensions(); - const observer = new ResizeObserver(updateViewportHeight); + const observer = new ResizeObserver(updateDimensions); observer.observe(element); return () => { @@ -334,310 +179,348 @@ export default function Screen() { }; }, []); - useLayoutEffect(() => { - const element = composerRef.current; + React.useLayoutEffect(() => { + const frame = requestAnimationFrame(() => { + virtualizer.measure(); + }); + + return () => { + cancelAnimationFrame(frame); + }; + }, [inputHeight, measuredHeights, scrollWidth, virtualizer]); + + React.useLayoutEffect(() => { + if (typeof ResizeObserver === "undefined") { + return; + } + + const measureHeights = () => { + setMeasuredHeights((prev) => { + let changed = false; + const next: Record = {}; + + for (const message of messages) { + const element = measurementRefs.current.get(message.send_time); + const measuredHeight = element?.getBoundingClientRect().height; + const nextHeight = + typeof measuredHeight === "number" && + Number.isFinite(measuredHeight) + ? Math.ceil(measuredHeight) + : (prev[message.send_time] ?? FALLBACK_MESSAGE_HEIGHT); + + next[message.send_time] = nextHeight; + + if (prev[message.send_time] !== nextHeight) { + changed = true; + } + } + + if (!changed && Object.keys(prev).length === messages.length) { + return prev; + } + + return next; + }); + }; + + measureHeights(); + + const observer = new ResizeObserver(() => { + measureHeights(); + }); + + for (const message of messages) { + const element = measurementRefs.current.get(message.send_time); + if (element) { + observer.observe(element); + } + } + + return () => { + observer.disconnect(); + }; + }, [messages, scrollWidth]); + + React.useEffect(() => { + if (typeof window === "undefined") { + return; + } + + const restoreBottomAfterResize = () => { + shouldRestoreBottomOnResizeRef.current = + isAtBottomRef.current || shouldRestoreBottomOnFocusResizeRef.current; + + requestAnimationFrame(() => { + virtualizer.measure(); + + requestAnimationFrame(() => { + if (!scrollRef.current || !shouldRestoreBottomOnResizeRef.current) { + return; + } + + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + isAtBottomRef.current = true; + shouldRestoreBottomOnFocusResizeRef.current = false; + shouldRestoreBottomOnResizeRef.current = false; + }); + }); + }; + + window.addEventListener("resize", restoreBottomAfterResize); + window.visualViewport?.addEventListener("resize", restoreBottomAfterResize); + + return () => { + window.removeEventListener("resize", restoreBottomAfterResize); + window.visualViewport?.removeEventListener( + "resize", + restoreBottomAfterResize, + ); + }; + }, [virtualizer]); + + React.useLayoutEffect(() => { + const element = inputBoxRef.current; if (!element || typeof ResizeObserver === "undefined") { return; } - const updateComposerHeight = () => { - setComposerHeight(element.getBoundingClientRect().height); + const updateHeight = () => { + setInputHeight(element.getBoundingClientRect().height); }; - updateComposerHeight(); + updateHeight(); - const observer = new ResizeObserver(updateComposerHeight); + const observer = new ResizeObserver(updateHeight); observer.observe(element); return () => { observer.disconnect(); }; - }, []); + }, [inputBoxRef]); - useLayoutEffect(() => { - if (didInitialScrollRef.current || virtualRowCount === 0) { + React.useEffect(() => { + const element = inputBoxRef.current; + if (!element) { return; } - requestAnimationFrame(() => { - if (scrollRef.current) { - scrollRef.current.scrollTop = 0; + const handleFocusIn = () => { + if (!scrollRef.current) { + return; } - requestAnimationFrame(() => { - didInitialScrollRef.current = true; - setDidInitialScroll(true); - }); - }); - }, [virtualizer, virtualRowCount]); + shouldRestoreBottomOnFocusResizeRef.current = + getDistanceFromBottom(scrollRef.current) <= stickyBottomThreshold; + }; - useLayoutEffect(() => { + const handleFocusOut = () => { + shouldRestoreBottomOnFocusResizeRef.current = false; + }; + + element.addEventListener("focusin", handleFocusIn); + element.addEventListener("focusout", handleFocusOut); + + return () => { + element.removeEventListener("focusin", handleFocusIn); + element.removeEventListener("focusout", handleFocusOut); + }; + }, [inputBoxRef, stickyBottomThreshold]); + + /** + * Loads the next page when the scroll container reaches the top. + * @returns Promise that resolves once pagination handling completes. + */ + const onScroll = React.useCallback(async () => { + if (!scrollRef.current) { + return; + } + + if (scrollRef.current.scrollTop > 96) { + return; + } + + if (messagesQuery.isFetchingNextPage || !messagesQuery.hasNextPage) { + return; + } + + setPrependAnchor({ + totalSize: virtualizer.getTotalSize(), + scrollTop: scrollRef.current.scrollTop, + }); + + await messagesQuery.fetchNextPage(); + }, [messagesQuery, virtualizer]); + + React.useLayoutEffect(() => { + if ( + !scrollRef.current || + hasScrolledToBottomInitially || + virtualRowCount === 0 + ) { + return; + } + + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + isAtBottomRef.current = true; + setHasScrolledToBottomInitially(true); + }, [hasScrolledToBottomInitially, virtualRowCount]); + + React.useLayoutEffect(() => { const count = liveMessagesSnapshot.length; - if (count > lastLiveMessageCount && isAtBottomRef.current) { - requestAnimationFrame(() => { - if (scrollRef.current) { - scrollRef.current.scrollTop = 0; - } - }); + if (!scrollRef.current) { + return; + } + + if (count > lastLiveMessageCount) { + const shouldStickToBottom = + getDistanceFromBottom(scrollRef.current) <= stickyBottomThreshold; + + if (shouldStickToBottom) { + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + isAtBottomRef.current = true; + } } setLastLiveMessageCount(count); }, [ lastLiveMessageCount, liveMessagesSnapshot.length, - virtualRowCount, - virtualizer, + stickyBottomThreshold, ]); - useEffect(() => { + React.useLayoutEffect(() => { if ( - viewportHeight === 0 || - totalSize > viewportHeight || - messagesQuery.isFetchingNextPage || - !messagesQuery.hasNextPage + !scrollRef.current || + !prependAnchor || + messagesQuery.isFetchingNextPage ) { return; } - void messagesQuery.fetchNextPage(); - }, [didInitialScroll, messagesQuery, totalSize, viewportHeight]); + const delta = virtualizer.getTotalSize() - prependAnchor.totalSize; + scrollRef.current.scrollTop = prependAnchor.scrollTop + delta; + setPrependAnchor(null); + }, [messagesQuery.isFetchingNextPage, prependAnchor, virtualizer]); - useEffect(() => { - const root = scrollRef.current; - const sentinel = topSentinelRef.current; - - if (!root || !sentinel || !didInitialScroll) { - return; + /** + * Triggers asynchronous scroll pagination without returning a promise to JSX. + * @returns Void. + */ + const handleContainerScroll = React.useCallback((): void => { + if (scrollRef.current) { + isAtBottomRef.current = + getDistanceFromBottom(scrollRef.current) <= stickyBottomThreshold; } - const observer = new IntersectionObserver( - (entries) => { - const entry = entries[0]; - if ( - shouldFetchPreviousPage({ - entry, - hasNextPage: messagesQuery.hasNextPage, - isFetchingNextPage: messagesQuery.isFetchingNextPage, - userScrolledUp: userScrolledUpRef.current, - }) - ) { - void messagesQuery.fetchNextPage(); - } - }, - { root, rootMargin: "240px 0px 0px 0px" }, - ); + void onScroll(); + }, [onScroll, stickyBottomThreshold]); - observer.observe(sentinel); - - return () => { - observer.disconnect(); - }; - }, [didInitialScroll, messagesQuery, virtualRowCount]); - - const handleContainerScroll = useCallback(() => { - if (!scrollRef.current) { - return; - } - - isAtBottomRef.current = scrollRef.current.scrollTop <= 140; - - if (didInitialScrollRef.current && scrollRef.current.scrollTop > 140) { - userScrolledUpRef.current = true; - } - }, []); - - useEffect(() => { - const element = scrollRef.current; - if (!element) { - return; - } - - smoothScrollTargetRef.current = element.scrollTop; - - const animateScroll = () => { - const distance = smoothScrollTargetRef.current - element.scrollTop; - if (Math.abs(distance) < 0.5) { - element.scrollTop = smoothScrollTargetRef.current; - smoothScrollFrameRef.current = null; - handleContainerScroll(); - return; - } - - element.scrollTop += distance * 0.35; - handleContainerScroll(); - smoothScrollFrameRef.current = requestAnimationFrame(animateScroll); - }; - - const fetchNextPageNearTop = (scrollTop: number, maxScrollTop: number) => { - if (didInitialScrollRef.current && scrollTop > 140) { - userScrolledUpRef.current = true; - } - - if ( - scrollTop >= maxScrollTop - 240 && - userScrolledUpRef.current && - hasNextPageRef.current && - !isFetchingNextPageRef.current - ) { - fetchNextPageRef.current?.(); - } - }; - - const handleWheel = (event: WheelEvent) => { - event.preventDefault(); - event.stopPropagation(); - - if (smoothScrollFrameRef.current === null) { - smoothScrollTargetRef.current = element.scrollTop; - } - - const maxScrollTop = Math.max( - 0, - element.scrollHeight - element.clientHeight, - ); - smoothScrollTargetRef.current = Math.min( - Math.max(0, smoothScrollTargetRef.current - event.deltaY), - maxScrollTop, - ); - fetchNextPageNearTop(smoothScrollTargetRef.current, maxScrollTop); - - if (smoothScrollFrameRef.current === null) { - smoothScrollFrameRef.current = requestAnimationFrame(animateScroll); - } - }; - - element.addEventListener("wheel", handleWheel, { passive: false }); - - return () => { - element.removeEventListener("wheel", handleWheel); - if (smoothScrollFrameRef.current !== null) { - cancelAnimationFrame(smoothScrollFrameRef.current); - smoothScrollFrameRef.current = null; - } - }; - }, [handleContainerScroll]); + const [value, setValue] = React.useState(""); + const totalSize = virtualizer.getTotalSize(); + const verticalOffset = Math.max(0, scrollHeight - totalSize); + const contentHeight = Math.max(totalSize, scrollHeight); + // Render if (!hasValidChatUser) { return ( -
-

Invalid User

+
+ Invalid user
); } return (
- {error !== "" && errorDescription !== "" ? ( -
-

{error}

-

{errorDescription}

-
- ) : ( - <> -
-
-
- {virtualizer.getVirtualItems().map((virtualRow) => { - const chunkIndex = virtualRow.index; - const chunk = messageChunks[chunkIndex]; - if (!chunk) { - return null; - } - - return ( -
-
- {chunk.messages.map((message, chunkMessageIndex) => { - const messageIndex = - chunk.startIndex + chunkMessageIndex; - const lastMessage = messages[messageIndex - 1]; - const startsNewDay = - !lastMessage || - !isSameDay(lastMessage.SendTime, message.SendTime); - const dateLabel = startsNewDay - ? formatMessageDate(message.SendTime) - : null; - const isGrouped = - lastMessage && - !startsNewDay && - !message.ReplyId && - lastMessage.SenderId === message.SenderId && - Math.round(lastMessage.SendTime / 10000) === - Math.round(message.SendTime / 10000); - - return ( - ( - <> - {dateLabel && ( - - )} - - setEditingMessageId( - editing ? message.SendTime : null, - ) - } - user={user} - /> - - )} - /> - ); - })} +
+
+ {virtualizer.getVirtualItems().map((virtualRow) => { + if (shouldShowConversationStart && virtualRow.index === 0) { + return ( +
+
+
+ This is the start of your conversation with{" "}
- ); - })} -
+
+ ); + } + + const messageIndex = shouldShowConversationStart + ? virtualRow.index - 1 + : virtualRow.index; + const message = messages[messageIndex]; + if (!message) { + return null; + } + + return ( +
+ +
+ ); + })} +
+
+
+ +
+
); } diff --git a/packages/chat/src/values.ts b/packages/chat/src/values.ts index c269cc4..880091c 100644 --- a/packages/chat/src/values.ts +++ b/packages/chat/src/values.ts @@ -1,17 +1,13 @@ import { z } from "zod"; -import { mtp } from "@tensamin/shared/data"; +import { ttp } from "@tensamin/shared/data"; -export type RawMessages = z.infer["Messages"]; +export type RawMessages = z.infer["messages"]; -export type RawMessage = RawMessages[number] & { - decryptionFailed?: boolean; -}; +export type RawMessage = RawMessages[number]; export type LiveMessage = RawMessage & { failed?: boolean; localId: string; }; -export const PAGE_SIZE = 30; -export const FALLBACK_MESSAGE_HEIGHT = 28; -export const MESSAGES_PER_VIRTUAL_ROW = 30; +export const PAGE_SIZE = 50; diff --git a/packages/chat/todo.md b/packages/chat/todo.md deleted file mode 100644 index 9ecfbeb..0000000 --- a/packages/chat/todo.md +++ /dev/null @@ -1,10 +0,0 @@ -- Implement context menu features - - Forward - - Pin Message -- Placeholder image if media fails to load -- Signature verifications via ed25519 key -- Confirmation when exiting with text in the input box. -- Drop any unique reactions above 10 -- Reply jumping -- Add emoji picker -- Improve arrow-up hotkey diff --git a/packages/crypto/package.json b/packages/crypto/package.json index 2b40fdf..705faa0 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -5,18 +5,18 @@ "type": "module", "exports": { "./context": "./src/context.tsx", - "./chatSecret": "./src/chatSecret.ts", - "./callSecret": "./src/callSecret.ts" + "./worker": "./src/worker.ts" }, "scripts": { - "format": "pnpm exec prettier --write .", + "format": "bunx prettier --write .", "lint": "eslint src", - "test": "vitest run", - "build": "tsc -p tsconfig.json --noEmit" + "test": "bun test", + "build": "bun run test && tsc -p tsconfig.json --noEmit" }, "dependencies": { - "mtp": "*", - "react": "^19.2.8", - "react-dom": "^19.2.8" + "@noble/curves": "^2.0.1", + "comlink": "^4.4.2", + "react": "^19.2.0", + "react-dom": "^19.2.0" } } diff --git a/packages/crypto/src/bun-test.d.ts b/packages/crypto/src/bun-test.d.ts new file mode 100644 index 0000000..4ac4faa --- /dev/null +++ b/packages/crypto/src/bun-test.d.ts @@ -0,0 +1,11 @@ +declare module "bun:test" { + export const describe: (...args: unknown[]) => unknown; + export const test: (...args: unknown[]) => unknown; + export const it: (...args: unknown[]) => unknown; + export const expect: (value: unknown) => { + toBe: (expected: unknown) => void; + toEqual: (expected: unknown) => void; + toContain: (expected: unknown) => void; + toThrow: (expected?: unknown) => void; + }; +} diff --git a/packages/crypto/src/callSecret.ts b/packages/crypto/src/callSecret.ts deleted file mode 100644 index 556d131..0000000 --- a/packages/crypto/src/callSecret.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { crypto } from "mtp"; - -const textEncoder = new TextEncoder(); -const textDecoder = new TextDecoder(); - -export const CALL_SECRET_WRAPPING_SCHEME = - "mtp-call-secret-kem-chacha20poly1305-hkdf-sha256-v1"; - -const CALL_SECRET_SALT = textEncoder.encode("tensamin-call-secret-v1"); - -export function deriveCallSecretId(callId: string): string { - return `call:${callId}:main`; -} - -export function ownKemPublicKeyFromKeyring(keyring: string): Uint8Array { - return crypto.keyringToKeys({ value: keyring, encoding: "base64" }) - .kemPublicKey; -} - -export function kemPublicKeyFromPublicKeyBundle(publicKey: string): Uint8Array { - return crypto.publicKeyBundleToKeys({ value: publicKey, encoding: "base64" }) - .kemPublicKey; -} - -export async function wrapCallSecret(args: { - callSecret: string; - recipientKemPublicKey: Uint8Array; - callId: string; - secretId: string; - version: number; -}): Promise<{ - encryptedSecret: Uint8Array; - kemCiphertext: Uint8Array; - wrappingScheme: string; -}> { - const enc = crypto.encapsulate(args.recipientKemPublicKey); - try { - const wrappingKey = deriveWrappingKey({ - sharedSecret: enc.shared_secret, - callId: args.callId, - secretId: args.secretId, - version: args.version, - }); - - try { - return { - encryptedSecret: await crypto.encrypt( - wrappingKey, - textEncoder.encode(args.callSecret), - ), - kemCiphertext: enc.ciphertext, - wrappingScheme: CALL_SECRET_WRAPPING_SCHEME, - }; - } finally { - wrappingKey.fill(0); - } - } finally { - enc.shared_secret.fill(0); - } -} - -export async function unwrapCallSecret(args: { - encryptedSecret: Uint8Array; - kemCiphertext: Uint8Array; - keyring: string; - callId: string; - secretId: string; - version: number; - wrappingScheme: string; -}): Promise { - if (args.wrappingScheme !== CALL_SECRET_WRAPPING_SCHEME) { - throw new Error( - `Unsupported call secret wrapping scheme: ${args.wrappingScheme}`, - ); - } - - const ownKeys = crypto.keyringToKeys({ - value: args.keyring, - encoding: "base64", - }); - const sharedSecret = crypto.decapsulate( - ownKeys.kemSecretKey, - args.kemCiphertext, - ); - - try { - const wrappingKey = deriveWrappingKey({ - sharedSecret, - callId: args.callId, - secretId: args.secretId, - version: args.version, - }); - - try { - return textDecoder.decode( - await crypto.decrypt(wrappingKey, args.encryptedSecret), - ); - } finally { - wrappingKey.fill(0); - } - } finally { - sharedSecret.fill(0); - } -} - -function deriveWrappingKey(args: { - sharedSecret: Uint8Array; - callId: string; - secretId: string; - version: number; -}): Uint8Array { - return crypto.deriveEncryptionKey( - args.sharedSecret, - CALL_SECRET_SALT, - textEncoder.encode(`${args.callId}:${args.secretId}:${args.version}`), - ); -} diff --git a/packages/crypto/src/chatSecret.ts b/packages/crypto/src/chatSecret.ts deleted file mode 100644 index cbac59a..0000000 --- a/packages/crypto/src/chatSecret.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { base64ToBytes, bytesToBase64, crypto } from "mtp"; - -const textEncoder = new TextEncoder(); -const textDecoder = new TextDecoder(); - -export const CHAT_SECRET_WRAPPING_SCHEME = - "mtp-chat-secret-kem-chacha20poly1305-hkdf-sha256-v1"; - -const CHAT_SECRET_SALT = textEncoder.encode("tensamin-chat-secret-v1"); -const CHAT_MESSAGE_SALT = textEncoder.encode("tensamin-chat-message-v1"); - -export function deriveChatId(ownUserId: number, peerUserId: number): string { - const ids = [ownUserId, peerUserId].sort((a, b) => a - b); - return `${ids[0]}:${ids[1]}`; -} - -export function deriveChatSecretId(chatId: string): string { - return `chat:${chatId}:main`; -} - -export function randomChatSecret(): Uint8Array { - return globalThis.crypto.getRandomValues(new Uint8Array(32)); -} - -export function ownKemPublicKeyFromKeyring(keyring: string): Uint8Array { - return crypto.keyringToKeys({ value: keyring, encoding: "base64" }) - .kemPublicKey; -} - -export function kemPublicKeyFromPublicKeyBundle(publicKey: string): Uint8Array { - return crypto.publicKeyBundleToKeys({ value: publicKey, encoding: "base64" }) - .kemPublicKey; -} - -export async function wrapChatSecret(args: { - chatSecret: Uint8Array; - recipientKemPublicKey: Uint8Array; - chatId: string; - secretId: string; - version: number; -}): Promise<{ - encryptedSecret: Uint8Array; - kemCiphertext: Uint8Array; - wrappingScheme: string; -}> { - const enc = crypto.encapsulate(args.recipientKemPublicKey); - try { - const wrappingKey = deriveWrappingKey({ - sharedSecret: enc.shared_secret, - chatId: args.chatId, - secretId: args.secretId, - version: args.version, - }); - - try { - return { - encryptedSecret: await crypto.encrypt(wrappingKey, args.chatSecret), - kemCiphertext: enc.ciphertext, - wrappingScheme: CHAT_SECRET_WRAPPING_SCHEME, - }; - } finally { - wrappingKey.fill(0); - } - } finally { - enc.shared_secret.fill(0); - } -} - -export async function unwrapChatSecret(args: { - encryptedSecret: Uint8Array; - kemCiphertext: Uint8Array; - keyring: string; - chatId: string; - secretId: string; - version: number; - wrappingScheme: string; -}): Promise { - if (args.wrappingScheme !== CHAT_SECRET_WRAPPING_SCHEME) { - throw new Error( - `Unsupported chat secret wrapping scheme: ${args.wrappingScheme}`, - ); - } - - const ownKeys = crypto.keyringToKeys({ - value: args.keyring, - encoding: "base64", - }); - const sharedSecret = crypto.decapsulate( - ownKeys.kemSecretKey, - args.kemCiphertext, - ); - - try { - const wrappingKey = deriveWrappingKey({ - sharedSecret, - chatId: args.chatId, - secretId: args.secretId, - version: args.version, - }); - - try { - return await crypto.decrypt(wrappingKey, args.encryptedSecret); - } finally { - wrappingKey.fill(0); - } - } finally { - sharedSecret.fill(0); - } -} - -export async function encryptChatText( - chatSecret: Uint8Array, - plaintext: string, -): Promise { - const key = deriveMessageKey(chatSecret); - try { - return bytesToBase64( - await crypto.encrypt(key, textEncoder.encode(plaintext)), - ); - } finally { - key.fill(0); - } -} - -export async function decryptChatText( - chatSecret: Uint8Array, - ciphertext: string, -): Promise { - const key = deriveMessageKey(chatSecret); - try { - return textDecoder.decode( - await crypto.decrypt(key, base64ToBytes(ciphertext)), - ); - } finally { - key.fill(0); - } -} - -function deriveWrappingKey(args: { - sharedSecret: Uint8Array; - chatId: string; - secretId: string; - version: number; -}): Uint8Array { - return crypto.deriveEncryptionKey( - args.sharedSecret, - CHAT_SECRET_SALT, - textEncoder.encode(`${args.chatId}:${args.secretId}:${args.version}`), - ); -} - -function deriveMessageKey(chatSecret: Uint8Array): Uint8Array { - return crypto.deriveEncryptionKey( - chatSecret, - CHAT_MESSAGE_SALT, - textEncoder.encode("message-content"), - ); -} diff --git a/packages/crypto/src/context.test.ts b/packages/crypto/src/context.test.ts new file mode 100644 index 0000000..9494caa --- /dev/null +++ b/packages/crypto/src/context.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, test } from "bun:test"; +import { createCryptoActions } from "./context"; + +/** + * Creates a rejected API getter used to verify initialization guards. + * @returns Null API reference. + */ +function getUninitializedApi(): null { + return null; +} + +describe("createCryptoActions", () => { + const textEncoder = new TextEncoder(); + const textDecoder = new TextDecoder(); + + test("throws when API is not initialized", async () => { + const actions = createCryptoActions(getUninitializedApi); + + let failed = false; + try { + await actions.encrypt("ab", new TextEncoder().encode("plain")); + } catch (error) { + failed = (error as Error).message.includes("API not initialized"); + } + + expect(failed).toBe(true); + }); + + test("delegates encrypt/decrypt/getSharedSecret to API reference", async () => { + const api = { + encrypt: async ( + secret: string, + input: Uint8Array, + ): Promise> => + textEncoder.encode(`${secret}:${textDecoder.decode(input)}`), + decrypt: async ( + secret: string, + input: Uint8Array, + ): Promise> => + textEncoder.encode(`${secret}|${textDecoder.decode(input)}`), + encryptText: async (secret: string, plaintext: string): Promise => + `${secret}:${plaintext}`, + decryptText: async ( + secret: string, + ciphertext: string, + ): Promise => `${secret}|${ciphertext}`, + getSharedSecret: async ( + ownPrivateKey: string, + ownPublicKey: string, + otherPublicKey: string, + ): Promise => + `${ownPrivateKey}.${ownPublicKey}.${otherPublicKey}`, + }; + + const actions = createCryptoActions(() => api); + + expect( + textDecoder.decode(await actions.encrypt("s", textEncoder.encode("p"))), + ).toBe("s:p"); + expect( + textDecoder.decode(await actions.decrypt("s", textEncoder.encode("c"))), + ).toBe("s|c"); + expect(await actions.encryptText("s", "p")).toBe("s:p"); + expect(await actions.decryptText("s", "c")).toBe("s|c"); + expect(await actions.getSharedSecret("a", "b", "c")).toBe("a.b.c"); + }); +}); diff --git a/packages/crypto/src/context.tsx b/packages/crypto/src/context.tsx index b9ca137..6275de6 100644 --- a/packages/crypto/src/context.tsx +++ b/packages/crypto/src/context.tsx @@ -1,5 +1,5 @@ -import { createContext, useContext } from "react"; -import { base64ToBytes, crypto } from "mtp"; +import * as React from "react"; +import * as Comlink from "comlink"; type CryptoContextType = { decrypt: ( @@ -12,62 +12,205 @@ type CryptoContextType = { input: Uint8Array, ) => Promise>; encryptText: (secret: string, plaintext: string) => Promise; + getSharedSecret: ( + ownPrivateKey: string, + ownPublicKey: string, + otherPublicKey: string, + ) => Promise; }; -export const context = createContext(undefined); +type ApiRef = { + encrypt: ( + secret: string, + input: Uint8Array, + ) => Promise>; + decrypt: ( + secret: string, + input: Uint8Array, + ) => Promise>; + decryptText: (secret: string, ciphertext: string) => Promise; + encryptText: (secret: string, plaintext: string) => Promise; + getSharedSecret: ( + ownPrivateKey: string, + ownPublicKey: string, + otherPublicKey: string, + ) => Promise; +}; -export function createCryptoActions( - getApi: () => CryptoContextType | null | undefined, -): CryptoContextType { - const requireApi = () => { - const api = getApi(); - if (!api) { - throw new Error("Crypto API not initialized"); - } - return api; - }; - - return { - decrypt: (secret, input) => requireApi().decrypt(secret, input), - decryptText: (secret, ciphertext) => - requireApi().decryptText(secret, ciphertext), - encrypt: (secret, input) => requireApi().encrypt(secret, input), - encryptText: (secret, plaintext) => - requireApi().encryptText(secret, plaintext), - }; +export function bytesToBase64(bytes: Uint8Array): string { + let binary = ""; + for (const b of bytes) binary += String.fromCharCode(b); + return btoa(binary); } -function ownedBytes(bytes: Uint8Array): Uint8Array { - const out = new Uint8Array(bytes.byteLength); - out.set(bytes); - return out; +export function base64ToBytes(base64: string): Uint8Array { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); + return bytes; } -function secretKeyFromString(secret: string): Uint8Array { - return crypto.deriveEncryptionKey( - base64ToBytes(secret), - new Uint8Array(0), - new TextEncoder().encode("tensamin:shared-secret-text"), - ); -} +export const context = React.createContext( + undefined, +); +/** + * Provides cryptographic actions backed by a worker without coupling to UI state. + * @param props Component props with children. + * @returns Crypto context provider JSX. + */ export default function Provider(props: { children: React.ReactNode }) { - const actions = createCryptoActions(() => ({ - decrypt: async (secret, input) => - ownedBytes(await crypto.decrypt(secretKeyFromString(secret), input)), - decryptText: (secret, ciphertext) => - crypto.decryptText(secretKeyFromString(secret), ciphertext), - encrypt: async (secret, input) => - ownedBytes(await crypto.encrypt(secretKeyFromString(secret), input)), - encryptText: (secret, plaintext) => - crypto.encryptText(secretKeyFromString(secret), plaintext), - })); + const apiRef = React.useRef(null); - return {props.children}; + const value = React.useMemo( + () => ({ + encrypt: async (secret, plaintext) => { + const api = apiRef.current; + if (!api) throw new Error("API not initialized"); + return await api.encrypt(secret, plaintext); + }, + decrypt: async (secret, ciphertext) => { + const api = apiRef.current; + if (!api) throw new Error("API not initialized"); + return await api.decrypt(secret, ciphertext); + }, + encryptText: async (secret, plaintext) => { + const api = apiRef.current; + if (!api) throw new Error("API not initialized"); + return await api.encryptText(secret, plaintext); + }, + decryptText: async (secret, ciphertext) => { + const api = apiRef.current; + if (!api) throw new Error("API not initialized"); + return await api.decryptText(secret, ciphertext); + }, + getSharedSecret: async (ownPrivateKey, ownPublicKey, otherPublicKey) => { + const api = apiRef.current; + if (!api) throw new Error("API not initialized"); + return await api.getSharedSecret( + ownPrivateKey, + ownPublicKey, + otherPublicKey, + ); + }, + }), + [], + ); + + React.useEffect(() => { + const worker = new Worker(new URL("./worker.ts", import.meta.url), { + type: "module", + }); + + apiRef.current = Comlink.wrap(worker); + + return () => { + apiRef.current = null; + worker.terminate(); + }; + }, []); + + return {props.children}; } +/** + * Creates crypto action functions that safely delegate to the worker API. + * @param getApiRef Function that returns the worker API reference. + * @returns Typed crypto action functions. + */ +export function createCryptoActions( + getApiRef: () => ApiRef | null, +): CryptoContextType { + /** + * Encrypts bytes by delegating to the crypto worker API. + * @param secret Hex-encoded shared secret. + * @param input Plaintext bytes to encrypt. + * @returns Ciphertext bytes. + */ + const encrypt = async ( + secret: string, + input: Uint8Array, + ): Promise> => { + const api = getApiRef(); + if (!api) throw new Error("API not initialized"); + return await api.encrypt(secret, input); + }; + + /** + * Decrypts bytes by delegating to the crypto worker API. + * @param secret Hex-encoded shared secret. + * @param input Ciphertext bytes to decrypt. + * @returns Plaintext bytes. + */ + const decrypt = async ( + secret: string, + input: Uint8Array, + ): Promise> => { + const api = getApiRef(); + if (!api) throw new Error("API not initialized"); + return await api.decrypt(secret, input); + }; + + /** + * Encrypts plaintext text by delegating to the crypto worker API. + * @param secret Hex-encoded shared secret. + * @param plaintext Plaintext to encrypt. + * @returns Base64 ciphertext. + */ + const encryptText = async ( + secret: string, + plaintext: string, + ): Promise => { + const api = getApiRef(); + if (!api) throw new Error("API not initialized"); + return await api.encryptText(secret, plaintext); + }; + + /** + * Decrypts base64 ciphertext text by delegating to the crypto worker API. + * @param secret Hex-encoded shared secret. + * @param ciphertext Base64 ciphertext to decrypt. + * @returns Decrypted plaintext. + */ + const decryptText = async ( + secret: string, + ciphertext: string, + ): Promise => { + const api = getApiRef(); + if (!api) throw new Error("API not initialized"); + return await api.decryptText(secret, ciphertext); + }; + + /** + * Derives a shared secret from local and peer key material via the worker API. + * @param ownPrivateKey Local private key. + * @param ownPublicKey Local public key. + * @param otherPublicKey Peer public key. + * @returns Hex-encoded shared secret. + */ + const getSharedSecret = async ( + ownPrivateKey: string, + ownPublicKey: string, + otherPublicKey: string, + ): Promise => { + const api = getApiRef(); + if (!api) throw new Error("API not initialized"); + return await api.getSharedSecret( + ownPrivateKey, + ownPublicKey, + otherPublicKey, + ); + }; + + return { encrypt, decrypt, encryptText, decryptText, getSharedSecret }; +} + +/** + * Returns the crypto actions from the nearest provider. + * Throws when used outside of the crypto provider tree. + */ export function useCrypto(): CryptoContextType { - const ctx = useContext(context); + const ctx = React.useContext(context); if (!ctx) { throw new Error("useCrypto must be used within a CryptoProvider"); } diff --git a/packages/crypto/src/worker.test.ts b/packages/crypto/src/worker.test.ts new file mode 100644 index 0000000..4157039 --- /dev/null +++ b/packages/crypto/src/worker.test.ts @@ -0,0 +1,123 @@ +import { describe, expect, test } from "bun:test"; +import { x448 } from "@noble/curves/ed448.js"; +import { + decrypt, + decryptText, + encrypt, + encryptText, + getSharedSecret, +} from "./worker"; + +/** + * Encodes bytes to URL-safe base64 without padding. + * @param value Input bytes. + * @returns Base64url string. + */ +function bytesToB64u(value: Uint8Array): string { + const alphabet = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + let output = ""; + + for (let index = 0; index < value.length; index += 3) { + const first = value[index] ?? 0; + const second = value[index + 1] ?? 0; + const third = value[index + 2] ?? 0; + const chunk = (first << 16) | (second << 8) | third; + + output += alphabet[(chunk >> 18) & 63]; + output += alphabet[(chunk >> 12) & 63]; + output += index + 1 < value.length ? alphabet[(chunk >> 6) & 63] : "="; + output += index + 2 < value.length ? alphabet[chunk & 63] : "="; + } + + return output.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, ""); +} + +/** + * Converts bytes to lowercase hex. + * @param value Input bytes. + * @returns Hex string. + */ +function bytesToHex(value: Uint8Array): string { + return Array.from(value, (byte) => byte.toString(16).padStart(2, "0")).join( + "", + ); +} + +/** + * Creates deterministic 56-byte private key material for tests. + * @param seed Offset seed used to vary generated bytes. + * @returns Deterministic private key bytes. + */ +function createPrivateKey(seed: number): Uint8Array { + const output = new Uint8Array(56); + + for (let index = 0; index < output.length; index += 1) { + output[index] = (seed + index) % 255; + } + + return output; +} + +describe("crypto worker", () => { + const textEncoder = new TextEncoder(); + const textDecoder = new TextDecoder(); + + test("encrypt/decrypt byte round-trip returns original plaintext", async () => { + const secret = "0f".repeat(56); + const input = "hello encrypted world"; + + const encryptedContent = await encrypt(secret, textEncoder.encode(input)); + const decryptedContent = await decrypt(secret, encryptedContent); + + expect(textDecoder.decode(decryptedContent)).toBe(input); + }); + + test("encryptText/decryptText round-trip returns original plaintext", async () => { + const secret = "0f".repeat(56); + const input = "hello encrypted world"; + + const ciphertext = await encryptText(secret, input); + const plaintext = await decryptText(secret, ciphertext); + + expect(plaintext).toBe(input); + }); + + test("decrypt fails with wrong shared secret", async () => { + const secret = "0f".repeat(56); + const wrongSecret = "f0".repeat(56); + const input = "sensitive"; + + const ciphertext = await encrypt(secret, textEncoder.encode(input)); + + let failed = false; + try { + await decrypt(wrongSecret, ciphertext); + } catch { + failed = true; + } + + expect(failed).toBe(true); + }); + + test("getSharedSecret matches noble x448 derivation", async () => { + const ownPrivateBytes = createPrivateKey(7); + const peerPrivateBytes = createPrivateKey(23); + + const ownPublicBytes = x448.getPublicKey(ownPrivateBytes); + const peerPublicBytes = x448.getPublicKey(peerPrivateBytes); + + const expected = bytesToHex( + new Uint8Array(x448.getSharedSecret(ownPrivateBytes, peerPublicBytes)), + ); + + const actual = await getSharedSecret( + bytesToB64u(ownPrivateBytes), + bytesToB64u(ownPublicBytes), + bytesToB64u(peerPublicBytes), + ); + + expect(actual).toBe(expected); + }); +}); diff --git a/packages/crypto/src/worker.ts b/packages/crypto/src/worker.ts new file mode 100644 index 0000000..da393f6 --- /dev/null +++ b/packages/crypto/src/worker.ts @@ -0,0 +1,519 @@ +import * as Comlink from "comlink"; + +type Base64URLString = string; + +type JWK = { + kty: string; + crv: string; + x?: string; + d?: string; +}; + +const textEncoder = new TextEncoder(); +const textDecoder = new TextDecoder(); +const crypto = globalThis.crypto; + +/** + * Encodes bytes as standard base64 text. + * @param bytes Bytes to encode. + * @returns Base64 string. + */ +function bytesToBase64(bytes: Uint8Array): string { + let binary = ""; + for (const byte of bytes) binary += String.fromCharCode(byte); + return btoa(binary); +} + +/** + * Decodes standard base64 text into bytes. + * @param base64 Base64 string. + * @returns Decoded bytes. + */ +function base64ToBytes(base64: string): Uint8Array { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let index = 0; index < binary.length; index += 1) { + bytes[index] = binary.charCodeAt(index); + } + return bytes; +} + +/** + * Encrypts bytes with a symmetric key derived from a hex shared secret. + * @param secret Hex-encoded shared secret. + * @param input Plaintext bytes to encrypt. + * @returns Ciphertext bytes. + */ +export async function encrypt( + secret: string, + input: Uint8Array, +): Promise> { + const sharedSecret = new Uint8Array( + secret.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)), + ); + + const hkdfKey = await crypto.subtle.importKey( + "raw", + sharedSecret, + "HKDF", + false, + ["deriveBits"], + ); + + const okm = await crypto.subtle.deriveBits( + { + name: "HKDF", + hash: "SHA-256", + salt: new Uint8Array([]), + info: textEncoder.encode("x448-aes-gcm-no-overhead"), + }, + hkdfKey, + 44 * 8, + ); + + const okmBytes = new Uint8Array(okm); + const keyBytes = okmBytes.slice(0, 32); + const nonce = okmBytes.slice(32, 44); + + const aesKey = await crypto.subtle.importKey( + "raw", + keyBytes, + { name: "AES-GCM" }, + false, + ["encrypt"], + ); + + const encryptedBuffer = await crypto.subtle.encrypt( + { name: "AES-GCM", iv: nonce }, + aesKey, + input, + ); + + return new Uint8Array(encryptedBuffer); +} + +/** + * Decrypts bytes with a symmetric key derived from a hex shared secret. + * @param secret Hex-encoded shared secret. + * @param input Ciphertext bytes to decrypt. + * @returns Plaintext bytes. + */ +export async function decrypt( + secret: string, + input: Uint8Array, +): Promise> { + const sharedSecret = new Uint8Array( + secret.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)), + ); + + const hkdfKey = await crypto.subtle.importKey( + "raw", + sharedSecret, + "HKDF", + false, + ["deriveBits"], + ); + + const okm = await crypto.subtle.deriveBits( + { + name: "HKDF", + hash: "SHA-256", + salt: new Uint8Array([]), + info: textEncoder.encode("x448-aes-gcm-no-overhead"), + }, + hkdfKey, + 44 * 8, + ); + + const okmBytes = new Uint8Array(okm); + const keyBytes = okmBytes.slice(0, 32); + const nonce = okmBytes.slice(32, 44); + + const aesKey = await crypto.subtle.importKey( + "raw", + keyBytes, + { name: "AES-GCM" }, + false, + ["decrypt"], + ); + + const decryptedBuffer = await crypto.subtle.decrypt( + { + name: "AES-GCM", + iv: nonce, + }, + aesKey, + input, + ); + + return new Uint8Array(decryptedBuffer); +} + +/** + * Encrypts UTF-8 text and returns base64 ciphertext for easy transport/storage. + * @param secret Hex-encoded shared secret. + * @param plaintext Text to encrypt. + * @returns Base64 ciphertext. + */ +export async function encryptText( + secret: string, + plaintext: string, +): Promise { + const encrypted = await encrypt(secret, textEncoder.encode(plaintext)); + return bytesToBase64(encrypted); +} + +/** + * Decrypts base64 ciphertext into UTF-8 text. + * @param secret Hex-encoded shared secret. + * @param ciphertext Base64 ciphertext. + * @returns Decrypted text. + */ +export async function decryptText( + secret: string, + ciphertext: string, +): Promise { + const decrypted = await decrypt(secret, base64ToBytes(ciphertext)); + return textDecoder.decode(decrypted); +} + +/** + * Computes an X448 shared secret from local and peer key material. + * @param ownPrivateKey Local private key in raw/base64/base64url or PKCS#8-wrapped form. + * @param ownPublicKey Local public key in raw/base64/base64url or SPKI-wrapped form. + * @param otherPublicKey Peer public key in raw/base64/base64url or SPKI-wrapped form. + * @returns Hex-encoded shared secret, or a failure message when key material is missing/invalid. + */ +export async function getSharedSecret( + ownPrivateKey: string, + ownPublicKey: string, + otherPublicKey: string, +): Promise { + const otherJwk: JWK = { kty: "OKP", crv: "X448", x: otherPublicKey }; + const ownJwk: JWK = { + kty: "OKP", + crv: "X448", + x: ownPublicKey, + d: ownPrivateKey, + }; + + /** + * Converts bytes to a lowercase hex string. + * @param u8 Byte array. + * @returns Hex string. + */ + const bytesToHex = (u8: Uint8Array): string => + Array.from(u8, (b) => b.toString(16).padStart(2, "0")).join(""); + + /** + * Decodes standard base64 text into bytes. + * @param s Base64 string. + * @returns Decoded bytes. + */ + const b64ToBytes = (s: Base64URLString): Uint8Array => { + const bin = atob(s); + const out = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i); + return out; + }; + + /** + * Decodes URL-safe base64 text into bytes. + * @param s Base64url string. + * @returns Decoded bytes. + */ + const b64uToBytes = (s: Base64URLString): Uint8Array => { + const b64 = + s.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((s.length + 3) % 4); + return b64ToBytes(b64); + }; + + /** + * Encodes bytes as URL-safe base64 without padding. + * @param u8 Byte array. + * @returns Base64url string. + */ + const bytesToB64u = (u8: Uint8Array): string => { + const b64 = btoa(String.fromCharCode(...u8)); + return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, ""); + }; + + /** + * Decodes either base64 or base64url text into bytes. + * @param s Base64/base64url string. + * @returns Decoded bytes. + */ + const decodeBase64Auto = (s: string): Uint8Array => + /[-_]/.test(s) ? b64uToBytes(s) : b64ToBytes(s); + + /** + * Reads a DER TLV item from the provided offset. + * @param view DER-encoded bytes. + * @param off Start offset. + * @returns Parsed TLV metadata with tag, length, and boundaries. + */ + const readTLV = (view: Uint8Array, off: number) => { + const tag = view[off++]; + if (off >= view.length) throw new Error("DER: truncated"); + let len = view[off++]; + if (len & 0x80) { + const n = len & 0x7f; + if (n === 0) throw new Error("DER: indefinite length not supported"); + if (off + n > view.length) throw new Error("DER: truncated length"); + len = 0; + for (let i = 0; i < n; i++) len = (len << 8) | view[off++]; + } + const start = off; + const end = off + len; + if (end > view.length) throw new Error("DER: content truncated"); + return { tag, len, start, end }; + }; + + /** + * Validates that a DER OID matches X448. + * @param view DER-encoded bytes. + * @param start Offset of the OID TLV. + * @returns True when the OID is X448. + */ + const ensureOidX448 = (view: Uint8Array, start: number): boolean => { + const oid = readTLV(view, start); + if (oid.tag !== 0x06) return false; + const len = oid.end - oid.start; + if (len !== 3) return false; + return ( + view[oid.start] === 0x2b && + view[oid.start + 1] === 0x65 && + view[oid.start + 2] === 0x6f + ); + }; + + /** + * Extracts raw 56-byte X448 public key material from SPKI bytes. + * @param spkiBytes DER-encoded SPKI bytes. + * @returns Raw X448 public key bytes. + */ + const extractRawX448FromSPKI = (spkiBytes: Uint8Array): Uint8Array => { + const view = spkiBytes; + const outer = readTLV(view, 0); + if (outer.tag !== 0x30) throw new Error("SPKI: expected SEQUENCE"); + const alg = readTLV(view, outer.start); + if (alg.tag !== 0x30) throw new Error("SPKI: expected AlgorithmIdentifier"); + if (!ensureOidX448(view, alg.start)) throw new Error("SPKI: not X448"); + const bitstr = readTLV(view, alg.end); + if (bitstr.tag !== 0x03) throw new Error("SPKI: expected BIT STRING"); + const unusedBits = view[bitstr.start]; + if (unusedBits !== 0x00) throw new Error("SPKI: unexpected unused bits"); + const raw = view.subarray(bitstr.start + 1, bitstr.end); + if (raw.length !== 56) + throw new Error("SPKI: X448 public key must be 56 bytes"); + return raw; + }; + + /** + * Extracts raw 56-byte X448 private key material from PKCS#8 bytes. + * @param pkcs8Bytes DER-encoded PKCS#8 bytes. + * @returns Raw X448 private key bytes. + */ + const extractRawX448FromPKCS8 = (pkcs8Bytes: Uint8Array): Uint8Array => { + const view = pkcs8Bytes; + const outer = readTLV(view, 0); + if (outer.tag !== 0x30) throw new Error("PKCS8: expected SEQUENCE"); + let off = outer.start; + + const version = readTLV(view, off); + if (version.tag !== 0x02) + throw new Error("PKCS8: expected version INTEGER"); + off = version.end; + + const alg = readTLV(view, off); + if (alg.tag !== 0x30) + throw new Error("PKCS8: expected AlgorithmIdentifier"); + if (!ensureOidX448(view, alg.start)) throw new Error("PKCS8: not X448"); + off = alg.end; + + const priv = readTLV(view, off); + if (priv.tag !== 0x04) + throw new Error("PKCS8: expected privateKey OCTET STRING"); + let raw = view.subarray(priv.start, priv.end); + + // Some encoders nest another OCTET STRING inside + if (raw[0] === 0x04) { + const inner = readTLV(raw, 0); + if (inner.tag === 0x04) { + raw = raw.subarray(inner.start, inner.end); + } + } + if (raw.length !== 56) + throw new Error("PKCS8: X448 private key must be 56 bytes"); + return raw; + }; + + /** + * Normalizes X448 JWK fields into raw base64url key material. + * @param jwk Candidate JWK. + * @param label Error label for diagnostics. + * @returns Normalized JWK suitable for WebCrypto import. + */ + const normalizeOkpX448Jwk = (jwk: JWK, label: string): JWK => { + if (!jwk || jwk.kty !== "OKP" || jwk.crv !== "X448") { + throw new Error(`${label}: expected OKP JWK with crv "X448"`); + } + const out = { ...jwk }; + + if (out.x) { + const xBytes = decodeBase64Auto(out.x); + let rawX: Uint8Array; + try { + rawX = extractRawX448FromSPKI(xBytes); + } catch { + if (xBytes.length !== 56) { + throw new Error( + `${label}: "x" is not a valid X448 SPKI or raw 56-byte key`, + ); + } + rawX = xBytes; + } + out.x = bytesToB64u(rawX); + } + + if (out.d) { + const dBytes = decodeBase64Auto(out.d); + let rawD: Uint8Array; + try { + rawD = extractRawX448FromPKCS8(dBytes); + } catch { + if (dBytes.length !== 56) { + throw new Error( + `${label}: "d" is not a valid X448 PKCS#8 or raw 56-byte key`, + ); + } + rawD = dBytes; + } + out.d = bytesToB64u(rawD); + } + + return out; + }; + + /** + * Returns WebCrypto subtle API when available. + * @returns SubtleCrypto instance or undefined. + */ + const getSubtle = () => globalThis.crypto?.subtle; + + { + /* + const hkdfAesGcmFromShared = async ( + sharedSecret: BufferSource, + infoStr: string + ): Promise => { + const subtle = getSubtle(); + if (!subtle) throw new Error("WebCrypto subtle not available"); + const info = textEncoder.encode(infoStr); + const baseKey = await subtle.importKey( + "raw", + sharedSecret, + "HKDF", + false, + ["deriveKey"] + ); + return await subtle.deriveKey( + { + name: "HKDF", + hash: "SHA-256", + salt: new Uint8Array(0), + info, + }, + baseKey, + { name: "AES-GCM", length: 256 }, + false, + ["encrypt", "decrypt"] + ); + }; + */ + } + + const myJwk: JWK = normalizeOkpX448Jwk(ownJwk, "own_jwk"); + const peerJwk: JWK = normalizeOkpX448Jwk(otherJwk, "other_jwk"); + + const subtle = getSubtle(); + //const infoStr = `ECDH-X448-AES-GCM-v1|my=${myJwk.x}|peer=${peerJwk.x}`; + + if (subtle) { + const algorithms = [{ name: "ECDH", namedCurve: "X448" }, { name: "X448" }]; + + for (const algorithm of algorithms) { + try { + const [myPriv, peerPub] = await Promise.all([ + subtle.importKey("jwk", myJwk, algorithm, false, ["deriveBits"]), + subtle.importKey("jwk", peerJwk, algorithm, false, []), + ]); + + const sharedBits = await subtle.deriveBits( + { name: algorithm.name, public: peerPub }, + myPriv, + 448, + ); + + const sharedSecret = new Uint8Array(sharedBits); + //const aeadKey = await hkdfAesGcmFromShared(sharedSecret, infoStr); + + return bytesToHex(sharedSecret); + } catch { + // Browser doesn't support this algorithm, try next or fall through to software fallback + } + } + } + + const { d: dMyB64u } = myJwk; + //const { x: xMyB64u, d: dMyB64u } = myJwk; + const { x: xPeerB64u } = peerJwk; + + if (!dMyB64u || !xPeerB64u) { + return "Failed to get shared secret due to missing keys"; + } + + const [dRaw, xRawPeer] = [b64uToBytes(dMyB64u), b64uToBytes(xPeerB64u)]; + if (dRaw.length !== 56 || xRawPeer.length !== 56) { + return "Failed to get shared secret due to invalid key lengths"; + } + + const { x448 } = await import("@noble/curves/ed448.js"); + const sharedSecret = new Uint8Array(x448.getSharedSecret(dRaw, xRawPeer)); + //const aeadKey = await hkdfAesGcmFromShared(sharedSecret, infoStr); + + return bytesToHex(sharedSecret); +} + +/** + * @deprecated Use getSharedSecret instead. + * @param ownPrivateKey Local private key. + * @param ownPublicKey Local public key. + * @param otherPublicKey Peer public key. + * @returns Shared secret derived by getSharedSecret. + */ +export async function get_shared_secret( + ownPrivateKey: string, + ownPublicKey: string, + otherPublicKey: string, +): Promise { + return await getSharedSecret(ownPrivateKey, ownPublicKey, otherPublicKey); +} + +/** + * Checks whether the current runtime context is a worker global scope. + * @returns True when executed inside a worker-like runtime. + */ +function isWorkerRuntime(): boolean { + return "postMessage" in globalThis && "importScripts" in globalThis; +} + +if (isWorkerRuntime()) { + Comlink.expose({ + encrypt, + decrypt, + encryptText, + decryptText, + getSharedSecret, + }); +} diff --git a/packages/hotkeys/package.json b/packages/hotkeys/package.json deleted file mode 100644 index 5a41a78..0000000 --- a/packages/hotkeys/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@tensamin/hotkeys", - "private": true, - "version": "0.0.0", - "type": "module", - "exports": { - ".": "./src/index.ts" - }, - "scripts": { - "format": "pnpm exec prettier --write .", - "lint": "eslint src", - "test": "vitest run --passWithNoTests", - "build": "pnpm run test && tsc -p tsconfig.json --noEmit" - }, - "dependencies": { - "@tanstack/react-hotkeys": "^0.10.0", - "@tensamin/storage": "workspace:*", - "react": "^19.2.8", - "react-dom": "^19.2.8" - }, - "devDependencies": { - "vite": "^8.2.1" - } -} diff --git a/packages/hotkeys/src/context.tsx b/packages/hotkeys/src/context.tsx deleted file mode 100644 index c7040cf..0000000 --- a/packages/hotkeys/src/context.tsx +++ /dev/null @@ -1,252 +0,0 @@ -import { - HotkeysProvider as TanStackHotkeysProvider, - useHotkeys as useTanStackHotkeys, - type Hotkey, - type UseHotkeyOptions, -} from "@tanstack/react-hotkeys"; -import { - createContext, - type ReactNode, - useCallback, - useContext, - useEffect, - useMemo, - useRef, - useState, -} from "react"; -import { useStorage } from "@tensamin/storage/context"; -import { - getHotkeyDefinitions, - normalizeHotkeyOverrides, - toElectronAccelerator, - type HotkeyDefinition, -} from "./registry"; - -type GlobalRegistrationStatus = "registered" | "unavailable"; - -type HotkeysContextValue = { - overrides: Record; - bindingFor: (definition: HotkeyDefinition) => Hotkey | null; - setBinding: (definition: HotkeyDefinition, binding: Hotkey | null) => void; - resetBinding: (definition: HotkeyDefinition) => void; - resetAll: () => void; - globalStatuses: Record; - setRecording: (recording: boolean) => void; - registerGlobalHandler: ( - definition: HotkeyDefinition, - handler: () => void, - ) => () => void; -}; - -const HotkeysContext = createContext( - undefined, -); - -export function HotkeysProvider({ children }: { children: ReactNode }) { - const { load, save } = useStorage(); - const [overrides, setOverrides] = useState>({}); - const [handlersRevision, setHandlersRevision] = useState(0); - const [globalStatuses, setGlobalStatuses] = useState< - Record - >({}); - const handlers = useRef(new Map void>>()); - const overridesRef = useRef>({}); - - useEffect(() => { - let active = true; - void load("hotkey_overrides") - .then((stored) => { - if (!active) return; - const next = normalizeHotkeyOverrides(stored); - overridesRef.current = next; - setOverrides(next); - }) - .catch((error: unknown) => { - console.error("Failed to load hotkey settings", error); - }); - return () => { - active = false; - }; - }, [load]); - - const persist = useCallback( - (next: Record) => { - overridesRef.current = next; - setOverrides(next); - void save("hotkey_overrides", next).catch((error: unknown) => { - console.error("Failed to save hotkey settings", error); - }); - }, - [save], - ); - - const bindingFor = useCallback( - (definition: HotkeyDefinition) => - Object.hasOwn(overrides, definition.id) - ? (overrides[definition.id] ?? null) - : definition.defaultBinding, - [overrides], - ); - - const setBinding = useCallback( - (definition: HotkeyDefinition, binding: Hotkey | null) => { - const next = { ...overridesRef.current }; - if (binding === definition.defaultBinding) delete next[definition.id]; - else next[definition.id] = binding; - persist(next); - }, - [persist], - ); - - const resetBinding = useCallback( - (definition: HotkeyDefinition) => { - const next = { ...overridesRef.current }; - delete next[definition.id]; - persist(next); - }, - [persist], - ); - - const resetAll = useCallback(() => persist({}), [persist]); - - const registerGlobalHandler = useCallback( - (definition: HotkeyDefinition, handler: () => void) => { - const current = handlers.current.get(definition.id) ?? new Set(); - current.add(handler); - handlers.current.set(definition.id, current); - setHandlersRevision((revision) => revision + 1); - return () => { - current.delete(handler); - if (current.size === 0) handlers.current.delete(definition.id); - setHandlersRevision((revision) => revision + 1); - }; - }, - [], - ); - - useEffect(() => { - return window.tensaminDesktop?.hotkeys?.onTriggered?.((id) => { - handlers.current.get(id)?.forEach((handler) => handler()); - }); - }, []); - - useEffect(() => { - const desktopHotkeys = window.tensaminDesktop?.hotkeys; - if (!desktopHotkeys?.setBindings) return; - - const unsupported: string[] = []; - const registrations = getHotkeyDefinitions().flatMap((definition) => { - if (!definition.global || !handlers.current.has(definition.id)) return []; - const binding = bindingFor(definition); - const accelerator = binding && toElectronAccelerator(binding); - if (binding && !accelerator) unsupported.push(definition.id); - return accelerator ? [{ id: definition.id, accelerator }] : []; - }); - let active = true; - void desktopHotkeys - .setBindings(registrations) - .then((statuses) => { - if (!active) return; - setGlobalStatuses( - Object.fromEntries( - [ - ...unsupported.map((id) => [id, false] as const), - ...Object.entries(statuses), - ].map(([id, registered]) => [ - id, - registered ? "registered" : "unavailable", - ]), - ), - ); - }) - .catch((error: unknown) => { - console.error("Failed to register global hotkeys", error); - }); - return () => { - active = false; - }; - }, [bindingFor, handlersRevision]); - - const setRecording = useCallback((recording: boolean) => { - void window.tensaminDesktop?.hotkeys - ?.setSuspended?.(recording) - .catch((error: unknown) => { - console.error("Failed to suspend global hotkeys", error); - }); - }, []); - - const value = useMemo( - () => ({ - overrides, - bindingFor, - setBinding, - resetBinding, - resetAll, - globalStatuses, - setRecording, - registerGlobalHandler, - }), - [ - bindingFor, - globalStatuses, - overrides, - registerGlobalHandler, - resetAll, - resetBinding, - setBinding, - setRecording, - ], - ); - - return ( - - {children} - - ); -} - -export function useHotkeysContext() { - const value = useContext(HotkeysContext); - if (!value) - throw new Error("useHotkeysContext must be used within HotkeysProvider"); - return value; -} - -export function useHotkey( - definition: HotkeyDefinition, - callback: () => void, - options: UseHotkeyOptions = {}, -) { - const { bindingFor, registerGlobalHandler } = useHotkeysContext(); - const callbackRef = useRef(callback); - useEffect(() => { - callbackRef.current = callback; - }, [callback]); - const binding = bindingFor(definition); - const handledByElectron = Boolean( - definition.global && window.tensaminDesktop?.hotkeys, - ); - - useTanStackHotkeys( - binding && !handledByElectron && options.enabled !== false - ? [ - { - hotkey: binding, - callback: () => callbackRef.current(), - options: { - ...options, - meta: { - name: definition.name, - description: definition.description, - }, - }, - }, - ] - : [], - ); - - useEffect(() => { - if (!definition.global || options.enabled === false || !binding) return; - return registerGlobalHandler(definition, () => callbackRef.current()); - }, [binding, definition, options.enabled, registerGlobalHandler]); -} diff --git a/packages/hotkeys/src/index.ts b/packages/hotkeys/src/index.ts deleted file mode 100644 index a46ac7c..0000000 --- a/packages/hotkeys/src/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -export { HotkeysProvider, useHotkey, useHotkeysContext } from "./context"; -export { - defineHotkey, - getHotkeyDefinitions, - normalizeHotkeyOverrides, - toElectronAccelerator, - useHotkeyDefinitions, - type HotkeyDefinition, -} from "./registry"; -export { - formatForDisplay, - useHotkeyRecorder, - type Hotkey, -} from "@tanstack/react-hotkeys"; diff --git a/packages/hotkeys/src/registry.ts b/packages/hotkeys/src/registry.ts deleted file mode 100644 index 7d02ea6..0000000 --- a/packages/hotkeys/src/registry.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { useSyncExternalStore } from "react"; -import { validateHotkey, type Hotkey } from "@tanstack/react-hotkeys"; - -export type HotkeyDefinition = Readonly<{ - id: string; - name: string; - description?: string; - category: string; - defaultBinding: Hotkey; - global?: boolean; -}>; - -const definitions = new Map(); -const listeners = new Set<() => void>(); -let snapshot: HotkeyDefinition[] = []; - -export function defineHotkey(definition: HotkeyDefinition) { - const existing = definitions.get(definition.id); - if (existing) return existing; - - definitions.set(definition.id, Object.freeze({ ...definition })); - snapshot = [...definitions.values()]; - listeners.forEach((listener) => listener()); - return definitions.get(definition.id)!; -} - -export function getHotkeyDefinitions() { - return snapshot; -} - -export function useHotkeyDefinitions() { - return useSyncExternalStore( - (listener) => { - listeners.add(listener); - return () => listeners.delete(listener); - }, - getHotkeyDefinitions, - getHotkeyDefinitions, - ); -} - -export function normalizeHotkeyOverrides(value: unknown) { - if (!value || typeof value !== "object" || Array.isArray(value)) return {}; - - return Object.fromEntries( - Object.entries(value).filter( - ([id, binding]) => - id.length > 0 && - id.length <= 128 && - (binding === null || - (typeof binding === "string" && - binding.length > 0 && - binding.length <= 128 && - validateHotkey(binding).valid)), - ), - ) as Record; -} - -export function toElectronAccelerator(hotkey: Hotkey) { - const keyAliases: Record = { - ArrowDown: "Down", - ArrowLeft: "Left", - ArrowRight: "Right", - ArrowUp: "Up", - " ": "Space", - }; - const modifierAliases: Record = { - Alt: "Alt", - Control: "Control", - Ctrl: "Control", - Meta: "Command", - Mod: "CommandOrControl", - Shift: "Shift", - }; - const parts = hotkey.split("+"); - if (parts.length === 0) return null; - - const key = parts.at(-1)!; - const modifiers = parts.slice(0, -1).map((part) => modifierAliases[part]); - if (modifiers.some((part) => !part)) return null; - - const acceleratorKey = keyAliases[key] ?? key; - if ( - !/^[A-Za-z0-9]$/.test(acceleratorKey) && - !keyAliases[key] && - !/^(Backspace|Delete|End|Enter|Escape|F([1-9]|1[0-9]|2[0-4])|Home|PageDown|PageUp|Space|Tab)$/.test( - acceleratorKey, - ) - ) { - return null; - } - - return [...modifiers, acceleratorKey].join("+"); -} diff --git a/packages/hotkeys/tsconfig.json b/packages/hotkeys/tsconfig.json deleted file mode 100644 index 7740474..0000000 --- a/packages/hotkeys/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "jsx": "react-jsx", - "strict": true, - "skipLibCheck": true, - "noEmit": true, - "types": ["vite/client"] - }, - "include": ["src"] -} diff --git a/packages/markdown/package.json b/packages/markdown/package.json index 247d715..5090799 100644 --- a/packages/markdown/package.json +++ b/packages/markdown/package.json @@ -5,26 +5,20 @@ "type": "module", "exports": { "./text": "./src/text.tsx", - "./input": "./src/input.tsx", - "./emoji": "./src/emoji.tsx" + "./input": "./src/input.tsx" }, "scripts": { - "format": "pnpm exec prettier --write .", + "format": "bunx prettier --write .", "lint": "eslint src", "build": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@codemirror/autocomplete": "^6.20.3", - "@codemirror/commands": "^6.10.4", - "@codemirror/lang-markdown": "^6.5.2", - "@codemirror/language": "^6.12.4", - "@codemirror/state": "^6.7.1", - "@codemirror/view": "^6.43.8", - "@methanium/ui": "*", - "@twemoji/api": "^17.0.3", - "emojibase-data": "^17.0.0", - "lucide-react": "^1.30.0", - "react": "^19.2.8", - "react-dom": "^19.2.8" + "@codemirror/commands": "^6.10.2", + "@codemirror/lang-markdown": "^6.5.0", + "@codemirror/state": "^6.5.4", + "@codemirror/view": "^6.41.1", + "@tensamin/ui": "*", + "react": "^19.2.0", + "react-dom": "^19.2.0" } } diff --git a/packages/markdown/src/emoji.tsx b/packages/markdown/src/emoji.tsx deleted file mode 100644 index c0eb397..0000000 --- a/packages/markdown/src/emoji.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import twemoji from "@twemoji/api"; -import { Tooltip, TooltipContent, TooltipTrigger } from "@methanium/ui"; -import { resolveEmoji } from "./emojiData"; - -export { - emojis, - findEmojiShortcodes, - normalizeShortcode, - resolveEmoji, - searchEmojis, -} from "./emojiData"; -export type { EmojiDefinition } from "./emojiData"; - -export function getEmojiUrl(shortcode: string): string | undefined { - const emoji = resolveEmoji(shortcode); - return emoji ? `${twemoji.base}svg/${emoji.hexcode}.svg` : undefined; -} - -export default function Emoji({ - className = "h-6 w-6", - shortcode, - tooltip = true, -}: { - className?: string; - shortcode: string; - tooltip?: boolean; -}) { - const emoji = resolveEmoji(shortcode); - if (!emoji) return {shortcode}; - - const image = ( - {emoji.shortcode} - ); - - if (!tooltip) return image; - - return ( - - - {emoji.shortcode} - - ); -} diff --git a/packages/markdown/src/emojiData.ts b/packages/markdown/src/emojiData.ts deleted file mode 100644 index 158b5b0..0000000 --- a/packages/markdown/src/emojiData.ts +++ /dev/null @@ -1,94 +0,0 @@ -import shortcodeData from "emojibase-data/en/shortcodes/joypixels.json"; - -export type EmojiDefinition = { - aliases: readonly string[]; - hexcode: string; - name: string; - shortcode: string; -}; - -function normalizeName(value: string) { - return value - .trim() - .replace(/^:+|:+$/g, "") - .toLowerCase(); -} - -export const emojis: readonly EmojiDefinition[] = Object.entries( - shortcodeData as Record, -).map(([hexcode, value]) => { - const aliases = Array.isArray(value) ? value : [value]; - const name = aliases[0]; - - return { - aliases, - hexcode: hexcode.toLowerCase().replaceAll("_", "-"), - name, - shortcode: `:${name}:`, - }; -}); - -const emojiByName = new Map(); -for (const emoji of emojis) { - for (const alias of emoji.aliases) { - emojiByName.set(normalizeName(alias), emoji); - } -} - -export function resolveEmoji(value: string): EmojiDefinition | undefined { - return emojiByName.get(normalizeName(value)); -} - -export function normalizeShortcode(value: string): string | undefined { - return resolveEmoji(value)?.shortcode; -} - -export function findEmojiShortcodes(value: string) { - const matches: Array<{ - emoji: EmojiDefinition; - from: number; - to: number; - }> = []; - let searchFrom = 0; - - while (searchFrom < value.length) { - const from = value.indexOf(":", searchFrom); - if (from === -1) break; - - const candidate = value.slice(from).match(/^:([a-z0-9_+-]+):/i); - if (!candidate) { - searchFrom = from + 1; - continue; - } - - const emoji = resolveEmoji(candidate[1]); - if (!emoji) { - // The closing colon may also open the next valid shortcode. - searchFrom = from + candidate[0].length - 1; - continue; - } - - const to = from + candidate[0].length; - matches.push({ emoji, from, to }); - searchFrom = to; - } - - return matches; -} - -export function searchEmojis(query: string): EmojiDefinition[] { - const normalizedQuery = normalizeName(query); - if (!normalizedQuery) return [...emojis]; - - return emojis - .map((emoji) => { - const names = emoji.aliases.map(normalizeName); - const exact = names.includes(normalizedQuery); - const prefix = names.some((name) => name.startsWith(normalizedQuery)); - const contains = names.some((name) => name.includes(normalizedQuery)); - return { emoji, rank: exact ? 0 : prefix ? 1 : contains ? 2 : 3 }; - }) - .filter(({ rank }) => rank < 3) - .sort((a, b) => a.rank - b.rank || a.emoji.name.localeCompare(b.emoji.name)) - .map(({ emoji }) => emoji); -} diff --git a/packages/markdown/src/input.tsx b/packages/markdown/src/input.tsx index e16c84d..f44ced6 100644 --- a/packages/markdown/src/input.tsx +++ b/packages/markdown/src/input.tsx @@ -1,22 +1,7 @@ import { markdown } from "@codemirror/lang-markdown"; -import { syntaxTree } from "@codemirror/language"; -import { - acceptCompletion, - autocompletion, - completionStatus, - pickedCompletion, - startCompletion, - type Completion, - type CompletionContext, - type CompletionResult, -} from "@codemirror/autocomplete"; import { EditorState, - EditorSelection, - Annotation, - Compartment, Prec, - Transaction, type Extension, type Range, type SelectionRange, @@ -27,7 +12,6 @@ import { keymap, placeholder, ViewPlugin, - WidgetType, type DecorationSet, type KeyBinding, type ViewUpdate, @@ -39,24 +23,8 @@ import { indentWithTab, } from "@codemirror/commands"; import { useEffect, useRef } from "react"; -import type { CSSProperties } from "react"; -import { createRoot, type Root } from "react-dom/client"; import { collectInlineRanges, ensureMarkdownStyles } from "./markdown"; -import Emoji, { - findEmojiShortcodes, - getEmojiUrl, - resolveEmoji, - searchEmojis, -} from "./emoji"; - -export const MAX_RENDERED_EMOJI_OPTIONS = 100; - -export type InputController = { - focus: () => void; - hasFocus: () => boolean; - insertText: (text: string) => void; -}; export type InputProps = { ref?: HTMLDivElement; @@ -65,35 +33,12 @@ export type InputProps = { setValue: (value: string) => void; onSubmit?: () => void; invertEnterBehavior?: boolean; - styled?: boolean; - fontSize?: CSSProperties["fontSize"]; - paddingX?: CSSProperties["padding"]; - paddingY?: CSSProperties["padding"]; - className?: string; - emojiFrequencies?: Readonly>; - onEmojiSelect?: (shortcode: string) => void; - autoFocus?: boolean; - onControllerChange?: (controller: InputController | null) => void; }; -function toCssLength(value: CSSProperties["padding"]): string | undefined { - if (value === undefined) { - return undefined; - } - - return typeof value === "number" ? `${value}px` : value; -} - -function toCssPadding( - vertical: CSSProperties["padding"], - horizontal: CSSProperties["padding"], - styled: boolean, -): string { - const defaultVertical = styled ? "0.25rem" : "0"; - const defaultHorizontal = styled ? "0.625rem" : "0"; - - return `${toCssLength(vertical) ?? defaultVertical} ${toCssLength(horizontal) ?? defaultHorizontal}`; -} +type TokenRange = { + from: number; + to: number; +}; const hiddenTokenDecoration = Decoration.mark({ class: "tm-md-hidden-token" }); const strongDecoration = Decoration.mark({ class: "tm-md-strong" }); @@ -102,180 +47,6 @@ const delDecoration = Decoration.mark({ class: "tm-md-del" }); const codeDecoration = Decoration.mark({ class: "tm-md-code" }); const linkDecoration = Decoration.mark({ class: "tm-md-link" }); const codeLineDecoration = Decoration.line({ class: "tm-md-code-line" }); -const externalValueSync = Annotation.define(); -const widgetRoots = new WeakMap(); - -type EmojiRange = { - from: number; - shortcode: string; - to: number; - url: string; -}; - -class EmojiWidget extends WidgetType { - readonly shortcode: string; - readonly url: string; - - constructor(shortcode: string, url: string) { - super(); - this.shortcode = shortcode; - this.url = url; - } - - eq(other: EmojiWidget) { - return other.shortcode === this.shortcode && other.url === this.url; - } - - toDOM() { - const container = document.createElement("span"); - const root = createRoot(container); - root.render( - , - ); - widgetRoots.set(container, root); - return container; - } - - destroy(dom: HTMLElement) { - widgetRoots.get(dom)?.unmount(); - widgetRoots.delete(dom); - } - - ignoreEvent() { - return true; - } -} - -function codeRanges(state: EditorState) { - const ranges: Array<{ from: number; to: number }> = []; - - syntaxTree(state).iterate({ - enter(node) { - if ( - node.name === "InlineCode" || - node.name === "FencedCode" || - node.name === "CodeBlock" - ) { - ranges.push({ from: node.from, to: node.to }); - return false; - } - }, - }); - - return ranges; -} - -export function findEmojiRanges(state: EditorState): EmojiRange[] { - const document = state.doc.toString(); - const excluded = codeRanges(state); - const ranges: EmojiRange[] = []; - - for (const match of findEmojiShortcodes(document)) { - const { from, to } = match; - const inCode = excluded.some((range) => from < range.to && to > range.from); - const emoji = inCode ? undefined : match.emoji; - const url = emoji ? getEmojiUrl(emoji.shortcode) : undefined; - - if (emoji && url) { - ranges.push({ from, shortcode: emoji.shortcode, to, url }); - } - } - - return ranges; -} - -class EmojiPluginValue { - decorations: DecorationSet; - ranges: EmojiRange[]; - - constructor(view: EditorView) { - this.ranges = findEmojiRanges(view.state); - this.decorations = this.buildDecorations(); - } - - update(update: ViewUpdate) { - if ( - update.docChanged || - syntaxTree(update.startState) !== syntaxTree(update.state) - ) { - this.ranges = findEmojiRanges(update.state); - this.decorations = this.buildDecorations(); - } - } - - private buildDecorations() { - return Decoration.set( - this.ranges.map((range) => - Decoration.replace({ - inclusive: false, - widget: new EmojiWidget(range.shortcode, range.url), - }).range(range.from, range.to), - ), - true, - ); - } -} - -const emojiDecorations = ViewPlugin.fromClass(EmojiPluginValue, { - decorations: (instance) => instance.decorations, - provide: (plugin) => - EditorView.atomicRanges.of( - (view) => view.plugin(plugin)?.decorations ?? Decoration.none, - ), -}); - -function deleteEmoji(view: EditorView, direction: "backward" | "forward") { - const ranges = view.plugin(emojiDecorations)?.ranges ?? []; - const deletions: Array<{ from: number; to: number }> = []; - - for (const selection of view.state.selection.ranges) { - if (selection.empty) { - const emoji = ranges.find((range) => - direction === "backward" - ? selection.from > range.from && selection.from <= range.to - : selection.from >= range.from && selection.from < range.to, - ); - if (emoji) deletions.push({ from: emoji.from, to: emoji.to }); - continue; - } - - let from = selection.from; - let to = selection.to; - let changed = false; - - for (const emoji of ranges) { - if (from < emoji.to && to > emoji.from) { - from = Math.min(from, emoji.from); - to = Math.max(to, emoji.to); - changed = true; - } - } - - if (changed) deletions.push({ from, to }); - } - - if (deletions.length === 0) return false; - - const merged = deletions - .sort((a, b) => a.from - b.from) - .reduce>((result, deletion) => { - const previous = result.at(-1); - if (previous && deletion.from <= previous.to) { - previous.to = Math.max(previous.to, deletion.to); - } else { - result.push({ ...deletion }); - } - return result; - }, []); - - view.dispatch({ - changes: merged.map((range) => ({ from: range.from, to: range.to })), - selection: EditorSelection.cursor(merged[0].from), - scrollIntoView: true, - userEvent: direction === "backward" ? "delete.backward" : "delete.forward", - }); - return true; -} /** * Builds markdown styling decorations every time the document or cursor selection changes. @@ -309,33 +80,16 @@ const markdownDecorations = ViewPlugin.fromClass( export default function Input(props: InputProps) { ensureMarkdownStyles(); - const shellClassName = props.styled - ? "min-h-8 w-full min-w-0 rounded-lg border border-input bg-transparent text-base transition-colors outline-none placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40" - : ""; - const elementRef = useRef(null); const viewRef = useRef(undefined); - const setValueRef = useRef(props.setValue); + const ignoreSyncRef = useRef(false); const onSubmitRef = useRef(props.onSubmit); - const onEmojiSelectRef = useRef( - props.onEmojiSelect, - ); const invertEnterBehaviorRef = useRef(Boolean(props.invertEnterBehavior)); - const completionCompartmentRef = useRef(null); - completionCompartmentRef.current ??= new Compartment(); - const completionCompartment = completionCompartmentRef.current; useEffect(() => { - setValueRef.current = props.setValue; onSubmitRef.current = props.onSubmit; - onEmojiSelectRef.current = props.onEmojiSelect; invertEnterBehaviorRef.current = Boolean(props.invertEnterBehavior); - }, [ - props.onEmojiSelect, - props.onSubmit, - props.invertEnterBehavior, - props.setValue, - ]); + }, [props.onSubmit, props.invertEnterBehavior]); useEffect(() => { if (!elementRef.current) return; @@ -344,14 +98,12 @@ export default function Input(props: InputProps) { doc: props.value, extensions: createEditorExtensions( (value) => { - setValueRef.current(value); + ignoreSyncRef.current = true; + props.setValue(value); }, () => props.placeholder, () => invertEnterBehaviorRef.current, () => onSubmitRef.current?.(), - completionCompartment, - props.emojiFrequencies, - (shortcode) => onEmojiSelectRef.current?.(shortcode), ), }); @@ -359,23 +111,8 @@ export default function Input(props: InputProps) { state, parent: elementRef.current, }); - props.onControllerChange?.({ - focus: () => viewRef.current?.contentDOM.focus({ preventScroll: true }), - hasFocus: () => viewRef.current?.hasFocus ?? false, - insertText: (text) => { - const editor = viewRef.current; - if (!editor) return; - editor.dispatch({ - ...editor.state.replaceSelection(text), - annotations: Transaction.userEvent.of("input.type"), - scrollIntoView: true, - }); - }, - }); - if (props.autoFocus) viewRef.current.focus(); return () => { - props.onControllerChange?.(null); viewRef.current?.destroy(); viewRef.current = undefined; }; @@ -390,6 +127,11 @@ export default function Input(props: InputProps) { const next = props.value; const current = editor.state.doc.toString(); + if (ignoreSyncRef.current) { + ignoreSyncRef.current = false; + return; + } + if (next === current) return; editor.dispatch({ @@ -398,48 +140,10 @@ export default function Input(props: InputProps) { to: current.length, insert: next, }, - annotations: [ - externalValueSync.of(true), - Transaction.addToHistory.of(false), - ], - filter: false, }); }, [props.value]); - useEffect(() => { - const editor = viewRef.current; - const compartment = completionCompartmentRef.current; - if (!editor || !compartment) return; - - const wasActive = completionStatus(editor.state) === "active"; - editor.dispatch({ - effects: compartment.reconfigure( - createEmojiAutocomplete(props.emojiFrequencies, (shortcode) => - onEmojiSelectRef.current?.(shortcode), - ), - ), - }); - if (wasActive) startCompletion(editor); - }, [props.emojiFrequencies]); - - return ( -
- ); + return
; } /** @@ -455,9 +159,6 @@ function createEditorExtensions( getPlaceholder: () => string | undefined, getInvertEnterBehavior: () => boolean, onSubmit: () => void, - completionCompartment: Compartment, - emojiFrequencies: Readonly> | undefined, - onEmojiSelect: (shortcode: string) => void, ): Extension[] { const editorKeymap = [ ...defaultKeymap, @@ -468,10 +169,7 @@ function createEditorExtensions( const customEnterKeymap = keymap.of([ { key: "Shift-Enter", - run: (view) => { - if (completionStatus(view.state) === "active") { - return acceptCompletion(view); - } + run: () => { if (!getInvertEnterBehavior()) { return false; } @@ -482,10 +180,7 @@ function createEditorExtensions( }, { key: "Enter", - run: (view) => { - if (completionStatus(view.state) === "active") { - return acceptCompletion(view); - } + run: () => { if (getInvertEnterBehavior()) { return false; } @@ -495,53 +190,21 @@ function createEditorExtensions( }, }, ]); - const completionTabKeymap = keymap.of([ - { - key: "Tab", - run: (view) => - completionStatus(view.state) === "active" - ? acceptCompletion(view) - : false, - }, - ]); - const emojiDeletionKeymap = keymap.of([ - { - key: "Backspace", - run: (view) => deleteEmoji(view, "backward"), - }, - { - key: "Delete", - run: (view) => deleteEmoji(view, "forward"), - }, - ]); return [ history(), markdown(), - completionCompartment.of( - createEmojiAutocomplete(emojiFrequencies, onEmojiSelect), - ), - emojiDecorations, keymap.of(editorKeymap), - Prec.highest(completionTabKeymap), - Prec.highest(emojiDeletionKeymap), Prec.highest(customEnterKeymap), EditorView.lineWrapping, placeholder(getPlaceholder() ?? ""), EditorView.updateListener.of((update: ViewUpdate) => { if (!update.docChanged) return; - if ( - update.transactions.some( - (transaction) => transaction.annotation(externalValueSync) === true, - ) - ) { - return; - } onChange(update.state.doc.toString()); }), EditorView.theme({ "&": { - fontSize: "inherit", + fontSize: "1rem", }, "&.cm-editor": { width: "100%", @@ -556,126 +219,6 @@ function createEditorExtensions( ]; } -function createEmojiAutocomplete( - frequencies: Readonly> | undefined, - onEmojiSelect: (shortcode: string) => void, -) { - return autocompletion({ - activateOnTyping: true, - addToOptions: [ - { - position: 20, - render(completion) { - const container = document.createElement("span"); - createRoot(container).render( - , - ); - return container; - }, - }, - ], - maxRenderedOptions: MAX_RENDERED_EMOJI_OPTIONS, - override: [createEmojiCompletionSource(frequencies, onEmojiSelect)], - }); -} - -function normalizedFrequencies( - frequencies: Readonly> | undefined, -) { - const normalized = new Map(); - for (const [value, frequency] of Object.entries(frequencies ?? {})) { - const shortcode = resolveEmoji(value)?.shortcode; - if (!shortcode || !Number.isFinite(frequency) || frequency <= 0) continue; - normalized.set(shortcode, (normalized.get(shortcode) ?? 0) + frequency); - } - return normalized; -} - -export function createEmojiCompletionSource( - frequencies?: Readonly>, - onEmojiSelect: (shortcode: string) => void = () => undefined, -) { - const normalized = normalizedFrequencies(frequencies); - const maxFrequency = Math.max(0, ...normalized.values()); - - return (context: CompletionContext): CompletionResult | null => { - const token = context.matchBefore(/:[a-z0-9_+-]*$/i); - if (!token) return null; - if ( - codeRanges(context.state).some( - (range) => token.from < range.to && token.to > range.from, - ) - ) { - return null; - } - - const characterBefore = context.state.sliceDoc( - Math.max(0, token.from - 1), - token.from, - ); - if (characterBefore && /[a-z0-9_]/i.test(characterBefore)) return null; - - const query = token.text.slice(1).toLowerCase(); - const options: Completion[] = searchEmojis(query) - .map((emoji) => { - const aliases = emoji.aliases.map((alias) => alias.toLowerCase()); - const matchedAlias = - aliases.find((alias) => alias === query) ?? - aliases.find((alias) => alias.startsWith(query)) ?? - aliases.find((alias) => alias.includes(query)) ?? - emoji.name; - const relevance = !query - ? 0 - : matchedAlias === query - ? 80 - : matchedAlias.startsWith(query) - ? 40 - : 0; - const frequency = normalized.get(emoji.shortcode) ?? 0; - const usage = - maxFrequency > 0 - ? (15 * Math.log1p(frequency)) / Math.log1p(maxFrequency) - : 0; - - return { - apply(view, completion, from, to) { - view.dispatch({ - annotations: pickedCompletion.of(completion), - changes: { from, insert: `${emoji.shortcode} `, to }, - selection: EditorSelection.cursor( - from + emoji.shortcode.length + 1, - ), - }); - onEmojiSelect(emoji.shortcode); - }, - boost: relevance + usage, - displayLabel: emoji.shortcode, - label: `:${matchedAlias}:`, - type: "text", - frequency, - relevance, - } satisfies Completion & { frequency: number; relevance: number }; - }) - .sort( - (a, b) => - b.relevance - a.relevance || - b.frequency - a.frequency || - (a.displayLabel ?? a.label).localeCompare(b.displayLabel ?? b.label), - ); - - return { - from: token.from, - options, - validFor: /^:[a-z0-9_+-]*$/i, - }; - }; -} - -export const emojiCompletionSource = createEmojiCompletionSource(); - /** * Executes buildDecorations. * @param view Parameter view. @@ -827,10 +370,7 @@ function buildDecorations(view: EditorView): DecorationSet { function addHiddenToken( builder: Range[], selections: ReadonlyArray<{ from: number; to: number }>, - token: { - from: number; - to: number; - }, + token: TokenRange, ): void { if (token.from >= token.to) return; diff --git a/packages/markdown/src/markdown.tsx b/packages/markdown/src/markdown.tsx index f8684c4..c3a17a6 100644 --- a/packages/markdown/src/markdown.tsx +++ b/packages/markdown/src/markdown.tsx @@ -1,18 +1,7 @@ -import { - Fragment, - useEffect, - useRef, - useState, - type ReactElement, - type ReactNode, -} from "react"; -import Emoji from "./emoji"; -import { findEmojiShortcodes } from "./emojiData"; -import { Check } from "lucide-react"; +import * as React from "react"; type InlineNode = | { type: "text"; value: string } - | { type: "emoji"; shortcode: string } | { type: "strong"; value: string } | { type: "em"; value: string } | { type: "del"; value: string } @@ -31,11 +20,43 @@ type InlineTokenRange = { to: number; }; +type ParagraphBlock = { + type: "paragraph"; + text: string; +}; + +type HeadingBlock = { + type: "heading"; + level: number; + text: string; +}; + +type HrBlock = { + type: "hr"; +}; + +type BlockQuoteBlock = { + type: "blockquote"; + text: string; +}; + +type CodeBlock = { + type: "code"; + language: string; + code: string; +}; + type ListItem = { text: string; checked: boolean | null; }; +type ListBlock = { + type: "list"; + ordered: boolean; + items: ListItem[]; +}; + type TableBlock = { type: "table"; headers: string[]; @@ -43,123 +64,23 @@ type TableBlock = { }; type MarkdownBlock = - | { - type: "paragraph"; - text: string; - } - | { - type: "heading"; - level: number; - text: string; - } - | { - type: "hr"; - } - | { - type: "blockquote"; - text: string; - } - | { - type: "code"; - language: string; - code: string; - } - | { - type: "list"; - ordered: boolean; - items: ListItem[]; - } + | ParagraphBlock + | HeadingBlock + | HrBlock + | BlockQuoteBlock + | CodeBlock + | ListBlock | TableBlock; const INLINE_TOKEN_REGEX = - /!\[([^\]]*)\]\(([^)\s]+(?:\s+"[^"]*")?)\)|\[([^\]]+)\]\(([^)\s]+(?:\s+"[^"]*")?)\)|`([^`\n]+)`|~~([^~\n]+)~~|\*\*([^*\n]+)\*\*|__([^_\n]+)__|\*([^*\n]+)\*|(? - - - ); -} - -function CopyableCode({ - block = false, - language, - value, -}: { - block?: boolean; - language?: string; - value: string; -}) { - const [copied, setCopied] = useState(false); - const copiedTimer = useRef | undefined>( - undefined, - ); - - useEffect( - () => () => { - clearTimeout(copiedTimer.current); - }, - [], - ); - - async function copy() { - await navigator.clipboard.writeText(value); - setCopied(true); - clearTimeout(copiedTimer.current); - copiedTimer.current = setTimeout(() => setCopied(false), 1200); - } - - const code = ( - void copy()} - onKeyDown={(event) => { - if (event.key !== "Enter" && event.key !== " ") return; - event.preventDefault(); - void copy(); - }} - > - {value} - - ); - - if (block) { - return ( -
-
{code}
- -
- ); - } - - return ( - <> - {code} - - - ); -} + /!\[([^\]]*)\]\(([^)\s]+(?:\s+"[^"]*")?)\)|\[([^\]]+)\]\(([^)\s]+(?:\s+"[^"]*")?)\)|`([^`\n]+)`|~~([^~\n]+)~~|\*\*([^*\n]+)\*\*|__([^_\n]+)__|\*([^*\n]+)\*|_([^_\n]+)_/g; /** * Executes parseInlineNodes. * @param input Parameter input. * @returns InlineNode[]. */ -function parseInlineNodes(input: string): InlineNode[] { +export function parseInlineNodes(input: string): InlineNode[] { const nodes: InlineNode[] = []; let cursor = 0; @@ -170,7 +91,7 @@ function parseInlineNodes(input: string): InlineNode[] { const raw = match[0]; if (index > cursor) { - nodes.push(...parseEmojiText(input.slice(cursor, index))); + nodes.push({ type: "text", value: input.slice(cursor, index) }); } if (match[1] !== undefined && match[2] !== undefined) { @@ -190,38 +111,18 @@ function parseInlineNodes(input: string): InlineNode[] { } else if (match[9] !== undefined || match[10] !== undefined) { nodes.push({ type: "em", value: match[9] ?? match[10] ?? "" }); } else { - nodes.push(...parseEmojiText(raw)); + nodes.push({ type: "text", value: raw }); } cursor = index + raw.length; match = INLINE_TOKEN_REGEX.exec(input); } - if (cursor < input.length) { - nodes.push(...parseEmojiText(input.slice(cursor))); - } - - INLINE_TOKEN_REGEX.lastIndex = 0; - return nodes; -} - -function parseEmojiText(input: string): InlineNode[] { - const nodes: InlineNode[] = []; - let cursor = 0; - - for (const match of findEmojiShortcodes(input)) { - if (match.from > cursor) { - nodes.push({ type: "text", value: input.slice(cursor, match.from) }); - } - - nodes.push({ type: "emoji", shortcode: match.emoji.shortcode }); - cursor = match.to; - } - if (cursor < input.length) { nodes.push({ type: "text", value: input.slice(cursor) }); } + INLINE_TOKEN_REGEX.lastIndex = 0; return nodes; } @@ -468,22 +369,16 @@ export function parseMarkdownBlocks(markdown: string): MarkdownBlock[] { * @param nodes Parameter nodes. * @returns React.ReactNode[]. */ -function renderInline(nodes: InlineNode[]): ReactNode[] { +export function renderInline(nodes: InlineNode[]): React.ReactNode[] { return nodes.map((node, index) => { if (node.type === "text") { return node.value; } - if (node.type === "emoji") { - return ( - - ); - } - if (node.type === "strong") { return ( - {renderInline(parseEmojiText(node.value))} + {node.value} ); } @@ -491,7 +386,7 @@ function renderInline(nodes: InlineNode[]): ReactNode[] { if (node.type === "em") { return ( - {renderInline(parseEmojiText(node.value))} + {node.value} ); } @@ -499,13 +394,17 @@ function renderInline(nodes: InlineNode[]): ReactNode[] { if (node.type === "del") { return ( - {renderInline(parseEmojiText(node.value))} + {node.value} ); } if (node.type === "code") { - return ; + return ( + + {node.value} + + ); } if (node.type === "link") { @@ -517,7 +416,7 @@ function renderInline(nodes: InlineNode[]): ReactNode[] { target="_blank" rel="noreferrer" > - {renderInline(parseEmojiText(node.label))} + {node.label} ); } @@ -540,7 +439,7 @@ function renderInline(nodes: InlineNode[]): ReactNode[] { * @param blocks Parameter blocks. * @returns React.ReactElement. */ -export function renderBlocks(blocks: MarkdownBlock[]): ReactElement { +export function renderBlocks(blocks: MarkdownBlock[]): React.ReactElement { return ( <> {blocks.map((block, blockIndex) => { @@ -595,12 +494,11 @@ export function renderBlocks(blocks: MarkdownBlock[]): ReactElement { if (block.type === "code") { return ( - +
+              
+                {block.code}
+              
+            
); } @@ -662,12 +560,12 @@ export function renderBlocks(blocks: MarkdownBlock[]): ReactElement { } return ( -

+

{block.text.split("\n").map((line, lineIndex) => ( - + {lineIndex > 0 ?
: null} {renderInline(parseInlineNodes(line))} -
+ ))}

); @@ -738,8 +636,8 @@ function readTable( }; } -const markdownStyles = ` -.tm-md-root { color: var(--foreground); line-height: 1.65; font-size: 1rem; } +export const markdownStyles = ` +.tm-md-root { color: hsl(var(--foreground)); line-height: 1.55; font-size: 0.95rem; } .tm-md-heading { margin: 0.2rem 0 0.35rem; font-weight: 700; line-height: 1.25; } .tm-md-h1 { font-size: 1.65rem; } .tm-md-h2 { font-size: 1.45rem; } @@ -747,49 +645,34 @@ const markdownStyles = ` .tm-md-h4 { font-size: 1.1rem; } .tm-md-h5 { font-size: 1rem; } .tm-md-h6 { font-size: 0.95rem; opacity: 0.9; } +.tm-md-p { margin: 0.25rem 0; } .tm-md-blockquote { margin: 0.45rem 0; padding-left: 0.75rem; opacity: 0.95; } .tm-md-blockquote p { margin: 0.2rem 0; } -.tm-md-pre { margin: 0.45rem 0; padding: 0.65rem 0.75rem; border-radius: 0.5rem; background: var(--muted); overflow-x: auto; } -.tm-md-code, .tm-md-codeblock { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 0.87em; cursor: pointer; } -.tm-md-code { padding: 0.08rem 0.32rem; border: 1px solid var(--border); border-radius: 0.28rem; background: var(--muted); } -.tm-md-codeblock { display: block; } -.tm-md-code:focus-visible, .tm-md-codeblock:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; } +.tm-md-pre { margin: 0.45rem 0; padding: 0.65rem 0.75rem; border-radius: 0.5rem; background: hsl(var(--muted)); overflow-x: auto; } +.tm-md-code, .tm-md-codeblock { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } +.tm-md-code { padding: 0.08rem 0.32rem; border-radius: 0.28rem; background: hsl(var(--muted)); } .tm-md-strong { font-weight: 700; } .tm-md-em { font-style: italic; } .tm-md-del { text-decoration: line-through; } -.tm-md-link { color: var(--primary); text-decoration: underline; text-underline-offset: 0.14rem; } +.tm-md-link { color: hsl(var(--primary)); text-decoration: underline; text-underline-offset: 0.14rem; } .tm-md-image { display: block; max-width: 100%; border-radius: 0.4rem; margin: 0.5rem 0; } -.tm-md-emoji { display: inline-block; width: 1.15em; height: 1.15em; vertical-align: -0.18em; } .tm-md-ul, .tm-md-ol { margin: 0.3rem 0 0.35rem 1.2rem; padding: 0; } .tm-md-li { margin: 0.2rem 0; } .tm-md-checkbox { margin-right: 0.5rem; vertical-align: middle; } .tm-md-table-wrap { overflow-x: auto; margin: 0.45rem 0; } .tm-md-table { border-collapse: collapse; width: 100%; min-width: 16rem; } .tm-md-table th, .tm-md-table td { padding: 0.4rem 0.5rem; text-align: left; } -.tm-md-table th { background: var(--muted); font-weight: 600; } +.tm-md-table th { background: hsl(var(--muted)); font-weight: 600; } .tm-md-hr { margin: 0.55rem 0; } -.cm-editor.tm-md-editor { border-radius: inherit; background: transparent; caret-color: var(--foreground); } +.cm-editor.tm-md-editor { border-radius: 0.65rem; background: hsl(var(--card)); caret-color: var(--foreground); } .cm-editor.tm-md-editor.cm-focused { outline: none; box-shadow: none; } .cm-editor.tm-md-editor .cm-scroller { font-family: inherit; line-height: 1.55; max-height: 30vh; overflow-y: auto; overflow-x: hidden; } .cm-editor.tm-md-editor .cm-content { caret-color: var(--foreground); } -.cm-editor.tm-md-editor .cm-content { padding: var(--tm-md-content-padding, 0.25rem 0.625rem); min-height: 2rem; } -.cm-editor.tm-md-editor .cm-line { padding: 0; color: var(--foreground); } -.cm-editor.tm-md-editor .tm-md-editor-emoji { display: inline-block; width: 1.15em; height: 1.15em; vertical-align: -0.18em; object-fit: contain; pointer-events: none; } +.cm-editor.tm-md-editor .cm-content { padding: 0.7rem 0.85rem; min-height: 2.75rem; } +.cm-editor.tm-md-editor .cm-line { padding: 0 1px; color: hsl(var(--foreground)); } .cm-editor.tm-md-editor .tm-md-hidden-token { color: transparent; opacity: 0; font-size: inherit; } -.cm-editor.tm-md-editor .tm-md-code-line { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; background: var(--muted); border-radius: 0.3rem; } -.cm-tooltip.cm-tooltip-autocomplete { min-width: 18rem; max-width: min(26rem, calc(100vw - 1rem)); overflow: hidden; border: 1px solid var(--border); border-radius: var(--radius); background: var(--popover); color: var(--popover-foreground); box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); font-family: "Public Sans Variable", sans-serif; font-size: 0.875rem; } -.cm-editor.tm-md-editor .cm-tooltip.cm-tooltip-autocomplete > ul { max-height: min(20rem, 45vh); padding: 0.25rem; font-family: "Public Sans Variable", sans-serif; scrollbar-width: thin; scrollbar-color: var(--border) transparent; } -.cm-tooltip.cm-tooltip-autocomplete > ul::-webkit-scrollbar { width: 6px; height: 6px; } -.cm-tooltip.cm-tooltip-autocomplete > ul::-webkit-scrollbar-track { background: transparent; } -.cm-tooltip.cm-tooltip-autocomplete > ul::-webkit-scrollbar-thumb { border-radius: 9999px; background: var(--border); } -.cm-tooltip.cm-tooltip-autocomplete > ul > li { display: flex; min-height: 2.25rem; align-items: center; border-radius: calc(var(--radius) * 0.8); padding: 0.3rem 0.5rem; color: var(--popover-foreground); } -.cm-tooltip.cm-tooltip-autocomplete > ul > li:hover, -.cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected] { background: var(--accent); color: var(--accent-foreground); } -.cm-tooltip.cm-tooltip-autocomplete .cm-completionIcon { display: none; } -.cm-tooltip.cm-tooltip-autocomplete .cm-completionLabel { overflow: hidden; text-overflow: ellipsis; } -.cm-tooltip.cm-tooltip-autocomplete .cm-completionMatchedText { color: inherit; text-decoration: none; font-weight: 600; } -.cm-tooltip-autocomplete .tm-md-completion-emoji { display: inline-block; width: 1.35rem; height: 1.35rem; flex: 0 0 auto; margin-right: 0.5rem; vertical-align: middle; } +.cm-editor.tm-md-editor .tm-md-code-line { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; background: hsl(var(--muted)); border-radius: 0.3rem; } `; /** @@ -801,15 +684,10 @@ export function ensureMarkdownStyles(): void { if (typeof document === "undefined") return; const styleId = "tensamin-markdown-styles"; - let style = document.getElementById(styleId) as HTMLStyleElement | null; + if (document.getElementById(styleId)) return; - if (!style) { - style = document.createElement("style"); - style.id = styleId; - document.head.appendChild(style); - } - - if (style.textContent !== markdownStyles) { - style.textContent = markdownStyles; - } + const style = document.createElement("style"); + style.id = styleId; + style.textContent = markdownStyles; + document.head.appendChild(style); } diff --git a/packages/markdown/src/text.tsx b/packages/markdown/src/text.tsx index ca65fe0..07e1bb8 100644 --- a/packages/markdown/src/text.tsx +++ b/packages/markdown/src/text.tsx @@ -1,4 +1,4 @@ -import { useMemo, type CSSProperties } from "react"; +import * as React from "react"; import { ensureMarkdownStyles, @@ -8,7 +8,6 @@ import { export type TextProps = { value: string; - fontSize?: CSSProperties["fontSize"]; }; /** @@ -19,12 +18,10 @@ export type TextProps = { export default function Text(props: TextProps) { ensureMarkdownStyles(); - const blocks = useMemo(() => parseMarkdownBlocks(props.value), [props.value]); - const renderedBlocks = useMemo(() => renderBlocks(blocks), [blocks]); - - return ( -
- {renderedBlocks} -
+ const blocks = React.useMemo( + () => parseMarkdownBlocks(props.value), + [props.value], ); + + return
{renderBlocks(blocks)}
; } diff --git a/packages/mtp/package.json b/packages/mtp/package.json deleted file mode 100644 index 44f5750..0000000 --- a/packages/mtp/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "@tensamin/mtp", - "private": true, - "version": "0.0.0", - "type": "module", - "exports": { - ".": "./src/index.ts" - }, - "scripts": { - "format": "pnpm exec prettier --write .", - "lint": "eslint src --ext .ts,.tsx", - "test": "vitest run --passWithNoTests", - "build": "pnpm run test && tsc -p tsconfig.json --noEmit" - }, - "dependencies": { - "@methanium/ui": "*", - "@tauri-apps/api": "^2.11.1", - "@tensamin/shared": "workspace:*", - "@tensamin/storage": "workspace:*", - "mtp": "*", - "react": "^19.2.8" - }, - "devDependencies": { - "eslint": "^10.8.0" - } -} diff --git a/packages/mtp/src/browser.tsx b/packages/mtp/src/browser.tsx deleted file mode 100644 index 755a021..0000000 --- a/packages/mtp/src/browser.tsx +++ /dev/null @@ -1,529 +0,0 @@ -import { type ReactNode, useEffect, useMemo, useRef, useState } from "react"; -import { toast as sonnerToast } from "@methanium/ui"; -import { base64ToBytes, ConnectionState, MTPClient } from "mtp"; -import createAsyncQueue from "@tensamin/shared/asyncQueue"; -import { - mtp as mtpSchemas, - type Calls, - type Communities, - type Contacts, -} from "@tensamin/shared/data"; -import { log } from "@tensamin/shared/log"; -import { useStorage } from "@tensamin/storage/context"; - -import { - type BoundSendFn, - MTPContext, - type MTPContextType, - type ProtocolMessage, - removeMissingContacts, - useMessageHandlers, -} from "./mtpContext"; -import { - DISCOVERY_TIMEOUT, - INITIAL_SYNC_TIMEOUT, - RECONNECT_JITTER, - RECONNECT_LONG_INTERVAL, - RECONNECT_RESET, - RECONNECT_TRIES, - RETRY_INTERVAL, - STATE_ACK_TIMEOUT, -} from "./values"; - -type BrowserMtpClient = Awaited>; - -function createBrowserClient( - options: Omit[0], "schemas">, -) { - return MTPClient.create({ - ...options, - schemas: mtpSchemas, - throwProtocolErrors: true, - onValidationError: (error) => { - log(1, "mtp", "red", "Failed to validate push message", error); - }, - }); -} - -function abortError(signal: AbortSignal): Error { - return signal.reason instanceof Error - ? signal.reason - : new Error("Initial state synchronization was cancelled"); -} - -function withDeadline( - promise: Promise, - timeoutMs: number, - timeoutMessage: string, - signal: AbortSignal, -): Promise { - return new Promise((resolve, reject) => { - if (signal.aborted) { - reject(abortError(signal)); - return; - } - let settled = false; - const finish = (complete: () => void) => { - if (settled) return; - settled = true; - clearTimeout(timeout); - signal.removeEventListener("abort", onAbort); - complete(); - }; - const timeout = setTimeout( - () => finish(() => reject(new Error(timeoutMessage))), - timeoutMs, - ); - const onAbort = () => finish(() => reject(abortError(signal))); - signal.addEventListener("abort", onAbort, { once: true }); - promise.then( - (value) => finish(() => resolve(value)), - (error: unknown) => finish(() => reject(error)), - ); - }); -} - -async function completeInitialSynchronization( - client: BrowserMtpClient, - subscribe: MTPContextType["subscribe"], - signal: AbortSignal, - syncTimeoutMs = INITIAL_SYNC_TIMEOUT, - ackTimeoutMs = STATE_ACK_TIMEOUT, -): Promise> { - const stateSync = new Promise>( - (resolve, reject) => { - let unsubscribeStateSync = () => {}; - let unsubscribeNoIota = () => {}; - const cleanup = () => { - clearTimeout(timeout); - unsubscribeStateSync(); - unsubscribeNoIota(); - signal.removeEventListener("abort", onAbort); - }; - const onAbort = () => { - cleanup(); - reject(abortError(signal)); - }; - const timeout = setTimeout(() => { - cleanup(); - reject(new Error("Initial state synchronization timed out")); - }, syncTimeoutMs); - signal.addEventListener("abort", onAbort, { once: true }); - if (signal.aborted) { - onAbort(); - return; - } - unsubscribeStateSync = subscribe("ClientStateSync", (message) => { - cleanup(); - resolve(message); - }); - unsubscribeNoIota = subscribe("ErrorNoIota", () => { - cleanup(); - reject(new Error("No Iota is currently connected")); - }); - }, - ); - const [, state] = await Promise.all([ - withDeadline( - client.auth(), - syncTimeoutMs, - "MTP authentication timed out", - signal, - ), - stateSync, - ]); - await withDeadline( - client.request("ClientStateAck", { - SessionId: state.data.SessionId, - VersionNumber: state.data.VersionNumber, - }), - ackTimeoutMs, - "State acknowledgement timed out", - signal, - ); - if (signal.aborted) throw abortError(signal); - return state; -} - -function protocolErrorDetails(error: unknown) { - if (typeof error !== "object" || error === null || !("type" in error)) { - return null; - } - const protocolError = error as { - id?: unknown; - type?: unknown; - frame?: unknown; - }; - return { - id: protocolError.id, - type: protocolError.type, - frame: protocolError.frame, - }; -} - -export function BrowserProvider(props: { - children: ReactNode; - blockConnection?: boolean; -}) { - const { load } = useStorage(); - const [readyState, setReadyState] = useState( - ConnectionState.Disconnected, - ); - const [identified, setIdentified] = useState(false); - const [identifying, setIdentifying] = useState(false); - const [freshCommunities, setFreshCommunities] = useState([]); - const [freshContacts, setFreshContacts] = useState([]); - const [freshCalls, setFreshCalls] = useState([]); - const clientRef = useRef(null); - const { addInterceptor, attachSubscriptions, interceptorsRef, subscribe } = - useMessageHandlers(); - const connected = readyState === ConnectionState.Connected; - - const [mtpUrl, setMtpUrl] = useState(null); - useEffect(() => { - load("omega_url").then(setMtpUrl); - }, [load]); - - const send: BoundSendFn = useMemo( - () => async (type, data, options) => { - const client = clientRef.current; - if (!client) throw new Error("mtp is not connected"); - const response = await client.request(type, data, options); - if (response.type === "GetStates") { - setFreshContacts((contacts) => - removeMissingContacts( - contacts, - response as ProtocolMessage<"GetStates">, - ), - ); - } - return response; - }, - [], - ); - - const resolveConnectionRef = useRef(() => {}); - useEffect(() => { - if (!mtpUrl) return; - let attempts = 0; - let reconnectTimer: ReturnType | null = null; - let reconnectResetTimer: ReturnType | null = null; - let reconnectScheduled = false; - let disposed = false; - let connectionGeneration = 0; - let cleanupConnection = () => {}; - - const clearReconnectTimer = () => { - if (!reconnectTimer) return; - clearTimeout(reconnectTimer); - reconnectTimer = null; - reconnectScheduled = false; - }; - const clearReconnectResetTimer = () => { - if (!reconnectResetTimer) return; - clearTimeout(reconnectResetTimer); - reconnectResetTimer = null; - }; - const scheduleReconnect = (error: unknown) => { - if (disposed || reconnectScheduled) return; - attempts += 1; - const shortRetry = attempts <= RECONNECT_TRIES; - if (!shortRetry) { - log(0, "mtp", "red", "Reconnection attempts exhausted", error); - sonnerToast.error("Connection failed", { - id: "mtp-connection-toast", - description: - error instanceof Error - ? `${error.message.split(":")[0]}. Retrying in the background.` - : "Connection lost. Retrying in the background.", - icon: null, - duration: Infinity, - closeButton: true, - promise: null, - } as unknown as Parameters[1]); - } else { - sonnerToast.loading( - `Reconnecting to server... (attempt ${attempts} of ${RECONNECT_TRIES})`, - { id: "mtp-connection-toast" }, - ); - } - const baseDelay = shortRetry ? RETRY_INTERVAL : RECONNECT_LONG_INTERVAL; - const jitter = 1 + (Math.random() * 2 - 1) * RECONNECT_JITTER; - reconnectScheduled = true; - reconnectTimer = setTimeout( - () => { - reconnectScheduled = false; - reconnectTimer = null; - void connect(); - }, - Math.round(baseDelay * jitter), - ); - }; - - async function connect() { - if (disposed || props.blockConnection) return; - const generation = ++connectionGeneration; - let client: BrowserMtpClient | null = null; - let failed = false; - let connectionReady = false; - let detachSubscriptions = () => {}; - let unsubscribeNoIota = () => {}; - const attemptAbort = new AbortController(); - const cleanup = () => { - attemptAbort.abort( - new Error("Initial state synchronization was cancelled"), - ); - unsubscribeNoIota(); - detachSubscriptions(); - client?.disconnect(); - if (clientRef.current === client) clientRef.current = null; - clearReconnectResetTimer(); - if (generation === connectionGeneration) { - setReadyState(ConnectionState.Disconnected); - setIdentified(false); - setIdentifying(false); - } - }; - cleanupConnection = cleanup; - try { - setIdentified(false); - setIdentifying(false); - const [userId, keyring] = await Promise.all([ - load("user_id"), - load("mtp_keyring"), - ]); - if (!userId || !keyring) throw new Error("Missing login credentials"); - const forcedOmikronUrl = await load("forced_omikron_url"); - const forcedOmikronPublicKey = await load("forced_omikron_public_key"); - let url = null; - let omikronPublicKey = null; - if (forcedOmikronUrl && forcedOmikronPublicKey) { - url = forcedOmikronUrl; - omikronPublicKey = forcedOmikronPublicKey; - } else { - log(2, "mtp", "purple", "Fetching Omikron data."); - const data = await fetch(`${mtpUrl}api/get/omikron/${userId}`, { - signal: AbortSignal.any([ - attemptAbort.signal, - AbortSignal.timeout(DISCOVERY_TIMEOUT), - ]), - }); - if (data.status === 404) { - throw new Error("No Omikron assignment is currently available"); - } - if (!data.ok) - throw new Error(`Omikron discovery failed: HTTP ${data.status}`); - const omikronData = (await data.json()) as { - ip_address: string; - port: number; - public_key: string; - }; - if ( - !omikronData.ip_address || - !omikronData.port || - !omikronData.public_key - ) { - throw new Error("Invalid Omikron data"); - } - url = `https://${omikronData.ip_address}:${omikronData.port}`; - omikronPublicKey = omikronData.public_key; - } - if (!url || !omikronPublicKey) { - throw new Error("Missing Omikron URL or Public Key"); - } - log(2, "mtp", "green", "Connecting to: " + url); - client = await createBrowserClient({ - url, - credentials: { clientId: userId, keyring: base64ToBytes(keyring) }, - hostPublicKey: { value: omikronPublicKey, encoding: "base64" }, - descriptor: "client", - pings: true, - logger: (event) => { - if (event.type === "state") { - if (generation !== connectionGeneration) return; - const state = client?.state ?? ConnectionState.Disconnected; - setReadyState(state); - if ( - state === ConnectionState.Disconnected && - clientRef.current === client && - !failed - ) { - failed = true; - const error = new Error("MTP connection lost"); - attemptAbort.abort(error); - if (connectionReady) { - cleanup(); - scheduleReconnect(error); - } - } - } - if (event.type !== "Pong" && event.type !== "Ping") { - log( - 2, - "mtp", - event.type === "state" - ? "purple" - : event.direction === "recv" - ? "cyan" - : event.direction === "send" - ? "gray" - : "blue", - event.type === "state" - ? event.data - : event.direction === "recv" - ? "< " + event.type - : event.direction === "send" - ? "> " + event.type - : event.type, - event, - ); - } - }, - }); - if (disposed || generation !== connectionGeneration) { - client.disconnect(); - return; - } - const activeClient = client; - clientRef.current = activeClient; - detachSubscriptions = attachSubscriptions(activeClient); - unsubscribeNoIota = subscribe("ErrorNoIota", () => { - if (clientRef.current !== activeClient || failed) return; - failed = true; - const error = new Error("No Iota is currently connected"); - attemptAbort.abort(error); - cleanup(); - scheduleReconnect(error); - }); - setReadyState(activeClient.state); - setIdentifying(true); - const finalResponse = await completeInitialSynchronization( - activeClient, - subscribe, - attemptAbort.signal, - ); - if (disposed || clientRef.current !== activeClient) return; - setFreshContacts(finalResponse.data.Contacts); - setFreshCommunities(finalResponse.data.Communities); - setFreshCalls(finalResponse.data.Calls); - connectionReady = true; - setIdentifying(false); - setIdentified(true); - clearReconnectTimer(); - clearReconnectResetTimer(); - reconnectResetTimer = setTimeout(() => { - attempts = 0; - reconnectResetTimer = null; - }, RECONNECT_RESET * 1_000); - resolveConnectionRef.current?.(); - } catch (connectError) { - if (disposed || generation !== connectionGeneration) { - client?.disconnect(); - return; - } - failed = true; - cleanup(); - const message = - connectError instanceof Error - ? connectError.message - : String(connectError ?? "Unknown error"); - log( - 0, - "mtp", - "red", - `Connection/authentication attempt failed: ${message}`, - protocolErrorDetails(connectError) ?? connectError, - ); - scheduleReconnect(connectError); - } - } - - void connect(); - return () => { - disposed = true; - clearReconnectTimer(); - clearReconnectResetTimer(); - cleanupConnection(); - setReadyState(ConnectionState.Disconnected); - setIdentified(false); - setIdentifying(false); - sonnerToast.dismiss("mtp-connection-toast"); - }; - }, [attachSubscriptions, load, mtpUrl, props.blockConnection, subscribe]); - - useEffect(() => { - return subscribe("ErrorNoIota", () => { - setIdentified(false); - setIdentifying(false); - sonnerToast.error("We couldn't reach your Iota", { - description: - "Check your network connection and try restarting your Iota", - icon: null, - duration: Infinity, - closeButton: true, - }); - resolveConnectionRef.current?.(); - }); - }, [subscribe]); - - useEffect( - () => - subscribe("GetStates", (message) => { - setFreshContacts((contacts) => - removeMissingContacts(contacts, message), - ); - }), - [subscribe], - ); - - const loadingDescription = useMemo(() => { - if (!mtpUrl) return "Loading connection details"; - if (readyState === ConnectionState.Connecting || !connected) { - return "Establishing transport channel"; - } - if (identifying || !identified) return "Waiting for authenticated session"; - return "Loading..."; - }, [connected, identified, identifying, readyState, mtpUrl]); - const contextReady = connected && identified && mtpUrl !== null; - const mtpRef = useMemo(() => createAsyncQueue<{ send: typeof send }>(), []); - useEffect(() => { - if (connected && identified && mtpUrl) { - mtpRef.set({ send }); - } - }, [connected, identified, mtpUrl, send, mtpRef]); - - const sendQueued: BoundSendFn = useMemo( - () => async (type, data, options) => { - const mtp = await mtpRef.get(); - const response = await mtp.send(type, data, options); - for (const interceptor of interceptorsRef.current) { - void Promise.resolve(interceptor({ type, data, response })).catch( - (error) => { - log(1, "mtp", "yellow", "MTP interceptor failed", error, { type }); - }, - ); - } - return response; - }, - [interceptorsRef, mtpRef], - ); - - return ( - - {props.children} - - ); -} diff --git a/packages/mtp/src/context.tsx b/packages/mtp/src/context.tsx deleted file mode 100644 index c07579f..0000000 --- a/packages/mtp/src/context.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { type ReactNode, useContext, useEffect, useState } from "react"; -import { isTauri } from "@tauri-apps/api/core"; -import { MTPClient } from "mtp"; - -import { BrowserProvider } from "./browser"; -import { MTPContext, type MTPContextType } from "./mtpContext"; -import { TauriProvider } from "./tauri"; - -export function Provider(props: { - children: ReactNode; - blockConnection?: boolean; -}) { - if (isTauri()) return ; - return ; -} - -function BrowserWasmProvider(props: { - children: ReactNode; - blockConnection?: boolean; -}) { - const [wasmReady, setWasmReady] = useState(false); - const [wasmError, setWasmError] = useState(); - useEffect(() => { - let active = true; - void MTPClient.init().then( - () => active && setWasmReady(true), - (error: unknown) => active && setWasmError(() => error), - ); - return () => { - active = false; - }; - }, []); - if (wasmError) throw wasmError; - return wasmReady ? : null; -} - -export function useMTP(): MTPContextType { - const context = useContext(MTPContext); - if (!context) throw new Error("useMTP must be used within an MTPProvider"); - return context; -} diff --git a/packages/mtp/src/index.ts b/packages/mtp/src/index.ts deleted file mode 100644 index a4f05c8..0000000 --- a/packages/mtp/src/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { Provider, useMTP } from "./context"; -export type { - BoundSendFn, - MTPExchange, - MTPInterceptor, - ProtocolMessage, -} from "./mtpContext"; diff --git a/packages/mtp/src/mtpContext.tsx b/packages/mtp/src/mtpContext.tsx deleted file mode 100644 index 1f1654d..0000000 --- a/packages/mtp/src/mtpContext.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { createContext, useCallback, useRef } from "react"; -import type { - MTPRequestFunction, - MTPResponseFrame, - MTPSubscriptionFunction, -} from "mtp"; -import { - mtp as mtpSchemas, - type Calls, - type Communities, - type Contacts, -} from "@tensamin/shared/data"; -import { log } from "@tensamin/shared/log"; - -export type ProtocolMessage< - Type extends keyof typeof mtpSchemas & string = keyof typeof mtpSchemas & - string, -> = MTPResponseFrame; - -export type BoundSendFn = MTPRequestFunction; - -export type MTPExchange = { - type: keyof typeof mtpSchemas & string; - data: unknown; - response: ProtocolMessage; -}; - -export type MTPInterceptor = (exchange: MTPExchange) => void | Promise; - -export type MTPContextType = { - send: BoundSendFn; - subscribe: MTPSubscriptionFunction; - addInterceptor: (interceptor: MTPInterceptor) => () => void; - readyState: number; - identified: boolean; - freshContacts: Contacts; - freshCommunities: Communities; - freshCalls: Calls; - contextReady: boolean; - loadingDescription: string; -}; - -export const MTPContext = createContext(undefined); - -export function removeMissingContacts( - contacts: Contacts, - message: ProtocolMessage<"GetStates">, -): Contacts { - const missing = new Set(message.data.MissingUserIds ?? []); - return contacts.filter((contact) => !missing.has(contact.UserId)); -} - -export function useMessageHandlers() { - const interceptorsRef = useRef(new Set()); - const subscriptionHandlersRef = useRef( - new Map void | Promise>>(), - ); - const transportRef = useRef<{ - subscribe: MTPSubscriptionFunction; - } | null>(null); - const transportGenerationRef = useRef(0); - const transportUnsubscribersRef = useRef(new Map void>()); - const lastInitialStateRef = useRef | null>(null); - - const attachType = useCallback( - (type: Type) => { - const transport = transportRef.current; - if (!transport || transportUnsubscribersRef.current.has(type)) return; - const generation = transportGenerationRef.current; - const unsubscribe = transport.subscribe(type, (message) => { - if ( - transportRef.current !== transport || - transportGenerationRef.current !== generation - ) - return; - if (type === "GetStates") { - lastInitialStateRef.current = message as ProtocolMessage<"GetStates">; - } - for (const handler of [ - ...(subscriptionHandlersRef.current.get(type) ?? []), - ]) { - void Promise.resolve(handler(message as ProtocolMessage)).catch( - (error) => { - log(1, "mtp", "red", "Subscription handler failed", error, { - type, - }); - }, - ); - } - }); - transportUnsubscribersRef.current.set(type, unsubscribe); - }, - [], - ); - - const attachSubscriptions = useCallback( - (transport: { subscribe: MTPSubscriptionFunction }) => { - for (const unsubscribe of transportUnsubscribersRef.current.values()) { - unsubscribe(); - } - transportUnsubscribersRef.current.clear(); - transportRef.current = transport; - const generation = ++transportGenerationRef.current; - for (const type of subscriptionHandlersRef.current.keys()) { - attachType(type as keyof typeof mtpSchemas & string); - } - return () => { - if ( - transportRef.current !== transport || - transportGenerationRef.current !== generation - ) - return; - transportRef.current = null; - transportGenerationRef.current += 1; - for (const unsubscribe of transportUnsubscribersRef.current.values()) { - unsubscribe(); - } - transportUnsubscribersRef.current.clear(); - }; - }, - [attachType], - ); - - const subscribe = useCallback>( - (type, handler) => { - const handlers = subscriptionHandlersRef.current.get(type) ?? new Set(); - const untypedHandler = handler as ( - message: ProtocolMessage, - ) => void | Promise; - handlers.add(untypedHandler); - subscriptionHandlersRef.current.set(type, handlers); - attachType(type); - const initialState = lastInitialStateRef.current; - if (type === "GetStates" && initialState) { - void Promise.resolve(untypedHandler(initialState)).catch( - () => undefined, - ); - } - return () => { - handlers.delete(untypedHandler); - if (handlers.size !== 0) return; - subscriptionHandlersRef.current.delete(type); - transportUnsubscribersRef.current.get(type)?.(); - transportUnsubscribersRef.current.delete(type); - }; - }, - [attachType], - ); - const addInterceptor = useCallback((interceptor: MTPInterceptor) => { - interceptorsRef.current.add(interceptor); - return () => interceptorsRef.current.delete(interceptor); - }, []); - return { - addInterceptor, - attachSubscriptions, - interceptorsRef, - subscribe, - }; -} diff --git a/packages/mtp/src/tauri.tsx b/packages/mtp/src/tauri.tsx deleted file mode 100644 index 92ac209..0000000 --- a/packages/mtp/src/tauri.tsx +++ /dev/null @@ -1,258 +0,0 @@ -import { - type ReactNode, - useCallback, - useEffect, - useRef, - useState, -} from "react"; -import { invoke } from "@tauri-apps/api/core"; -import { listen, type UnlistenFn } from "@tauri-apps/api/event"; -import { - ConnectionState, - MTPProxyConnection, - type MTPFrame, - type MTPProxyAdapter, -} from "mtp"; -import { - mtp as mtpSchemas, - type Calls, - type Communities, - type Contacts, -} from "@tensamin/shared/data"; -import { log } from "@tensamin/shared/log"; - -import { - type BoundSendFn, - MTPContext, - type ProtocolMessage, - removeMissingContacts, - useMessageHandlers, -} from "./mtpContext"; - -type NativeSnapshot = { - generation: number; - readyState: number; - identified: boolean; - state?: unknown; - error?: string; -}; - -function createTauriAdapter() { - const subscriptions = new Map void>>(); - const adapter: MTPProxyAdapter = { - request: (type, data) => - invoke("mtp_request", { typeName: type, data }), - subscribe(type, handler) { - const handlers = subscriptions.get(type) ?? new Set(); - handlers.add(handler); - subscriptions.set(type, handlers); - return () => { - handlers.delete(handler); - if (handlers.size === 0) subscriptions.delete(type); - }; - }, - }; - return { - adapter, - dispatch(message: MTPFrame) { - for (const handler of subscriptions.get(message.type) ?? []) - handler(message); - }, - }; -} - -export function TauriProvider(props: { - children: ReactNode; - blockConnection?: boolean; -}) { - const [snapshot, setSnapshot] = useState({ - generation: 0, - readyState: ConnectionState.Disconnected, - identified: false, - }); - const [freshContacts, setFreshContacts] = useState([]); - const [freshCommunities, setFreshCommunities] = useState([]); - const [freshCalls, setFreshCalls] = useState([]); - const generationRef = useRef(0); - const { addInterceptor, attachSubscriptions, interceptorsRef, subscribe } = - useMessageHandlers(); - const [{ bridge, connection }] = useState(() => { - const bridge = createTauriAdapter(); - return { - bridge, - connection: new MTPProxyConnection(bridge.adapter, { - schemas: mtpSchemas, - throwProtocolErrors: true, - onValidationError: (error) => { - log(1, "mtp", "red", "Failed to validate native MTP message", error); - }, - }), - }; - }); - - const applySnapshot = useCallback(async (next: NativeSnapshot) => { - if (next.generation < generationRef.current) return; - generationRef.current = next.generation; - if (next.error) - log(0, "android", "orange", "MTP connection failed", next.error); - if (!next.identified) { - setSnapshot(next); - return; - } - if (next.state === undefined) { - setSnapshot({ - ...next, - identified: false, - error: "Native MTP connection omitted initial state", - }); - return; - } - try { - const state = await mtpSchemas.ClientStateSync.response.parseAsync( - next.state, - ); - setFreshContacts(state.Contacts); - setFreshCommunities(state.Communities); - setFreshCalls(state.Calls); - setSnapshot(next); - } catch (error) { - log(0, "mtp", "red", "Invalid native MTP state", error); - setSnapshot({ - ...next, - identified: false, - error: "Invalid ClientStateSync payload", - }); - } - }, []); - - const dispatchMessage = useCallback( - (message: MTPFrame) => { - bridge.dispatch(message); - }, - [bridge], - ); - - useEffect(() => { - return attachSubscriptions(connection); - }, [attachSubscriptions, connection]); - - useEffect( - () => - subscribe("GetStates", (message) => { - setFreshContacts((contacts) => - removeMissingContacts(contacts, message), - ); - }), - [subscribe], - ); - - useEffect(() => { - if (props.blockConnection) return; - let disposed = false; - let unlisten: UnlistenFn | undefined; - void (async () => { - try { - const nextUnlisten = await listen< - | { kind: "state"; snapshot: NativeSnapshot } - | { kind: "message"; generation: number; message: MTPFrame } - | { kind: "log"; level: number; message: string; details?: unknown } - >("mtp://event", ({ payload }) => { - if (disposed) return; - if (payload.kind === "state") { - void applySnapshot(payload.snapshot); - } else if (payload.kind === "message") { - if (payload.generation === generationRef.current) { - dispatchMessage(payload.message); - } - } else { - log( - payload.level, - "android", - "orange", - payload.message, - payload.details, - ); - } - }); - if (disposed) nextUnlisten(); - else unlisten = nextUnlisten; - } catch (error) { - log(0, "mtp", "red", "Failed to subscribe to native MTP events", error); - } - try { - const current = await invoke("mtp_status"); - if (!disposed) await applySnapshot(current); - } catch (error) { - log(0, "mtp", "red", "Failed to load native MTP status", error); - } - })(); - return () => { - disposed = true; - unlisten?.(); - }; - }, [applySnapshot, dispatchMessage, props.blockConnection]); - - useEffect(() => { - if (props.blockConnection) return; - const updateVisibility = () => { - void invoke("mtp_set_ui_visible", { - visible: document.visibilityState === "visible" && document.hasFocus(), - }); - }; - updateVisibility(); - document.addEventListener("visibilitychange", updateVisibility); - window.addEventListener("focus", updateVisibility); - window.addEventListener("blur", updateVisibility); - return () => { - document.removeEventListener("visibilitychange", updateVisibility); - window.removeEventListener("focus", updateVisibility); - window.removeEventListener("blur", updateVisibility); - void invoke("mtp_set_ui_visible", { visible: false }); - }; - }, [props.blockConnection]); - - const send = useCallback( - async (type, data, options) => { - const response = await connection.request(type, data, options); - if (response.type === "GetStates") { - setFreshContacts((contacts) => - removeMissingContacts( - contacts, - response as ProtocolMessage<"GetStates">, - ), - ); - } - for (const interceptor of interceptorsRef.current) { - void Promise.resolve(interceptor({ type, data, response })).catch( - (error) => { - log(1, "mtp", "yellow", "MTP interceptor failed", error, { type }); - }, - ); - } - return response; - }, - [connection, interceptorsRef], - ); - const connected = snapshot.readyState === ConnectionState.Connected; - - return ( - - {props.children} - - ); -} diff --git a/packages/mtp/src/values.ts b/packages/mtp/src/values.ts deleted file mode 100644 index c5ba321..0000000 --- a/packages/mtp/src/values.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const RETRY_INTERVAL = 3_000; -export const RECONNECT_TRIES = 3; -export const RECONNECT_RESET = 6; -export const RECONNECT_LONG_INTERVAL = 60_000; -export const RECONNECT_JITTER = 0.2; -export const DISCOVERY_TIMEOUT = 20_000; -export const INITIAL_SYNC_TIMEOUT = 120_000; -export const STATE_ACK_TIMEOUT = 30_000; diff --git a/packages/mtp/tsconfig.json b/packages/mtp/tsconfig.json deleted file mode 100644 index f456417..0000000 --- a/packages/mtp/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "jsx": "react-jsx", - "module": "ESNext", - "target": "ES2020", - "moduleResolution": "Bundler", - "strict": true, - "skipLibCheck": true, - "noEmit": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true - }, - "include": ["src"] -} diff --git a/packages/notifications/package.json b/packages/notifications/package.json index 2425f58..af90c45 100644 --- a/packages/notifications/package.json +++ b/packages/notifications/package.json @@ -7,23 +7,20 @@ "./context": "./src/context.tsx" }, "scripts": { - "format": "pnpm exec prettier --write .", + "format": "bunx prettier --write .", "lint": "eslint src", "build": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@methanium/ui": "*", - "@tanstack/react-router": "^1.170.21", - "@tauri-apps/api": "^2.11.1", - "@tauri-apps/plugin-notification": "~2.3.3", - "@tensamin/chat": "workspace:*", + "@tauri-apps/api": "^2.11.0", "@tensamin/crypto": "workspace:*", - "@tensamin/mtp": "workspace:*", "@tensamin/shared": "workspace:*", + "@tensamin/chat": "workspace:*", "@tensamin/storage": "workspace:*", + "@tensamin/ttp": "workspace:*", "@tensamin/user": "workspace:*", - "react": "^19.2.8", - "react-dom": "^19.2.8", - "sonner": "^2.0.7" + "react": "^19.2.0", + "react-dom": "^19.2.0", + "zod": "^4.3.6" } } diff --git a/packages/notifications/src/context.tsx b/packages/notifications/src/context.tsx index e0da277..4e433a3 100644 --- a/packages/notifications/src/context.tsx +++ b/packages/notifications/src/context.tsx @@ -1,190 +1,96 @@ +import { useCrypto } from "@tensamin/crypto/context"; import { useStorage } from "@tensamin/storage/context"; import { useUser } from "@tensamin/user/context"; import { useChat } from "@tensamin/chat/context"; -import { useMTP } from "@tensamin/mtp"; +import { useTTP } from "@tensamin/ttp"; import { createContext, useEffect, useContext } from "react"; +import z from "zod"; import { toast as sonnerToast } from "sonner"; -import { Avatar, AvatarFallback, AvatarImage } from "@methanium/ui"; -import { invoke, isTauri } from "@tauri-apps/api/core"; -import { - isPermissionGranted as isTauriNotificationPermissionGranted, - requestPermission as requestTauriNotificationPermission, - sendNotification as sendTauriNotification, -} from "@tauri-apps/plugin-notification"; +import { message as messageSchema } from "@tensamin/shared/data"; +import { Avatar, AvatarFallback, AvatarImage } from "@tensamin/ui"; +import { isTauri } from "@tauri-apps/api/core"; import { useSession } from "@tensamin/storage/session"; -import { useLocation, useNavigate } from "@tanstack/react-router"; -import { decryptChatText } from "@tensamin/crypto/chatSecret"; -import { log } from "@tensamin/shared/log"; -import { playSound } from "@tensamin/shared/sounds"; export const context = createContext(undefined); -async function requestNotificationPermission() { - if (!("Notification" in window)) return false; - - if (Notification.permission === "granted") return true; - - const permission = await Notification.requestPermission(); - return permission === "granted"; +function reduceDisplay(display: string) { + const words = display.split(" "); + if (words.length === 1) { + return display.slice(0, 2).toUpperCase(); + } else { + return words[0].charAt(0).toUpperCase() + words[1].charAt(0).toUpperCase(); + } } export default function Provider(props: { children: React.ReactNode }) { - const { subscribe, send } = useMTP(); + const { subscribePush } = useTTP(); const { load } = useStorage(); const { get } = useUser(); - const { addLiveMessage, chatSecret, getChatSecret, userId } = useChat(); + const { decryptText, getSharedSecret } = useCrypto(); + const { addLiveMessage, userId } = useChat(); const { moveUserIdToTop } = useSession(); - const navigate = useNavigate(); - const location = useLocation(); useEffect(() => { - return subscribe("MessageLive", async ({ data }) => { - if (!data.SenderId) return; + return subscribePush(async (ttpMessage) => { + if (ttpMessage.type === "message_live") { + const { message, sender_id } = ttpMessage.data as { + message: z.infer; + sender_id: number; + }; - const isCurrentChat = - location.pathname === "/chat" && userId === data.SenderId; - const appFocused = - document.hasFocus() && document.visibilityState === "visible"; - const shouldAlert = !isCurrentChat || !appFocused; + const user = await get(sender_id); - const messageSecret = - isCurrentChat && chatSecret - ? chatSecret - : await getChatSecret(data.SenderId); + const decryptedContent = await decryptText( + await getSharedSecret( + await load("private_key"), + await get(await load("user_id")).then((data) => data.public_key), + user.public_key, + ), + message.content, + ); - if (!data.Message || !messageSecret) return; - if (shouldAlert) playSound("message"); - - void decryptChatText(messageSecret, data.Message.Content) - .catch((err) => { - log(1, "chat", "red", "Failed to decrypt live message", err, { - SendTime: data.Message?.SendTime, + console.log(userId, sender_id, userId === sender_id); + if (userId === sender_id) { + addLiveMessage({ + ...message, + content: decryptedContent, + sent_by_self: false, }); - return null; - }) - .then(async (content) => { - if (!data.Message || !content || !data.SenderId) return; - if (isCurrentChat) { - addLiveMessage({ - ...data.Message, - Content: content ?? "Failed to decrypt message", - decryptionFailed: content === null, - }); - } + return; + } - if (!shouldAlert) return; + // add notification symbol to conversation cards - if (!isCurrentChat) { - // todo: add notification symbol to conversation cards (incl. message start) - moveUserIdToTop(data.SenderId); + moveUserIdToTop(sender_id); - if (await load("settings.receive_confirmations")) { - void send("MessageState", { - MessageState: "received", - }); - } - } - - const user = await get(data.SenderId, [ - "UserId", - "Display", - "Avatar", - ]); - - if (isTauri()) { - if (!appFocused) return; - const permissionGranted = - (await isTauriNotificationPermissionGranted()) || - (await requestTauriNotificationPermission()) === "granted"; - - if (permissionGranted) { - let handledNatively = false; - try { - handledNatively = await invoke( - "mtp_post_message_notification", - { - senderId: user.UserId, - sender: user.Display, - body: content, - avatar: user.Avatar, - }, - ); - } catch (error) { - log( - 1, - "notifications", - "red", - "Failed to create native message notification", - error, - ); - } - - if (!handledNatively) { - sendTauriNotification({ title: user.Display, body: content }); - } - } - } else { - const hasPermissions = await requestNotificationPermission(); - - if (hasPermissions) { - const options: NotificationOptions = { - body: content, - icon: user.Avatar || "/icons/icon-192.png", - badge: "/icons/notification-badge.png", - tag: `message-${user.UserId}`, - silent: true, - }; - if ("serviceWorker" in navigator) { - const registration = - await navigator.serviceWorker.getRegistration(); - if (registration) { - await registration.showNotification(user.Display, { - ...options, - data: { url: `/chat?id=${user.UserId}` }, - }); - return; - } - } - const notification = new Notification(user.Display, options); - notification.onclick = () => { - window.focus(); - navigate({ - to: `/chat?id=${user.UserId}`, - }); - notification.close(); - }; - } else { - sonnerToast(user.Display, { - classNames: { - content: "pl-4", - }, - description: content, - icon: ( - - - - {user.Display.slice(0, 2).toUpperCase()} - - - ), - }); - } - } - }); + if (isTauri()) { + console.log("weewoo"); + } else { + sonnerToast(user.display, { + classNames: { + content: "pl-4", + }, + description: decryptedContent, + icon: ( + + + {reduceDisplay(user.display)} + + ), + }); + } + } }); }, [ - addLiveMessage, - chatSecret, - get, + subscribePush, + decryptText, + getSharedSecret, load, - location.pathname, - navigate, - send, - moveUserIdToTop, - subscribe, - getChatSecret, + get, + addLiveMessage, userId, + moveUserIdToTop, ]); return ( diff --git a/packages/notifications/todo.md b/packages/notifications/todo.md index be726ed..599b984 100644 --- a/packages/notifications/todo.md +++ b/packages/notifications/todo.md @@ -1 +1,3 @@ +- Tauri notifications +- Mobile notifications channel setup - Add notification symbol / outline to user modal diff --git a/packages/onboarding/package.json b/packages/onboarding/package.json deleted file mode 100644 index ce184bb..0000000 --- a/packages/onboarding/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "@tensamin/onboarding", - "private": true, - "version": "0.0.0", - "type": "module", - "exports": { - ".": "./src/index.tsx" - }, - "scripts": { - "format": "pnpm exec prettier --write .", - "lint": "eslint src", - "build": "tsc -p tsconfig.json --noEmit" - }, - "dependencies": { - "@methanium/ui": "*", - "@tauri-apps/api": "^2.11.1", - "@tauri-apps/plugin-notification": "~2.3.3", - "@tensamin/shared": "workspace:*", - "@tensamin/storage": "workspace:*", - "lucide-react": "^1.29.0", - "react": "^19.2.8", - "react-dom": "^19.2.8", - "zod": "^4.4.3" - } -} diff --git a/packages/onboarding/src/index.tsx b/packages/onboarding/src/index.tsx deleted file mode 100644 index 0184776..0000000 --- a/packages/onboarding/src/index.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import { useCallback, useEffect, useState, type ReactNode } from "react"; -import { - ErrorScreen, - LoadingScreen, - OnboardingFlow, - type OnboardingStep, -} from "@methanium/ui"; -import { useStorage } from "@tensamin/storage/context"; -import { legalDocsSchema } from "@tensamin/shared/features/legal/schema"; -import { log } from "@tensamin/shared/log"; -import { isTauri } from "@tauri-apps/api/core"; -import type { z } from "zod"; - -import LegalPage from "./pages/legal"; -import { onboardingSteps } from "./steps"; -import TauriPermissionsPage from "./pages/tauriPermissions"; - -export { - useOnboardingStep, - type OnboardingStep, - type OnboardingStepControls, -} from "@methanium/ui"; - -interface GateState { - docs: z.infer; - acceptedPP: boolean; - acceptedTOS: boolean; - changedPP: boolean; - changedTOS: boolean; - includeLegal: boolean; - includeOnboarding: boolean; - includeTauriPermissions: boolean; -} - -async function fetchLegalDocumentHash(document: string) { - const response = await fetch( - `https://legal.methanium.net/tensamin/${document}/raw`, - ); - if (!response.ok) { - throw new Error(`Legal document request failed: ${response.status}`); - } - if (!response.headers.get("content-type")?.startsWith("text/plain")) { - throw new Error("Legal document request returned an invalid content type"); - } - - const hash = await crypto.subtle.digest( - "SHA-256", - await response.arrayBuffer(), - ); - return Array.from(new Uint8Array(hash), (byte) => - byte.toString(16).padStart(2, "0"), - ).join(""); -} - -export default function OnboardingGate({ children }: { children: ReactNode }) { - const { load, save } = useStorage(); - const [state, setState] = useState(); - const [complete, setComplete] = useState(false); - const [showLoading, setShowLoading] = useState(false); - const [error, setError] = useState(""); - const [errorDescription, setErrorDescription] = useState(""); - const [onboardingThemeId, setOnboardingThemeId] = useState( - null, - ); - - useEffect(() => { - let active = true; - const loadingTimer = setTimeout(() => { - if (active) setShowLoading(true); - }, 200); - - void (async () => { - try { - const [ - ppHash, - tosHash, - localDocs, - acceptedPP, - acceptedTOS, - onboardingDone, - onboardingStarted, - tauriPermissionsDone, - ] = await Promise.all([ - fetchLegalDocumentHash("privacy-policy"), - fetchLegalDocumentHash("terms-of-service"), - load("legal_docs"), - load("accepted_privacy_policy"), - load("accepted_terms_of_service"), - load("onboarding_done"), - load("onboarding_started"), - load("tauri_permissions_done"), - ]); - - if (!active) return; - - const docs = legalDocsSchema.parse({ - pp: { hash: ppHash }, - tos: { hash: tosHash }, - }); - const changedPP = acceptedPP && localDocs.pp.hash !== docs.pp.hash; - const changedTOS = acceptedTOS && localDocs.tos.hash !== docs.tos.hash; - const currentAcceptedPP = - acceptedPP && localDocs.pp.hash === docs.pp.hash; - const currentAcceptedTOS = - acceptedTOS && localDocs.tos.hash === docs.tos.hash; - const existingUser = acceptedPP && acceptedTOS; - const includeOnboarding = - !onboardingDone && (!existingUser || onboardingStarted); - - if (!onboardingDone && existingUser && !onboardingStarted) { - await save("onboarding_done", true); - if (!active) return; - } else if (includeOnboarding && !onboardingStarted) { - await save("onboarding_started", true); - if (!active) return; - } - - setState({ - docs, - acceptedPP: currentAcceptedPP, - acceptedTOS: currentAcceptedTOS, - changedPP, - changedTOS, - includeLegal: !currentAcceptedPP || !currentAcceptedTOS, - includeOnboarding, - includeTauriPermissions: - isTauri() && - /Android/.test(navigator.userAgent) && - !tauriPermissionsDone, - }); - } catch (caught) { - if (!active) return; - setError("Failed to load legal documents"); - setErrorDescription( - "An error occurred while fetching the legal documents from the server. Please try again later.", - ); - log(0, "Legal", "red", "Failed to fetch legal documents", caught); - } finally { - clearTimeout(loadingTimer); - } - })(); - - return () => { - active = false; - clearTimeout(loadingTimer); - }; - }, [load, save]); - - const acceptLegal = useCallback(async () => { - if (!state) return; - await Promise.all([ - save("accepted_privacy_policy", true), - save("accepted_terms_of_service", true), - save("legal_docs", state.docs), - ]); - setState((current) => - current ? { ...current, acceptedPP: true, acceptedTOS: true } : current, - ); - }, [save, state]); - - const finish = useCallback(async () => { - if (state?.includeOnboarding) { - await Promise.all([ - save("onboarding_done", true), - save("onboarding_started", false), - ]); - } - if (state?.includeTauriPermissions) { - await save("tauri_permissions_done", true); - } - setComplete(true); - }, [save, state?.includeOnboarding, state?.includeTauriPermissions]); - - if (error && errorDescription) { - return ; - } - - if (!state) { - if (!showLoading) return null; - return ; - } - - const steps: OnboardingStep[] = []; - if (state.includeLegal) { - const changedDocuments = [ - state.changedPP && "Privacy Policy", - state.changedTOS && "Terms of Service", - ].filter(Boolean); - - steps.push({ - id: "legal", - title: - changedDocuments.length > 0 - ? "Legal documents changed" - : "Privacy Policy & ToS", - description: - changedDocuments.length > 0 - ? changedDocuments.join(" & ") - : "Review and accept our legal documents", - defaultCanContinue: false, - content: ( - - ), - }); - } - if (state.includeOnboarding) { - steps.push(...onboardingSteps(onboardingThemeId, setOnboardingThemeId)); - } - if (state.includeTauriPermissions) { - steps.push({ - id: "tauri-permissions", - title: "Enable notifications", - description: - "We need these permissions so that notifications can be independent of Google Play Services.", - defaultCanContinue: false, - content: , - }); - } - - if (complete || steps.length === 0) return <>{children}; - - return ; -} diff --git a/packages/onboarding/src/pages/legal.tsx b/packages/onboarding/src/pages/legal.tsx deleted file mode 100644 index 6619ae8..0000000 --- a/packages/onboarding/src/pages/legal.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { useCallback, useState } from "react"; -import { Checkbox, Label, Link, useOnboardingStep } from "@methanium/ui"; - -export default function LegalPage({ - initiallyAcceptedPP, - initiallyAcceptedTOS, - changedPP, - changedTOS, - onAccept, -}: { - initiallyAcceptedPP: boolean; - initiallyAcceptedTOS: boolean; - changedPP: boolean; - changedTOS: boolean; - onAccept: () => Promise; -}) { - const [acceptedPP, setAcceptedPP] = useState(initiallyAcceptedPP); - const [acceptedTOS, setAcceptedTOS] = useState(initiallyAcceptedTOS); - - const handleContinue = useCallback(async () => { - if (!acceptedPP || !acceptedTOS) return false; - await onAccept(); - }, [acceptedPP, acceptedTOS, onAccept]); - - useOnboardingStep({ - canContinue: acceptedPP && acceptedTOS, - onContinue: handleContinue, - }); - - return ( -
-
-
- {!initiallyAcceptedPP && ( - - )} - {!initiallyAcceptedTOS && ( - - )} -
-
-
- ); -} - -function LegalDocumentAcceptance({ - id, - name, - link, - changed, - checked, - onChange, -}: { - id: string; - name: string; - link: string; - changed: boolean; - checked: boolean; - onChange: (checked: boolean) => void; -}) { - return ( -
- - -
- ); -} - -function BigCheckbox({ - id, - label, - checked, - onChange, -}: { - id: string; - label: string; - checked: boolean; - onChange: (checked: boolean) => void; -}) { - return ( -
- - -
- ); -} diff --git a/packages/onboarding/src/pages/tauriPermissions.tsx b/packages/onboarding/src/pages/tauriPermissions.tsx deleted file mode 100644 index 5d6ca43..0000000 --- a/packages/onboarding/src/pages/tauriPermissions.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { useEffect, useState } from "react"; -import { Button, useOnboardingStep } from "@methanium/ui"; -import { invoke } from "@tauri-apps/api/core"; -import { - isPermissionGranted, - requestPermission, -} from "@tauri-apps/plugin-notification"; -import { BatteryCharging, Bell } from "lucide-react"; - -export default function TauriPermissionsPage() { - const [notificationsGranted, setNotificationsGranted] = useState(false); - const [batteryExempt, setBatteryExempt] = useState(false); - const [notificationAttempted, setNotificationAttempted] = useState(false); - const [batteryAttempted, setBatteryAttempted] = useState(false); - - useEffect(() => { - const refresh = () => { - void Promise.all([ - isPermissionGranted(), - invoke("mtp_is_ignoring_battery_optimizations"), - ]).then(([notifications, battery]) => { - setNotificationsGranted(notifications); - setBatteryExempt(battery); - }); - }; - refresh(); - window.addEventListener("focus", refresh); - document.addEventListener("visibilitychange", refresh); - const interval = window.setInterval(refresh, 1_000); - return () => { - window.removeEventListener("focus", refresh); - document.removeEventListener("visibilitychange", refresh); - window.clearInterval(interval); - }; - }, []); - - useOnboardingStep({ - canContinue: - (notificationsGranted || notificationAttempted) && - (batteryExempt || batteryAttempted), - onContinue: async () => { - await invoke("mtp_set_enabled", { enabled: true }); - }, - }); - - return ( -
-
-
-
- -
-
-

Allow notifications

-

- Allow Tensamin to notify you while it's closed. -

-
-
- -
-
-
-
- -
-
-

Allow running in the background

-

- Exclude Tensamin from battery optimisation so Android does not - suspend it's connection for decryption of live messages. -

-
-
- -
-
- ); -} diff --git a/packages/onboarding/src/steps.tsx b/packages/onboarding/src/steps.tsx deleted file mode 100644 index 26a49df..0000000 --- a/packages/onboarding/src/steps.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { ThemeOnboardingPage, type OnboardingStep } from "@methanium/ui"; - -export const onboardingSteps = ( - themeId: string | null, - setThemeId: (id: string) => void, -): OnboardingStep[] => [ - { - id: "theme", - title: "Theming", - description: - "Pick a main theme and after that ... let's just hope you don't get into decision paralysis", - defaultCanContinue: false, - content: , - }, -]; diff --git a/packages/settings/package.json b/packages/settings/package.json deleted file mode 100644 index d281b7f..0000000 --- a/packages/settings/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@tensamin/settings", - "private": true, - "version": "0.0.0", - "type": "module", - "exports": { - ".": "./src/index.tsx" - }, - "scripts": { - "format": "pnpm exec prettier --write .", - "lint": "eslint src", - "build": "tsc -p tsconfig.json --noEmit" - }, - "dependencies": { - "@methanium/ui": "*", - "@tanstack/react-router": "^1.170.21", - "@tauri-apps/api": "^2.11.1", - "@tensamin/cache": "workspace:*", - "@tensamin/hotkeys": "workspace:*", - "@tensamin/mtp": "workspace:*", - "@tensamin/shared": "workspace:*", - "@tensamin/storage": "workspace:*", - "@tensamin/user": "workspace:*", - "lucide-react": "^1.29.0", - "react": "^19.2.8", - "react-dom": "^19.2.8" - }, - "devDependencies": { - "vite": "^8.2.1" - } -} diff --git a/packages/settings/src/components.tsx b/packages/settings/src/components.tsx deleted file mode 100644 index f2ca317..0000000 --- a/packages/settings/src/components.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { EditableList, LabeledSwitch } from "@methanium/ui"; -import { useEffect, useState } from "react"; - -import { storageDefaults, type Storage } from "@tensamin/shared/data"; -import { settingsStorageDefaults } from "@tensamin/shared/settings"; -import { useStorage } from "@tensamin/storage/context"; - - - -type ListStorageKey = { - [K in keyof Storage]: Storage[K] extends (string | number)[] ? K : never; -}[keyof Storage]; - -type ListStorageItem = - Storage[K] extends Array ? Item & (string | number) : never; - -export function Switch({ - label, - id, -}: { - label: React.ReactNode; - id: keyof typeof settingsStorageDefaults & ({ - [K in keyof Storage]: Storage[K] extends boolean ? K : never; -}[keyof Storage]); -}) { - const { save, load } = useStorage(); - const [value, setValue] = useState(settingsStorageDefaults[id]); - - useEffect(() => { - load(id).then((value) => setValue(value)); - }, [id, load]); - - return ( - { - setValue(nextValue); - save(id, nextValue); - }} - /> - ); -} - -export function List({ - label, - id, -}: { - label: React.ReactNode; - id: K; -}) { - const { save, load } = useStorage(); - const [items, setItems] = useState(storageDefaults[id]); - - useEffect(() => { - load(id).then((value) => setItems(value)); - }, [id, load]); - - const persistItems = (nextItems: ListStorageItem[]) => { - setItems(nextItems as Storage[K]); - save(id, nextItems as Storage[K]); - }; - - const toStorageItem = (value: string): ListStorageItem => { - const referenceItem = items[0] ?? storageDefaults[id][0]; - return ( - typeof referenceItem === "number" ? Number(value) : value - ) as ListStorageItem; - }; - - return ( - > - label={label} - items={items as ListStorageItem[]} - parseItem={(value) => { - const item = toStorageItem(value); - return typeof item === "number" && Number.isNaN(item) - ? undefined - : item; - }} - onItemsChange={persistItems} - /> - ); -} diff --git a/packages/settings/src/index.tsx b/packages/settings/src/index.tsx deleted file mode 100644 index 9315658..0000000 --- a/packages/settings/src/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { createRoute, type AnyRoute } from "@tanstack/react-router"; - -import SettingsLayout from "./layout"; -import { settingsPages } from "./manifest"; - -export function createSettingsRoute(parentRoute: AnyRoute) { - const settingsRoute = createRoute({ - getParentRoute: () => parentRoute, - path: "settings", - component: SettingsLayout, - staticData: { showMobileNavbar: true }, - }); - - return settingsRoute.addChildren( - settingsPages.map((page) => - createRoute({ - getParentRoute: () => settingsRoute, - path: page.path, - component: page.component, - staticData: { showMobileNavbar: true }, - }), - ), - ); -} diff --git a/packages/settings/src/layout.tsx b/packages/settings/src/layout.tsx deleted file mode 100644 index 5d49f0b..0000000 --- a/packages/settings/src/layout.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Outlet, useLocation } from "@tanstack/react-router"; -import { Button, useIsMobile } from "@methanium/ui"; -import { ArrowLeft } from "lucide-react"; - -import { SettingsSidebar } from "./sidebar"; - -export default function SettingsLayout() { - const isMobile = useIsMobile(); - const location = useLocation(); - - return ( -
- -
-
- {isMobile && !/^\/settings\/?$/.test(location.pathname) && ( - - )} -

- {location.pathname - .split("/") - .pop() - ?.replace(/-/g, " ") - .replace(/\b\w/g, (letter) => letter.toUpperCase())} -

-
- -
-
- ); -} diff --git a/packages/settings/src/manifest.ts b/packages/settings/src/manifest.ts deleted file mode 100644 index 1afdd5a..0000000 --- a/packages/settings/src/manifest.ts +++ /dev/null @@ -1,31 +0,0 @@ -import Accessibility from "./pages/accessibility"; -import Cache from "./pages/cache"; -import Call from "./pages/call"; -import Chat from "./pages/chat"; -import Index from "./pages/index"; -import Licenses from "./pages/licenses"; -import Profile from "./pages/profile"; -import Security from "./pages/security"; -import Theme from "./pages/theme"; -import Hotkeys from "./pages/hotkeys"; -import { settingsNavigation } from "./navigation"; - -const pageComponents = { - profile: Profile, - security: Security, - chat: Chat, - call: Call, - cache: Cache, - theme: Theme, - accessibility: Accessibility, - hotkeys: Hotkeys, - licenses: Licenses, -} as const; - -export const settingsPages = [ - { path: "/", component: Index }, - ...settingsNavigation.map((page) => ({ - ...page, - component: pageComponents[page.path], - })), -] as const; diff --git a/packages/settings/src/navigation.ts b/packages/settings/src/navigation.ts deleted file mode 100644 index b47fae9..0000000 --- a/packages/settings/src/navigation.ts +++ /dev/null @@ -1,31 +0,0 @@ -export const settingsNavigation = [ - { - category: "account", - path: "profile", - label: "Profile", - }, - { - category: "account", - path: "security", - label: "Security", - }, - { category: "general", path: "chat", label: "Chat" }, - { category: "general", path: "call", label: "Call" }, - { category: "application", path: "cache", label: "Cache" }, - { category: "application", path: "theme", label: "Theme" }, - { - category: "application", - path: "accessibility", - label: "Accessibility", - }, - { - category: "application", - path: "hotkeys", - label: "Hotkeys", - }, - { - category: "application", - path: "licenses", - label: "Licenses", - }, -] as const; diff --git a/packages/settings/src/pages/accessibility.tsx b/packages/settings/src/pages/accessibility.tsx deleted file mode 100644 index 026c3b1..0000000 --- a/packages/settings/src/pages/accessibility.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { Button, Label, Slider } from "@methanium/ui"; -import { invoke, isTauri } from "@tauri-apps/api/core"; -import { RotateCcw } from "lucide-react"; -import { useEffect, useState } from "react"; - -const DEFAULT_INITIAL_SCALE = 290; -const MIN_INITIAL_SCALE = 210; -const isAndroid = isTauri() && /Android/.test(navigator.userAgent); - -export default function Page() { - const [initialScale, setInitialScale] = useState(DEFAULT_INITIAL_SCALE); - const [loading, setLoading] = useState(isAndroid); - - useEffect(() => { - if (!isAndroid) return; - - let active = true; - void invoke("accessibility_get_initial_scale") - .then((scale) => { - if (active) setInitialScale(scale); - }) - .catch((error: unknown) => { - console.error("Failed to load the Android initial scale", error); - }) - .finally(() => { - if (active) setLoading(false); - }); - - return () => { - active = false; - }; - }, []); - - if (!isAndroid) return null; - - function updateInitialScale(nextScale: number) { - setInitialScale(nextScale); - void invoke("accessibility_set_initial_scale", { - initialScale: nextScale, - }).catch((error: unknown) => { - console.error("Failed to update the Android initial scale", error); - }); - } - - return ( -
-
- - - {initialScale}% - -
-
- { - const nextScale = Array.isArray(value) ? value[0] : value; - if (nextScale !== undefined) updateInitialScale(nextScale); - }} - /> - -
-
- ); -} diff --git a/packages/settings/src/pages/cache.tsx b/packages/settings/src/pages/cache.tsx deleted file mode 100644 index 8dc5eee..0000000 --- a/packages/settings/src/pages/cache.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { createCache } from "@tensamin/cache"; -import { storageDefaults } from "@tensamin/shared/data"; -import { useStorage } from "@tensamin/storage/context"; -import { secureValueCodec } from "@tensamin/storage/secure"; -import { Button, Input, Label } from "@methanium/ui"; -import { useEffect, useState } from "react"; - -const validLimit = (value: number) => Number.isSafeInteger(value) && value >= 0; - -export default function Page() { - const { load, save } = useStorage(); - const [contacts, setContacts] = useState(storageDefaults.cache_contacts); - const [messagesPerChat, setMessagesPerChat] = useState( - storageDefaults.cache_messages_per_chat, - ); - const [savedContacts, setSavedContacts] = useState(contacts); - const [savedMessagesPerChat, setSavedMessagesPerChat] = - useState(messagesPerChat); - const [saving, setSaving] = useState(false); - useEffect(() => { - void Promise.all([ - load("cache_contacts"), - load("cache_messages_per_chat"), - ]).then(([nextContacts, nextMessages]) => { - setContacts(nextContacts); - setSavedContacts(nextContacts); - setMessagesPerChat(nextMessages); - setSavedMessagesPerChat(nextMessages); - }); - }, [load]); - const valid = validLimit(contacts) && validLimit(messagesPerChat); - const changed = - contacts !== savedContacts || messagesPerChat !== savedMessagesPerChat; - async function persist() { - if (!valid || saving) return; - setSaving(true); - try { - await Promise.all([ - save("cache_contacts", contacts), - save("cache_messages_per_chat", messagesPerChat), - ]); - const accountId = await load("user_id"); - if (accountId) - await createCache(String(accountId), { - codec: secureValueCodec, - }).conversations.prune(); - setSavedContacts(contacts); - setSavedMessagesPerChat(messagesPerChat); - } finally { - setSaving(false); - } - } - return ( -
-
- - setContacts(event.currentTarget.valueAsNumber)} - /> -
-
- - - setMessagesPerChat(event.currentTarget.valueAsNumber) - } - /> -
- {!valid && ( -

- Cache limits must be whole numbers greater than or equal to 0. -

- )} -
- - -
-
- ); -} diff --git a/packages/settings/src/pages/call.tsx b/packages/settings/src/pages/call.tsx deleted file mode 100644 index 454e7b1..0000000 --- a/packages/settings/src/pages/call.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { - Button, - Label, - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@methanium/ui"; -import { settingsStorageDefaults } from "@tensamin/shared/settings"; -import { playSound, stopSound } from "@tensamin/shared/sounds"; -import { useStorage } from "@tensamin/storage/context"; -import { Play, Square } from "lucide-react"; -import { useEffect, useRef, useState } from "react"; - -type CallJingle = (typeof settingsStorageDefaults)["settings.call_jingle"]; - -const jingleLabels: Record = { - jingle_1: "Jingle 1", - jingle_2: "Jingle 2", -}; - -export default function Page() { - const { load, save } = useStorage(); - const preview = useRef(null); - const [jingle, setJingle] = useState( - settingsStorageDefaults["settings.call_jingle"], - ); - const [isPreviewing, setIsPreviewing] = useState(false); - - function stopPreview() { - stopSound(preview.current); - preview.current = null; - setIsPreviewing(false); - } - - useEffect(() => { - void load("settings.call_jingle").then(setJingle); - }, [load]); - - useEffect(() => () => stopSound(preview.current), []); - - return ( -
- -
- - -
-
- ); -} diff --git a/packages/settings/src/pages/chat.tsx b/packages/settings/src/pages/chat.tsx deleted file mode 100644 index 1cc0d58..0000000 --- a/packages/settings/src/pages/chat.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { storageDefaults } from "@tensamin/shared/data"; -import { useStorage } from "@tensamin/storage/context"; -import { Button, Kbd } from "@methanium/ui"; -import { List, Switch } from "../components"; - -export default function Page() { - const { save } = useStorage(); - return ( -
- - Change Enter behavior to Shift +{" "} - Enter -

- } - id="settings.reverse_enter_behavior" - /> - - - -

- Trusted embed domains can get your IP-Address! Only add domains if you - really trust them! -

-
- -
- -
- ); -} diff --git a/packages/settings/src/pages/hotkeys.tsx b/packages/settings/src/pages/hotkeys.tsx deleted file mode 100644 index 3f5cb5f..0000000 --- a/packages/settings/src/pages/hotkeys.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { Button, Kbd } from "@methanium/ui"; -import { - formatForDisplay, - useHotkeyDefinitions, - useHotkeyRecorder, - useHotkeysContext, - type HotkeyDefinition, -} from "@tensamin/hotkeys"; -import { useEffect, useState } from "react"; - -function HotkeyRow({ - definition, - conflicts, - isRecording, - startRecording, -}: { - definition: HotkeyDefinition; - conflicts: string[]; - isRecording: boolean; - startRecording: () => void; -}) { - const { bindingFor, setBinding, resetBinding, globalStatuses } = - useHotkeysContext(); - const binding = bindingFor(definition); - - return ( -
-
-
-

{definition.name}

- {definition.description && ( -

- {definition.description} -

- )} -

- {definition.global - ? "Global in the Electron desktop app" - : "Active while its screen or component is available"} -

-
- {binding ? formatForDisplay(binding) : "Unbound"} -
- {conflicts.length > 0 && ( -

- Also assigned to {conflicts.join(", ")}. -

- )} - {definition.global && globalStatuses[definition.id] === "unavailable" && ( -

- Electron could not register this shortcut. It may be reserved by the - operating system or another application. -

- )} -
- - - -
-
- ); -} - -export default function Page() { - const definitions = useHotkeyDefinitions(); - const { bindingFor, resetAll, setBinding, setRecording } = - useHotkeysContext(); - const [recordingId, setRecordingId] = useState(null); - const categories = [...new Set(definitions.map(({ category }) => category))]; - const recorder = useHotkeyRecorder({ - ignoreInputs: false, - onRecord: (hotkey) => { - const definition = definitions.find(({ id }) => id === recordingId); - if (definition) setBinding(definition, hotkey || null); - setRecordingId(null); - setRecording(false); - }, - onCancel: () => { - setRecordingId(null); - setRecording(false); - }, - }); - - useEffect(() => () => setRecording(false), [setRecording]); - - return ( -
-
-

- Click Record, then press the replacement shortcut. Conflicting - shortcuts are allowed and will run together when their scopes overlap. -

- -
- {categories.map((category) => ( -
-

{category}

- {definitions - .filter((definition) => definition.category === category) - .map((definition) => { - const binding = bindingFor(definition); - const conflicts = binding - ? definitions - .filter( - (candidate) => - candidate.id !== definition.id && - bindingFor(candidate) === binding, - ) - .map(({ name }) => name) - : []; - return ( - { - setRecordingId(definition.id); - setRecording(true); - recorder.startRecording(); - }} - /> - ); - })} -
- ))} - {definitions.length === 0 && ( -

- No configurable hotkeys are registered. -

- )} -
- ); -} diff --git a/packages/settings/src/pages/index.tsx b/packages/settings/src/pages/index.tsx deleted file mode 100644 index b5d04e3..0000000 --- a/packages/settings/src/pages/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { SettingsSidebar } from "../sidebar"; - -export default function Page() { - return ( -
- -
- ); -} diff --git a/packages/settings/src/pages/profile.tsx b/packages/settings/src/pages/profile.tsx deleted file mode 100644 index dc8de3c..0000000 --- a/packages/settings/src/pages/profile.tsx +++ /dev/null @@ -1,228 +0,0 @@ -import { Input as MDInput } from "@methanium/ui/markdown"; -import { useMTP } from "@tensamin/mtp"; -import { mtp } from "@tensamin/shared/data"; -import { useStorage } from "@tensamin/storage/context"; -import { - Avatar, - AvatarFallback, - AvatarImage, - Button, - cn, - Input, - useIsMobile, -} from "@methanium/ui"; -import { - useUser, - useUserFields, - type SelectedUser, -} from "@tensamin/user/context"; -import { Check } from "lucide-react"; -import { useEffect, useRef, useState } from "react"; - -const PROFILE_FIELDS = [ - "Avatar", - "Display", - "Username", - "About", - "UserId", -] as const; -type ProfileDraft = Partial< - Omit, "UserId"> ->; - -async function prepImage( - file: File, - size = 300, - quality = 0.8, -): Promise { - const bitmap = await createImageBitmap(file); - const canvas = document.createElement("canvas"); - canvas.width = size; - canvas.height = size; - const context = canvas.getContext("2d"); - if (!context) throw new Error("Could not get canvas context"); - const scale = Math.max(size / bitmap.width, size / bitmap.height); - const width = bitmap.width * scale; - const height = bitmap.height * scale; - try { - context.drawImage( - bitmap, - (size - width) / 2, - (size - height) / 2, - width, - height, - ); - } finally { - bitmap.close(); - } - return canvas.toDataURL("image/webp", quality); -} - -export default function Page() { - const { updateProfile } = useUser(); - const { load } = useStorage(); - const { send } = useMTP(); - const isMobile = useIsMobile(); - const [userId, setUserId] = useState(null); - const { data: currentUser } = useUserFields(userId, PROFILE_FIELDS); - const [draftUser, setDraftUser] = useState({}); - const [errorMessage, setErrorMessage] = useState(""); - const [saveSucceeded, setSaveSucceeded] = useState(false); - const avatarUploadRef = useRef(null); - const draftInitializedRef = useRef(false); - const effectiveAvatar = - draftUser.Avatar === "none" ? undefined : draftUser.Avatar; - const updateDraftUser = ( - updater: (previous: ProfileDraft) => ProfileDraft, - ) => { - setSaveSucceeded(false); - setErrorMessage(""); - setDraftUser(updater); - }; - useEffect(() => { - void load("user_id").then(setUserId); - }, [load]); - useEffect(() => { - if (!currentUser || draftInitializedRef.current) return; - setDraftUser({ - Avatar: currentUser.Avatar, - Display: currentUser.Display, - Username: currentUser.Username, - About: currentUser.About, - }); - draftInitializedRef.current = true; - }, [currentUser]); - async function handleAvatarUpload(file: File) { - const avatar = await prepImage(file); - updateDraftUser((previous) => ({ ...previous, Avatar: avatar })); - if (avatarUploadRef.current) avatarUploadRef.current.value = ""; - } - if (!currentUser) return

Loading...

; - return ( - <> - - event.target.files?.[0] && handleAvatarUpload(event.target.files[0]) - } - type="file" - /> -
-
- - - - {draftUser.Display?.slice(0, 2).toUpperCase() || - currentUser.Display.slice(0, 2).toUpperCase()} - - -
-

Avatar

-
- - -
-

- GIFs are supported in decentralised mode or with Tensamin Premium. -
- Maximum file size is 16mb. -

-
-
- - updateDraftUser((previous) => ({ - ...previous, - Display: event.target.value, - })) - } - placeholder="Display Name" - value={draftUser.Display || ""} - /> - - updateDraftUser((previous) => ({ - ...previous, - Username: event.target.value, - })) - } - placeholder="Username" - value={draftUser.Username || ""} - /> - - updateDraftUser((previous) => ({ ...previous, About: value })) - } - value={draftUser.About || ""} - /> - - {errorMessage && ( -

{errorMessage}

- )} -
- - ); -} diff --git a/packages/settings/src/pages/security.tsx b/packages/settings/src/pages/security.tsx deleted file mode 100644 index 74557d0..0000000 --- a/packages/settings/src/pages/security.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { useStorage } from "@tensamin/storage/context"; -import { Button, Input, Label } from "@methanium/ui"; -import { useEffect, useState } from "react"; - -export default function Page() { - const { save, load } = useStorage(); - const [draftOmegaUrl, setDraftOmegaUrl] = useState(""); - const [currentOmegaUrl, setCurrentOmegaUrl] = useState(""); - const [draftForcedOmikronUrl, setDraftForcedOmikronUrl] = useState(""); - const [currentForcedOmikronUrl, setCurrentForcedOmikronUrl] = useState(""); - const [draftForcedOmikronPublicKey, setDraftForcedOmikronPublicKey] = - useState(""); - const [currentForcedOmikronPublicKey, setCurrentForcedOmikronPublicKey] = - useState(""); - - useEffect(() => { - load("omega_url").then((value) => { - setDraftOmegaUrl(value); - setCurrentOmegaUrl(value); - }); - load("forced_omikron_url").then((value) => { - setDraftForcedOmikronUrl(value || ""); - setCurrentForcedOmikronUrl(value || ""); - }); - load("forced_omikron_public_key").then((value) => { - setDraftForcedOmikronPublicKey(value || ""); - setCurrentForcedOmikronPublicKey(value || ""); - }); - }, [load]); - - return ( -
-

- It's best not to touch these settings! They can be exploited to gain - access to your account! -

-
- -
- setDraftOmegaUrl(event.target.value)} - /> - -
-
-
- -
- setDraftForcedOmikronUrl(event.target.value)} - /> - - setDraftForcedOmikronPublicKey(event.target.value) - } - /> - -
-
-
- ); -} diff --git a/packages/settings/src/pages/theme.tsx b/packages/settings/src/pages/theme.tsx deleted file mode 100644 index e8352e7..0000000 --- a/packages/settings/src/pages/theme.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { ThemeCustomizer } from "@methanium/ui"; - -export default function Page() { - return ( - - ); -} diff --git a/packages/settings/src/sidebar.tsx b/packages/settings/src/sidebar.tsx deleted file mode 100644 index 3a2155e..0000000 --- a/packages/settings/src/sidebar.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Button, ClearStorageButton, cn } from "@methanium/ui"; -import { useNavigate } from "@tanstack/react-router"; - -import { settingsNavigation } from "./navigation"; - -export function SettingsSidebar({ - mobile = false, - className, -}: { - mobile?: boolean; - className?: string; -}) { - const navigate = useNavigate(); - const categories = [ - ...new Set(settingsNavigation.map((page) => page.category)), - ]; - - return ( -
- {categories.map((category) => ( -
-

{category}

- {settingsNavigation - .filter((page) => page.category === category) - .map((page) => ( - - ))} -
- ))} -
- -
-
- ); -} diff --git a/packages/settings/tsconfig.json b/packages/settings/tsconfig.json deleted file mode 100644 index ce5e465..0000000 --- a/packages/settings/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "jsx": "react-jsx", - "strict": true, - "skipLibCheck": true, - "noEmit": true, - "resolveJsonModule": true, - "types": ["vite/client"] - }, - "include": ["src"] -} diff --git a/packages/shared/package.json b/packages/shared/package.json index b38160b..ab2ea1b 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -4,28 +4,23 @@ "version": "0.0.0", "type": "module", "exports": { - "./asyncQueue": "./src/asyncQueue.ts", - "./code": "./src/code.ts", - "./errors": "./src/errors.ts", "./data": "./src/data.ts", - "./desktopMedia": "./src/desktopMedia.tsx", "./log": "./src/log.tsx", - "./indexedDb": "./src/indexedDb.ts", "./settings": "./src/settings.ts", - "./sounds": "./src/sounds.ts", "./features/legal/schema": "./src/features/legal/schema.ts", "./features/conversation/schema": "./src/features/conversation/schema.ts" }, "scripts": { - "format": "pnpm exec prettier --write .", + "format": "bunx prettier --write .", "lint": "eslint src", "build": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@methanium/ui": "*", - "lucide-react": "^1.29.0", - "react": "^19.2.8", - "react-dom": "^19.2.8", - "zod": "^4.4.3" + "@tensamin/ui": "*", + "lucide-react": "^1.14.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "sonner": "^2.0.7", + "zod": "^4.3.6" } } diff --git a/packages/shared/src/asyncQueue.ts b/packages/shared/src/asyncQueue.ts deleted file mode 100644 index 60d12f3..0000000 --- a/packages/shared/src/asyncQueue.ts +++ /dev/null @@ -1,27 +0,0 @@ -export default function createAsyncQueue() { - let resolve!: (value: T) => void; - - const promise = new Promise((r) => { - resolve = r; - }); - - return { - set: resolve, - get: () => promise, - }; -} - -// eslint-disable-next-line -type AnyFn = (...args: any[]) => any; - -export function createQueuedFunc( - getFn: () => F | null | undefined, -) { - return (async (...args: Parameters): Promise>> => { - while (!getFn()) { - await new Promise((r) => setTimeout(r, 50)); - } - - return await getFn()!(...args); - }) as F; -} diff --git a/packages/shared/src/code.ts b/packages/shared/src/code.ts deleted file mode 100644 index 1f1d10e..0000000 --- a/packages/shared/src/code.ts +++ /dev/null @@ -1,20 +0,0 @@ -export function toLossySixDigitCode(input: string): string { - let firstHash = 0xdeadbeef; - let secondHash = 0x41c6ce57; - - for (let index = 0; index < input.length; index += 1) { - const character = input.charCodeAt(index); - - firstHash = Math.imul(firstHash ^ character, 2654435761); - secondHash = Math.imul(secondHash ^ character, 1597334677); - } - - firstHash = Math.imul(firstHash ^ (firstHash >>> 16), 2246822507); - firstHash ^= Math.imul(secondHash ^ (secondHash >>> 13), 3266489909); - secondHash = Math.imul(secondHash ^ (secondHash >>> 16), 2246822507); - secondHash ^= Math.imul(firstHash ^ (firstHash >>> 13), 3266489909); - - const hash = 4294967296 * (2097151 & secondHash) + (firstHash >>> 0); - - return String(hash % 1_000_000).padStart(6, "0"); -} diff --git a/packages/shared/src/data.ts b/packages/shared/src/data.ts index eed6e7e..a059e9c 100644 --- a/packages/shared/src/data.ts +++ b/packages/shared/src/data.ts @@ -1,5 +1,4 @@ import { z } from "zod"; -import type { ThemeDesign } from "@methanium/ui"; import { legalDocsSchema } from "./features/legal/schema"; import { @@ -13,503 +12,229 @@ const fileFromMessage = z.object({ type: z.enum(["image", "image_top_right", "file"]), }); -const bytesLike = z.union([ - z.instanceof(Uint8Array), - z.array(z.number().int().min(0).max(255)), - z.base64(), -]); - -const protocolBytes = z.instanceof(Uint8Array); - -function bytesFromProtocol(value: z.infer): Uint8Array { - if (value instanceof Uint8Array) return value; - if (Array.isArray(value)) return new Uint8Array(value); - - const bin = atob(value); - const out = new Uint8Array(bin.length); - for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i); - return out; -} - -const protocolBytesResponse = bytesLike.transform(bytesFromProtocol); - -const chatSecretResponse = z.object({ - UserId: z.string(), - ChatId: z.string(), - SecretId: z.string(), - VersionNumber: z.number(), - EncryptedSecret: bytesLike, - KemCiphertext: bytesLike, - WrappingScheme: z.string(), - CreatedAt: z.number(), - UpdatedAt: z.number(), -}); - -const chatSecretRecipient = z.object({ - UserId: z.string(), - EncryptedSecret: protocolBytes, - KemCiphertext: protocolBytes, -}); - -const callSecretEnvelopeResponse = z.object({ - SecretId: z.string(), - VersionNumber: z.number(), - EncryptedSecret: protocolBytesResponse, - KemCiphertext: protocolBytesResponse, - WrappingScheme: z.string(), -}); - -const callSecretEnvelopeRequest = z.object({ - SecretId: z.string(), - VersionNumber: z.number(), - EncryptedSecret: protocolBytes, - KemCiphertext: protocolBytes, - WrappingScheme: z.string(), -}); - -export const Reaction = z.object({ - Reaction: z.string(), - SenderId: z.number(), -}); - -export const Message = z.object({ - NotEncrypted: z.boolean().optional(), - SenderId: z.number(), - SendTime: z.number(), - Content: z.base64(), - Files: z.array(fileFromMessage).optional(), - Tint: z.string().length(7).startsWith("#").optional(), - Avatar: z.boolean().optional(), - Display: z.boolean().optional(), - ReplyId: z.number().optional(), - MessageState: z - .enum(["read", "received", "sent", "sending", "awaiting"]) // awaiting for 'internal' use - .default("received"), - Edited: z.boolean().optional(), - Reactions: z.array(Reaction).optional(), +export const message = z.object({ + height: z.number(), + not_encrypted: z.boolean().optional(), + sent_by_self: z.boolean().optional(), + send_time: z.number(), + content: z.base64(), + files: z.array(fileFromMessage).optional(), + tint: z.string().length(7).startsWith("#").optional(), + avatar: z.boolean().optional(), + display: z.boolean().optional(), + message_state: z + .enum(["read", "received", "sent", "sending", "awaiting"]) + .default("received"), // awaiting for 'internal' use }); export const failedUser = { - Display: "Failed", - IotaId: 0, - OmikronConnections: [], - OnlineStatus: "user_borked", - PublicKey: "", - SubEnd: 0, - SubLevel: 0, - UserId: 0, - Username: "unknown", -} as z.infer; + display: "Failed", + iota_id: 0, + omikron_connections: [], + online_status: "user_borked", + public_key: "", + sub_end: 0, + sub_level: 0, + user_id: 0, + username: "unknown", +} as z.infer; -const authPayload = z.object({ - Communities: z.array(z.object({})).default([]), - Contacts: z - .array( - z.object({ - LastMessageAt: z.number().default(0), - UserId: z.number(), - LastMessage: z - .object({ - Content: z.base64(), - SenderId: z.number(), - }) - .optional(), - Messages: z.array(Message).default([]), - }), - ) - .default([]), - Calls: z - .array( - z.object({ - CallId: z.string(), - CallSecret: callSecretEnvelopeResponse.optional(), - CallMembers: z.array(z.number()), - }), - ) - .default([]), -}); +export type Contacts = z.infer< + typeof ttp.challenge_response.response.shape.contacts +>; +export type Communities = z.infer< + typeof ttp.challenge_response.response.shape.communities +>; +export type Calls = z.infer; -const clientStateSync = authPayload.extend({ - SessionId: z.number().int().positive(), - VersionNumber: z.number().int().nonnegative(), - CacheSchemaVersion: z.number().int().nonnegative(), - SyncMode: z.enum(["full", "delta"]), - Messages: z.array(Message).default([]), - DeletedMessageIds: z.array(z.number()).default([]), - DeletedContactIds: z.array(z.number()).default([]), -}); - -export type Contacts = z.infer; -export type Communities = z.infer; -export type Calls = z.infer; - -// MTP -const userFields = { - About: z.string().max(255).optional(), - Avatar: z.string().optional(), - Display: z.string().min(1).max(15), - IotaId: z.number(), - OmikronConnections: z.array(z.number()), - OmikronId: z.number().optional(), - PublicKey: z.base64(), - Status: z.string().max(15).optional(), - SubEnd: z.number(), - SubLevel: z.number(), - UserId: z.number(), - Username: z.string().min(1).max(15), -}; - -export const publicUserStateSchema = z.enum([ - "user_online", - "user_idle", - "user_dnd", - "user_wc", - "user_offline", - "iota_offline", -]); - -export const userPresencePreferenceSchema = z.enum([ - "user_online", - "user_idle", - "user_dnd", - "user_wc", - "user_invisible", -]); - -export const clientUserStateSchema = z.union([ - publicUserStateSchema, - userPresencePreferenceSchema, -]); - -export const publicUserSchema = z.object({ - ...userFields, - OnlineStatus: z.enum([ - "user_online", - "user_idle", - "user_dnd", - "user_wc", - "user_offline", - "iota_offline", - "user_borked", - "iota_online", - "iota_borked", - ]), -}); - -export const accountUserSchema = z.object({ - ...userFields, - OnlineStatus: userPresencePreferenceSchema, -}); - -export const userSchema = z.union([publicUserSchema, accountUserSchema]); - -export const userStateEntrySchema = z.object({ - UserId: z.number().int().positive(), - UserState: publicUserStateSchema, -}); - -export const mtp = { - IdentificationResponse: { - request: z.object({}).optional(), - response: authPayload, - }, - ClientConnected: { +// TTP +export const ttp = { + identification: { request: z.object({ - SessionId: z.number().int().positive(), - VersionNumber: z.number().int().nonnegative(), - CacheValid: z.boolean(), - CacheSchemaVersion: z.number().int().nonnegative(), - }), - response: clientStateSync, - }, - ClientStateSync: { - request: z.object({}).optional(), - response: clientStateSync, - }, - ClientStateAck: { - request: z.object({ - SessionId: z.number().int().positive(), - VersionNumber: z.number().int().nonnegative(), - }), - response: z.object({}), - }, - GetUserData: { - request: z.object({ - UserId: z.number().optional(), - Username: z.string().optional(), - }), - response: userSchema, - }, - GetStates: { - request: z.object({ - SessionId: z.number().int().positive(), - UserIds: z.array(z.number().int().positive()), + version: z.string(), + session_id: z.number(), + user_id: z.number(), }), response: z.object({ - SessionId: z.number().int().positive(), - // Keep valid entries when one entry in a server snapshot is malformed. - UserStates: z.array(userStateEntrySchema.nullable().catch(null)), - MissingUserIds: z.array(z.number().int().positive()).optional(), + challenge: z.string(), + public_key: z.base64(), }), }, - ClientChanged: { - request: z.object({}).optional(), - response: z.object({ - SessionId: z.number().int().positive(), - UserId: z.number().int().positive(), - UserState: clientUserStateSchema, - }), - }, - ChangeUserData: { - request: z - .object({ - About: userFields.About, - Avatar: userFields.Avatar, - Display: userFields.Display, - OnlineStatus: userPresencePreferenceSchema, - PublicKey: userFields.PublicKey, - Status: userFields.Status, - Username: userFields.Username, - }) - .partial(), - response: z.object({}), - }, - MessageDelete: { + challenge_response: { request: z.object({ - ChatPartnerId: z.number(), - SendTime: z.number(), - }), - response: z.object({}), - }, - MessageDeleteLive: { - request: z.object({}), - response: z.object({ - ChatPartnerId: z.number(), - SendTime: z.number(), - }), - }, - MessageEditLive: { - request: z.object({}), - response: z.object({ - Content: z.base64(), - ChatPartnerId: z.number(), - SendTime: z.number(), - }), - }, - MessageEdit: { - request: z.object({ - Content: z.base64(), - ChatPartnerId: z.number(), - SendTime: z.number(), - }), - response: z.object({}), - }, - MessageReactionAdd: { - request: z.object({ - Reaction: z.string(), - SendTime: z.number(), - ChatPartnerId: z.number(), - }), - response: z.object(), - }, - MessageReactionRemove: { - request: z.object({ - Reaction: z.string(), - SendTime: z.number(), - ChatPartnerId: z.number(), - }), - response: z.object(), - }, - MessageReactionLive: { - request: z.object(), - response: z.object({ - Reaction: z.string(), - SendTime: z.number(), - ChatPartnerId: z.number(), - SenderId: z.number(), - Accepted: z.boolean(), - }), - }, - MessageLive: { - request: z.object({}).optional(), - response: z - .object({ - SenderId: z.number(), - Message: Message.extend({ SenderId: z.number().optional() }), - }) - .transform(({ SenderId, Message }) => ({ - SenderId, - Message: { ...Message, SenderId: Message.SenderId ?? SenderId }, - })), - }, - MessageGet: { - request: z.object({ - SendTime: z.number(), - }), - response: Message, - }, - MessagesGet: { - request: z.object({ - UserId: z.number(), - Amount: z.number(), - Offset: z.number(), + challenge: z.base64(), }), response: z.object({ - Messages: z.array(Message), - }), - }, - MessageSend: { - request: z.object({ - Content: z.base64(), - ReceiverId: z.number(), - SendTime: z.number(), - ReplyId: z.number().optional(), - Files: z.array(fileFromMessage).optional(), - }), - response: z.object({}), - }, - AddConversation: { - request: z.object({ - ChatPartnerId: z.number().optional(), - ChatPartnerName: z.string().min(1).max(15).optional(), - }), - response: z.object({}), - }, - MessageState: { - request: z - .object({ - ChatPartnerId: z.number(), - SendTime: z.number(), - MessageState: Message.shape.MessageState, - }) - .or( + communities: z.array(z.object({})).optional(), + contacts: z.array( z.object({ - MessageState: Message.shape.MessageState, + last_message_at: z.number(), + user_id: z.number(), + last_message: z + .object({ + content: z.base64(), + sender_id: z.number(), + }) + .optional(), + messages: z.array(message), }), ), + calls: z.array( + z.object({ + call_id: z.string(), + call_secret: z.base64().optional(), + call_members: z.array(z.number()), + }), + ), + }), + }, + get_user_data: { + request: z.object({ + user_id: z.number(), + }), response: z.object({ - ChatPartnerId: z.number(), - MessageState: Message.shape.MessageState, - SendTime: z.number(), + about: z.string().max(255).optional(), + avatar: z.string().optional(), + display: z.string().max(15), + iota_id: z.number(), + omikron_connections: z.array(z.number()), + omikron_id: z.number().optional(), + online_status: z.enum([ + "user_offline", + "user_online", + "user_dnd", + "user_idle", + "user_wc", + "user_borked", + "iota_offline", + "iota_online", + "iota_borked", + ]), + public_key: z.base64(), + status: z.string().max(15).optional(), + sub_end: z.number(), + sub_level: z.number(), + user_id: z.number(), + username: z.string().max(15), + }), + }, + ping: { + request: z.object({ + last_ping: z.number(), + }), + response: z.object({ + ping_iota: z.number(), + }), + }, + message_live: { + request: z.object({}).optional(), + response: z.object({ + sender_id: z.number(), + message, + }), + }, + messages_get: { + request: z.object({ + user_id: z.number(), + amount: z.number(), + offset: z.number(), + }), + response: z.object({ + messages: z.array(message), + }), + }, + message_send: { + request: z.object({ + height: z.number(), + content: z.base64(), + receiver_id: z.number(), + send_time: z.number(), + files: z.array(fileFromMessage).optional(), + }), + response: z.object({}), + }, + add_conversation: { + request: z.object({ + chat_partner_id: z.number().optional(), + chat_partner_name: z.string().min(1).max(15).optional(), + }), + response: z.object({}), + }, + message_state: { + request: z.object({ + chat_partner_id: z.number(), + send_time: z.number(), + message_state: message.shape.message_state, + }), + response: z.object({ + chat_partner_id: z.number(), + message_state: message.shape.message_state, + send_time: z.number(), }), }, - LoadTxtRecord: { + load_txt_record: { request: z.object({ - Path: z.string(), + path: z.string(), }), response: z.object({ - Content: z.string(), + content: z.string(), }), }, - AuthenticateApp: { + authenticate_app: { request: z.object({ - AppIdentifier: z.string(), + app_identifier: z.string(), }), response: z.object({ - Challenge: z.base64(), + challenge: z.base64(), }), }, - CreateApp: { + create_app: { request: z.object({ - AppPublicKey: z.base64(), - AppIdentifier: z.string(), + app_public_key: z.base64(), + app_identifier: z.string(), }), response: z.object({}), }, // Calls - CallToken: { + call_token: { request: z.object({ - CallId: z.string(), + call_id: z.string(), }), response: z.object({ - CallToken: z.string(), + call_token: z.string(), }), }, - CallData: { + call_data: { request: z.object({ - CallId: z.string(), + call_id: z.string(), }), response: z.object({ - UserIds: z.array(z.number()), + user_ids: z.array(z.number()), }), }, - CallInvite: { + call_invite: { request: z.object({ - CallId: z.string(), - CallSecret: callSecretEnvelopeRequest, - ReceiverId: z.number(), + call_id: z.string(), + call_secret: z.base64(), + receiver_id: z.number(), }), response: z.object({ - CallId: z.string().optional(), - CallSecret: callSecretEnvelopeResponse.optional(), - SenderId: z.number().optional(), + call_id: z.string().optional(), + call_secret: z.base64().optional(), + sender_id: z.number().optional(), }), }, - SetChatSecret: { - request: z.object({ - ChatId: z.string(), - SecretId: z.string(), - VersionNumber: z.number(), - WrappingScheme: z.string(), - CreatedAt: z.number(), - Recipients: z.array(chatSecretRecipient).min(1), - }), - response: z.object({}), - }, - GetChatSecret: { - request: z.object({ - UserId: z.string(), - ChatId: z.string(), - SecretId: z.string().optional(), - }), - response: chatSecretResponse, - }, - ChatSecretResponse: { - request: z.object({}).optional(), - response: chatSecretResponse, - }, - ChatSecretForward: { - request: z.object({ - ChatId: z.string(), - SenderUserId: z.string(), - RecipientUserId: z.string(), - SecretId: z.string(), - VersionNumber: z.number(), - EncryptedSecret: protocolBytes, - KemCiphertext: protocolBytes, - WrappingScheme: z.string(), - CreatedAt: z.number(), - }), - response: z.object({}), - }, - ErrorNoIota: { - request: z.object({}).optional(), - response: z.object({}), - }, - ErrorNotSet: { - request: z.object({}).optional(), - response: z.object({}), - }, } satisfies Record; -export type MTP = typeof mtp; +export type TTP = typeof ttp; // Storage -export type ChatPickerMediaGroup = { - id: string; - name: string; - media: string[]; -}; - export interface Storage extends SettingsStorageDefaults { session_id: number; user_id: number; - mtp_keyring: string; - onboarding_done: boolean; - onboarding_started: boolean; - tauri_permissions_done: boolean; + private_key: string; ppandtos_done: boolean; accepted_terms_of_service: boolean; accepted_privacy_policy: boolean; @@ -519,59 +244,12 @@ export interface Storage extends SettingsStorageDefaults { legal_docs: z.infer; cached_contacts: Contacts; cached_communities: Communities; - cache_contacts: number; - cache_messages_per_chat: number; - omega_url: string; - forced_omikron_url: string | undefined; - forced_omikron_public_key: string | undefined; - call_mute_range_start: number; - call_mute_range_end: number; - theme_color: string; - theme_palette: Record< - | "base00" - | "base01" - | "base02" - | "base03" - | "base04" - | "base05" - | "base06" - | "base07" - | "base08" - | "base09" - | "base0A" - | "base0B" - | "base0C" - | "base0D" - | "base0E" - | "base0F", - string - > | null; - theme_primary_color: string; - theme_polarity: "dark" | "light" | "system"; - theme_tint: "soft" | "hard" | "extreme"; - theme_border_radius: number; - theme_custom_css: string; - theme_parent: string; - theme_design: ThemeDesign; - chat_trusted_domains: string[]; - chat_picker_saved_media: string[]; - chat_picker_saved_media_groups: ChatPickerMediaGroup[]; - chat_picker_last_tab: "gif" | "meme" | "saved"; - chat_picker_size: { - width: number; - height: number; - } | null; - reactions: Record; - hotkey_overrides: Record; } export const storageDefaults: Storage = { session_id: 0, user_id: 0, - mtp_keyring: "", - onboarding_done: false, - onboarding_started: false, - tauri_permissions_done: false, + private_key: "", ppandtos_done: false, accepted_terms_of_service: false, accepted_privacy_policy: false, @@ -580,98 +258,22 @@ export const storageDefaults: Storage = { analytics_done: false, ...settingsStorageDefaults, legal_docs: { + eula: { + version: "0.0", + hash: "000000000000", + unix: 0, + }, tos: { - hash: "0000000000000000000000000000000000000000000000000000000000000000", + version: "0.0", + hash: "000000000000", + unix: 0, }, pp: { - hash: "0000000000000000000000000000000000000000000000000000000000000000", + version: "0.0", + hash: "000000000000", + unix: 0, }, }, cached_contacts: [], cached_communities: [], - cache_contacts: 5, - cache_messages_per_chat: 20, - omega_url: "https://omega.tensamin.net", - forced_omikron_url: undefined, - forced_omikron_public_key: undefined, - call_mute_range_start: -55, - call_mute_range_end: -45, - theme_color: "", - theme_palette: null, - theme_primary_color: "", - theme_polarity: "system", - theme_tint: "soft", - theme_border_radius: 0.5, - theme_custom_css: "", - theme_parent: "tensamin", - theme_design: { - density: 1, - borderWidth: 1, - shadowStrength: 0, - fontScale: 1, - headingWeight: 600, - motion: 1, - fontFamily: "public-sans", - }, - chat_trusted_domains: [ - // Other platforms - "cdn.discordapp.com", - - // Tenor - "tenor.com", - "c.tenor.com", - - // Klipy - "static.klipy.com", - "static1.klipy.com", - "static2.klipy.com", - - // Giphy - "giphy.com", - "www.giphy.com", - "media.giphy.com", - "i.giphy.com", - "media0.giphy.com", - "media1.giphy.com", - "media2.giphy.com", - "media3.giphy.com", - "media4.giphy.com", - ], - chat_picker_saved_media: [], - chat_picker_saved_media_groups: [], - chat_picker_last_tab: "gif", - chat_picker_size: null, - reactions: { - ":thumbsup:": 3, - ":fire:": 2, - ":white_check_mark:": 1, - }, - hotkey_overrides: {}, }; - -// User Status -export function getStatusColor( - status: - | z.infer - | z.infer, -) { - switch (status) { - case "user_online": - return "#22c55e"; - case "iota_online": - return "#22c55e"; - case "user_dnd": - return "#ef4444"; - case "user_idle": - return "#f59e0b"; - case "user_wc": - return "#3b82f6"; - case "user_borked": - case "iota_borked": - return "#6b7280"; - case "user_offline": - case "iota_offline": - default: - return "#9ca3af"; - } -} diff --git a/packages/shared/src/errors.ts b/packages/shared/src/errors.ts deleted file mode 100644 index ab2d058..0000000 --- a/packages/shared/src/errors.ts +++ /dev/null @@ -1,25 +0,0 @@ -export class ProtocolError extends Error { - readonly type: string; - readonly id: number | undefined; - readonly communicationType: string; - readonly requestId: number | undefined; - readonly errorType: string | undefined; - - constructor(options: { - type: string; - requestId?: number; - errorType?: string; - }) { - super( - options.errorType - ? `${options.type}: ${options.errorType}` - : options.type, - ); - this.name = "ProtocolError"; - this.type = options.type; - this.id = options.requestId; - this.communicationType = options.type; - this.requestId = options.requestId; - this.errorType = options.errorType; - } -} diff --git a/packages/shared/src/features/legal/schema.ts b/packages/shared/src/features/legal/schema.ts index 26d35b1..cbdbf4e 100644 --- a/packages/shared/src/features/legal/schema.ts +++ b/packages/shared/src/features/legal/schema.ts @@ -1,10 +1,13 @@ import { z } from "zod"; const legalDocSchema = z.object({ - hash: z.string().regex(/^[a-f0-9]{64}$/), + version: z.string().regex(/^\d+\.\d+$/), + hash: z.string().regex(/^[a-f0-9]{12}$/), + unix: z.number().int().positive(), }); export const legalDocsSchema = z.object({ + eula: legalDocSchema, tos: legalDocSchema, pp: legalDocSchema, }); diff --git a/packages/shared/src/indexedDb.ts b/packages/shared/src/indexedDb.ts deleted file mode 100644 index c043787..0000000 --- a/packages/shared/src/indexedDb.ts +++ /dev/null @@ -1,98 +0,0 @@ -export const TENSAMIN_DB_NAME = "tensamin"; -export const TENSAMIN_DB_VERSION = 1; -export type TensaminStore = "storage" | "cache" | "keys"; - -const stores: TensaminStore[] = ["storage", "cache", "keys"]; -let databasePromise: Promise | undefined; - -export function openTensaminDatabase( - indexedDb: IDBFactory = globalThis.indexedDB, -) { - databasePromise ??= new Promise((resolve, reject) => { - const request = indexedDb.open(TENSAMIN_DB_NAME, TENSAMIN_DB_VERSION); - request.onupgradeneeded = () => { - for (const store of stores) { - if (!request.result.objectStoreNames.contains(store)) { - request.result.createObjectStore(store); - } - } - }; - request.onsuccess = () => { - request.result.onversionchange = () => request.result.close(); - resolve(request.result); - }; - request.onerror = () => { - databasePromise = undefined; - reject(request.error); - }; - request.onblocked = () => { - databasePromise = undefined; - reject(new Error("The Tensamin database upgrade is blocked.")); - }; - }); - return databasePromise; -} - -export async function getDatabaseEntry(store: TensaminStore, key: string) { - const database = await openTensaminDatabase(); - return new Promise((resolve, reject) => { - const request = database - .transaction(store, "readonly") - .objectStore(store) - .get(key); - request.onsuccess = () => resolve(request.result as T | undefined); - request.onerror = () => reject(request.error); - }); -} - -export async function setDatabaseEntry( - store: TensaminStore, - key: string, - value: unknown, -) { - const database = await openTensaminDatabase(); - return new Promise((resolve, reject) => { - const transaction = database.transaction(store, "readwrite"); - transaction.objectStore(store).put(value, key); - transaction.oncomplete = () => resolve(); - transaction.onerror = () => reject(transaction.error); - transaction.onabort = () => reject(transaction.error); - }); -} - -export async function deleteDatabaseEntry(store: TensaminStore, key: string) { - const database = await openTensaminDatabase(); - return new Promise((resolve, reject) => { - const transaction = database.transaction(store, "readwrite"); - transaction.objectStore(store).delete(key); - transaction.oncomplete = () => resolve(); - transaction.onerror = () => reject(transaction.error); - transaction.onabort = () => reject(transaction.error); - }); -} - -export async function listDatabaseEntries( - store: TensaminStore, - keyPrefix: string, -) { - const database = await openTensaminDatabase(); - return new Promise>((resolve, reject) => { - const entries: Array<[string, unknown]> = []; - const cursor = database - .transaction(store, "readonly") - .objectStore(store) - .openCursor(); - cursor.onsuccess = () => { - const value = cursor.result; - if (!value) { - resolve(entries); - return; - } - if (typeof value.key === "string" && value.key.startsWith(keyPrefix)) { - entries.push([value.key, value.value]); - } - value.continue(); - }; - cursor.onerror = () => reject(cursor.error); - }); -} diff --git a/packages/shared/src/log.tsx b/packages/shared/src/log.tsx index 50ebba7..ebea635 100644 --- a/packages/shared/src/log.tsx +++ b/packages/shared/src/log.tsx @@ -1,4 +1,4 @@ -import { toast as sonnerToast } from "@methanium/ui"; +import { toast as sonnerToast } from "@tensamin/ui"; import { Ban, Check, Info, TriangleAlert } from "lucide-react"; /** @@ -15,7 +15,6 @@ export function log( | "red" | "green" | "yellow" - | "orange" | "purple" | "blue" | "cyan" @@ -33,7 +32,6 @@ export function log( red: "\x1b[31m", green: "\x1b[32m", yellow: "\x1b[33m", - orange: "\x1b[38;5;208m", purple: "\x1b[35m", blue: "\x1b[34m", cyan: "\x1b[36m", @@ -69,31 +67,26 @@ const size = 20; export function toast( type: "error" | "info" | "warn" | "success", message: string, - description?: string, ) { switch (type) { case "error": sonnerToast(message, { icon: , - description, }); break; case "info": sonnerToast(message, { icon: , - description, }); break; case "warn": sonnerToast(message, { icon: , - description, }); break; case "success": sonnerToast(message, { icon: , - description, }); break; } diff --git a/packages/shared/src/settings.ts b/packages/shared/src/settings.ts index 6fd4d2a..0099f64 100644 --- a/packages/shared/src/settings.ts +++ b/packages/shared/src/settings.ts @@ -1,14 +1,14 @@ type StringKeyOf = Extract; - - -export type SettingsSchema = Record< - string, - Record> +}; + +export type SettingsSchema = Record< + string, + Record> >; const settings = { @@ -28,71 +28,35 @@ const settings = { type: "boolean", default: false, }, - read_confirmations: { - display: "Enable Read Confirmations", - type: "boolean", - default: true, - }, - receive_confirmations: { - display: "Enable Receive Confirmations", - type: "boolean", - default: false, - }, - show_start_of_last_message_in_sidebar: { - display: "Show Start of Last Message in Sidebar", - type: "boolean", - default: true, - }, - }, - }, - call: { - call: { - call_jingle: { - display: "Jingle", - type: "select", - default: "jingle_1" as "jingle_1" | "jingle_2", - }, }, }, application: { - cache: {}, - theme: {}, licenses: {}, }, } as const satisfies SettingsSchema; -// Assemble storage defaults export default settings; -type SettingStorageEntry = { +type BooleanSettingNames = { [C in StringKeyOf]: { [P in StringKeyOf]: { - [S in StringKeyOf]: T[C][P][S] extends { default: infer V } - ? { key: `settings.${S}`; value: V } + [S in StringKeyOf]: T[C][P][S] extends { + type: "boolean"; + default: boolean; + } + ? S : never; }[StringKeyOf]; }[StringKeyOf]; }[StringKeyOf]; -type Widen = T extends boolean - ? boolean - : T extends string - ? string extends T - ? string - : T - : T extends number - ? number - : T; - export type SettingsStorageKey = - SettingStorageEntry["key"]; + `settings.${BooleanSettingNames}`; export type SettingsStorageDefaults< T extends SettingsSchema = typeof settings, > = { - [K in SettingsStorageKey]: Widen< - Extract, { key: K }>["value"] - >; + [K in SettingsStorageKey]: boolean; }; function buildSettingsStorageDefaults( @@ -103,10 +67,12 @@ function buildSettingsStorageDefaults( for (const category of Object.values(schema)) { for (const page of Object.values(category)) { for (const [settingName, setting] of Object.entries(page)) { - if ("default" in setting) { + if ( + setting.type === "boolean" && + typeof setting.default === "boolean" + ) { const key = `settings.${settingName}` as SettingsStorageKey; - defaults[key] = - setting.default as SettingsStorageDefaults[typeof key]; + defaults[key] = setting.default; } } } diff --git a/packages/shared/src/sounds.ts b/packages/shared/src/sounds.ts deleted file mode 100644 index b75713b..0000000 --- a/packages/shared/src/sounds.ts +++ /dev/null @@ -1,34 +0,0 @@ -export type SoundName = - | "call_jingle_1" - | "call_jingle_2" - | "call_join" - | "call_leave" - | "message" - | "stream_end_other" - | "stream_end_self" - | "stream_start_other" - | "stream_start_self" - | "stream_watch_end" - | "stream_watch_start"; - -function soundUrl(sound: SoundName) { - if (window.location.protocol === "file:") { - return new URL(`./sounds/${sound}.wav`, document.baseURI).href; - } - - return `/sounds/${sound}.wav`; -} - -export function playSound(sound: SoundName, loop = false) { - const audio = new Audio(soundUrl(sound)); - audio.loop = loop; - void audio.play().catch(() => undefined); - return audio; -} - -export function stopSound(audio: HTMLAudioElement | null) { - if (!audio) return; - - audio.pause(); - audio.currentTime = 0; -} diff --git a/packages/storage/package.json b/packages/storage/package.json index dce1554..ef28e8b 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -6,22 +6,18 @@ "exports": { "./session": "./src/session.tsx", "./context": "./src/context.tsx", - "./secure": "./src/secure.ts", - "./browserSecure": "./src/browserSecure.ts", - "./credentials": "./src/credentials.ts" + "./indexed-db": "./src/indexed-db.ts" }, "scripts": { - "format": "pnpm exec prettier --write .", + "format": "bunx prettier --write .", "lint": "eslint src", "build": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@methanium/ui": "*", - "@tauri-apps/api": "^2.11.1", - "@tensamin/cache": "workspace:*", - "@tensamin/mtp": "workspace:*", "@tensamin/shared": "workspace:*", - "react": "^19.2.8", - "react-dom": "^19.2.8" + "@tensamin/ttp": "workspace:*", + "@tensamin/ui": "*", + "react": "^19.2.0", + "react-dom": "^19.2.0" } } diff --git a/packages/storage/src/browserSecure.ts b/packages/storage/src/browserSecure.ts deleted file mode 100644 index 494b5f0..0000000 --- a/packages/storage/src/browserSecure.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { getDatabaseEntry } from "@tensamin/shared/indexedDb"; - -type SecureEnvelope = { - __tensaminSecure: 1; - version: 1; - iv: string; - data: string; -}; - -function base64ToBytes(value: string) { - const binary = atob(value); - return Uint8Array.from(binary, (character) => character.charCodeAt(0)); -} - -function isSecureEnvelope(value: unknown): value is SecureEnvelope { - if (!value || typeof value !== "object") return false; - const envelope = value as Partial; - return ( - envelope.__tensaminSecure === 1 && - envelope.version === 1 && - typeof envelope.iv === "string" && - typeof envelope.data === "string" - ); -} - -export async function loadSecureBrowserValue(key: string) { - const stored = await getDatabaseEntry("storage", key); - if (stored === undefined || !isSecureEnvelope(stored)) { - return stored as T | undefined; - } - - const masterKey = await getDatabaseEntry("keys", "master-v1"); - if (!masterKey) throw new Error("Secure storage key is unavailable."); - const plaintext = await crypto.subtle.decrypt( - { name: "AES-GCM", iv: base64ToBytes(stored.iv) }, - masterKey, - base64ToBytes(stored.data), - ); - return JSON.parse(new TextDecoder().decode(plaintext)) as T; -} diff --git a/packages/storage/src/context.tsx b/packages/storage/src/context.tsx index 407adfe..d25cf2e 100644 --- a/packages/storage/src/context.tsx +++ b/packages/storage/src/context.tsx @@ -1,47 +1,22 @@ -import { - createContext, - type ReactNode, - useCallback, - useContext, - useEffect, - useMemo, - useRef, - useState, -} from "react"; +import * as React from "react"; import { type Storage as StorageSchema, storageDefaults as defaults, } from "@tensamin/shared/data"; -import { - deleteDatabaseEntry, - getDatabaseEntry, - setDatabaseEntry, -} from "@tensamin/shared/indexedDb"; -import { ErrorScreen } from "@methanium/ui"; +import { getEntry, setEntry, deleteEntry } from "./indexed-db"; +import { ErrorScreen } from "@tensamin/ui"; import { log } from "@tensamin/shared/log"; -import { invoke, isTauri } from "@tauri-apps/api/core"; -import { - decodeSecureValue, - encodeSecureValue, - getSecureStorageStatus, - isSecureEnvelope, - type SecureStorageStatus, -} from "./secure"; -export type SaveOptions = { secure?: boolean }; - -export interface StorageContextValue { +interface StorageContextValue { load(key: K): Promise; save( key: K, value: StorageSchema[K], - options?: SaveOptions, ): Promise; clear: () => Promise; - secureStorage: SecureStorageStatus | null; } -const StorageContext = createContext( +const StorageContext = React.createContext( undefined, ); @@ -52,164 +27,99 @@ const isIndexedDBSupported = typeof indexedDB !== "undefined"; * @param props Parameter props. * @returns unknown. */ -export default function StorageProvider(props: { children: ReactNode }) { - const [storage, setStorage] = useState(defaults); - const storageRef = useRef(storage); - const loadedKeys = useRef(new Set()); - const loadPromises = useRef( - new Map>(), - ); - const generations = useRef(new Map()); - const [secureStorage, setSecureStorage] = - useState(null); - const secureStorageRef = useRef(null); +export default function StorageProvider(props: { children: React.ReactNode }) { + const [storage, setStorage] = React.useState(defaults); + const storageRef = React.useRef(storage); - const [error, setError] = useState(""); - const [errorDescription, setErrorDescription] = useState(""); + const [error, setError] = React.useState(""); + const [errorDescription, setErrorDescription] = React.useState(""); - useEffect(() => { - void getSecureStorageStatus().then((status) => { - secureStorageRef.current = status; - setSecureStorage(status); - }); - }, []); + React.useEffect(() => { + storageRef.current = storage; + }, [storage]); - const commit = useCallback( - (key: K, nextValue: StorageSchema[K]) => { - const next = { ...storageRef.current, [key]: nextValue }; - storageRef.current = next; - loadedKeys.current.add(key); - setStorage(next); + const loadIO = React.useCallback( + async ( + key: K, + ): Promise => { + let stored: StorageSchema[K] | undefined; + + try { + stored = await getEntry(key); + } catch (err) { + setError("Failed to load data"); + setErrorDescription( + "An error occurred while loading data from IndexedDB. Please try again.", + ); + log(0, "Storage", "red", err); + } + + if (stored !== undefined) { + setStorage((prev) => ({ ...prev, [key]: stored })); + return stored; + } + + return defaults[key]; }, [], ); - const load = useCallback( - async ( - key: K, - ): Promise => { - if (loadedKeys.current.has(key)) return storageRef.current[key]; - const pending = loadPromises.current.get(key); - if (pending) return pending as Promise; - - const generation = generations.current.get(key) ?? 0; - const request = (async () => { - try { - if (key === "mtp_keyring" && isTauri()) { - const nativeValue = await invoke("mtp_load_keyring"); - const value = (nativeValue ?? defaults[key]) as StorageSchema[K]; - if ((generations.current.get(key) ?? 0) === generation) { - commit(key, value); - } - return value; - } - const desktopStorage = window.tensaminDesktop?.secureStorage; - const desktopStatus = desktopStorage?.getStatus - ? await desktopStorage.getStatus() - : null; - const desktopValue = - desktopStatus?.available && desktopStorage?.load - ? await desktopStorage.load(String(key)) - : null; - let stored = - desktopValue === null - ? await getDatabaseEntry("storage", key) - : (JSON.parse(desktopValue) as StorageSchema[K]); - if (isSecureEnvelope(stored)) { - stored = (await decodeSecureValue(stored)) as StorageSchema[K]; - } - const value = stored ?? defaults[key]; - if ((generations.current.get(key) ?? 0) === generation) { - commit(key, value); - } - return (generations.current.get(key) ?? 0) === generation - ? value - : storageRef.current[key]; - } catch (err) { - setError("Failed to load data"); - setErrorDescription( - "An error occurred while loading local data. Please reload and try again.", - ); - log(0, "Storage", "red", err); - throw err; - } finally { - loadPromises.current.delete(key); - } - })(); - loadPromises.current.set( - key, - request as Promise, - ); - return request; - }, - [commit], - ); - - const save = useCallback( + const saveIO = React.useCallback( async ( key: K, value: StorageSchema[K], - options: SaveOptions = {}, ): Promise => { - generations.current.set(key, (generations.current.get(key) ?? 0) + 1); - if (key === "mtp_keyring" && isTauri()) { - commit(key, value); - return; - } - const desktopStorage = window.tensaminDesktop?.secureStorage; if (JSON.stringify(value) === JSON.stringify(defaults[key])) { - if (desktopStorage?.delete) - await desktopStorage.delete(String(key)).catch(() => undefined); - await deleteDatabaseEntry("storage", key); - commit(key, defaults[key]); + await deleteEntry(key); + setStorage((prev) => ({ ...prev, [key]: defaults[key] })); } else { - const status = options.secure - ? (secureStorageRef.current ?? (await getSecureStorageStatus())) - : secureStorageRef.current; - if ( - options.secure && - status?.backend === "electron-keyring" && - desktopStorage?.save - ) { - await desktopStorage.save(String(key), JSON.stringify(value)); - await deleteDatabaseEntry("storage", key); - } else { - const persisted = options.secure - ? await encodeSecureValue(value) - : value; - await setDatabaseEntry("storage", key, persisted as StorageSchema[K]); - } - commit(key, value); + await setEntry(key, value); + setStorage((prev) => ({ ...prev, [key]: value })); } }, - [commit], + [], ); - const clear = useCallback(async () => { - const keys = Object.keys(defaults) as (keyof StorageSchema)[]; - for (const key of keys) { - generations.current.set(key, (generations.current.get(key) ?? 0) + 1); - } - await Promise.all(keys.map((key) => deleteDatabaseEntry("storage", key))); - await window.tensaminDesktop?.secureStorage - ?.clear?.() - .catch(() => undefined); - loadedKeys.current.clear(); - loadPromises.current.clear(); - storageRef.current = defaults; - setStorage(defaults); - }, []); - - const value = useMemo( + const value = React.useMemo( () => ({ - load, - save, - clear, - secureStorage, + async load( + key: K, + ): Promise { + const current = storageRef.current[key]; + + if ( + current === undefined || + JSON.stringify(current) === JSON.stringify(defaults[key]) + ) { + const loadedValue = await loadIO(key); + setStorage((prev) => ({ ...prev, [key]: loadedValue })); + return loadedValue; + } + + return current; + }, + + async save( + key: K, + nextValue: StorageSchema[K], + ): Promise { + await saveIO(key, nextValue); + }, + + async clear() { + const keys = Object.keys(defaults) as (keyof StorageSchema)[]; + await Promise.all(keys.map((key) => deleteEntry(key))); + setStorage(defaults); + }, }), - [clear, load, save, secureStorage], + [loadIO, saveIO], ); + React.useEffect(() => { + // @ts-expect-error development utility + window.save = value.save; + }, [value]); + if (error !== "" && errorDescription !== "") { return ; } @@ -236,7 +146,7 @@ export default function StorageProvider(props: { children: ReactNode }) { * @returns StorageContextValue. */ export function useStorage(): StorageContextValue { - const context = useContext(StorageContext); + const context = React.useContext(StorageContext); if (!context) { throw new Error("useStorage must be used within a StorageProvider"); } diff --git a/packages/storage/src/credentials.ts b/packages/storage/src/credentials.ts deleted file mode 100644 index 7562992..0000000 --- a/packages/storage/src/credentials.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { invoke, isTauri } from "@tauri-apps/api/core"; - -import type { StorageContextValue } from "./context"; - -export function parseTuFileContent(rawFileContent: string): { - userId: number; - privateKey: string; - domain: string | null; -} { - const content = rawFileContent.trim(); - const separator = content.indexOf("::"); - if (separator <= 0 || separator !== content.lastIndexOf("::")) { - throw new Error("Invalid file"); - } - - const identity = content.slice(0, separator); - const privateKey = content.slice(separator + 2).trim(); - const [userIdValue, domain, ...extraDomainParts] = identity.split("@"); - const userId = Number(userIdValue); - if ( - !Number.isSafeInteger(userId) || - userId <= 0 || - !privateKey || - extraDomainParts.length > 0 || - (identity.includes("@") && !domain) - ) { - throw new Error("Invalid file"); - } - - return { userId, privateKey, domain: domain ?? null }; -} - -export async function persistMtpCredentials({ - storage, - userId, - keyring, - domain, -}: { - storage: Pick; - userId: number; - keyring: string; - domain?: string | null; -}) { - const omegaUrl = domain - ? `https://${domain}/` - : await storage.load("omega_url"); - if (domain) await storage.save("omega_url", omegaUrl); - - if (isTauri()) { - const [forcedOmikronUrl, forcedOmikronPublicKey] = await Promise.all([ - storage.load("forced_omikron_url"), - storage.load("forced_omikron_public_key"), - ]); - await invoke("mtp_store_credentials", { - config: { - userId, - keyring, - omegaUrl, - forcedOmikronUrl, - forcedOmikronPublicKey, - }, - }); - } - - await storage.save("mtp_keyring", keyring, { secure: true }); - await storage.save("session_id", Date.now()); - await storage.save("user_id", userId); -} diff --git a/packages/storage/src/indexed-db.ts b/packages/storage/src/indexed-db.ts new file mode 100644 index 0000000..ebdc58d --- /dev/null +++ b/packages/storage/src/indexed-db.ts @@ -0,0 +1,92 @@ +import type { Storage as StorageSchema } from "@tensamin/shared/data"; + +const DB_NAME = "tensamin"; +const DB_VERSION = 1; +const STORE_NAME = "storage"; + +let dbPromise: Promise | null = null; + +/** + * Executes openDB. + * @param none This function has no parameters. + * @returns Promise. + */ +function openDB(): Promise { + if (dbPromise) return dbPromise; + + dbPromise = new Promise((resolve, reject) => { + const request = indexedDB.open(DB_NAME, DB_VERSION); + + request.onupgradeneeded = () => { + const db = request.result; + if (!db.objectStoreNames.contains(STORE_NAME)) { + db.createObjectStore(STORE_NAME); + } + }; + + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + + return dbPromise; +} + +/** + * Executes getEntry. + * @param key Parameter key. + * @returns Promise. + */ +export async function getEntry( + key: K, +): Promise { + const db = await openDB(); + return new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readonly"); + const store = tx.objectStore(STORE_NAME); + const request = store.get(key as string); + + request.onsuccess = () => + resolve(request.result as StorageSchema[K] | undefined); + request.onerror = () => reject(request.error); + }); +} + +/** + * Executes setEntry. + * @param key Parameter key. + * @param value Parameter value. + * @returns Promise. + */ +export async function setEntry( + key: K, + value: StorageSchema[K], +): Promise { + const db = await openDB(); + return new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readwrite"); + const store = tx.objectStore(STORE_NAME); + const request = store.put(value, key as string); + + request.onsuccess = () => resolve(); + request.onerror = () => reject(request.error); + }); +} + +/** + * Executes deleteEntry. + * @param key Parameter key. + * @returns Promise. + */ +export async function deleteEntry( + key: K, +): Promise { + const db = await openDB(); + return new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readwrite"); + const store = tx.objectStore(STORE_NAME); + const request = store.delete(key as string); + + request.onsuccess = () => resolve(); + request.onerror = () => reject(request.error); + }); +} diff --git a/packages/storage/src/secure.ts b/packages/storage/src/secure.ts deleted file mode 100644 index 367c494..0000000 --- a/packages/storage/src/secure.ts +++ /dev/null @@ -1,154 +0,0 @@ -import type {} from "@tensamin/shared/desktopMedia"; -import { isTauri } from "@tauri-apps/api/core"; -import { getDatabaseEntry, setDatabaseEntry } from "@tensamin/shared/indexedDb"; - -export type SecureStorageStatus = { - backend: - "electron-keyring" | "application-storage" | "webcrypto" | "indexeddb"; - secure: boolean; - reason?: string; -}; - -type SecureEnvelope = { - __tensaminSecure: 1; - version: 1; - iv: string; - data: string; -}; - -const MASTER_KEY_NAME = "master-v1"; -let keyPromise: Promise | undefined; - -function bytesToBase64(value: Uint8Array) { - let binary = ""; - for (const byte of value) binary += String.fromCharCode(byte); - return btoa(binary); -} - -function base64ToBytes(value: string) { - const binary = atob(value); - return Uint8Array.from(binary, (character) => character.charCodeAt(0)); -} - -async function loadBrowserKey() { - const existing = await getDatabaseEntry("keys", MASTER_KEY_NAME); - if (existing) return existing; - - const key = await crypto.subtle.generateKey( - { name: "AES-GCM", length: 256 }, - false, - ["encrypt", "decrypt"], - ); - await setDatabaseEntry("keys", MASTER_KEY_NAME, key); - return key; -} - -async function loadElectronKey() { - const storage = window.tensaminDesktop?.secureStorage; - if (!storage?.getStatus || !storage.load || !storage.save) return null; - const status = await storage.getStatus(); - if (!status.available) return null; - - let encoded = await storage.load(MASTER_KEY_NAME); - if (encoded === null) { - encoded = bytesToBase64(crypto.getRandomValues(new Uint8Array(32))); - await storage.save(MASTER_KEY_NAME, encoded); - } - return crypto.subtle.importKey( - "raw", - base64ToBytes(encoded), - "AES-GCM", - false, - ["encrypt", "decrypt"], - ); -} - -async function getKey() { - keyPromise ??= (async () => { - if (!globalThis.crypto?.subtle || typeof indexedDB === "undefined") { - return null; - } - if ( - typeof window !== "undefined" && - window.tensaminDesktop?.secureStorage - ) { - return loadElectronKey(); - } - return loadBrowserKey(); - })().catch(() => null); - return keyPromise; -} - -export function isSecureEnvelope(value: unknown): value is SecureEnvelope { - if (!value || typeof value !== "object") return false; - const envelope = value as Partial; - return ( - envelope.__tensaminSecure === 1 && - envelope.version === 1 && - typeof envelope.iv === "string" && - typeof envelope.data === "string" - ); -} - -export async function encodeSecureValue(value: unknown): Promise { - if (isTauri()) return value; - const key = await getKey(); - if (!key) return value; - const iv = crypto.getRandomValues(new Uint8Array(12)); - const plaintext = new TextEncoder().encode(JSON.stringify(value)); - const encrypted = await crypto.subtle.encrypt( - { name: "AES-GCM", iv }, - key, - plaintext, - ); - return { - __tensaminSecure: 1, - version: 1, - iv: bytesToBase64(iv), - data: bytesToBase64(new Uint8Array(encrypted)), - } satisfies SecureEnvelope; -} - -export async function decodeSecureValue(value: unknown): Promise { - if (!isSecureEnvelope(value)) return value; - const key = await getKey(); - if (!key) throw new Error("Secure storage key is unavailable."); - const plaintext = await crypto.subtle.decrypt( - { name: "AES-GCM", iv: base64ToBytes(value.iv) }, - key, - base64ToBytes(value.data), - ); - return JSON.parse(new TextDecoder().decode(plaintext)) as unknown; -} - -export async function getSecureStorageStatus(): Promise { - const desktop = - typeof window === "undefined" - ? undefined - : window.tensaminDesktop?.secureStorage; - if (desktop?.getStatus) { - const status = await desktop.getStatus(); - if (status.available) return { backend: "electron-keyring", secure: true }; - return { - backend: "indexeddb", - secure: false, - reason: - status.backend === "basic_text" - ? "The operating system keyring is unavailable." - : "Electron secure storage is unavailable.", - }; - } - if (isTauri()) return { backend: "application-storage", secure: true }; - return (await getKey()) - ? { backend: "webcrypto", secure: true } - : { - backend: "indexeddb", - secure: false, - reason: "This browser cannot protect local credentials with WebCrypto.", - }; -} - -export const secureValueCodec = { - encode: encodeSecureValue, - decode: decodeSecureValue, -}; diff --git a/packages/storage/src/session.tsx b/packages/storage/src/session.tsx index 394718b..03b3826 100644 --- a/packages/storage/src/session.tsx +++ b/packages/storage/src/session.tsx @@ -1,4 +1,4 @@ -import { useMTP } from "@tensamin/mtp"; +import { useTTP } from "@tensamin/ttp"; import { createContext, type ReactNode, @@ -8,56 +8,33 @@ import { } from "react"; import { useStorage } from "./context"; import type { Contacts, Communities, Calls } from "@tensamin/shared/data"; -import { createCache } from "@tensamin/cache"; -import { secureValueCodec } from "./secure"; interface SessionContextType { contacts: Contacts; communities: Communities; calls: Calls; moveUserIdToTop: (userId: number) => void; - insertContact: (userId: number) => void; - insertCall: (call: Calls[number]) => void; } const SessionContext = createContext(undefined); export default function SessionProvider({ children }: { children: ReactNode }) { - const { freshContacts, freshCommunities, freshCalls, contextReady } = - useMTP(); + const { freshContacts, freshCommunities, freshCalls } = useTTP(); const { load, save } = useStorage(); const [contacts, setContacts] = useState([]); const [communities, setCommunities] = useState([]); - const [localCalls, setLocalCalls] = useState([]); - const [accountId, setAccountId] = useState(null); - const calls = [ - ...freshCalls, - ...localCalls.filter( - (call) => !freshCalls.some((fresh) => fresh.CallId === call.CallId), - ), - ]; + // Get cached data and merge fresh data useEffect(() => { - void load("user_id").then(setAccountId); - }, [load]); - - // Cached contacts seed the session, then authenticated server data replaces them. - useEffect(() => { - if (!accountId) return; - const cache = createCache(String(accountId), { - codec: secureValueCodec, + load("cached_contacts").then((cachedData) => { + setContacts([ + ...freshContacts, + ...cachedData.filter( + (item) => + !freshContacts.some((fresh) => fresh.user_id === item.user_id), + ), + ]); }); - void cache.contacts.get().then((cached) => { - if (cached) setContacts(cached); - }); - }, [accountId]); - - useEffect(() => { - if (!accountId || !contextReady) return; - setContacts(freshContacts); - }, [accountId, contextReady, freshContacts]); - - useEffect(() => { load("cached_communities").then((cachedData) => { if (cachedData && freshCommunities) { setCommunities([ @@ -71,7 +48,12 @@ export default function SessionProvider({ children }: { children: ReactNode }) { ]); } }); - }, [load, freshCommunities]); + }, [load, freshContacts, freshCommunities]); + + // Save data + useEffect(() => { + save("cached_contacts", contacts); + }, [contacts, save]); useEffect(() => { save("cached_communities", communities); }, [communities, save]); @@ -79,54 +61,17 @@ export default function SessionProvider({ children }: { children: ReactNode }) { const moveUserIdToTop = (userId: number) => { setContacts((prevContacts) => { const userIndex = prevContacts.findIndex( - (contact) => contact.UserId === userId, + (contact) => contact.user_id === userId, ); if (userIndex === -1) return prevContacts; - const user = prevContacts[userIndex]; - return [ - user, - ...prevContacts.slice(0, userIndex), - ...prevContacts.slice(userIndex + 1), - ]; - }); - }; - - const insertContact = (userId: number) => { - setContacts((prevContacts) => { - if (prevContacts.some((contact) => contact.UserId === userId)) { - return prevContacts; - } - - const newUser = { - UserId: userId, - LastMessageAt: new Date().getTime(), - Messages: [], - } satisfies Contacts[0]; - - return [newUser, ...prevContacts]; - }); - }; - - const insertCall = (call: Calls[number]) => { - setLocalCalls((prevCalls) => { - if (prevCalls.some((prevCall) => prevCall.CallId === call.CallId)) { - return prevCalls; - } - - return [call, ...prevCalls]; + const [user] = prevContacts.splice(userIndex, 1); + return [user, ...prevContacts]; }); }; return ( {children} diff --git a/packages/storage/todo.md b/packages/storage/todo.md index 244fc2c..761fd8e 100644 --- a/packages/storage/todo.md +++ b/packages/storage/todo.md @@ -1 +1,2 @@ - Add `notifications: number` to contacts +- Store private key in a device bound session diff --git a/packages/tauth/package.json b/packages/tauth/package.json index 1112ad4..3f5044a 100644 --- a/packages/tauth/package.json +++ b/packages/tauth/package.json @@ -7,20 +7,24 @@ "./context": "./src/context.tsx" }, "scripts": { - "format": "pnpm exec prettier --write .", + "format": "bunx prettier --write .", "lint": "eslint src", "build": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@methanium/ui": "*", - "@tanstack/react-router": "^1.170.21", + "@tanstack/react-router": "^1.0.0", + "@tauri-apps/api": "^2.10.1", "@tensamin/crypto": "workspace:*", - "@tensamin/mtp": "workspace:*", "@tensamin/shared": "workspace:*", "@tensamin/storage": "workspace:*", "@tensamin/tauri": "workspace:*", + "@tensamin/ttp": "workspace:*", + "@tensamin/ui": "*", "@tensamin/user": "workspace:*", - "react": "^19.2.8", - "react-dom": "^19.2.8" + "lucide-react": "^1.8.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "sonner": "^2.0.7", + "zod": "^4.3.6" } } diff --git a/packages/tauth/src/context.tsx b/packages/tauth/src/context.tsx index 2a0203d..faa00bd 100644 --- a/packages/tauth/src/context.tsx +++ b/packages/tauth/src/context.tsx @@ -1,6 +1,5 @@ -import { type ReactNode } from "react"; -/** import { useEffect, useState, type ReactNode } from "react"; +import { useCrypto } from "@tensamin/crypto/context"; import { useUser } from "@tensamin/user/context"; import { useStorage } from "@tensamin/storage/context"; import { @@ -11,60 +10,47 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@methanium/ui"; -import { useLocation, useNavigate } from "@tanstack/react-router"; +} from "@tensamin/ui"; +import { useLocation } from "@tanstack/react-router"; import { useDeeplinks } from "@tensamin/tauri/deeplinkHandler"; -import { useMTP } from "@tensamin/mtp"; +import { useTTP } from "@tensamin/ttp"; import { log, toast } from "@tensamin/shared/log"; import { Loader2 } from "lucide-react"; import { isTauri } from "@tauri-apps/api/core"; -*/ +import { decryptText } from "@tensamin/crypto/worker"; export default function Wrapper({ children }: { children: ReactNode }) { - /** const { get } = useUser(); const { load } = useStorage(); - const { send } = useMTP(); + const { send } = useTTP(); + const { getSharedSecret } = useCrypto(); const { searchStr } = useLocation(); - const navigate = useNavigate(); const [dialogOpen, setDialogOpen] = useState(false); const [loading, setLoading] = useState(false); const [identifier, setIdentifier] = useState(null); const [redirect, setRedirect] = useState(null); const [challenge, setChallenge] = useState(null); - const [appPublicKey, setAppPublicKey] = useState(null); - const [sessionId, setSessionId] = useState(null); const [allowChildern, setAllowChildern] = useState(false); const { deeplinks } = useDeeplinks(); const authorizeApp = async () => { - if (!identifier || !redirect || !challenge || !appPublicKey) return; + if (!identifier || !redirect || !challenge) return; try { // Get Data const user = await get(await load("user_id")); const { - data: { Content: appPublicKeyHash }, - } = await send("LoadTxtRecord", { - Path: "tauth." + identifier, + data: { content: appPublicKey }, + } = await send("load_txt_record", { + path: "tauth." + identifier, }); - const verifiedAppPublicKeyHash = await sha256Hex(appPublicKey); - if (normalizeHash(appPublicKeyHash) !== verifiedAppPublicKeyHash) { - log(1, "tauth", "red", "App public key hash mismatch", undefined, { - appPublicKey, - appPublicKeyHash, - verifiedAppPublicKeyHash, - }); - throw new Error("App public key hash mismatch"); - } - // Get Shared Secret const sharedSecret = await getSharedSecret( - await load("mtp_keyring"), - user.PublicKey, + await load("private_key"), + user.public_key, appPublicKey, ).catch((err) => { log(1, "tauth", "red", "Failed to get shared secret", err, { @@ -88,15 +74,14 @@ export default function Wrapper({ children }: { children: ReactNode }) { finalUrl.searchParams.set("userId", String(await load("user_id"))); finalUrl.searchParams.set("challenge", solvedChallenge); finalUrl.searchParams.set("originalChallenge", challenge); - finalUrl.searchParams.set( - "sessionId", - String(sessionId || new Date().getTime()), - ); + + const session = Date.now(); + finalUrl.searchParams.set("sessionId", String(session)); // Save Session - await send("CreateApp", { - AppPublicKey: appPublicKey, - AppIdentifier: identifier, + await send("create_app", { + app_public_key: appPublicKey, + app_identifier: identifier, }); // Open Redirect URL @@ -107,15 +92,11 @@ export default function Wrapper({ children }: { children: ReactNode }) { setIdentifier(null); setRedirect(null); setChallenge(null); - setAppPublicKey(null); - setSessionId(null); toast("success", "App authorized successfully"); return; } else { - navigate({ - href: finalUrl.toString(), - }); + window.location.href = finalUrl.toString(); return; } } catch (err) { @@ -127,28 +108,9 @@ export default function Wrapper({ children }: { children: ReactNode }) { setIdentifier(null); setRedirect(null); setChallenge(null); - setAppPublicKey(null); - setSessionId(null); } }; - async function sha256Hex(value: string): Promise { - const hash = await crypto.subtle.digest( - "SHA-256", - new TextEncoder().encode(value), - ); - return [...new Uint8Array(hash)] - .map((byte) => byte.toString(16).padStart(2, "0")) - .join(""); - } - - function normalizeHash(value: string): string { - return value - .trim() - .toLowerCase() - .replace(/^sha256[:=]/, ""); - } - function hexToBase64(hex: string): string { const bytes = hex.match(/.{2}/g)?.map((byte) => parseInt(byte, 16)) ?? []; const binaryString = String.fromCharCode(...bytes); @@ -160,9 +122,8 @@ export default function Wrapper({ children }: { children: ReactNode }) { const identifier = params.get("identifier"); const redirect = params.get("redirect"); const challenge = params.get("challenge"); - const appPublicKey = params.get("public_key"); - const urlSessionId = params.get("sessionId"); if (!identifier || !redirect) { + // eslint-disable-next-line setAllowChildern(true); return; } @@ -171,27 +132,14 @@ export default function Wrapper({ children }: { children: ReactNode }) { if (!challenge) { const newUrl = new URL(redirect); newUrl.searchParams.set("userId", String(userId)); - newUrl.searchParams.set( - "sessionId", - String(urlSessionId || new Date().getTime()), - ); window.location.href = newUrl.toString(); return; } - if (!appPublicKey) { - toast("error", "Missing app public key"); - log(1, "tauth", "red", "Missing app public key"); - setAllowChildern(true); - return; - } - setAllowChildern(true); setIdentifier(identifier); setRedirect(redirect); setChallenge(hexToBase64(challenge || "")); - setAppPublicKey(appPublicKey); - setSessionId(urlSessionId); setDialogOpen(true); }); }, [searchStr, load]); @@ -202,15 +150,11 @@ export default function Wrapper({ children }: { children: ReactNode }) { const url = new URL(link); const identifier = url.searchParams.get("identifier"); const redirect = url.searchParams.get("redirect"); - const appPublicKey = url.searchParams.get("public_key"); - const urlSessionId = url.searchParams.get("sessionId"); - if (identifier && redirect && appPublicKey) { + if (identifier && redirect) { setIdentifier(identifier); setRedirect(redirect); setChallenge(hexToBase64(url.searchParams.get("challenge") || "")); - setAppPublicKey(appPublicKey); - setSessionId(urlSessionId); setDialogOpen(true); } } @@ -227,8 +171,6 @@ export default function Wrapper({ children }: { children: ReactNode }) { setIdentifier(null); setRedirect(null); setChallenge(null); - setAppPublicKey(null); - setSessionId(null); } }} > @@ -264,8 +206,6 @@ export default function Wrapper({ children }: { children: ReactNode }) { setIdentifier(null); setRedirect(null); setChallenge(null); - setAppPublicKey(null); - setSessionId(null); }} > Deny @@ -276,6 +216,4 @@ export default function Wrapper({ children }: { children: ReactNode }) { {allowChildern && children} ); - */ - return children; } diff --git a/packages/ttp/package.json b/packages/ttp/package.json new file mode 100644 index 0000000..ed09f35 --- /dev/null +++ b/packages/ttp/package.json @@ -0,0 +1,30 @@ +{ + "name": "@tensamin/ttp", + "private": true, + "version": "0.0.0", + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "format": "bunx prettier --write .", + "lint": "eslint src --ext .ts,.tsx", + "build": "tsc -p tsconfig.json --noEmit" + }, + "dependencies": { + "@tensamin/ttp-core": "*", + "@tanstack/react-router": "^1.0.0", + "@tensamin/crypto": "workspace:*", + "@tensamin/storage": "workspace:*", + "@tensamin/shared": "workspace:*", + "@tensamin/ui": "*", + "@tauri-apps/api": "^2", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "tauri-plugin-app-events-api": "^0.2.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "eslint": "^10.0.3" + } +} diff --git a/packages/ttp/src/context.tsx b/packages/ttp/src/context.tsx new file mode 100644 index 0000000..f1c30aa --- /dev/null +++ b/packages/ttp/src/context.tsx @@ -0,0 +1,681 @@ +import { + useState, + createContext, + type ReactNode, + useRef, + useEffect, + useContext, + useCallback, + useMemo, +} from "react"; +import { useCrypto } from "@tensamin/crypto/context"; +import { log } from "@tensamin/shared/log"; +import { useStorage } from "@tensamin/storage/context"; +import { + createTransportClient, + READY_STATE, + type BoundSendFn, +} from "@tensamin/ttp-core"; +import { isTauri } from "@tauri-apps/api/core"; +import { onResume } from "tauri-plugin-app-events-api"; +import type { PushHandler } from "@tensamin/ttp-core"; +import { + PING_INTERVAL, + RECONNECT_RESET, + RECONNECT_TRIES, + RETRY_INTERVAL, + TRANSPORT_URL, +} from "./values"; +import { + type Calls, + type Communities, + type Contacts, + ttp as schemas, + type TTP as Schemas, +} from "@tensamin/shared/data"; +import { LoadingScreen as Loading } from "@tensamin/ui"; +import { ErrorScreen } from "@tensamin/ui"; + +import { version } from "../../../package.json"; +import { decryptText } from "@tensamin/crypto/worker"; + +const FATAL_IDENTIFICATION_ERROR_TYPES = new Set([ + "error", + "error_invalid_user_id", + "error_no_user_id", + "error_invalid_challenge", + "error_invalid_secret", + "error_invalid_private_key", + "error_invalid_public_key", + "error_not_authenticated", +]); + +/** + * Detects whether an error chain contains a STOP_SENDING transport signal. + * @param error Unknown error value from transport operations. + * @returns True when the error represents a STOP_SENDING condition. + */ +function isStopSendingError(error: unknown) { + if (typeof error === "string") { + return error.includes("STOP_SENDING"); + } + + if (error instanceof Error) { + if (error.message.includes("STOP_SENDING")) { + return true; + } + + const errorWithCause = error as Error & { cause?: unknown }; + if (errorWithCause.cause !== undefined) { + return isStopSendingError(errorWithCause.cause); + } + + return false; + } + + if (typeof error === "object" && error !== null) { + const maybeMessage = (error as { message?: unknown }).message; + if (typeof maybeMessage === "string") { + return maybeMessage.includes("STOP_SENDING"); + } + } + + return false; +} + +/** + * Classifies identification errors that should be treated as terminal. + * @param error Unknown error raised during identification. + * @returns True when identification should fail without retry. + */ +function isFatalIdentificationError(error: unknown) { + if (typeof error === "object" && error !== null && "type" in error) { + const type = (error as { type?: unknown }).type; + if ( + typeof type === "string" && + FATAL_IDENTIFICATION_ERROR_TYPES.has(type) + ) { + return true; + } + } + + if (!(error instanceof Error)) { + return false; + } + + if ( + error.message.includes("Missing or invalid user id") || + error.message.includes("Missing private key") || + error.message.includes("Identification challenge was rejected") || + error.message.includes("timed out after") || + error.message.includes("Response validation failed") + ) { + return true; + } + + return false; +} + +/** + * Extracts structured protocol error details for identification logging. + * @param error Unknown error raised during identification. + * @returns Structured protocol error details when available. + */ +function getProtocolErrorDetails(error: unknown) { + if (typeof error !== "object" || error === null) { + return null; + } + + if (!("type" in error)) { + return null; + } + + const protocolError = error as { + id?: unknown; + type?: unknown; + data?: unknown; + }; + + return { + id: protocolError.id, + type: protocolError.type, + data: protocolError.data, + }; +} + +function isTauriMobile() { + return isTauri() && /Android|iPhone|iPad|iPod/.test(navigator.userAgent); +} + +type ContextType = { + send: BoundSendFn; + subscribePush: (handler: PushHandler) => () => void; + readyState: number; + ownPing: number; + iotaPing: number; + identified: boolean; + freshContacts: Contacts; + freshCommunities: Communities; + freshCalls: Calls; +}; + +const TTPContext = createContext(undefined); + +/** + * Provides ttp transport state and authenticated send operations to children. + * @param props Component props with children. + * @returns Loading, error, or provider-wrapped JSX. + */ +export function Provider(props: { + children: ReactNode; + blockConnection?: boolean; +}) { + const { load } = useStorage(); + const { decrypt, getSharedSecret } = useCrypto(); + + const [readyState, setReadyState] = useState(READY_STATE.CLOSED); + const [connected, setConnected] = useState(false); + const [identified, setIdentified] = useState(false); + const [identifying, setIdentifying] = useState(false); + + const [ownPing, setOwnPing] = useState(0); + const [iotaPing, setIotaPing] = useState(0); + + const [error, setError] = useState(""); + const [errorDescription, setErrorDescription] = useState(""); + + const [freshCommunities, setFreshCommunities] = useState([]); + const [freshContacts, setFreshContacts] = useState([]); + const [freshCalls, setFreshCalls] = useState([]); + + const clientRef = useRef + > | null>(null); + const identificationStartedRef = useRef(false); + + /** + * Sends typed protocol messages through the active transport client. + * @param type Protocol message type. + * @param data Optional request payload. + * @param options Optional request id. + * @returns A promise for the typed message payload. + */ + const send: BoundSendFn = useMemo( + () => + (( + type: string, + data?: Record, + options?: { id?: number }, + ) => { + const client = clientRef.current; + + if (!client) { + return Promise.reject(new Error("ttp is not connected")); + } + + return client.send(type as keyof Schemas & string, data as never, { + ...options, + }); + }) as BoundSendFn, + [], + ); + + /** + * Subscribes to unsolicited push events from the active transport client. + * @param handler Callback invoked for each push message. + * @returns Unsubscribe function. + */ + const subscribePush = useCallback((handler: PushHandler) => { + const client = clientRef.current; + + if (!client) { + return () => {}; + } + + return client.subscribePush(handler); + }, []); + + useEffect(() => { + if (!connected || !identified) { + return; + } + + const interval = setInterval(async () => { + try { + const originalNow = Date.now(); + + const data = await send("ping", { + last_ping: originalNow, + }); + + const travelTime = Date.now() - originalNow; + setOwnPing(travelTime); + + const remotePing = data.data.ping_iota; + if (typeof remotePing === "number") { + setIotaPing(remotePing); + } + } catch (intervalError) { + log(1, "ttp", "yellow", "Ping failed", intervalError); + } + }, PING_INTERVAL); + + return () => { + clearInterval(interval); + }; + }, [connected, identified, send]); + + useEffect(() => { + let attempts = 0; + let reconnectTimer: ReturnType | null = null; + let reconnectResetTimer: ReturnType | null = null; + let reconnectScheduled = false; + let disposed = false; + let resumeListenerRegistered = false; + let currentReadyState: number = READY_STATE.CLOSED; + + /** + * Clears any scheduled reconnect timeout and resets scheduling flags. + * @returns Void. + */ + const clearReconnectTimer = () => { + if (!reconnectTimer) { + return; + } + + clearTimeout(reconnectTimer); + reconnectTimer = null; + reconnectScheduled = false; + }; + + /** + * Clears the stability timer that resets reconnect attempt counters. + * @returns Void. + */ + const clearReconnectResetTimer = () => { + if (!reconnectResetTimer) { + return; + } + + clearTimeout(reconnectResetTimer); + reconnectResetTimer = null; + }; + + /** + * Starts the stability timer that resets reconnect attempts after uptime. + * @returns Void. + */ + const scheduleReconnectReset = () => { + clearReconnectResetTimer(); + reconnectResetTimer = setTimeout(() => { + attempts = 0; + reconnectResetTimer = null; + }, RECONNECT_RESET * 1_000); + }; + + /** + * Schedules a delayed reconnect attempt unless retries are exhausted. + * @param reason Optional reason for reconnect scheduling. + * @returns Void. + */ + const scheduleReconnect = (reason?: unknown) => { + if (disposed || reconnectScheduled) { + return; + } + + if (attempts >= RECONNECT_TRIES) { + setError("Connection Failed"); + setErrorDescription( + "Unable to connect to the server after multiple attempts. Please check your internet connection or try again later.", + ); + log(0, "ttp", "red", "Reconnection attempts exhausted", reason); + return; + } + + attempts += 1; + reconnectScheduled = true; + reconnectTimer = setTimeout(() => { + reconnectScheduled = false; + reconnectTimer = null; + void connect(); + }, RETRY_INTERVAL); + }; + + const transportClient = !props.blockConnection + ? createTransportClient(schemas, { + url: TRANSPORT_URL, + onReadyStateChange: (state) => { + currentReadyState = state; + setReadyState(state); + + if (state === READY_STATE.OPEN) { + clearReconnectTimer(); + scheduleReconnectReset(); + identificationStartedRef.current = false; + setConnected(true); + setIdentified(false); + setError(""); + setErrorDescription(""); + return; + } + + clearReconnectResetTimer(); + identificationStartedRef.current = false; + setConnected(false); + setIdentified(false); + setIdentifying(false); + }, + onClose: ({ error: closeError, intentional }) => { + clearReconnectResetTimer(); + setConnected(false); + setIdentified(false); + setIdentifying(false); + + if (disposed || intentional) { + return; + } + + log(0, "ttp", "red", "Disconnected", closeError); + scheduleReconnect(closeError); + }, + }) + : null; + + clientRef.current = transportClient; + + /** + * Establishes the transport connection and schedules reconnect on failures. + * @returns Promise that resolves after one connection attempt. + */ + async function connect() { + if (disposed) { + return; + } + + try { + await transportClient?.connect(TRANSPORT_URL); + } catch (connectError) { + if (disposed) { + return; + } + + log(0, "ttp", "red", "Connection attempt failed", connectError); + scheduleReconnect(connectError); + } + } + + /** + * Starts a reconnect after resume only when the transport is disconnected. + * @returns Promise that resolves after any needed resume reconnect starts. + */ + async function reconnectAfterResume() { + if (disposed || !transportClient) { + return; + } + + if ( + currentReadyState === READY_STATE.OPEN || + currentReadyState === READY_STATE.CONNECTING + ) { + return; + } + + clearReconnectTimer(); + clearReconnectResetTimer(); + attempts = 0; + reconnectScheduled = false; + setError(""); + setErrorDescription(""); + + await connect(); + } + + void connect(); + + if (!props.blockConnection && isTauriMobile()) { + resumeListenerRegistered = true; + onResume(() => { + void reconnectAfterResume(); + }); + } + + return () => { + disposed = true; + clearReconnectTimer(); + clearReconnectResetTimer(); + + if (resumeListenerRegistered) { + onResume(); + } + + if (clientRef.current === transportClient) { + clientRef.current = null; + } + + void transportClient?.close("context-dispose"); + currentReadyState = READY_STATE.CLOSED; + setReadyState(READY_STATE.CLOSED); + setConnected(false); + setIdentified(false); + setIdentifying(false); + identificationStartedRef.current = false; + }; + }, [props.blockConnection]); + + useEffect(() => { + if (!connected) { + identificationStartedRef.current = false; + return; + } + + if (identificationStartedRef.current) { + return; + } + + identificationStartedRef.current = true; + let cancelled = false; + setIdentifying(true); + setIdentified(false); + + /** + * Executes the challenge-response identification handshake. + * @returns Promise that resolves when identification flow completes. + */ + const identify = async () => { + try { + const sessionId = await load("session_id"); + const userId = await load("user_id"); + const privateKey = await load("private_key"); + + if ( + !Number.isSafeInteger(userId) || + userId <= 0 || + privateKey.trim() === "" || + !Number.isSafeInteger(sessionId) || + sessionId <= 0 + ) { + throw new Error("Invalid credentials"); + } + + // Challenge Request + const challengeEnvelope = await send("identification", { + version, + session_id: sessionId, + user_id: userId, + }).catch((challengeError) => { + throw new Error( + "Failed to obtain identification challenge from server", + challengeError, + ); + }); + + // Shared Secret + const sharedSecret = await getSharedSecret( + privateKey, + "", + challengeEnvelope.data.public_key, + ).catch((secretError) => { + throw new Error( + "Failed to derive shared secret for identification", + secretError, + ); + }); + + // Challenge Decryption + const decryptedChallenge = await decryptText( + sharedSecret, + challengeEnvelope.data.challenge, + ).catch((decryptionError) => { + throw new Error( + "Failed to decrypt identification challenge: " + + String(decryptionError), + ); + }); + + // Challenge Response + const finalResponse = await send("challenge_response", { + challenge: decryptedChallenge, + }).catch((error) => { + setError("Identification Failed"); + setErrorDescription( + "Unable to complete secure identification. Please verify your credentials and try again.", + ); + throw error; + }); + + // Data handling + setFreshContacts(finalResponse.data.contacts); + setFreshCommunities(finalResponse.data.communities); + setFreshCalls(finalResponse.data.calls); + if (cancelled) { + return; + } + + setError(""); + setErrorDescription(""); + setIdentified(true); + } catch (identificationError) { + if (cancelled) { + return; + } + + if (isStopSendingError(identificationError)) { + setError("Connection closed"); + setErrorDescription( + "The connection was forcefully closed by the Omikron.", + ); + setIdentified(false); + return; + } + + const isFatal = isFatalIdentificationError(identificationError); + const protocolErrorDetails = + getProtocolErrorDetails(identificationError); + + log( + isFatal ? 0 : 1, + "ttp", + isFatal ? "red" : "yellow", + "Identification handshake failed", + protocolErrorDetails ?? identificationError, + ); + + setIdentified(false); + + setError("Identification Failed"); + setErrorDescription( + isFatal + ? "Unable to complete secure identification. Please verify your credentials and try again." + : "Unable to complete secure identification because the transport request failed.", + ); + } finally { + if (!cancelled) { + setIdentifying(false); + } + } + }; + + void identify(); + + return () => { + cancelled = true; + }; + }, [connected, decrypt, getSharedSecret, load, send]); + + const progress = useMemo(() => { + if (readyState === READY_STATE.CONNECTING) return 30; + if (!connected) return 45; + if (identifying) return 75; + if (!identified) return 90; + return 100; + }, [connected, identified, identifying, readyState]); + + const loadingTitle = useMemo(() => { + if (readyState === READY_STATE.CONNECTING || !connected) { + return "Connecting to Tensamin"; + } + + if (identifying || !identified) { + return "Identifying secure session"; + } + + return "Loading"; + }, [connected, identified, identifying, readyState]); + + const loadingDescription = useMemo(() => { + if (readyState === READY_STATE.CONNECTING || !connected) { + return "Establishing transport channel"; + } + + if (identifying || !identified) { + return "Verifying challenge-response handshake"; + } + + return undefined; + }, [connected, identified, identifying, readyState]); + + if (error !== "" && errorDescription !== "") { + return ; + } + + if (!connected || !identified) { + return ( + + ); + } + + return ( + + {props.children} + + ); +} + +/** + * Returns the active ttp context and enforces provider usage. + * @returns ttp context API for transport operations and connection state. + */ +export function useTTP(): ContextType { + const context = useContext(TTPContext); + if (!context) { + throw new Error("useTTP must be used within a TTPProvider"); + } + return context; +} diff --git a/packages/ttp/src/index.ts b/packages/ttp/src/index.ts new file mode 100644 index 0000000..e1a274f --- /dev/null +++ b/packages/ttp/src/index.ts @@ -0,0 +1,3 @@ +export * from "@tensamin/ttp-core"; +export * from "./context"; +export * from "./values"; diff --git a/packages/ttp/src/values.ts b/packages/ttp/src/values.ts new file mode 100644 index 0000000..c48cec1 --- /dev/null +++ b/packages/ttp/src/values.ts @@ -0,0 +1,7 @@ +export const RESPONSE_TIMEOUT = 15_000; +export const RETRY_COUNT = 10; +export const RETRY_INTERVAL = 3_000; +export const PING_INTERVAL = 3_000; +export const TRANSPORT_URL = "https://tensamin.net:959"; +export const RECONNECT_TRIES = 3; +export const RECONNECT_RESET = 6; diff --git a/packages/onboarding/tsconfig.json b/packages/ttp/tsconfig.json similarity index 100% rename from packages/onboarding/tsconfig.json rename to packages/ttp/tsconfig.json diff --git a/packages/user/package.json b/packages/user/package.json index 956cd0d..ced2f84 100644 --- a/packages/user/package.json +++ b/packages/user/package.json @@ -9,18 +9,16 @@ "./values": "./src/values.ts" }, "scripts": { - "format": "pnpm exec prettier --write .", + "format": "bunx prettier --write .", "lint": "eslint src", - "test": "vitest run", "build": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@tensamin/cache": "workspace:*", - "@tensamin/mtp": "workspace:*", + "@tensamin/ttp": "workspace:*", "@tensamin/shared": "workspace:*", "@tensamin/storage": "workspace:*", - "react": "^19.2.8", - "react-dom": "^19.2.8", - "zod": "^4.4.3" + "react": "^19.2.0", + "react-dom": "^19.2.0", + "zod": "^4.3.6" } } diff --git a/packages/user/src/context.tsx b/packages/user/src/context.tsx index 8c41aa4..1ab964b 100644 --- a/packages/user/src/context.tsx +++ b/packages/user/src/context.tsx @@ -1,271 +1,62 @@ -import { - createContext, - type ReactNode, - useCallback, - useContext, - useEffect, - useMemo, - useRef, - useState, - useSyncExternalStore, -} from "react"; -import { useMTP } from "@tensamin/mtp"; +import * as React from "react"; +import { useTTP } from "@tensamin/ttp"; -import { - clientUserStateSchema, - mtp as schemas, - publicUserStateSchema, -} from "@tensamin/shared/data"; +import { ttp as schemas } from "@tensamin/shared/data"; import type z from "zod"; -import { createCache } from "@tensamin/cache"; -import { useStorage } from "@tensamin/storage/context"; -import { useSession } from "@tensamin/storage/session"; -import { getChangedUserFields, selectUserFields } from "./selection"; -export { getChangedUserFields, selectUserFields } from "./selection"; - -export type User = z.infer; -type ClientUserState = z.infer; -export type UserField = keyof User; -export type UserFields = readonly [UserField, ...UserField[]]; -export type SelectedUser = Readonly< - Pick ->; -export type UserProfilePatch = Omit< - z.infer, - "OnlineStatus" ->; - -export function mergeTransientPresence( - user: T, - presence: ReadonlyMap, -): T { - const state = presence.get(user.UserId); - return state === undefined ? user : ({ ...user, OnlineStatus: state } as T); -} - -const USER_CACHE_MAX_AGE = 5 * 60 * 1000; +export type User = z.infer; interface contextValue { - get( - userId: number, - fields: Fields, - ): Promise>; - peek( - userId: number, - fields: Fields, - ): SelectedUser | undefined; - subscribe( - userId: number, - fields: readonly UserField[], - listener: () => void, - ): () => void; - getVersion(userId: number, fields: readonly UserField[]): string; - updateProfile(userId: number, patch: UserProfilePatch): Promise; - updateState(userId: number, state: ClientUserState): void; + get(userId: number): Promise; } -const UserContext = createContext(undefined); +const UserContext = React.createContext(undefined); /** * Executes UserProvider. * @param props Parameter props. * @returns unknown. */ -export default function UserProvider(props: { children: ReactNode }) { - const storageRef = useRef>({}); - const pendingRef = useRef | undefined>>({}); - const profileUpdateQueuesRef = useRef(new Map>()); - const profileGenerationsRef = useRef(new Map()); - const checkedAtRef = useRef>({}); - const presenceRef = useRef(new Map()); - const durableProfileRef = useRef>({}); - const listenersRef = useRef( - new Map< - number, - Set<{ fields: ReadonlySet; listener: () => void }> - >(), - ); - const revisionsRef = useRef(new Map>()); - - const { send, subscribe: subscribeMTP } = useMTP(); - const { load } = useStorage(); - const { contacts } = useSession(); - const [accountId, setAccountId] = useState(null); - const accountIdRef = useRef(null); - const contactsRef = useRef(contacts); - const initialStatesRef = useRef( - new Map>(), +export default function UserProvider(props: { children: React.ReactNode }) { + const storageRef = React.useRef>({}); + const pendingRef = React.useRef | undefined>>( + {}, ); - useEffect(() => { - contactsRef.current = contacts; - }, [contacts]); + const { send } = useTTP(); - const mergeUserPresence = useCallback((user: User): User => { - return mergeTransientPresence(user, presenceRef.current); - }, []); - - const publishUser = useCallback((userId: number, user?: User) => { - const previous = storageRef.current[userId]; - if (user) storageRef.current[userId] = user; - else delete storageRef.current[userId]; - - const changedFields = getChangedUserFields(previous, user); - if (changedFields.length === 0) return; - - let revisions = revisionsRef.current.get(userId); - if (!revisions) { - revisions = new Map(); - revisionsRef.current.set(userId, revisions); - } - for (const field of changedFields) { - revisions.set(field, (revisions.get(field) ?? 0) + 1); - } - - const changed = new Set(changedFields); - for (const entry of listenersRef.current.get(userId) ?? []) { - if ([...entry.fields].some((field) => changed.has(field))) { - entry.listener(); - } - } - }, []); - - const installProfile = useCallback( - (profile: User) => { - durableProfileRef.current[profile.UserId] = profile; - publishUser(profile.UserId, mergeUserPresence(profile)); - }, - [mergeUserPresence, publishUser], - ); - - const applyUserState = useCallback( - (userId: number, state: ClientUserState, privateState = false) => { - const currentAccountId = accountIdRef.current; - const known = - userId === currentAccountId || - durableProfileRef.current[userId] !== undefined || - contactsRef.current.some((contact) => contact.UserId === userId); - if (!known) return false; - - if (state === "user_invisible" && userId !== currentAccountId) - return false; - if (userId === currentAccountId && !privateState) return false; - if (presenceRef.current.get(userId) === state) return true; - - presenceRef.current.set(userId, state); - const profile = durableProfileRef.current[userId]; - if (profile) publishUser(userId, mergeUserPresence(profile)); - return true; - }, - [mergeUserPresence, publishUser], - ); - - const removePresence = useCallback( - (userId: number) => { - const removed = presenceRef.current.delete(userId); - initialStatesRef.current.delete(userId); - delete checkedAtRef.current[userId]; - if (!removed) return; - const profile = durableProfileRef.current[userId]; - if (profile) publishUser(userId, profile); - }, - [publishUser], - ); - - useEffect(() => { - void load("user_id").then((accountId) => { - accountIdRef.current = accountId; - setAccountId(accountId); - }); - }, [load]); - - const getAccountId = useCallback(async () => { - if (accountIdRef.current !== null) return accountIdRef.current; - const value = await load("user_id"); - accountIdRef.current = value; - return value; - }, [load]); - - useEffect(() => { - if (!accountId) return; - const unsubscribeStates = subscribeMTP("GetStates", ({ data }) => { - for (const userId of data.MissingUserIds ?? []) removePresence(userId); - for (const entry of data.UserStates) { - if (!entry || entry.UserId === accountId) continue; - initialStatesRef.current.set(entry.UserId, entry.UserState); - if (applyUserState(entry.UserId, entry.UserState)) { - initialStatesRef.current.delete(entry.UserId); - } - } - }); - const unsubscribeChanged = subscribeMTP("ClientChanged", ({ data }) => { - applyUserState(data.UserId, data.UserState, true); - }); - return () => { - unsubscribeStates(); - unsubscribeChanged(); - }; - }, [accountId, applyUserState, removePresence, subscribeMTP]); - - const loadUser = useCallback( + /** + * Executes get. + * @param userId Parameter userId. + * @returns Promise. + */ + const get = React.useCallback( async (userId: number): Promise => { if (userId == null) { throw new Error("userId is required"); } + const cachedUser = storageRef.current[userId]; + if (cachedUser !== undefined) { + return cachedUser; + } + const pendingUser = pendingRef.current[userId]; if (pendingUser !== undefined) { return pendingUser; } const request = (async () => { - const cache = createCache(String(await getAccountId())); - const cachedValue = - durableProfileRef.current[userId] ?? - (await cache.profiles.get(userId)); - const cachedResult = - schemas.GetUserData.response.safeParse(cachedValue); - const cached = cachedResult.success ? cachedResult.data : undefined; - if (cached) { - installProfile(cached); - const checkedAt = checkedAtRef.current[userId]; - if (!checkedAt || Date.now() - checkedAt < USER_CACHE_MAX_AGE) { - checkedAtRef.current[userId] = Date.now(); - return storageRef.current[userId]; - } - } - try { - const profileGeneration = - profileGenerationsRef.current.get(userId) ?? 0; - const userData = await send("GetUserData", { UserId: userId }); - if (userData.type === "ErrorNotFound" || userData.data.UserId === 0) { - delete durableProfileRef.current[userId]; - delete checkedAtRef.current[userId]; - publishUser(userId); - await cache.profiles.delete(userId); - throw new Error("GetUserData failed: user not found"); - } - if (userData.type.startsWith("Error")) { - throw new Error(`GetUserData failed: ${userData.type}`); - } - if ( - profileGeneration !== - (profileGenerationsRef.current.get(userId) ?? 0) && - storageRef.current[userId] - ) { - return storageRef.current[userId]; - } - installProfile(userData.data); - checkedAtRef.current[userId] = Date.now(); - return storageRef.current[userId]; - } catch (error) { - if (cached && storageRef.current[userId]) { - checkedAtRef.current[userId] = Date.now(); - return storageRef.current[userId]; - } - throw error; - } + const userData = await send("get_user_data", { user_id: userId }); + const user = { + ...userData.data, + avatar: userData.data.avatar + ? `data:image/png;base64,${userData.data.avatar}` + : undefined, + }; + + storageRef.current[userId] = user; + return user; })(); pendingRef.current[userId] = request; @@ -276,128 +67,13 @@ export default function UserProvider(props: { children: ReactNode }) { delete pendingRef.current[userId]; } }, - [getAccountId, installProfile, publishUser, send], - ); - - const get = useCallback( - async ( - userId: number, - fields: Fields, - ): Promise> => { - return selectUserFields(await loadUser(userId), fields); - }, - [loadUser], - ); - - const peek = useCallback( - (userId: number, fields: Fields) => { - const user = storageRef.current[userId]; - return user ? selectUserFields(user, fields) : undefined; - }, - [], - ); - - const subscribe = useCallback( - (userId: number, fields: readonly UserField[], listener: () => void) => { - let listeners = listenersRef.current.get(userId); - if (!listeners) { - listeners = new Set(); - listenersRef.current.set(userId, listeners); - } - const entry = { fields: new Set(fields), listener }; - listeners.add(entry); - return () => { - listeners.delete(entry); - if (listeners.size === 0) listenersRef.current.delete(userId); - }; - }, - [], - ); - - const getVersion = useCallback( - (userId: number, fields: readonly UserField[]) => { - const revisions = revisionsRef.current.get(userId); - return fields - .map((field) => `${field}:${revisions?.get(field) ?? 0}`) - .join("|"); - }, - [], - ); - - const updateProfile = useCallback( - async (userId: number, patch: UserProfilePatch) => { - const previousUpdate = profileUpdateQueuesRef.current.get(userId); - const update = ( - previousUpdate?.catch(() => undefined) ?? Promise.resolve() - ).then(async () => { - const current = durableProfileRef.current[userId]; - if (!current) throw new Error(`User ${userId} is not loaded`); - profileGenerationsRef.current.set( - userId, - (profileGenerationsRef.current.get(userId) ?? 0) + 1, - ); - const profile = schemas.GetUserData.response.parse({ - ...current, - ...patch, - }); - installProfile(profile); - checkedAtRef.current[userId] = Date.now(); - await createCache(String(await getAccountId())).profiles.put(profile); - }); - profileUpdateQueuesRef.current.set(userId, update); - try { - await update; - } finally { - if (profileUpdateQueuesRef.current.get(userId) === update) { - profileUpdateQueuesRef.current.delete(userId); - } - } - }, - [getAccountId, installProfile], - ); - - const updateState = useCallback( - (userId: number, state: ClientUserState) => { - applyUserState(userId, state, true); - }, - [applyUserState], - ); - - useEffect(() => { - if (!accountId) return; - void (async () => { - const userIds = [ - accountId, - ...contacts.map((contact) => contact.UserId), - ].filter((userId, index, all) => all.indexOf(userId) === index); - for (const userId of userIds) { - try { - await loadUser(userId); - const state = initialStatesRef.current.get(userId); - if (state && applyUserState(userId, state)) { - initialStatesRef.current.delete(userId); - } - } catch { - // The normal user loading path reports profile failures to its caller. - } - } - })(); - }, [accountId, applyUserState, contacts, loadUser]); - - const value = useMemo( - () => ({ - get, - peek, - subscribe, - getVersion, - updateProfile, - updateState, - }), - [get, getVersion, peek, subscribe, updateProfile, updateState], + [send], ); return ( - {props.children} + + {props.children} + ); } @@ -407,69 +83,9 @@ export default function UserProvider(props: { children: ReactNode }) { * @returns contextValue. */ export function useUser(): contextValue { - const context = useContext(UserContext); + const context = React.useContext(UserContext); if (!context) { throw new Error("useUser must be used within a UserProvider"); } return context; } - -export type UserLoadState = - | { data: undefined; error: undefined; loading: true } - | { data: undefined; error: unknown; loading: false } - | { data: SelectedUser; error: undefined; loading: false }; - -export function useUserFields( - userId: number | null, - fields: Fields, -): UserLoadState { - const { get, getVersion, peek, subscribe } = useUser(); - const fieldKey = fields.join("|"); - const selectedFields = useMemo( - () => fieldKey.split("|") as unknown as Fields, - [fieldKey], - ); - const requestKey = `${userId ?? "unresolved"}:${fieldKey}`; - const [failure, setFailure] = useState<{ - error: unknown; - requestKey: string; - }>(); - - const version = useSyncExternalStore( - useCallback( - (listener) => - userId === null - ? () => undefined - : subscribe(userId, selectedFields, listener), - [selectedFields, subscribe, userId], - ), - useCallback( - () => - userId === null ? "unresolved" : getVersion(userId, selectedFields), - [getVersion, selectedFields, userId], - ), - () => "server", - ); - - useEffect(() => { - if (userId === null) return; - let active = true; - void get(userId, selectedFields).catch((nextError: unknown) => { - if (active) setFailure({ error: nextError, requestKey }); - }); - return () => { - active = false; - }; - }, [get, requestKey, selectedFields, userId]); - - const data = useMemo(() => { - void version; - return userId === null ? undefined : peek(userId, selectedFields); - }, [peek, selectedFields, userId, version]); - - if (data) return { data, error: undefined, loading: false }; - if (failure?.requestKey === requestKey) { - return { data: undefined, error: failure.error, loading: false }; - } - return { data: undefined, error: undefined, loading: true }; -} diff --git a/packages/user/src/selection.test.ts b/packages/user/src/selection.test.ts deleted file mode 100644 index 4235945..0000000 --- a/packages/user/src/selection.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import type { User } from "./context"; -import { getChangedUserFields, selectUserFields } from "./selection"; - -const user = { - Avatar: undefined, - Display: "Alice", - IotaId: 1, - OmikronConnections: [], - OnlineStatus: "user_online", - PublicKey: "AA==", - SubEnd: 0, - SubLevel: 0, - UserId: 1, - Username: "alice", -} satisfies User; - -describe("presence selection", () => { - it("notifies OnlineStatus selections when presence changes", () => { - const next = { ...user, OnlineStatus: "user_dnd" as const }; - - expect(getChangedUserFields(user, next)).toEqual(["OnlineStatus"]); - expect(selectUserFields(next, ["OnlineStatus"])).toEqual({ - OnlineStatus: "user_dnd", - }); - }); -}); diff --git a/packages/user/src/selection.ts b/packages/user/src/selection.ts deleted file mode 100644 index 57b0fe3..0000000 --- a/packages/user/src/selection.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { User, UserField, UserFields, SelectedUser } from "./context"; - -const USER_FIELD_MAP = { - About: true, - Avatar: true, - Display: true, - IotaId: true, - OmikronConnections: true, - OmikronId: true, - OnlineStatus: true, - PublicKey: true, - Status: true, - SubEnd: true, - SubLevel: true, - UserId: true, - Username: true, -} satisfies Record; - -const USER_FIELDS = Object.keys(USER_FIELD_MAP) as UserField[]; - -export function selectUserFields( - user: User, - fields: Fields, -): SelectedUser { - return Object.fromEntries( - fields.map((field) => [field, user[field]]), - ) as SelectedUser; -} - -export function getChangedUserFields( - previous: User | undefined, - next: User | undefined, -): UserField[] { - if ((previous === undefined) !== (next === undefined)) { - return [...USER_FIELDS]; - } - return USER_FIELDS.filter( - (field) => !Object.is(previous?.[field], next?.[field]), - ); -} diff --git a/packages/user/src/wrapper.tsx b/packages/user/src/wrapper.tsx index 2a8752f..5f13e54 100644 --- a/packages/user/src/wrapper.tsx +++ b/packages/user/src/wrapper.tsx @@ -1,47 +1,48 @@ import { useEffect, useState } from "react"; -import { type SelectedUser, type UserFields, useUserFields } from "./context"; +import { useUser, type User } from "./context"; import { failedUser } from "@tensamin/shared/data"; import { useStorage } from "@tensamin/storage/context"; // Wrapper function to pass user data to some component -export default function Wrapper(props: { +export default function Wrapper(props: { userId: number | "own"; - fields: Fields; loading: React.ReactNode; - component: (user: SelectedUser) => React.ReactNode; + component: (user: User) => React.ReactNode; }) { + const { get } = useUser(); const { load } = useStorage(); - const [ownUserId, setOwnUserId] = useState(null); - const [ownUserError, setOwnUserError] = useState(false); + const [user, setUser] = useState(null); useEffect(() => { - if (props.userId !== "own") return; + if (props.userId == null) return; + let active = true; - void load("user_id").then( - (value) => { + + void (async () => { + try { + const userId = + props.userId === "own" ? await load("user_id") : props.userId; + const value = await get(userId); + if (active) { - setOwnUserId(value); - setOwnUserError(false); + setUser(value); } - }, - () => { - if (active) setOwnUserError(true); - }, - ); + } catch { + if (active) { + setUser(failedUser); + } + } + })(); + return () => { active = false; }; - }, [load, props.userId]); + }, [load, get, props.userId]); - const userId = props.userId === "own" ? ownUserId : props.userId; - const result = useUserFields(userId, props.fields); - if ( - (props.userId === "own" && ownUserError) || - (!result.loading && !result.data) - ) { - return <>{props.component(failedUser as SelectedUser)}; + if (props.userId == null) { + return <>{props.component(failedUser)}; } - return <>{result.data ? props.component(result.data) : props.loading}; + return <>{user ? props.component(user) : props.loading}; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index 83d88ff..0000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,13089 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -overrides: - '@methanium/ui': https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz - mtp: https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz - -importers: - - .: - dependencies: - '@methanium/ui': - specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz - version: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@7.2.0)(typescript@6.0.3) - mtp: - specifier: https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz - version: https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz - sonner: - specifier: ^2.0.8 - version: 2.0.8(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - devDependencies: - '@eslint/js': - specifier: ^10.0.1 - version: 10.0.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0)) - '@types/node': - specifier: ^26.2.0 - version: 26.2.0 - '@types/react': - specifier: ^19.2.18 - version: 19.2.18 - '@types/react-dom': - specifier: ^19.2.4 - version: 19.2.4(@types/react@19.2.18) - '@typescript-eslint/parser': - specifier: ^8.67.0 - version: 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: - specifier: ^10.8.1 - version: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - eslint-plugin-react-hooks: - specifier: ^7.1.1 - version: 7.1.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0) - fallow: - specifier: ^3.17.0 - version: 3.17.0 - globals: - specifier: ^17.11.0 - version: 17.11.0 - prettier: - specifier: ^3.9.6 - version: 3.9.6 - typescript: - specifier: ^6.0.3 - version: 6.0.3 - typescript-eslint: - specifier: ^8.67.0 - version: 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - vitest: - specifier: ^4.1.11 - version: 4.1.11(@types/node@26.2.0)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0)) - - apps/electron: - devDependencies: - '@types/node': - specifier: ^26.1.2 - version: 26.2.0 - electron: - specifier: ^43.3.0 - version: 43.3.0(supports-color@7.2.0) - electron-builder: - specifier: ^26.15.3 - version: 26.15.3(electron-builder-squirrel-windows@26.15.3)(supports-color@7.2.0) - esbuild: - specifier: ^0.28.1 - version: 0.28.1 - typescript: - specifier: ~6.0.3 - version: 6.0.3 - - apps/pwa: - dependencies: - '@methanium/ui': - specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz - version: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@7.2.0)(typescript@6.0.3) - '@tauri-apps/api': - specifier: ^2.11.1 - version: 2.11.1 - '@tensamin/crypto': - specifier: workspace:* - version: link:../../packages/crypto - '@tensamin/shared': - specifier: workspace:* - version: link:../../packages/shared - '@tensamin/storage': - specifier: workspace:* - version: link:../../packages/storage - mtp: - specifier: https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz - version: https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz - react: - specifier: ^19.2.8 - version: 19.2.8 - sonner: - specifier: ^2.0.7 - version: 2.0.8(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - vite-plugin-pwa: - specifier: ^1.1.0 - version: 1.3.0(supports-color@7.2.0)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0))(workbox-build@7.4.1(supports-color@7.2.0))(workbox-window@7.4.1) - workbox-core: - specifier: ^7.3.0 - version: 7.4.1 - workbox-precaching: - specifier: ^7.3.0 - version: 7.4.1 - workbox-routing: - specifier: ^7.3.0 - version: 7.4.1 - workbox-strategies: - specifier: ^7.3.0 - version: 7.4.1 - devDependencies: - '@types/node': - specifier: ^26.1.2 - version: 26.2.0 - '@types/react': - specifier: ^19.2.18 - version: 19.2.18 - typescript: - specifier: ~6.0.3 - version: 6.0.3 - vite: - specifier: ^8.2.1 - version: 8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0) - - apps/tauri: - dependencies: - '@methanium/ui': - specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz - version: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@7.2.0)(typescript@6.0.3) - '@tauri-apps/api': - specifier: ^2.11.1 - version: 2.11.1 - '@tauri-apps/plugin-deep-link': - specifier: ~2.4.9 - version: 2.4.9 - '@tensamin/shared': - specifier: workspace:* - version: link:../../packages/shared - react: - specifier: ^19.2.8 - version: 19.2.8 - react-dom: - specifier: ^19.2.8 - version: 19.2.8(react@19.2.8) - devDependencies: - '@tauri-apps/cli': - specifier: ^2.11.4 - version: 2.11.4 - '@types/node': - specifier: ^26.1.2 - version: 26.2.0 - - apps/web: - dependencies: - '@fontsource-variable/public-sans': - specifier: ^5.3.0 - version: 5.3.0 - '@methanium/ui': - specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz - version: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@7.2.0)(typescript@6.0.3) - '@tailwindcss/vite': - specifier: ^4.3.3 - version: 4.3.3(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0)) - '@tanstack/react-router': - specifier: ^1.170.21 - version: 1.170.23(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@tanstack/react-virtual': - specifier: ^3.14.9 - version: 3.14.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@tauri-apps/api': - specifier: ^2.11.1 - version: 2.11.1 - '@tensamin/cache': - specifier: workspace:* - version: link:../../packages/cache - '@tensamin/call': - specifier: workspace:* - version: link:../../packages/call - '@tensamin/chat': - specifier: workspace:* - version: link:../../packages/chat - '@tensamin/crypto': - specifier: workspace:* - version: link:../../packages/crypto - '@tensamin/hotkeys': - specifier: workspace:* - version: link:../../packages/hotkeys - '@tensamin/mtp': - specifier: workspace:* - version: link:../../packages/mtp - '@tensamin/notifications': - specifier: workspace:* - version: link:../../packages/notifications - '@tensamin/onboarding': - specifier: workspace:* - version: link:../../packages/onboarding - '@tensamin/pwa': - specifier: workspace:* - version: link:../pwa - '@tensamin/settings': - specifier: workspace:* - version: link:../../packages/settings - '@tensamin/shared': - specifier: workspace:* - version: link:../../packages/shared - '@tensamin/storage': - specifier: workspace:* - version: link:../../packages/storage - '@tensamin/tauri': - specifier: workspace:* - version: link:../tauri - '@tensamin/tauth': - specifier: workspace:* - version: link:../../packages/tauth - '@tensamin/user': - specifier: workspace:* - version: link:../../packages/user - decimal.js-light: - specifier: ^2.5.1 - version: 2.5.1 - eventemitter3: - specifier: ^5.0.4 - version: 5.0.4 - lucide-react: - specifier: ^1.29.0 - version: 1.30.0(react@19.2.8) - react: - specifier: ^19.2.8 - version: 19.2.8 - react-dom: - specifier: ^19.2.8 - version: 19.2.8(react@19.2.8) - react-is: - specifier: ^19.2.8 - version: 19.2.8 - react-redux: - specifier: ^9.3.0 - version: 9.3.0(@types/react@19.2.18)(react@19.2.8)(redux@5.0.1) - tailwind-scrollbar-hide: - specifier: ^4.0.0 - version: 4.0.0(tailwindcss@4.3.3) - tailwindcss: - specifier: ^4.3.3 - version: 4.3.3 - tw-animate-css: - specifier: ^1.4.0 - version: 1.4.0 - use-sync-external-store: - specifier: ^1.6.0 - version: 1.6.0(react@19.2.8) - zod: - specifier: ^4.4.3 - version: 4.4.3 - devDependencies: - '@eslint/js': - specifier: ^10.0.1 - version: 10.0.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0)) - '@types/qrcode': - specifier: ^1.5.6 - version: 1.5.6 - '@types/react': - specifier: ^19.2.18 - version: 19.2.18 - '@types/react-dom': - specifier: ^19.2.4 - version: 19.2.4(@types/react@19.2.18) - '@vitejs/plugin-react': - specifier: ^6.0.5 - version: 6.0.5(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0)) - esbuild: - specifier: ^0.28.1 - version: 0.28.1 - eslint: - specifier: ^10.8.0 - version: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - globals: - specifier: ^17.9.0 - version: 17.9.0 - mtp: - specifier: https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz - version: https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz - typescript: - specifier: ~6.0.3 - version: 6.0.3 - typescript-eslint: - specifier: ^8.66.0 - version: 8.66.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - vite: - specifier: ^8.2.1 - version: 8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0) - - packages/cache: - dependencies: - '@tensamin/mtp': - specifier: workspace:* - version: link:../mtp - '@tensamin/shared': - specifier: workspace:* - version: link:../shared - '@tensamin/storage': - specifier: workspace:* - version: link:../storage - react: - specifier: ^19.2.8 - version: 19.2.8 - zod: - specifier: ^4.4.3 - version: 4.4.3 - - packages/call: - dependencies: - '@livekit/components-react': - specifier: ^2.9.23 - version: 2.9.23(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tslib@2.8.1) - '@methanium/ui': - specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz - version: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@7.2.0)(typescript@6.0.3) - '@tanstack/react-router': - specifier: ^1.170.21 - version: 1.170.23(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@tensamin/crypto': - specifier: workspace:* - version: link:../crypto - '@tensamin/mtp': - specifier: workspace:* - version: link:../mtp - '@tensamin/shared': - specifier: workspace:* - version: link:../shared - '@tensamin/storage': - specifier: workspace:* - version: link:../storage - '@tensamin/user': - specifier: workspace:* - version: link:../user - deepfilternet3-noise-filter: - specifier: 1.3.0 - version: 1.3.0(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22)) - livekit-client: - specifier: ^2.21.0 - version: 2.21.0(@types/dom-mediacapture-record@1.0.22) - lucide-react: - specifier: ^1.29.0 - version: 1.30.0(react@19.2.8) - mtp: - specifier: https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz - version: https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz - react: - specifier: ^19.2.8 - version: 19.2.8 - react-dom: - specifier: ^19.2.8 - version: 19.2.8(react@19.2.8) - recharts: - specifier: ^3.10.1 - version: 3.10.1(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1) - zod: - specifier: ^4.4.3 - version: 4.4.3 - zustand: - specifier: ^5.0.14 - version: 5.0.14(@types/react@19.2.18)(immer@11.1.16)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)) - - packages/chat: - dependencies: - '@methanium/ui': - specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz - version: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@7.2.0)(typescript@6.0.3) - '@tanstack/pacer': - specifier: ^0.21.1 - version: 0.21.1 - '@tanstack/react-query': - specifier: ^5.101.4 - version: 5.101.4(react@19.2.8) - '@tanstack/react-router': - specifier: ^1.170.21 - version: 1.170.23(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@tanstack/react-virtual': - specifier: ^3.14.9 - version: 3.14.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@tensamin/cache': - specifier: workspace:* - version: link:../cache - '@tensamin/crypto': - specifier: workspace:* - version: link:../crypto - '@tensamin/hotkeys': - specifier: workspace:* - version: link:../hotkeys - '@tensamin/mtp': - specifier: workspace:* - version: link:../mtp - '@tensamin/shared': - specifier: workspace:* - version: link:../shared - '@tensamin/storage': - specifier: workspace:* - version: link:../storage - '@tensamin/user': - specifier: workspace:* - version: link:../user - lucide-react: - specifier: ^1.29.0 - version: 1.30.0(react@19.2.8) - motion: - specifier: ^13.0.0 - version: 13.0.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: - specifier: ^19.2.8 - version: 19.2.8 - react-dom: - specifier: ^19.2.8 - version: 19.2.8(react@19.2.8) - zod: - specifier: ^4.4.3 - version: 4.4.3 - - packages/crypto: - dependencies: - mtp: - specifier: https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz - version: https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz - react: - specifier: ^19.2.8 - version: 19.2.8 - react-dom: - specifier: ^19.2.8 - version: 19.2.8(react@19.2.8) - - packages/hotkeys: - dependencies: - '@tanstack/react-hotkeys': - specifier: ^0.10.0 - version: 0.10.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@tensamin/storage': - specifier: workspace:* - version: link:../storage - react: - specifier: ^19.2.8 - version: 19.2.8 - react-dom: - specifier: ^19.2.8 - version: 19.2.8(react@19.2.8) - devDependencies: - vite: - specifier: ^8.2.1 - version: 8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0) - - packages/markdown: - dependencies: - '@codemirror/autocomplete': - specifier: ^6.20.3 - version: 6.20.3 - '@codemirror/commands': - specifier: ^6.10.4 - version: 6.11.0 - '@codemirror/lang-markdown': - specifier: ^6.5.2 - version: 6.5.2 - '@codemirror/language': - specifier: ^6.12.4 - version: 6.12.4 - '@codemirror/state': - specifier: ^6.7.1 - version: 6.7.1 - '@codemirror/view': - specifier: ^6.43.8 - version: 6.43.9 - '@methanium/ui': - specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz - version: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@7.2.0)(typescript@6.0.3) - '@twemoji/api': - specifier: ^17.0.3 - version: 17.0.3 - emojibase-data: - specifier: ^17.0.0 - version: 17.0.0(emojibase@17.0.0) - lucide-react: - specifier: ^1.30.0 - version: 1.32.0(react@19.2.8) - react: - specifier: ^19.2.8 - version: 19.2.8 - react-dom: - specifier: ^19.2.8 - version: 19.2.8(react@19.2.8) - - packages/mtp: - dependencies: - '@methanium/ui': - specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz - version: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@7.2.0)(typescript@6.0.3) - '@tauri-apps/api': - specifier: ^2.11.1 - version: 2.11.1 - '@tensamin/shared': - specifier: workspace:* - version: link:../shared - '@tensamin/storage': - specifier: workspace:* - version: link:../storage - mtp: - specifier: https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz - version: https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz - react: - specifier: ^19.2.8 - version: 19.2.8 - devDependencies: - eslint: - specifier: ^10.8.0 - version: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - - packages/notifications: - dependencies: - '@methanium/ui': - specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz - version: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@7.2.0)(typescript@6.0.3) - '@tanstack/react-router': - specifier: ^1.170.21 - version: 1.170.23(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@tauri-apps/api': - specifier: ^2.11.1 - version: 2.11.1 - '@tauri-apps/plugin-notification': - specifier: ~2.3.3 - version: 2.3.3 - '@tensamin/chat': - specifier: workspace:* - version: link:../chat - '@tensamin/crypto': - specifier: workspace:* - version: link:../crypto - '@tensamin/mtp': - specifier: workspace:* - version: link:../mtp - '@tensamin/shared': - specifier: workspace:* - version: link:../shared - '@tensamin/storage': - specifier: workspace:* - version: link:../storage - '@tensamin/user': - specifier: workspace:* - version: link:../user - react: - specifier: ^19.2.8 - version: 19.2.8 - react-dom: - specifier: ^19.2.8 - version: 19.2.8(react@19.2.8) - sonner: - specifier: ^2.0.7 - version: 2.0.7(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - - packages/onboarding: - dependencies: - '@methanium/ui': - specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz - version: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@7.2.0)(typescript@6.0.3) - '@tauri-apps/api': - specifier: ^2.11.1 - version: 2.11.1 - '@tauri-apps/plugin-notification': - specifier: ~2.3.3 - version: 2.3.3 - '@tensamin/shared': - specifier: workspace:* - version: link:../shared - '@tensamin/storage': - specifier: workspace:* - version: link:../storage - lucide-react: - specifier: ^1.29.0 - version: 1.30.0(react@19.2.8) - react: - specifier: ^19.2.8 - version: 19.2.8 - react-dom: - specifier: ^19.2.8 - version: 19.2.8(react@19.2.8) - zod: - specifier: ^4.4.3 - version: 4.4.3 - - packages/settings: - dependencies: - '@methanium/ui': - specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz - version: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@7.2.0)(typescript@6.0.3) - '@tanstack/react-router': - specifier: ^1.170.21 - version: 1.170.23(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@tauri-apps/api': - specifier: ^2.11.1 - version: 2.11.1 - '@tensamin/cache': - specifier: workspace:* - version: link:../cache - '@tensamin/hotkeys': - specifier: workspace:* - version: link:../hotkeys - '@tensamin/mtp': - specifier: workspace:* - version: link:../mtp - '@tensamin/shared': - specifier: workspace:* - version: link:../shared - '@tensamin/storage': - specifier: workspace:* - version: link:../storage - '@tensamin/user': - specifier: workspace:* - version: link:../user - lucide-react: - specifier: ^1.29.0 - version: 1.30.0(react@19.2.8) - react: - specifier: ^19.2.8 - version: 19.2.8 - react-dom: - specifier: ^19.2.8 - version: 19.2.8(react@19.2.8) - devDependencies: - vite: - specifier: ^8.2.1 - version: 8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0) - - packages/shared: - dependencies: - '@methanium/ui': - specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz - version: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@7.2.0)(typescript@6.0.3) - lucide-react: - specifier: ^1.29.0 - version: 1.30.0(react@19.2.8) - react: - specifier: ^19.2.8 - version: 19.2.8 - react-dom: - specifier: ^19.2.8 - version: 19.2.8(react@19.2.8) - zod: - specifier: ^4.4.3 - version: 4.4.3 - - packages/storage: - dependencies: - '@methanium/ui': - specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz - version: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@7.2.0)(typescript@6.0.3) - '@tauri-apps/api': - specifier: ^2.11.1 - version: 2.11.1 - '@tensamin/cache': - specifier: workspace:* - version: link:../cache - '@tensamin/mtp': - specifier: workspace:* - version: link:../mtp - '@tensamin/shared': - specifier: workspace:* - version: link:../shared - react: - specifier: ^19.2.8 - version: 19.2.8 - react-dom: - specifier: ^19.2.8 - version: 19.2.8(react@19.2.8) - - packages/tauth: - dependencies: - '@methanium/ui': - specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz - version: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@7.2.0)(typescript@6.0.3) - '@tanstack/react-router': - specifier: ^1.170.21 - version: 1.170.23(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@tensamin/crypto': - specifier: workspace:* - version: link:../crypto - '@tensamin/mtp': - specifier: workspace:* - version: link:../mtp - '@tensamin/shared': - specifier: workspace:* - version: link:../shared - '@tensamin/storage': - specifier: workspace:* - version: link:../storage - '@tensamin/tauri': - specifier: workspace:* - version: link:../../apps/tauri - '@tensamin/user': - specifier: workspace:* - version: link:../user - react: - specifier: ^19.2.8 - version: 19.2.8 - react-dom: - specifier: ^19.2.8 - version: 19.2.8(react@19.2.8) - - packages/user: - dependencies: - '@tensamin/cache': - specifier: workspace:* - version: link:../cache - '@tensamin/mtp': - specifier: workspace:* - version: link:../mtp - '@tensamin/shared': - specifier: workspace:* - version: link:../shared - '@tensamin/storage': - specifier: workspace:* - version: link:../storage - react: - specifier: ^19.2.8 - version: 19.2.8 - react-dom: - specifier: ^19.2.8 - version: 19.2.8(react@19.2.8) - zod: - specifier: ^4.4.3 - version: 4.4.3 - -packages: - - '@apideck/better-ajv-errors@0.3.7': - resolution: {integrity: sha512-TajUJwGWbDwkCx/CZi7tRE8PVB7simCvKJfHUsSdvps+aTM/PDPP4gkLmKnc+x3CE//y9i/nj74GqdL/hwk7Iw==} - engines: {node: '>=10'} - peerDependencies: - ajv: '>=8' - - '@babel/code-frame@7.29.7': - resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.29.7': - resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.29.7': - resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.29.8': - resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-annotate-as-pure@7.29.7': - resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.29.7': - resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} - engines: {node: '>=6.9.0'} - - '@babel/helper-create-class-features-plugin@7.29.7': - resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-create-regexp-features-plugin@7.29.7': - resolution: {integrity: sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-define-polyfill-provider@0.6.8': - resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - - '@babel/helper-globals@7.29.7': - resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-member-expression-to-functions@7.29.7': - resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.29.7': - resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.29.7': - resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-optimise-call-expression@7.29.7': - resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} - engines: {node: '>=6.9.0'} - - '@babel/helper-plugin-utils@7.29.7': - resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-remap-async-to-generator@7.29.7': - resolution: {integrity: sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-replace-supers@7.29.7': - resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-skip-transparent-expression-wrappers@7.29.7': - resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.29.7': - resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.29.7': - resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.29.7': - resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-wrap-function@7.29.7': - resolution: {integrity: sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.29.7': - resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.29.8': - resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7': - resolution: {integrity: sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7': - resolution: {integrity: sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7': - resolution: {integrity: sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7': - resolution: {integrity: sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7': - resolution: {integrity: sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.13.0 - - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7': - resolution: {integrity: sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': - resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-assertions@7.29.7': - resolution: {integrity: sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-attributes@7.29.7': - resolution: {integrity: sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-jsx@7.29.7': - resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.29.7': - resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-unicode-sets-regex@7.18.6': - resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-transform-arrow-functions@7.29.7': - resolution: {integrity: sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-async-generator-functions@7.29.7': - resolution: {integrity: sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-async-to-generator@7.29.7': - resolution: {integrity: sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-block-scoped-functions@7.29.7': - resolution: {integrity: sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-block-scoping@7.29.7': - resolution: {integrity: sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-class-properties@7.29.7': - resolution: {integrity: sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-class-static-block@7.29.7': - resolution: {integrity: sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.12.0 - - '@babel/plugin-transform-classes@7.29.7': - resolution: {integrity: sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-computed-properties@7.29.7': - resolution: {integrity: sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-destructuring@7.29.7': - resolution: {integrity: sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-dotall-regex@7.29.7': - resolution: {integrity: sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-duplicate-keys@7.29.7': - resolution: {integrity: sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7': - resolution: {integrity: sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-transform-dynamic-import@7.29.7': - resolution: {integrity: sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-explicit-resource-management@7.29.7': - resolution: {integrity: sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-exponentiation-operator@7.29.7': - resolution: {integrity: sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-export-namespace-from@7.29.7': - resolution: {integrity: sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-for-of@7.29.7': - resolution: {integrity: sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-function-name@7.29.7': - resolution: {integrity: sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-json-strings@7.29.7': - resolution: {integrity: sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-literals@7.29.7': - resolution: {integrity: sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-logical-assignment-operators@7.29.7': - resolution: {integrity: sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-member-expression-literals@7.29.7': - resolution: {integrity: sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-amd@7.29.7': - resolution: {integrity: sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-commonjs@7.29.7': - resolution: {integrity: sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-systemjs@7.29.8': - resolution: {integrity: sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-umd@7.29.7': - resolution: {integrity: sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-named-capturing-groups-regex@7.29.7': - resolution: {integrity: sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-transform-new-target@7.29.7': - resolution: {integrity: sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-nullish-coalescing-operator@7.29.7': - resolution: {integrity: sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-numeric-separator@7.29.7': - resolution: {integrity: sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-object-rest-spread@7.29.7': - resolution: {integrity: sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-object-super@7.29.7': - resolution: {integrity: sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-optional-catch-binding@7.29.7': - resolution: {integrity: sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-optional-chaining@7.29.7': - resolution: {integrity: sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-parameters@7.29.7': - resolution: {integrity: sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-private-methods@7.29.7': - resolution: {integrity: sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-private-property-in-object@7.29.7': - resolution: {integrity: sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-property-literals@7.29.7': - resolution: {integrity: sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-regenerator@7.29.8': - resolution: {integrity: sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-regexp-modifiers@7.29.7': - resolution: {integrity: sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-transform-reserved-words@7.29.7': - resolution: {integrity: sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-shorthand-properties@7.29.7': - resolution: {integrity: sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-spread@7.29.8': - resolution: {integrity: sha512-4S9ksMGVWUshvgK0mKfvZky7leuG5/uoFVwMpAomJ8bMoDJiNHRVmc1EglwW/CmGVSqqWpEbXm9FmbRit22qoA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-sticky-regex@7.29.7': - resolution: {integrity: sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-template-literals@7.29.7': - resolution: {integrity: sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-typeof-symbol@7.29.7': - resolution: {integrity: sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-typescript@7.29.7': - resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-escapes@7.29.7': - resolution: {integrity: sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-property-regex@7.29.7': - resolution: {integrity: sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-regex@7.29.7': - resolution: {integrity: sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-sets-regex@7.29.7': - resolution: {integrity: sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/preset-env@7.29.7': - resolution: {integrity: sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/preset-modules@0.1.6-no-external-plugins': - resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} - peerDependencies: - '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 - - '@babel/preset-typescript@7.29.7': - resolution: {integrity: sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/runtime@7.29.7': - resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} - engines: {node: '>=6.9.0'} - - '@babel/template@7.29.7': - resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.29.8': - resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.29.8': - resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} - engines: {node: '>=6.9.0'} - - '@base-ui/react@1.7.0': - resolution: {integrity: sha512-j+8QjX44C32jrXD/qyEAGpFr70FRpGL2CY61mQd9nBPWN737CK0xxD1ceJ055rW4RtdvFDT1e7otzdlfxvsYug==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@date-fns/tz': ^1.2.0 - '@types/react': ^17 || ^18 || ^19 - date-fns: ^4.0.0 - react: ^17 || ^18 || ^19 - react-dom: ^17 || ^18 || ^19 - peerDependenciesMeta: - '@date-fns/tz': - optional: true - '@types/react': - optional: true - date-fns: - optional: true - - '@base-ui/utils@0.3.2': - resolution: {integrity: sha512-oWy1aq/I2GmYjpl4PhEAhzflF8VPGKgZeq0xAWTbfD5KBWyxcN0ZP2+WHSUm/5Z6lVMBDLReLcoXwSYoRc/zNQ==} - peerDependencies: - '@types/react': ^17 || ^18 || ^19 - react: ^17 || ^18 || ^19 - react-dom: ^17 || ^18 || ^19 - peerDependenciesMeta: - '@types/react': - optional: true - - '@bufbuild/protobuf@1.10.1': - resolution: {integrity: sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==} - - '@codemirror/autocomplete@6.20.3': - resolution: {integrity: sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==} - - '@codemirror/commands@6.11.0': - resolution: {integrity: sha512-/K4Rl5BN0OtTiPWmJCdqODu38XnDMsDxKY5rgrPnCkutPTJf2wVbkoixLfealF5Kwse/s8P8M5jAiURiwSwnFA==} - - '@codemirror/lang-css@6.3.1': - resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==} - - '@codemirror/lang-html@6.4.12': - resolution: {integrity: sha512-pw2ReWKUqSkbvh76RAT4NYxiogRu+PWkR2ukAwO9uOgrm8uipkzjtKKtNpyeAQwHOqxEeSvAXZ6vr3AfyB9y/w==} - - '@codemirror/lang-javascript@6.2.5': - resolution: {integrity: sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==} - - '@codemirror/lang-markdown@6.5.2': - resolution: {integrity: sha512-AwBOdkWYuA//WcM0xO5PfHPUcmz/O2i5o0Nsg1U69SII/loCJlFI1Romd9xp2HYb1kYJRGZotyqRghuHH5n8Kw==} - - '@codemirror/language@6.12.4': - resolution: {integrity: sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==} - - '@codemirror/lint@6.9.7': - resolution: {integrity: sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==} - - '@codemirror/state@6.7.1': - resolution: {integrity: sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==} - - '@codemirror/view@6.43.9': - resolution: {integrity: sha512-sTuUzTpPMFebRhg6dawChoKKgndIwfjmJgKVxBefPElcU2NwQ6AFroupk0SFqEerQyZOGRfDNnSN8Dw/lMAsXw==} - - '@dotenvx/dotenvx@1.75.1': - resolution: {integrity: sha512-/BITOC9dmS/edY2zQwZNicQ059O6RKabtQfyEafV0nGtfYRNHYy1DIPiYVcov40+tob9hfmBnbR963dS+EQ1DQ==} - hasBin: true - - '@dotenvx/primitives@0.8.0': - resolution: {integrity: sha512-VYJy0uhFm9zTJ1TxBaW/pA8bjbOM/OttaNMwZ1RHG4JKyRG7DhSdiqD1ipQoAyoD22olUtxbP78W9xY3Wd11bg==} - - '@electron-internal/extract-zip@1.0.5': - resolution: {integrity: sha512-+bqFCP98pLI0Tt0XQo1TmlXtwjWchISndDOxCkEcIuUgXWpBnLyRI+2DU+mesvnMMX6L1XDqYNA0lXNDHd/yiA==} - engines: {node: '>=22.12.0'} - - '@electron/asar@3.4.1': - resolution: {integrity: sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==} - engines: {node: '>=10.12.0'} - hasBin: true - - '@electron/fuses@1.8.0': - resolution: {integrity: sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==} - hasBin: true - - '@electron/get@3.1.0': - resolution: {integrity: sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==} - engines: {node: '>=14'} - - '@electron/get@5.1.0': - resolution: {integrity: sha512-3kSBtG8ObcTVfXanm5vVJ6UnBLEVmVsRk1M+vGqCuMBV+XLCbJYuWQful+yIy0GQDsSlK0kHEriEHn7SPk4EnA==} - engines: {node: '>=22.12.0'} - - '@electron/notarize@2.5.0': - resolution: {integrity: sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==} - engines: {node: '>= 10.0.0'} - - '@electron/osx-sign@1.3.3': - resolution: {integrity: sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==} - engines: {node: '>=12.0.0'} - hasBin: true - - '@electron/rebuild@4.2.0': - resolution: {integrity: sha512-RKL/O+jGoXJMxrx/5771y1n0xTKmFuOYGO3gMmwypBM6rsH0kou0mswwdXA2JrhIkE4xyC7v9vGk0n6NPzgOxQ==} - engines: {node: '>=22.12.0'} - hasBin: true - - '@electron/universal@2.0.3': - resolution: {integrity: sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==} - engines: {node: '>=16.4'} - - '@electron/windows-sign@1.2.2': - resolution: {integrity: sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==} - engines: {node: '>=14.14'} - hasBin: true - - '@esbuild/aix-ppc64@0.28.1': - resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.28.1': - resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.28.1': - resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.28.1': - resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.28.1': - resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.28.1': - resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.28.1': - resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.28.1': - resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.28.1': - resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.28.1': - resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.28.1': - resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.28.1': - resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.28.1': - resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.28.1': - resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.28.1': - resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.28.1': - resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.28.1': - resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.28.1': - resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.28.1': - resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.28.1': - resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.28.1': - resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.28.1': - resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.28.1': - resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.28.1': - resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.28.1': - resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.28.1': - resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@eslint-community/eslint-utils@4.10.1': - resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.12.2': - resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/config-array@0.23.5': - resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@eslint/config-helpers@0.7.0': - resolution: {integrity: sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@eslint/core@1.2.1': - resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@eslint/js@10.0.1': - resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - peerDependencies: - eslint: ^10.0.0 - peerDependenciesMeta: - eslint: - optional: true - - '@eslint/object-schema@3.0.5': - resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@eslint/plugin-kit@0.7.2': - resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@fallow-cli/darwin-arm64@3.17.0': - resolution: {integrity: sha512-u9O4aQaTSdaZaa4MAVEZEiwOx6QQzChymyQPrxV6KvDcVbcl0oYSekJKdvN4gEqUY8ClMze9gWS1tbn/W3N4dQ==} - cpu: [arm64] - os: [darwin] - - '@fallow-cli/darwin-x64@3.17.0': - resolution: {integrity: sha512-l3tZJovnCa8j+im4qTl4GVSfrx4gisJYeHDwb5TbcdTGaWYz42gHriAaz5MhOR8S24q3xRegX8QrI8PA4XXU4g==} - cpu: [x64] - os: [darwin] - - '@fallow-cli/linux-arm64-gnu@3.17.0': - resolution: {integrity: sha512-IAjS6qR4tHcRo1y5lzYT6mktN/ATPyPNrJ6yjwTORwwQV1YfMO58A5zGBfJlPBY+lUmaMZ9w37ftvH9a2AS4dQ==} - cpu: [arm64] - os: [linux] - - '@fallow-cli/linux-arm64-musl@3.17.0': - resolution: {integrity: sha512-Oe1dvk5Wt5Bmnxfc6HufJTaFY9SEwDVrZpnSdVQlv+nvz7DzrTK1r91cR3Ip477ANSLa9XD/MHPMpfqqo0dlZg==} - cpu: [arm64] - os: [linux] - - '@fallow-cli/linux-x64-gnu@3.17.0': - resolution: {integrity: sha512-XgLU2a51zuky7gifjVDHuiJGToAReDsVCDcyDF84TQz/h3bqO0iRkpDL8dh7+HODku84+EffpK5ZHY19n6Ij0w==} - cpu: [x64] - os: [linux] - - '@fallow-cli/linux-x64-musl@3.17.0': - resolution: {integrity: sha512-GnDZx2kbLKKdYAp05NSKv6CZHgzEKvhRd0btCKFMMpUEhdzmr4uh2Qb4UzKDBg/q2r1EhkzN4b/qC+tbFCmtwA==} - cpu: [x64] - os: [linux] - - '@fallow-cli/win32-arm64-msvc@3.17.0': - resolution: {integrity: sha512-u9n5WzPuKuC1PKegKQknWtngAUJ2szQaRXsbY54s7UMqeFNyC+W04LV5D+sEZywU7L0kzL+Kck9LxFLXBQcgvA==} - cpu: [arm64] - os: [win32] - - '@fallow-cli/win32-x64-msvc@3.17.0': - resolution: {integrity: sha512-/OcyxHuCKabzQ9hKIjM/CFJ9fr30n0Mw5MK6nzi5rn26mbYUgh073UZq4FONOpPJ/FqHtC726rxpgBtR4qS+gg==} - cpu: [x64] - os: [win32] - - '@floating-ui/core@1.8.0': - resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} - - '@floating-ui/dom@1.7.6': - resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} - - '@floating-ui/dom@1.8.0': - resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} - - '@floating-ui/react-dom@2.1.9': - resolution: {integrity: sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@floating-ui/utils@0.2.12': - resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} - - '@fontsource-variable/public-sans@5.3.0': - resolution: {integrity: sha512-AVfkmAt50BMXWpOO21FAntiJFKGX6xTc2dSL8dxtDteONe9IuRXJWGbs0EbG955vAMCq23ENeuopuW87cGWDSQ==} - - '@hono/node-server@2.1.1': - resolution: {integrity: sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==} - engines: {node: '>=20'} - peerDependencies: - hono: ^4 - - '@humanfs/core@0.19.2': - resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} - engines: {node: '>=18.18.0'} - - '@humanfs/node@0.16.8': - resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} - engines: {node: '>=18.18.0'} - - '@humanfs/types@0.15.0': - resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} - engines: {node: '>=18.18.0'} - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/retry@0.4.3': - resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} - engines: {node: '>=18.18'} - - '@isaacs/cliui@9.0.0': - resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} - engines: {node: '>=18'} - - '@isaacs/fs-minipass@4.0.1': - resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} - engines: {node: '>=18.0.0'} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/source-map@0.3.11': - resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@lezer/common@1.5.2': - resolution: {integrity: sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==} - - '@lezer/css@1.3.6': - resolution: {integrity: sha512-YJE78Wcg+zX8f10hiHWQ4Az48Qr/c13eId0VtRQYLBpxHDmDeSrXIlkbl+fJGW42rWC/uoUco9mhBZeVWP/A1g==} - - '@lezer/highlight@1.2.3': - resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==} - - '@lezer/html@1.3.13': - resolution: {integrity: sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==} - - '@lezer/javascript@1.5.4': - resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==} - - '@lezer/lr@1.4.10': - resolution: {integrity: sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==} - - '@lezer/markdown@1.7.2': - resolution: {integrity: sha512-iTkYvoVcKt3WkeL7qUDyXHONZEwLio4wj8KTNi2dnjQEXBZKMV63BpQrPqfsM+OkvuRbiSTAcycYAsQzLhRNoQ==} - - '@livekit/components-core@0.12.14': - resolution: {integrity: sha512-6OKP/1Ok2fCZewDLKd3SzaTb7KvfZl/6hjggI+TgUdhp0Y7HBg3+tHA7YmhZRc0BO8wIyVy4VgTPn7qkLHt2nQ==} - engines: {node: '>=18'} - peerDependencies: - livekit-client: ^2.20.1 - tslib: ^2.6.2 - - '@livekit/components-react@2.9.23': - resolution: {integrity: sha512-clO+0g/u3YBpuOvnAjUSAJBH/o7w+RpUAseswjiSML9rHrXlTUhwBNm8LPk+qN5GOU3A6lzsNnFYVpUHUBVOkg==} - engines: {node: '>=18'} - peerDependencies: - '@livekit/krisp-noise-filter': ^0.2.12 || ^0.3.0 || ^0.4.0 - livekit-client: ^2.20.1 - react: '>=18' - react-dom: '>=18' - tslib: ^2.6.2 - peerDependenciesMeta: - '@livekit/krisp-noise-filter': - optional: true - - '@livekit/mutex@1.1.1': - resolution: {integrity: sha512-EsshAucklmpuUAfkABPxJNhzj9v2sG7JuzFDL4ML1oJQSV14sqrpTYnsaOudMAw9yOaW53NU3QQTlUQoRs4czw==} - - '@livekit/protocol@1.50.4': - resolution: {integrity: sha512-L1uggNQAqyY21smQY8AllyOYbcv9Me9TaxwuLytL1R8ck9nbYPmQLNwEDi3pOFGAMa5F8I2nUi2Jc59W5awxlA==} - - '@malept/cross-spawn-promise@2.0.0': - resolution: {integrity: sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==} - engines: {node: '>= 12.13.0'} - - '@malept/flatpak-bundler@0.4.0': - resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==} - engines: {node: '>= 10.0.0'} - - '@marijn/find-cluster-break@1.0.3': - resolution: {integrity: sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA==} - - '@methanium/ui@https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz': - resolution: {integrity: sha512-ij9zo/PdP5l/F/70t7a1o+OFZbMFRNNseNc1dlqddUFqIkAy+71VL6ptrXNpeVtzKFq8LuycwzXGsGihuViVig==, tarball: https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz} - version: 0.0.29 - peerDependencies: - react: ^19.2.7 - react-dom: ^19.2.7 - - '@modelcontextprotocol/sdk@1.30.0': - resolution: {integrity: sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==} - engines: {node: '>=18'} - peerDependencies: - '@cfworker/json-schema': ^4.1.1 - zod: ^3.25 || ^4.0 - peerDependenciesMeta: - '@cfworker/json-schema': - optional: true - - '@napi-rs/lzma-linux-x64-gnu@1.5.1': - resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} - engines: {node: ^22.20 || ^24.12 || >=25} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@noble/hashes@1.4.0': - resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} - engines: {node: '>= 16'} - - '@noble/hashes@2.3.0': - resolution: {integrity: sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==} - engines: {node: '>= 20.19.0'} - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@oxc-project/types@0.143.0': - resolution: {integrity: sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==} - - '@peculiar/asn1-schema@2.8.0': - resolution: {integrity: sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q==} - - '@peculiar/json-schema@1.1.12': - resolution: {integrity: sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==} - engines: {node: '>=8.0.0'} - - '@peculiar/utils@2.0.3': - resolution: {integrity: sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==} - - '@peculiar/webcrypto@1.7.1': - resolution: {integrity: sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==} - engines: {node: '>=14.18.0'} - - '@radix-ui/primitive@1.1.7': - resolution: {integrity: sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==} - - '@radix-ui/react-compose-refs@1.1.5': - resolution: {integrity: sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-context@1.2.2': - resolution: {integrity: sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-dialog@1.1.23': - resolution: {integrity: sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-dismissable-layer@1.1.19': - resolution: {integrity: sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-focus-guards@1.1.6': - resolution: {integrity: sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-focus-scope@1.1.16': - resolution: {integrity: sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-id@1.1.4': - resolution: {integrity: sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-portal@1.1.17': - resolution: {integrity: sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-presence@1.1.10': - resolution: {integrity: sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-primitive@2.1.10': - resolution: {integrity: sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-slot@1.3.3': - resolution: {integrity: sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-callback-ref@1.1.4': - resolution: {integrity: sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-controllable-state@1.2.6': - resolution: {integrity: sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-effect-event@0.0.5': - resolution: {integrity: sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-layout-effect@1.1.4': - resolution: {integrity: sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@reduxjs/toolkit@2.12.0': - resolution: {integrity: sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==} - peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 || ^19 - react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - - '@rolldown/binding-android-arm64@1.2.3': - resolution: {integrity: sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.2.3': - resolution: {integrity: sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.2.3': - resolution: {integrity: sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.2.3': - resolution: {integrity: sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.2.3': - resolution: {integrity: sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.2.3': - resolution: {integrity: sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-arm64-musl@1.2.3': - resolution: {integrity: sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rolldown/binding-linux-ppc64-gnu@1.2.3': - resolution: {integrity: sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-s390x-gnu@1.2.3': - resolution: {integrity: sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-gnu@1.2.3': - resolution: {integrity: sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-musl@1.2.3': - resolution: {integrity: sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rolldown/binding-openharmony-arm64@1.2.3': - resolution: {integrity: sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-win32-arm64-msvc@1.2.3': - resolution: {integrity: sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.2.3': - resolution: {integrity: sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/pluginutils@1.0.1': - resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - - '@rollup/plugin-babel@6.1.0': - resolution: {integrity: sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@types/babel__core': ^7.1.9 - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - '@types/babel__core': - optional: true - rollup: - optional: true - - '@rollup/plugin-node-resolve@16.0.3': - resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.78.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-replace@6.0.3': - resolution: {integrity: sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-terser@1.0.0': - resolution: {integrity: sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==} - engines: {node: '>=20.0.0'} - peerDependencies: - rollup: ^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/pluginutils@5.4.0': - resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/rollup-android-arm-eabi@4.62.4': - resolution: {integrity: sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.62.4': - resolution: {integrity: sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.62.4': - resolution: {integrity: sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.62.4': - resolution: {integrity: sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.62.4': - resolution: {integrity: sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.62.4': - resolution: {integrity: sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.62.4': - resolution: {integrity: sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm-musleabihf@4.62.4': - resolution: {integrity: sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-arm64-gnu@4.62.4': - resolution: {integrity: sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm64-musl@4.62.4': - resolution: {integrity: sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-loong64-gnu@4.62.4': - resolution: {integrity: sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-loong64-musl@4.62.4': - resolution: {integrity: sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-ppc64-gnu@4.62.4': - resolution: {integrity: sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-ppc64-musl@4.62.4': - resolution: {integrity: sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==} - cpu: [ppc64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-gnu@4.62.4': - resolution: {integrity: sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-riscv64-musl@4.62.4': - resolution: {integrity: sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-s390x-gnu@4.62.4': - resolution: {integrity: sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-gnu@4.62.4': - resolution: {integrity: sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-musl@4.62.4': - resolution: {integrity: sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rollup/rollup-openbsd-x64@4.62.4': - resolution: {integrity: sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.62.4': - resolution: {integrity: sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-win32-arm64-msvc@4.62.4': - resolution: {integrity: sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.62.4': - resolution: {integrity: sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.62.4': - resolution: {integrity: sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.62.4': - resolution: {integrity: sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==} - cpu: [x64] - os: [win32] - - '@sec-ant/readable-stream@0.4.1': - resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - - '@shikijs/core@4.4.3': - resolution: {integrity: sha512-QCR4q2ZO/ILJEuwiBMel4wdcTDb1JGwfjKTxPDF6x8ixOaluPrVqIn06C99AcRPhmYlBR56d/Fb+GN58GzExpg==} - engines: {node: '>=20'} - - '@shikijs/engine-javascript@4.4.3': - resolution: {integrity: sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ==} - engines: {node: '>=20'} - - '@shikijs/engine-oniguruma@4.4.3': - resolution: {integrity: sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w==} - engines: {node: '>=20'} - - '@shikijs/langs@4.4.3': - resolution: {integrity: sha512-ePic0yfAJGOF83D5wBHK/00EjK65oahBYxFk5epgq33WRv7X9UuxLEV8PtR0szC0z8dl7INIpIodB99JRFlR+A==} - engines: {node: '>=20'} - - '@shikijs/primitive@4.4.3': - resolution: {integrity: sha512-m0wBeLDQDeIxRdUmrCPdQqfuUamDwRL5isCfYbguKD6NiaKpVbsv+3J81DyIKgNW5h4WAIIr8T4EkgQrBBxvaQ==} - engines: {node: '>=20'} - - '@shikijs/themes@4.4.3': - resolution: {integrity: sha512-w8UHjeUnIR965KMWJHUPXOc2mNJUnK3vpVLYLvw5IYU2mnTTJ89E24OrJDBNiJDQ0qzb0tc4l7mrIXx5cFeIyw==} - engines: {node: '>=20'} - - '@shikijs/types@4.4.3': - resolution: {integrity: sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g==} - engines: {node: '>=20'} - - '@shikijs/vscode-textmate@10.0.2': - resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - - '@sindresorhus/is@4.6.0': - resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} - engines: {node: '>=10'} - - '@sindresorhus/merge-streams@4.0.0': - resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} - engines: {node: '>=18'} - - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - - '@standard-schema/utils@0.3.0': - resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} - - '@szmarczak/http-timer@4.0.6': - resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} - engines: {node: '>=10'} - - '@tailwindcss/node@4.3.3': - resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} - - '@tailwindcss/oxide-android-arm64@4.3.3': - resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.3.3': - resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.3.3': - resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.3.3': - resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': - resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==} - engines: {node: '>= 20'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': - resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-arm64-musl@4.3.3': - resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-linux-x64-gnu@4.3.3': - resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-x64-musl@4.3.3': - resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-wasm32-wasi@4.3.3': - resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': - resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.3.3': - resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.3.3': - resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} - engines: {node: '>= 20'} - - '@tailwindcss/vite@4.3.3': - resolution: {integrity: sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==} - peerDependencies: - vite: ^5.2.0 || ^6 || ^7 || ^8 - - '@tanstack/devtools-event-client@0.4.4': - resolution: {integrity: sha512-6T5Yop/793YI+H+5J8Hsyj4kCih9sl4t3ElLgKioW5hk3ocn+ZdSJ94tT7vL7uabxSugWYBZlOTMPzEw2puvQw==} - engines: {node: '>=18'} - hasBin: true - - '@tanstack/history@1.162.1': - resolution: {integrity: sha512-DR9t6lfLVdrjgCwpglrR9DR7Ok8/HlXjcOE+goWXF3zyuLUO/ug7vMbSFxTqrQTtbRghJfyhmIZ0S6LhPIy44w==} - engines: {node: '>=20.19'} - - '@tanstack/hotkeys@0.8.0': - resolution: {integrity: sha512-vqH7X9nb0MTJ/O08++dB5bP9jgj4+BIPOUu/U+6myG86lDsirZSVSobpq5UQpE7nBuk62i8eIYeOhd+OMl/UrA==} - engines: {node: '>=18'} - - '@tanstack/pacer@0.21.1': - resolution: {integrity: sha512-hB01dd4rlsYcTCNP7wK186jgAe6K5qimgM1Y5Jtvz+9PUaILvpmeLLjmQNUNSO1l23lIt+CeQR6mO1mjlPvRtQ==} - engines: {node: '>=18'} - - '@tanstack/query-core@5.101.4': - resolution: {integrity: sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==} - - '@tanstack/react-hotkeys@0.10.0': - resolution: {integrity: sha512-GwOSndI5j3qBVYTmgP1mYyRTnlxb2MS17cwGlsavSxMQPSnmDf+m3LzMIpRMs+3zzQMjg3cYhHsFYizYlFI2tw==} - engines: {node: '>=18'} - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - - '@tanstack/react-query@5.101.4': - resolution: {integrity: sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==} - peerDependencies: - react: ^18 || ^19 - - '@tanstack/react-router@1.170.23': - resolution: {integrity: sha512-iKyHk7vGVaTdk7wukFZLjzlOs4TQbQJiRMkvFsphpysOlxzLaqWSlWHKU4gVPW3WjJ19k7EjVUCD4q3DJqZpZg==} - engines: {node: '>=20.19'} - peerDependencies: - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' - - '@tanstack/react-store@0.11.1': - resolution: {integrity: sha512-HaIGKI3YLmjBYIvy5DFDY23oNaYZIsTZfngey07Uh5iLVJgM3bIGCnZeOFOqzjFld9JHWcaHJnasD/bKoGKwJQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - '@tanstack/react-store@0.9.3': - resolution: {integrity: sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - '@tanstack/react-virtual@3.14.9': - resolution: {integrity: sha512-qZyr0FZDP8rDC4WBhsryIZmAd9bveJvFGUJJtskWaew6/0dTRS6wZxnR6VQ5bY2KwL3LjerrHqQLk3a0GKcPXQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - '@tanstack/router-core@1.171.19': - resolution: {integrity: sha512-uCZhgnfmuBA3PoRLIVSjUhQpJQd/HA7p0XxG1IFKnvXU9lIMZ7gIqx45hyuf9JopxXZI8k7qaz9/6PC7mZLdfQ==} - engines: {node: '>=20.19'} - - '@tanstack/store@0.11.1': - resolution: {integrity: sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA==} - - '@tanstack/store@0.9.3': - resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==} - - '@tanstack/virtual-core@3.17.7': - resolution: {integrity: sha512-bp+v10y65sp2H7WpWfIMyxTNfl8ZVfxFTLRjPIFRryi6FV/J33z4IS53WO4pTk36KlvJ4iLiQz+oaydDC1xbcA==} - - '@tauri-apps/api@2.11.1': - resolution: {integrity: sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==} - - '@tauri-apps/cli-darwin-arm64@2.11.4': - resolution: {integrity: sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@tauri-apps/cli-darwin-x64@2.11.4': - resolution: {integrity: sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@tauri-apps/cli-linux-arm-gnueabihf@2.11.4': - resolution: {integrity: sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - - '@tauri-apps/cli-linux-arm64-gnu@2.11.4': - resolution: {integrity: sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@tauri-apps/cli-linux-arm64-musl@2.11.4': - resolution: {integrity: sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@tauri-apps/cli-linux-riscv64-gnu@2.11.4': - resolution: {integrity: sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==} - engines: {node: '>= 10'} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@tauri-apps/cli-linux-x64-gnu@2.11.4': - resolution: {integrity: sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@tauri-apps/cli-linux-x64-musl@2.11.4': - resolution: {integrity: sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@tauri-apps/cli-win32-arm64-msvc@2.11.4': - resolution: {integrity: sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@tauri-apps/cli-win32-ia32-msvc@2.11.4': - resolution: {integrity: sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@tauri-apps/cli-win32-x64-msvc@2.11.4': - resolution: {integrity: sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@tauri-apps/cli@2.11.4': - resolution: {integrity: sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ==} - engines: {node: '>= 10'} - hasBin: true - - '@tauri-apps/plugin-deep-link@2.4.9': - resolution: {integrity: sha512-u0SKOUHnJ1wqeqXsDFq2+kASCBj9xxbG0g9XZWPy9SOmU4wXtp6b/wiYpm6oH6/5fBTQsLqnLhIvqLBRpgHJlA==} - - '@tauri-apps/plugin-notification@2.3.3': - resolution: {integrity: sha512-Zw+ZH18RJb41G4NrfHgIuofJiymusqN+q8fGUIIV7vyCH+5sSn5coqRv/MWB9qETsUs97vmU045q7OyseCV3Qg==} - - '@trickfilm400/rollup-plugin-off-main-thread@3.0.0-pre1': - resolution: {integrity: sha512-/67zpWDBLV+oYAEL682s1ktXL0HgqX76f6gaVGkGnVZlBbm1zd0v4Bz8MFF2GGhoX9rvfq3KSQHubFHwa6w6/Q==} - engines: {node: '>=12'} - - '@ts-morph/common@0.27.0': - resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==} - - '@twemoji/api@17.0.3': - resolution: {integrity: sha512-iwERjxY0QgPGVwT6b1OKG0Oa9nIfHhJw+Ij1TapTBMKTvVCU6qdXPXX/XKwxKx5QZIJW5GwELUCtw8wlaIQ2ug==} - - '@twemoji/parser@17.0.2': - resolution: {integrity: sha512-X/P7pHsGOxnrupQYUVetIeuxBGgffFu8CLwoPMMjH9CWmQvlXiCpbTW/BXxMOCWXQojgHdmgdvm6IsCqAQ5nxA==} - - '@types/cacheable-request@6.0.3': - resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} - - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - - '@types/d3-array@3.2.2': - resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} - - '@types/d3-color@3.1.3': - resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} - - '@types/d3-ease@3.0.2': - resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} - - '@types/d3-interpolate@3.0.4': - resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} - - '@types/d3-path@3.1.1': - resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} - - '@types/d3-scale@4.0.9': - resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} - - '@types/d3-shape@3.1.8': - resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} - - '@types/d3-time@3.0.4': - resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} - - '@types/d3-timer@3.0.2': - resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} - - '@types/debug@4.1.13': - resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/dom-mediacapture-record@1.0.22': - resolution: {integrity: sha512-mUMZLK3NvwRLcAAT9qmcK+9p7tpU2FHdDsntR3YI4+GY88XrgG4XiE7u1Q2LAN2/FZOz/tdMDC3GQCR4T8nFuw==} - - '@types/esrecurse@4.3.1': - resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} - - '@types/estree-jsx@1.0.5': - resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} - - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - - '@types/fs-extra@9.0.13': - resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} - - '@types/hast@3.0.5': - resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} - - '@types/http-cache-semantics@4.2.0': - resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} - - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - - '@types/katex@0.16.8': - resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} - - '@types/keyv@3.1.4': - resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} - - '@types/mdast@4.0.4': - resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - - '@types/ms@2.1.0': - resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - - '@types/node@24.13.3': - resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} - - '@types/node@26.2.0': - resolution: {integrity: sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==} - - '@types/qrcode@1.5.6': - resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} - - '@types/react-dom@19.2.4': - resolution: {integrity: sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==} - peerDependencies: - '@types/react': ^19.2.0 - - '@types/react@19.2.18': - resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} - - '@types/resolve@1.20.2': - resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} - - '@types/responselike@1.0.3': - resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} - - '@types/trusted-types@2.0.7': - resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - - '@types/unist@2.0.11': - resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} - - '@types/unist@3.0.3': - resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - - '@types/use-sync-external-store@0.0.6': - resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} - - '@types/validate-npm-package-name@4.0.2': - resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==} - - '@typescript-eslint/eslint-plugin@8.66.0': - resolution: {integrity: sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.66.0 - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/eslint-plugin@8.67.0': - resolution: {integrity: sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.67.0 - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/parser@8.66.0': - resolution: {integrity: sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/parser@8.67.0': - resolution: {integrity: sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/project-service@8.66.0': - resolution: {integrity: sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/project-service@8.67.0': - resolution: {integrity: sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/scope-manager@8.66.0': - resolution: {integrity: sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/scope-manager@8.67.0': - resolution: {integrity: sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/tsconfig-utils@8.66.0': - resolution: {integrity: sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/tsconfig-utils@8.67.0': - resolution: {integrity: sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/type-utils@8.66.0': - resolution: {integrity: sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/type-utils@8.67.0': - resolution: {integrity: sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/types@8.66.0': - resolution: {integrity: sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/types@8.67.0': - resolution: {integrity: sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.66.0': - resolution: {integrity: sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/typescript-estree@8.67.0': - resolution: {integrity: sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/utils@8.66.0': - resolution: {integrity: sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/utils@8.67.0': - resolution: {integrity: sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/visitor-keys@8.66.0': - resolution: {integrity: sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/visitor-keys@8.67.0': - resolution: {integrity: sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript/typescript-aix-ppc64@7.0.2': - resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} - engines: {node: '>=16.20.0'} - cpu: [ppc64] - os: [aix] - - '@typescript/typescript-darwin-arm64@7.0.2': - resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [darwin] - - '@typescript/typescript-darwin-x64@7.0.2': - resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [darwin] - - '@typescript/typescript-freebsd-arm64@7.0.2': - resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [freebsd] - - '@typescript/typescript-freebsd-x64@7.0.2': - resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [freebsd] - - '@typescript/typescript-linux-arm64@7.0.2': - resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [linux] - - '@typescript/typescript-linux-arm@7.0.2': - resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} - engines: {node: '>=16.20.0'} - cpu: [arm] - os: [linux] - - '@typescript/typescript-linux-loong64@7.0.2': - resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} - engines: {node: '>=16.20.0'} - cpu: [loong64] - os: [linux] - - '@typescript/typescript-linux-mips64el@7.0.2': - resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} - engines: {node: '>=16.20.0'} - cpu: [mips64el] - os: [linux] - - '@typescript/typescript-linux-ppc64@7.0.2': - resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} - engines: {node: '>=16.20.0'} - cpu: [ppc64] - os: [linux] - - '@typescript/typescript-linux-riscv64@7.0.2': - resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} - engines: {node: '>=16.20.0'} - cpu: [riscv64] - os: [linux] - - '@typescript/typescript-linux-s390x@7.0.2': - resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} - engines: {node: '>=16.20.0'} - cpu: [s390x] - os: [linux] - - '@typescript/typescript-linux-x64@7.0.2': - resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [linux] - - '@typescript/typescript-netbsd-arm64@7.0.2': - resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [netbsd] - - '@typescript/typescript-netbsd-x64@7.0.2': - resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [netbsd] - - '@typescript/typescript-openbsd-arm64@7.0.2': - resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [openbsd] - - '@typescript/typescript-openbsd-x64@7.0.2': - resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [openbsd] - - '@typescript/typescript-sunos-x64@7.0.2': - resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [sunos] - - '@typescript/typescript-win32-arm64@7.0.2': - resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [win32] - - '@typescript/typescript-win32-x64@7.0.2': - resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [win32] - - '@ungap/structured-clone@1.3.3': - resolution: {integrity: sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==} - - '@vitejs/plugin-react@6.0.5': - resolution: {integrity: sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==} - engines: {node: ^20.19.0 || >=22.12.0} - peerDependencies: - '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 - babel-plugin-react-compiler: ^1.0.0 - vite: ^8.0.0 - peerDependenciesMeta: - '@rolldown/plugin-babel': - optional: true - babel-plugin-react-compiler: - optional: true - - '@vitest/expect@4.1.11': - resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} - - '@vitest/mocker@4.1.11': - resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@4.1.11': - resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} - - '@vitest/runner@4.1.11': - resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} - - '@vitest/snapshot@4.1.11': - resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} - - '@vitest/spy@4.1.11': - resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} - - '@vitest/utils@4.1.11': - resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} - - '@xmldom/xmldom@0.8.13': - resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} - engines: {node: '>=10.0.0'} - deprecated: this version has critical issues, please update to the latest version - - abbrev@4.0.0: - resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==} - engines: {node: ^20.17.0 || >=22.9.0} - - accepts@2.0.0: - resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} - engines: {node: '>= 0.6'} - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn@8.18.0: - resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} - engines: {node: '>=0.4.0'} - hasBin: true - - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} - - ajv-formats@2.1.1: - resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - - ajv-formats@3.0.1: - resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - - ajv@6.15.0: - resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} - - ajv@8.20.0: - resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} - - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.3.0: - resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - app-builder-lib@26.15.3: - resolution: {integrity: sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==} - engines: {node: '>=14.0.0'} - peerDependencies: - dmg-builder: 26.15.3 - electron-builder-squirrel-windows: 26.15.3 - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - aria-hidden@1.2.6: - resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} - engines: {node: '>=10'} - - array-buffer-byte-length@1.0.2: - resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} - engines: {node: '>= 0.4'} - - arraybuffer.prototype.slice@1.0.4: - resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} - engines: {node: '>= 0.4'} - - asn1js@3.0.10: - resolution: {integrity: sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==} - engines: {node: '>=12.0.0'} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - ast-types@0.16.1: - resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} - engines: {node: '>=4'} - - async-exit-hook@2.0.1: - resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==} - engines: {node: '>=0.12.0'} - - async-function@1.0.0: - resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} - engines: {node: '>= 0.4'} - - async@3.2.6: - resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - at-least-node@1.0.0: - resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} - engines: {node: '>= 4.0.0'} - - atomically@1.7.0: - resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==} - engines: {node: '>=10.12.0'} - - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} - - aws4@1.13.2: - resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} - - babel-plugin-polyfill-corejs2@0.4.17: - resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - - babel-plugin-polyfill-corejs3@0.14.2: - resolution: {integrity: sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - - babel-plugin-polyfill-regenerator@0.6.8: - resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - - bail@2.0.2: - resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - baseline-browser-mapping@2.11.15: - resolution: {integrity: sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA==} - engines: {node: '>=6.0.0'} - hasBin: true - - bluebird@3.7.2: - resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - - body-parser@2.3.0: - resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} - engines: {node: '>=18'} - - boolean@3.2.0: - resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - - brace-expansion@1.1.18: - resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==} - - brace-expansion@2.1.4: - resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==} - - brace-expansion@5.0.9: - resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} - engines: {node: 20 || >=22} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - browserslist@4.28.8: - resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - builder-util-runtime@9.7.0: - resolution: {integrity: sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==} - engines: {node: '>=12.0.0'} - - builder-util@26.15.3: - resolution: {integrity: sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==} - engines: {node: '>=14.0.0'} - - bundle-name@4.1.0: - resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} - engines: {node: '>=18'} - - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - bytestreamjs@2.0.1: - resolution: {integrity: sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==} - engines: {node: '>=6.0.0'} - - cacheable-lookup@5.0.4: - resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} - engines: {node: '>=10.6.0'} - - cacheable-request@7.0.4: - resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} - engines: {node: '>=8'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bind@1.0.9: - resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - caniuse-lite@1.0.30001809: - resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} - - ccount@2.0.1: - resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} - engines: {node: '>=18'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - - character-entities-html4@2.1.0: - resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} - - character-entities-legacy@3.0.0: - resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - - character-entities@2.0.2: - resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} - - character-reference-invalid@2.0.1: - resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - - chownr@3.0.0: - resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} - engines: {node: '>=18'} - - chromium-pickle-js@0.2.0: - resolution: {integrity: sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==} - - ci-info@4.3.1: - resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} - engines: {node: '>=8'} - - ci-info@4.4.0: - resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} - engines: {node: '>=8'} - - class-variance-authority@0.7.1: - resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} - - cli-cursor@5.0.0: - resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} - engines: {node: '>=18'} - - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - - clone-response@1.0.3: - resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} - - clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} - - cmdk@1.1.1: - resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} - peerDependencies: - react: ^18 || ^19 || ^19.0.0-rc - react-dom: ^18 || ^19 || ^19.0.0-rc - - code-block-writer@13.0.3: - resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - - comma-separated-tokens@2.0.3: - resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - - commander@11.1.0: - resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} - engines: {node: '>=16'} - - commander@14.0.3: - resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} - engines: {node: '>=20'} - - commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - - commander@5.1.0: - resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} - engines: {node: '>= 6'} - - commander@8.3.0: - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} - engines: {node: '>= 12'} - - commander@9.5.0: - resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} - engines: {node: ^12.20.0 || >=14} - - common-tags@1.8.2: - resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} - engines: {node: '>=4.0.0'} - - compare-version@0.1.2: - resolution: {integrity: sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==} - engines: {node: '>=0.10.0'} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - conf@10.2.0: - resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==} - engines: {node: '>=12'} - - content-disposition@1.1.0: - resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} - engines: {node: '>=18'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - content-type@2.1.0: - resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} - engines: {node: '>=18'} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - cookie-es@3.1.1: - resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} - - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} - - core-js-compat@3.50.0: - resolution: {integrity: sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==} - engines: {node: '>=6.4.0'} - - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - - cors@2.8.6: - resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} - engines: {node: '>= 0.10'} - - cosmiconfig@9.0.2: - resolution: {integrity: sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true - - crelt@1.0.7: - resolution: {integrity: sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==} - - cross-dirname@0.1.0: - resolution: {integrity: sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - crypto-random-string@2.0.0: - resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} - engines: {node: '>=8'} - - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - d3-array@3.2.4: - resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} - engines: {node: '>=12'} - - d3-color@3.1.0: - resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} - engines: {node: '>=12'} - - d3-ease@3.0.1: - resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} - engines: {node: '>=12'} - - d3-format@3.1.2: - resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} - engines: {node: '>=12'} - - d3-interpolate@3.0.1: - resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} - engines: {node: '>=12'} - - d3-path@3.1.0: - resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} - engines: {node: '>=12'} - - d3-scale@4.0.2: - resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} - engines: {node: '>=12'} - - d3-shape@3.2.0: - resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} - engines: {node: '>=12'} - - d3-time-format@4.1.0: - resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} - engines: {node: '>=12'} - - d3-time@3.1.0: - resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} - engines: {node: '>=12'} - - d3-timer@3.0.1: - resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} - engines: {node: '>=12'} - - data-view-buffer@1.0.2: - resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} - engines: {node: '>= 0.4'} - - data-view-byte-length@1.0.2: - resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} - engines: {node: '>= 0.4'} - - data-view-byte-offset@1.0.1: - resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} - engines: {node: '>= 0.4'} - - debounce-fn@4.0.0: - resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==} - engines: {node: '>=10'} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decimal.js-light@2.5.1: - resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} - - decode-named-character-reference@1.3.0: - resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} - - decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - - dedent@1.7.2: - resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - deepfilternet3-noise-filter@1.3.0: - resolution: {integrity: sha512-yYFUlPuvPguqcd/R6/OSsr0noGqlqOE50JkCWYHogk+PjLj9qrNgwTt5zKraVkKnw0l4+eXJagkz2SUWtUl4sQ==} - engines: {node: '>=18.0.0'} - peerDependencies: - livekit-client: ^2.0.0 - - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - - default-browser-id@5.0.1: - resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} - engines: {node: '>=18'} - - default-browser@5.5.1: - resolution: {integrity: sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==} - engines: {node: '>=18'} - - defer-to-connect@2.0.1: - resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} - engines: {node: '>=10'} - - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - define-lazy-prop@2.0.0: - resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} - engines: {node: '>=8'} - - define-lazy-prop@3.0.0: - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} - engines: {node: '>=12'} - - define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} - - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - detect-node-es@1.1.0: - resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - - detect-node@2.1.0: - resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} - - devlop@1.1.0: - resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - - diff@8.0.4: - resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} - engines: {node: '>=0.3.1'} - - dir-compare@4.2.0: - resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} - - dmg-builder@26.15.3: - resolution: {integrity: sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==} - - dot-prop@6.0.1: - resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} - engines: {node: '>=10'} - - dotenv-expand@11.0.7: - resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} - engines: {node: '>=12'} - - dotenv@16.6.1: - resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} - engines: {node: '>=12'} - - dotenv@17.4.2: - resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} - engines: {node: '>=12'} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - duplexer2@0.1.4: - resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - - ejs@3.1.10: - resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} - engines: {node: '>=0.10.0'} - hasBin: true - - electron-builder-squirrel-windows@26.15.3: - resolution: {integrity: sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==} - - electron-builder@26.15.3: - resolution: {integrity: sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==} - engines: {node: '>=14.0.0'} - hasBin: true - - electron-publish@26.15.3: - resolution: {integrity: sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==} - - electron-to-chromium@1.5.411: - resolution: {integrity: sha512-gglkxzokjHfawpGxq75XdBV2/l3BAPzrsMs70qgaZdTW5rpV1tC4MdgJVP9fN126bODA4ZJQkn1wryEzJyQXIg==} - - electron-winstaller@5.4.0: - resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==} - engines: {node: '>=8.0.0'} - - electron@43.3.0: - resolution: {integrity: sha512-nLlvu0WFjftWsSaTkV2B/c4NDuJBspTyXu8vKSQ6vLvFt8uG3NgN49LLKcXddwX0GqVvAQDhciWp+4xOdTdhew==} - engines: {node: '>= 22.12.0'} - hasBin: true - - embla-carousel-react@8.6.0: - resolution: {integrity: sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==} - peerDependencies: - react: ^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - - embla-carousel-reactive-utils@8.6.0: - resolution: {integrity: sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==} - peerDependencies: - embla-carousel: 8.6.0 - - embla-carousel@8.6.0: - resolution: {integrity: sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==} - - emoji-regex@10.6.0: - resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emojibase-data@17.0.0: - resolution: {integrity: sha512-Yvgb5AWoHViHV/gq1qr5ZAarcBip+B27/ZLRsUJkbgAEaLlZ/fof9g882LTpmEpyhBNEC0m2SEmItljHsTygjA==} - peerDependencies: - emojibase: '*' - - emojibase@17.0.0: - resolution: {integrity: sha512-bXdpf4HPY3p41zK5swVKZdC/VynsMZ4LoLxdYDE+GucqkFwzcM1GVc4ODfYAlwoKaf2U2oNNUoOO78N96ovpBA==} - engines: {node: '>=18.12.0'} - - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - - enhanced-resolve@5.24.5: - resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==} - engines: {node: '>=10.13.0'} - - enquirer@2.4.1: - resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} - engines: {node: '>=8.6'} - - entities@6.0.1: - resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} - engines: {node: '>=0.12'} - - env-paths@2.2.1: - resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} - engines: {node: '>=6'} - - env-paths@3.0.0: - resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - err-code@2.0.3: - resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} - - error-ex@1.3.4: - resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - - es-abstract-get@1.0.0: - resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==} - engines: {node: '>= 0.4'} - - es-abstract@1.24.2: - resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} - engines: {node: '>= 0.4'} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-module-lexer@2.3.2: - resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} - - es-object-atoms@1.1.2: - resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} - engines: {node: '>= 0.4'} - - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} - - es-to-primitive@1.3.4: - resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==} - engines: {node: '>= 0.4'} - - es-toolkit@1.50.0: - resolution: {integrity: sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==} - - es6-error@4.1.1: - resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} - - esbuild@0.28.1: - resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} - engines: {node: '>=18'} - hasBin: true - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - - eslint-plugin-react-hooks@7.1.1: - resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} - engines: {node: '>=18'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 - - eslint-scope@9.1.2: - resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@5.0.1: - resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - eslint@10.8.1: - resolution: {integrity: sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true - - espree@11.2.0: - resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - esquery@1.7.0: - resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - estree-util-is-identifier-name@3.0.0: - resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} - - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - eta@4.6.0: - resolution: {integrity: sha512-lW6is4T1NFOYnmqGZIfvixqj7A7sSvScF+DN8EK6K58xI5MZ5UvYe0GjopxOXQtZvUn4eDdVuZ8XSoYWTMEKwA==} - engines: {node: '>=20'} - - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - - eventemitter3@5.0.4: - resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} - - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - - eventsource-parser@3.1.1: - resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} - engines: {node: '>=18.0.0'} - - eventsource@3.0.7: - resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} - engines: {node: '>=18.0.0'} - - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - - execa@9.6.1: - resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} - engines: {node: ^18.19.0 || >=20.5.0} - - expect-type@1.4.0: - resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} - engines: {node: '>=12.0.0'} - - exponential-backoff@3.1.3: - resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} - - express-rate-limit@8.6.2: - resolution: {integrity: sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A==} - engines: {node: '>= 16'} - peerDependencies: - express: '>= 4.11' - - express@5.2.1: - resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} - engines: {node: '>= 18'} - - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - - fallow-type-aware@3.17.0: - resolution: {integrity: sha512-DFtZUK5oqP56tSsrBWPzjrTIwPPxwlBV8QoSaGghMru347Nqj8ZBY6u9HFa/TIPcxvFU/zIZMRF8xS/XAWQTzg==} - engines: {node: '>=20'} - hasBin: true - - fallow@3.17.0: - resolution: {integrity: sha512-tbLuvsPq3I2CzpoF/9MgSQ7hWzOqlj3avFUAmCr1J9OERE6z714SlDmA38NVvNy/zuK/70utXB3/b33xC9z29g==} - engines: {node: '>=22'} - hasBin: true - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - - fast-uri@3.1.5: - resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} - - fastq@1.20.1: - resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - figures@6.1.0: - resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} - engines: {node: '>=18'} - - file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} - - filelist@1.0.6: - resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - finalhandler@2.1.1: - resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} - engines: {node: '>= 18.0.0'} - - find-up@3.0.0: - resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} - engines: {node: '>=6'} - - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} - - flatted@3.4.4: - resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} - - for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} - - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - - form-data@4.0.6: - resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} - engines: {node: '>= 6'} - - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - - framer-motion@13.0.0: - resolution: {integrity: sha512-nQGZXlsiigN48nzvE7AL1GLekql+etcphp8v+PQ0X04oxO20yVmV9rU9XQ25c136RdeIYoaWlKT9oAwvJza3mg==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - - fresh@2.0.0: - resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} - engines: {node: '>= 0.8'} - - fs-extra@10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} - - fs-extra@11.3.1: - resolution: {integrity: sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==} - engines: {node: '>=14.14'} - - fs-extra@11.4.0: - resolution: {integrity: sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==} - engines: {node: '>=14.14'} - - fs-extra@7.0.1: - resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} - engines: {node: '>=6 <7 || >=8'} - - fs-extra@8.1.0: - resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} - engines: {node: '>=6 <7 || >=8'} - - fs-extra@9.1.0: - resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} - engines: {node: '>=10'} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - function.prototype.name@1.2.0: - resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==} - engines: {node: '>= 0.4'} - - functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - - fuzzysort@3.1.0: - resolution: {integrity: sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==} - - generator-function@2.0.1: - resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} - engines: {node: '>= 0.4'} - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-east-asian-width@1.6.0: - resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} - engines: {node: '>=18'} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-nonce@1.0.1: - resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} - engines: {node: '>=6'} - - get-own-enumerable-keys@1.0.0: - resolution: {integrity: sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==} - engines: {node: '>=14.16'} - - get-own-enumerable-property-symbols@3.0.2: - resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} - - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - get-stream@9.0.1: - resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} - engines: {node: '>=18'} - - get-symbol-description@1.1.0: - resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} - engines: {node: '>= 0.4'} - - github-slugger@2.0.0: - resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - glob@11.1.0: - resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} - engines: {node: 20 || >=22} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - hasBin: true - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - - global-agent@3.0.0: - resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} - engines: {node: '>=10.0'} - - globals@17.11.0: - resolution: {integrity: sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==} - engines: {node: '>=18'} - - globals@17.9.0: - resolution: {integrity: sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==} - engines: {node: '>=18'} - - globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - got@11.8.6: - resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} - engines: {node: '>=10.19.0'} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - has-bigints@1.1.0: - resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} - engines: {node: '>= 0.4'} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - - has-proto@1.2.0: - resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} - engines: {node: '>= 0.4'} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - - hasown@2.0.4: - resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} - engines: {node: '>= 0.4'} - - hast-util-from-dom@5.0.1: - resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==} - - hast-util-from-html-isomorphic@2.0.0: - resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==} - - hast-util-from-html@2.0.3: - resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} - - hast-util-from-parse5@8.0.3: - resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} - - hast-util-heading-rank@3.0.0: - resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} - - hast-util-is-element@3.0.0: - resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} - - hast-util-parse-selector@4.0.0: - resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} - - hast-util-to-html@9.0.5: - resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} - - hast-util-to-jsx-runtime@2.3.6: - resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} - - hast-util-to-string@3.0.1: - resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} - - hast-util-to-text@4.0.2: - resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} - - hast-util-whitespace@3.0.0: - resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} - - hastscript@9.0.1: - resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} - - hermes-estree@0.25.1: - resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} - - hermes-parser@0.25.1: - resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - - hono@4.13.3: - resolution: {integrity: sha512-r8AO2mYHoLxSHkgafNeC/BXyb2vWRxD3jem4Ts+ptav8oTG5FIRifAjuJEmZI4bSvvc2ns0GxmIYiZnHqN3mMw==} - engines: {node: '>=16.9.0'} - - hosted-git-info@4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} - - html-url-attributes@3.0.1: - resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} - - html-void-elements@3.0.0: - resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - - http-cache-semantics@4.2.0: - resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} - - http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} - engines: {node: '>= 0.8'} - - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} - - http2-wrapper@1.0.3: - resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} - engines: {node: '>=10.19.0'} - - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - human-signals@8.0.1: - resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} - engines: {node: '>=18.18.0'} - - iconv-lite@0.7.3: - resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} - engines: {node: '>=0.10.0'} - - idb@7.1.1: - resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} - - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - ignore@7.0.6: - resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} - engines: {node: '>= 4'} - - immer@11.1.16: - resolution: {integrity: sha512-Xs7H9rBc+kti1J6RueUvbEBkmOz7jqj11XYgf+YMXAYzu8EeE7hwZ9poLXdVfVnGmJu7QAf41T7H2KuF6QoK6Q==} - - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - inline-style-parser@0.2.7: - resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} - - input-otp@1.5.0: - resolution: {integrity: sha512-3AcfdW1sNG0FmSA5hHMBXG7jNW5CdcdKs8ln8JOmn6S2SRkoXoJx+2UwC+ZvjflfnOdHJx0SfQ9/YjlxVYLtGQ==} - peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc - - internal-slot@1.1.0: - resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} - engines: {node: '>= 0.4'} - - internmap@2.0.3: - resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} - engines: {node: '>=12'} - - ip-address@10.5.0: - resolution: {integrity: sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==} - engines: {node: '>= 12'} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - - is-alphabetical@2.0.1: - resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} - - is-alphanumerical@2.0.1: - resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} - - is-array-buffer@3.0.5: - resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} - engines: {node: '>= 0.4'} - - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - is-async-function@2.1.1: - resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} - engines: {node: '>= 0.4'} - - is-bigint@1.1.0: - resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} - engines: {node: '>= 0.4'} - - is-boolean-object@1.2.2: - resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} - engines: {node: '>= 0.4'} - - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - - is-core-module@2.16.2: - resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} - engines: {node: '>= 0.4'} - - is-data-view@1.0.2: - resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} - engines: {node: '>= 0.4'} - - is-date-object@1.1.0: - resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} - engines: {node: '>= 0.4'} - - is-decimal@2.0.1: - resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} - - is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true - - is-docker@3.0.0: - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true - - is-document.all@1.0.0: - resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==} - engines: {node: '>= 0.4'} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-finalizationregistry@1.1.1: - resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} - engines: {node: '>= 0.4'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-generator-function@1.1.2: - resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} - engines: {node: '>= 0.4'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-hexadecimal@2.0.1: - resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - - is-in-ssh@1.0.0: - resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} - engines: {node: '>=20'} - - is-inside-container@1.0.0: - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} - engines: {node: '>=14.16'} - hasBin: true - - is-interactive@2.0.0: - resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} - engines: {node: '>=12'} - - is-map@2.0.3: - resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} - engines: {node: '>= 0.4'} - - is-module@1.0.0: - resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} - - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - - is-number-object@1.1.1: - resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} - engines: {node: '>= 0.4'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-obj@1.0.1: - resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} - engines: {node: '>=0.10.0'} - - is-obj@2.0.0: - resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} - engines: {node: '>=8'} - - is-obj@3.0.0: - resolution: {integrity: sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==} - engines: {node: '>=12'} - - is-plain-obj@4.1.0: - resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} - engines: {node: '>=12'} - - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - - is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} - - is-regexp@1.0.0: - resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} - engines: {node: '>=0.10.0'} - - is-regexp@3.1.0: - resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==} - engines: {node: '>=12'} - - is-set@2.0.3: - resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} - engines: {node: '>= 0.4'} - - is-shared-array-buffer@1.0.4: - resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} - engines: {node: '>= 0.4'} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - - is-stream@4.0.1: - resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} - engines: {node: '>=18'} - - is-string@1.1.1: - resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} - engines: {node: '>= 0.4'} - - is-symbol@1.1.1: - resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} - engines: {node: '>= 0.4'} - - is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} - - is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} - - is-unicode-supported@2.1.0: - resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} - engines: {node: '>=18'} - - is-weakmap@2.0.2: - resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} - engines: {node: '>= 0.4'} - - is-weakref@1.1.1: - resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} - engines: {node: '>= 0.4'} - - is-weakset@2.0.4: - resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} - engines: {node: '>= 0.4'} - - is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} - - is-wsl@3.1.1: - resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} - engines: {node: '>=16'} - - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - - isbinaryfile@4.0.10: - resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} - engines: {node: '>= 8.0.0'} - - isbinaryfile@5.0.7: - resolution: {integrity: sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==} - engines: {node: '>= 18.0.0'} - - isbot@5.2.1: - resolution: {integrity: sha512-dJ+LpKyClQZ7NG+j3OensC/mAZkGpukE9YUrgPYvAZj2doVL0edfDgywTUh5CXa0o+nW9a1V9e5+CJTX8+SxRw==} - engines: {node: '>=18'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - isexe@3.1.5: - resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} - engines: {node: '>=18'} - - isexe@4.0.0: - resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} - engines: {node: '>=20'} - - jackspeak@4.2.3: - resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} - engines: {node: 20 || >=22} - - jake@10.9.4: - resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} - engines: {node: '>=10'} - hasBin: true - - jiti@2.7.0: - resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} - hasBin: true - - jose@6.2.8: - resolution: {integrity: sha512-Bsdjwm3Qsd/P0jR+BHDe3LytDfY7WBq2HmCCLIwuVRHMuEC9ae7/R474GIUdF1NgCyZjzVo/A9DOiOBtXq8ZoQ==} - - jose@6.2.9: - resolution: {integrity: sha512-XrchZOFZUl/T3vTwRe8XK+cJrGtMF4th1ARnDfwbBXFKThGhlsxEE4Zu03AD/bjJSt/9jT/mxrOCkJWOg77aPA==} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-yaml@4.3.1: - resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} - hasBin: true - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - - json-schema-typed@7.0.3: - resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==} - - json-schema-typed@8.0.2: - resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} - - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jsonfile@4.0.0: - resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - - jsonfile@5.0.0: - resolution: {integrity: sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==} - - jsonfile@6.2.1: - resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} - - jsonpointer@5.0.1: - resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} - engines: {node: '>=0.10.0'} - - katex@0.16.47: - resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} - hasBin: true - - katex@0.18.4: - resolution: {integrity: sha512-IMPntbRLOU+eu88XDiFKqQ8Akhr9Tv7jDMXqPhjG9SI1JMA4DIgXk4x9k4skJz2NZJXBRbC+2pYBLj9olqcZow==} - hasBin: true - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - - kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - - lazy-val@1.0.5: - resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==} - - leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-android-arm64@1.33.0: - resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-arm64@1.33.0: - resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-darwin-x64@1.33.0: - resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-freebsd-x64@1.33.0: - resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm-gnueabihf@1.33.0: - resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - lightningcss-linux-arm64-gnu@1.33.0: - resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - lightningcss-linux-arm64-musl@1.33.0: - resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - lightningcss-linux-x64-gnu@1.33.0: - resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - lightningcss-linux-x64-musl@1.33.0: - resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-arm64-msvc@1.33.0: - resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss-win32-x64-msvc@1.33.0: - resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} - engines: {node: '>= 12.0.0'} - - lightningcss@1.33.0: - resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} - engines: {node: '>= 12.0.0'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - livekit-client@2.21.0: - resolution: {integrity: sha512-RBUhPkV/sl1nzl8lokVlK5uATPwn0AlsudCBZXissw/kDl9yz8ac4pNJ43iPpMVoHOeBYH/BZ3vUC1adqa/zFQ==} - peerDependencies: - '@types/dom-mediacapture-record': ^1 - - locate-path@3.0.0: - resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} - engines: {node: '>=6'} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - lodash.debounce@4.0.8: - resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - - lodash@4.18.1: - resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} - - log-symbols@6.0.0: - resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} - engines: {node: '>=18'} - - loglevel@1.9.1: - resolution: {integrity: sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==} - engines: {node: '>= 0.6.0'} - - loglevel@1.9.2: - resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} - engines: {node: '>= 0.6.0'} - - longest-streak@3.1.0: - resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} - - lowercase-keys@2.0.0: - resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} - engines: {node: '>=8'} - - lru-cache@11.5.2: - resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} - engines: {node: 20 || >=22} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - - lucide-react@1.30.0: - resolution: {integrity: sha512-tUIr2jXLbWpCkdtH8XP7P7YppM9ueWgTky99lpWDY6z5REs6B+O6ZQ3U5tHkUUY59ANyOv/PBcs8E4Fe3KO3eA==} - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - lucide-react@1.32.0: - resolution: {integrity: sha512-txX56hMFnRxPi1f9/nH69YN8uvAO6a7Y1KSWKjCDAtdD9+soEgmWuCt6iRm1pkxUZo2+YntSdsE1L6bIuKoY8Q==} - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - markdown-table@3.0.4: - resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} - - matcher@3.0.0: - resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} - engines: {node: '>=10'} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - mdast-util-find-and-replace@3.0.2: - resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} - - mdast-util-from-markdown@2.0.3: - resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} - - mdast-util-gfm-autolink-literal@2.0.1: - resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} - - mdast-util-gfm-footnote@2.1.0: - resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} - - mdast-util-gfm-strikethrough@2.0.0: - resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} - - mdast-util-gfm-table@2.0.0: - resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} - - mdast-util-gfm-task-list-item@2.0.0: - resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} - - mdast-util-gfm@3.1.0: - resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} - - mdast-util-math@3.0.0: - resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==} - - mdast-util-mdx-expression@2.0.1: - resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} - - mdast-util-mdx-jsx@3.2.0: - resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} - - mdast-util-mdxjs-esm@2.0.1: - resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} - - mdast-util-newline-to-break@2.0.0: - resolution: {integrity: sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==} - - mdast-util-phrasing@4.1.0: - resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} - - mdast-util-to-hast@13.2.1: - resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} - - mdast-util-to-markdown@2.1.2: - resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} - - mdast-util-to-string@4.0.0: - resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} - - media-typer@1.1.1: - resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} - engines: {node: '>= 0.8'} - - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromark-core-commonmark@2.0.3: - resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} - - micromark-extension-gfm-autolink-literal@2.1.0: - resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} - - micromark-extension-gfm-footnote@2.1.0: - resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} - - micromark-extension-gfm-strikethrough@2.1.0: - resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} - - micromark-extension-gfm-table@2.1.1: - resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} - - micromark-extension-gfm-tagfilter@2.0.0: - resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} - - micromark-extension-gfm-task-list-item@2.1.0: - resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} - - micromark-extension-gfm@3.0.0: - resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} - - micromark-extension-math@3.1.0: - resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==} - - micromark-factory-destination@2.0.1: - resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} - - micromark-factory-label@2.0.1: - resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} - - micromark-factory-space@2.0.1: - resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} - - micromark-factory-title@2.0.1: - resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} - - micromark-factory-whitespace@2.0.1: - resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} - - micromark-util-character@2.1.1: - resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} - - micromark-util-chunked@2.0.1: - resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} - - micromark-util-classify-character@2.0.1: - resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} - - micromark-util-combine-extensions@2.0.1: - resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} - - micromark-util-decode-numeric-character-reference@2.0.2: - resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} - - micromark-util-decode-string@2.0.1: - resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} - - micromark-util-encode@2.0.1: - resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} - - micromark-util-html-tag-name@2.0.1: - resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} - - micromark-util-normalize-identifier@2.0.1: - resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} - - micromark-util-resolve-all@2.0.1: - resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} - - micromark-util-sanitize-uri@2.0.1: - resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} - - micromark-util-subtokenize@2.1.0: - resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} - - micromark-util-symbol@2.0.1: - resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} - - micromark-util-types@2.0.2: - resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} - - micromark@4.0.2: - resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mime-types@3.0.2: - resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} - engines: {node: '>=18'} - - mime@2.6.0: - resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} - engines: {node: '>=4.0.0'} - hasBin: true - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - mimic-fn@3.1.0: - resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==} - engines: {node: '>=8'} - - mimic-function@5.0.1: - resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} - engines: {node: '>=18'} - - mimic-response@1.0.1: - resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} - engines: {node: '>=4'} - - mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - - minimatch@10.2.6: - resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} - engines: {node: 18 || 20 || >=22} - - minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - - minimatch@5.1.9: - resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} - engines: {node: '>=10'} - - minimatch@9.0.9: - resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} - engines: {node: '>=16 || 14 >=14.17'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass@7.1.3: - resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} - engines: {node: '>=16 || 14 >=14.17'} - - minizlib@3.1.0: - resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} - engines: {node: '>= 18'} - - mkdirp@0.5.6: - resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} - hasBin: true - - motion-dom@13.0.0: - resolution: {integrity: sha512-Xk+SJas70uMAUIApg+m3lZDShxI3LBFHq7mFGbBKoRXc2PVPDyAKmzN64Bbzt4CZdP/CItTiJxWtn4TA0v53Ng==} - - motion-utils@13.0.0: - resolution: {integrity: sha512-7DnN7TmbLcYXcG4RVadXIihWlyuM9afoUww8Y5Agg431kGKiuL2/OMyP4mJ5wLz+pvN3t5ySClLOaVXJ+wekRQ==} - - motion@13.0.0: - resolution: {integrity: sha512-RYZjEpgHUCKjBNqjmUQzW93VJWLvEZGTPdKRFSqOdOl07IIMvz+WrsZmD1XXAkoFLruW/8bO1L7UF+ViQTxtLg==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - mtp@https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz: - resolution: {integrity: sha512-MzqeWSaS2lVoiK0coNfY8EPgGNk7QYFS3eRqVYeYBCT0NNm2lQ7T8lLjBuIuMZ6Zh2KAVWhwI2xQIkMdZ7QThA==, tarball: https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz} - version: 0.3.0 - - nanoid@3.3.18: - resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} - - next-themes@0.4.6: - resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} - peerDependencies: - react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - - node-abi@4.33.0: - resolution: {integrity: sha512-vLBWCKb+7LWsX+TbfzWOkw0W81m377tyx3hOweBTjO43CXZnRGS1/JPWs20fr0PgZyDXk6ROYrylsEycK8raDA==} - engines: {node: '>=22.12.0'} - - node-api-version@0.2.1: - resolution: {integrity: sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==} - - node-gyp@12.4.0: - resolution: {integrity: sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==} - engines: {node: ^20.17.0 || >=22.9.0} - hasBin: true - - node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - - node-releases@2.0.53: - resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} - engines: {node: '>=18'} - - nopt@9.0.0: - resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==} - engines: {node: ^20.17.0 || >=22.9.0} - hasBin: true - - normalize-url@6.1.0: - resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} - engines: {node: '>=10'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - - npm-run-path@6.0.0: - resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} - engines: {node: '>=18'} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - object-treeify@1.1.33: - resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==} - engines: {node: '>= 10'} - - object.assign@4.1.7: - resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} - engines: {node: '>= 0.4'} - - obug@2.1.4: - resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} - engines: {node: '>=12.20.0'} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - onetime@7.0.0: - resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} - engines: {node: '>=18'} - - oniguruma-parser@0.12.2: - resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} - - oniguruma-to-es@4.3.6: - resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} - - open@11.0.1: - resolution: {integrity: sha512-NzwMUB6C1D0+Kd+9iMS/H4k+Ck3cTX6Ckyfr/gAGlmvSE1LUQZnEZvWBi4PYmMwH/S5SMeTXnE+9uAz8uF+pWw==} - engines: {node: '>=20'} - - open@8.4.2: - resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} - engines: {node: '>=12'} - - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - ora@8.2.0: - resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} - engines: {node: '>=18'} - - own-keys@1.0.2: - resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==} - engines: {node: '>= 0.4'} - - p-cancelable@2.1.1: - resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} - engines: {node: '>=8'} - - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@3.0.0: - resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} - engines: {node: '>=6'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-entities@4.0.2: - resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - - parse-ms@4.0.0: - resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} - engines: {node: '>=18'} - - parse5@7.3.0: - resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} - - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - - path-browserify@1.0.1: - resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} - - path-exists@3.0.0: - resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} - engines: {node: '>=4'} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-scurry@2.0.2: - resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} - engines: {node: 18 || 20 || >=22} - - path-to-regexp@8.4.2: - resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - pe-library@0.4.1: - resolution: {integrity: sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==} - engines: {node: '>=12', npm: '>=6'} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.2: - resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} - engines: {node: '>=8.6'} - - picomatch@4.0.5: - resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} - engines: {node: '>=12'} - - pkce-challenge@5.0.1: - resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} - engines: {node: '>=16.20.0'} - - pkg-up@3.1.0: - resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} - engines: {node: '>=8'} - - pkijs@3.4.0: - resolution: {integrity: sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==} - engines: {node: '>=16.0.0'} - - plist@3.1.0: - resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} - engines: {node: '>=10.4.0'} - - possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} - - postcss-selector-parser@7.1.5: - resolution: {integrity: sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==} - engines: {node: '>=4'} - - postcss@8.5.26: - resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} - engines: {node: ^10 || ^12 || >=14} - - postject@1.0.0-alpha.6: - resolution: {integrity: sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==} - engines: {node: '>=14.0.0'} - hasBin: true - - powershell-utils@0.1.0: - resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} - engines: {node: '>=20'} - - powershell-utils@0.2.0: - resolution: {integrity: sha512-ZlsFlG7MtSFCoc5xreOvBAozCJ6Pf06opgJjh9ONEv418xpZSAzNjstD36C6+JwOnfSqOW/9uDkqKjezTdxZhw==} - engines: {node: '>=20'} - - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier@3.9.6: - resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} - engines: {node: '>=14'} - hasBin: true - - pretty-bytes@5.6.0: - resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} - engines: {node: '>=6'} - - pretty-bytes@6.1.1: - resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} - engines: {node: ^14.13.1 || >=16.0.0} - - pretty-ms@9.3.0: - resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} - engines: {node: '>=18'} - - proc-log@6.1.0: - resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==} - engines: {node: ^20.17.0 || >=22.9.0} - - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - progress@2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} - - promise-retry@2.0.1: - resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} - engines: {node: '>=10'} - - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - - proper-lockfile@4.1.2: - resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} - - property-information@7.2.0: - resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} - - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - - pump@3.0.4: - resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - pvtsutils@1.3.6: - resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} - - pvutils@1.2.0: - resolution: {integrity: sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==} - engines: {node: '>=16.0.0'} - - qs@6.15.3: - resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} - engines: {node: '>=0.6'} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - quick-lru@5.1.1: - resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} - engines: {node: '>=10'} - - range-parser@1.3.0: - resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} - engines: {node: '>= 0.6'} - - raw-body@3.0.2: - resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} - engines: {node: '>= 0.10'} - - react-dom@19.2.8: - resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==} - peerDependencies: - react: ^19.2.8 - - react-is@19.2.8: - resolution: {integrity: sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==} - - react-markdown@10.1.0: - resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} - peerDependencies: - '@types/react': '>=18' - react: '>=18' - - react-redux@9.3.0: - resolution: {integrity: sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==} - peerDependencies: - '@types/react': ^18.2.25 || ^19 - react: ^18.0 || ^19 - redux: ^5.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - redux: - optional: true - - react-remove-scroll-bar@2.3.8: - resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - react-remove-scroll@2.7.2: - resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - react-resizable-panels@4.12.3: - resolution: {integrity: sha512-GHMJWnDXui/3RX4bT+cgBP+N3N2nkwcoZATr/2xLFpqQQe7TlBrE0cGM0dUa9ceT7A90tUrSRsiqgRG+g0/2AA==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - - react-style-singleton@2.2.3: - resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - react@19.2.8: - resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==} - engines: {node: '>=0.10.0'} - - read-binary-file-arch@1.0.6: - resolution: {integrity: sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==} - hasBin: true - - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - - recast@0.23.21: - resolution: {integrity: sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw==} - engines: {node: '>= 4'} - - recharts@3.10.1: - resolution: {integrity: sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==} - engines: {node: '>=18'} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - redux-thunk@3.1.0: - resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} - peerDependencies: - redux: ^5.0.0 - - redux@5.0.1: - resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} - - reflect.getprototypeof@1.0.10: - resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} - engines: {node: '>= 0.4'} - - regenerate-unicode-properties@10.2.2: - resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==} - engines: {node: '>=4'} - - regenerate@1.4.2: - resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} - - regex-recursion@6.0.2: - resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} - - regex-utilities@2.3.0: - resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} - - regex@6.1.0: - resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} - - regexp.prototype.flags@1.5.4: - resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} - engines: {node: '>= 0.4'} - - regexpu-core@6.4.0: - resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} - engines: {node: '>=4'} - - regjsgen@0.8.0: - resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} - - regjsparser@0.13.2: - resolution: {integrity: sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==} - hasBin: true - - rehype-autolink-headings@7.1.0: - resolution: {integrity: sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==} - - rehype-katex@7.0.1: - resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==} - - rehype-slug@6.0.0: - resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==} - - remark-breaks@4.0.0: - resolution: {integrity: sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==} - - remark-gfm@4.0.1: - resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} - - remark-math@6.0.0: - resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==} - - remark-parse@11.0.0: - resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} - - remark-rehype@11.1.2: - resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} - - remark-stringify@11.0.0: - resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - resedit@1.7.2: - resolution: {integrity: sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==} - engines: {node: '>=12', npm: '>=6'} - - reselect@5.2.0: - resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==} - - resolve-alpn@1.2.1: - resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - resolve@1.22.12: - resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} - engines: {node: '>= 0.4'} - hasBin: true - - responselike@2.0.1: - resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} - - restore-cursor@5.1.0: - resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} - engines: {node: '>=18'} - - retry@0.12.0: - resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} - engines: {node: '>= 4'} - - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rimraf@2.6.3: - resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - - roarr@2.15.4: - resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} - engines: {node: '>=8.0'} - - rolldown@1.2.3: - resolution: {integrity: sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - rollup@4.62.4: - resolution: {integrity: sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} - - run-applescript@7.1.0: - resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} - engines: {node: '>=18'} - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - rxjs@7.8.2: - resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - - safe-array-concat@1.1.4: - resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} - engines: {node: '>=0.4'} - - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - - safe-push-apply@1.0.0: - resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} - engines: {node: '>= 0.4'} - - safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - sanitize-filename@1.6.4: - resolution: {integrity: sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==} - - sax@1.6.1: - resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==} - engines: {node: '>=11.0.0'} - - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - - sdp-transform@2.15.0: - resolution: {integrity: sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==} - hasBin: true - - sdp@3.2.2: - resolution: {integrity: sha512-xZocWwfyp4hkbN4hLWxMjmv2Q8aNa9MhmOZ7L9aCZPT+dZsgRr6wZRrSYE3HTdyk/2pZKPSgqI7ns7Een1xMSA==} - - semver-compare@1.0.0: - resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} - - semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} - hasBin: true - - semver@7.8.5: - resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} - engines: {node: '>=10'} - hasBin: true - - send@1.2.1: - resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} - engines: {node: '>= 18'} - - serialize-error@7.0.1: - resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} - engines: {node: '>=10'} - - serialize-javascript@7.1.0: - resolution: {integrity: sha512-RNEqWOyhhUQYN9V1GfHwu9AR/g+NTciH6Z5u3/no6X3/w+04J2lVDL+svFQVXgXrEGBMG2puMVN3gq2SNGuTGw==} - engines: {node: '>=20.0.0'} - - seroval-plugins@1.6.2: - resolution: {integrity: sha512-TfxuUjlbBESzUOWdTkTKqvSmav0ABym+itetDXLK6mDz8SmrpdI30aF8RTXE8Bvq+tH/1yIDkvy3W0lfQb1ipQ==} - engines: {node: '>=10'} - peerDependencies: - seroval: ^1.0 - - seroval@1.6.2: - resolution: {integrity: sha512-mPT+SD2TrlB6wvte1KkYOYUkubaTbd6pZ/6Kk3C9nxzrHmCZyhxOO7XGAeL7f+yLKZglzGtM9odUVvg/EhO+vQ==} - engines: {node: '>=10'} - - serve-static@2.2.1: - resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} - engines: {node: '>= 18'} - - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - - set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} - - set-proto@1.0.0: - resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} - engines: {node: '>= 0.4'} - - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - - shadcn@4.18.0: - resolution: {integrity: sha512-tUFZgkYmfVNQVm3xX7lhSzOvDsp+O14ac5dwgXIr5mIsr79ISueb/Mu+ZtWMz0DH6v77u4eYyvbQ9TTMpSn3aw==} - engines: {node: '>=20.18.1'} - hasBin: true - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - shiki@4.4.3: - resolution: {integrity: sha512-Mb/GvXPHBAXdgGIcnfU5L3ldpn1XcxrGkPHwqgRx17/I2XRfqlFKk2vGkHWINn1kdXvzJZeuO3is6I9KLPFm0g==} - engines: {node: '>=20'} - - side-channel-list@1.0.1: - resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.1: - resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} - engines: {node: '>= 0.4'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - simple-update-notifier@2.0.0: - resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} - engines: {node: '>=10'} - - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - smart-buffer@4.2.0: - resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} - engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - - smob@1.6.2: - resolution: {integrity: sha512-RQsvleCbF8cVHEv+xuDGaA4pOizFqJ0GgjtMSRo6oP8pnN7WsigHgVGey6aILRBKv4W2YOMHLqbKdnB6hpB9fw==} - engines: {node: '>=20.0.0'} - - socks@2.8.9: - resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==} - engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - - sonner@2.0.7: - resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc - react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc - - sonner@2.0.8: - resolution: {integrity: sha512-UM/ByIoFra8yzV75n1o0Puu0bw5U/9UNnDacrJNspekBewIfsQ3D6ez1nvlWpt7aTsO6rujQtifBpycwIivqlg==} - peerDependencies: - '@types/react': ^18.0.0 || ^19.0.0 - react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc - react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - source-map@0.8.0: - resolution: {integrity: sha512-d8EqvL+k/SOXCreS/SUzg2ciyHqBBLcN/yuRjFsbvVhHTE2pgei7oAhmPM7kWFbkX6OSMQfUq4KbkF3au9lhYQ==} - engines: {node: '>= 12'} - - space-separated-tokens@2.0.2: - resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - - sprintf-js@1.1.3: - resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - stat-mode@1.0.0: - resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==} - engines: {node: '>= 6'} - - statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} - engines: {node: '>= 0.8'} - - std-env@4.2.0: - resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} - - stdin-discarder@0.2.2: - resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} - engines: {node: '>=18'} - - stop-iteration-iterator@1.1.0: - resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} - engines: {node: '>= 0.4'} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} - - string.prototype.matchall@4.0.12: - resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} - engines: {node: '>= 0.4'} - - string.prototype.trim@1.2.11: - resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==} - engines: {node: '>= 0.4'} - - string.prototype.trimend@1.0.10: - resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==} - engines: {node: '>= 0.4'} - - string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} - - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - - stringify-entities@4.0.4: - resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - - stringify-object@3.3.0: - resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} - engines: {node: '>=4'} - - stringify-object@5.0.0: - resolution: {integrity: sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==} - engines: {node: '>=14.16'} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} - - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - - strip-comments@2.0.1: - resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==} - engines: {node: '>=10'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-final-newline@4.0.0: - resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} - engines: {node: '>=18'} - - style-mod@4.1.3: - resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} - - style-to-js@1.1.21: - resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} - - style-to-object@1.0.14: - resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} - - sumchecker@3.0.1: - resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} - engines: {node: '>= 8.0'} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - systeminformation@5.33.1: - resolution: {integrity: sha512-DEN6ICHk3Tk0Uf/hrAHh7xlt7iL5CJFBtPZinA0H62DrGG/KPKqq/Nzj6lCXPS4Ay/sf/14zNnk9LpqKzBIc+w==} - engines: {node: '>=10.0.0'} - os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] - hasBin: true - - tailwind-merge@3.6.0: - resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} - - tailwind-scrollbar-hide@4.0.0: - resolution: {integrity: sha512-gobtvVcThB2Dxhy0EeYSS1RKQJ5baDFkamkhwBvzvevwX6L4XQfpZ3me9s25Ss1ecFVT5jPYJ50n+7xTBJG9WQ==} - peerDependencies: - tailwindcss: '>=3.0.0 || >= 4.0.0 || >= 4.0.0-beta.8 || >= 4.0.0-alpha.20' - - tailwindcss@4.3.3: - resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} - - tapable@2.3.3: - resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} - engines: {node: '>=6'} - - tar@7.5.22: - resolution: {integrity: sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==} - engines: {node: '>=18'} - - temp-dir@2.0.0: - resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} - engines: {node: '>=8'} - - temp-file@3.4.0: - resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==} - - temp@0.9.4: - resolution: {integrity: sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==} - engines: {node: '>=6.0.0'} - - tempy@0.6.0: - resolution: {integrity: sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==} - engines: {node: '>=10'} - - terser@5.50.0: - resolution: {integrity: sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w==} - engines: {node: '>=10'} - hasBin: true - - tiny-async-pool@1.3.0: - resolution: {integrity: sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==} - - tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@1.3.0: - resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} - engines: {node: '>=18'} - - tinyglobby@0.2.17: - resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} - engines: {node: '>=12.0.0'} - - tinyrainbow@3.1.1: - resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} - engines: {node: '>=14.0.0'} - - tmp-promise@3.0.3: - resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} - - tmp@0.2.7: - resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} - engines: {node: '>=14.14'} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - - trim-lines@3.0.1: - resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - - trough@2.2.0: - resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - - truncate-utf8-bytes@1.0.2: - resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} - - ts-api-utils@2.5.0: - resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' - - ts-morph@26.0.0: - resolution: {integrity: sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==} - - tsconfig-paths@4.2.0: - resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} - engines: {node: '>=6'} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tw-animate-css@1.4.0: - resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} - - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - type-fest@0.13.1: - resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} - engines: {node: '>=10'} - - type-fest@0.16.0: - resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} - engines: {node: '>=10'} - - type-is@2.1.0: - resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} - engines: {node: '>= 18'} - - typed-array-buffer@1.0.3: - resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} - engines: {node: '>= 0.4'} - - typed-array-byte-length@1.0.3: - resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} - engines: {node: '>= 0.4'} - - typed-array-byte-offset@1.0.4: - resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} - engines: {node: '>= 0.4'} - - typed-array-length@1.0.8: - resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} - engines: {node: '>= 0.4'} - - typed-emitter@2.1.0: - resolution: {integrity: sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==} - - typescript-eslint@8.66.0: - resolution: {integrity: sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - typescript-eslint@8.67.0: - resolution: {integrity: sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} - hasBin: true - - typescript@7.0.2: - resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} - engines: {node: '>=16.20.0'} - hasBin: true - - unbox-primitive@1.1.0: - resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} - engines: {node: '>= 0.4'} - - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} - - undici-types@8.3.0: - resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} - - undici@6.28.0: - resolution: {integrity: sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==} - engines: {node: '>=18.17'} - - undici@7.29.0: - resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} - engines: {node: '>=20.18.1'} - - unicode-canonical-property-names-ecmascript@2.0.1: - resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} - engines: {node: '>=4'} - - unicode-match-property-ecmascript@2.0.0: - resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} - engines: {node: '>=4'} - - unicode-match-property-value-ecmascript@2.2.1: - resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==} - engines: {node: '>=4'} - - unicode-property-aliases-ecmascript@2.2.0: - resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} - engines: {node: '>=4'} - - unicorn-magic@0.3.0: - resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} - engines: {node: '>=18'} - - unified@11.0.5: - resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - - unique-string@2.0.0: - resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} - engines: {node: '>=8'} - - unist-util-find-after@5.0.0: - resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} - - unist-util-is@6.0.1: - resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} - - unist-util-position@5.0.0: - resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} - - unist-util-remove-position@5.0.0: - resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} - - unist-util-stringify-position@4.0.0: - resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - - unist-util-visit-parents@6.0.2: - resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} - - unist-util-visit@5.1.0: - resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} - - universalify@0.1.2: - resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} - engines: {node: '>= 4.0.0'} - - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - - unzipper@0.12.5: - resolution: {integrity: sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==} - - upath@1.2.0: - resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} - engines: {node: '>=4'} - - update-browserslist-db@1.3.1: - resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - use-callback-ref@1.3.3: - resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - use-sidecar@1.1.3: - resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - use-sync-external-store@1.6.0: - resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - usehooks-ts@3.1.1: - resolution: {integrity: sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA==} - engines: {node: '>=16.15.0'} - peerDependencies: - react: ^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc - - utf8-byte-length@1.0.5: - resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - validate-npm-package-name@7.0.2: - resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==} - engines: {node: ^20.17.0 || >=22.9.0} - - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - - vaul@1.1.2: - resolution: {integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==} - peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc - - vfile-location@5.0.3: - resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} - - vfile-message@4.0.3: - resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} - - vfile@6.0.3: - resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - - victory-vendor@37.3.6: - resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} - - vite-plugin-pwa@1.3.0: - resolution: {integrity: sha512-c5kMgN+ITrOtHXp8PAtk2uOIEea6XjP/unCGxOWWBzQ6qa65qj/awHg0wf+QF9E/2u9vh86LqxPwzEPNbM2r5A==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@vite-pwa/assets-generator': ^1.0.0 - vite: ^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - workbox-build: ^7.4.1 - workbox-window: ^7.4.1 - peerDependenciesMeta: - '@vite-pwa/assets-generator': - optional: true - - vite@8.2.1: - resolution: {integrity: sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.4.0 - esbuild: ^0.27.0 || ^0.28.0 - jiti: '>=1.21.0' - less: ^4.0.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vitest@4.1.11: - resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.11 - '@vitest/browser-preview': 4.1.11 - '@vitest/browser-webdriverio': 4.1.11 - '@vitest/coverage-istanbul': 4.1.11 - '@vitest/coverage-v8': 4.1.11 - '@vitest/ui': 4.1.11 - happy-dom: '*' - jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@opentelemetry/api': - optional: true - '@types/node': - optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/coverage-istanbul': - optional: true - '@vitest/coverage-v8': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - w3c-keyname@2.2.8: - resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} - - web-namespaces@2.0.1: - resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} - - webcrypto-core@1.9.2: - resolution: {integrity: sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==} - - webrtc-adapter@9.0.6: - resolution: {integrity: sha512-CHbl2ZQbxx164IgWRgzJno4hWtM4tFbRam1QfI3Yxhs3w/DvqluVxVWeXs3oL5/fbGkSNLKo0Ty5MgUWceNhog==} - engines: {node: '>=6.0.0', npm: '>=3.10.0'} - - which-boxed-primitive@1.1.1: - resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} - engines: {node: '>= 0.4'} - - which-builtin-type@1.2.1: - resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} - engines: {node: '>= 0.4'} - - which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} - - which-typed-array@1.1.22: - resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==} - engines: {node: '>= 0.4'} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - which@4.0.0: - resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} - engines: {node: ^16.13.0 || >=18.0.0} - hasBin: true - - which@5.0.0: - resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} - engines: {node: ^18.17.0 || >=20.5.0} - hasBin: true - - which@6.0.1: - resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==} - engines: {node: ^20.17.0 || >=22.9.0} - hasBin: true - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - workbox-background-sync@7.4.1: - resolution: {integrity: sha512-HhT7KE8tOWDm02wRNshXUnUPofMlhenF2DBdUnDPOubhizzPeItkYTmAB6td1Z2cjYPa98vzEiPLEuzn5hN66g==} - - workbox-broadcast-update@7.4.1: - resolution: {integrity: sha512-uAlgslKLvbQY+suirIdnBCSYrcgBhjp81Nj4l1lj/Jmj0MJO2CJERnCJjT0GFVwmReV0N+zs78K6gqd5gr9/+A==} - - workbox-build@7.4.1: - resolution: {integrity: sha512-SDhxIvEAde9Gy/5w4Yo1Jh/M49Z0qE3q0oteyE8zGq0DScxFqVBcCtIXFuLtmtxRQZCMbf0prco4VyEu3KBQuw==} - engines: {node: '>=20.0.0'} - - workbox-cacheable-response@7.4.1: - resolution: {integrity: sha512-8xaFoJdDc2OjrlbbL3gEeBO1WKcMwRqwLRupgqahYXu75yXajPLuwrbXMrIGZuWYXrQwk0xDjOxZ/ujCy/oJYw==} - - workbox-core@7.4.1: - resolution: {integrity: sha512-DT+vu46eh/2vRsSHTY4Xmc32Z1rr9PRlQUXr1Dx30ZuXRWwOsvZgGgcwxcasubQLQmbTNYZjv44LkBAQ4tT5tQ==} - - workbox-expiration@7.4.1: - resolution: {integrity: sha512-lRKUF7b+OGbeXkQk1s6MHXOa3d7Xxf7Of31W6c6hCfipfIyrtdWZ89stq21AHZMaoG7VNFoHply4Ox+rU31TWg==} - - workbox-google-analytics@7.4.1: - resolution: {integrity: sha512-Mks1JwLEt++ZAkF6sS1OpSh9RtAMIsiDgRpK+codiHGIPXeaUOgi4cPc3GFadUl8V5QPeypEk8Oxgl3HlwVzHw==} - - workbox-navigation-preload@7.4.1: - resolution: {integrity: sha512-C4KVsjPcYKJOhr631AxR9XoG2rLF3QiTk5aMv36MXOjtWvm8axwNFAtKUPGsWUwLXXAMgYM1En7fsvndaXeXRQ==} - - workbox-precaching@7.4.1: - resolution: {integrity: sha512-cdr/9qByww7yzEp7zg/qI4ukUrrNjQLgN+ONQRpjy/VqGQXwkgHwr00KksGJK8v0VifwDXBb8a4cWNZH71jn3Q==} - - workbox-range-requests@7.4.1: - resolution: {integrity: sha512-7i2oxAUE82gHdAJBCAQ04JzNOdRPqzuOzGfoUyJpFSmeqBNYGPrAH8GPoPjUQTfp+NycwrD2H68VtuF8qxv0vQ==} - - workbox-recipes@7.4.1: - resolution: {integrity: sha512-gnbVfmV4/TtmQaM4x9AtuXhcdstJsep3XMVeztOrQVPT+R6+6DeBjGTCQ7fFCXm+4GEHUA5VEBTyi5+4gWGeog==} - - workbox-routing@7.4.1: - resolution: {integrity: sha512-yubJGErZOusuidAenaL5ypfhQOa7urxP/f8E0ws7FPb4039RiWXUWBAyUkmUoOL/BcQGen3h0J8872d51IYxtA==} - - workbox-strategies@7.4.1: - resolution: {integrity: sha512-GZxpaw9NbmOelj7667uZ2kpk5BFpOGbO4X0qjwh5ls8XQ8C+Lha5LQchTiUzsTFSS+NlUpftYAyOVXvQUrcqOQ==} - - workbox-streams@7.4.1: - resolution: {integrity: sha512-HWWtraKUbJknd9kgqGcpQ3G114HOPYvqs8HaJMDs2ebLNAimDkVDaWfAXE6Ybl+m8U6KsCE6pWyLYuigWmnAXw==} - - workbox-sw@7.4.1: - resolution: {integrity: sha512-fez5f2DUlDJWTFYkCWQpY10N8gtztd849NswCbVFk0QlcSM4HT5A8x4g4ii650yem4I8tHY0R7JZahwp3ltIPw==} - - workbox-window@7.4.1: - resolution: {integrity: sha512-notZDH2u8VXaqyuD7xaqIfEFi6SRM4SUSd7ewe9PDsVqADuepxX2ZMY3uvuZGxzY5ZOsGC/vD3A/3smFtJt4/A==} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - wsl-utils@1.0.0: - resolution: {integrity: sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==} - engines: {node: '>=20'} - - xmlbuilder@15.1.1: - resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} - engines: {node: '>=8.0'} - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - - yallist@5.0.0: - resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} - engines: {node: '>=18'} - - yaml@2.9.0: - resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} - engines: {node: '>= 14.6'} - hasBin: true - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs@17.7.3: - resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} - engines: {node: '>=12'} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - - yocto-spinner@1.2.2: - resolution: {integrity: sha512-DODGl1wJjA/s5pnJFKau9lIYHT81lnhob1i3e1TjxZRxEhWRKl74nTbWE6H5KlkViQQTo/Z29YFdxzTZAMY3ng==} - engines: {node: '>=18.19'} - - yoctocolors@2.2.0: - resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==} - engines: {node: '>=18'} - - zod-to-json-schema@3.25.2: - resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} - peerDependencies: - zod: ^3.25.28 || ^4 - - zod-validation-error@4.0.2: - resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} - engines: {node: '>=18.0.0'} - peerDependencies: - zod: ^3.25.0 || ^4.0.0 - - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - - zod@4.4.3: - resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} - - zustand@5.0.14: - resolution: {integrity: sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==} - engines: {node: '>=12.20.0'} - peerDependencies: - '@types/react': '>=18.0.0' - immer: '>=9.0.6' - react: '>=18.0.0' - use-sync-external-store: '>=1.2.0' - peerDependenciesMeta: - '@types/react': - optional: true - immer: - optional: true - react: - optional: true - use-sync-external-store: - optional: true - - zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - -snapshots: - - '@apideck/better-ajv-errors@0.3.7(ajv@8.20.0)': - dependencies: - ajv: 8.20.0 - jsonpointer: 5.0.1 - leven: 3.1.0 - - '@babel/code-frame@7.29.7': - dependencies: - '@babel/helper-validator-identifier': 7.29.7 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.29.7': {} - - '@babel/core@7.29.7(supports-color@7.2.0)': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.8 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/helpers': 7.29.7 - '@babel/parser': 7.29.8 - '@babel/template': 7.29.7 - '@babel/traverse': 7.29.8(supports-color@7.2.0) - '@babel/types': 7.29.8 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@7.2.0) - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.29.8': - dependencies: - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/helper-annotate-as-pure@7.29.7': - dependencies: - '@babel/types': 7.29.8 - - '@babel/helper-compilation-targets@7.29.7': - dependencies: - '@babel/compat-data': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.8 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.29.7(supports-color@7.2.0) - '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@7.2.0) - '@babel/traverse': 7.29.8(supports-color@7.2.0) - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/helper-create-regexp-features-plugin@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-annotate-as-pure': 7.29.7 - regexpu-core: 6.4.0 - semver: 6.3.1 - - '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - debug: 4.4.3(supports-color@7.2.0) - lodash.debounce: 4.0.8 - resolve: 1.22.12 - transitivePeerDependencies: - - supports-color - - '@babel/helper-globals@7.29.7': {} - - '@babel/helper-member-expression-to-functions@7.29.7(supports-color@7.2.0)': - dependencies: - '@babel/traverse': 7.29.8(supports-color@7.2.0) - '@babel/types': 7.29.8 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-imports@7.29.7(supports-color@7.2.0)': - dependencies: - '@babel/traverse': 7.29.8(supports-color@7.2.0) - '@babel/types': 7.29.8 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-module-imports': 7.29.7(supports-color@7.2.0) - '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.8(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/helper-optimise-call-expression@7.29.7': - dependencies: - '@babel/types': 7.29.8 - - '@babel/helper-plugin-utils@7.29.7': {} - - '@babel/helper-remap-async-to-generator@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-wrap-function': 7.29.7(supports-color@7.2.0) - '@babel/traverse': 7.29.8(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-member-expression-to-functions': 7.29.7(supports-color@7.2.0) - '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/traverse': 7.29.8(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/helper-skip-transparent-expression-wrappers@7.29.7(supports-color@7.2.0)': - dependencies: - '@babel/traverse': 7.29.8(supports-color@7.2.0) - '@babel/types': 7.29.8 - transitivePeerDependencies: - - supports-color - - '@babel/helper-string-parser@7.29.7': {} - - '@babel/helper-validator-identifier@7.29.7': {} - - '@babel/helper-validator-option@7.29.7': {} - - '@babel/helper-wrap-function@7.29.7(supports-color@7.2.0)': - dependencies: - '@babel/template': 7.29.7 - '@babel/traverse': 7.29.8(supports-color@7.2.0) - '@babel/types': 7.29.8 - transitivePeerDependencies: - - supports-color - - '@babel/helpers@7.29.7': - dependencies: - '@babel/template': 7.29.7 - '@babel/types': 7.29.8 - - '@babel/parser@7.29.8': - dependencies: - '@babel/types': 7.29.8 - - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.8(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@7.2.0) - '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.8(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - - '@babel/plugin-syntax-import-assertions@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-arrow-functions@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-async-generator-functions@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/traverse': 7.29.8(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-async-to-generator@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-module-imports': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-block-scoped-functions@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-block-scoping@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-class-properties@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-class-static-block@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-classes@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-globals': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/traverse': 7.29.8(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-computed-properties@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/template': 7.29.7 - - '@babel/plugin-transform-destructuring@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.8(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-dotall-regex@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-duplicate-keys@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-dynamic-import@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-explicit-resource-management@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-exponentiation-operator@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-export-namespace-from@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-for-of@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-function-name@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.8(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-json-strings@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-literals@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-logical-assignment-operators@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-member-expression-literals@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-modules-amd@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-modules-systemjs@7.29.8(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.8(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-modules-umd@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-named-capturing-groups-regex@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-new-target@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-nullish-coalescing-operator@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-numeric-separator@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-object-rest-spread@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/traverse': 7.29.8(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-object-super@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-optional-catch-binding@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-optional-chaining@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-parameters@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-private-methods@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-private-property-in-object@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-property-literals@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-regenerator@7.29.8(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-regexp-modifiers@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-reserved-words@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-shorthand-properties@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-spread@7.29.8(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-sticky-regex@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-template-literals@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-typeof-symbol@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@7.2.0) - '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-unicode-escapes@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-unicode-property-regex@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-unicode-regex@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-unicode-sets-regex@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/compat-data': 7.29.7 - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-syntax-import-assertions': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-async-generator-functions': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-async-to-generator': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-block-scoped-functions': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-block-scoping': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-class-static-block': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-computed-properties': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-dotall-regex': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-duplicate-keys': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-dynamic-import': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-explicit-resource-management': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-exponentiation-operator': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-export-namespace-from': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-function-name': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-json-strings': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-literals': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-logical-assignment-operators': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-member-expression-literals': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-modules-amd': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-modules-systemjs': 7.29.8(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-modules-umd': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-new-target': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-numeric-separator': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-object-rest-spread': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-object-super': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-optional-catch-binding': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-property-literals': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-regenerator': 7.29.8(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-regexp-modifiers': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-reserved-words': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-shorthand-properties': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-spread': 7.29.8(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-sticky-regex': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-typeof-symbol': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-unicode-escapes': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-unicode-property-regex': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-unicode-sets-regex': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.7(supports-color@7.2.0)) - babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - core-js-compat: 3.50.0 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.7(supports-color@7.2.0))': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/types': 7.29.8 - esutils: 2.0.3 - - '@babel/preset-typescript@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/runtime@7.29.7': {} - - '@babel/template@7.29.7': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 - - '@babel/traverse@7.29.8(supports-color@7.2.0)': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.8 - '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.8 - '@babel/template': 7.29.7 - '@babel/types': 7.29.8 - debug: 4.4.3(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@babel/types@7.29.8': - dependencies: - '@babel/helper-string-parser': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - - '@base-ui/react@1.7.0(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@babel/runtime': 7.29.7 - '@base-ui/utils': 0.3.2(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@floating-ui/react-dom': 2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@floating-ui/utils': 0.2.12 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - use-sync-external-store: 1.6.0(react@19.2.8) - optionalDependencies: - '@types/react': 19.2.18 - - '@base-ui/utils@0.3.2(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@babel/runtime': 7.29.7 - '@floating-ui/utils': 0.2.12 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - reselect: 5.2.0 - use-sync-external-store: 1.6.0(react@19.2.8) - optionalDependencies: - '@types/react': 19.2.18 - - '@bufbuild/protobuf@1.10.1': {} - - '@codemirror/autocomplete@6.20.3': - dependencies: - '@codemirror/language': 6.12.4 - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.9 - '@lezer/common': 1.5.2 - - '@codemirror/commands@6.11.0': - dependencies: - '@codemirror/language': 6.12.4 - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.9 - '@lezer/common': 1.5.2 - - '@codemirror/lang-css@6.3.1': - dependencies: - '@codemirror/autocomplete': 6.20.3 - '@codemirror/language': 6.12.4 - '@codemirror/state': 6.7.1 - '@lezer/common': 1.5.2 - '@lezer/css': 1.3.6 - - '@codemirror/lang-html@6.4.12': - dependencies: - '@codemirror/autocomplete': 6.20.3 - '@codemirror/lang-css': 6.3.1 - '@codemirror/lang-javascript': 6.2.5 - '@codemirror/language': 6.12.4 - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.9 - '@lezer/common': 1.5.2 - '@lezer/css': 1.3.6 - '@lezer/html': 1.3.13 - - '@codemirror/lang-javascript@6.2.5': - dependencies: - '@codemirror/autocomplete': 6.20.3 - '@codemirror/language': 6.12.4 - '@codemirror/lint': 6.9.7 - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.9 - '@lezer/common': 1.5.2 - '@lezer/javascript': 1.5.4 - - '@codemirror/lang-markdown@6.5.2': - dependencies: - '@codemirror/autocomplete': 6.20.3 - '@codemirror/lang-html': 6.4.12 - '@codemirror/language': 6.12.4 - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.9 - '@lezer/common': 1.5.2 - '@lezer/markdown': 1.7.2 - - '@codemirror/language@6.12.4': - dependencies: - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.9 - '@lezer/common': 1.5.2 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.10 - style-mod: 4.1.3 - - '@codemirror/lint@6.9.7': - dependencies: - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.9 - crelt: 1.0.7 - - '@codemirror/state@6.7.1': - dependencies: - '@marijn/find-cluster-break': 1.0.3 - - '@codemirror/view@6.43.9': - dependencies: - '@codemirror/state': 6.7.1 - crelt: 1.0.7 - style-mod: 4.1.3 - w3c-keyname: 2.2.8 - - '@dotenvx/dotenvx@1.75.1': - dependencies: - '@dotenvx/primitives': 0.8.0 - commander: 11.1.0 - conf: 10.2.0 - dotenv: 17.4.2 - enquirer: 2.4.1 - env-paths: 2.2.1 - execa: 5.1.1 - fdir: 6.5.0(picomatch@4.0.5) - ignore: 5.3.2 - object-treeify: 1.1.33 - open: 8.4.2 - picomatch: 4.0.5 - systeminformation: 5.33.1 - undici: 7.29.0 - which: 4.0.0 - yocto-spinner: 1.2.2 - - '@dotenvx/primitives@0.8.0': {} - - '@electron-internal/extract-zip@1.0.5': {} - - '@electron/asar@3.4.1': - dependencies: - commander: 5.1.0 - glob: 7.2.3 - minimatch: 3.1.5 - - '@electron/fuses@1.8.0': - dependencies: - chalk: 4.1.2 - fs-extra: 9.1.0 - minimist: 1.2.8 - - '@electron/get@3.1.0(supports-color@7.2.0)': - dependencies: - debug: 4.4.3(supports-color@7.2.0) - env-paths: 2.2.1 - fs-extra: 8.1.0 - got: 11.8.6 - progress: 2.0.3 - semver: 6.3.1 - sumchecker: 3.0.1(supports-color@7.2.0) - optionalDependencies: - global-agent: 3.0.0 - transitivePeerDependencies: - - supports-color - - '@electron/get@5.1.0(supports-color@7.2.0)': - dependencies: - debug: 4.4.3(supports-color@7.2.0) - env-paths: 3.0.0 - graceful-fs: 4.2.11 - progress: 2.0.3 - semver: 7.8.5 - sumchecker: 3.0.1(supports-color@7.2.0) - optionalDependencies: - undici: 7.29.0 - transitivePeerDependencies: - - supports-color - - '@electron/notarize@2.5.0(supports-color@7.2.0)': - dependencies: - debug: 4.4.3(supports-color@7.2.0) - fs-extra: 9.1.0 - promise-retry: 2.0.1 - transitivePeerDependencies: - - supports-color - - '@electron/osx-sign@1.3.3(supports-color@7.2.0)': - dependencies: - compare-version: 0.1.2 - debug: 4.4.3(supports-color@7.2.0) - fs-extra: 10.1.0 - isbinaryfile: 4.0.10 - minimist: 1.2.8 - plist: 3.1.0 - transitivePeerDependencies: - - supports-color - - '@electron/rebuild@4.2.0(supports-color@7.2.0)': - dependencies: - '@malept/cross-spawn-promise': 2.0.0 - debug: 4.4.3(supports-color@7.2.0) - node-abi: 4.33.0 - node-api-version: 0.2.1 - node-gyp: 12.4.0 - read-binary-file-arch: 1.0.6(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - '@electron/universal@2.0.3(supports-color@7.2.0)': - dependencies: - '@electron/asar': 3.4.1 - '@malept/cross-spawn-promise': 2.0.0 - debug: 4.4.3(supports-color@7.2.0) - dir-compare: 4.2.0 - fs-extra: 11.4.0 - minimatch: 9.0.9 - plist: 3.1.0 - transitivePeerDependencies: - - supports-color - - '@electron/windows-sign@1.2.2(supports-color@7.2.0)': - dependencies: - cross-dirname: 0.1.0 - debug: 4.4.3(supports-color@7.2.0) - fs-extra: 11.4.0 - minimist: 1.2.8 - postject: 1.0.0-alpha.6 - transitivePeerDependencies: - - supports-color - optional: true - - '@esbuild/aix-ppc64@0.28.1': - optional: true - - '@esbuild/android-arm64@0.28.1': - optional: true - - '@esbuild/android-arm@0.28.1': - optional: true - - '@esbuild/android-x64@0.28.1': - optional: true - - '@esbuild/darwin-arm64@0.28.1': - optional: true - - '@esbuild/darwin-x64@0.28.1': - optional: true - - '@esbuild/freebsd-arm64@0.28.1': - optional: true - - '@esbuild/freebsd-x64@0.28.1': - optional: true - - '@esbuild/linux-arm64@0.28.1': - optional: true - - '@esbuild/linux-arm@0.28.1': - optional: true - - '@esbuild/linux-ia32@0.28.1': - optional: true - - '@esbuild/linux-loong64@0.28.1': - optional: true - - '@esbuild/linux-mips64el@0.28.1': - optional: true - - '@esbuild/linux-ppc64@0.28.1': - optional: true - - '@esbuild/linux-riscv64@0.28.1': - optional: true - - '@esbuild/linux-s390x@0.28.1': - optional: true - - '@esbuild/linux-x64@0.28.1': - optional: true - - '@esbuild/netbsd-arm64@0.28.1': - optional: true - - '@esbuild/netbsd-x64@0.28.1': - optional: true - - '@esbuild/openbsd-arm64@0.28.1': - optional: true - - '@esbuild/openbsd-x64@0.28.1': - optional: true - - '@esbuild/openharmony-arm64@0.28.1': - optional: true - - '@esbuild/sunos-x64@0.28.1': - optional: true - - '@esbuild/win32-arm64@0.28.1': - optional: true - - '@esbuild/win32-ia32@0.28.1': - optional: true - - '@esbuild/win32-x64@0.28.1': - optional: true - - '@eslint-community/eslint-utils@4.10.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))': - dependencies: - eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.2': {} - - '@eslint/config-array@0.23.5(supports-color@7.2.0)': - dependencies: - '@eslint/object-schema': 3.0.5 - debug: 4.4.3(supports-color@7.2.0) - minimatch: 10.2.6 - transitivePeerDependencies: - - supports-color - - '@eslint/config-helpers@0.7.0': - dependencies: - '@eslint/core': 1.2.1 - - '@eslint/core@1.2.1': - dependencies: - '@types/json-schema': 7.0.15 - - '@eslint/js@10.0.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))': - optionalDependencies: - eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - - '@eslint/object-schema@3.0.5': {} - - '@eslint/plugin-kit@0.7.2': - dependencies: - '@eslint/core': 1.2.1 - levn: 0.4.1 - - '@fallow-cli/darwin-arm64@3.17.0': - optional: true - - '@fallow-cli/darwin-x64@3.17.0': - optional: true - - '@fallow-cli/linux-arm64-gnu@3.17.0': - optional: true - - '@fallow-cli/linux-arm64-musl@3.17.0': - optional: true - - '@fallow-cli/linux-x64-gnu@3.17.0': - optional: true - - '@fallow-cli/linux-x64-musl@3.17.0': - optional: true - - '@fallow-cli/win32-arm64-msvc@3.17.0': - optional: true - - '@fallow-cli/win32-x64-msvc@3.17.0': - optional: true - - '@floating-ui/core@1.8.0': - dependencies: - '@floating-ui/utils': 0.2.12 - - '@floating-ui/dom@1.7.6': - dependencies: - '@floating-ui/core': 1.8.0 - '@floating-ui/utils': 0.2.12 - - '@floating-ui/dom@1.8.0': - dependencies: - '@floating-ui/core': 1.8.0 - '@floating-ui/utils': 0.2.12 - - '@floating-ui/react-dom@2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@floating-ui/dom': 1.8.0 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@floating-ui/utils@0.2.12': {} - - '@fontsource-variable/public-sans@5.3.0': {} - - '@hono/node-server@2.1.1(hono@4.13.3)': - dependencies: - hono: 4.13.3 - - '@humanfs/core@0.19.2': - dependencies: - '@humanfs/types': 0.15.0 - - '@humanfs/node@0.16.8': - dependencies: - '@humanfs/core': 0.19.2 - '@humanfs/types': 0.15.0 - '@humanwhocodes/retry': 0.4.3 - - '@humanfs/types@0.15.0': {} - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/retry@0.4.3': {} - - '@isaacs/cliui@9.0.0': {} - - '@isaacs/fs-minipass@4.0.1': - dependencies: - minipass: 7.1.3 - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/source-map@0.3.11': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@lezer/common@1.5.2': {} - - '@lezer/css@1.3.6': - dependencies: - '@lezer/common': 1.5.2 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.10 - - '@lezer/highlight@1.2.3': - dependencies: - '@lezer/common': 1.5.2 - - '@lezer/html@1.3.13': - dependencies: - '@lezer/common': 1.5.2 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.10 - - '@lezer/javascript@1.5.4': - dependencies: - '@lezer/common': 1.5.2 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.10 - - '@lezer/lr@1.4.10': - dependencies: - '@lezer/common': 1.5.2 - - '@lezer/markdown@1.7.2': - dependencies: - '@lezer/common': 1.5.2 - '@lezer/highlight': 1.2.3 - - '@livekit/components-core@0.12.14(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(tslib@2.8.1)': - dependencies: - '@floating-ui/dom': 1.7.6 - livekit-client: 2.21.0(@types/dom-mediacapture-record@1.0.22) - loglevel: 1.9.1 - rxjs: 7.8.2 - tslib: 2.8.1 - - '@livekit/components-react@2.9.23(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tslib@2.8.1)': - dependencies: - '@livekit/components-core': 0.12.14(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22))(tslib@2.8.1) - clsx: 2.1.1 - events: 3.3.0 - jose: 6.2.8 - livekit-client: 2.21.0(@types/dom-mediacapture-record@1.0.22) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - tslib: 2.8.1 - usehooks-ts: 3.1.1(react@19.2.8) - - '@livekit/mutex@1.1.1': {} - - '@livekit/protocol@1.50.4': - dependencies: - '@bufbuild/protobuf': 1.10.1 - - '@malept/cross-spawn-promise@2.0.0': - dependencies: - cross-spawn: 7.0.6 - - '@malept/flatpak-bundler@0.4.0(supports-color@7.2.0)': - dependencies: - debug: 4.4.3(supports-color@7.2.0) - fs-extra: 9.1.0 - lodash: 4.18.1 - tmp-promise: 3.0.3 - transitivePeerDependencies: - - supports-color - - '@marijn/find-cluster-break@1.0.3': {} - - '@methanium/ui@https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(emojibase@17.0.0)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1)(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@base-ui/react': 1.7.0(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@codemirror/autocomplete': 6.20.3 - '@codemirror/commands': 6.11.0 - '@codemirror/lang-markdown': 6.5.2 - '@codemirror/language': 6.12.4 - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.9 - '@shikijs/langs': 4.4.3 - '@tauri-apps/api': 2.11.1 - class-variance-authority: 0.7.1 - clsx: 2.1.1 - cmdk: 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - embla-carousel-react: 8.6.0(react@19.2.8) - emojibase-data: 17.0.0(emojibase@17.0.0) - hast-util-to-jsx-runtime: 2.3.6(supports-color@7.2.0) - input-otp: 1.5.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - katex: 0.18.4 - lucide-react: 1.32.0(react@19.2.8) - next-themes: 0.4.6(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - react-markdown: 10.1.0(@types/react@19.2.18)(react@19.2.8)(supports-color@7.2.0) - react-resizable-panels: 4.12.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - recharts: 3.10.1(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1) - rehype-autolink-headings: 7.1.0 - rehype-katex: 7.0.1 - rehype-slug: 6.0.0 - remark-breaks: 4.0.0 - remark-gfm: 4.0.1(supports-color@7.2.0) - remark-math: 6.0.0(supports-color@7.2.0) - shadcn: 4.18.0(supports-color@7.2.0)(typescript@6.0.3) - shiki: 4.4.3 - sonner: 2.0.8(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - tailwind-merge: 3.6.0 - tw-animate-css: 1.4.0 - unist-util-visit: 5.1.0 - vaul: 1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - transitivePeerDependencies: - - '@cfworker/json-schema' - - '@date-fns/tz' - - '@types/react' - - '@types/react-dom' - - babel-plugin-macros - - date-fns - - emojibase - - react-is - - redux - - supports-color - - typescript - - '@modelcontextprotocol/sdk@1.30.0(supports-color@7.2.0)(zod@3.25.76)': - dependencies: - '@hono/node-server': 2.1.1(hono@4.13.3) - ajv: 8.20.0 - ajv-formats: 3.0.1(ajv@8.20.0) - content-type: 1.0.5 - cors: 2.8.6 - cross-spawn: 7.0.6 - eventsource: 3.0.7 - eventsource-parser: 3.1.1 - express: 5.2.1(supports-color@7.2.0) - express-rate-limit: 8.6.2(express@5.2.1(supports-color@7.2.0))(supports-color@7.2.0) - hono: 4.13.3 - jose: 6.2.9 - json-schema-typed: 8.0.2 - pkce-challenge: 5.0.1 - raw-body: 3.0.2 - zod: 3.25.76 - zod-to-json-schema: 3.25.2(zod@3.25.76) - transitivePeerDependencies: - - supports-color - - '@napi-rs/lzma-linux-x64-gnu@1.5.1': - optional: true - - '@noble/hashes@1.4.0': {} - - '@noble/hashes@2.3.0': {} - - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 - - '@oxc-project/types@0.143.0': {} - - '@peculiar/asn1-schema@2.8.0': - dependencies: - '@peculiar/utils': 2.0.3 - asn1js: 3.0.10 - tslib: 2.8.1 - - '@peculiar/json-schema@1.1.12': - dependencies: - tslib: 2.8.1 - - '@peculiar/utils@2.0.3': - dependencies: - tslib: 2.8.1 - - '@peculiar/webcrypto@1.7.1': - dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/json-schema': 1.1.12 - '@peculiar/utils': 2.0.3 - tslib: 2.8.1 - webcrypto-core: 1.9.2 - - '@radix-ui/primitive@1.1.7': {} - - '@radix-ui/react-compose-refs@1.1.5(@types/react@19.2.18)(react@19.2.8)': - dependencies: - react: 19.2.8 - optionalDependencies: - '@types/react': 19.2.18 - - '@radix-ui/react-context@1.2.2(@types/react@19.2.18)(react@19.2.8)': - dependencies: - react: 19.2.8 - optionalDependencies: - '@types/react': 19.2.18 - - '@radix-ui/react-dialog@1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) - aria-hidden: 1.2.6 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8) - optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) - - '@radix-ui/react-dismissable-layer@1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) - - '@radix-ui/react-focus-guards@1.1.6(@types/react@19.2.18)(react@19.2.8)': - dependencies: - react: 19.2.8 - optionalDependencies: - '@types/react': 19.2.18 - - '@radix-ui/react-focus-scope@1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) - - '@radix-ui/react-id@1.1.4(@types/react@19.2.18)(react@19.2.8)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - optionalDependencies: - '@types/react': 19.2.18 - - '@radix-ui/react-portal@1.1.17(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) - - '@radix-ui/react-presence@1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) - - '@radix-ui/react-primitive@2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) - - '@radix-ui/react-slot@1.3.3(@types/react@19.2.18)(react@19.2.8)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - optionalDependencies: - '@types/react': 19.2.18 - - '@radix-ui/react-use-callback-ref@1.1.4(@types/react@19.2.18)(react@19.2.8)': - dependencies: - react: 19.2.8 - optionalDependencies: - '@types/react': 19.2.18 - - '@radix-ui/react-use-controllable-state@1.2.6(@types/react@19.2.18)(react@19.2.8)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - optionalDependencies: - '@types/react': 19.2.18 - - '@radix-ui/react-use-effect-event@0.0.5(@types/react@19.2.18)(react@19.2.8)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - optionalDependencies: - '@types/react': 19.2.18 - - '@radix-ui/react-use-layout-effect@1.1.4(@types/react@19.2.18)(react@19.2.8)': - dependencies: - react: 19.2.8 - optionalDependencies: - '@types/react': 19.2.18 - - '@reduxjs/toolkit@2.12.0(react-redux@9.3.0(@types/react@19.2.18)(react@19.2.8))(react@19.2.8)': - dependencies: - '@standard-schema/spec': 1.1.0 - '@standard-schema/utils': 0.3.0 - immer: 11.1.16 - redux: 5.0.1 - redux-thunk: 3.1.0(redux@5.0.1) - reselect: 5.2.0 - optionalDependencies: - react: 19.2.8 - react-redux: 9.3.0(@types/react@19.2.18)(react@19.2.8)(redux@5.0.1) - - '@rolldown/binding-android-arm64@1.2.3': - optional: true - - '@rolldown/binding-darwin-arm64@1.2.3': - optional: true - - '@rolldown/binding-darwin-x64@1.2.3': - optional: true - - '@rolldown/binding-freebsd-x64@1.2.3': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.2.3': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.2.3': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.2.3': - optional: true - - '@rolldown/binding-linux-ppc64-gnu@1.2.3': - optional: true - - '@rolldown/binding-linux-s390x-gnu@1.2.3': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.2.3': - optional: true - - '@rolldown/binding-linux-x64-musl@1.2.3': - optional: true - - '@rolldown/binding-openharmony-arm64@1.2.3': - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.2.3': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.2.3': - optional: true - - '@rolldown/pluginutils@1.0.1': {} - - '@rollup/plugin-babel@6.1.0(@babel/core@7.29.7(supports-color@7.2.0))(rollup@4.62.4)(supports-color@7.2.0)': - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-module-imports': 7.29.7(supports-color@7.2.0) - '@rollup/pluginutils': 5.4.0(rollup@4.62.4) - optionalDependencies: - rollup: 4.62.4 - transitivePeerDependencies: - - supports-color - - '@rollup/plugin-node-resolve@16.0.3(rollup@4.62.4)': - dependencies: - '@rollup/pluginutils': 5.4.0(rollup@4.62.4) - '@types/resolve': 1.20.2 - deepmerge: 4.3.1 - is-module: 1.0.0 - resolve: 1.22.12 - optionalDependencies: - rollup: 4.62.4 - - '@rollup/plugin-replace@6.0.3(rollup@4.62.4)': - dependencies: - '@rollup/pluginutils': 5.4.0(rollup@4.62.4) - magic-string: 0.30.21 - optionalDependencies: - rollup: 4.62.4 - - '@rollup/plugin-terser@1.0.0(rollup@4.62.4)': - dependencies: - serialize-javascript: 7.1.0 - smob: 1.6.2 - terser: 5.50.0 - optionalDependencies: - rollup: 4.62.4 - - '@rollup/pluginutils@5.4.0(rollup@4.62.4)': - dependencies: - '@types/estree': 1.0.9 - estree-walker: 2.0.2 - picomatch: 4.0.5 - optionalDependencies: - rollup: 4.62.4 - - '@rollup/rollup-android-arm-eabi@4.62.4': - optional: true - - '@rollup/rollup-android-arm64@4.62.4': - optional: true - - '@rollup/rollup-darwin-arm64@4.62.4': - optional: true - - '@rollup/rollup-darwin-x64@4.62.4': - optional: true - - '@rollup/rollup-freebsd-arm64@4.62.4': - optional: true - - '@rollup/rollup-freebsd-x64@4.62.4': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.62.4': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.62.4': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.62.4': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.62.4': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.62.4': - optional: true - - '@rollup/rollup-linux-loong64-musl@4.62.4': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.62.4': - optional: true - - '@rollup/rollup-linux-ppc64-musl@4.62.4': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.62.4': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.62.4': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.62.4': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.62.4': - optional: true - - '@rollup/rollup-linux-x64-musl@4.62.4': - optional: true - - '@rollup/rollup-openbsd-x64@4.62.4': - optional: true - - '@rollup/rollup-openharmony-arm64@4.62.4': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.62.4': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.62.4': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.62.4': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.62.4': - optional: true - - '@sec-ant/readable-stream@0.4.1': {} - - '@shikijs/core@4.4.3': - dependencies: - '@shikijs/primitive': 4.4.3 - '@shikijs/types': 4.4.3 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.5 - hast-util-to-html: 9.0.5 - - '@shikijs/engine-javascript@4.4.3': - dependencies: - '@shikijs/types': 4.4.3 - '@shikijs/vscode-textmate': 10.0.2 - oniguruma-to-es: 4.3.6 - - '@shikijs/engine-oniguruma@4.4.3': - dependencies: - '@shikijs/types': 4.4.3 - '@shikijs/vscode-textmate': 10.0.2 - - '@shikijs/langs@4.4.3': - dependencies: - '@shikijs/types': 4.4.3 - - '@shikijs/primitive@4.4.3': - dependencies: - '@shikijs/types': 4.4.3 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.5 - - '@shikijs/themes@4.4.3': - dependencies: - '@shikijs/types': 4.4.3 - - '@shikijs/types@4.4.3': - dependencies: - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.5 - - '@shikijs/vscode-textmate@10.0.2': {} - - '@sindresorhus/is@4.6.0': {} - - '@sindresorhus/merge-streams@4.0.0': {} - - '@standard-schema/spec@1.1.0': {} - - '@standard-schema/utils@0.3.0': {} - - '@szmarczak/http-timer@4.0.6': - dependencies: - defer-to-connect: 2.0.1 - - '@tailwindcss/node@4.3.3': - dependencies: - '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.24.5 - jiti: 2.7.0 - lightningcss: 1.32.0 - magic-string: 0.30.21 - source-map-js: 1.2.1 - tailwindcss: 4.3.3 - - '@tailwindcss/oxide-android-arm64@4.3.3': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.3.3': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.3.3': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.3.3': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.3.3': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.3.3': - optional: true - - '@tailwindcss/oxide@4.3.3': - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.3.3 - '@tailwindcss/oxide-darwin-arm64': 4.3.3 - '@tailwindcss/oxide-darwin-x64': 4.3.3 - '@tailwindcss/oxide-freebsd-x64': 4.3.3 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3 - '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3 - '@tailwindcss/oxide-linux-arm64-musl': 4.3.3 - '@tailwindcss/oxide-linux-x64-gnu': 4.3.3 - '@tailwindcss/oxide-linux-x64-musl': 4.3.3 - '@tailwindcss/oxide-wasm32-wasi': 4.3.3 - '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 - '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 - - '@tailwindcss/vite@4.3.3(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0))': - dependencies: - '@tailwindcss/node': 4.3.3 - '@tailwindcss/oxide': 4.3.3 - tailwindcss: 4.3.3 - vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0) - - '@tanstack/devtools-event-client@0.4.4': {} - - '@tanstack/history@1.162.1': {} - - '@tanstack/hotkeys@0.8.0': - dependencies: - '@tanstack/store': 0.11.1 - - '@tanstack/pacer@0.21.1': - dependencies: - '@tanstack/devtools-event-client': 0.4.4 - '@tanstack/store': 0.11.1 - - '@tanstack/query-core@5.101.4': {} - - '@tanstack/react-hotkeys@0.10.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@tanstack/hotkeys': 0.8.0 - '@tanstack/react-store': 0.11.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@tanstack/react-query@5.101.4(react@19.2.8)': - dependencies: - '@tanstack/query-core': 5.101.4 - react: 19.2.8 - - '@tanstack/react-router@1.170.23(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@tanstack/history': 1.162.1 - '@tanstack/react-store': 0.9.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@tanstack/router-core': 1.171.19 - isbot: 5.2.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@tanstack/react-store@0.11.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@tanstack/store': 0.11.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - use-sync-external-store: 1.6.0(react@19.2.8) - - '@tanstack/react-store@0.9.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@tanstack/store': 0.9.3 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - use-sync-external-store: 1.6.0(react@19.2.8) - - '@tanstack/react-virtual@3.14.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@tanstack/virtual-core': 3.17.7 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@tanstack/router-core@1.171.19': - dependencies: - '@tanstack/history': 1.162.1 - cookie-es: 3.1.1 - seroval: 1.6.2 - seroval-plugins: 1.6.2(seroval@1.6.2) - - '@tanstack/store@0.11.1': {} - - '@tanstack/store@0.9.3': {} - - '@tanstack/virtual-core@3.17.7': {} - - '@tauri-apps/api@2.11.1': {} - - '@tauri-apps/cli-darwin-arm64@2.11.4': - optional: true - - '@tauri-apps/cli-darwin-x64@2.11.4': - optional: true - - '@tauri-apps/cli-linux-arm-gnueabihf@2.11.4': - optional: true - - '@tauri-apps/cli-linux-arm64-gnu@2.11.4': - optional: true - - '@tauri-apps/cli-linux-arm64-musl@2.11.4': - optional: true - - '@tauri-apps/cli-linux-riscv64-gnu@2.11.4': - optional: true - - '@tauri-apps/cli-linux-x64-gnu@2.11.4': - optional: true - - '@tauri-apps/cli-linux-x64-musl@2.11.4': - optional: true - - '@tauri-apps/cli-win32-arm64-msvc@2.11.4': - optional: true - - '@tauri-apps/cli-win32-ia32-msvc@2.11.4': - optional: true - - '@tauri-apps/cli-win32-x64-msvc@2.11.4': - optional: true - - '@tauri-apps/cli@2.11.4': - optionalDependencies: - '@tauri-apps/cli-darwin-arm64': 2.11.4 - '@tauri-apps/cli-darwin-x64': 2.11.4 - '@tauri-apps/cli-linux-arm-gnueabihf': 2.11.4 - '@tauri-apps/cli-linux-arm64-gnu': 2.11.4 - '@tauri-apps/cli-linux-arm64-musl': 2.11.4 - '@tauri-apps/cli-linux-riscv64-gnu': 2.11.4 - '@tauri-apps/cli-linux-x64-gnu': 2.11.4 - '@tauri-apps/cli-linux-x64-musl': 2.11.4 - '@tauri-apps/cli-win32-arm64-msvc': 2.11.4 - '@tauri-apps/cli-win32-ia32-msvc': 2.11.4 - '@tauri-apps/cli-win32-x64-msvc': 2.11.4 - - '@tauri-apps/plugin-deep-link@2.4.9': - dependencies: - '@tauri-apps/api': 2.11.1 - - '@tauri-apps/plugin-notification@2.3.3': - dependencies: - '@tauri-apps/api': 2.11.1 - - '@trickfilm400/rollup-plugin-off-main-thread@3.0.0-pre1': - dependencies: - ejs: 3.1.10 - json5: 2.2.3 - magic-string: 0.30.21 - string.prototype.matchall: 4.0.12 - - '@ts-morph/common@0.27.0': - dependencies: - fast-glob: 3.3.3 - minimatch: 10.2.6 - path-browserify: 1.0.1 - - '@twemoji/api@17.0.3': - dependencies: - '@twemoji/parser': 17.0.2 - fs-extra: 8.1.0 - jsonfile: 5.0.0 - universalify: 0.1.2 - - '@twemoji/parser@17.0.2': {} - - '@types/cacheable-request@6.0.3': - dependencies: - '@types/http-cache-semantics': 4.2.0 - '@types/keyv': 3.1.4 - '@types/node': 26.2.0 - '@types/responselike': 1.0.3 - - '@types/chai@5.2.3': - dependencies: - '@types/deep-eql': 4.0.2 - assertion-error: 2.0.1 - - '@types/d3-array@3.2.2': {} - - '@types/d3-color@3.1.3': {} - - '@types/d3-ease@3.0.2': {} - - '@types/d3-interpolate@3.0.4': - dependencies: - '@types/d3-color': 3.1.3 - - '@types/d3-path@3.1.1': {} - - '@types/d3-scale@4.0.9': - dependencies: - '@types/d3-time': 3.0.4 - - '@types/d3-shape@3.1.8': - dependencies: - '@types/d3-path': 3.1.1 - - '@types/d3-time@3.0.4': {} - - '@types/d3-timer@3.0.2': {} - - '@types/debug@4.1.13': - dependencies: - '@types/ms': 2.1.0 - - '@types/deep-eql@4.0.2': {} - - '@types/dom-mediacapture-record@1.0.22': {} - - '@types/esrecurse@4.3.1': {} - - '@types/estree-jsx@1.0.5': - dependencies: - '@types/estree': 1.0.9 - - '@types/estree@1.0.9': {} - - '@types/fs-extra@9.0.13': - dependencies: - '@types/node': 26.2.0 - - '@types/hast@3.0.5': - dependencies: - '@types/unist': 3.0.3 - - '@types/http-cache-semantics@4.2.0': {} - - '@types/json-schema@7.0.15': {} - - '@types/katex@0.16.8': {} - - '@types/keyv@3.1.4': - dependencies: - '@types/node': 26.2.0 - - '@types/mdast@4.0.4': - dependencies: - '@types/unist': 3.0.3 - - '@types/ms@2.1.0': {} - - '@types/node@24.13.3': - dependencies: - undici-types: 7.18.2 - - '@types/node@26.2.0': - dependencies: - undici-types: 8.3.0 - - '@types/qrcode@1.5.6': - dependencies: - '@types/node': 26.2.0 - - '@types/react-dom@19.2.4(@types/react@19.2.18)': - dependencies: - '@types/react': 19.2.18 - - '@types/react@19.2.18': - dependencies: - csstype: 3.2.3 - - '@types/resolve@1.20.2': {} - - '@types/responselike@1.0.3': - dependencies: - '@types/node': 26.2.0 - - '@types/trusted-types@2.0.7': {} - - '@types/unist@2.0.11': {} - - '@types/unist@3.0.3': {} - - '@types/use-sync-external-store@0.0.6': {} - - '@types/validate-npm-package-name@4.0.2': {} - - '@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.66.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.66.0 - '@typescript-eslint/type-utils': 8.66.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.66.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.66.0 - eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - ignore: 7.0.6 - natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.67.0 - '@typescript-eslint/type-utils': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.67.0 - eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - ignore: 7.0.6 - natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.66.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.66.0 - '@typescript-eslint/types': 8.66.0 - '@typescript-eslint/typescript-estree': 8.66.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.66.0 - debug: 4.4.3(supports-color@7.2.0) - eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.67.0 - '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.67.0 - debug: 4.4.3(supports-color@7.2.0) - eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/project-service@8.66.0(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@6.0.3) - '@typescript-eslint/types': 8.66.0 - debug: 4.4.3(supports-color@7.2.0) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/project-service@8.67.0(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.3) - '@typescript-eslint/types': 8.67.0 - debug: 4.4.3(supports-color@7.2.0) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@8.66.0': - dependencies: - '@typescript-eslint/types': 8.66.0 - '@typescript-eslint/visitor-keys': 8.66.0 - - '@typescript-eslint/scope-manager@8.67.0': - dependencies: - '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/visitor-keys': 8.67.0 - - '@typescript-eslint/tsconfig-utils@8.66.0(typescript@6.0.3)': - dependencies: - typescript: 6.0.3 - - '@typescript-eslint/tsconfig-utils@8.67.0(typescript@6.0.3)': - dependencies: - typescript: 6.0.3 - - '@typescript-eslint/type-utils@8.66.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@typescript-eslint/types': 8.66.0 - '@typescript-eslint/typescript-estree': 8.66.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.66.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - debug: 4.4.3(supports-color@7.2.0) - eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - ts-api-utils: 2.5.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/type-utils@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - debug: 4.4.3(supports-color@7.2.0) - eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - ts-api-utils: 2.5.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@8.66.0': {} - - '@typescript-eslint/types@8.67.0': {} - - '@typescript-eslint/typescript-estree@8.66.0(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@typescript-eslint/project-service': 8.66.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@6.0.3) - '@typescript-eslint/types': 8.66.0 - '@typescript-eslint/visitor-keys': 8.66.0 - debug: 4.4.3(supports-color@7.2.0) - minimatch: 10.2.6 - semver: 7.8.5 - tinyglobby: 0.2.17 - ts-api-utils: 2.5.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/typescript-estree@8.67.0(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@typescript-eslint/project-service': 8.67.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.3) - '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/visitor-keys': 8.67.0 - debug: 4.4.3(supports-color@7.2.0) - minimatch: 10.2.6 - semver: 7.8.5 - tinyglobby: 0.2.17 - ts-api-utils: 2.5.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.66.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0)) - '@typescript-eslint/scope-manager': 8.66.0 - '@typescript-eslint/types': 8.66.0 - '@typescript-eslint/typescript-estree': 8.66.0(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': - dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0)) - '@typescript-eslint/scope-manager': 8.67.0 - '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/visitor-keys@8.66.0': - dependencies: - '@typescript-eslint/types': 8.66.0 - eslint-visitor-keys: 5.0.1 - - '@typescript-eslint/visitor-keys@8.67.0': - dependencies: - '@typescript-eslint/types': 8.67.0 - eslint-visitor-keys: 5.0.1 - - '@typescript/typescript-aix-ppc64@7.0.2': - optional: true - - '@typescript/typescript-darwin-arm64@7.0.2': - optional: true - - '@typescript/typescript-darwin-x64@7.0.2': - optional: true - - '@typescript/typescript-freebsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-freebsd-x64@7.0.2': - optional: true - - '@typescript/typescript-linux-arm64@7.0.2': - optional: true - - '@typescript/typescript-linux-arm@7.0.2': - optional: true - - '@typescript/typescript-linux-loong64@7.0.2': - optional: true - - '@typescript/typescript-linux-mips64el@7.0.2': - optional: true - - '@typescript/typescript-linux-ppc64@7.0.2': - optional: true - - '@typescript/typescript-linux-riscv64@7.0.2': - optional: true - - '@typescript/typescript-linux-s390x@7.0.2': - optional: true - - '@typescript/typescript-linux-x64@7.0.2': - optional: true - - '@typescript/typescript-netbsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-netbsd-x64@7.0.2': - optional: true - - '@typescript/typescript-openbsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-openbsd-x64@7.0.2': - optional: true - - '@typescript/typescript-sunos-x64@7.0.2': - optional: true - - '@typescript/typescript-win32-arm64@7.0.2': - optional: true - - '@typescript/typescript-win32-x64@7.0.2': - optional: true - - '@ungap/structured-clone@1.3.3': {} - - '@vitejs/plugin-react@6.0.5(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0))': - dependencies: - '@rolldown/pluginutils': 1.0.1 - vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0) - - '@vitest/expect@4.1.11': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 - chai: 6.2.2 - tinyrainbow: 3.1.1 - - '@vitest/mocker@4.1.11(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0))': - dependencies: - '@vitest/spy': 4.1.11 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0) - - '@vitest/pretty-format@4.1.11': - dependencies: - tinyrainbow: 3.1.1 - - '@vitest/runner@4.1.11': - dependencies: - '@vitest/utils': 4.1.11 - pathe: 2.0.3 - - '@vitest/snapshot@4.1.11': - dependencies: - '@vitest/pretty-format': 4.1.11 - '@vitest/utils': 4.1.11 - magic-string: 0.30.21 - pathe: 2.0.3 - - '@vitest/spy@4.1.11': {} - - '@vitest/utils@4.1.11': - dependencies: - '@vitest/pretty-format': 4.1.11 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.1 - - '@xmldom/xmldom@0.8.13': {} - - abbrev@4.0.0: {} - - accepts@2.0.0: - dependencies: - mime-types: 3.0.2 - negotiator: 1.0.0 - - acorn-jsx@5.3.2(acorn@8.18.0): - dependencies: - acorn: 8.18.0 - - acorn@8.18.0: {} - - agent-base@7.1.4: {} - - ajv-formats@2.1.1(ajv@8.20.0): - optionalDependencies: - ajv: 8.20.0 - - ajv-formats@3.0.1(ajv@8.20.0): - optionalDependencies: - ajv: 8.20.0 - - ajv@6.15.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - - ajv@8.20.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.5 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - - ansi-colors@4.1.3: {} - - ansi-regex@5.0.1: {} - - ansi-regex@6.3.0: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - app-builder-lib@26.15.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@26.15.3)(supports-color@7.2.0): - dependencies: - '@electron/asar': 3.4.1 - '@electron/fuses': 1.8.0 - '@electron/get': 3.1.0(supports-color@7.2.0) - '@electron/notarize': 2.5.0(supports-color@7.2.0) - '@electron/osx-sign': 1.3.3(supports-color@7.2.0) - '@electron/rebuild': 4.2.0(supports-color@7.2.0) - '@electron/universal': 2.0.3(supports-color@7.2.0) - '@malept/flatpak-bundler': 0.4.0(supports-color@7.2.0) - '@noble/hashes': 2.3.0 - '@peculiar/webcrypto': 1.7.1 - '@types/fs-extra': 9.0.13 - ajv: 8.20.0 - asn1js: 3.0.10 - async-exit-hook: 2.0.1 - builder-util: 26.15.3(supports-color@7.2.0) - builder-util-runtime: 9.7.0(supports-color@7.2.0) - chromium-pickle-js: 0.2.0 - ci-info: 4.3.1 - debug: 4.4.3(supports-color@7.2.0) - dmg-builder: 26.15.3(electron-builder-squirrel-windows@26.15.3)(supports-color@7.2.0) - dotenv: 16.6.1 - dotenv-expand: 11.0.7 - ejs: 3.1.10 - electron-builder-squirrel-windows: 26.15.3(dmg-builder@26.15.3)(supports-color@7.2.0) - electron-publish: 26.15.3(supports-color@7.2.0) - fs-extra: 10.1.0 - hosted-git-info: 4.1.0 - isbinaryfile: 5.0.7 - jiti: 2.7.0 - js-yaml: 4.3.1 - json5: 2.2.3 - lazy-val: 1.0.5 - minimatch: 10.2.6 - pkijs: 3.4.0 - plist: 3.1.0 - proper-lockfile: 4.1.2 - resedit: 1.7.2 - semver: 7.7.4 - tar: 7.5.22 - temp-file: 3.4.0 - tiny-async-pool: 1.3.0 - unzipper: 0.12.5 - which: 5.0.0 - transitivePeerDependencies: - - supports-color - - argparse@2.0.1: {} - - aria-hidden@1.2.6: - dependencies: - tslib: 2.8.1 - - array-buffer-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - is-array-buffer: 3.0.5 - - arraybuffer.prototype.slice@1.0.4: - dependencies: - array-buffer-byte-length: 1.0.2 - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - is-array-buffer: 3.0.5 - - asn1js@3.0.10: - dependencies: - pvtsutils: 1.3.6 - pvutils: 1.2.0 - tslib: 2.8.1 - - assertion-error@2.0.1: {} - - ast-types@0.16.1: - dependencies: - tslib: 2.8.1 - - async-exit-hook@2.0.1: {} - - async-function@1.0.0: {} - - async@3.2.6: {} - - asynckit@0.4.0: {} - - at-least-node@1.0.0: {} - - atomically@1.7.0: {} - - available-typed-arrays@1.0.7: - dependencies: - possible-typed-array-names: 1.1.0 - - aws4@1.13.2: {} - - babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0): - dependencies: - '@babel/compat-data': 7.29.7 - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0): - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - core-js-compat: 3.50.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0): - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - bail@2.0.2: {} - - balanced-match@1.0.2: {} - - balanced-match@4.0.4: {} - - base64-js@1.5.1: {} - - baseline-browser-mapping@2.11.15: {} - - bluebird@3.7.2: {} - - body-parser@2.3.0(supports-color@7.2.0): - dependencies: - bytes: 3.1.2 - content-type: 2.1.0 - debug: 4.4.3(supports-color@7.2.0) - http-errors: 2.0.1 - iconv-lite: 0.7.3 - on-finished: 2.4.1 - qs: 6.15.3 - raw-body: 3.0.2 - type-is: 2.1.0 - transitivePeerDependencies: - - supports-color - - boolean@3.2.0: - optional: true - - brace-expansion@1.1.18: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.1.4: - dependencies: - balanced-match: 1.0.2 - - brace-expansion@5.0.9: - dependencies: - balanced-match: 4.0.4 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - browserslist@4.28.8: - dependencies: - baseline-browser-mapping: 2.11.15 - caniuse-lite: 1.0.30001809 - electron-to-chromium: 1.5.411 - node-releases: 2.0.53 - update-browserslist-db: 1.3.1(browserslist@4.28.8) - - buffer-from@1.1.2: {} - - builder-util-runtime@9.7.0(supports-color@7.2.0): - dependencies: - debug: 4.4.3(supports-color@7.2.0) - sax: 1.6.1 - transitivePeerDependencies: - - supports-color - - builder-util@26.15.3(supports-color@7.2.0): - dependencies: - '@types/debug': 4.1.13 - builder-util-runtime: 9.7.0(supports-color@7.2.0) - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@7.2.0) - fs-extra: 10.1.0 - http-proxy-agent: 7.0.2(supports-color@7.2.0) - https-proxy-agent: 7.0.6(supports-color@7.2.0) - js-yaml: 4.3.1 - sanitize-filename: 1.6.4 - source-map-support: 0.5.21 - stat-mode: 1.0.0 - temp-file: 3.4.0 - tiny-async-pool: 1.3.0 - transitivePeerDependencies: - - supports-color - - bundle-name@4.1.0: - dependencies: - run-applescript: 7.1.0 - - bytes@3.1.2: {} - - bytestreamjs@2.0.1: {} - - cacheable-lookup@5.0.4: {} - - cacheable-request@7.0.4: - dependencies: - clone-response: 1.0.3 - get-stream: 5.2.0 - http-cache-semantics: 4.2.0 - keyv: 4.5.4 - lowercase-keys: 2.0.0 - normalize-url: 6.1.0 - responselike: 2.0.1 - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bind@1.0.9: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - get-intrinsic: 1.3.0 - set-function-length: 1.2.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - - callsites@3.1.0: {} - - caniuse-lite@1.0.30001809: {} - - ccount@2.0.1: {} - - chai@6.2.2: {} - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - chalk@5.6.2: {} - - character-entities-html4@2.1.0: {} - - character-entities-legacy@3.0.0: {} - - character-entities@2.0.2: {} - - character-reference-invalid@2.0.1: {} - - chownr@3.0.0: {} - - chromium-pickle-js@0.2.0: {} - - ci-info@4.3.1: {} - - ci-info@4.4.0: {} - - class-variance-authority@0.7.1: - dependencies: - clsx: 2.1.1 - - cli-cursor@5.0.0: - dependencies: - restore-cursor: 5.1.0 - - cli-spinners@2.9.2: {} - - cliui@8.0.1: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - - clone-response@1.0.3: - dependencies: - mimic-response: 1.0.1 - - clsx@2.1.1: {} - - cmdk@1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): - dependencies: - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - transitivePeerDependencies: - - '@types/react' - - '@types/react-dom' - - code-block-writer@13.0.3: {} - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - - comma-separated-tokens@2.0.3: {} - - commander@11.1.0: {} - - commander@14.0.3: {} - - commander@2.20.3: {} - - commander@5.1.0: {} - - commander@8.3.0: {} - - commander@9.5.0: - optional: true - - common-tags@1.8.2: {} - - compare-version@0.1.2: {} - - concat-map@0.0.1: {} - - conf@10.2.0: - dependencies: - ajv: 8.20.0 - ajv-formats: 2.1.1(ajv@8.20.0) - atomically: 1.7.0 - debounce-fn: 4.0.0 - dot-prop: 6.0.1 - env-paths: 2.2.1 - json-schema-typed: 7.0.3 - onetime: 5.1.2 - pkg-up: 3.1.0 - semver: 7.8.5 - - content-disposition@1.1.0: {} - - content-type@1.0.5: {} - - content-type@2.1.0: {} - - convert-source-map@2.0.0: {} - - cookie-es@3.1.1: {} - - cookie-signature@1.2.2: {} - - cookie@0.7.2: {} - - core-js-compat@3.50.0: - dependencies: - browserslist: 4.28.8 - - core-util-is@1.0.3: {} - - cors@2.8.6: - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 - - cosmiconfig@9.0.2(typescript@6.0.3): - dependencies: - env-paths: 2.2.1 - import-fresh: 3.3.1 - js-yaml: 4.3.1 - parse-json: 5.2.0 - optionalDependencies: - typescript: 6.0.3 - - crelt@1.0.7: {} - - cross-dirname@0.1.0: - optional: true - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - crypto-random-string@2.0.0: {} - - cssesc@3.0.0: {} - - csstype@3.2.3: {} - - d3-array@3.2.4: - dependencies: - internmap: 2.0.3 - - d3-color@3.1.0: {} - - d3-ease@3.0.1: {} - - d3-format@3.1.2: {} - - d3-interpolate@3.0.1: - dependencies: - d3-color: 3.1.0 - - d3-path@3.1.0: {} - - d3-scale@4.0.2: - dependencies: - d3-array: 3.2.4 - d3-format: 3.1.2 - d3-interpolate: 3.0.1 - d3-time: 3.1.0 - d3-time-format: 4.1.0 - - d3-shape@3.2.0: - dependencies: - d3-path: 3.1.0 - - d3-time-format@4.1.0: - dependencies: - d3-time: 3.1.0 - - d3-time@3.1.0: - dependencies: - d3-array: 3.2.4 - - d3-timer@3.0.1: {} - - data-view-buffer@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - data-view-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - data-view-byte-offset@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - debounce-fn@4.0.0: - dependencies: - mimic-fn: 3.1.0 - - debug@4.4.3(supports-color@7.2.0): - dependencies: - ms: 2.1.3 - optionalDependencies: - supports-color: 7.2.0 - - decimal.js-light@2.5.1: {} - - decode-named-character-reference@1.3.0: - dependencies: - character-entities: 2.0.2 - - decompress-response@6.0.0: - dependencies: - mimic-response: 3.1.0 - - dedent@1.7.2: {} - - deep-is@0.1.4: {} - - deepfilternet3-noise-filter@1.3.0(livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22)): - dependencies: - livekit-client: 2.21.0(@types/dom-mediacapture-record@1.0.22) - - deepmerge@4.3.1: {} - - default-browser-id@5.0.1: {} - - default-browser@5.5.1: - dependencies: - bundle-name: 4.1.0 - default-browser-id: 5.0.1 - - defer-to-connect@2.0.1: {} - - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 - - define-lazy-prop@2.0.0: {} - - define-lazy-prop@3.0.0: {} - - define-properties@1.2.1: - dependencies: - define-data-property: 1.1.4 - has-property-descriptors: 1.0.2 - object-keys: 1.1.1 - - delayed-stream@1.0.0: {} - - depd@2.0.0: {} - - dequal@2.0.3: {} - - detect-libc@2.1.2: {} - - detect-node-es@1.1.0: {} - - detect-node@2.1.0: - optional: true - - devlop@1.1.0: - dependencies: - dequal: 2.0.3 - - diff@8.0.4: {} - - dir-compare@4.2.0: - dependencies: - minimatch: 3.1.5 - p-limit: 3.1.0 - - dmg-builder@26.15.3(electron-builder-squirrel-windows@26.15.3)(supports-color@7.2.0): - dependencies: - app-builder-lib: 26.15.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@26.15.3)(supports-color@7.2.0) - builder-util: 26.15.3(supports-color@7.2.0) - fs-extra: 10.1.0 - js-yaml: 4.3.1 - transitivePeerDependencies: - - electron-builder-squirrel-windows - - supports-color - - dot-prop@6.0.1: - dependencies: - is-obj: 2.0.0 - - dotenv-expand@11.0.7: - dependencies: - dotenv: 16.6.1 - - dotenv@16.6.1: {} - - dotenv@17.4.2: {} - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - duplexer2@0.1.4: - dependencies: - readable-stream: 2.3.8 - - ee-first@1.1.1: {} - - ejs@3.1.10: - dependencies: - jake: 10.9.4 - - electron-builder-squirrel-windows@26.15.3(dmg-builder@26.15.3)(supports-color@7.2.0): - dependencies: - app-builder-lib: 26.15.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@26.15.3)(supports-color@7.2.0) - builder-util: 26.15.3(supports-color@7.2.0) - electron-winstaller: 5.4.0(supports-color@7.2.0) - transitivePeerDependencies: - - dmg-builder - - supports-color - - electron-builder@26.15.3(electron-builder-squirrel-windows@26.15.3)(supports-color@7.2.0): - dependencies: - app-builder-lib: 26.15.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@26.15.3)(supports-color@7.2.0) - builder-util: 26.15.3(supports-color@7.2.0) - builder-util-runtime: 9.7.0(supports-color@7.2.0) - chalk: 4.1.2 - ci-info: 4.4.0 - dmg-builder: 26.15.3(electron-builder-squirrel-windows@26.15.3)(supports-color@7.2.0) - fs-extra: 10.1.0 - lazy-val: 1.0.5 - simple-update-notifier: 2.0.0 - yargs: 17.7.3 - transitivePeerDependencies: - - electron-builder-squirrel-windows - - supports-color - - electron-publish@26.15.3(supports-color@7.2.0): - dependencies: - '@types/fs-extra': 9.0.13 - aws4: 1.13.2 - builder-util: 26.15.3(supports-color@7.2.0) - builder-util-runtime: 9.7.0(supports-color@7.2.0) - chalk: 4.1.2 - form-data: 4.0.6 - fs-extra: 10.1.0 - lazy-val: 1.0.5 - mime: 2.6.0 - transitivePeerDependencies: - - supports-color - - electron-to-chromium@1.5.411: {} - - electron-winstaller@5.4.0(supports-color@7.2.0): - dependencies: - '@electron/asar': 3.4.1 - debug: 4.4.3(supports-color@7.2.0) - fs-extra: 7.0.1 - lodash: 4.18.1 - temp: 0.9.4 - optionalDependencies: - '@electron/windows-sign': 1.2.2(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - electron@43.3.0(supports-color@7.2.0): - dependencies: - '@electron-internal/extract-zip': 1.0.5 - '@electron/get': 5.1.0(supports-color@7.2.0) - '@types/node': 24.13.3 - transitivePeerDependencies: - - supports-color - - embla-carousel-react@8.6.0(react@19.2.8): - dependencies: - embla-carousel: 8.6.0 - embla-carousel-reactive-utils: 8.6.0(embla-carousel@8.6.0) - react: 19.2.8 - - embla-carousel-reactive-utils@8.6.0(embla-carousel@8.6.0): - dependencies: - embla-carousel: 8.6.0 - - embla-carousel@8.6.0: {} - - emoji-regex@10.6.0: {} - - emoji-regex@8.0.0: {} - - emojibase-data@17.0.0(emojibase@17.0.0): - dependencies: - emojibase: 17.0.0 - - emojibase@17.0.0: {} - - encodeurl@2.0.0: {} - - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - - enhanced-resolve@5.24.5: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.3 - - enquirer@2.4.1: - dependencies: - ansi-colors: 4.1.3 - strip-ansi: 6.0.1 - - entities@6.0.1: {} - - env-paths@2.2.1: {} - - env-paths@3.0.0: {} - - err-code@2.0.3: {} - - error-ex@1.3.4: - dependencies: - is-arrayish: 0.2.1 - - es-abstract-get@1.0.0: - dependencies: - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - is-callable: 1.2.7 - object-inspect: 1.13.4 - - es-abstract@1.24.2: - dependencies: - array-buffer-byte-length: 1.0.2 - arraybuffer.prototype.slice: 1.0.4 - available-typed-arrays: 1.0.7 - call-bind: 1.0.9 - call-bound: 1.0.4 - data-view-buffer: 1.0.2 - data-view-byte-length: 1.0.2 - data-view-byte-offset: 1.0.1 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - es-set-tostringtag: 2.1.0 - es-to-primitive: 1.3.4 - function.prototype.name: 1.2.0 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - get-symbol-description: 1.1.0 - globalthis: 1.0.4 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - has-proto: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.4 - internal-slot: 1.1.0 - is-array-buffer: 3.0.5 - is-callable: 1.2.7 - is-data-view: 1.0.2 - is-negative-zero: 2.0.3 - is-regex: 1.2.1 - is-set: 2.0.3 - is-shared-array-buffer: 1.0.4 - is-string: 1.1.1 - is-typed-array: 1.1.15 - is-weakref: 1.1.1 - math-intrinsics: 1.1.0 - object-inspect: 1.13.4 - object-keys: 1.1.1 - object.assign: 4.1.7 - own-keys: 1.0.2 - regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.4 - safe-push-apply: 1.0.0 - safe-regex-test: 1.1.0 - set-proto: 1.0.0 - stop-iteration-iterator: 1.1.0 - string.prototype.trim: 1.2.11 - string.prototype.trimend: 1.0.10 - string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.3 - typed-array-byte-length: 1.0.3 - typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.8 - unbox-primitive: 1.1.0 - which-typed-array: 1.1.22 - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-module-lexer@2.3.2: {} - - es-object-atoms@1.1.2: - dependencies: - es-errors: 1.3.0 - - es-set-tostringtag@2.1.0: - dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - has-tostringtag: 1.0.2 - hasown: 2.0.4 - - es-to-primitive@1.3.4: - dependencies: - es-abstract-get: 1.0.0 - es-define-property: 1.0.1 - es-errors: 1.3.0 - is-callable: 1.2.7 - is-date-object: 1.1.0 - is-symbol: 1.1.1 - - es-toolkit@1.50.0: {} - - es6-error@4.1.1: - optional: true - - esbuild@0.28.1: - optionalDependencies: - '@esbuild/aix-ppc64': 0.28.1 - '@esbuild/android-arm': 0.28.1 - '@esbuild/android-arm64': 0.28.1 - '@esbuild/android-x64': 0.28.1 - '@esbuild/darwin-arm64': 0.28.1 - '@esbuild/darwin-x64': 0.28.1 - '@esbuild/freebsd-arm64': 0.28.1 - '@esbuild/freebsd-x64': 0.28.1 - '@esbuild/linux-arm': 0.28.1 - '@esbuild/linux-arm64': 0.28.1 - '@esbuild/linux-ia32': 0.28.1 - '@esbuild/linux-loong64': 0.28.1 - '@esbuild/linux-mips64el': 0.28.1 - '@esbuild/linux-ppc64': 0.28.1 - '@esbuild/linux-riscv64': 0.28.1 - '@esbuild/linux-s390x': 0.28.1 - '@esbuild/linux-x64': 0.28.1 - '@esbuild/netbsd-arm64': 0.28.1 - '@esbuild/netbsd-x64': 0.28.1 - '@esbuild/openbsd-arm64': 0.28.1 - '@esbuild/openbsd-x64': 0.28.1 - '@esbuild/openharmony-arm64': 0.28.1 - '@esbuild/sunos-x64': 0.28.1 - '@esbuild/win32-arm64': 0.28.1 - '@esbuild/win32-ia32': 0.28.1 - '@esbuild/win32-x64': 0.28.1 - - escalade@3.2.0: {} - - escape-html@1.0.3: {} - - escape-string-regexp@4.0.0: {} - - escape-string-regexp@5.0.0: {} - - eslint-plugin-react-hooks@7.1.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0): - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/parser': 7.29.8 - eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - hermes-parser: 0.25.1 - zod: 4.4.3 - zod-validation-error: 4.0.2(zod@4.4.3) - transitivePeerDependencies: - - supports-color - - eslint-scope@9.1.2: - dependencies: - '@types/esrecurse': 4.3.1 - '@types/estree': 1.0.9 - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint-visitor-keys@5.0.1: {} - - eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0): - dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0)) - '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.23.5(supports-color@7.2.0) - '@eslint/config-helpers': 0.7.0 - '@eslint/core': 1.2.1 - '@eslint/plugin-kit': 0.7.2 - '@humanfs/node': 0.16.8 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.9 - ajv: 6.15.0 - cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@7.2.0) - escape-string-regexp: 4.0.0 - eslint-scope: 9.1.2 - eslint-visitor-keys: 5.0.1 - espree: 11.2.0 - esquery: 1.7.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 - find-up: 5.0.0 - glob-parent: 6.0.2 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - json-stable-stringify-without-jsonify: 1.0.1 - minimatch: 10.2.6 - natural-compare: 1.4.0 - optionator: 0.9.4 - optionalDependencies: - jiti: 2.7.0 - transitivePeerDependencies: - - supports-color - - espree@11.2.0: - dependencies: - acorn: 8.18.0 - acorn-jsx: 5.3.2(acorn@8.18.0) - eslint-visitor-keys: 5.0.1 - - esprima@4.0.1: {} - - esquery@1.7.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@5.3.0: {} - - estree-util-is-identifier-name@3.0.0: {} - - estree-walker@2.0.2: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.9 - - esutils@2.0.3: {} - - eta@4.6.0: {} - - etag@1.8.1: {} - - eventemitter3@5.0.4: {} - - events@3.3.0: {} - - eventsource-parser@3.1.1: {} - - eventsource@3.0.7: - dependencies: - eventsource-parser: 3.1.1 - - execa@5.1.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - execa@9.6.1: - dependencies: - '@sindresorhus/merge-streams': 4.0.0 - cross-spawn: 7.0.6 - figures: 6.1.0 - get-stream: 9.0.1 - human-signals: 8.0.1 - is-plain-obj: 4.1.0 - is-stream: 4.0.1 - npm-run-path: 6.0.0 - pretty-ms: 9.3.0 - signal-exit: 4.1.0 - strip-final-newline: 4.0.0 - yoctocolors: 2.2.0 - - expect-type@1.4.0: {} - - exponential-backoff@3.1.3: {} - - express-rate-limit@8.6.2(express@5.2.1(supports-color@7.2.0))(supports-color@7.2.0): - dependencies: - debug: 4.4.3(supports-color@7.2.0) - express: 5.2.1(supports-color@7.2.0) - ip-address: 10.5.0 - transitivePeerDependencies: - - supports-color - - express@5.2.1(supports-color@7.2.0): - dependencies: - accepts: 2.0.0 - body-parser: 2.3.0(supports-color@7.2.0) - content-disposition: 1.1.0 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.2.2 - debug: 4.4.3(supports-color@7.2.0) - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 2.1.1(supports-color@7.2.0) - fresh: 2.0.0 - http-errors: 2.0.1 - merge-descriptors: 2.0.0 - mime-types: 3.0.2 - on-finished: 2.4.1 - once: 1.4.0 - parseurl: 1.3.3 - proxy-addr: 2.0.7 - qs: 6.15.3 - range-parser: 1.3.0 - router: 2.2.0(supports-color@7.2.0) - send: 1.2.1(supports-color@7.2.0) - serve-static: 2.2.1(supports-color@7.2.0) - statuses: 2.0.2 - type-is: 2.1.0 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - - extend@3.0.2: {} - - fallow-type-aware@3.17.0: - dependencies: - typescript: 7.0.2 - optional: true - - fallow@3.17.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - '@fallow-cli/darwin-arm64': 3.17.0 - '@fallow-cli/darwin-x64': 3.17.0 - '@fallow-cli/linux-arm64-gnu': 3.17.0 - '@fallow-cli/linux-arm64-musl': 3.17.0 - '@fallow-cli/linux-x64-gnu': 3.17.0 - '@fallow-cli/linux-x64-musl': 3.17.0 - '@fallow-cli/win32-arm64-msvc': 3.17.0 - '@fallow-cli/win32-x64-msvc': 3.17.0 - fallow-type-aware: 3.17.0 - - fast-deep-equal@3.1.3: {} - - fast-glob@3.3.3: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - - fast-uri@3.1.5: {} - - fastq@1.20.1: - dependencies: - reusify: 1.1.0 - - fdir@6.5.0(picomatch@4.0.5): - optionalDependencies: - picomatch: 4.0.5 - - figures@6.1.0: - dependencies: - is-unicode-supported: 2.1.0 - - file-entry-cache@8.0.0: - dependencies: - flat-cache: 4.0.1 - - filelist@1.0.6: - dependencies: - minimatch: 5.1.9 - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - finalhandler@2.1.1(supports-color@7.2.0): - dependencies: - debug: 4.4.3(supports-color@7.2.0) - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - find-up@3.0.0: - dependencies: - locate-path: 3.0.0 - - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - flat-cache@4.0.1: - dependencies: - flatted: 3.4.4 - keyv: 4.5.4 - - flatted@3.4.4: {} - - for-each@0.3.5: - dependencies: - is-callable: 1.2.7 - - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - - form-data@4.0.6: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.4 - mime-types: 2.1.35 - - forwarded@0.2.0: {} - - framer-motion@13.0.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8): - dependencies: - motion-dom: 13.0.0 - motion-utils: 13.0.0 - tslib: 2.8.1 - optionalDependencies: - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - fresh@2.0.0: {} - - fs-extra@10.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.2.1 - universalify: 2.0.1 - - fs-extra@11.3.1: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.2.1 - universalify: 2.0.1 - - fs-extra@11.4.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.2.1 - universalify: 2.0.1 - - fs-extra@7.0.1: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - - fs-extra@8.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - - fs-extra@9.1.0: - dependencies: - at-least-node: 1.0.0 - graceful-fs: 4.2.11 - jsonfile: 6.2.1 - universalify: 2.0.1 - - fs.realpath@1.0.0: {} - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - function.prototype.name@1.2.0: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - es-define-property: 1.0.1 - es-errors: 1.3.0 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.2 - hasown: 2.0.4 - is-callable: 1.2.7 - is-document.all: 1.0.0 - - functions-have-names@1.2.3: {} - - fuzzysort@3.1.0: {} - - generator-function@2.0.1: {} - - gensync@1.0.0-beta.2: {} - - get-caller-file@2.0.5: {} - - get-east-asian-width@1.6.0: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.4 - math-intrinsics: 1.1.0 - - get-nonce@1.0.1: {} - - get-own-enumerable-keys@1.0.0: {} - - get-own-enumerable-property-symbols@3.0.2: {} - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.2 - - get-stream@5.2.0: - dependencies: - pump: 3.0.4 - - get-stream@6.0.1: {} - - get-stream@9.0.1: - dependencies: - '@sec-ant/readable-stream': 0.4.1 - is-stream: 4.0.1 - - get-symbol-description@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - - github-slugger@2.0.0: {} - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - - glob@11.1.0: - dependencies: - foreground-child: 3.3.1 - jackspeak: 4.2.3 - minimatch: 10.2.6 - minipass: 7.1.3 - package-json-from-dist: 1.0.1 - path-scurry: 2.0.2 - - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.5 - once: 1.4.0 - path-is-absolute: 1.0.1 - - global-agent@3.0.0: - dependencies: - boolean: 3.2.0 - es6-error: 4.1.1 - matcher: 3.0.0 - roarr: 2.15.4 - semver: 7.8.5 - serialize-error: 7.0.1 - optional: true - - globals@17.11.0: {} - - globals@17.9.0: {} - - globalthis@1.0.4: - dependencies: - define-properties: 1.2.1 - gopd: 1.2.0 - - gopd@1.2.0: {} - - got@11.8.6: - dependencies: - '@sindresorhus/is': 4.6.0 - '@szmarczak/http-timer': 4.0.6 - '@types/cacheable-request': 6.0.3 - '@types/responselike': 1.0.3 - cacheable-lookup: 5.0.4 - cacheable-request: 7.0.4 - decompress-response: 6.0.0 - http2-wrapper: 1.0.3 - lowercase-keys: 2.0.0 - p-cancelable: 2.1.1 - responselike: 2.0.1 - - graceful-fs@4.2.11: {} - - has-bigints@1.1.0: {} - - has-flag@4.0.0: {} - - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 - - has-proto@1.2.0: - dependencies: - dunder-proto: 1.0.1 - - has-symbols@1.1.0: {} - - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - - hasown@2.0.4: - dependencies: - function-bind: 1.1.2 - - hast-util-from-dom@5.0.1: - dependencies: - '@types/hast': 3.0.5 - hastscript: 9.0.1 - web-namespaces: 2.0.1 - - hast-util-from-html-isomorphic@2.0.0: - dependencies: - '@types/hast': 3.0.5 - hast-util-from-dom: 5.0.1 - hast-util-from-html: 2.0.3 - unist-util-remove-position: 5.0.0 - - hast-util-from-html@2.0.3: - dependencies: - '@types/hast': 3.0.5 - devlop: 1.1.0 - hast-util-from-parse5: 8.0.3 - parse5: 7.3.0 - vfile: 6.0.3 - vfile-message: 4.0.3 - - hast-util-from-parse5@8.0.3: - dependencies: - '@types/hast': 3.0.5 - '@types/unist': 3.0.3 - devlop: 1.1.0 - hastscript: 9.0.1 - property-information: 7.2.0 - vfile: 6.0.3 - vfile-location: 5.0.3 - web-namespaces: 2.0.1 - - hast-util-heading-rank@3.0.0: - dependencies: - '@types/hast': 3.0.5 - - hast-util-is-element@3.0.0: - dependencies: - '@types/hast': 3.0.5 - - hast-util-parse-selector@4.0.0: - dependencies: - '@types/hast': 3.0.5 - - hast-util-to-html@9.0.5: - dependencies: - '@types/hast': 3.0.5 - '@types/unist': 3.0.3 - ccount: 2.0.1 - comma-separated-tokens: 2.0.3 - hast-util-whitespace: 3.0.0 - html-void-elements: 3.0.0 - mdast-util-to-hast: 13.2.1 - property-information: 7.2.0 - space-separated-tokens: 2.0.2 - stringify-entities: 4.0.4 - zwitch: 2.0.4 - - hast-util-to-jsx-runtime@2.3.6(supports-color@7.2.0): - dependencies: - '@types/estree': 1.0.9 - '@types/hast': 3.0.5 - '@types/unist': 3.0.3 - comma-separated-tokens: 2.0.3 - devlop: 1.1.0 - estree-util-is-identifier-name: 3.0.0 - hast-util-whitespace: 3.0.0 - mdast-util-mdx-expression: 2.0.1(supports-color@7.2.0) - mdast-util-mdx-jsx: 3.2.0(supports-color@7.2.0) - mdast-util-mdxjs-esm: 2.0.1(supports-color@7.2.0) - property-information: 7.2.0 - space-separated-tokens: 2.0.2 - style-to-js: 1.1.21 - unist-util-position: 5.0.0 - vfile-message: 4.0.3 - transitivePeerDependencies: - - supports-color - - hast-util-to-string@3.0.1: - dependencies: - '@types/hast': 3.0.5 - - hast-util-to-text@4.0.2: - dependencies: - '@types/hast': 3.0.5 - '@types/unist': 3.0.3 - hast-util-is-element: 3.0.0 - unist-util-find-after: 5.0.0 - - hast-util-whitespace@3.0.0: - dependencies: - '@types/hast': 3.0.5 - - hastscript@9.0.1: - dependencies: - '@types/hast': 3.0.5 - comma-separated-tokens: 2.0.3 - hast-util-parse-selector: 4.0.0 - property-information: 7.2.0 - space-separated-tokens: 2.0.2 - - hermes-estree@0.25.1: {} - - hermes-parser@0.25.1: - dependencies: - hermes-estree: 0.25.1 - - hono@4.13.3: {} - - hosted-git-info@4.1.0: - dependencies: - lru-cache: 6.0.0 - - html-url-attributes@3.0.1: {} - - html-void-elements@3.0.0: {} - - http-cache-semantics@4.2.0: {} - - http-errors@2.0.1: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.2 - toidentifier: 1.0.1 - - http-proxy-agent@7.0.2(supports-color@7.2.0): - dependencies: - agent-base: 7.1.4 - debug: 4.4.3(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - http2-wrapper@1.0.3: - dependencies: - quick-lru: 5.1.1 - resolve-alpn: 1.2.1 - - https-proxy-agent@7.0.6(supports-color@7.2.0): - dependencies: - agent-base: 7.1.4 - debug: 4.4.3(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - human-signals@2.1.0: {} - - human-signals@8.0.1: {} - - iconv-lite@0.7.3: - dependencies: - safer-buffer: 2.1.2 - - idb@7.1.1: {} - - ignore@5.3.2: {} - - ignore@7.0.6: {} - - immer@11.1.16: {} - - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - imurmurhash@0.1.4: {} - - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - - inline-style-parser@0.2.7: {} - - input-otp@1.5.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8): - dependencies: - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - internal-slot@1.1.0: - dependencies: - es-errors: 1.3.0 - hasown: 2.0.4 - side-channel: 1.1.1 - - internmap@2.0.3: {} - - ip-address@10.5.0: {} - - ipaddr.js@1.9.1: {} - - is-alphabetical@2.0.1: {} - - is-alphanumerical@2.0.1: - dependencies: - is-alphabetical: 2.0.1 - is-decimal: 2.0.1 - - is-array-buffer@3.0.5: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - - is-arrayish@0.2.1: {} - - is-async-function@2.1.1: - dependencies: - async-function: 1.0.0 - call-bound: 1.0.4 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - - is-bigint@1.1.0: - dependencies: - has-bigints: 1.1.0 - - is-boolean-object@1.2.2: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-callable@1.2.7: {} - - is-core-module@2.16.2: - dependencies: - hasown: 2.0.4 - - is-data-view@1.0.2: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - is-typed-array: 1.1.15 - - is-date-object@1.1.0: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-decimal@2.0.1: {} - - is-docker@2.2.1: {} - - is-docker@3.0.0: {} - - is-document.all@1.0.0: - dependencies: - call-bound: 1.0.4 - - is-extglob@2.1.1: {} - - is-finalizationregistry@1.1.1: - dependencies: - call-bound: 1.0.4 - - is-fullwidth-code-point@3.0.0: {} - - is-generator-function@1.1.2: - dependencies: - call-bound: 1.0.4 - generator-function: 2.0.1 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-hexadecimal@2.0.1: {} - - is-in-ssh@1.0.0: {} - - is-inside-container@1.0.0: - dependencies: - is-docker: 3.0.0 - - is-interactive@2.0.0: {} - - is-map@2.0.3: {} - - is-module@1.0.0: {} - - is-negative-zero@2.0.3: {} - - is-number-object@1.1.1: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-number@7.0.0: {} - - is-obj@1.0.1: {} - - is-obj@2.0.0: {} - - is-obj@3.0.0: {} - - is-plain-obj@4.1.0: {} - - is-promise@4.0.0: {} - - is-regex@1.2.1: - dependencies: - call-bound: 1.0.4 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.4 - - is-regexp@1.0.0: {} - - is-regexp@3.1.0: {} - - is-set@2.0.3: {} - - is-shared-array-buffer@1.0.4: - dependencies: - call-bound: 1.0.4 - - is-stream@2.0.1: {} - - is-stream@4.0.1: {} - - is-string@1.1.1: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-symbol@1.1.1: - dependencies: - call-bound: 1.0.4 - has-symbols: 1.1.0 - safe-regex-test: 1.1.0 - - is-typed-array@1.1.15: - dependencies: - which-typed-array: 1.1.22 - - is-unicode-supported@1.3.0: {} - - is-unicode-supported@2.1.0: {} - - is-weakmap@2.0.2: {} - - is-weakref@1.1.1: - dependencies: - call-bound: 1.0.4 - - is-weakset@2.0.4: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - - is-wsl@2.2.0: - dependencies: - is-docker: 2.2.1 - - is-wsl@3.1.1: - dependencies: - is-inside-container: 1.0.0 - - isarray@1.0.0: {} - - isarray@2.0.5: {} - - isbinaryfile@4.0.10: {} - - isbinaryfile@5.0.7: {} - - isbot@5.2.1: {} - - isexe@2.0.0: {} - - isexe@3.1.5: {} - - isexe@4.0.0: {} - - jackspeak@4.2.3: - dependencies: - '@isaacs/cliui': 9.0.0 - - jake@10.9.4: - dependencies: - async: 3.2.6 - filelist: 1.0.6 - picocolors: 1.1.1 - - jiti@2.7.0: {} - - jose@6.2.8: {} - - jose@6.2.9: {} - - js-tokens@4.0.0: {} - - js-yaml@4.3.1: - dependencies: - argparse: 2.0.1 - - jsesc@3.1.0: {} - - json-buffer@3.0.1: {} - - json-parse-even-better-errors@2.3.1: {} - - json-schema-traverse@0.4.1: {} - - json-schema-traverse@1.0.0: {} - - json-schema-typed@7.0.3: {} - - json-schema-typed@8.0.2: {} - - json-stable-stringify-without-jsonify@1.0.1: {} - - json-stringify-safe@5.0.1: - optional: true - - json5@2.2.3: {} - - jsonfile@4.0.0: - optionalDependencies: - graceful-fs: 4.2.11 - - jsonfile@5.0.0: - dependencies: - universalify: 0.1.2 - optionalDependencies: - graceful-fs: 4.2.11 - - jsonfile@6.2.1: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - - jsonpointer@5.0.1: {} - - katex@0.16.47: - dependencies: - commander: 8.3.0 - - katex@0.18.4: - dependencies: - commander: 8.3.0 - - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - - kleur@3.0.3: {} - - kleur@4.1.5: {} - - lazy-val@1.0.5: {} - - leven@3.1.0: {} - - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - - lightningcss-android-arm64@1.32.0: - optional: true - - lightningcss-android-arm64@1.33.0: - optional: true - - lightningcss-darwin-arm64@1.32.0: - optional: true - - lightningcss-darwin-arm64@1.33.0: - optional: true - - lightningcss-darwin-x64@1.32.0: - optional: true - - lightningcss-darwin-x64@1.33.0: - optional: true - - lightningcss-freebsd-x64@1.32.0: - optional: true - - lightningcss-freebsd-x64@1.33.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.32.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.33.0: - optional: true - - lightningcss-linux-arm64-gnu@1.32.0: - optional: true - - lightningcss-linux-arm64-gnu@1.33.0: - optional: true - - lightningcss-linux-arm64-musl@1.32.0: - optional: true - - lightningcss-linux-arm64-musl@1.33.0: - optional: true - - lightningcss-linux-x64-gnu@1.32.0: - optional: true - - lightningcss-linux-x64-gnu@1.33.0: - optional: true - - lightningcss-linux-x64-musl@1.32.0: - optional: true - - lightningcss-linux-x64-musl@1.33.0: - optional: true - - lightningcss-win32-arm64-msvc@1.32.0: - optional: true - - lightningcss-win32-arm64-msvc@1.33.0: - optional: true - - lightningcss-win32-x64-msvc@1.32.0: - optional: true - - lightningcss-win32-x64-msvc@1.33.0: - optional: true - - lightningcss@1.32.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 - - lightningcss@1.33.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.33.0 - lightningcss-darwin-arm64: 1.33.0 - lightningcss-darwin-x64: 1.33.0 - lightningcss-freebsd-x64: 1.33.0 - lightningcss-linux-arm-gnueabihf: 1.33.0 - lightningcss-linux-arm64-gnu: 1.33.0 - lightningcss-linux-arm64-musl: 1.33.0 - lightningcss-linux-x64-gnu: 1.33.0 - lightningcss-linux-x64-musl: 1.33.0 - lightningcss-win32-arm64-msvc: 1.33.0 - lightningcss-win32-x64-msvc: 1.33.0 - - lines-and-columns@1.2.4: {} - - livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22): - dependencies: - '@livekit/mutex': 1.1.1 - '@livekit/protocol': 1.50.4 - '@types/dom-mediacapture-record': 1.0.22 - events: 3.3.0 - jose: 6.2.8 - loglevel: 1.9.2 - sdp-transform: 2.15.0 - tslib: 2.8.1 - typed-emitter: 2.1.0 - webrtc-adapter: 9.0.6 - - locate-path@3.0.0: - dependencies: - p-locate: 3.0.0 - path-exists: 3.0.0 - - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - lodash.debounce@4.0.8: {} - - lodash@4.18.1: {} - - log-symbols@6.0.0: - dependencies: - chalk: 5.6.2 - is-unicode-supported: 1.3.0 - - loglevel@1.9.1: {} - - loglevel@1.9.2: {} - - longest-streak@3.1.0: {} - - lowercase-keys@2.0.0: {} - - lru-cache@11.5.2: {} - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 - - lucide-react@1.30.0(react@19.2.8): - dependencies: - react: 19.2.8 - - lucide-react@1.32.0(react@19.2.8): - dependencies: - react: 19.2.8 - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - markdown-table@3.0.4: {} - - matcher@3.0.0: - dependencies: - escape-string-regexp: 4.0.0 - optional: true - - math-intrinsics@1.1.0: {} - - mdast-util-find-and-replace@3.0.2: - dependencies: - '@types/mdast': 4.0.4 - escape-string-regexp: 5.0.0 - unist-util-is: 6.0.1 - unist-util-visit-parents: 6.0.2 - - mdast-util-from-markdown@2.0.3(supports-color@7.2.0): - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - mdast-util-to-string: 4.0.0 - micromark: 4.0.2(supports-color@7.2.0) - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-decode-string: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - unist-util-stringify-position: 4.0.0 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-autolink-literal@2.0.1: - dependencies: - '@types/mdast': 4.0.4 - ccount: 2.0.1 - devlop: 1.1.0 - mdast-util-find-and-replace: 3.0.2 - micromark-util-character: 2.1.1 - - mdast-util-gfm-footnote@2.1.0(supports-color@7.2.0): - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3(supports-color@7.2.0) - mdast-util-to-markdown: 2.1.2 - micromark-util-normalize-identifier: 2.0.1 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-strikethrough@2.0.0(supports-color@7.2.0): - dependencies: - '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.3(supports-color@7.2.0) - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-table@2.0.0(supports-color@7.2.0): - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - markdown-table: 3.0.4 - mdast-util-from-markdown: 2.0.3(supports-color@7.2.0) - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-task-list-item@2.0.0(supports-color@7.2.0): - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3(supports-color@7.2.0) - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm@3.1.0(supports-color@7.2.0): - dependencies: - mdast-util-from-markdown: 2.0.3(supports-color@7.2.0) - mdast-util-gfm-autolink-literal: 2.0.1 - mdast-util-gfm-footnote: 2.1.0(supports-color@7.2.0) - mdast-util-gfm-strikethrough: 2.0.0(supports-color@7.2.0) - mdast-util-gfm-table: 2.0.0(supports-color@7.2.0) - mdast-util-gfm-task-list-item: 2.0.0(supports-color@7.2.0) - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-math@3.0.0(supports-color@7.2.0): - dependencies: - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - devlop: 1.1.0 - longest-streak: 3.1.0 - mdast-util-from-markdown: 2.0.3(supports-color@7.2.0) - mdast-util-to-markdown: 2.1.2 - unist-util-remove-position: 5.0.0 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx-expression@2.0.1(supports-color@7.2.0): - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3(supports-color@7.2.0) - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx-jsx@3.2.0(supports-color@7.2.0): - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - ccount: 2.0.1 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3(supports-color@7.2.0) - mdast-util-to-markdown: 2.1.2 - parse-entities: 4.0.2 - stringify-entities: 4.0.4 - unist-util-stringify-position: 4.0.0 - vfile-message: 4.0.3 - transitivePeerDependencies: - - supports-color - - mdast-util-mdxjs-esm@2.0.1(supports-color@7.2.0): - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3(supports-color@7.2.0) - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-newline-to-break@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-find-and-replace: 3.0.2 - - mdast-util-phrasing@4.1.0: - dependencies: - '@types/mdast': 4.0.4 - unist-util-is: 6.0.1 - - mdast-util-to-hast@13.2.1: - dependencies: - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.3.3 - devlop: 1.1.0 - micromark-util-sanitize-uri: 2.0.1 - trim-lines: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit: 5.1.0 - vfile: 6.0.3 - - mdast-util-to-markdown@2.1.2: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - longest-streak: 3.1.0 - mdast-util-phrasing: 4.1.0 - mdast-util-to-string: 4.0.0 - micromark-util-classify-character: 2.0.1 - micromark-util-decode-string: 2.0.1 - unist-util-visit: 5.1.0 - zwitch: 2.0.4 - - mdast-util-to-string@4.0.0: - dependencies: - '@types/mdast': 4.0.4 - - media-typer@1.1.1: {} - - merge-descriptors@2.0.0: {} - - merge-stream@2.0.0: {} - - merge2@1.4.1: {} - - micromark-core-commonmark@2.0.3: - dependencies: - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - micromark-factory-destination: 2.0.1 - micromark-factory-label: 2.0.1 - micromark-factory-space: 2.0.1 - micromark-factory-title: 2.0.1 - micromark-factory-whitespace: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-html-tag-name: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-autolink-literal@2.1.0: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-footnote@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-strikethrough@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-table@2.1.1: - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-tagfilter@2.0.0: - dependencies: - micromark-util-types: 2.0.2 - - micromark-extension-gfm-task-list-item@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm@3.0.0: - dependencies: - micromark-extension-gfm-autolink-literal: 2.1.0 - micromark-extension-gfm-footnote: 2.1.0 - micromark-extension-gfm-strikethrough: 2.1.0 - micromark-extension-gfm-table: 2.1.1 - micromark-extension-gfm-tagfilter: 2.0.0 - micromark-extension-gfm-task-list-item: 2.1.0 - micromark-util-combine-extensions: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-math@3.1.0: - dependencies: - '@types/katex': 0.16.8 - devlop: 1.1.0 - katex: 0.16.47 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-destination@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-label@2.0.1: - dependencies: - devlop: 1.1.0 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-space@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-types: 2.0.2 - - micromark-factory-title@2.0.1: - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-whitespace@2.0.1: - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-character@2.1.1: - dependencies: - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-chunked@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-classify-character@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-combine-extensions@2.0.1: - dependencies: - micromark-util-chunked: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-decode-numeric-character-reference@2.0.2: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-decode-string@2.0.1: - dependencies: - decode-named-character-reference: 1.3.0 - micromark-util-character: 2.1.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-symbol: 2.0.1 - - micromark-util-encode@2.0.1: {} - - micromark-util-html-tag-name@2.0.1: {} - - micromark-util-normalize-identifier@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-resolve-all@2.0.1: - dependencies: - micromark-util-types: 2.0.2 - - micromark-util-sanitize-uri@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-encode: 2.0.1 - micromark-util-symbol: 2.0.1 - - micromark-util-subtokenize@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-symbol@2.0.1: {} - - micromark-util-types@2.0.2: {} - - micromark@4.0.2(supports-color@7.2.0): - dependencies: - '@types/debug': 4.1.13 - debug: 4.4.3(supports-color@7.2.0) - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-combine-extensions: 2.0.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-encode: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - transitivePeerDependencies: - - supports-color - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.2 - - mime-db@1.52.0: {} - - mime-db@1.54.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - - mime-types@3.0.2: - dependencies: - mime-db: 1.54.0 - - mime@2.6.0: {} - - mimic-fn@2.1.0: {} - - mimic-fn@3.1.0: {} - - mimic-function@5.0.1: {} - - mimic-response@1.0.1: {} - - mimic-response@3.1.0: {} - - minimatch@10.2.6: - dependencies: - brace-expansion: 5.0.9 - - minimatch@3.1.5: - dependencies: - brace-expansion: 1.1.18 - - minimatch@5.1.9: - dependencies: - brace-expansion: 2.1.4 - - minimatch@9.0.9: - dependencies: - brace-expansion: 2.1.4 - - minimist@1.2.8: {} - - minipass@7.1.3: {} - - minizlib@3.1.0: - dependencies: - minipass: 7.1.3 - - mkdirp@0.5.6: - dependencies: - minimist: 1.2.8 - - motion-dom@13.0.0: - dependencies: - motion-utils: 13.0.0 - - motion-utils@13.0.0: {} - - motion@13.0.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8): - dependencies: - framer-motion: 13.0.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - tslib: 2.8.1 - optionalDependencies: - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - ms@2.1.3: {} - - mtp@https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz: - dependencies: - yaml: 2.9.0 - - nanoid@3.3.18: {} - - natural-compare@1.4.0: {} - - negotiator@1.0.0: {} - - next-themes@0.4.6(react-dom@19.2.8(react@19.2.8))(react@19.2.8): - dependencies: - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - node-abi@4.33.0: - dependencies: - semver: 7.8.5 - - node-api-version@0.2.1: - dependencies: - semver: 7.8.5 - - node-gyp@12.4.0: - dependencies: - env-paths: 2.2.1 - exponential-backoff: 3.1.3 - graceful-fs: 4.2.11 - nopt: 9.0.0 - proc-log: 6.1.0 - semver: 7.8.5 - tar: 7.5.22 - tinyglobby: 0.2.17 - undici: 6.28.0 - which: 6.0.1 - - node-int64@0.4.0: {} - - node-releases@2.0.53: {} - - nopt@9.0.0: - dependencies: - abbrev: 4.0.0 - - normalize-url@6.1.0: {} - - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - - npm-run-path@6.0.0: - dependencies: - path-key: 4.0.0 - unicorn-magic: 0.3.0 - - object-assign@4.1.1: {} - - object-inspect@1.13.4: {} - - object-keys@1.1.1: {} - - object-treeify@1.1.33: {} - - object.assign@4.1.7: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.2 - has-symbols: 1.1.0 - object-keys: 1.1.1 - - obug@2.1.4: {} - - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - - onetime@7.0.0: - dependencies: - mimic-function: 5.0.1 - - oniguruma-parser@0.12.2: {} - - oniguruma-to-es@4.3.6: - dependencies: - oniguruma-parser: 0.12.2 - regex: 6.1.0 - regex-recursion: 6.0.2 - - open@11.0.1: - dependencies: - default-browser: 5.5.1 - define-lazy-prop: 3.0.0 - is-in-ssh: 1.0.0 - is-inside-container: 1.0.0 - powershell-utils: 0.2.0 - wsl-utils: 1.0.0 - - open@8.4.2: - dependencies: - define-lazy-prop: 2.0.0 - is-docker: 2.2.1 - is-wsl: 2.2.0 - - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - - ora@8.2.0: - dependencies: - chalk: 5.6.2 - cli-cursor: 5.0.0 - cli-spinners: 2.9.2 - is-interactive: 2.0.0 - is-unicode-supported: 2.1.0 - log-symbols: 6.0.0 - stdin-discarder: 0.2.2 - string-width: 7.2.0 - strip-ansi: 7.2.0 - - own-keys@1.0.2: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - object-keys: 1.1.1 - safe-push-apply: 1.0.0 - - p-cancelable@2.1.1: {} - - p-limit@2.3.0: - dependencies: - p-try: 2.2.0 - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@3.0.0: - dependencies: - p-limit: 2.3.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - - p-try@2.2.0: {} - - package-json-from-dist@1.0.1: {} - - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - - parse-entities@4.0.2: - dependencies: - '@types/unist': 2.0.11 - character-entities-legacy: 3.0.0 - character-reference-invalid: 2.0.1 - decode-named-character-reference: 1.3.0 - is-alphanumerical: 2.0.1 - is-decimal: 2.0.1 - is-hexadecimal: 2.0.1 - - parse-json@5.2.0: - dependencies: - '@babel/code-frame': 7.29.7 - error-ex: 1.3.4 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - - parse-ms@4.0.0: {} - - parse5@7.3.0: - dependencies: - entities: 6.0.1 - - parseurl@1.3.3: {} - - path-browserify@1.0.1: {} - - path-exists@3.0.0: {} - - path-exists@4.0.0: {} - - path-is-absolute@1.0.1: {} - - path-key@3.1.1: {} - - path-key@4.0.0: {} - - path-parse@1.0.7: {} - - path-scurry@2.0.2: - dependencies: - lru-cache: 11.5.2 - minipass: 7.1.3 - - path-to-regexp@8.4.2: {} - - pathe@2.0.3: {} - - pe-library@0.4.1: {} - - picocolors@1.1.1: {} - - picomatch@2.3.2: {} - - picomatch@4.0.5: {} - - pkce-challenge@5.0.1: {} - - pkg-up@3.1.0: - dependencies: - find-up: 3.0.0 - - pkijs@3.4.0: - dependencies: - '@noble/hashes': 1.4.0 - asn1js: 3.0.10 - bytestreamjs: 2.0.1 - pvtsutils: 1.3.6 - pvutils: 1.2.0 - tslib: 2.8.1 - - plist@3.1.0: - dependencies: - '@xmldom/xmldom': 0.8.13 - base64-js: 1.5.1 - xmlbuilder: 15.1.1 - - possible-typed-array-names@1.1.0: {} - - postcss-selector-parser@7.1.5: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss@8.5.26: - dependencies: - nanoid: 3.3.18 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - postject@1.0.0-alpha.6: - dependencies: - commander: 9.5.0 - optional: true - - powershell-utils@0.1.0: {} - - powershell-utils@0.2.0: {} - - prelude-ls@1.2.1: {} - - prettier@3.9.6: {} - - pretty-bytes@5.6.0: {} - - pretty-bytes@6.1.1: {} - - pretty-ms@9.3.0: - dependencies: - parse-ms: 4.0.0 - - proc-log@6.1.0: {} - - process-nextick-args@2.0.1: {} - - progress@2.0.3: {} - - promise-retry@2.0.1: - dependencies: - err-code: 2.0.3 - retry: 0.12.0 - - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - - proper-lockfile@4.1.2: - dependencies: - graceful-fs: 4.2.11 - retry: 0.12.0 - signal-exit: 3.0.7 - - property-information@7.2.0: {} - - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - - pump@3.0.4: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - - punycode@2.3.1: {} - - pvtsutils@1.3.6: - dependencies: - tslib: 2.8.1 - - pvutils@1.2.0: {} - - qs@6.15.3: - dependencies: - es-define-property: 1.0.1 - side-channel: 1.1.1 - - queue-microtask@1.2.3: {} - - quick-lru@5.1.1: {} - - range-parser@1.3.0: {} - - raw-body@3.0.2: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.1 - iconv-lite: 0.7.3 - unpipe: 1.0.0 - - react-dom@19.2.8(react@19.2.8): - dependencies: - react: 19.2.8 - scheduler: 0.27.0 - - react-is@19.2.8: {} - - react-markdown@10.1.0(@types/react@19.2.18)(react@19.2.8)(supports-color@7.2.0): - dependencies: - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - '@types/react': 19.2.18 - devlop: 1.1.0 - hast-util-to-jsx-runtime: 2.3.6(supports-color@7.2.0) - html-url-attributes: 3.0.1 - mdast-util-to-hast: 13.2.1 - react: 19.2.8 - remark-parse: 11.0.0(supports-color@7.2.0) - remark-rehype: 11.1.2 - unified: 11.0.5 - unist-util-visit: 5.1.0 - vfile: 6.0.3 - transitivePeerDependencies: - - supports-color - - react-redux@9.3.0(@types/react@19.2.18)(react@19.2.8)(redux@5.0.1): - dependencies: - '@types/use-sync-external-store': 0.0.6 - react: 19.2.8 - use-sync-external-store: 1.6.0(react@19.2.8) - optionalDependencies: - '@types/react': 19.2.18 - redux: 5.0.1 - - react-remove-scroll-bar@2.3.8(@types/react@19.2.18)(react@19.2.8): - dependencies: - react: 19.2.8 - react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.2.8) - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.2.18 - - react-remove-scroll@2.7.2(@types/react@19.2.18)(react@19.2.8): - dependencies: - react: 19.2.8 - react-remove-scroll-bar: 2.3.8(@types/react@19.2.18)(react@19.2.8) - react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.2.8) - tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.2.18)(react@19.2.8) - use-sidecar: 1.1.3(@types/react@19.2.18)(react@19.2.8) - optionalDependencies: - '@types/react': 19.2.18 - - react-resizable-panels@4.12.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8): - dependencies: - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - react-style-singleton@2.2.3(@types/react@19.2.18)(react@19.2.8): - dependencies: - get-nonce: 1.0.1 - react: 19.2.8 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.2.18 - - react@19.2.8: {} - - read-binary-file-arch@1.0.6(supports-color@7.2.0): - dependencies: - debug: 4.4.3(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - readable-stream@2.3.8: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - - recast@0.23.21: - dependencies: - ast-types: 0.16.1 - esprima: 4.0.1 - source-map: 0.6.1 - tiny-invariant: 1.3.3 - tslib: 2.8.1 - - recharts@3.10.1(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.8)(react@19.2.8)(redux@5.0.1): - dependencies: - '@reduxjs/toolkit': 2.12.0(react-redux@9.3.0(@types/react@19.2.18)(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - decimal.js-light: 2.5.1 - es-toolkit: 1.50.0 - eventemitter3: 5.0.4 - immer: 11.1.16 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - react-is: 19.2.8 - react-redux: 9.3.0(@types/react@19.2.18)(react@19.2.8)(redux@5.0.1) - reselect: 5.2.0 - tiny-invariant: 1.3.3 - use-sync-external-store: 1.6.0(react@19.2.8) - victory-vendor: 37.3.6 - transitivePeerDependencies: - - '@types/react' - - redux - - redux-thunk@3.1.0(redux@5.0.1): - dependencies: - redux: 5.0.1 - - redux@5.0.1: {} - - reflect.getprototypeof@1.0.10: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - which-builtin-type: 1.2.1 - - regenerate-unicode-properties@10.2.2: - dependencies: - regenerate: 1.4.2 - - regenerate@1.4.2: {} - - regex-recursion@6.0.2: - dependencies: - regex-utilities: 2.3.0 - - regex-utilities@2.3.0: {} - - regex@6.1.0: - dependencies: - regex-utilities: 2.3.0 - - regexp.prototype.flags@1.5.4: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-errors: 1.3.0 - get-proto: 1.0.1 - gopd: 1.2.0 - set-function-name: 2.0.2 - - regexpu-core@6.4.0: - dependencies: - regenerate: 1.4.2 - regenerate-unicode-properties: 10.2.2 - regjsgen: 0.8.0 - regjsparser: 0.13.2 - unicode-match-property-ecmascript: 2.0.0 - unicode-match-property-value-ecmascript: 2.2.1 - - regjsgen@0.8.0: {} - - regjsparser@0.13.2: - dependencies: - jsesc: 3.1.0 - - rehype-autolink-headings@7.1.0: - dependencies: - '@types/hast': 3.0.5 - '@ungap/structured-clone': 1.3.3 - hast-util-heading-rank: 3.0.0 - hast-util-is-element: 3.0.0 - unified: 11.0.5 - unist-util-visit: 5.1.0 - - rehype-katex@7.0.1: - dependencies: - '@types/hast': 3.0.5 - '@types/katex': 0.16.8 - hast-util-from-html-isomorphic: 2.0.0 - hast-util-to-text: 4.0.2 - katex: 0.16.47 - unist-util-visit-parents: 6.0.2 - vfile: 6.0.3 - - rehype-slug@6.0.0: - dependencies: - '@types/hast': 3.0.5 - github-slugger: 2.0.0 - hast-util-heading-rank: 3.0.0 - hast-util-to-string: 3.0.1 - unist-util-visit: 5.1.0 - - remark-breaks@4.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-newline-to-break: 2.0.0 - unified: 11.0.5 - - remark-gfm@4.0.1(supports-color@7.2.0): - dependencies: - '@types/mdast': 4.0.4 - mdast-util-gfm: 3.1.0(supports-color@7.2.0) - micromark-extension-gfm: 3.0.0 - remark-parse: 11.0.0(supports-color@7.2.0) - remark-stringify: 11.0.0 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-math@6.0.0(supports-color@7.2.0): - dependencies: - '@types/mdast': 4.0.4 - mdast-util-math: 3.0.0(supports-color@7.2.0) - micromark-extension-math: 3.1.0 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-parse@11.0.0(supports-color@7.2.0): - dependencies: - '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.3(supports-color@7.2.0) - micromark-util-types: 2.0.2 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-rehype@11.1.2: - dependencies: - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - mdast-util-to-hast: 13.2.1 - unified: 11.0.5 - vfile: 6.0.3 - - remark-stringify@11.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-to-markdown: 2.1.2 - unified: 11.0.5 - - require-directory@2.1.1: {} - - require-from-string@2.0.2: {} - - resedit@1.7.2: - dependencies: - pe-library: 0.4.1 - - reselect@5.2.0: {} - - resolve-alpn@1.2.1: {} - - resolve-from@4.0.0: {} - - resolve@1.22.12: - dependencies: - es-errors: 1.3.0 - is-core-module: 2.16.2 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - responselike@2.0.1: - dependencies: - lowercase-keys: 2.0.0 - - restore-cursor@5.1.0: - dependencies: - onetime: 7.0.0 - signal-exit: 4.1.0 - - retry@0.12.0: {} - - reusify@1.1.0: {} - - rimraf@2.6.3: - dependencies: - glob: 7.2.3 - - roarr@2.15.4: - dependencies: - boolean: 3.2.0 - detect-node: 2.1.0 - globalthis: 1.0.4 - json-stringify-safe: 5.0.1 - semver-compare: 1.0.0 - sprintf-js: 1.1.3 - optional: true - - rolldown@1.2.3: - dependencies: - '@oxc-project/types': 0.143.0 - '@rolldown/pluginutils': 1.0.1 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.2.3 - '@rolldown/binding-darwin-arm64': 1.2.3 - '@rolldown/binding-darwin-x64': 1.2.3 - '@rolldown/binding-freebsd-x64': 1.2.3 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.3 - '@rolldown/binding-linux-arm64-gnu': 1.2.3 - '@rolldown/binding-linux-arm64-musl': 1.2.3 - '@rolldown/binding-linux-ppc64-gnu': 1.2.3 - '@rolldown/binding-linux-s390x-gnu': 1.2.3 - '@rolldown/binding-linux-x64-gnu': 1.2.3 - '@rolldown/binding-linux-x64-musl': 1.2.3 - '@rolldown/binding-openharmony-arm64': 1.2.3 - '@rolldown/binding-win32-arm64-msvc': 1.2.3 - '@rolldown/binding-win32-x64-msvc': 1.2.3 - - rollup@4.62.4: - dependencies: - '@types/estree': 1.0.9 - optionalDependencies: - '@napi-rs/lzma-linux-x64-gnu': 1.5.1 - '@rollup/rollup-android-arm-eabi': 4.62.4 - '@rollup/rollup-android-arm64': 4.62.4 - '@rollup/rollup-darwin-arm64': 4.62.4 - '@rollup/rollup-darwin-x64': 4.62.4 - '@rollup/rollup-freebsd-arm64': 4.62.4 - '@rollup/rollup-freebsd-x64': 4.62.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.62.4 - '@rollup/rollup-linux-arm-musleabihf': 4.62.4 - '@rollup/rollup-linux-arm64-gnu': 4.62.4 - '@rollup/rollup-linux-arm64-musl': 4.62.4 - '@rollup/rollup-linux-loong64-gnu': 4.62.4 - '@rollup/rollup-linux-loong64-musl': 4.62.4 - '@rollup/rollup-linux-ppc64-gnu': 4.62.4 - '@rollup/rollup-linux-ppc64-musl': 4.62.4 - '@rollup/rollup-linux-riscv64-gnu': 4.62.4 - '@rollup/rollup-linux-riscv64-musl': 4.62.4 - '@rollup/rollup-linux-s390x-gnu': 4.62.4 - '@rollup/rollup-linux-x64-gnu': 4.62.4 - '@rollup/rollup-linux-x64-musl': 4.62.4 - '@rollup/rollup-openbsd-x64': 4.62.4 - '@rollup/rollup-openharmony-arm64': 4.62.4 - '@rollup/rollup-win32-arm64-msvc': 4.62.4 - '@rollup/rollup-win32-ia32-msvc': 4.62.4 - '@rollup/rollup-win32-x64-gnu': 4.62.4 - '@rollup/rollup-win32-x64-msvc': 4.62.4 - fsevents: 2.3.3 - - router@2.2.0(supports-color@7.2.0): - dependencies: - debug: 4.4.3(supports-color@7.2.0) - depd: 2.0.0 - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.4.2 - transitivePeerDependencies: - - supports-color - - run-applescript@7.1.0: {} - - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - - rxjs@7.8.2: - dependencies: - tslib: 2.8.1 - - safe-array-concat@1.1.4: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - has-symbols: 1.1.0 - isarray: 2.0.5 - - safe-buffer@5.1.2: {} - - safe-push-apply@1.0.0: - dependencies: - es-errors: 1.3.0 - isarray: 2.0.5 - - safe-regex-test@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-regex: 1.2.1 - - safer-buffer@2.1.2: {} - - sanitize-filename@1.6.4: - dependencies: - truncate-utf8-bytes: 1.0.2 - - sax@1.6.1: {} - - scheduler@0.27.0: {} - - sdp-transform@2.15.0: {} - - sdp@3.2.2: {} - - semver-compare@1.0.0: - optional: true - - semver@5.7.2: {} - - semver@6.3.1: {} - - semver@7.7.4: {} - - semver@7.8.5: {} - - send@1.2.1(supports-color@7.2.0): - dependencies: - debug: 4.4.3(supports-color@7.2.0) - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 2.0.0 - http-errors: 2.0.1 - mime-types: 3.0.2 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.3.0 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - serialize-error@7.0.1: - dependencies: - type-fest: 0.13.1 - optional: true - - serialize-javascript@7.1.0: {} - - seroval-plugins@1.6.2(seroval@1.6.2): - dependencies: - seroval: 1.6.2 - - seroval@1.6.2: {} - - serve-static@2.2.1(supports-color@7.2.0): - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 1.2.1(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - set-function-length@1.2.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - - set-function-name@2.0.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.2 - - set-proto@1.0.0: - dependencies: - dunder-proto: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - - setprototypeof@1.2.0: {} - - shadcn@4.18.0(supports-color@7.2.0)(typescript@6.0.3): - dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/parser': 7.29.8 - '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@dotenvx/dotenvx': 1.75.1 - '@modelcontextprotocol/sdk': 1.30.0(supports-color@7.2.0)(zod@3.25.76) - '@types/validate-npm-package-name': 4.0.2 - browserslist: 4.28.8 - commander: 14.0.3 - cosmiconfig: 9.0.2(typescript@6.0.3) - dedent: 1.7.2 - deepmerge: 4.3.1 - diff: 8.0.4 - execa: 9.6.1 - fast-glob: 3.3.3 - fs-extra: 11.4.0 - fuzzysort: 3.1.0 - kleur: 4.1.5 - open: 11.0.1 - ora: 8.2.0 - postcss: 8.5.26 - postcss-selector-parser: 7.1.5 - prompts: 2.4.2 - recast: 0.23.21 - socks: 2.8.9 - stringify-object: 5.0.0 - tailwind-merge: 3.6.0 - ts-morph: 26.0.0 - tsconfig-paths: 4.2.0 - undici: 7.29.0 - validate-npm-package-name: 7.0.2 - zod: 3.25.76 - zod-to-json-schema: 3.25.2(zod@3.25.76) - transitivePeerDependencies: - - '@cfworker/json-schema' - - babel-plugin-macros - - supports-color - - typescript - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - shiki@4.4.3: - dependencies: - '@shikijs/core': 4.4.3 - '@shikijs/engine-javascript': 4.4.3 - '@shikijs/engine-oniguruma': 4.4.3 - '@shikijs/langs': 4.4.3 - '@shikijs/themes': 4.4.3 - '@shikijs/types': 4.4.3 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.5 - - side-channel-list@1.0.1: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.1: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.1 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - - siginfo@2.0.0: {} - - signal-exit@3.0.7: {} - - signal-exit@4.1.0: {} - - simple-update-notifier@2.0.0: - dependencies: - semver: 7.8.5 - - sisteransi@1.0.5: {} - - smart-buffer@4.2.0: {} - - smob@1.6.2: {} - - socks@2.8.9: - dependencies: - ip-address: 10.5.0 - smart-buffer: 4.2.0 - - sonner@2.0.7(react-dom@19.2.8(react@19.2.8))(react@19.2.8): - dependencies: - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - sonner@2.0.8(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): - dependencies: - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - optionalDependencies: - '@types/react': 19.2.18 - - source-map-js@1.2.1: {} - - source-map-support@0.5.21: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - - source-map@0.6.1: {} - - source-map@0.8.0: {} - - space-separated-tokens@2.0.2: {} - - sprintf-js@1.1.3: - optional: true - - stackback@0.0.2: {} - - stat-mode@1.0.0: {} - - statuses@2.0.2: {} - - std-env@4.2.0: {} - - stdin-discarder@0.2.2: {} - - stop-iteration-iterator@1.1.0: - dependencies: - es-errors: 1.3.0 - internal-slot: 1.1.0 - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@7.2.0: - dependencies: - emoji-regex: 10.6.0 - get-east-asian-width: 1.6.0 - strip-ansi: 7.2.0 - - string.prototype.matchall@4.0.12: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-symbols: 1.1.0 - internal-slot: 1.1.0 - regexp.prototype.flags: 1.5.4 - set-function-name: 2.0.2 - side-channel: 1.1.1 - - string.prototype.trim@1.2.11: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-data-property: 1.1.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.2 - has-property-descriptors: 1.0.2 - safe-regex-test: 1.1.0 - - string.prototype.trimend@1.0.10: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.2 - - string.prototype.trimstart@1.0.8: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-object-atoms: 1.1.2 - - string_decoder@1.1.1: - dependencies: - safe-buffer: 5.1.2 - - stringify-entities@4.0.4: - dependencies: - character-entities-html4: 2.1.0 - character-entities-legacy: 3.0.0 - - stringify-object@3.3.0: - dependencies: - get-own-enumerable-property-symbols: 3.0.2 - is-obj: 1.0.1 - is-regexp: 1.0.0 - - stringify-object@5.0.0: - dependencies: - get-own-enumerable-keys: 1.0.0 - is-obj: 3.0.0 - is-regexp: 3.1.0 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.2.0: - dependencies: - ansi-regex: 6.3.0 - - strip-bom@3.0.0: {} - - strip-comments@2.0.1: {} - - strip-final-newline@2.0.0: {} - - strip-final-newline@4.0.0: {} - - style-mod@4.1.3: {} - - style-to-js@1.1.21: - dependencies: - style-to-object: 1.0.14 - - style-to-object@1.0.14: - dependencies: - inline-style-parser: 0.2.7 - - sumchecker@3.0.1(supports-color@7.2.0): - dependencies: - debug: 4.4.3(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-preserve-symlinks-flag@1.0.0: {} - - systeminformation@5.33.1: {} - - tailwind-merge@3.6.0: {} - - tailwind-scrollbar-hide@4.0.0(tailwindcss@4.3.3): - dependencies: - tailwindcss: 4.3.3 - - tailwindcss@4.3.3: {} - - tapable@2.3.3: {} - - tar@7.5.22: - dependencies: - '@isaacs/fs-minipass': 4.0.1 - chownr: 3.0.0 - minipass: 7.1.3 - minizlib: 3.1.0 - yallist: 5.0.0 - - temp-dir@2.0.0: {} - - temp-file@3.4.0: - dependencies: - async-exit-hook: 2.0.1 - fs-extra: 10.1.0 - - temp@0.9.4: - dependencies: - mkdirp: 0.5.6 - rimraf: 2.6.3 - - tempy@0.6.0: - dependencies: - is-stream: 2.0.1 - temp-dir: 2.0.0 - type-fest: 0.16.0 - unique-string: 2.0.0 - - terser@5.50.0: - dependencies: - '@jridgewell/source-map': 0.3.11 - acorn: 8.18.0 - commander: 2.20.3 - source-map-support: 0.5.21 - - tiny-async-pool@1.3.0: - dependencies: - semver: 5.7.2 - - tiny-invariant@1.3.3: {} - - tinybench@2.9.0: {} - - tinyexec@1.3.0: {} - - tinyglobby@0.2.17: - dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 - - tinyrainbow@3.1.1: {} - - tmp-promise@3.0.3: - dependencies: - tmp: 0.2.7 - - tmp@0.2.7: {} - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - toidentifier@1.0.1: {} - - trim-lines@3.0.1: {} - - trough@2.2.0: {} - - truncate-utf8-bytes@1.0.2: - dependencies: - utf8-byte-length: 1.0.5 - - ts-api-utils@2.5.0(typescript@6.0.3): - dependencies: - typescript: 6.0.3 - - ts-morph@26.0.0: - dependencies: - '@ts-morph/common': 0.27.0 - code-block-writer: 13.0.3 - - tsconfig-paths@4.2.0: - dependencies: - json5: 2.2.3 - minimist: 1.2.8 - strip-bom: 3.0.0 - - tslib@2.8.1: {} - - tw-animate-css@1.4.0: {} - - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 - - type-fest@0.13.1: - optional: true - - type-fest@0.16.0: {} - - type-is@2.1.0: - dependencies: - content-type: 2.1.0 - media-typer: 1.1.1 - mime-types: 3.0.2 - - typed-array-buffer@1.0.3: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-typed-array: 1.1.15 - - typed-array-byte-length@1.0.3: - dependencies: - call-bind: 1.0.9 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 - - typed-array-byte-offset@1.0.4: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.9 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 - reflect.getprototypeof: 1.0.10 - - typed-array-length@1.0.8: - dependencies: - call-bind: 1.0.9 - for-each: 0.3.5 - gopd: 1.2.0 - is-typed-array: 1.1.15 - possible-typed-array-names: 1.1.0 - reflect.getprototypeof: 1.0.10 - - typed-emitter@2.1.0: - optionalDependencies: - rxjs: 7.8.2 - - typescript-eslint@8.66.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): - dependencies: - '@typescript-eslint/eslint-plugin': 8.66.0(@typescript-eslint/parser@8.66.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/parser': 8.66.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.66.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.66.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - typescript-eslint@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): - dependencies: - '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/parser': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - typescript@6.0.3: {} - - typescript@7.0.2: - optionalDependencies: - '@typescript/typescript-aix-ppc64': 7.0.2 - '@typescript/typescript-darwin-arm64': 7.0.2 - '@typescript/typescript-darwin-x64': 7.0.2 - '@typescript/typescript-freebsd-arm64': 7.0.2 - '@typescript/typescript-freebsd-x64': 7.0.2 - '@typescript/typescript-linux-arm': 7.0.2 - '@typescript/typescript-linux-arm64': 7.0.2 - '@typescript/typescript-linux-loong64': 7.0.2 - '@typescript/typescript-linux-mips64el': 7.0.2 - '@typescript/typescript-linux-ppc64': 7.0.2 - '@typescript/typescript-linux-riscv64': 7.0.2 - '@typescript/typescript-linux-s390x': 7.0.2 - '@typescript/typescript-linux-x64': 7.0.2 - '@typescript/typescript-netbsd-arm64': 7.0.2 - '@typescript/typescript-netbsd-x64': 7.0.2 - '@typescript/typescript-openbsd-arm64': 7.0.2 - '@typescript/typescript-openbsd-x64': 7.0.2 - '@typescript/typescript-sunos-x64': 7.0.2 - '@typescript/typescript-win32-arm64': 7.0.2 - '@typescript/typescript-win32-x64': 7.0.2 - optional: true - - unbox-primitive@1.1.0: - dependencies: - call-bound: 1.0.4 - has-bigints: 1.1.0 - has-symbols: 1.1.0 - which-boxed-primitive: 1.1.1 - - undici-types@7.18.2: {} - - undici-types@8.3.0: {} - - undici@6.28.0: {} - - undici@7.29.0: {} - - unicode-canonical-property-names-ecmascript@2.0.1: {} - - unicode-match-property-ecmascript@2.0.0: - dependencies: - unicode-canonical-property-names-ecmascript: 2.0.1 - unicode-property-aliases-ecmascript: 2.2.0 - - unicode-match-property-value-ecmascript@2.2.1: {} - - unicode-property-aliases-ecmascript@2.2.0: {} - - unicorn-magic@0.3.0: {} - - unified@11.0.5: - dependencies: - '@types/unist': 3.0.3 - bail: 2.0.2 - devlop: 1.1.0 - extend: 3.0.2 - is-plain-obj: 4.1.0 - trough: 2.2.0 - vfile: 6.0.3 - - unique-string@2.0.0: - dependencies: - crypto-random-string: 2.0.0 - - unist-util-find-after@5.0.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - - unist-util-is@6.0.1: - dependencies: - '@types/unist': 3.0.3 - - unist-util-position@5.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-remove-position@5.0.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-visit: 5.1.0 - - unist-util-stringify-position@4.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-visit-parents@6.0.2: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - - unist-util-visit@5.1.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - unist-util-visit-parents: 6.0.2 - - universalify@0.1.2: {} - - universalify@2.0.1: {} - - unpipe@1.0.0: {} - - unzipper@0.12.5: - dependencies: - bluebird: 3.7.2 - duplexer2: 0.1.4 - fs-extra: 11.3.1 - graceful-fs: 4.2.11 - node-int64: 0.4.0 - - upath@1.2.0: {} - - update-browserslist-db@1.3.1(browserslist@4.28.8): - dependencies: - browserslist: 4.28.8 - escalade: 3.2.0 - picocolors: 1.1.1 - - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - - use-callback-ref@1.3.3(@types/react@19.2.18)(react@19.2.8): - dependencies: - react: 19.2.8 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.2.18 - - use-sidecar@1.1.3(@types/react@19.2.18)(react@19.2.8): - dependencies: - detect-node-es: 1.1.0 - react: 19.2.8 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.2.18 - - use-sync-external-store@1.6.0(react@19.2.8): - dependencies: - react: 19.2.8 - - usehooks-ts@3.1.1(react@19.2.8): - dependencies: - lodash.debounce: 4.0.8 - react: 19.2.8 - - utf8-byte-length@1.0.5: {} - - util-deprecate@1.0.2: {} - - validate-npm-package-name@7.0.2: {} - - vary@1.1.2: {} - - vaul@1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): - dependencies: - '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - transitivePeerDependencies: - - '@types/react' - - '@types/react-dom' - - vfile-location@5.0.3: - dependencies: - '@types/unist': 3.0.3 - vfile: 6.0.3 - - vfile-message@4.0.3: - dependencies: - '@types/unist': 3.0.3 - unist-util-stringify-position: 4.0.0 - - vfile@6.0.3: - dependencies: - '@types/unist': 3.0.3 - vfile-message: 4.0.3 - - victory-vendor@37.3.6: - dependencies: - '@types/d3-array': 3.2.2 - '@types/d3-ease': 3.0.2 - '@types/d3-interpolate': 3.0.4 - '@types/d3-scale': 4.0.9 - '@types/d3-shape': 3.1.8 - '@types/d3-time': 3.0.4 - '@types/d3-timer': 3.0.2 - d3-array: 3.2.4 - d3-ease: 3.0.1 - d3-interpolate: 3.0.1 - d3-scale: 4.0.2 - d3-shape: 3.2.0 - d3-time: 3.1.0 - d3-timer: 3.0.1 - - vite-plugin-pwa@1.3.0(supports-color@7.2.0)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0))(workbox-build@7.4.1(supports-color@7.2.0))(workbox-window@7.4.1): - dependencies: - debug: 4.4.3(supports-color@7.2.0) - pretty-bytes: 6.1.1 - tinyglobby: 0.2.17 - vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0) - workbox-build: 7.4.1(supports-color@7.2.0) - workbox-window: 7.4.1 - transitivePeerDependencies: - - supports-color - - vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0): - dependencies: - lightningcss: 1.33.0 - picomatch: 4.0.5 - postcss: 8.5.26 - rolldown: 1.2.3 - tinyglobby: 0.2.17 - optionalDependencies: - '@types/node': 26.2.0 - esbuild: 0.28.1 - fsevents: 2.3.3 - jiti: 2.7.0 - terser: 5.50.0 - yaml: 2.9.0 - - vitest@4.1.11(@types/node@26.2.0)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0)): - dependencies: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.11 - '@vitest/runner': 4.1.11 - '@vitest/snapshot': 4.1.11 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 - es-module-lexer: 2.3.2 - expect-type: 1.4.0 - magic-string: 0.30.21 - obug: 2.1.4 - pathe: 2.0.3 - picomatch: 4.0.5 - std-env: 4.2.0 - tinybench: 2.9.0 - tinyexec: 1.3.0 - tinyglobby: 0.2.17 - tinyrainbow: 3.1.1 - vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 26.2.0 - transitivePeerDependencies: - - msw - - w3c-keyname@2.2.8: {} - - web-namespaces@2.0.1: {} - - webcrypto-core@1.9.2: - dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/json-schema': 1.1.12 - '@peculiar/utils': 2.0.3 - asn1js: 3.0.10 - tslib: 2.8.1 - - webrtc-adapter@9.0.6: - dependencies: - sdp: 3.2.2 - - which-boxed-primitive@1.1.1: - dependencies: - is-bigint: 1.1.0 - is-boolean-object: 1.2.2 - is-number-object: 1.1.1 - is-string: 1.1.1 - is-symbol: 1.1.1 - - which-builtin-type@1.2.1: - dependencies: - call-bound: 1.0.4 - function.prototype.name: 1.2.0 - has-tostringtag: 1.0.2 - is-async-function: 2.1.1 - is-date-object: 1.1.0 - is-finalizationregistry: 1.1.1 - is-generator-function: 1.1.2 - is-regex: 1.2.1 - is-weakref: 1.1.1 - isarray: 2.0.5 - which-boxed-primitive: 1.1.1 - which-collection: 1.0.2 - which-typed-array: 1.1.22 - - which-collection@1.0.2: - dependencies: - is-map: 2.0.3 - is-set: 2.0.3 - is-weakmap: 2.0.2 - is-weakset: 2.0.4 - - which-typed-array@1.1.22: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.9 - call-bound: 1.0.4 - for-each: 0.3.5 - get-proto: 1.0.1 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - which@4.0.0: - dependencies: - isexe: 3.1.5 - - which@5.0.0: - dependencies: - isexe: 3.1.5 - - which@6.0.1: - dependencies: - isexe: 4.0.0 - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - word-wrap@1.2.5: {} - - workbox-background-sync@7.4.1: - dependencies: - idb: 7.1.1 - workbox-core: 7.4.1 - - workbox-broadcast-update@7.4.1: - dependencies: - workbox-core: 7.4.1 - - workbox-build@7.4.1(supports-color@7.2.0): - dependencies: - '@apideck/better-ajv-errors': 0.3.7(ajv@8.20.0) - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/preset-env': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - '@babel/runtime': 7.29.7 - '@rollup/plugin-babel': 6.1.0(@babel/core@7.29.7(supports-color@7.2.0))(rollup@4.62.4)(supports-color@7.2.0) - '@rollup/plugin-node-resolve': 16.0.3(rollup@4.62.4) - '@rollup/plugin-replace': 6.0.3(rollup@4.62.4) - '@rollup/plugin-terser': 1.0.0(rollup@4.62.4) - '@trickfilm400/rollup-plugin-off-main-thread': 3.0.0-pre1 - ajv: 8.20.0 - common-tags: 1.8.2 - eta: 4.6.0 - fast-json-stable-stringify: 2.1.0 - fs-extra: 9.1.0 - glob: 11.1.0 - pretty-bytes: 5.6.0 - rollup: 4.62.4 - source-map: 0.8.0 - stringify-object: 3.3.0 - strip-comments: 2.0.1 - tempy: 0.6.0 - upath: 1.2.0 - workbox-background-sync: 7.4.1 - workbox-broadcast-update: 7.4.1 - workbox-cacheable-response: 7.4.1 - workbox-core: 7.4.1 - workbox-expiration: 7.4.1 - workbox-google-analytics: 7.4.1 - workbox-navigation-preload: 7.4.1 - workbox-precaching: 7.4.1 - workbox-range-requests: 7.4.1 - workbox-recipes: 7.4.1 - workbox-routing: 7.4.1 - workbox-strategies: 7.4.1 - workbox-streams: 7.4.1 - workbox-sw: 7.4.1 - workbox-window: 7.4.1 - transitivePeerDependencies: - - '@types/babel__core' - - supports-color - - workbox-cacheable-response@7.4.1: - dependencies: - workbox-core: 7.4.1 - - workbox-core@7.4.1: {} - - workbox-expiration@7.4.1: - dependencies: - idb: 7.1.1 - workbox-core: 7.4.1 - - workbox-google-analytics@7.4.1: - dependencies: - workbox-background-sync: 7.4.1 - workbox-core: 7.4.1 - workbox-routing: 7.4.1 - workbox-strategies: 7.4.1 - - workbox-navigation-preload@7.4.1: - dependencies: - workbox-core: 7.4.1 - - workbox-precaching@7.4.1: - dependencies: - workbox-core: 7.4.1 - workbox-routing: 7.4.1 - workbox-strategies: 7.4.1 - - workbox-range-requests@7.4.1: - dependencies: - workbox-core: 7.4.1 - - workbox-recipes@7.4.1: - dependencies: - workbox-cacheable-response: 7.4.1 - workbox-core: 7.4.1 - workbox-expiration: 7.4.1 - workbox-precaching: 7.4.1 - workbox-routing: 7.4.1 - workbox-strategies: 7.4.1 - - workbox-routing@7.4.1: - dependencies: - workbox-core: 7.4.1 - - workbox-strategies@7.4.1: - dependencies: - workbox-core: 7.4.1 - - workbox-streams@7.4.1: - dependencies: - workbox-core: 7.4.1 - workbox-routing: 7.4.1 - - workbox-sw@7.4.1: {} - - workbox-window@7.4.1: - dependencies: - '@types/trusted-types': 2.0.7 - workbox-core: 7.4.1 - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrappy@1.0.2: {} - - wsl-utils@1.0.0: - dependencies: - is-wsl: 3.1.1 - powershell-utils: 0.1.0 - - xmlbuilder@15.1.1: {} - - y18n@5.0.8: {} - - yallist@3.1.1: {} - - yallist@4.0.0: {} - - yallist@5.0.0: {} - - yaml@2.9.0: {} - - yargs-parser@21.1.1: {} - - yargs@17.7.3: - dependencies: - cliui: 8.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - - yocto-queue@0.1.0: {} - - yocto-spinner@1.2.2: - dependencies: - yoctocolors: 2.2.0 - - yoctocolors@2.2.0: {} - - zod-to-json-schema@3.25.2(zod@3.25.76): - dependencies: - zod: 3.25.76 - - zod-validation-error@4.0.2(zod@4.4.3): - dependencies: - zod: 4.4.3 - - zod@3.25.76: {} - - zod@4.4.3: {} - - zustand@5.0.14(@types/react@19.2.18)(immer@11.1.16)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)): - optionalDependencies: - '@types/react': 19.2.18 - immer: 11.1.16 - react: 19.2.8 - use-sync-external-store: 1.6.0(react@19.2.8) - - zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml deleted file mode 100644 index 6ec3531..0000000 --- a/pnpm-workspace.yaml +++ /dev/null @@ -1,10 +0,0 @@ -packages: - - "packages/*" - - "apps/*" -allowBuilds: - electron: true - electron-winstaller: true - esbuild: true -overrides: - "@methanium/ui": "https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz" - mtp: "https://git.methanium.net/methanium/mtp/releases/download/0.3.0-dev-c7c7afe/mtp-0.3.0.tgz" diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 6336684..0000000 --- a/renovate.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "baseBranchPatterns": ["dev"] -} diff --git a/utils/scripts/build-packages.ts b/scripts/build-packages.ts similarity index 74% rename from utils/scripts/build-packages.ts rename to scripts/build-packages.ts index 1f1f4fd..0f4991a 100644 --- a/utils/scripts/build-packages.ts +++ b/scripts/build-packages.ts @@ -7,7 +7,7 @@ import { dirname } from "node:path"; const _dirname = dirname(fileURLToPath(import.meta.url)); -const packagesDir = join(_dirname, "..", "..", "packages"); +const packagesDir = join(_dirname, "..", "packages"); function getPackageDirs(dir: string): string[] { const entries = readdirSync(dir); @@ -15,14 +15,7 @@ function getPackageDirs(dir: string): string[] { for (const entry of entries) { const fullPath = join(dir, entry); - if (entry === "node_modules" || entry.startsWith(".")) continue; - let stats; - try { - stats = statSync(fullPath); - } catch { - continue; - } - if (!stats.isDirectory()) continue; + if (!statSync(fullPath).isDirectory()) continue; if (existsSync(join(fullPath, "package.json"))) { dirs.push(fullPath); @@ -41,7 +34,7 @@ for (const fullPath of packageDirs) { const entry = fullPath.replace(packagesDir + "/", ""); console.log(`Building ${entry}...`); try { - execSync("pnpm run build", { cwd: fullPath, stdio: "inherit" }); + execSync("bun run build", { cwd: fullPath, stdio: "inherit" }); console.log(`${entry} built successfully.`); } catch { console.error(`Failed to build ${entry}.`); diff --git a/utils/scripts/copy-licenses.ts b/scripts/copy-licenses.ts similarity index 80% rename from utils/scripts/copy-licenses.ts rename to scripts/copy-licenses.ts index 4a0f100..5361e82 100644 --- a/utils/scripts/copy-licenses.ts +++ b/scripts/copy-licenses.ts @@ -1,9 +1,10 @@ import { promises as fs } from "node:fs"; import path from "node:path"; +import { parse } from "jsonc-parser"; -type PnpmLock = { +type BunLock = { lockfileVersion?: number; - importers?: Record; + workspaces?: Record; packages?: Record; }; @@ -35,7 +36,7 @@ type ResolvedPackage = { }; const ROOT = process.cwd(); -const LOCKFILE_PATH = path.join(ROOT, "pnpm-lock.yaml"); +const LOCKFILE_PATH = path.join(ROOT, "bun.lock"); const OUTPUT_DIR = path.join(ROOT, "licenses"); const COMPLIANCE_FILE_PATTERNS = [ @@ -47,15 +48,15 @@ const COMPLIANCE_FILE_PATTERNS = [ async function main(): Promise { await ensureExists( LOCKFILE_PATH, - "Could not find pnpm-lock.yaml in the project root.", + "Could not find bun.lock in the project root.", ); - const lock = await readPnpmLock(); + const lock = await readBunLock(); const nodeModulesRoots = await getNodeModulesRoots(lock); if (nodeModulesRoots.length === 0) { throw new Error( - "Could not find any node_modules directories in the repo root or workspace folders. Run `pnpm install` first.", + "Could not find any node_modules directories in the repo root or workspace folders. Run `bun install` first.", ); } @@ -94,104 +95,25 @@ async function main(): Promise { ); } -async function readPnpmLock(): Promise { +async function readBunLock(): Promise { const raw = await fs.readFile(LOCKFILE_PATH, "utf8"); + const parsed = parse(raw); - return { - importers: extractTopLevelMappingKeys(raw, "importers"), - packages: extractTopLevelMappingKeys(raw, "packages"), - }; -} - -function extractTopLevelMappingKeys( - yaml: string, - sectionName: string, -): Record { - const result: Record = {}; - const lines = yaml.split(/\r?\n/); - - let inSection = false; - - for (const line of lines) { - if (!line.trim() || line.trimStart().startsWith("#")) continue; - - const indent = line.length - line.trimStart().length; - - if (indent === 0) { - inSection = line === `${sectionName}:`; - continue; - } - - if (!inSection || indent !== 2) continue; - - const trimmed = line.trim(); - const colonIndex = findYamlKeyColon(trimmed); - if (colonIndex < 0) continue; - - const rawKey = trimmed.slice(0, colonIndex).trim(); - if (!rawKey) continue; - - result[decodeYamlKey(rawKey)] = {}; + if (!parsed || typeof parsed !== "object") { + throw new Error("Failed to parse bun.lock"); } - return result; + return parsed as BunLock; } -function findYamlKeyColon(value: string): number { - let quote: "'" | '"' | null = null; - - for (let i = 0; i < value.length; i++) { - const ch = value[i]; - - if (quote === '"') { - if (ch === "\\") { - i++; - } else if (ch === '"') { - quote = null; - } - continue; - } - - if (quote === "'") { - if (ch === "'" && value[i + 1] === "'") { - i++; - } else if (ch === "'") { - quote = null; - } - continue; - } - - if (ch === '"' || ch === "'") { - quote = ch; - continue; - } - - if (ch === ":") return i; - } - - return -1; -} - -function decodeYamlKey(value: string): string { - if (value.startsWith('"') && value.endsWith('"')) { - return JSON.parse(value) as string; - } - - if (value.startsWith("'") && value.endsWith("'")) { - return value.slice(1, -1).replace(/''/g, "'"); - } - - return value; -} - -async function getNodeModulesRoots(lock: PnpmLock): Promise { +async function getNodeModulesRoots(lock: BunLock): Promise { const candidates = new Set(); candidates.add(path.join(ROOT, "node_modules")); - for (const workspacePath of Object.keys(lock.importers ?? {})) { + for (const workspacePath of Object.keys(lock.workspaces ?? {})) { const workspaceDir = - workspacePath === "." ? ROOT : path.join(ROOT, workspacePath); + workspacePath === "" ? ROOT : path.join(ROOT, workspacePath); candidates.add(path.join(workspaceDir, "node_modules")); } @@ -208,15 +130,25 @@ async function getNodeModulesRoots(lock: PnpmLock): Promise { return existing; } -function extractResolvedThirdPartyPackages(lock: PnpmLock): ResolvedPackage[] { +function extractResolvedThirdPartyPackages(lock: BunLock): ResolvedPackage[] { const results = new Map(); const packages = lock.packages ?? {}; - for (const rawKey of Object.keys(packages)) { - const resolved = parsePnpmPackageKey(rawKey); - if (!resolved) continue; + for (const rawValue of Object.values(packages)) { + if (!Array.isArray(rawValue) || rawValue.length === 0) continue; - const { name, version } = resolved; + const first = rawValue[0]; + if (typeof first !== "string") continue; + + // Examples: + // react@19.2.0 + // @types/react@19.2.2 + // @tensamin/ui@workspace:packages/ui + const atIndex = first.lastIndexOf("@"); + if (atIndex <= 0) continue; + + const name = first.slice(0, atIndex); + const version = first.slice(atIndex + 1); if (!name || !version) continue; if (version.startsWith("workspace:")) continue; @@ -230,21 +162,6 @@ function extractResolvedThirdPartyPackages(lock: PnpmLock): ResolvedPackage[] { return [...results.values()]; } -function parsePnpmPackageKey(rawKey: string): ResolvedPackage | null { - const withoutPeerSuffix = rawKey.replace(/\(.+\)$/, ""); - const normalized = withoutPeerSuffix.startsWith("/") - ? withoutPeerSuffix.slice(1) - : withoutPeerSuffix; - const atIndex = normalized.lastIndexOf("@"); - - if (atIndex <= 0) return null; - - return { - name: normalized.slice(0, atIndex), - version: normalized.slice(atIndex + 1), - }; -} - async function processInstalledPackage( packageName: string, versionFromLock: string, @@ -437,7 +354,7 @@ async function writeThirdPartyNotices(records: PackageRecord[]): Promise { lines.push("# Third-Party Notices"); lines.push(""); lines.push( - "Generated from pnpm-lock.yaml and installed packages in workspace node_modules folders.", + "Generated from bun.lock and installed packages in workspace node_modules folders.", ); lines.push(""); @@ -525,7 +442,7 @@ async function writeCycloneDxSbom(records: PackageRecord[]): Promise { tools: [ { vendor: "OpenAI", - name: "custom pnpm license generator", + name: "custom bun license generator", }, ], component: { diff --git a/scripts/copy-releases.ts b/scripts/copy-releases.ts new file mode 100644 index 0000000..e840169 --- /dev/null +++ b/scripts/copy-releases.ts @@ -0,0 +1,80 @@ +import { + existsSync, + mkdirSync, + readdirSync, + renameSync, + rmSync, + statSync, +} from "node:fs"; +import { join } from "node:path"; +import packageJson from "../package.json" with { type: "json" }; + +const { version } = packageJson; +const releasesDir = join("releases"); + +// directory +rmSync(releasesDir, { recursive: true, force: true }); +mkdirSync(releasesDir, { recursive: true }); + +// apk +const apkSrc = + "apps/tauri/src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk"; +if (existsSync(apkSrc)) { + renameSync(apkSrc, join(releasesDir, `Tensamin-${version}.apk`)); +} else { + console.warn(`Skipping .apk: ${apkSrc} does not exist.`); +} + +// deb & rpm +const debDir = "apps/tauri/src-tauri/target/release/bundle/deb"; +const rpmDir = "apps/tauri/src-tauri/target/release/bundle/rpm"; + +const moveBundleArtifact = ( + sourceDir: string, + extension: ".deb" | ".rpm", + destinationFileName: string, +) => { + if (!existsSync(sourceDir)) { + console.warn(`Skipping ${extension}: ${sourceDir} does not exist.`); + return; + } + + const candidates = readdirSync(sourceDir) + .filter((file) => file.endsWith(extension)) + .map((file) => { + const filePath = join(sourceDir, file); + + return { + file, + filePath, + matchesVersion: file.includes(version), + modifiedAt: statSync(filePath).mtimeMs, + }; + }) + .sort((a, b) => { + if (a.matchesVersion !== b.matchesVersion) { + return Number(b.matchesVersion) - Number(a.matchesVersion); + } + + return b.modifiedAt - a.modifiedAt; + }); + + const selected = candidates[0]; + if (!selected) { + console.warn(`Skipping ${extension}: no files found in ${sourceDir}.`); + return; + } + + if (!selected.matchesVersion) { + console.warn( + `Using ${selected.file} for ${extension} even though it does not include version ${version}.`, + ); + } + + renameSync(selected.filePath, join(releasesDir, destinationFileName)); +}; + +moveBundleArtifact(debDir, ".deb", `Tensamin-${version}.deb`); +moveBundleArtifact(rpmDir, ".rpm", `Tensamin-${version}.rpm`); + +console.log("Releases copied successfully."); diff --git a/utils/scripts/lint-packages.ts b/scripts/lint-packages.ts similarity index 77% rename from utils/scripts/lint-packages.ts rename to scripts/lint-packages.ts index c84e0e0..b9abdf2 100644 --- a/utils/scripts/lint-packages.ts +++ b/scripts/lint-packages.ts @@ -7,7 +7,7 @@ import { dirname } from "node:path"; const _dirname = dirname(fileURLToPath(import.meta.url)); -const rootDir = join(_dirname, "..", ".."); +const rootDir = join(_dirname, ".."); const targetDirs = [join(rootDir, "packages"), join(rootDir, "apps")]; function getPackageDirs(dir: string): string[] { @@ -16,14 +16,7 @@ function getPackageDirs(dir: string): string[] { for (const entry of entries) { const fullPath = join(dir, entry); - if (entry === "node_modules" || entry.startsWith(".")) continue; - let stats; - try { - stats = statSync(fullPath); - } catch { - continue; - } - if (!stats.isDirectory()) continue; + if (!statSync(fullPath).isDirectory()) continue; if (existsSync(join(fullPath, "package.json"))) { dirs.push(fullPath); @@ -43,7 +36,7 @@ for (const targetDir of targetDirs) { const entry = relative(rootDir, fullPath); console.log(`Linting ${entry}...`); try { - execSync("pnpm run lint --fix", { cwd: fullPath, stdio: "inherit" }); + execSync("bun run lint", { cwd: fullPath, stdio: "inherit" }); console.log(`${entry} linted successfully.`); } catch { console.error(`Failed to lint ${entry}.`); diff --git a/utils/scripts/update-packages.ts b/scripts/update-packages.ts similarity index 74% rename from utils/scripts/update-packages.ts rename to scripts/update-packages.ts index 90b3b0c..403943a 100644 --- a/utils/scripts/update-packages.ts +++ b/scripts/update-packages.ts @@ -7,7 +7,7 @@ import { dirname } from "node:path"; const _dirname = dirname(fileURLToPath(import.meta.url)); -const packagesDir = join(_dirname, "..", "..", "packages"); +const packagesDir = join(_dirname, "..", "packages"); function getPackageDirs(dir: string): string[] { const entries = readdirSync(dir); @@ -15,14 +15,7 @@ function getPackageDirs(dir: string): string[] { for (const entry of entries) { const fullPath = join(dir, entry); - if (entry === "node_modules" || entry.startsWith(".")) continue; - let stats; - try { - stats = statSync(fullPath); - } catch { - continue; - } - if (!stats.isDirectory()) continue; + if (!statSync(fullPath).isDirectory()) continue; if (existsSync(join(fullPath, "package.json"))) { dirs.push(fullPath); @@ -41,7 +34,7 @@ for (const fullPath of packageDirs) { const entry = fullPath.replace(packagesDir + "/", ""); console.log(`Updating ${entry}...`); try { - execSync("pnpm update --interactive", { cwd: fullPath, stdio: "inherit" }); + execSync("bun update --interactive", { cwd: fullPath, stdio: "inherit" }); console.log(`${entry} updated successfully.`); } catch { console.error(`Failed to update ${entry}.`); diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..837b847 --- /dev/null +++ b/todo.md @@ -0,0 +1 @@ +- Move legal to extra onboarding package diff --git a/tsconfig.json b/tsconfig.json index 4a67b79..0096dc3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "target": "ES2020", "module": "nodenext", "lib": ["ES2020"], - "rootDir": ".", + "rootDir": "./scripts", "strict": true, "esModuleInterop": true, "skipLibCheck": true, @@ -12,6 +12,6 @@ "moduleResolution": "nodenext", "types": ["node"] }, - "include": ["utils", "eslint.config.ts"], + "include": ["scripts"], "exclude": ["node_modules", "dist"] } diff --git a/utils/eslint-rules/index.ts b/utils/eslint-rules/index.ts deleted file mode 100644 index ea4eea7..0000000 --- a/utils/eslint-rules/index.ts +++ /dev/null @@ -1,282 +0,0 @@ -import type { Rule } from "eslint"; - -export const noReactNamespaceImport: Rule.RuleModule = { - meta: { - type: "suggestion", - docs: { - description: "Require named imports from React", - }, - messages: { - namespaceImport: - "Import only the React exports used by this module instead of using a namespace import.", - }, - schema: [], - }, - create(context) { - return { - ImportDeclaration(node) { - if ( - node.source.value === "react" && - node.specifiers.some( - (specifier) => specifier.type === "ImportNamespaceSpecifier", - ) - ) { - context.report({ node, messageId: "namespaceImport" }); - } - }, - }; - }, -}; - -function isPropertyNamed( - member: { - computed: boolean; - property: { type: string; name?: string; value?: unknown }; - }, - name: string, -): boolean { - return member.computed - ? member.property.type === "Literal" && member.property.value === name - : member.property.type === "Identifier" && member.property.name === name; -} - -export const noWindowLocationReload: Rule.RuleModule = { - meta: { - type: "problem", - docs: { - description: "Disallow reloads that bypass TanStack Router", - }, - messages: { - reload: - "Do not use window.location.reload(); use TanStack Router navigation instead.", - }, - schema: [], - }, - create(context) { - return { - CallExpression(node) { - const callee = node.callee; - if ( - callee.type !== "MemberExpression" || - !isPropertyNamed(callee, "reload") || - callee.object.type !== "MemberExpression" || - !isPropertyNamed(callee.object, "location") || - callee.object.object.type !== "Identifier" || - callee.object.object.name !== "window" - ) { - return; - } - - context.report({ node, messageId: "reload" }); - }, - }; - }, -}; - -interface AstNode { - type: string; - parent: AstNode | null; - range: [number, number]; -} - -interface TypeAliasDeclaration extends AstNode { - type: "TSTypeAliasDeclaration"; - typeAnnotation: AstNode; - typeParameters?: unknown; -} - -interface FunctionDeclaration extends AstNode { - type: "FunctionDeclaration"; - async: boolean; - generator: boolean; - params: AstNode[]; - body: AstNode & { body: AstNode[] }; -} - -interface ReturnStatement extends AstNode { - type: "ReturnStatement"; - argument: AstNode | null; -} - -function isExported(node: AstNode): boolean { - return ( - node.parent?.type === "ExportNamedDeclaration" || - node.parent?.type === "ExportDefaultDeclaration" - ); -} - -function containsContextSensitiveNode(value: unknown): boolean { - if (!value || typeof value !== "object") return false; - - const node = value as { type?: string; [key: string]: unknown }; - if ( - node.type === "ThisExpression" || - node.type === "Super" || - node.type === "MetaProperty" - ) { - return true; - } - - return Object.entries(node).some( - ([key, child]) => - key !== "parent" && - key !== "loc" && - key !== "range" && - containsContextSensitiveNode(child), - ); -} - -const unambiguousInlineTypes = new Set([ - "TSAnyKeyword", - "TSBigIntKeyword", - "TSBooleanKeyword", - "TSIntrinsicKeyword", - "TSLiteralType", - "TSNeverKeyword", - "TSNullKeyword", - "TSNumberKeyword", - "TSObjectKeyword", - "TSStringKeyword", - "TSSymbolKeyword", - "TSThisType", - "TSTupleType", - "TSTypeLiteral", - "TSTypeReference", - "TSUndefinedKeyword", - "TSUnknownKeyword", - "TSVoidKeyword", -]); - -export const inlineSingleUseDeclarations: Rule.RuleModule = { - meta: { - type: "suggestion", - docs: { - description: "Inline local types and functions that are used only once", - }, - fixable: "code", - messages: { - function: "Inline this function at its only call site.", - type: "Inline this type at its only use site.", - }, - schema: [], - }, - create(context) { - const sourceCode = context.sourceCode; - - return { - TSTypeAliasDeclaration(untypedNode: Rule.Node) { - const node = untypedNode as unknown as TypeAliasDeclaration; - if (node.typeParameters || isExported(node)) return; - - const eslintNode = node as unknown as Rule.Node; - const [variable] = sourceCode.getDeclaredVariables(eslintNode); - if (!variable || variable.references.length !== 1) return; - - const reference = variable.references[0] - .identifier as unknown as AstNode; - if ( - reference.range[0] >= node.range[0] && - reference.range[1] <= node.range[1] - ) { - return; - } - - const referenceParent = reference.parent; - if ( - !referenceParent || - referenceParent.type !== "TSTypeReference" || - referenceParent.parent?.type === "TSClassImplements" || - referenceParent.parent?.type === "TSInterfaceHeritage" - ) { - return; - } - - context.report({ - node: eslintNode, - messageId: "type", - fix(fixer) { - const annotation = sourceCode.getText( - node.typeAnnotation as unknown as Rule.Node, - ); - const replacement = unambiguousInlineTypes.has( - node.typeAnnotation.type, - ) - ? annotation - : `(${annotation})`; - - return [ - fixer.replaceText( - referenceParent as unknown as Rule.Node, - replacement, - ), - fixer.remove(eslintNode), - ]; - }, - }); - }, - - FunctionDeclaration(untypedNode: Rule.Node) { - const node = untypedNode as unknown as FunctionDeclaration; - if ( - node.async || - node.generator || - node.params.length !== 0 || - node.body.body.length !== 1 || - isExported(node) - ) { - return; - } - - const statement = node.body.body[0] as ReturnStatement; - if (statement.type !== "ReturnStatement" || !statement.argument) return; - - const eslintNode = node as unknown as Rule.Node; - const functionScope = sourceCode.getScope(eslintNode); - if ( - functionScope.references.length !== 0 || - functionScope.through.length !== 0 || - containsContextSensitiveNode(statement.argument) - ) { - return; - } - - const [variable] = sourceCode.getDeclaredVariables(eslintNode); - if (!variable || variable.references.length !== 1) return; - - const reference = variable.references[0] - .identifier as unknown as AstNode; - const call = reference.parent; - if ( - !call || - call.type !== "CallExpression" || - (call as AstNode & { callee: AstNode }).callee !== reference || - (call as AstNode & { arguments: AstNode[] }).arguments.length !== 0 || - (call as AstNode & { optional?: boolean }).optional || - (reference.range[0] >= node.range[0] && - reference.range[1] <= node.range[1]) - ) { - return; - } - - context.report({ - node: eslintNode, - messageId: "function", - fix(fixer) { - const expression = sourceCode.getText( - statement.argument! as unknown as Rule.Node, - ); - const replacement = - statement.argument!.type === "Literal" - ? expression - : `(${expression})`; - - return [ - fixer.replaceText(call as unknown as Rule.Node, replacement), - fixer.remove(eslintNode), - ]; - }, - }); - }, - }; - }, -}; diff --git a/utils/scripts/copy-releases.ts b/utils/scripts/copy-releases.ts deleted file mode 100644 index f7c0d20..0000000 --- a/utils/scripts/copy-releases.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { - copyFileSync, - createReadStream, - existsSync, - writeFileSync, - mkdirSync, - readdirSync, - renameSync, - rmSync, - statSync, -} from "node:fs"; -import { createHash } from "node:crypto"; -import { basename, join } from "node:path"; -import packageJson from "../../package.json" with { type: "json" }; - -const { version } = packageJson; -const releaseVersion = process.env.TENSAMIN_RELEASE_VERSION || version; -const releaseTag = process.env.TENSAMIN_RELEASE_TAG || releaseVersion; -const releasesDir = join("releases"); -const releaseAssetBaseUrl = process.env.FORGEJO_RELEASE_ASSET_BASE_URL; - -function sha256(filePath: string) { - const hash = createHash("sha256"); - const stream = createReadStream(filePath); - - return new Promise((resolve, reject) => { - stream.on("data", (chunk) => hash.update(chunk)); - stream.on("error", reject); - stream.on("end", () => resolve(hash.digest("hex"))); - }); -} - -function platformFor(file: string) { - if (/\.apk$/i.test(file)) return "android"; - if (/win|nsis|portable|\.exe$/i.test(file)) return "windows"; - if (/mac|darwin|\.dmg$/i.test(file)) return "macos"; - return "linux"; -} - -function archFor(file: string) { - if (/arm64|aarch64/i.test(file)) return "arm64"; - if (/\.apk$/i.test(file)) return "universal"; - return "x64"; -} - -// directory -rmSync(releasesDir, { recursive: true, force: true }); -mkdirSync(releasesDir, { recursive: true }); - -// apk -const apkSrc = - "apps/tauri/src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk"; -if (existsSync(apkSrc)) { - renameSync(apkSrc, join(releasesDir, `Tensamin-${version}.apk`)); -} else { - console.warn(`Skipping .apk: ${apkSrc} does not exist.`); -} - -// Electron desktop artifacts -const electronReleaseDir = "apps/electron/release"; -const electronArtifactPattern = /\.(AppImage|deb|rpm|exe|dmg|zip)$/i; - -function readFilesRecursive(dir: string): string[] { - return readdirSync(dir).flatMap((file) => { - const filePath = join(dir, file); - const stat = statSync(filePath); - - if (stat.isDirectory()) return readFilesRecursive(filePath); - if (stat.isFile()) return [filePath]; - return []; - }); -} - -const copyElectronArtifacts = () => { - if (!existsSync(electronReleaseDir)) { - console.warn( - `Skipping Electron artifacts: ${electronReleaseDir} does not exist.`, - ); - return; - } - - for (const source of readFilesRecursive(electronReleaseDir)) { - const file = basename(source); - if (!electronArtifactPattern.test(file)) continue; - - copyFileSync(source, join(releasesDir, file)); - } -}; - -copyElectronArtifacts(); - -const artifacts = await Promise.all( - readdirSync(releasesDir) - .map((file) => join(releasesDir, file)) - .filter((file) => statSync(file).isFile()) - .filter( - (file) => - !file.endsWith("electron-release-metadata.json") && - !file.endsWith("SHA256SUMS"), - ) - .map(async (filePath) => { - const name = filePath.split(/[\\/]/).at(-1)!; - - return { - name, - platform: platformFor(name), - arch: archFor(name), - url: releaseAssetBaseUrl - ? `${releaseAssetBaseUrl.replace(/\/$/, "")}/${encodeURIComponent(name)}` - : `__FORGEJO_RELEASE_ASSET_URL__/${encodeURIComponent(name)}`, - sha256: await sha256(filePath), - size: statSync(filePath).size, - }; - }), -); - -writeFileSync( - join(releasesDir, "electron-release-metadata.json"), - `${JSON.stringify({ version: releaseVersion, tag: releaseTag, publishedAt: new Date().toISOString(), artifacts: artifacts.filter((artifact) => artifact.platform !== "android") }, null, 2)}\n`, - "utf8", -); -writeFileSync( - join(releasesDir, "SHA256SUMS"), - `${artifacts.map((artifact) => `${artifact.sha256} ${artifact.name}`).join("\n")}\n`, - "utf8", -); - -console.log("Releases copied successfully."); diff --git a/vitest.config.ts b/vitest.config.ts deleted file mode 100644 index 30e49b8..0000000 --- a/vitest.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - exclude: ["**/node_modules/**", "**/.direnv/**", "**/dist/**"], - }, -});