diff --git a/.github/workflows/build-macos.yaml b/.github/workflows/build-macos.yaml
new file mode 100644
index 00000000..41a65ba0
--- /dev/null
+++ b/.github/workflows/build-macos.yaml
@@ -0,0 +1,90 @@
+name: Build macOS app
+on:
+ push:
+ branches:
+ - main
+ - dev
+ - "release/**"
+ paths-ignore:
+ - "*.md"
+ - "LICENSE"
+ tags:
+ - v*.*.*
+
+jobs:
+ build-macos:
+ runs-on:
+ - self-hosted
+ - macOS
+ env:
+ APPLE_SIGNING_IDENTITY: "Apple Distribution: defguard sp. z o.o. (82GZ7KN29J)"
+ APPLE_SIGNING_IDENTITY_INSTALLER: "3rd Party Mac Developer Installer: defguard sp. z o.o. (82GZ7KN29J)"
+ APPLE_PROVIDER_SHORT_NAME: "82GZ7KN29J"
+ APPLE_ID: "kamil@defguard.net"
+ APPLE_TEAM_ID: "82GZ7KN29J"
+ steps:
+ - uses: actions/checkout@v5
+ with:
+ submodules: recursive
+
+ - name: Write release version
+ run: |
+ VERSION=$(echo ${GITHUB_REF_NAME#v} | cut -d '-' -f1)
+ echo Version: $VERSION
+ echo "VERSION=$VERSION" >> ${GITHUB_ENV}
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version: "24"
+ cache: "pnpm"
+
+ - uses: pnpm/action-setup@v4
+ with:
+ version: 10
+ run_install: false
+
+ - name: Get pnpm store directory
+ shell: bash
+ run: echo "STORE_PATH=$(pnpm store path --silent)" >> ${GITHUB_ENV}
+
+ - name: Install deps
+ run: pnpm install --frozen-lockfile
+
+ - uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: aarch64-apple-darwin,x86_64-apple-darwin
+
+ - name: Unlock keychain
+ run: security -v unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" login.keychain
+
+ - name: Set build number
+ run: |
+ sed -i '' "s,@BUILD_NUMBER@,${{ github.run_number }}," src-tauri/tauri.conf.json
+ sed -i '' "s,@BUILD_NUMBER@,${{ github.run_number }}," swift/extension/VPNExtension.xcodeproj/project.pbxproj
+
+ - name: Build app
+ # Switch back to tauri-action when this gets merged https://github.com/tauri-apps/tauri/pull/14379
+ # uses: tauri-apps/tauri-action@v0.5.23 # .24 seems broken, TODO: update when fixed
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ # APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
+ # APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
+ run: cd src-tauri && cargo tauri build --bundles app --target universal-apple-darwin
+
+ - name: Build installation package
+ run: |
+ security -v unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" login.keychain
+ xcrun productbuild --sign "${{ env.APPLE_SIGNING_IDENTITY_INSTALLER }}" --component "src-tauri/target/universal-apple-darwin/release/bundle/macos/defguard-client.app" /Applications defguard-client.pkg
+ xcrun altool --upload-app --type macos --file defguard-client.pkg --apiKey ${{ secrets.APPLE_API_KEY }} --apiIssuer ${{ secrets.APPLE_API_ISSUER }}
+ # xcrun notarytool submit --wait --apple-id ${{ env.APPLE_ID }} --password ${{ secrets.NOTARYTOOL_APP_SPECIFIC_PASSWORD }} --team-id ${{ env.APPLE_TEAM_ID }} defguard-client.pkg
+ # xcrun stapler staple defguard-client.pkg
+
+ # - name: Upload installation package
+ # uses: actions/upload-release-asset@v1
+ # env:
+ # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ # with:
+ # upload_url: ${{ needs.create-release.outputs.upload_url }}
+ # asset_path: defguard-client.pkg
+ # asset_name: defguard-client-universal-${{ env.VERSION }}.pkg
+ # asset_content_type: application/octet-stream
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index d1e8adda..577a0b99 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -3,6 +3,7 @@ on:
push:
tags:
- v*.*.*
+
jobs:
create-release:
name: create-release
@@ -105,12 +106,11 @@ jobs:
apt-get install -y ruby
gem install deb-s3
echo "$(ruby -r rubygems -e 'puts Gem.user_dir')/bin" >> $GITHUB_PATH
- - name: Upload DEB to APT repository
+ - name: Upload DEB to APT repository
run: |
COMPONENT=$([[ "${{ github.ref_name }}" == *"-"* ]] && echo "pre-release" || echo "release") # if tag contain "-" assume it's pre-release.
deb-s3 upload -l --bucket=apt.defguard.net --access-key-id=${{ secrets.AWS_ACCESS_KEY_APT }} --secret-access-key=${{ secrets.AWS_SECRET_KEY_APT }} --s3-region=eu-north-1 --no-fail-if-exists --codename=bookworm --component="$COMPONENT" src-tauri/target/release/bundle/deb/defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb
-
build-linux:
needs:
@@ -208,7 +208,6 @@ jobs:
- name: Upload DEB to APT repository #Add this to ubuntu 22.04 job (on merge dev -> main) with --codename=bookworm
run: |
COMPONENT=$([[ "${{ github.ref_name }}" == *"-"* ]] && echo "pre-release" || echo "release") # if tag contain "-" assume it's pre-release.
-
deb-s3 upload -l --bucket=apt.defguard.net --access-key-id=${{ secrets.AWS_ACCESS_KEY_APT }} --secret-access-key=${{ secrets.AWS_SECRET_KEY_APT }} --s3-region=eu-north-1 --no-fail-if-exists --codename=trixie --component="$COMPONENT" src-tauri/target/release/bundle/deb/defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb
- name: Rename client binary
run: mv src-tauri/target/release/defguard-client defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}
@@ -301,8 +300,6 @@ jobs:
- self-hosted
- Linux
- X64
- strategy:
- fail-fast: false
steps:
- name: Sign APT repository
run: |
@@ -314,15 +311,15 @@ jobs:
for DIST in trixie bookworm; do
aws s3 cp s3://apt.defguard.net/dists/${DIST}/Release .
-
+
curl -X POST "${{ secrets.DEFGUARD_SIGNING_URL }}?signature_type=both" \
-H "Authorization: Bearer ${{ secrets.DEFGUARD_SIGNING_API_KEY }}" \
-F "file=@Release" \
-o response.json
-
+
cat response.json | jq -r '.files["Release.gpg"].content' | base64 --decode > Release.gpg
cat response.json | jq -r '.files.Release.content' | base64 --decode > InRelease
-
+
aws s3 cp Release.gpg s3://apt.defguard.net/dists/${DIST}/ --acl public-read
aws s3 cp InRelease s3://apt.defguard.net/dists/${DIST}/ --acl public-read
@@ -398,83 +395,6 @@ jobs:
cat PKGBUILD
cat .SRCINFO
- build-macos:
- needs:
- - create-release
- strategy:
- fail-fast: false
- matrix:
- target: [aarch64-apple-darwin, x86_64-apple-darwin]
- runs-on:
- - self-hosted
- - macOS
- env:
- APPLE_SIGNING_IDENTITY_APPLICATION: "Developer ID Application: defguard sp. z o.o. (82GZ7KN29J)"
- APPLE_SIGNING_IDENTITY_INSTALLER: "Developer ID Installer: defguard sp. z o.o. (82GZ7KN29J)"
- APPLE_ID: "kamil@defguard.net"
- APPLE_TEAM_ID: "82GZ7KN29J"
- steps:
- - uses: actions/checkout@v5
- with:
- submodules: "recursive"
- - name: Write release version
- run: |
- VERSION=$(echo ${GITHUB_REF_NAME#v} | cut -d '-' -f1)
- echo Version: $VERSION
- echo "VERSION=$VERSION" >> ${GITHUB_ENV}
- - uses: actions/setup-node@v4
- with:
- node-version: "22"
- - uses: pnpm/action-setup@v4
- with:
- version: 10
- run_install: false
- - name: Get pnpm store directory
- shell: bash
- run: echo "STORE_PATH=$(pnpm store path --silent)" >> ${GITHUB_ENV}
- - uses: actions/cache@v4
- name: Setup pnpm cache
- with:
- path: ${{ env.STORE_PATH }}
- key: ${{ runner.os }}-pnpm-build-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- restore-keys: |
- ${{ runner.os }}-pnpm-build-store-
- - name: Install deps
- run: pnpm install --frozen-lockfile
- - uses: dtolnay/rust-toolchain@stable
- - name: Install protobuf compiler
- run: brew install protobuf
- - name: Install ARM target
- run: rustup target add aarch64-apple-darwin
- - name: Unlock keychain
- run: security -v unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" /Users/admin/Library/Keychains/login.keychain
- - name: Build app
- uses: tauri-apps/tauri-action@v0.5.23 # .24 seems broken, TODO: update when fixed
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY_APPLICATION }}
- APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
- APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
- APPLE_ID: ${{ env.APPLE_ID }}
- APPLE_PASSWORD: ${{ secrets.NOTARYTOOL_APP_SPECIFIC_PASSWORD }}
- APPLE_TEAM_ID: ${{ env.APPLE_TEAM_ID }}
- with:
- args: --target ${{ matrix.target }} -v
- - name: Build installation package
- run: |
- bash build-macos-package.sh src-tauri/target/${{ matrix.target }} src-tauri/resources-macos/scripts '${{ env.APPLE_SIGNING_IDENTITY_INSTALLER }}' /Users/admin/Library/Keychains/login.keychain
- xcrun notarytool submit --wait --apple-id ${{ env.APPLE_ID }} --password ${{ secrets.NOTARYTOOL_APP_SPECIFIC_PASSWORD }} --team-id ${{ env.APPLE_TEAM_ID }} src-tauri/target/${{ matrix.target }}/product-signed/defguard.pkg
- xcrun stapler staple src-tauri/target/${{ matrix.target }}/product-signed/defguard.pkg
- - name: Upload installation package
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- upload_url: ${{ needs.create-release.outputs.upload_url }}
- asset_path: src-tauri/target/${{ matrix.target }}/product-signed/defguard.pkg
- asset_name: defguard-${{ matrix.target }}-${{ env.VERSION }}.pkg
- asset_content_type: application/octet-stream
-
# Builds Windows MSI and uploads it as artifact
build-windows:
needs:
diff --git a/.gitmodules b/.gitmodules
index 5aee25bb..d4d2f74e 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,6 @@
[submodule "src-tauri/proto"]
path = src-tauri/proto
url = ../proto.git
+[submodule "swift/boringtun"]
+ path = swift/boringtun
+ url = ../boringtun.git
diff --git a/biome.json b/biome.json
index a0a83836..2441f3ed 100644
--- a/biome.json
+++ b/biome.json
@@ -1,5 +1,5 @@
{
- "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
+ "$schema": "https://biomejs.dev/schemas/2.3.2/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
diff --git a/build-macos-package.sh b/build-macos-package.sh
deleted file mode 100755
index 99ed7b29..00000000
--- a/build-macos-package.sh
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/bin/bash
-
-set -e
-
-TARGET_DIRECTORY=$1
-SCRIPTS_DIRECTORY=$2
-APPLE_PACKAGE_SIGNING_IDENTITY=$3
-KEYCHAIN=$4
-
-mkdir -p "${TARGET_DIRECTORY}/package"
-mkdir -p "${TARGET_DIRECTORY}/product"
-mkdir -p "${TARGET_DIRECTORY}/product-signed"
-
-APP_ROOT="${TARGET_DIRECTORY}/release/bundle/macos/defguard-client.app"
-
-chmod -R 755 ${APP_ROOT}
-
-pkgbuild \
- --analyze \
- --root ${APP_ROOT} \
- "${TARGET_DIRECTORY}/defguard-client.plist"
-
-PACKAGE_PATH="${TARGET_DIRECTORY}/package/defguard.pkg"
-
-pkgbuild \
- --identifier "net.defguard" \
- --root ${APP_ROOT} \
- --component-plist ${TARGET_DIRECTORY}/defguard-client.plist \
- --install-location "/Applications/defguard-client.app" \
- --scripts ${SCRIPTS_DIRECTORY} \
- "${PACKAGE_PATH}"
-
-productbuild \
- --package "${PACKAGE_PATH}" \
- "${TARGET_DIRECTORY}/product/defguard.pkg"
-
-productsign \
- --sign "${APPLE_PACKAGE_SIGNING_IDENTITY}" \
- --keychain "${KEYCHAIN}" \
- "${TARGET_DIRECTORY}/product/defguard.pkg" \
- "${TARGET_DIRECTORY}/product-signed/defguard.pkg"
diff --git a/package.json b/package.json
index 03e77a3b..18f2486b 100644
--- a/package.json
+++ b/package.json
@@ -52,7 +52,7 @@
"@react-hook/resize-observer": "^2.0.2",
"@stablelib/base64": "^2.0.1",
"@stablelib/x25519": "^2.0.1",
- "@tanstack/query-core": "^5.90.2",
+ "@tanstack/query-core": "^5.90.5",
"@tanstack/react-virtual": "3.13.12",
"@tauri-apps/api": "^2.8.0",
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
@@ -72,19 +72,19 @@
"classnames": "^2.5.1",
"clsx": "^2.1.1",
"compare-versions": "^6.1.1",
- "dayjs": "^1.11.18",
+ "dayjs": "^1.11.19",
"deepmerge-ts": "^7.1.5",
"detect-browser": "^5.3.0",
"fast-deep-equal": "^3.1.3",
"file-saver": "^2.0.5",
"get-text-width": "^1.0.3",
- "html-react-parser": "^5.2.6",
+ "html-react-parser": "^5.2.7",
"itertools": "^2.5.0",
"js-base64": "^3.7.8",
"lodash-es": "^4.17.21",
"merge-refs": "^2.0.0",
"millify": "^6.1.0",
- "motion": "^12.23.22",
+ "motion": "^12.23.24",
"p-timeout": "^6.1.4",
"prop-types": "^15.8.1",
"radash": "^12.1.1",
@@ -100,36 +100,36 @@
"react-router-dom": "^6.30.1",
"react-use-websocket": "^4.13.0",
"react-virtualized-auto-sizer": "^1.0.26",
- "recharts": "^3.2.1",
+ "recharts": "^3.3.0",
"rehype-sanitize": "^6.0.0",
"rxjs": "^7.8.2",
- "use-breakpoint": "^4.0.6",
+ "use-breakpoint": "^4.0.10",
"zod": "^3.25.76",
"zustand": "^5.0.8"
},
"devDependencies": {
- "@biomejs/biome": "^2.2.4",
+ "@biomejs/biome": "^2.3.2",
"@hookform/devtools": "^4.4.0",
"@svgr/cli": "^8.1.0",
- "@tanstack/react-query": "^5.90.2",
+ "@tanstack/react-query": "^5.90.5",
"@tanstack/react-query-devtools": "^5.90.2",
- "@tauri-apps/cli": "^2.8.4",
+ "@tauri-apps/cli": "^2.9.2",
"@types/file-saver": "^2.0.7",
"@types/lodash-es": "^4.17.12",
- "@types/node": "^24.6.2",
- "@types/react": "^19.2.0",
- "@types/react-dom": "^19.2.0",
- "@vitejs/plugin-react": "^5.0.4",
- "@vitejs/plugin-react-swc": "^4.1.0",
+ "@types/node": "^24.9.2",
+ "@types/react": "^19.2.2",
+ "@types/react-dom": "^19.2.2",
+ "@vitejs/plugin-react": "^5.1.0",
+ "@vitejs/plugin-react-swc": "^4.2.0",
"autoprefixer": "^10.4.21",
"npm-run-all": "^4.1.5",
"postcss": "^8.5.6",
"prettier": "^3.6.2",
"sass": "~1.92.1",
- "typedoc": "^0.28.13",
+ "typedoc": "^0.28.14",
"typesafe-i18n": "^5.26.2",
"typescript": "^5.9.3",
- "vite": "^7.1.8"
+ "vite": "^7.1.12"
},
"volta": {
"node": "20.5.1"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7a8fc6b6..8f665e50 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -13,7 +13,7 @@ importers:
version: 0.27.16(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@hookform/resolvers':
specifier: ^3.10.0
- version: 3.10.0(react-hook-form@7.63.0(react@19.2.0))
+ version: 3.10.0(react-hook-form@7.65.0(react@19.2.0))
'@react-hook/resize-observer':
specifier: ^2.0.2
version: 2.0.2(react@19.2.0)
@@ -24,47 +24,47 @@ importers:
specifier: ^2.0.1
version: 2.0.1
'@tanstack/query-core':
- specifier: ^5.90.2
- version: 5.90.2
+ specifier: ^5.90.5
+ version: 5.90.5
'@tanstack/react-virtual':
specifier: 3.13.12
version: 3.13.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@tauri-apps/api':
specifier: ^2.8.0
- version: 2.8.0
+ version: 2.9.0
'@tauri-apps/plugin-clipboard-manager':
specifier: ^2.3.0
- version: 2.3.0
+ version: 2.3.2
'@tauri-apps/plugin-deep-link':
specifier: ^2.4.3
- version: 2.4.3
+ version: 2.4.5
'@tauri-apps/plugin-dialog':
specifier: ^2.4.0
- version: 2.4.0
+ version: 2.4.2
'@tauri-apps/plugin-fs':
specifier: ^2.4.2
- version: 2.4.2
+ version: 2.4.4
'@tauri-apps/plugin-http':
specifier: ^2.5.2
- version: 2.5.2
+ version: 2.5.4
'@tauri-apps/plugin-log':
specifier: ^2.7.0
- version: 2.7.0
+ version: 2.7.1
'@tauri-apps/plugin-notification':
specifier: ^2.3.1
- version: 2.3.1
+ version: 2.3.3
'@tauri-apps/plugin-opener':
specifier: ^2.5.0
- version: 2.5.0
+ version: 2.5.2
'@tauri-apps/plugin-os':
specifier: ^2.3.1
- version: 2.3.1
+ version: 2.3.2
'@tauri-apps/plugin-process':
specifier: ^2.3.0
- version: 2.3.0
+ version: 2.3.1
'@tauri-apps/plugin-window-state':
specifier: ^2.4.0
- version: 2.4.0
+ version: 2.4.1
'@types/byte-size':
specifier: ^8.1.2
version: 8.1.2
@@ -84,8 +84,8 @@ importers:
specifier: ^6.1.1
version: 6.1.1
dayjs:
- specifier: ^1.11.18
- version: 1.11.18
+ specifier: ^1.11.19
+ version: 1.11.19
deepmerge-ts:
specifier: ^7.1.5
version: 7.1.5
@@ -102,8 +102,8 @@ importers:
specifier: ^1.0.3
version: 1.0.3
html-react-parser:
- specifier: ^5.2.6
- version: 5.2.6(@types/react@19.2.0)(react@19.2.0)
+ specifier: ^5.2.7
+ version: 5.2.7(@types/react@19.2.2)(react@19.2.0)
itertools:
specifier: ^2.5.0
version: 2.5.0
@@ -115,13 +115,13 @@ importers:
version: 4.17.21
merge-refs:
specifier: ^2.0.0
- version: 2.0.0(@types/react@19.2.0)
+ version: 2.0.0(@types/react@19.2.2)
millify:
specifier: ^6.1.0
version: 6.1.0
motion:
- specifier: ^12.23.22
- version: 12.23.22(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ specifier: ^12.23.24
+ version: 12.23.24(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
p-timeout:
specifier: ^6.1.4
version: 6.1.4
@@ -145,7 +145,7 @@ importers:
version: 19.2.0(react@19.2.0)
react-hook-form:
specifier: ^7.63.0
- version: 7.63.0(react@19.2.0)
+ version: 7.65.0(react@19.2.0)
react-hotkeys-hook:
specifier: ^5.2.1
version: 5.2.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -154,7 +154,7 @@ importers:
version: 3.5.0(react@19.2.0)
react-markdown:
specifier: ^10.1.0
- version: 10.1.0(@types/react@19.2.0)(react@19.2.0)
+ version: 10.1.0(@types/react@19.2.2)(react@19.2.0)
react-qr-code:
specifier: ^2.0.18
version: 2.0.18(react@19.2.0)
@@ -168,8 +168,8 @@ importers:
specifier: ^1.0.26
version: 1.0.26(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
recharts:
- specifier: ^3.2.1
- version: 3.2.1(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react-is@18.3.1)(react@19.2.0)(redux@5.0.1)
+ specifier: ^3.3.0
+ version: 3.3.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react-is@18.3.1)(react@19.2.0)(redux@5.0.1)
rehype-sanitize:
specifier: ^6.0.0
version: 6.0.0
@@ -177,33 +177,33 @@ importers:
specifier: ^7.8.2
version: 7.8.2
use-breakpoint:
- specifier: ^4.0.6
- version: 4.0.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ specifier: ^4.0.10
+ version: 4.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
zod:
specifier: ^3.25.76
version: 3.25.76
zustand:
specifier: ^5.0.8
- version: 5.0.8(@types/react@19.2.0)(immer@10.1.3)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0))
+ version: 5.0.8(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0))
devDependencies:
'@biomejs/biome':
- specifier: ^2.2.4
- version: 2.2.4
+ specifier: ^2.3.2
+ version: 2.3.2
'@hookform/devtools':
specifier: ^4.4.0
- version: 4.4.0(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ version: 4.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@svgr/cli':
specifier: ^8.1.0
version: 8.1.0(typescript@5.9.3)
'@tanstack/react-query':
- specifier: ^5.90.2
- version: 5.90.2(react@19.2.0)
+ specifier: ^5.90.5
+ version: 5.90.5(react@19.2.0)
'@tanstack/react-query-devtools':
specifier: ^5.90.2
- version: 5.90.2(@tanstack/react-query@5.90.2(react@19.2.0))(react@19.2.0)
+ version: 5.90.2(@tanstack/react-query@5.90.5(react@19.2.0))(react@19.2.0)
'@tauri-apps/cli':
- specifier: ^2.8.4
- version: 2.8.4
+ specifier: ^2.9.2
+ version: 2.9.2
'@types/file-saver':
specifier: ^2.0.7
version: 2.0.7
@@ -211,20 +211,20 @@ importers:
specifier: ^4.17.12
version: 4.17.12
'@types/node':
- specifier: ^24.6.2
- version: 24.6.2
+ specifier: ^24.9.2
+ version: 24.9.2
'@types/react':
- specifier: ^19.2.0
- version: 19.2.0
+ specifier: ^19.2.2
+ version: 19.2.2
'@types/react-dom':
- specifier: ^19.2.0
- version: 19.2.0(@types/react@19.2.0)
+ specifier: ^19.2.2
+ version: 19.2.2(@types/react@19.2.2)
'@vitejs/plugin-react':
- specifier: ^5.0.4
- version: 5.0.4(vite@7.1.8(@types/node@24.6.2)(sass@1.92.1)(yaml@2.8.1))
+ specifier: ^5.1.0
+ version: 5.1.0(vite@7.1.12(@types/node@24.9.2)(sass@1.92.1)(yaml@2.8.1))
'@vitejs/plugin-react-swc':
- specifier: ^4.1.0
- version: 4.1.0(vite@7.1.8(@types/node@24.6.2)(sass@1.92.1)(yaml@2.8.1))
+ specifier: ^4.2.0
+ version: 4.2.0(vite@7.1.12(@types/node@24.9.2)(sass@1.92.1)(yaml@2.8.1))
autoprefixer:
specifier: ^10.4.21
version: 10.4.21(postcss@8.5.6)
@@ -241,8 +241,8 @@ importers:
specifier: ~1.92.1
version: 1.92.1
typedoc:
- specifier: ^0.28.13
- version: 0.28.13(typescript@5.9.3)
+ specifier: ^0.28.14
+ version: 0.28.14(typescript@5.9.3)
typesafe-i18n:
specifier: ^5.26.2
version: 5.26.2(typescript@5.9.3)
@@ -250,8 +250,8 @@ importers:
specifier: ^5.9.3
version: 5.9.3
vite:
- specifier: ^7.1.8
- version: 7.1.8(@types/node@24.6.2)(sass@1.92.1)(yaml@2.8.1)
+ specifier: ^7.1.12
+ version: 7.1.12(@types/node@24.9.2)(sass@1.92.1)(yaml@2.8.1)
packages:
@@ -259,16 +259,16 @@ packages:
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
engines: {node: '>=6.9.0'}
- '@babel/compat-data@7.28.4':
- resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==}
+ '@babel/compat-data@7.28.5':
+ resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==}
engines: {node: '>=6.9.0'}
- '@babel/core@7.28.4':
- resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==}
+ '@babel/core@7.28.5':
+ resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==}
engines: {node: '>=6.9.0'}
- '@babel/generator@7.28.3':
- resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==}
+ '@babel/generator@7.28.5':
+ resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==}
engines: {node: '>=6.9.0'}
'@babel/helper-compilation-targets@7.27.2':
@@ -297,8 +297,8 @@ packages:
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'}
- '@babel/helper-validator-identifier@7.27.1':
- resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'}
'@babel/helper-validator-option@7.27.1':
@@ -309,8 +309,8 @@ packages:
resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==}
engines: {node: '>=6.9.0'}
- '@babel/parser@7.28.4':
- resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==}
+ '@babel/parser@7.28.5':
+ resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -334,63 +334,63 @@ packages:
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
engines: {node: '>=6.9.0'}
- '@babel/traverse@7.28.4':
- resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==}
+ '@babel/traverse@7.28.5':
+ resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==}
engines: {node: '>=6.9.0'}
- '@babel/types@7.28.4':
- resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==}
+ '@babel/types@7.28.5':
+ resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
engines: {node: '>=6.9.0'}
- '@biomejs/biome@2.2.4':
- resolution: {integrity: sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg==}
+ '@biomejs/biome@2.3.2':
+ resolution: {integrity: sha512-8e9tzamuDycx7fdrcJ/F/GDZ8SYukc5ud6tDicjjFqURKYFSWMl0H0iXNXZEGmcmNUmABgGuHThPykcM41INgg==}
engines: {node: '>=14.21.3'}
hasBin: true
- '@biomejs/cli-darwin-arm64@2.2.4':
- resolution: {integrity: sha512-RJe2uiyaloN4hne4d2+qVj3d3gFJFbmrr5PYtkkjei1O9c+BjGXgpUPVbi8Pl8syumhzJjFsSIYkcLt2VlVLMA==}
+ '@biomejs/cli-darwin-arm64@2.3.2':
+ resolution: {integrity: sha512-4LECm4kc3If0JISai4c3KWQzukoUdpxy4fRzlrPcrdMSRFksR9ZoXK7JBcPuLBmd2SoT4/d7CQS33VnZpgBjew==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [darwin]
- '@biomejs/cli-darwin-x64@2.2.4':
- resolution: {integrity: sha512-cFsdB4ePanVWfTnPVaUX+yr8qV8ifxjBKMkZwN7gKb20qXPxd/PmwqUH8mY5wnM9+U0QwM76CxFyBRJhC9tQwg==}
+ '@biomejs/cli-darwin-x64@2.3.2':
+ resolution: {integrity: sha512-jNMnfwHT4N3wi+ypRfMTjLGnDmKYGzxVr1EYAPBcauRcDnICFXN81wD6wxJcSUrLynoyyYCdfW6vJHS/IAoTDA==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [darwin]
- '@biomejs/cli-linux-arm64-musl@2.2.4':
- resolution: {integrity: sha512-7TNPkMQEWfjvJDaZRSkDCPT/2r5ESFPKx+TEev+I2BXDGIjfCZk2+b88FOhnJNHtksbOZv8ZWnxrA5gyTYhSsQ==}
+ '@biomejs/cli-linux-arm64-musl@2.3.2':
+ resolution: {integrity: sha512-2Zz4usDG1GTTPQnliIeNx6eVGGP2ry5vE/v39nT73a3cKN6t5H5XxjcEoZZh62uVZvED7hXXikclvI64vZkYqw==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
- '@biomejs/cli-linux-arm64@2.2.4':
- resolution: {integrity: sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw==}
+ '@biomejs/cli-linux-arm64@2.3.2':
+ resolution: {integrity: sha512-amnqvk+gWybbQleRRq8TMe0rIv7GHss8mFJEaGuEZYWg1Tw14YKOkeo8h6pf1c+d3qR+JU4iT9KXnBKGON4klw==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
- '@biomejs/cli-linux-x64-musl@2.2.4':
- resolution: {integrity: sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg==}
+ '@biomejs/cli-linux-x64-musl@2.3.2':
+ resolution: {integrity: sha512-gzB19MpRdTuOuLtPpFBGrV3Lq424gHyq2lFj8wfX9tvLMLdmA/R9C7k/mqBp/spcbWuHeIEKgEs3RviOPcWGBA==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
- '@biomejs/cli-linux-x64@2.2.4':
- resolution: {integrity: sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ==}
+ '@biomejs/cli-linux-x64@2.3.2':
+ resolution: {integrity: sha512-8BG/vRAhFz1pmuyd24FQPhNeueLqPtwvZk6yblABY2gzL2H8fLQAF/Z2OPIc+BPIVPld+8cSiKY/KFh6k81xfA==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
- '@biomejs/cli-win32-arm64@2.2.4':
- resolution: {integrity: sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ==}
+ '@biomejs/cli-win32-arm64@2.3.2':
+ resolution: {integrity: sha512-lCruqQlfWjhMlOdyf5pDHOxoNm4WoyY2vZ4YN33/nuZBRstVDuqPPjS0yBkbUlLEte11FbpW+wWSlfnZfSIZvg==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [win32]
- '@biomejs/cli-win32-x64@2.2.4':
- resolution: {integrity: sha512-3Y4V4zVRarVh/B/eSHczR4LYoSVyv3Dfuvm3cWs5w/HScccS0+Wt/lHOcDTRYeHjQmMYVC3rIRWqyN2EI52+zg==}
+ '@biomejs/cli-win32-x64@2.3.2':
+ resolution: {integrity: sha512-6Ee9P26DTb4D8sN9nXxgbi9Dw5vSOfH98M7UlmkjKB2vtUbrRqCbZiNfryGiwnPIpd6YUoTl7rLVD2/x1CyEHQ==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [win32]
@@ -449,158 +449,158 @@ packages:
'@emotion/weak-memoize@0.4.0':
resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==}
- '@esbuild/aix-ppc64@0.25.10':
- resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==}
+ '@esbuild/aix-ppc64@0.25.11':
+ resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
- '@esbuild/android-arm64@0.25.10':
- resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==}
+ '@esbuild/android-arm64@0.25.11':
+ resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
- '@esbuild/android-arm@0.25.10':
- resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==}
+ '@esbuild/android-arm@0.25.11':
+ resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
- '@esbuild/android-x64@0.25.10':
- resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==}
+ '@esbuild/android-x64@0.25.11':
+ resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
- '@esbuild/darwin-arm64@0.25.10':
- resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==}
+ '@esbuild/darwin-arm64@0.25.11':
+ resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
- '@esbuild/darwin-x64@0.25.10':
- resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==}
+ '@esbuild/darwin-x64@0.25.11':
+ resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
- '@esbuild/freebsd-arm64@0.25.10':
- resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==}
+ '@esbuild/freebsd-arm64@0.25.11':
+ resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
- '@esbuild/freebsd-x64@0.25.10':
- resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==}
+ '@esbuild/freebsd-x64@0.25.11':
+ resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
- '@esbuild/linux-arm64@0.25.10':
- resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==}
+ '@esbuild/linux-arm64@0.25.11':
+ resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
- '@esbuild/linux-arm@0.25.10':
- resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==}
+ '@esbuild/linux-arm@0.25.11':
+ resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
- '@esbuild/linux-ia32@0.25.10':
- resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==}
+ '@esbuild/linux-ia32@0.25.11':
+ resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
- '@esbuild/linux-loong64@0.25.10':
- resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==}
+ '@esbuild/linux-loong64@0.25.11':
+ resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
- '@esbuild/linux-mips64el@0.25.10':
- resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==}
+ '@esbuild/linux-mips64el@0.25.11':
+ resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
- '@esbuild/linux-ppc64@0.25.10':
- resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==}
+ '@esbuild/linux-ppc64@0.25.11':
+ resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
- '@esbuild/linux-riscv64@0.25.10':
- resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==}
+ '@esbuild/linux-riscv64@0.25.11':
+ resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
- '@esbuild/linux-s390x@0.25.10':
- resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==}
+ '@esbuild/linux-s390x@0.25.11':
+ resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
- '@esbuild/linux-x64@0.25.10':
- resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==}
+ '@esbuild/linux-x64@0.25.11':
+ resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
- '@esbuild/netbsd-arm64@0.25.10':
- resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==}
+ '@esbuild/netbsd-arm64@0.25.11':
+ resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
- '@esbuild/netbsd-x64@0.25.10':
- resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==}
+ '@esbuild/netbsd-x64@0.25.11':
+ resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
- '@esbuild/openbsd-arm64@0.25.10':
- resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==}
+ '@esbuild/openbsd-arm64@0.25.11':
+ resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
- '@esbuild/openbsd-x64@0.25.10':
- resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==}
+ '@esbuild/openbsd-x64@0.25.11':
+ resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
- '@esbuild/openharmony-arm64@0.25.10':
- resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==}
+ '@esbuild/openharmony-arm64@0.25.11':
+ resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
- '@esbuild/sunos-x64@0.25.10':
- resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==}
+ '@esbuild/sunos-x64@0.25.11':
+ resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
- '@esbuild/win32-arm64@0.25.10':
- resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==}
+ '@esbuild/win32-arm64@0.25.11':
+ resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
- '@esbuild/win32-ia32@0.25.10':
- resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==}
+ '@esbuild/win32-ia32@0.25.11':
+ resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
- '@esbuild/win32-x64@0.25.10':
- resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==}
+ '@esbuild/win32-x64@0.25.11':
+ resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
@@ -626,8 +626,8 @@ packages:
'@floating-ui/utils@0.2.10':
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
- '@gerrit0/mini-shiki@3.13.0':
- resolution: {integrity: sha512-mCrNvZNYNrwKer5PWLF6cOc0OEe2eKzgy976x+IT2tynwJYl+7UpHTSeXQJGijgTcoOf+f359L946unWlYRnsg==}
+ '@gerrit0/mini-shiki@3.14.0':
+ resolution: {integrity: sha512-c5X8fwPLOtUS8TVdqhynz9iV0GlOtFUT1ppXYzUUlEXe4kbZ/mvMT8wXoT8kCwUka+zsiloq7sD3pZ3+QVTuNQ==}
'@hookform/devtools@4.4.0':
resolution: {integrity: sha512-Mtlic+uigoYBPXlfvPBfiYYUZuyMrD3pTjDpVIhL6eCZTvQkHsKBSKeZCvXWUZr8fqrkzDg27N+ZuazLKq6Vmg==}
@@ -753,8 +753,8 @@ packages:
peerDependencies:
react: '>=18'
- '@reduxjs/toolkit@2.9.0':
- resolution: {integrity: sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog==}
+ '@reduxjs/toolkit@2.9.2':
+ resolution: {integrity: sha512-ZAYu/NXkl/OhqTz7rfPaAhY0+e8Fr15jqNxte/2exKUxvHyQ/hcqmdekiN1f+Lcw3pE+34FCgX+26zcUE3duCg==}
peerDependencies:
react: ^16.9.0 || ^17.0.0 || ^18 || ^19
react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0
@@ -768,133 +768,130 @@ packages:
resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==}
engines: {node: '>=14.0.0'}
- '@rolldown/pluginutils@1.0.0-beta.35':
- resolution: {integrity: sha512-slYrCpoxJUqzFDDNlvrOYRazQUNRvWPjXA17dAOISY3rDMxX6k8K4cj2H+hEYMHF81HO3uNd5rHVigAWRM5dSg==}
+ '@rolldown/pluginutils@1.0.0-beta.43':
+ resolution: {integrity: sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==}
- '@rolldown/pluginutils@1.0.0-beta.38':
- resolution: {integrity: sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==}
-
- '@rollup/rollup-android-arm-eabi@4.52.3':
- resolution: {integrity: sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==}
+ '@rollup/rollup-android-arm-eabi@4.52.5':
+ resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==}
cpu: [arm]
os: [android]
- '@rollup/rollup-android-arm64@4.52.3':
- resolution: {integrity: sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==}
+ '@rollup/rollup-android-arm64@4.52.5':
+ resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==}
cpu: [arm64]
os: [android]
- '@rollup/rollup-darwin-arm64@4.52.3':
- resolution: {integrity: sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==}
+ '@rollup/rollup-darwin-arm64@4.52.5':
+ resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==}
cpu: [arm64]
os: [darwin]
- '@rollup/rollup-darwin-x64@4.52.3':
- resolution: {integrity: sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==}
+ '@rollup/rollup-darwin-x64@4.52.5':
+ resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==}
cpu: [x64]
os: [darwin]
- '@rollup/rollup-freebsd-arm64@4.52.3':
- resolution: {integrity: sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==}
+ '@rollup/rollup-freebsd-arm64@4.52.5':
+ resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==}
cpu: [arm64]
os: [freebsd]
- '@rollup/rollup-freebsd-x64@4.52.3':
- resolution: {integrity: sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==}
+ '@rollup/rollup-freebsd-x64@4.52.5':
+ resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==}
cpu: [x64]
os: [freebsd]
- '@rollup/rollup-linux-arm-gnueabihf@4.52.3':
- resolution: {integrity: sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==}
+ '@rollup/rollup-linux-arm-gnueabihf@4.52.5':
+ resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==}
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm-musleabihf@4.52.3':
- resolution: {integrity: sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==}
+ '@rollup/rollup-linux-arm-musleabihf@4.52.5':
+ resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==}
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm64-gnu@4.52.3':
- resolution: {integrity: sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==}
+ '@rollup/rollup-linux-arm64-gnu@4.52.5':
+ resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==}
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-arm64-musl@4.52.3':
- resolution: {integrity: sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==}
+ '@rollup/rollup-linux-arm64-musl@4.52.5':
+ resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==}
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-loong64-gnu@4.52.3':
- resolution: {integrity: sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==}
+ '@rollup/rollup-linux-loong64-gnu@4.52.5':
+ resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==}
cpu: [loong64]
os: [linux]
- '@rollup/rollup-linux-ppc64-gnu@4.52.3':
- resolution: {integrity: sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==}
+ '@rollup/rollup-linux-ppc64-gnu@4.52.5':
+ resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==}
cpu: [ppc64]
os: [linux]
- '@rollup/rollup-linux-riscv64-gnu@4.52.3':
- resolution: {integrity: sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==}
+ '@rollup/rollup-linux-riscv64-gnu@4.52.5':
+ resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==}
cpu: [riscv64]
os: [linux]
- '@rollup/rollup-linux-riscv64-musl@4.52.3':
- resolution: {integrity: sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==}
+ '@rollup/rollup-linux-riscv64-musl@4.52.5':
+ resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==}
cpu: [riscv64]
os: [linux]
- '@rollup/rollup-linux-s390x-gnu@4.52.3':
- resolution: {integrity: sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==}
+ '@rollup/rollup-linux-s390x-gnu@4.52.5':
+ resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==}
cpu: [s390x]
os: [linux]
- '@rollup/rollup-linux-x64-gnu@4.52.3':
- resolution: {integrity: sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==}
+ '@rollup/rollup-linux-x64-gnu@4.52.5':
+ resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==}
cpu: [x64]
os: [linux]
- '@rollup/rollup-linux-x64-musl@4.52.3':
- resolution: {integrity: sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==}
+ '@rollup/rollup-linux-x64-musl@4.52.5':
+ resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==}
cpu: [x64]
os: [linux]
- '@rollup/rollup-openharmony-arm64@4.52.3':
- resolution: {integrity: sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==}
+ '@rollup/rollup-openharmony-arm64@4.52.5':
+ resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==}
cpu: [arm64]
os: [openharmony]
- '@rollup/rollup-win32-arm64-msvc@4.52.3':
- resolution: {integrity: sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==}
+ '@rollup/rollup-win32-arm64-msvc@4.52.5':
+ resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==}
cpu: [arm64]
os: [win32]
- '@rollup/rollup-win32-ia32-msvc@4.52.3':
- resolution: {integrity: sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==}
+ '@rollup/rollup-win32-ia32-msvc@4.52.5':
+ resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==}
cpu: [ia32]
os: [win32]
- '@rollup/rollup-win32-x64-gnu@4.52.3':
- resolution: {integrity: sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==}
+ '@rollup/rollup-win32-x64-gnu@4.52.5':
+ resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==}
cpu: [x64]
os: [win32]
- '@rollup/rollup-win32-x64-msvc@4.52.3':
- resolution: {integrity: sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==}
+ '@rollup/rollup-win32-x64-msvc@4.52.5':
+ resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==}
cpu: [x64]
os: [win32]
- '@shikijs/engine-oniguruma@3.13.0':
- resolution: {integrity: sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==}
+ '@shikijs/engine-oniguruma@3.14.0':
+ resolution: {integrity: sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug==}
- '@shikijs/langs@3.13.0':
- resolution: {integrity: sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==}
+ '@shikijs/langs@3.14.0':
+ resolution: {integrity: sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg==}
- '@shikijs/themes@3.13.0':
- resolution: {integrity: sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==}
+ '@shikijs/themes@3.14.0':
+ resolution: {integrity: sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA==}
- '@shikijs/types@3.13.0':
- resolution: {integrity: sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==}
+ '@shikijs/types@3.14.0':
+ resolution: {integrity: sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ==}
'@shikijs/vscode-textmate@10.0.2':
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
@@ -1014,68 +1011,68 @@ packages:
peerDependencies:
'@svgr/core': '*'
- '@swc/core-darwin-arm64@1.13.5':
- resolution: {integrity: sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==}
+ '@swc/core-darwin-arm64@1.14.0':
+ resolution: {integrity: sha512-uHPC8rlCt04nvYNczWzKVdgnRhxCa3ndKTBBbBpResOZsRmiwRAvByIGh599j+Oo6Z5eyTPrgY+XfJzVmXnN7Q==}
engines: {node: '>=10'}
cpu: [arm64]
os: [darwin]
- '@swc/core-darwin-x64@1.13.5':
- resolution: {integrity: sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng==}
+ '@swc/core-darwin-x64@1.14.0':
+ resolution: {integrity: sha512-2SHrlpl68vtePRknv9shvM9YKKg7B9T13tcTg9aFCwR318QTYo+FzsKGmQSv9ox/Ua0Q2/5y2BNjieffJoo4nA==}
engines: {node: '>=10'}
cpu: [x64]
os: [darwin]
- '@swc/core-linux-arm-gnueabihf@1.13.5':
- resolution: {integrity: sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ==}
+ '@swc/core-linux-arm-gnueabihf@1.14.0':
+ resolution: {integrity: sha512-SMH8zn01dxt809svetnxpeg/jWdpi6dqHKO3Eb11u4OzU2PK7I5uKS6gf2hx5LlTbcJMFKULZiVwjlQLe8eqtg==}
engines: {node: '>=10'}
cpu: [arm]
os: [linux]
- '@swc/core-linux-arm64-gnu@1.13.5':
- resolution: {integrity: sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw==}
+ '@swc/core-linux-arm64-gnu@1.14.0':
+ resolution: {integrity: sha512-q2JRu2D8LVqGeHkmpVCljVNltG0tB4o4eYg+dElFwCS8l2Mnt9qurMCxIeo9mgoqz0ax+k7jWtIRHktnVCbjvQ==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
- '@swc/core-linux-arm64-musl@1.13.5':
- resolution: {integrity: sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==}
+ '@swc/core-linux-arm64-musl@1.14.0':
+ resolution: {integrity: sha512-uofpVoPCEUjYIv454ZEZ3sLgMD17nIwlz2z7bsn7rl301Kt/01umFA7MscUovFfAK2IRGck6XB+uulMu6aFhKQ==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
- '@swc/core-linux-x64-gnu@1.13.5':
- resolution: {integrity: sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==}
+ '@swc/core-linux-x64-gnu@1.14.0':
+ resolution: {integrity: sha512-quTTx1Olm05fBfv66DEBuOsOgqdypnZ/1Bh3yGXWY7ANLFeeRpCDZpljD9BSjdsNdPOlwJmEUZXMHtGm3v1TZQ==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
- '@swc/core-linux-x64-musl@1.13.5':
- resolution: {integrity: sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==}
+ '@swc/core-linux-x64-musl@1.14.0':
+ resolution: {integrity: sha512-caaNAu+aIqT8seLtCf08i8C3/UC5ttQujUjejhMcuS1/LoCKtNiUs4VekJd2UGt+pyuuSrQ6dKl8CbCfWvWeXw==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
- '@swc/core-win32-arm64-msvc@1.13.5':
- resolution: {integrity: sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==}
+ '@swc/core-win32-arm64-msvc@1.14.0':
+ resolution: {integrity: sha512-EeW3jFlT3YNckJ6V/JnTfGcX7UHGyh6/AiCPopZ1HNaGiXVCKHPpVQZicmtyr/UpqxCXLrTgjHOvyMke7YN26A==}
engines: {node: '>=10'}
cpu: [arm64]
os: [win32]
- '@swc/core-win32-ia32-msvc@1.13.5':
- resolution: {integrity: sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw==}
+ '@swc/core-win32-ia32-msvc@1.14.0':
+ resolution: {integrity: sha512-dPai3KUIcihV5hfoO4QNQF5HAaw8+2bT7dvi8E5zLtecW2SfL3mUZipzampXq5FHll0RSCLzlrXnSx+dBRZIIQ==}
engines: {node: '>=10'}
cpu: [ia32]
os: [win32]
- '@swc/core-win32-x64-msvc@1.13.5':
- resolution: {integrity: sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q==}
+ '@swc/core-win32-x64-msvc@1.14.0':
+ resolution: {integrity: sha512-nm+JajGrTqUA6sEHdghDlHMNfH1WKSiuvljhdmBACW4ta4LC3gKurX2qZuiBARvPkephW9V/i5S8QPY1PzFEqg==}
engines: {node: '>=10'}
cpu: [x64]
os: [win32]
- '@swc/core@1.13.5':
- resolution: {integrity: sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==}
+ '@swc/core@1.14.0':
+ resolution: {integrity: sha512-oExhY90bes5pDTVrei0xlMVosTxwd/NMafIpqsC4dMbRYZ5KB981l/CX8tMnGsagTplj/RcG9BeRYmV6/J5m3w==}
engines: {node: '>=10'}
peerDependencies:
'@swc/helpers': '>=0.5.17'
@@ -1089,8 +1086,8 @@ packages:
'@swc/types@0.1.25':
resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
- '@tanstack/query-core@5.90.2':
- resolution: {integrity: sha512-k/TcR3YalnzibscALLwxeiLUub6jN5EDLwKDiO7q5f4ICEoptJ+n9+7vcEFy5/x/i6Q+Lb/tXrsKCggf5uQJXQ==}
+ '@tanstack/query-core@5.90.5':
+ resolution: {integrity: sha512-wLamYp7FaDq6ZnNehypKI5fNvxHPfTYylE0m/ZpuuzJfJqhR5Pxg9gvGBHZx4n7J+V5Rg5mZxHHTlv25Zt5u+w==}
'@tanstack/query-devtools@5.90.1':
resolution: {integrity: sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ==}
@@ -1101,8 +1098,8 @@ packages:
'@tanstack/react-query': ^5.90.2
react: ^18 || ^19
- '@tanstack/react-query@5.90.2':
- resolution: {integrity: sha512-CLABiR+h5PYfOWr/z+vWFt5VsOA2ekQeRQBFSKlcoW6Ndx/f8rfyVmq4LbgOM4GG2qtxAxjLYLOpCNTYm4uKzw==}
+ '@tanstack/react-query@5.90.5':
+ resolution: {integrity: sha512-pN+8UWpxZkEJ/Rnnj2v2Sxpx1WFlaa9L6a4UO89p6tTQbeo+m0MS8oYDjbggrR8QcTyjKoYWKS3xJQGr3ExT8Q==}
peerDependencies:
react: ^18 || ^19
@@ -1115,112 +1112,112 @@ packages:
'@tanstack/virtual-core@3.13.12':
resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==}
- '@tauri-apps/api@2.8.0':
- resolution: {integrity: sha512-ga7zdhbS2GXOMTIZRT0mYjKJtR9fivsXzsyq5U3vjDL0s6DTMwYRm0UHNjzTY5dh4+LSC68Sm/7WEiimbQNYlw==}
+ '@tauri-apps/api@2.9.0':
+ resolution: {integrity: sha512-qD5tMjh7utwBk9/5PrTA/aGr3i5QaJ/Mlt7p8NilQ45WgbifUNPyKWsA63iQ8YfQq6R8ajMapU+/Q8nMcPRLNw==}
- '@tauri-apps/cli-darwin-arm64@2.8.4':
- resolution: {integrity: sha512-BKu8HRkYV01SMTa7r4fLx+wjgtRK8Vep7lmBdHDioP6b8XH3q2KgsAyPWfEZaZIkZ2LY4SqqGARaE9oilNe0oA==}
+ '@tauri-apps/cli-darwin-arm64@2.9.2':
+ resolution: {integrity: sha512-g1OtCXydOZFYRUEAyGYdJ2lLaE3l5jk8o+Bro8y2WOLwBLtbWjBoJIVobOKFanfjG/Xr8H/UA+umEVILPhMc2A==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
- '@tauri-apps/cli-darwin-x64@2.8.4':
- resolution: {integrity: sha512-imb9PfSd/7G6VAO7v1bQ2A3ZH4NOCbhGJFLchxzepGcXf9NKkfun157JH9mko29K6sqAwuJ88qtzbKCbWJTH9g==}
+ '@tauri-apps/cli-darwin-x64@2.9.2':
+ resolution: {integrity: sha512-nHHIY33noUmMOyFwAJz0xQyrYIXU+bae8MNos4TGsTo491YWAF2uzr6iW+Bq0N530xDcbe7EyRvDHgK43RmmVw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
- '@tauri-apps/cli-linux-arm-gnueabihf@2.8.4':
- resolution: {integrity: sha512-Ml215UnDdl7/fpOrF1CNovym/KjtUbCuPgrcZ4IhqUCnhZdXuphud/JT3E8X97Y03TZ40Sjz8raXYI2ET0exzw==}
+ '@tauri-apps/cli-linux-arm-gnueabihf@2.9.2':
+ resolution: {integrity: sha512-Dq17LBdSuzf+fWOKMIyiSao+Fcq4FiQwYYlx3Nk8oafDINc8sVBjC5gv2xp18KzYhk9teSWfmDpD1sj+D3t7uw==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
- '@tauri-apps/cli-linux-arm64-gnu@2.8.4':
- resolution: {integrity: sha512-pbcgBpMyI90C83CxE5REZ9ODyIlmmAPkkJXtV398X3SgZEIYy5TACYqlyyv2z5yKgD8F8WH4/2fek7+jH+ZXAw==}
+ '@tauri-apps/cli-linux-arm64-gnu@2.9.2':
+ resolution: {integrity: sha512-Pxj5k29Rxj9xEht4gdE744t5HLXTwBojkjYDXXyJ3mE+BEg9hFX5WkStg7OkyZwH60u8NSkDSMpo7MJTH9srmA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
- '@tauri-apps/cli-linux-arm64-musl@2.8.4':
- resolution: {integrity: sha512-zumFeaU1Ws5Ay872FTyIm7z8kfzEHu8NcIn8M6TxbJs0a7GRV21KBdpW1zNj2qy7HynnpQCqjAYXTUUmm9JAOw==}
+ '@tauri-apps/cli-linux-arm64-musl@2.9.2':
+ resolution: {integrity: sha512-mx82BuD4q3Yj5Zw+LXveZgPaDCnmH2At2LosX1siK77kaD5Ap5FF+FN0V4y+3cwq+Hcrk9AhEUPbHqoNOx1R2g==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
- '@tauri-apps/cli-linux-riscv64-gnu@2.8.4':
- resolution: {integrity: sha512-qiqbB3Zz6IyO201f+1ojxLj65WYj8mixL5cOMo63nlg8CIzsP23cPYUrx1YaDPsCLszKZo7tVs14pc7BWf+/aQ==}
+ '@tauri-apps/cli-linux-riscv64-gnu@2.9.2':
+ resolution: {integrity: sha512-Ypm1nnr7k+ECC1+JfDcnxROHt6BX8t/4GplxBvdY68BDXtIcBbdhPWDos7MK+3bDmoaA0WSJbW+DUjpfSkyKgw==}
engines: {node: '>= 10'}
cpu: [riscv64]
os: [linux]
- '@tauri-apps/cli-linux-x64-gnu@2.8.4':
- resolution: {integrity: sha512-TaqaDd9Oy6k45Hotx3pOf+pkbsxLaApv4rGd9mLuRM1k6YS/aw81YrsMryYPThrxrScEIUcmNIHaHsLiU4GMkw==}
+ '@tauri-apps/cli-linux-x64-gnu@2.9.2':
+ resolution: {integrity: sha512-tg85cGIM9PWwsbQg8m3uah3SfoNapgUr4vhWtkqgeTDZOjQuQ2duTwCH4UiM7acBpbZHNzvRrxSFpv0U53TqQQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
- '@tauri-apps/cli-linux-x64-musl@2.8.4':
- resolution: {integrity: sha512-ot9STAwyezN8w+bBHZ+bqSQIJ0qPZFlz/AyscpGqB/JnJQVDFQcRDmUPFEaAtt2UUHSWzN3GoTJ5ypqLBp2WQA==}
+ '@tauri-apps/cli-linux-x64-musl@2.9.2':
+ resolution: {integrity: sha512-xW8qaz9bcwR35W2gIg7fKG9e1Z34idOsGpD2zIPgxlJyF314B/1qie50hbOqt5AbbXHR4iRpxKE4kA2grqMmkg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
- '@tauri-apps/cli-win32-arm64-msvc@2.8.4':
- resolution: {integrity: sha512-+2aJ/g90dhLiOLFSD1PbElXX3SoMdpO7HFPAZB+xot3CWlAZD1tReUFy7xe0L5GAR16ZmrxpIDM9v9gn5xRy/w==}
+ '@tauri-apps/cli-win32-arm64-msvc@2.9.2':
+ resolution: {integrity: sha512-A1PshB8oHdY7zYOPlLD7Om7/aD9sOUVREd765ElIzYDtptWcALwOP9jb22Wi01vDTqxf98E4ZGIcG2gxr4FhiA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
- '@tauri-apps/cli-win32-ia32-msvc@2.8.4':
- resolution: {integrity: sha512-yj7WDxkL1t9Uzr2gufQ1Hl7hrHuFKTNEOyascbc109EoiAqCp0tgZ2IykQqOZmZOHU884UAWI1pVMqBhS/BfhA==}
+ '@tauri-apps/cli-win32-ia32-msvc@2.9.2':
+ resolution: {integrity: sha512-AuCi0Vnc4qkXRLCC58das0u45SmXAjqcOjqF324CBKa1Z7jjNJESm0Sc2oc2G2q6f2eAbAfi34s2iJNaJU1hlQ==}
engines: {node: '>= 10'}
cpu: [ia32]
os: [win32]
- '@tauri-apps/cli-win32-x64-msvc@2.8.4':
- resolution: {integrity: sha512-XuvGB4ehBdd7QhMZ9qbj/8icGEatDuBNxyYHbLKsTYh90ggUlPa/AtaqcC1Fo69lGkTmq9BOKrs1aWSi7xDonA==}
+ '@tauri-apps/cli-win32-x64-msvc@2.9.2':
+ resolution: {integrity: sha512-kDoejyfvME/mLkR4VofQnmVPTt/smJvoXuE3xgTbUwcUQKqawM8EyQvxOHQosaJYfQphHi7G0ya8UZo3PlDZig==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
- '@tauri-apps/cli@2.8.4':
- resolution: {integrity: sha512-ejUZBzuQRcjFV+v/gdj/DcbyX/6T4unZQjMSBZwLzP/CymEjKcc2+Fc8xTORThebHDUvqoXMdsCZt8r+hyN15g==}
+ '@tauri-apps/cli@2.9.2':
+ resolution: {integrity: sha512-aGzdVgxQW6WQ7e5nydPZ/30u8HvltHjO3Ytzf1wOxX1N5Yj2TsjKWRb/AWJlB95Huml3k3c/b6s0ijAvlSo9xw==}
engines: {node: '>= 10'}
hasBin: true
- '@tauri-apps/plugin-clipboard-manager@2.3.0':
- resolution: {integrity: sha512-81NOBA2P+OTY8RLkBwyl9ZR/0CeggLub4F6zxcxUIfFOAqtky7J61+K/MkH2SC1FMxNBxrX0swDuKvkjkHadlA==}
+ '@tauri-apps/plugin-clipboard-manager@2.3.2':
+ resolution: {integrity: sha512-CUlb5Hqi2oZbcZf4VUyUH53XWPPdtpw43EUpCza5HWZJwxEoDowFzNUDt1tRUXA8Uq+XPn17Ysfptip33sG4eQ==}
- '@tauri-apps/plugin-deep-link@2.4.3':
- resolution: {integrity: sha512-yVCZpVG1ZrtfCvE7K5LRSrGqlyPlCrqlKgoREJHnfjyYdDtUhFmZqScOXpL8XL2PizJHDsoahEweuTaUPEokPA==}
+ '@tauri-apps/plugin-deep-link@2.4.5':
+ resolution: {integrity: sha512-Zf2RTj1D9IQQ45/jqW8XTKvql24HqlPjcpv0mV/O2jHQkNe11HOTZBVj6IK37qs+MWV7xZzcmazx/QVZnhAwaQ==}
- '@tauri-apps/plugin-dialog@2.4.0':
- resolution: {integrity: sha512-OvXkrEBfWwtd8tzVCEXIvRfNEX87qs2jv6SqmVPiHcJjBhSF/GUvjqUNIDmKByb5N8nvDqVUM7+g1sXwdC/S9w==}
+ '@tauri-apps/plugin-dialog@2.4.2':
+ resolution: {integrity: sha512-lNIn5CZuw8WZOn8zHzmFmDSzg5zfohWoa3mdULP0YFh/VogVdMVWZPcWSHlydsiJhRQYaTNSYKN7RmZKE2lCYQ==}
- '@tauri-apps/plugin-fs@2.4.2':
- resolution: {integrity: sha512-YGhmYuTgXGsi6AjoV+5mh2NvicgWBfVJHHheuck6oHD+HC9bVWPaHvCP0/Aw4pHDejwrvT8hE3+zZAaWf+hrig==}
+ '@tauri-apps/plugin-fs@2.4.4':
+ resolution: {integrity: sha512-MTorXxIRmOnOPT1jZ3w96vjSuScER38ryXY88vl5F0uiKdnvTKKTtaEjTEo8uPbl4e3gnUtfsDVwC7h77GQLvQ==}
- '@tauri-apps/plugin-http@2.5.2':
- resolution: {integrity: sha512-x1mQKHSLDk4mS2S938OTeyk8L7QyLpCrKZCZcjkljGsvTvRMojCvI9SeJ1kaxc7t8xSilkC7WdId8xER9TIGLg==}
+ '@tauri-apps/plugin-http@2.5.4':
+ resolution: {integrity: sha512-/i4U/9za3mrytTgfRn5RHneKubZE/dwRmshYwyMvNRlkWjvu1m4Ma72kcbVJMZFGXpkbl+qLyWMGrihtWB76Zg==}
- '@tauri-apps/plugin-log@2.7.0':
- resolution: {integrity: sha512-81XQ2f93x4vmIB5OY0XlYAxy60cHdYLs0Ki8Qp38tNATRiuBit+Orh3frpY3qfYQnqEvYVyRub7YRJWlmW2RRA==}
+ '@tauri-apps/plugin-log@2.7.1':
+ resolution: {integrity: sha512-jdb+o0wxQc8PjnLktgGpOs9Dh1YupaOGDXzO+Y8peA1UZ1ep3eXv4E1oiJ7nIQVN0XUFDDhnn3aBszl8ijhR+A==}
- '@tauri-apps/plugin-notification@2.3.1':
- resolution: {integrity: sha512-7gqgfANSREKhh35fY1L4j3TUjUdePmU735FYDqRGeIf8nMXWpcx6j4FhN9/4nYz+m0mv79DCTPLqIPTySggGgg==}
+ '@tauri-apps/plugin-notification@2.3.3':
+ resolution: {integrity: sha512-Zw+ZH18RJb41G4NrfHgIuofJiymusqN+q8fGUIIV7vyCH+5sSn5coqRv/MWB9qETsUs97vmU045q7OyseCV3Qg==}
- '@tauri-apps/plugin-opener@2.5.0':
- resolution: {integrity: sha512-B0LShOYae4CZjN8leiNDbnfjSrTwoZakqKaWpfoH6nXiJwt6Rgj6RnVIffG3DoJiKsffRhMkjmBV9VeilSb4TA==}
+ '@tauri-apps/plugin-opener@2.5.2':
+ resolution: {integrity: sha512-ei/yRRoCklWHImwpCcDK3VhNXx+QXM9793aQ64YxpqVF0BDuuIlXhZgiAkc15wnPVav+IbkYhmDJIv5R326Mew==}
- '@tauri-apps/plugin-os@2.3.1':
- resolution: {integrity: sha512-ty5V8XDUIFbSnrk3zsFoP3kzN+vAufYzalJSlmrVhQTImIZa1aL1a03bOaP2vuBvfR+WDRC6NgV2xBl8G07d+w==}
+ '@tauri-apps/plugin-os@2.3.2':
+ resolution: {integrity: sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A==}
- '@tauri-apps/plugin-process@2.3.0':
- resolution: {integrity: sha512-0DNj6u+9csODiV4seSxxRbnLpeGYdojlcctCuLOCgpH9X3+ckVZIEj6H7tRQ7zqWr7kSTEWnrxtAdBb0FbtrmQ==}
+ '@tauri-apps/plugin-process@2.3.1':
+ resolution: {integrity: sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA==}
- '@tauri-apps/plugin-window-state@2.4.0':
- resolution: {integrity: sha512-hRSzPNi2NG0lPFthfVY0V5C1MyWN/gGaQtQYw7i9zZhLzrhZveHZ2omHG1rIiIsjfTGbO7fhjydSoeTTK9GqLw==}
+ '@tauri-apps/plugin-window-state@2.4.1':
+ resolution: {integrity: sha512-OuvdrzyY8Q5Dbzpj+GcrnV1iCeoZbcFdzMjanZMMcAEUNy/6PH5pxZPXpaZLOR7whlzXiuzx0L9EKZbH7zpdRw==}
'@trysound/sax@0.2.0':
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
@@ -1295,19 +1292,19 @@ packages:
'@types/ms@2.1.0':
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
- '@types/node@24.6.2':
- resolution: {integrity: sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==}
+ '@types/node@24.9.2':
+ resolution: {integrity: sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==}
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
- '@types/react-dom@19.2.0':
- resolution: {integrity: sha512-brtBs0MnE9SMx7px208g39lRmC5uHZs96caOJfTjFcYSLHNamvaSMfJNagChVNkup2SdtOxKX1FDBkRSJe1ZAg==}
+ '@types/react-dom@19.2.2':
+ resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==}
peerDependencies:
'@types/react': ^19.2.0
- '@types/react@19.2.0':
- resolution: {integrity: sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA==}
+ '@types/react@19.2.2':
+ resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==}
'@types/unist@2.0.11':
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
@@ -1329,14 +1326,14 @@ packages:
peerDependencies:
react: '>= 16.8.0'
- '@vitejs/plugin-react-swc@4.1.0':
- resolution: {integrity: sha512-Ff690TUck0Anlh7wdIcnsVMhofeEVgm44Y4OYdeeEEPSKyZHzDI9gfVBvySEhDfXtBp8tLCbfsVKPWEMEjq8/g==}
+ '@vitejs/plugin-react-swc@4.2.0':
+ resolution: {integrity: sha512-/tesahXD1qpkGC6FzMoFOJj0RyZdw9xLELOL+6jbElwmWfwOnIVy+IfpY+o9JfD9PKaR/Eyb6DNrvbXpuvA+8Q==}
engines: {node: ^20.19.0 || >=22.12.0}
peerDependencies:
vite: ^4 || ^5 || ^6 || ^7
- '@vitejs/plugin-react@5.0.4':
- resolution: {integrity: sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==}
+ '@vitejs/plugin-react@5.1.0':
+ resolution: {integrity: sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew==}
engines: {node: ^20.19.0 || >=22.12.0}
peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
@@ -1389,8 +1386,8 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
- baseline-browser-mapping@2.8.10:
- resolution: {integrity: sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA==}
+ baseline-browser-mapping@2.8.21:
+ resolution: {integrity: sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q==}
hasBin: true
boolbase@1.0.0:
@@ -1406,8 +1403,8 @@ packages:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
- browserslist@4.26.3:
- resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==}
+ browserslist@4.27.0:
+ resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
@@ -1440,8 +1437,8 @@ packages:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
engines: {node: '>=10'}
- caniuse-lite@1.0.30001746:
- resolution: {integrity: sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==}
+ caniuse-lite@1.0.30001752:
+ resolution: {integrity: sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g==}
ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
@@ -1616,8 +1613,8 @@ packages:
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
engines: {node: '>= 0.4'}
- dayjs@1.11.18:
- resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==}
+ dayjs@1.11.19:
+ resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
@@ -1685,8 +1682,8 @@ packages:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
- electron-to-chromium@1.5.228:
- resolution: {integrity: sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==}
+ electron-to-chromium@1.5.244:
+ resolution: {integrity: sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -1726,11 +1723,11 @@ packages:
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
engines: {node: '>= 0.4'}
- es-toolkit@1.39.10:
- resolution: {integrity: sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==}
+ es-toolkit@1.41.0:
+ resolution: {integrity: sha512-bDd3oRmbVgqZCJS6WmeQieOrzpl3URcWBUVDXxOELlUW2FuW+0glPOz1n0KnRie+PdyvUZcXz2sOn00c6pPRIA==}
- esbuild@0.25.10:
- resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==}
+ esbuild@0.25.11:
+ resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==}
engines: {node: '>=18'}
hasBin: true
@@ -1784,8 +1781,8 @@ packages:
fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
- framer-motion@12.23.22:
- resolution: {integrity: sha512-ZgGvdxXCw55ZYvhoZChTlG6pUuehecgvEAJz0BHoC5pQKW1EC5xf1Mul1ej5+ai+pVY0pylyFfdl45qnM1/GsA==}
+ framer-motion@12.23.24:
+ resolution: {integrity: sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
@@ -1908,8 +1905,8 @@ packages:
html-dom-parser@5.1.1:
resolution: {integrity: sha512-+o4Y4Z0CLuyemeccvGN4bAO20aauB2N9tFEAep5x4OW34kV4PTarBHm6RL02afYt2BMKcr0D2Agep8S3nJPIBg==}
- html-react-parser@5.2.6:
- resolution: {integrity: sha512-qcpPWLaSvqXi+TndiHbCa+z8qt0tVzjMwFGFBAa41ggC+ZA5BHaMIeMJla9g3VSp4SmiZb9qyQbmbpHYpIfPOg==}
+ html-react-parser@5.2.7:
+ resolution: {integrity: sha512-WzIAcqQoZoF49J9aev8NBDLz9TJvt2RmipeYA+/5+5x0sWCwFxqKiq0lysieiSA/G6dbUZ6KGGy65Cx2fjie5Q==}
peerDependencies:
'@types/react': 0.14 || 15 || 16 || 17 || 18 || 19
react: 0.14 || 15 || 16 || 17 || 18 || 19
@@ -1923,11 +1920,11 @@ packages:
htmlparser2@10.0.0:
resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==}
- immer@10.1.3:
- resolution: {integrity: sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==}
+ immer@10.2.0:
+ resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==}
- immutable@5.1.3:
- resolution: {integrity: sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==}
+ immutable@5.1.4:
+ resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==}
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
@@ -2281,14 +2278,14 @@ packages:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
- motion-dom@12.23.21:
- resolution: {integrity: sha512-5xDXx/AbhrfgsQmSE7YESMn4Dpo6x5/DTZ4Iyy4xqDvVHWvFVoV+V2Ri2S/ksx+D40wrZ7gPYiMWshkdoqNgNQ==}
+ motion-dom@12.23.23:
+ resolution: {integrity: sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==}
motion-utils@12.23.6:
resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==}
- motion@12.23.22:
- resolution: {integrity: sha512-iSq6X9vLHbeYwmHvhK//+U74ROaPnZmBuy60XZzqNl0QtZkWfoZyMDHYnpKuWFv0sNMqHgED8aCXk94LCoQPGg==}
+ motion@12.23.24:
+ resolution: {integrity: sha512-Rc5E7oe2YZ72N//S3QXGzbnXgqNrTESv8KKxABR20q2FLch9gHLo0JLyYo2hZ238bZ9Gx6cWhj9VO0IgwbMjCw==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
@@ -2318,8 +2315,8 @@ packages:
node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
- node-releases@2.0.21:
- resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==}
+ node-releases@2.0.27:
+ resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
normalize-package-data@2.5.0:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
@@ -2468,8 +2465,8 @@ packages:
peerDependencies:
react: ^19.2.0
- react-hook-form@7.63.0:
- resolution: {integrity: sha512-ZwueDMvUeucovM2VjkCf7zIHcs1aAlDimZu2Hvel5C5907gUzMpm4xCrQXtRzCvsBqFjonB4m3x4LzCFI1ZKWA==}
+ react-hook-form@7.65.0:
+ resolution: {integrity: sha512-xtOzDz063WcXvGWaHgLNrNzlsdFgtUWcb32E6WFaGTd7kPZG3EeDusjdZfUsPwKCKVXy1ZlntifaHZ4l8pAsmw==}
engines: {node: '>=18.0.0'}
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
@@ -2517,8 +2514,8 @@ packages:
redux:
optional: true
- react-refresh@0.17.0:
- resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
+ react-refresh@0.18.0:
+ resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
engines: {node: '>=0.10.0'}
react-router-dom@6.30.1:
@@ -2560,8 +2557,8 @@ packages:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
- recharts@3.2.1:
- resolution: {integrity: sha512-0JKwHRiFZdmLq/6nmilxEZl3pqb4T+aKkOkOi/ZISRZwfBhVMgInxzlYU9D4KnCH3KINScLy68m/OvMXoYGZUw==}
+ recharts@3.3.0:
+ resolution: {integrity: sha512-Vi0qmTB0iz1+/Cz9o5B7irVyUjX2ynvEgImbgMt/3sKRREcUM07QiYjS1QpAVrkmVlXqy5gykq4nGWMz9AS4Rg==}
engines: {node: '>=18'}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -2604,13 +2601,13 @@ packages:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
- resolve@1.22.10:
- resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
+ resolve@1.22.11:
+ resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
engines: {node: '>= 0.4'}
hasBin: true
- rollup@4.52.3:
- resolution: {integrity: sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==}
+ rollup@4.52.5:
+ resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
@@ -2746,11 +2743,11 @@ packages:
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
engines: {node: '>=4'}
- style-to-js@1.1.17:
- resolution: {integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==}
+ style-to-js@1.1.18:
+ resolution: {integrity: sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg==}
- style-to-object@1.0.9:
- resolution: {integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==}
+ style-to-object@1.0.11:
+ resolution: {integrity: sha512-5A560JmXr7wDyGLK12Nq/EYS38VkGlglVzkis1JEdbGWSnbQIEhZzTJhzURXN5/8WwwFCs/f/VVcmkTppbXLow==}
stylis@4.2.0:
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
@@ -2775,8 +2772,8 @@ packages:
engines: {node: '>=14.0.0'}
hasBin: true
- tabbable@6.2.0:
- resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
+ tabbable@6.3.0:
+ resolution: {integrity: sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ==}
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
@@ -2814,8 +2811,8 @@ packages:
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
engines: {node: '>= 0.4'}
- typedoc@0.28.13:
- resolution: {integrity: sha512-dNWY8msnYB2a+7Audha+aTF1Pu3euiE7ySp53w8kEsXoYw7dMouV5A1UsTUY345aB152RHnmRMDiovuBi7BD+w==}
+ typedoc@0.28.14:
+ resolution: {integrity: sha512-ftJYPvpVfQvFzpkoSfHLkJybdA/geDJ8BGQt/ZnkkhnBYoYW6lBgPQXu6vqLxO4X75dA55hX8Af847H5KXlEFA==}
engines: {node: '>= 18', pnpm: '>= 10'}
hasBin: true
peerDependencies:
@@ -2839,14 +2836,14 @@ packages:
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'}
- undici-types@7.13.0:
- resolution: {integrity: sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==}
+ undici-types@7.16.0:
+ resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
unified@11.0.5:
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
- unist-util-is@6.0.0:
- resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
+ unist-util-is@6.0.1:
+ resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}
unist-util-position@5.0.0:
resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
@@ -2854,20 +2851,20 @@ packages:
unist-util-stringify-position@4.0.0:
resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
- unist-util-visit-parents@6.0.1:
- resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==}
+ unist-util-visit-parents@6.0.2:
+ resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==}
unist-util-visit@5.0.0:
resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
- update-browserslist-db@1.1.3:
- resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
+ update-browserslist-db@1.1.4:
+ resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==}
hasBin: true
peerDependencies:
browserslist: '>= 4.21.0'
- use-breakpoint@4.0.6:
- resolution: {integrity: sha512-1s7vUjf36eeZYTgY1KkmPNXrTbKJVRA9cjBFQdYjK8+pDr0qJgH6/cuX5qQ2zcfkqxN5LieVd/DTVK6ofnwRTQ==}
+ use-breakpoint@4.0.10:
+ resolution: {integrity: sha512-rnUpZwCQCTtexbpM8S5aiJrfIx6NTvt0WwATiH4hCBN6gQNgkYPFoFt6g/3pAuyqU9D9tLKwXfsVqEWMBnwo6A==}
peerDependencies:
react: '>=18'
react-dom: '>=18'
@@ -2899,8 +2896,8 @@ packages:
victory-vendor@37.3.6:
resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==}
- vite@7.1.8:
- resolution: {integrity: sha512-oBXvfSHEOL8jF+R9Am7h59Up07kVVGH1NrFGFoEG6bPDZP3tGpQhvkBpy5x7U6+E6wZCu9OihsWgJqDbQIm8LQ==}
+ vite@7.1.12:
+ resolution: {integrity: sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
@@ -3018,23 +3015,23 @@ snapshots:
'@babel/code-frame@7.27.1':
dependencies:
- '@babel/helper-validator-identifier': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
js-tokens: 4.0.0
picocolors: 1.1.1
- '@babel/compat-data@7.28.4': {}
+ '@babel/compat-data@7.28.5': {}
- '@babel/core@7.28.4':
+ '@babel/core@7.28.5':
dependencies:
'@babel/code-frame': 7.27.1
- '@babel/generator': 7.28.3
+ '@babel/generator': 7.28.5
'@babel/helper-compilation-targets': 7.27.2
- '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4)
+ '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5)
'@babel/helpers': 7.28.4
- '@babel/parser': 7.28.4
+ '@babel/parser': 7.28.5
'@babel/template': 7.27.2
- '@babel/traverse': 7.28.4
- '@babel/types': 7.28.4
+ '@babel/traverse': 7.28.5
+ '@babel/types': 7.28.5
'@jridgewell/remapping': 2.3.5
convert-source-map: 2.0.0
debug: 4.4.3
@@ -3044,19 +3041,19 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@babel/generator@7.28.3':
+ '@babel/generator@7.28.5':
dependencies:
- '@babel/parser': 7.28.4
- '@babel/types': 7.28.4
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
jsesc: 3.1.0
'@babel/helper-compilation-targets@7.27.2':
dependencies:
- '@babel/compat-data': 7.28.4
+ '@babel/compat-data': 7.28.5
'@babel/helper-validator-option': 7.27.1
- browserslist: 4.26.3
+ browserslist: 4.27.0
lru-cache: 5.1.1
semver: 6.3.1
@@ -3064,17 +3061,17 @@ snapshots:
'@babel/helper-module-imports@7.27.1':
dependencies:
- '@babel/traverse': 7.28.4
- '@babel/types': 7.28.4
+ '@babel/traverse': 7.28.5
+ '@babel/types': 7.28.5
transitivePeerDependencies:
- supports-color
- '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)':
+ '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)':
dependencies:
- '@babel/core': 7.28.4
+ '@babel/core': 7.28.5
'@babel/helper-module-imports': 7.27.1
- '@babel/helper-validator-identifier': 7.27.1
- '@babel/traverse': 7.28.4
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.28.5
transitivePeerDependencies:
- supports-color
@@ -3082,27 +3079,27 @@ snapshots:
'@babel/helper-string-parser@7.27.1': {}
- '@babel/helper-validator-identifier@7.27.1': {}
+ '@babel/helper-validator-identifier@7.28.5': {}
'@babel/helper-validator-option@7.27.1': {}
'@babel/helpers@7.28.4':
dependencies:
'@babel/template': 7.27.2
- '@babel/types': 7.28.4
+ '@babel/types': 7.28.5
- '@babel/parser@7.28.4':
+ '@babel/parser@7.28.5':
dependencies:
- '@babel/types': 7.28.4
+ '@babel/types': 7.28.5
- '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.4)':
+ '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)':
dependencies:
- '@babel/core': 7.28.4
+ '@babel/core': 7.28.5
'@babel/helper-plugin-utils': 7.27.1
- '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.4)':
+ '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)':
dependencies:
- '@babel/core': 7.28.4
+ '@babel/core': 7.28.5
'@babel/helper-plugin-utils': 7.27.1
'@babel/runtime@7.28.4': {}
@@ -3110,59 +3107,59 @@ snapshots:
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
- '@babel/parser': 7.28.4
- '@babel/types': 7.28.4
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
- '@babel/traverse@7.28.4':
+ '@babel/traverse@7.28.5':
dependencies:
'@babel/code-frame': 7.27.1
- '@babel/generator': 7.28.3
+ '@babel/generator': 7.28.5
'@babel/helper-globals': 7.28.0
- '@babel/parser': 7.28.4
+ '@babel/parser': 7.28.5
'@babel/template': 7.27.2
- '@babel/types': 7.28.4
+ '@babel/types': 7.28.5
debug: 4.4.3
transitivePeerDependencies:
- supports-color
- '@babel/types@7.28.4':
+ '@babel/types@7.28.5':
dependencies:
'@babel/helper-string-parser': 7.27.1
- '@babel/helper-validator-identifier': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
- '@biomejs/biome@2.2.4':
+ '@biomejs/biome@2.3.2':
optionalDependencies:
- '@biomejs/cli-darwin-arm64': 2.2.4
- '@biomejs/cli-darwin-x64': 2.2.4
- '@biomejs/cli-linux-arm64': 2.2.4
- '@biomejs/cli-linux-arm64-musl': 2.2.4
- '@biomejs/cli-linux-x64': 2.2.4
- '@biomejs/cli-linux-x64-musl': 2.2.4
- '@biomejs/cli-win32-arm64': 2.2.4
- '@biomejs/cli-win32-x64': 2.2.4
-
- '@biomejs/cli-darwin-arm64@2.2.4':
+ '@biomejs/cli-darwin-arm64': 2.3.2
+ '@biomejs/cli-darwin-x64': 2.3.2
+ '@biomejs/cli-linux-arm64': 2.3.2
+ '@biomejs/cli-linux-arm64-musl': 2.3.2
+ '@biomejs/cli-linux-x64': 2.3.2
+ '@biomejs/cli-linux-x64-musl': 2.3.2
+ '@biomejs/cli-win32-arm64': 2.3.2
+ '@biomejs/cli-win32-x64': 2.3.2
+
+ '@biomejs/cli-darwin-arm64@2.3.2':
optional: true
- '@biomejs/cli-darwin-x64@2.2.4':
+ '@biomejs/cli-darwin-x64@2.3.2':
optional: true
- '@biomejs/cli-linux-arm64-musl@2.2.4':
+ '@biomejs/cli-linux-arm64-musl@2.3.2':
optional: true
- '@biomejs/cli-linux-arm64@2.2.4':
+ '@biomejs/cli-linux-arm64@2.3.2':
optional: true
- '@biomejs/cli-linux-x64-musl@2.2.4':
+ '@biomejs/cli-linux-x64-musl@2.3.2':
optional: true
- '@biomejs/cli-linux-x64@2.2.4':
+ '@biomejs/cli-linux-x64@2.3.2':
optional: true
- '@biomejs/cli-win32-arm64@2.2.4':
+ '@biomejs/cli-win32-arm64@2.3.2':
optional: true
- '@biomejs/cli-win32-x64@2.2.4':
+ '@biomejs/cli-win32-x64@2.3.2':
optional: true
'@emotion/babel-plugin@11.13.5':
@@ -3197,7 +3194,7 @@ snapshots:
'@emotion/memoize@0.9.0': {}
- '@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0)':
+ '@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0)':
dependencies:
'@babel/runtime': 7.28.4
'@emotion/babel-plugin': 11.13.5
@@ -3209,7 +3206,7 @@ snapshots:
hoist-non-react-statics: 3.3.2
react: 19.2.0
optionalDependencies:
- '@types/react': 19.2.0
+ '@types/react': 19.2.2
transitivePeerDependencies:
- supports-color
@@ -3223,18 +3220,18 @@ snapshots:
'@emotion/sheet@1.4.0': {}
- '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0)':
+ '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0)':
dependencies:
'@babel/runtime': 7.28.4
'@emotion/babel-plugin': 11.13.5
'@emotion/is-prop-valid': 1.4.0
- '@emotion/react': 11.14.0(@types/react@19.2.0)(react@19.2.0)
+ '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0)
'@emotion/serialize': 1.3.3
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.0)
'@emotion/utils': 1.4.2
react: 19.2.0
optionalDependencies:
- '@types/react': 19.2.0
+ '@types/react': 19.2.2
transitivePeerDependencies:
- supports-color
@@ -3248,82 +3245,82 @@ snapshots:
'@emotion/weak-memoize@0.4.0': {}
- '@esbuild/aix-ppc64@0.25.10':
+ '@esbuild/aix-ppc64@0.25.11':
optional: true
- '@esbuild/android-arm64@0.25.10':
+ '@esbuild/android-arm64@0.25.11':
optional: true
- '@esbuild/android-arm@0.25.10':
+ '@esbuild/android-arm@0.25.11':
optional: true
- '@esbuild/android-x64@0.25.10':
+ '@esbuild/android-x64@0.25.11':
optional: true
- '@esbuild/darwin-arm64@0.25.10':
+ '@esbuild/darwin-arm64@0.25.11':
optional: true
- '@esbuild/darwin-x64@0.25.10':
+ '@esbuild/darwin-x64@0.25.11':
optional: true
- '@esbuild/freebsd-arm64@0.25.10':
+ '@esbuild/freebsd-arm64@0.25.11':
optional: true
- '@esbuild/freebsd-x64@0.25.10':
+ '@esbuild/freebsd-x64@0.25.11':
optional: true
- '@esbuild/linux-arm64@0.25.10':
+ '@esbuild/linux-arm64@0.25.11':
optional: true
- '@esbuild/linux-arm@0.25.10':
+ '@esbuild/linux-arm@0.25.11':
optional: true
- '@esbuild/linux-ia32@0.25.10':
+ '@esbuild/linux-ia32@0.25.11':
optional: true
- '@esbuild/linux-loong64@0.25.10':
+ '@esbuild/linux-loong64@0.25.11':
optional: true
- '@esbuild/linux-mips64el@0.25.10':
+ '@esbuild/linux-mips64el@0.25.11':
optional: true
- '@esbuild/linux-ppc64@0.25.10':
+ '@esbuild/linux-ppc64@0.25.11':
optional: true
- '@esbuild/linux-riscv64@0.25.10':
+ '@esbuild/linux-riscv64@0.25.11':
optional: true
- '@esbuild/linux-s390x@0.25.10':
+ '@esbuild/linux-s390x@0.25.11':
optional: true
- '@esbuild/linux-x64@0.25.10':
+ '@esbuild/linux-x64@0.25.11':
optional: true
- '@esbuild/netbsd-arm64@0.25.10':
+ '@esbuild/netbsd-arm64@0.25.11':
optional: true
- '@esbuild/netbsd-x64@0.25.10':
+ '@esbuild/netbsd-x64@0.25.11':
optional: true
- '@esbuild/openbsd-arm64@0.25.10':
+ '@esbuild/openbsd-arm64@0.25.11':
optional: true
- '@esbuild/openbsd-x64@0.25.10':
+ '@esbuild/openbsd-x64@0.25.11':
optional: true
- '@esbuild/openharmony-arm64@0.25.10':
+ '@esbuild/openharmony-arm64@0.25.11':
optional: true
- '@esbuild/sunos-x64@0.25.10':
+ '@esbuild/sunos-x64@0.25.11':
optional: true
- '@esbuild/win32-arm64@0.25.10':
+ '@esbuild/win32-arm64@0.25.11':
optional: true
- '@esbuild/win32-ia32@0.25.10':
+ '@esbuild/win32-ia32@0.25.11':
optional: true
- '@esbuild/win32-x64@0.25.10':
+ '@esbuild/win32-x64@0.25.11':
optional: true
'@floating-ui/core@1.7.3':
@@ -3347,22 +3344,22 @@ snapshots:
'@floating-ui/utils': 0.2.10
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
- tabbable: 6.2.0
+ tabbable: 6.3.0
'@floating-ui/utils@0.2.10': {}
- '@gerrit0/mini-shiki@3.13.0':
+ '@gerrit0/mini-shiki@3.14.0':
dependencies:
- '@shikijs/engine-oniguruma': 3.13.0
- '@shikijs/langs': 3.13.0
- '@shikijs/themes': 3.13.0
- '@shikijs/types': 3.13.0
+ '@shikijs/engine-oniguruma': 3.14.0
+ '@shikijs/langs': 3.14.0
+ '@shikijs/themes': 3.14.0
+ '@shikijs/types': 3.14.0
'@shikijs/vscode-textmate': 10.0.2
- '@hookform/devtools@4.4.0(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ '@hookform/devtools@4.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
- '@emotion/react': 11.14.0(@types/react@19.2.0)(react@19.2.0)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react@19.2.0)
+ '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0)
'@types/lodash': 4.17.20
little-state-machine: 4.8.1(react@19.2.0)
lodash: 4.17.21
@@ -3375,9 +3372,9 @@ snapshots:
- '@types/react'
- supports-color
- '@hookform/resolvers@3.10.0(react-hook-form@7.63.0(react@19.2.0))':
+ '@hookform/resolvers@3.10.0(react-hook-form@7.65.0(react@19.2.0))':
dependencies:
- react-hook-form: 7.63.0(react@19.2.0)
+ react-hook-form: 7.65.0(react@19.2.0)
'@jridgewell/gen-mapping@0.3.13':
dependencies:
@@ -3473,104 +3470,102 @@ snapshots:
'@react-hook/passive-layout-effect': 1.2.1(react@19.2.0)
react: 19.2.0
- '@reduxjs/toolkit@2.9.0(react-redux@9.2.0(@types/react@19.2.0)(react@19.2.0)(redux@5.0.1))(react@19.2.0)':
+ '@reduxjs/toolkit@2.9.2(react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1))(react@19.2.0)':
dependencies:
'@standard-schema/spec': 1.0.0
'@standard-schema/utils': 0.3.0
- immer: 10.1.3
+ immer: 10.2.0
redux: 5.0.1
redux-thunk: 3.1.0(redux@5.0.1)
reselect: 5.1.1
optionalDependencies:
react: 19.2.0
- react-redux: 9.2.0(@types/react@19.2.0)(react@19.2.0)(redux@5.0.1)
+ react-redux: 9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1)
'@remix-run/router@1.23.0': {}
- '@rolldown/pluginutils@1.0.0-beta.35': {}
-
- '@rolldown/pluginutils@1.0.0-beta.38': {}
+ '@rolldown/pluginutils@1.0.0-beta.43': {}
- '@rollup/rollup-android-arm-eabi@4.52.3':
+ '@rollup/rollup-android-arm-eabi@4.52.5':
optional: true
- '@rollup/rollup-android-arm64@4.52.3':
+ '@rollup/rollup-android-arm64@4.52.5':
optional: true
- '@rollup/rollup-darwin-arm64@4.52.3':
+ '@rollup/rollup-darwin-arm64@4.52.5':
optional: true
- '@rollup/rollup-darwin-x64@4.52.3':
+ '@rollup/rollup-darwin-x64@4.52.5':
optional: true
- '@rollup/rollup-freebsd-arm64@4.52.3':
+ '@rollup/rollup-freebsd-arm64@4.52.5':
optional: true
- '@rollup/rollup-freebsd-x64@4.52.3':
+ '@rollup/rollup-freebsd-x64@4.52.5':
optional: true
- '@rollup/rollup-linux-arm-gnueabihf@4.52.3':
+ '@rollup/rollup-linux-arm-gnueabihf@4.52.5':
optional: true
- '@rollup/rollup-linux-arm-musleabihf@4.52.3':
+ '@rollup/rollup-linux-arm-musleabihf@4.52.5':
optional: true
- '@rollup/rollup-linux-arm64-gnu@4.52.3':
+ '@rollup/rollup-linux-arm64-gnu@4.52.5':
optional: true
- '@rollup/rollup-linux-arm64-musl@4.52.3':
+ '@rollup/rollup-linux-arm64-musl@4.52.5':
optional: true
- '@rollup/rollup-linux-loong64-gnu@4.52.3':
+ '@rollup/rollup-linux-loong64-gnu@4.52.5':
optional: true
- '@rollup/rollup-linux-ppc64-gnu@4.52.3':
+ '@rollup/rollup-linux-ppc64-gnu@4.52.5':
optional: true
- '@rollup/rollup-linux-riscv64-gnu@4.52.3':
+ '@rollup/rollup-linux-riscv64-gnu@4.52.5':
optional: true
- '@rollup/rollup-linux-riscv64-musl@4.52.3':
+ '@rollup/rollup-linux-riscv64-musl@4.52.5':
optional: true
- '@rollup/rollup-linux-s390x-gnu@4.52.3':
+ '@rollup/rollup-linux-s390x-gnu@4.52.5':
optional: true
- '@rollup/rollup-linux-x64-gnu@4.52.3':
+ '@rollup/rollup-linux-x64-gnu@4.52.5':
optional: true
- '@rollup/rollup-linux-x64-musl@4.52.3':
+ '@rollup/rollup-linux-x64-musl@4.52.5':
optional: true
- '@rollup/rollup-openharmony-arm64@4.52.3':
+ '@rollup/rollup-openharmony-arm64@4.52.5':
optional: true
- '@rollup/rollup-win32-arm64-msvc@4.52.3':
+ '@rollup/rollup-win32-arm64-msvc@4.52.5':
optional: true
- '@rollup/rollup-win32-ia32-msvc@4.52.3':
+ '@rollup/rollup-win32-ia32-msvc@4.52.5':
optional: true
- '@rollup/rollup-win32-x64-gnu@4.52.3':
+ '@rollup/rollup-win32-x64-gnu@4.52.5':
optional: true
- '@rollup/rollup-win32-x64-msvc@4.52.3':
+ '@rollup/rollup-win32-x64-msvc@4.52.5':
optional: true
- '@shikijs/engine-oniguruma@3.13.0':
+ '@shikijs/engine-oniguruma@3.14.0':
dependencies:
- '@shikijs/types': 3.13.0
+ '@shikijs/types': 3.14.0
'@shikijs/vscode-textmate': 10.0.2
- '@shikijs/langs@3.13.0':
+ '@shikijs/langs@3.14.0':
dependencies:
- '@shikijs/types': 3.13.0
+ '@shikijs/types': 3.14.0
- '@shikijs/themes@3.13.0':
+ '@shikijs/themes@3.14.0':
dependencies:
- '@shikijs/types': 3.13.0
+ '@shikijs/types': 3.14.0
- '@shikijs/types@3.13.0':
+ '@shikijs/types@3.14.0':
dependencies:
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
@@ -3608,49 +3603,49 @@ snapshots:
'@standard-schema/utils@0.3.0': {}
- '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.4)':
+ '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.5)':
dependencies:
- '@babel/core': 7.28.4
+ '@babel/core': 7.28.5
- '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.28.4)':
+ '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.28.5)':
dependencies:
- '@babel/core': 7.28.4
+ '@babel/core': 7.28.5
- '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.28.4)':
+ '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.28.5)':
dependencies:
- '@babel/core': 7.28.4
+ '@babel/core': 7.28.5
- '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.28.4)':
+ '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.28.5)':
dependencies:
- '@babel/core': 7.28.4
+ '@babel/core': 7.28.5
- '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.28.4)':
+ '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.28.5)':
dependencies:
- '@babel/core': 7.28.4
+ '@babel/core': 7.28.5
- '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.28.4)':
+ '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.28.5)':
dependencies:
- '@babel/core': 7.28.4
+ '@babel/core': 7.28.5
- '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.28.4)':
+ '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.28.5)':
dependencies:
- '@babel/core': 7.28.4
+ '@babel/core': 7.28.5
- '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.28.4)':
+ '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.28.5)':
dependencies:
- '@babel/core': 7.28.4
+ '@babel/core': 7.28.5
- '@svgr/babel-preset@8.1.0(@babel/core@7.28.4)':
+ '@svgr/babel-preset@8.1.0(@babel/core@7.28.5)':
dependencies:
- '@babel/core': 7.28.4
- '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.28.4)
- '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.28.4)
- '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.28.4)
- '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.28.4)
- '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.28.4)
- '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.28.4)
- '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.28.4)
- '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.28.4)
+ '@babel/core': 7.28.5
+ '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.28.5)
+ '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.28.5)
+ '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.28.5)
+ '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.28.5)
+ '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.28.5)
+ '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.28.5)
+ '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.28.5)
+ '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.28.5)
'@svgr/cli@8.1.0(typescript@5.9.3)':
dependencies:
@@ -3670,8 +3665,8 @@ snapshots:
'@svgr/core@8.1.0(typescript@5.9.3)':
dependencies:
- '@babel/core': 7.28.4
- '@svgr/babel-preset': 8.1.0(@babel/core@7.28.4)
+ '@babel/core': 7.28.5
+ '@svgr/babel-preset': 8.1.0(@babel/core@7.28.5)
camelcase: 6.3.0
cosmiconfig: 8.3.6(typescript@5.9.3)
snake-case: 3.0.4
@@ -3681,13 +3676,13 @@ snapshots:
'@svgr/hast-util-to-babel-ast@8.0.0':
dependencies:
- '@babel/types': 7.28.4
+ '@babel/types': 7.28.5
entities: 4.5.0
'@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))':
dependencies:
- '@babel/core': 7.28.4
- '@svgr/babel-preset': 8.1.0(@babel/core@7.28.4)
+ '@babel/core': 7.28.5
+ '@svgr/babel-preset': 8.1.0(@babel/core@7.28.5)
'@svgr/core': 8.1.0(typescript@5.9.3)
'@svgr/hast-util-to-babel-ast': 8.0.0
svg-parser: 2.0.4
@@ -3709,51 +3704,51 @@ snapshots:
transitivePeerDependencies:
- typescript
- '@swc/core-darwin-arm64@1.13.5':
+ '@swc/core-darwin-arm64@1.14.0':
optional: true
- '@swc/core-darwin-x64@1.13.5':
+ '@swc/core-darwin-x64@1.14.0':
optional: true
- '@swc/core-linux-arm-gnueabihf@1.13.5':
+ '@swc/core-linux-arm-gnueabihf@1.14.0':
optional: true
- '@swc/core-linux-arm64-gnu@1.13.5':
+ '@swc/core-linux-arm64-gnu@1.14.0':
optional: true
- '@swc/core-linux-arm64-musl@1.13.5':
+ '@swc/core-linux-arm64-musl@1.14.0':
optional: true
- '@swc/core-linux-x64-gnu@1.13.5':
+ '@swc/core-linux-x64-gnu@1.14.0':
optional: true
- '@swc/core-linux-x64-musl@1.13.5':
+ '@swc/core-linux-x64-musl@1.14.0':
optional: true
- '@swc/core-win32-arm64-msvc@1.13.5':
+ '@swc/core-win32-arm64-msvc@1.14.0':
optional: true
- '@swc/core-win32-ia32-msvc@1.13.5':
+ '@swc/core-win32-ia32-msvc@1.14.0':
optional: true
- '@swc/core-win32-x64-msvc@1.13.5':
+ '@swc/core-win32-x64-msvc@1.14.0':
optional: true
- '@swc/core@1.13.5':
+ '@swc/core@1.14.0':
dependencies:
'@swc/counter': 0.1.3
'@swc/types': 0.1.25
optionalDependencies:
- '@swc/core-darwin-arm64': 1.13.5
- '@swc/core-darwin-x64': 1.13.5
- '@swc/core-linux-arm-gnueabihf': 1.13.5
- '@swc/core-linux-arm64-gnu': 1.13.5
- '@swc/core-linux-arm64-musl': 1.13.5
- '@swc/core-linux-x64-gnu': 1.13.5
- '@swc/core-linux-x64-musl': 1.13.5
- '@swc/core-win32-arm64-msvc': 1.13.5
- '@swc/core-win32-ia32-msvc': 1.13.5
- '@swc/core-win32-x64-msvc': 1.13.5
+ '@swc/core-darwin-arm64': 1.14.0
+ '@swc/core-darwin-x64': 1.14.0
+ '@swc/core-linux-arm-gnueabihf': 1.14.0
+ '@swc/core-linux-arm64-gnu': 1.14.0
+ '@swc/core-linux-arm64-musl': 1.14.0
+ '@swc/core-linux-x64-gnu': 1.14.0
+ '@swc/core-linux-x64-musl': 1.14.0
+ '@swc/core-win32-arm64-msvc': 1.14.0
+ '@swc/core-win32-ia32-msvc': 1.14.0
+ '@swc/core-win32-x64-msvc': 1.14.0
'@swc/counter@0.1.3': {}
@@ -3761,19 +3756,19 @@ snapshots:
dependencies:
'@swc/counter': 0.1.3
- '@tanstack/query-core@5.90.2': {}
+ '@tanstack/query-core@5.90.5': {}
'@tanstack/query-devtools@5.90.1': {}
- '@tanstack/react-query-devtools@5.90.2(@tanstack/react-query@5.90.2(react@19.2.0))(react@19.2.0)':
+ '@tanstack/react-query-devtools@5.90.2(@tanstack/react-query@5.90.5(react@19.2.0))(react@19.2.0)':
dependencies:
'@tanstack/query-devtools': 5.90.1
- '@tanstack/react-query': 5.90.2(react@19.2.0)
+ '@tanstack/react-query': 5.90.5(react@19.2.0)
react: 19.2.0
- '@tanstack/react-query@5.90.2(react@19.2.0)':
+ '@tanstack/react-query@5.90.5(react@19.2.0)':
dependencies:
- '@tanstack/query-core': 5.90.2
+ '@tanstack/query-core': 5.90.5
react: 19.2.0
'@tanstack/react-virtual@3.13.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
@@ -3784,121 +3779,121 @@ snapshots:
'@tanstack/virtual-core@3.13.12': {}
- '@tauri-apps/api@2.8.0': {}
+ '@tauri-apps/api@2.9.0': {}
- '@tauri-apps/cli-darwin-arm64@2.8.4':
+ '@tauri-apps/cli-darwin-arm64@2.9.2':
optional: true
- '@tauri-apps/cli-darwin-x64@2.8.4':
+ '@tauri-apps/cli-darwin-x64@2.9.2':
optional: true
- '@tauri-apps/cli-linux-arm-gnueabihf@2.8.4':
+ '@tauri-apps/cli-linux-arm-gnueabihf@2.9.2':
optional: true
- '@tauri-apps/cli-linux-arm64-gnu@2.8.4':
+ '@tauri-apps/cli-linux-arm64-gnu@2.9.2':
optional: true
- '@tauri-apps/cli-linux-arm64-musl@2.8.4':
+ '@tauri-apps/cli-linux-arm64-musl@2.9.2':
optional: true
- '@tauri-apps/cli-linux-riscv64-gnu@2.8.4':
+ '@tauri-apps/cli-linux-riscv64-gnu@2.9.2':
optional: true
- '@tauri-apps/cli-linux-x64-gnu@2.8.4':
+ '@tauri-apps/cli-linux-x64-gnu@2.9.2':
optional: true
- '@tauri-apps/cli-linux-x64-musl@2.8.4':
+ '@tauri-apps/cli-linux-x64-musl@2.9.2':
optional: true
- '@tauri-apps/cli-win32-arm64-msvc@2.8.4':
+ '@tauri-apps/cli-win32-arm64-msvc@2.9.2':
optional: true
- '@tauri-apps/cli-win32-ia32-msvc@2.8.4':
+ '@tauri-apps/cli-win32-ia32-msvc@2.9.2':
optional: true
- '@tauri-apps/cli-win32-x64-msvc@2.8.4':
+ '@tauri-apps/cli-win32-x64-msvc@2.9.2':
optional: true
- '@tauri-apps/cli@2.8.4':
+ '@tauri-apps/cli@2.9.2':
optionalDependencies:
- '@tauri-apps/cli-darwin-arm64': 2.8.4
- '@tauri-apps/cli-darwin-x64': 2.8.4
- '@tauri-apps/cli-linux-arm-gnueabihf': 2.8.4
- '@tauri-apps/cli-linux-arm64-gnu': 2.8.4
- '@tauri-apps/cli-linux-arm64-musl': 2.8.4
- '@tauri-apps/cli-linux-riscv64-gnu': 2.8.4
- '@tauri-apps/cli-linux-x64-gnu': 2.8.4
- '@tauri-apps/cli-linux-x64-musl': 2.8.4
- '@tauri-apps/cli-win32-arm64-msvc': 2.8.4
- '@tauri-apps/cli-win32-ia32-msvc': 2.8.4
- '@tauri-apps/cli-win32-x64-msvc': 2.8.4
+ '@tauri-apps/cli-darwin-arm64': 2.9.2
+ '@tauri-apps/cli-darwin-x64': 2.9.2
+ '@tauri-apps/cli-linux-arm-gnueabihf': 2.9.2
+ '@tauri-apps/cli-linux-arm64-gnu': 2.9.2
+ '@tauri-apps/cli-linux-arm64-musl': 2.9.2
+ '@tauri-apps/cli-linux-riscv64-gnu': 2.9.2
+ '@tauri-apps/cli-linux-x64-gnu': 2.9.2
+ '@tauri-apps/cli-linux-x64-musl': 2.9.2
+ '@tauri-apps/cli-win32-arm64-msvc': 2.9.2
+ '@tauri-apps/cli-win32-ia32-msvc': 2.9.2
+ '@tauri-apps/cli-win32-x64-msvc': 2.9.2
- '@tauri-apps/plugin-clipboard-manager@2.3.0':
+ '@tauri-apps/plugin-clipboard-manager@2.3.2':
dependencies:
- '@tauri-apps/api': 2.8.0
+ '@tauri-apps/api': 2.9.0
- '@tauri-apps/plugin-deep-link@2.4.3':
+ '@tauri-apps/plugin-deep-link@2.4.5':
dependencies:
- '@tauri-apps/api': 2.8.0
+ '@tauri-apps/api': 2.9.0
- '@tauri-apps/plugin-dialog@2.4.0':
+ '@tauri-apps/plugin-dialog@2.4.2':
dependencies:
- '@tauri-apps/api': 2.8.0
+ '@tauri-apps/api': 2.9.0
- '@tauri-apps/plugin-fs@2.4.2':
+ '@tauri-apps/plugin-fs@2.4.4':
dependencies:
- '@tauri-apps/api': 2.8.0
+ '@tauri-apps/api': 2.9.0
- '@tauri-apps/plugin-http@2.5.2':
+ '@tauri-apps/plugin-http@2.5.4':
dependencies:
- '@tauri-apps/api': 2.8.0
+ '@tauri-apps/api': 2.9.0
- '@tauri-apps/plugin-log@2.7.0':
+ '@tauri-apps/plugin-log@2.7.1':
dependencies:
- '@tauri-apps/api': 2.8.0
+ '@tauri-apps/api': 2.9.0
- '@tauri-apps/plugin-notification@2.3.1':
+ '@tauri-apps/plugin-notification@2.3.3':
dependencies:
- '@tauri-apps/api': 2.8.0
+ '@tauri-apps/api': 2.9.0
- '@tauri-apps/plugin-opener@2.5.0':
+ '@tauri-apps/plugin-opener@2.5.2':
dependencies:
- '@tauri-apps/api': 2.8.0
+ '@tauri-apps/api': 2.9.0
- '@tauri-apps/plugin-os@2.3.1':
+ '@tauri-apps/plugin-os@2.3.2':
dependencies:
- '@tauri-apps/api': 2.8.0
+ '@tauri-apps/api': 2.9.0
- '@tauri-apps/plugin-process@2.3.0':
+ '@tauri-apps/plugin-process@2.3.1':
dependencies:
- '@tauri-apps/api': 2.8.0
+ '@tauri-apps/api': 2.9.0
- '@tauri-apps/plugin-window-state@2.4.0':
+ '@tauri-apps/plugin-window-state@2.4.1':
dependencies:
- '@tauri-apps/api': 2.8.0
+ '@tauri-apps/api': 2.9.0
'@trysound/sax@0.2.0': {}
'@types/babel__core@7.20.5':
dependencies:
- '@babel/parser': 7.28.4
- '@babel/types': 7.28.4
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
'@types/babel__generator': 7.27.0
'@types/babel__template': 7.4.4
'@types/babel__traverse': 7.28.0
'@types/babel__generator@7.27.0':
dependencies:
- '@babel/types': 7.28.4
+ '@babel/types': 7.28.5
'@types/babel__template@7.4.4':
dependencies:
- '@babel/parser': 7.28.4
- '@babel/types': 7.28.4
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
'@types/babel__traverse@7.28.0':
dependencies:
- '@babel/types': 7.28.4
+ '@babel/types': 7.28.5
'@types/byte-size@8.1.2': {}
@@ -3954,17 +3949,17 @@ snapshots:
'@types/ms@2.1.0': {}
- '@types/node@24.6.2':
+ '@types/node@24.9.2':
dependencies:
- undici-types: 7.13.0
+ undici-types: 7.16.0
'@types/parse-json@4.0.2': {}
- '@types/react-dom@19.2.0(@types/react@19.2.0)':
+ '@types/react-dom@19.2.2(@types/react@19.2.2)':
dependencies:
- '@types/react': 19.2.0
+ '@types/react': 19.2.2
- '@types/react@19.2.0':
+ '@types/react@19.2.2':
dependencies:
csstype: 3.1.3
@@ -3983,23 +3978,23 @@ snapshots:
'@use-gesture/core': 10.3.1
react: 19.2.0
- '@vitejs/plugin-react-swc@4.1.0(vite@7.1.8(@types/node@24.6.2)(sass@1.92.1)(yaml@2.8.1))':
+ '@vitejs/plugin-react-swc@4.2.0(vite@7.1.12(@types/node@24.9.2)(sass@1.92.1)(yaml@2.8.1))':
dependencies:
- '@rolldown/pluginutils': 1.0.0-beta.35
- '@swc/core': 1.13.5
- vite: 7.1.8(@types/node@24.6.2)(sass@1.92.1)(yaml@2.8.1)
+ '@rolldown/pluginutils': 1.0.0-beta.43
+ '@swc/core': 1.14.0
+ vite: 7.1.12(@types/node@24.9.2)(sass@1.92.1)(yaml@2.8.1)
transitivePeerDependencies:
- '@swc/helpers'
- '@vitejs/plugin-react@5.0.4(vite@7.1.8(@types/node@24.6.2)(sass@1.92.1)(yaml@2.8.1))':
+ '@vitejs/plugin-react@5.1.0(vite@7.1.12(@types/node@24.9.2)(sass@1.92.1)(yaml@2.8.1))':
dependencies:
- '@babel/core': 7.28.4
- '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4)
- '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4)
- '@rolldown/pluginutils': 1.0.0-beta.38
+ '@babel/core': 7.28.5
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5)
+ '@rolldown/pluginutils': 1.0.0-beta.43
'@types/babel__core': 7.20.5
- react-refresh: 0.17.0
- vite: 7.1.8(@types/node@24.6.2)(sass@1.92.1)(yaml@2.8.1)
+ react-refresh: 0.18.0
+ vite: 7.1.12(@types/node@24.9.2)(sass@1.92.1)(yaml@2.8.1)
transitivePeerDependencies:
- supports-color
@@ -4034,8 +4029,8 @@ snapshots:
autoprefixer@10.4.21(postcss@8.5.6):
dependencies:
- browserslist: 4.26.3
- caniuse-lite: 1.0.30001746
+ browserslist: 4.27.0
+ caniuse-lite: 1.0.30001752
fraction.js: 4.3.7
normalize-range: 0.1.2
picocolors: 1.1.1
@@ -4050,13 +4045,13 @@ snapshots:
dependencies:
'@babel/runtime': 7.28.4
cosmiconfig: 7.1.0
- resolve: 1.22.10
+ resolve: 1.22.11
bail@2.0.2: {}
balanced-match@1.0.2: {}
- baseline-browser-mapping@2.8.10: {}
+ baseline-browser-mapping@2.8.21: {}
boolbase@1.0.0: {}
@@ -4074,13 +4069,13 @@ snapshots:
fill-range: 7.1.1
optional: true
- browserslist@4.26.3:
+ browserslist@4.27.0:
dependencies:
- baseline-browser-mapping: 2.8.10
- caniuse-lite: 1.0.30001746
- electron-to-chromium: 1.5.228
- node-releases: 2.0.21
- update-browserslist-db: 1.1.3(browserslist@4.26.3)
+ baseline-browser-mapping: 2.8.21
+ caniuse-lite: 1.0.30001752
+ electron-to-chromium: 1.5.244
+ node-releases: 2.0.27
+ update-browserslist-db: 1.1.4(browserslist@4.27.0)
byte-size@9.0.1: {}
@@ -4105,7 +4100,7 @@ snapshots:
camelcase@6.3.0: {}
- caniuse-lite@1.0.30001746: {}
+ caniuse-lite@1.0.30001752: {}
ccount@2.0.1: {}
@@ -4277,7 +4272,7 @@ snapshots:
es-errors: 1.3.0
is-data-view: 1.0.2
- dayjs@1.11.18: {}
+ dayjs@1.11.19: {}
debug@4.4.3:
dependencies:
@@ -4345,7 +4340,7 @@ snapshots:
es-errors: 1.3.0
gopd: 1.2.0
- electron-to-chromium@1.5.228: {}
+ electron-to-chromium@1.5.244: {}
emoji-regex@8.0.0: {}
@@ -4435,36 +4430,36 @@ snapshots:
is-date-object: 1.1.0
is-symbol: 1.1.1
- es-toolkit@1.39.10: {}
+ es-toolkit@1.41.0: {}
- esbuild@0.25.10:
+ esbuild@0.25.11:
optionalDependencies:
- '@esbuild/aix-ppc64': 0.25.10
- '@esbuild/android-arm': 0.25.10
- '@esbuild/android-arm64': 0.25.10
- '@esbuild/android-x64': 0.25.10
- '@esbuild/darwin-arm64': 0.25.10
- '@esbuild/darwin-x64': 0.25.10
- '@esbuild/freebsd-arm64': 0.25.10
- '@esbuild/freebsd-x64': 0.25.10
- '@esbuild/linux-arm': 0.25.10
- '@esbuild/linux-arm64': 0.25.10
- '@esbuild/linux-ia32': 0.25.10
- '@esbuild/linux-loong64': 0.25.10
- '@esbuild/linux-mips64el': 0.25.10
- '@esbuild/linux-ppc64': 0.25.10
- '@esbuild/linux-riscv64': 0.25.10
- '@esbuild/linux-s390x': 0.25.10
- '@esbuild/linux-x64': 0.25.10
- '@esbuild/netbsd-arm64': 0.25.10
- '@esbuild/netbsd-x64': 0.25.10
- '@esbuild/openbsd-arm64': 0.25.10
- '@esbuild/openbsd-x64': 0.25.10
- '@esbuild/openharmony-arm64': 0.25.10
- '@esbuild/sunos-x64': 0.25.10
- '@esbuild/win32-arm64': 0.25.10
- '@esbuild/win32-ia32': 0.25.10
- '@esbuild/win32-x64': 0.25.10
+ '@esbuild/aix-ppc64': 0.25.11
+ '@esbuild/android-arm': 0.25.11
+ '@esbuild/android-arm64': 0.25.11
+ '@esbuild/android-x64': 0.25.11
+ '@esbuild/darwin-arm64': 0.25.11
+ '@esbuild/darwin-x64': 0.25.11
+ '@esbuild/freebsd-arm64': 0.25.11
+ '@esbuild/freebsd-x64': 0.25.11
+ '@esbuild/linux-arm': 0.25.11
+ '@esbuild/linux-arm64': 0.25.11
+ '@esbuild/linux-ia32': 0.25.11
+ '@esbuild/linux-loong64': 0.25.11
+ '@esbuild/linux-mips64el': 0.25.11
+ '@esbuild/linux-ppc64': 0.25.11
+ '@esbuild/linux-riscv64': 0.25.11
+ '@esbuild/linux-s390x': 0.25.11
+ '@esbuild/linux-x64': 0.25.11
+ '@esbuild/netbsd-arm64': 0.25.11
+ '@esbuild/netbsd-x64': 0.25.11
+ '@esbuild/openbsd-arm64': 0.25.11
+ '@esbuild/openbsd-x64': 0.25.11
+ '@esbuild/openharmony-arm64': 0.25.11
+ '@esbuild/sunos-x64': 0.25.11
+ '@esbuild/win32-arm64': 0.25.11
+ '@esbuild/win32-ia32': 0.25.11
+ '@esbuild/win32-x64': 0.25.11
escalade@3.2.0: {}
@@ -4499,9 +4494,9 @@ snapshots:
fraction.js@4.3.7: {}
- framer-motion@12.23.22(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ framer-motion@12.23.24(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
- motion-dom: 12.23.21
+ motion-dom: 12.23.23
motion-utils: 12.23.6
tslib: 2.8.1
optionalDependencies:
@@ -4620,7 +4615,7 @@ snapshots:
mdast-util-mdxjs-esm: 2.0.1
property-information: 7.1.0
space-separated-tokens: 2.0.2
- style-to-js: 1.1.17
+ style-to-js: 1.1.18
unist-util-position: 5.0.0
vfile-message: 4.0.3
transitivePeerDependencies:
@@ -4641,15 +4636,15 @@ snapshots:
domhandler: 5.0.3
htmlparser2: 10.0.0
- html-react-parser@5.2.6(@types/react@19.2.0)(react@19.2.0):
+ html-react-parser@5.2.7(@types/react@19.2.2)(react@19.2.0):
dependencies:
domhandler: 5.0.3
html-dom-parser: 5.1.1
react: 19.2.0
react-property: 2.0.2
- style-to-js: 1.1.17
+ style-to-js: 1.1.18
optionalDependencies:
- '@types/react': 19.2.0
+ '@types/react': 19.2.2
html-url-attributes@3.0.1: {}
@@ -4660,9 +4655,9 @@ snapshots:
domutils: 3.2.2
entities: 6.0.1
- immer@10.1.3: {}
+ immer@10.2.0: {}
- immutable@5.1.3: {}
+ immutable@5.1.4: {}
import-fresh@3.3.1:
dependencies:
@@ -4943,7 +4938,7 @@ snapshots:
mdast-util-phrasing@4.1.0:
dependencies:
'@types/mdast': 4.0.4
- unist-util-is: 6.0.0
+ unist-util-is: 6.0.1
mdast-util-to-hast@13.2.0:
dependencies:
@@ -4981,9 +4976,9 @@ snapshots:
memorystream@0.3.1: {}
- merge-refs@2.0.0(@types/react@19.2.0):
+ merge-refs@2.0.0(@types/react@19.2.2):
optionalDependencies:
- '@types/react': 19.2.0
+ '@types/react': 19.2.2
micromark-core-commonmark@2.0.3:
dependencies:
@@ -5140,15 +5135,15 @@ snapshots:
dependencies:
brace-expansion: 2.0.2
- motion-dom@12.23.21:
+ motion-dom@12.23.23:
dependencies:
motion-utils: 12.23.6
motion-utils@12.23.6: {}
- motion@12.23.22(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ motion@12.23.24(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
- framer-motion: 12.23.22(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ framer-motion: 12.23.24(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
tslib: 2.8.1
optionalDependencies:
'@emotion/is-prop-valid': 1.4.0
@@ -5169,12 +5164,12 @@ snapshots:
node-addon-api@7.1.1:
optional: true
- node-releases@2.0.21: {}
+ node-releases@2.0.27: {}
normalize-package-data@2.5.0:
dependencies:
hosted-git-info: 2.8.9
- resolve: 1.22.10
+ resolve: 1.22.11
semver: 5.7.2
validate-npm-package-license: 3.0.4
@@ -5312,7 +5307,7 @@ snapshots:
react: 19.2.0
scheduler: 0.27.0
- react-hook-form@7.63.0(react@19.2.0):
+ react-hook-form@7.65.0(react@19.2.0):
dependencies:
react: 19.2.0
@@ -5329,11 +5324,11 @@ snapshots:
dependencies:
react: 19.2.0
- react-markdown@10.1.0(@types/react@19.2.0)(react@19.2.0):
+ react-markdown@10.1.0(@types/react@19.2.2)(react@19.2.0):
dependencies:
'@types/hast': 3.0.4
'@types/mdast': 4.0.4
- '@types/react': 19.2.0
+ '@types/react': 19.2.2
devlop: 1.1.0
hast-util-to-jsx-runtime: 2.3.6
html-url-attributes: 3.0.1
@@ -5355,16 +5350,16 @@ snapshots:
qr.js: 0.0.0
react: 19.2.0
- react-redux@9.2.0(@types/react@19.2.0)(react@19.2.0)(redux@5.0.1):
+ react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1):
dependencies:
'@types/use-sync-external-store': 0.0.6
react: 19.2.0
use-sync-external-store: 1.6.0(react@19.2.0)
optionalDependencies:
- '@types/react': 19.2.0
+ '@types/react': 19.2.2
redux: 5.0.1
- react-refresh@0.17.0: {}
+ react-refresh@0.18.0: {}
react-router-dom@6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
@@ -5399,18 +5394,18 @@ snapshots:
readdirp@4.1.2: {}
- recharts@3.2.1(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react-is@18.3.1)(react@19.2.0)(redux@5.0.1):
+ recharts@3.3.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react-is@18.3.1)(react@19.2.0)(redux@5.0.1):
dependencies:
- '@reduxjs/toolkit': 2.9.0(react-redux@9.2.0(@types/react@19.2.0)(react@19.2.0)(redux@5.0.1))(react@19.2.0)
+ '@reduxjs/toolkit': 2.9.2(react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1))(react@19.2.0)
clsx: 2.1.1
decimal.js-light: 2.5.1
- es-toolkit: 1.39.10
+ es-toolkit: 1.41.0
eventemitter3: 5.0.1
- immer: 10.1.3
+ immer: 10.2.0
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
react-is: 18.3.1
- react-redux: 9.2.0(@types/react@19.2.0)(react@19.2.0)(redux@5.0.1)
+ react-redux: 9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1)
reselect: 5.1.1
tiny-invariant: 1.3.3
use-sync-external-store: 1.6.0(react@19.2.0)
@@ -5473,38 +5468,38 @@ snapshots:
resolve-from@4.0.0: {}
- resolve@1.22.10:
+ resolve@1.22.11:
dependencies:
is-core-module: 2.16.1
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
- rollup@4.52.3:
+ rollup@4.52.5:
dependencies:
'@types/estree': 1.0.8
optionalDependencies:
- '@rollup/rollup-android-arm-eabi': 4.52.3
- '@rollup/rollup-android-arm64': 4.52.3
- '@rollup/rollup-darwin-arm64': 4.52.3
- '@rollup/rollup-darwin-x64': 4.52.3
- '@rollup/rollup-freebsd-arm64': 4.52.3
- '@rollup/rollup-freebsd-x64': 4.52.3
- '@rollup/rollup-linux-arm-gnueabihf': 4.52.3
- '@rollup/rollup-linux-arm-musleabihf': 4.52.3
- '@rollup/rollup-linux-arm64-gnu': 4.52.3
- '@rollup/rollup-linux-arm64-musl': 4.52.3
- '@rollup/rollup-linux-loong64-gnu': 4.52.3
- '@rollup/rollup-linux-ppc64-gnu': 4.52.3
- '@rollup/rollup-linux-riscv64-gnu': 4.52.3
- '@rollup/rollup-linux-riscv64-musl': 4.52.3
- '@rollup/rollup-linux-s390x-gnu': 4.52.3
- '@rollup/rollup-linux-x64-gnu': 4.52.3
- '@rollup/rollup-linux-x64-musl': 4.52.3
- '@rollup/rollup-openharmony-arm64': 4.52.3
- '@rollup/rollup-win32-arm64-msvc': 4.52.3
- '@rollup/rollup-win32-ia32-msvc': 4.52.3
- '@rollup/rollup-win32-x64-gnu': 4.52.3
- '@rollup/rollup-win32-x64-msvc': 4.52.3
+ '@rollup/rollup-android-arm-eabi': 4.52.5
+ '@rollup/rollup-android-arm64': 4.52.5
+ '@rollup/rollup-darwin-arm64': 4.52.5
+ '@rollup/rollup-darwin-x64': 4.52.5
+ '@rollup/rollup-freebsd-arm64': 4.52.5
+ '@rollup/rollup-freebsd-x64': 4.52.5
+ '@rollup/rollup-linux-arm-gnueabihf': 4.52.5
+ '@rollup/rollup-linux-arm-musleabihf': 4.52.5
+ '@rollup/rollup-linux-arm64-gnu': 4.52.5
+ '@rollup/rollup-linux-arm64-musl': 4.52.5
+ '@rollup/rollup-linux-loong64-gnu': 4.52.5
+ '@rollup/rollup-linux-ppc64-gnu': 4.52.5
+ '@rollup/rollup-linux-riscv64-gnu': 4.52.5
+ '@rollup/rollup-linux-riscv64-musl': 4.52.5
+ '@rollup/rollup-linux-s390x-gnu': 4.52.5
+ '@rollup/rollup-linux-x64-gnu': 4.52.5
+ '@rollup/rollup-linux-x64-musl': 4.52.5
+ '@rollup/rollup-openharmony-arm64': 4.52.5
+ '@rollup/rollup-win32-arm64-msvc': 4.52.5
+ '@rollup/rollup-win32-ia32-msvc': 4.52.5
+ '@rollup/rollup-win32-x64-gnu': 4.52.5
+ '@rollup/rollup-win32-x64-msvc': 4.52.5
fsevents: 2.3.3
rxjs@7.8.2:
@@ -5533,7 +5528,7 @@ snapshots:
sass@1.92.1:
dependencies:
chokidar: 4.0.3
- immutable: 5.1.3
+ immutable: 5.1.4
source-map-js: 1.2.1
optionalDependencies:
'@parcel/watcher': 2.5.1
@@ -5679,11 +5674,11 @@ snapshots:
strip-bom@3.0.0: {}
- style-to-js@1.1.17:
+ style-to-js@1.1.18:
dependencies:
- style-to-object: 1.0.9
+ style-to-object: 1.0.11
- style-to-object@1.0.9:
+ style-to-object@1.0.11:
dependencies:
inline-style-parser: 0.2.4
@@ -5711,7 +5706,7 @@ snapshots:
csso: 5.0.5
picocolors: 1.1.1
- tabbable@6.2.0: {}
+ tabbable@6.3.0: {}
tiny-invariant@1.3.3: {}
@@ -5764,9 +5759,9 @@ snapshots:
possible-typed-array-names: 1.1.0
reflect.getprototypeof: 1.0.10
- typedoc@0.28.13(typescript@5.9.3):
+ typedoc@0.28.14(typescript@5.9.3):
dependencies:
- '@gerrit0/mini-shiki': 3.13.0
+ '@gerrit0/mini-shiki': 3.14.0
lunr: 2.3.9
markdown-it: 14.1.0
minimatch: 9.0.5
@@ -5788,7 +5783,7 @@ snapshots:
has-symbols: 1.1.0
which-boxed-primitive: 1.1.1
- undici-types@7.13.0: {}
+ undici-types@7.16.0: {}
unified@11.0.5:
dependencies:
@@ -5800,7 +5795,7 @@ snapshots:
trough: 2.2.0
vfile: 6.0.3
- unist-util-is@6.0.0:
+ unist-util-is@6.0.1:
dependencies:
'@types/unist': 3.0.3
@@ -5812,24 +5807,24 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
- unist-util-visit-parents@6.0.1:
+ unist-util-visit-parents@6.0.2:
dependencies:
'@types/unist': 3.0.3
- unist-util-is: 6.0.0
+ unist-util-is: 6.0.1
unist-util-visit@5.0.0:
dependencies:
'@types/unist': 3.0.3
- unist-util-is: 6.0.0
- unist-util-visit-parents: 6.0.1
+ unist-util-is: 6.0.1
+ unist-util-visit-parents: 6.0.2
- update-browserslist-db@1.1.3(browserslist@4.26.3):
+ update-browserslist-db@1.1.4(browserslist@4.27.0):
dependencies:
- browserslist: 4.26.3
+ browserslist: 4.27.0
escalade: 3.2.0
picocolors: 1.1.1
- use-breakpoint@4.0.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ use-breakpoint@4.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
@@ -5878,16 +5873,16 @@ snapshots:
d3-time: 3.1.0
d3-timer: 3.0.1
- vite@7.1.8(@types/node@24.6.2)(sass@1.92.1)(yaml@2.8.1):
+ vite@7.1.12(@types/node@24.9.2)(sass@1.92.1)(yaml@2.8.1):
dependencies:
- esbuild: 0.25.10
+ esbuild: 0.25.11
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
- rollup: 4.52.3
+ rollup: 4.52.5
tinyglobby: 0.2.15
optionalDependencies:
- '@types/node': 24.6.2
+ '@types/node': 24.9.2
fsevents: 2.3.3
sass: 1.92.1
yaml: 2.8.1
@@ -5967,10 +5962,10 @@ snapshots:
zod@3.25.76: {}
- zustand@5.0.8(@types/react@19.2.0)(immer@10.1.3)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)):
+ zustand@5.0.8(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)):
optionalDependencies:
- '@types/react': 19.2.0
- immer: 10.1.3
+ '@types/react': 19.2.2
+ immer: 10.2.0
react: 19.2.0
use-sync-external-store: 1.6.0(react@19.2.0)
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index a5d3ae86..e6bf5c43 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
[[package]]
name = "adler2"
@@ -31,9 +31,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
-version = "1.1.3"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
@@ -669,7 +669,7 @@ dependencies = [
[[package]]
name = "boringtun"
version = "0.6.0"
-source = "git+https://github.com/DefGuard/wireguard-rs?rev=e50b6a6f2111381bfc010263210dda91694e1c6d#e50b6a6f2111381bfc010263210dda91694e1c6d"
+source = "git+https://github.com/DefGuard/wireguard-rs?rev=0db4ea7bf4a6bd21c449f9ab8fa6676aebf4698f#0db4ea7bf4a6bd21c449f9ab8fa6676aebf4698f"
dependencies = [
"aead",
"base64 0.22.1",
@@ -870,9 +870,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.2.43"
+version = "1.2.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2"
+checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -970,9 +970,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.5.50"
+version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623"
+checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
dependencies = [
"clap_builder",
"clap_derive",
@@ -980,9 +980,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.50"
+version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0"
+checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
dependencies = [
"anstream",
"anstyle",
@@ -1417,6 +1417,7 @@ dependencies = [
"sqlx",
"struct-patch",
"strum",
+ "swift-rs",
"tauri",
"tauri-build",
"tauri-plugin-clipboard-manager",
@@ -1475,8 +1476,8 @@ dependencies = [
[[package]]
name = "defguard_wireguard_rs"
-version = "0.8.0"
-source = "git+https://github.com/DefGuard/wireguard-rs?rev=e50b6a6f2111381bfc010263210dda91694e1c6d#e50b6a6f2111381bfc010263210dda91694e1c6d"
+version = "0.9.0"
+source = "git+https://github.com/DefGuard/wireguard-rs?rev=0db4ea7bf4a6bd21c449f9ab8fa6676aebf4698f#0db4ea7bf4a6bd21c449f9ab8fa6676aebf4698f"
dependencies = [
"base64 0.22.1",
"boringtun",
@@ -2834,9 +2835,9 @@ dependencies = [
[[package]]
name = "icu_collections"
-version = "2.0.0"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
+checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
dependencies = [
"displaydoc",
"potential_utf",
@@ -2847,9 +2848,9 @@ dependencies = [
[[package]]
name = "icu_locale_core"
-version = "2.0.0"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
+checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
dependencies = [
"displaydoc",
"litemap",
@@ -2860,11 +2861,10 @@ dependencies = [
[[package]]
name = "icu_normalizer"
-version = "2.0.0"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
+checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
dependencies = [
- "displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
@@ -2875,42 +2875,38 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
-version = "2.0.0"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
+checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
[[package]]
name = "icu_properties"
-version = "2.0.1"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
+checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99"
dependencies = [
- "displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
- "potential_utf",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
-version = "2.0.1"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
+checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899"
[[package]]
name = "icu_provider"
-version = "2.0.0"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
+checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
dependencies = [
"displaydoc",
"icu_locale_core",
- "stable_deref_trait",
- "tinystr",
"writeable",
"yoke",
"zerofrom",
@@ -3331,9 +3327,9 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "litemap"
-version = "0.8.0"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
+checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]]
name = "litrs"
@@ -3696,11 +3692,10 @@ dependencies = [
[[package]]
name = "num-bigint-dig"
-version = "0.8.4"
+version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
+checksum = "82c79c15c05d4bf82b6f5ef163104cc81a760d8e874d38ac50ab67c8877b647b"
dependencies = [
- "byteorder",
"lazy_static",
"libm",
"num-integer",
@@ -4557,9 +4552,9 @@ dependencies = [
[[package]]
name = "potential_utf"
-version = "0.1.3"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
+checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
dependencies = [
"zerovec",
]
@@ -5331,9 +5326,9 @@ dependencies = [
[[package]]
name = "rustls-webpki"
-version = "0.103.7"
+version = "0.103.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf"
+checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
dependencies = [
"ring",
"rustls-pki-types",
@@ -5399,9 +5394,9 @@ dependencies = [
[[package]]
name = "schemars"
-version = "1.0.4"
+version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0"
+checksum = "1317c3bf3e7df961da95b0a56a172a02abead31276215a0497241a7624b487ce"
dependencies = [
"dyn-clone",
"ref-cast",
@@ -5642,7 +5637,7 @@ dependencies = [
"indexmap 1.9.3",
"indexmap 2.12.0",
"schemars 0.9.0",
- "schemars 1.0.4",
+ "schemars 1.0.5",
"serde_core",
"serde_json",
"serde_with_macros",
@@ -6329,9 +6324,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
-version = "2.9.1"
+version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9871670c6711f50fddd4e20350be6b9dd6e6c2b5d77d8ee8900eb0d58cd837a"
+checksum = "8bceb52453e507c505b330afe3398510e87f428ea42b6e76ecb6bd63b15965b5"
dependencies = [
"anyhow",
"bytes",
@@ -6642,9 +6637,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-process"
-version = "2.3.0"
+version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7461c622a5ea00eb9cd9f7a08dbd3bf79484499fd5c21aa2964677f64ca651ab"
+checksum = "d55511a7bf6cd70c8767b02c97bf8134fa434daf3926cfc1be0a0f94132d165a"
dependencies = [
"tauri",
"tauri-plugin",
@@ -6933,9 +6928,9 @@ dependencies = [
[[package]]
name = "tinystr"
-version = "0.8.1"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
+checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
"displaydoc",
"zerovec",
@@ -7018,9 +7013,9 @@ dependencies = [
[[package]]
name = "tokio-util"
-version = "0.7.16"
+version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
+checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
dependencies = [
"bytes",
"futures-core",
@@ -7465,24 +7460,24 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]]
name = "unicode-ident"
-version = "1.0.20"
+version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
+checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unicode-normalization"
-version = "0.1.24"
+version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
+checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-properties"
-version = "0.1.3"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
+checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
[[package]]
name = "unicode-segmentation"
@@ -7758,9 +7753,9 @@ dependencies = [
[[package]]
name = "version-compare"
-version = "0.2.0"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
+checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
[[package]]
name = "version_check"
@@ -8060,9 +8055,9 @@ dependencies = [
[[package]]
name = "webpki-roots"
-version = "1.0.3"
+version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8"
+checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e"
dependencies = [
"rustls-pki-types",
]
@@ -8806,9 +8801,9 @@ dependencies = [
[[package]]
name = "writeable"
-version = "0.6.1"
+version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
+checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "wry"
@@ -8916,11 +8911,10 @@ dependencies = [
[[package]]
name = "yoke"
-version = "0.8.0"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
+checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
dependencies = [
- "serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
@@ -8928,9 +8922,9 @@ dependencies = [
[[package]]
name = "yoke-derive"
-version = "0.8.0"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
+checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [
"proc-macro2",
"quote",
@@ -9063,9 +9057,9 @@ dependencies = [
[[package]]
name = "zerotrie"
-version = "0.2.2"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
+checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
dependencies = [
"displaydoc",
"yoke",
@@ -9074,9 +9068,9 @@ dependencies = [
[[package]]
name = "zerovec"
-version = "0.11.4"
+version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
+checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
dependencies = [
"yoke",
"zerofrom",
@@ -9085,9 +9079,9 @@ dependencies = [
[[package]]
name = "zerovec-derive"
-version = "0.11.1"
+version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
+checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [
"proc-macro2",
"quote",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index d78acda2..1fb50322 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -4,7 +4,7 @@ default-members = [".", "cli"]
[workspace.dependencies]
clap = { version = "4.5", features = ["cargo", "derive", "env"] }
-defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs", rev = "e50b6a6f2111381bfc010263210dda91694e1c6d" }
+defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs", rev = "0db4ea7bf4a6bd21c449f9ab8fa6676aebf4698f" }
dirs-next = "2.0"
prost = "0.14"
reqwest = { version = "0.12", features = ["cookies", "json"] }
@@ -30,7 +30,7 @@ authors = ["Defguard"]
edition = "2021"
homepage = "https://github.com/DefGuard/client"
license-file = "../LICENSE.md"
-rust-version = "1.80"
+rust-version = "1.85"
version = "1.6.0"
[package]
@@ -38,6 +38,7 @@ name = "defguard-client"
description = "Defguard desktop client"
repository = "https://github.com/DefGuard/client"
default-run = "defguard-client"
+autobins = false
authors.workspace = true
edition.workspace = true
homepage.workspace = true
@@ -45,6 +46,13 @@ license-file.workspace = true
rust-version.workspace = true
version.workspace = true
+[[bin]]
+name = "defguard-client"
+
+[[bin]]
+name = "defguard-service"
+required-features = ["service"]
+
[build-dependencies]
tauri-build = { version = "2", features = [] }
tonic-prost-build.workspace = true
@@ -94,6 +102,7 @@ tauri-plugin-log = "2"
tauri-plugin-notification = "2"
tauri-plugin-opener = "2"
tauri-plugin-os = "2"
+tauri-plugin-process = "2"
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
tauri-plugin-window-state = "2"
thiserror.workspace = true
@@ -112,7 +121,12 @@ x25519-dalek = { version = "2", features = [
"serde",
"static_secrets",
] }
-tauri-plugin-process = "2.3.0"
+
+[target.'cfg(target_os = "macos")'.dependencies]
+swift-rs = "1.0"
+
+[target.'cfg(target_os = "macos")'.build-dependencies]
+swift-rs = { version = "1.0", features = ["build"] }
[target.'cfg(unix)'.dependencies]
nix = { version = "0.30.1", features = ["user", "fs"] }
@@ -150,6 +164,7 @@ windows-sys = { version = "0.61", features = [
# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.
# DO NOT REMOVE!!
custom-protocol = ["tauri/custom-protocol"]
+service = []
[dev-dependencies]
tokio = { version = "1", features = ["full"] }
diff --git a/src-tauri/Client.entitlements b/src-tauri/Client.entitlements
new file mode 100644
index 00000000..552baf4b
--- /dev/null
+++ b/src-tauri/Client.entitlements
@@ -0,0 +1,18 @@
+
+
+
+
+ com.apple.developer.networking.networkextension
+
+ packet-tunnel-provider
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.network.client
+
+ com.apple.application-identifier
+ 82GZ7KN29J.net.defguard
+ com.apple.developer.team-identifier
+ 82GZ7KN29J
+
+
diff --git a/src-tauri/Defguard_Client_Mac_App_Store.provisionprofile b/src-tauri/Defguard_Client_Mac_App_Store.provisionprofile
new file mode 100644
index 00000000..7eaff6cd
Binary files /dev/null and b/src-tauri/Defguard_Client_Mac_App_Store.provisionprofile differ
diff --git a/src-tauri/Defguard_VPNExtension_Mac_App_Store.provisionprofile b/src-tauri/Defguard_VPNExtension_Mac_App_Store.provisionprofile
new file mode 100644
index 00000000..c762e521
Binary files /dev/null and b/src-tauri/Defguard_VPNExtension_Mac_App_Store.provisionprofile differ
diff --git a/src-tauri/Info.plist b/src-tauri/Info.plist
new file mode 100644
index 00000000..cceceddc
--- /dev/null
+++ b/src-tauri/Info.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ ITSAppUsesNonExemptEncryption
+
+
+
diff --git a/src-tauri/build.rs b/src-tauri/build.rs
index 91455f0e..dd438547 100644
--- a/src-tauri/build.rs
+++ b/src-tauri/build.rs
@@ -23,6 +23,14 @@ fn main() -> Result<(), Box> {
tauri_build::build();
+ #[cfg(target_os = "macos")]
+ swift_rs::SwiftLinker::new("13")
+ .with_ios("15")
+ .with_package("defguard-vpn-plugin", "../swift/plugin")
+ .link();
+
println!("cargo:rerun-if-changed=proto");
+ #[cfg(target_os = "macos")]
+ println!("cargo:rerun-if-changed=../swift");
Ok(())
}
diff --git a/src-tauri/cli/src/bin/dg.rs b/src-tauri/cli/src/bin/dg.rs
index 93aa503c..f988257a 100644
--- a/src-tauri/cli/src/bin/dg.rs
+++ b/src-tauri/cli/src/bin/dg.rs
@@ -234,7 +234,7 @@ async fn connect(config: CliConfig, ifname: String, trigger: Arc) -> Res
name: config.instance_info.name.clone(),
prvkey: config.private_key.to_string(),
addresses,
- port: u32::from(find_free_tcp_port().ok_or(CliError::FreeTCPPort)?),
+ port: find_free_tcp_port().ok_or(CliError::FreeTCPPort)?,
peers: vec![peer.clone()],
mtu: None,
};
diff --git a/src-tauri/common/src/lib.rs b/src-tauri/common/src/lib.rs
index bd4a0117..4d2f836b 100644
--- a/src-tauri/common/src/lib.rs
+++ b/src-tauri/common/src/lib.rs
@@ -11,14 +11,11 @@ pub fn find_free_tcp_port() -> Option {
.map(|local_addr| local_addr.port())
}
-#[cfg(not(windows))]
-/// Find next available interface. On macOS, search for available `utun` interface.
-/// On other UNIX, search for available `wg` interface.
+#[cfg(not(any(windows, target_os = "macos")))]
+/// Find next available interface.
+/// Search for available `wg` interface.
#[must_use]
pub fn get_interface_name(_name: &str) -> String {
- #[cfg(target_os = "macos")]
- let base_ifname = "utun";
- #[cfg(not(target_os = "macos"))]
let base_ifname = "wg";
if let Ok(interfaces) = nix::net::if_::if_nameindex() {
for index in 0..=u16::MAX {
@@ -36,7 +33,7 @@ pub fn get_interface_name(_name: &str) -> String {
}
/// Strips location name of all non-alphanumeric characters returning usable interface name.
-#[cfg(windows)]
+#[cfg(any(windows, target_os = "macos"))]
#[must_use]
pub fn get_interface_name(name: &str) -> String {
name.chars().filter(|c| c.is_alphanumeric()).collect()
diff --git a/src-tauri/proto b/src-tauri/proto
index fee70601..764ba6e5 160000
--- a/src-tauri/proto
+++ b/src-tauri/proto
@@ -1 +1 @@
-Subproject commit fee706013b3bb5452c3c4dbf35bd973d0637ff25
+Subproject commit 764ba6e516781f5e13719d5ea6c5e7fcc7307c53
diff --git a/src-tauri/resources-macos/binaries/.gitkeep b/src-tauri/resources-macos/binaries/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/src-tauri/resources-macos/resources/net.defguard.plist b/src-tauri/resources-macos/resources/net.defguard.plist
deleted file mode 100644
index 479b9737..00000000
--- a/src-tauri/resources-macos/resources/net.defguard.plist
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
- Label
- net.defguard
- EnvironmentVariables
-
- PATH
- /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/sbin
-
- Program
- /usr/local/bin/defguard-service
- KeepAlive
-
- RunAtLoad
-
- GroupName
- staff
-
-
diff --git a/src-tauri/resources-macos/resources/uninstall.sh b/src-tauri/resources-macos/resources/uninstall.sh
deleted file mode 100644
index 629344c2..00000000
--- a/src-tauri/resources-macos/resources/uninstall.sh
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-
-SERVICE_BINARY=defguard-service
-DAEMON_PROPERTY_FILE=net.defguard.plist
-DAEMON_NAME=net.defguard
-PACKAGE_ID=net.defguard
-
-#Check running user
-if (( $EUID != 0 )); then
- echo "Please run as root."
- exit
-fi
-
-# Remove service shortcut at /usr/local/bin
-rm -f /usr/local/bin/${SERVICE_BINARY}
-
-# Remove daemon
-launchctl stop ${DAEMON_NAME}
-launchctl unload /Library/LaunchDaemons/${DAEMON_PROPERTY_FILE}
-rm -f /Library/LaunchDaemons/${DAEMON_PROPERTY_FILE}
-
-pkgutil --forget ${PACKAGE_ID} > /dev/null 2>&1
-
-rm -rf /Applications/defguard-client.app
-
-echo "Application uninstall process finished"
diff --git a/src-tauri/resources-macos/scripts/postinstall b/src-tauri/resources-macos/scripts/postinstall
deleted file mode 100755
index b1ed66ba..00000000
--- a/src-tauri/resources-macos/scripts/postinstall
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/bash
-
-SERVICE_BINARY=defguard-service
-DAEMON_PROPERTY_FILE=net.defguard.plist
-DAEMON_NAME=net.defguard
-APP_BUNDLE=defguard-client.app
-PRODUCT_HOME=/Applications/${APP_BUNDLE}
-BINARY_PATH=${PRODUCT_HOME}/Contents/MacOS
-RESOURCES_PATH=${PRODUCT_HOME}/Contents/Resources/resources-macos/resources
-
-echo "Post installation process started"
-
-mkdir -p /usr/local/bin
-
-# Add service shortcut to /usr/local/bin
-ln -sf ${BINARY_PATH}/${SERVICE_BINARY} /usr/local/bin/${SERVICE_BINARY}
-
-# Launch daemon
-ln -sf ${RESOURCES_PATH}/${DAEMON_PROPERTY_FILE} /Library/LaunchDaemons/${DAEMON_PROPERTY_FILE}
-sudo launchctl load /Library/LaunchDaemons/${DAEMON_PROPERTY_FILE}
-# Restart
-sudo launchctl stop ${DAEMON_NAME}
-
-echo "Post installation process finished"
diff --git a/src-tauri/src/bin/defguard-client.rs b/src-tauri/src/bin/defguard-client.rs
index e0237f9f..d133701e 100644
--- a/src-tauri/src/bin/defguard-client.rs
+++ b/src-tauri/src/bin/defguard-client.rs
@@ -57,7 +57,7 @@ async fn startup(app_handle: &AppHandle) {
// and they are still running after the restart. We sync them here to
// reflect the real system's state.
// TODO: Find a way to intercept the shutdown event and close all connections
- #[cfg(target_os = "windows")]
+ #[cfg(windows)]
{
match sync_connections(app_handle).await {
Ok(_) => {
diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs
index 8f050e5b..d7372597 100644
--- a/src-tauri/src/commands.rs
+++ b/src-tauri/src/commands.rs
@@ -54,7 +54,7 @@ use crate::{
CommonConnection, CommonConnectionInfo, CommonLocationStats, ConnectionType,
};
-// Create new WireGuard interface
+/// Open new WireGuard connection.
#[tauri::command(async)]
pub async fn connect(
location_id: Id,
@@ -1009,7 +1009,7 @@ pub fn parse_tunnel_config(config: &str) -> Result {
#[tauri::command(async)]
pub async fn update_tunnel(mut tunnel: Tunnel, handle: AppHandle) -> Result<(), Error> {
- debug!("Received tunnel configuration to update: {tunnel:?}");
+ debug!("Received tunnel configuration to update: {tunnel}");
tunnel.save(&*DB_POOL).await?;
info!("The tunnel {tunnel} configuration has been updated.");
handle.emit(EventKey::LocationUpdate.into(), ())?;
@@ -1018,7 +1018,7 @@ pub async fn update_tunnel(mut tunnel: Tunnel, handle: AppHandle) -> Result<
#[tauri::command(async)]
pub async fn save_tunnel(tunnel: Tunnel, handle: AppHandle) -> Result<(), Error> {
- debug!("Received tunnel configuration to save: {tunnel:?}");
+ debug!("Received tunnel configuration to save: {tunnel}");
let tunnel = tunnel.save(&*DB_POOL).await?;
info!("The tunnel {tunnel} configuration has been saved.");
handle.emit(EventKey::LocationUpdate.into(), ())?;
@@ -1042,7 +1042,6 @@ pub async fn all_tunnels() -> Result>, Error> {
let tunnels = Tunnel::all(&*DB_POOL).await?;
trace!("Found ({}) tunnels to get information about", tunnels.len());
- trace!("Tunnels found: {tunnels:#?}");
let mut tunnel_info = Vec::new();
let active_tunnel_ids = get_connection_id_by_type(ConnectionType::Tunnel).await;
diff --git a/src-tauri/src/database/models/location.rs b/src-tauri/src/database/models/location.rs
index 7bee53ee..3b092b50 100644
--- a/src-tauri/src/database/models/location.rs
+++ b/src-tauri/src/database/models/location.rs
@@ -1,14 +1,19 @@
-use std::fmt;
+#[cfg(target_os = "macos")]
+use std::net::IpAddr;
+use std::{fmt, str::FromStr};
+use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, InterfaceConfiguration};
use serde::{Deserialize, Serialize};
use sqlx::{prelude::Type, query, query_as, query_scalar, Error as SqlxError, SqliteExecutor};
use super::{Id, NoId};
use crate::{
+ database::models::wireguard_keys::WireguardKeys,
error::Error,
proto::{
LocationMfaMode as ProtoLocationMfaMode, ServiceLocationMode as ProtoServiceLocationMode,
},
+ utils::{DEFAULT_ROUTE_IPV4, DEFAULT_ROUTE_IPV6},
};
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Type)]
@@ -61,7 +66,7 @@ pub struct Location {
pub network_id: Id,
pub name: String,
pub address: String,
- pub pubkey: String,
+ pub pubkey: String, // Remote
pub endpoint: String,
pub allowed_ips: String,
pub dns: Option,
@@ -231,6 +236,117 @@ impl Location {
}
}
+ /// Split DNS settings into resolver IP addresses and search domains.
+ #[cfg(target_os = "macos")]
+ pub(crate) fn dns(&self) -> (Vec, Vec) {
+ let mut dns = Vec::new();
+ let mut dns_search = Vec::new();
+
+ if let Some(dns_string) = &self.dns {
+ for entry in dns_string.split(',').map(str::trim) {
+ // Assume that every entry that can't be parsed as an IP address is a domain name.
+ if let Ok(ip) = entry.parse::() {
+ dns.push(ip);
+ } else {
+ dns_search.push(entry.into());
+ }
+ }
+ }
+
+ (dns, dns_search)
+ }
+
+ #[cfg(not(target_os = "macos"))]
+ pub(crate) async fn interface_configurarion<'e, E>(
+ &self,
+ executor: E,
+ interface_name: String,
+ preshared_key: Option,
+ ) -> Result
+ where
+ E: SqliteExecutor<'e>,
+ {
+ debug!("Looking for WireGuard keys for location {self} instance");
+ let Some(keys) = WireguardKeys::find_by_instance_id(executor, self.instance_id).await?
+ else {
+ error!("No keys found for instance: {}", self.instance_id);
+ return Err(Error::InternalError(
+ "No keys found for instance".to_string(),
+ ));
+ };
+ debug!("WireGuard keys found for location {self} instance");
+
+ // prepare peer config
+ debug!("Decoding location {self} public key: {}.", self.pubkey);
+ let peer_key = Key::from_str(&self.pubkey)?;
+ debug!("Location {self} public key decoded: {peer_key}");
+ let mut peer = Peer::new(peer_key);
+
+ debug!("Parsing location {self} endpoint: {}", self.endpoint);
+ peer.set_endpoint(&self.endpoint)?;
+ peer.persistent_keepalive_interval = Some(25);
+ debug!("Parsed location {self} endpoint: {}", self.endpoint);
+
+ if let Some(psk) = preshared_key {
+ debug!("Decoding location {self} preshared key.");
+ let peer_psk = Key::from_str(&psk)?;
+ info!("Location {self} preshared key decoded.");
+ peer.preshared_key = Some(peer_psk);
+ }
+
+ debug!("Parsing location {self} allowed IPs: {}", self.allowed_ips);
+ let allowed_ips = if self.route_all_traffic {
+ debug!("Using all traffic routing for location {self}");
+ vec![DEFAULT_ROUTE_IPV4.into(), DEFAULT_ROUTE_IPV6.into()]
+ } else {
+ debug!(
+ "Using predefined location {self} traffic: {}",
+ self.allowed_ips
+ );
+ self.allowed_ips.split(',').map(str::to_string).collect()
+ };
+ for allowed_ip in &allowed_ips {
+ match IpAddrMask::from_str(allowed_ip) {
+ Ok(addr) => {
+ peer.allowed_ips.push(addr);
+ }
+ Err(err) => {
+ // Handle the error from IpAddrMask::from_str, if needed
+ error!(
+ "Error parsing IP address {allowed_ip} while setting up interface for \
+ location {self}, error details: {err}"
+ );
+ }
+ }
+ }
+ debug!(
+ "Parsed allowed IPs for location {self}: {:?}",
+ peer.allowed_ips
+ );
+
+ let addresses = self
+ .address
+ .split(',')
+ .map(str::trim)
+ .map(IpAddrMask::from_str)
+ .collect::>()
+ .map_err(|err| {
+ let msg = format!("Failed to parse IP addresses '{}': {err}", self.address);
+ error!("{msg}");
+ Error::InternalError(msg)
+ })?;
+ let interface_config = InterfaceConfiguration {
+ name: interface_name,
+ prvkey: keys.prvkey,
+ addresses,
+ port: 0,
+ peers: vec![peer],
+ mtu: None,
+ };
+
+ Ok(interface_config)
+ }
+
/// Returns a filter value that can be used in SQL queries like `service_location_mode <= ?` when querying locations
/// to exclude (<= 1) or include service locations (all service locations modes).
fn get_service_location_mode_filter(include_service_locations: bool) -> i32 {
diff --git a/src-tauri/src/database/models/tunnel.rs b/src-tauri/src/database/models/tunnel.rs
index 974861c7..7510ea0e 100644
--- a/src-tauri/src/database/models/tunnel.rs
+++ b/src-tauri/src/database/models/tunnel.rs
@@ -14,13 +14,13 @@ use crate::{
};
#[serde_as]
-#[derive(Debug, Serialize, Deserialize)]
+#[derive(Serialize, Deserialize)]
pub struct Tunnel {
pub id: I,
pub name: String,
- // user keys
- pub pubkey: String,
- pub prvkey: String,
+ // encryption keys
+ pub pubkey: String, // Remote
+ pub prvkey: String, // Local
// server config
pub address: String,
pub server_pubkey: String,
@@ -32,7 +32,7 @@ pub struct Tunnel {
pub endpoint: String,
#[serde_as(as = "NoneAsEmptyString")]
pub dns: Option,
- pub persistent_keep_alive: i64, // New field
+ pub persistent_keep_alive: i64,
pub route_all_traffic: bool,
// additional commands
#[serde_as(as = "NoneAsEmptyString")]
@@ -51,6 +51,12 @@ impl fmt::Display for Tunnel {
}
}
+impl fmt::Display for Tunnel {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.name)
+ }
+}
+
impl Tunnel {
pub(crate) async fn save<'e, E>(&mut self, executor: E) -> Result<(), SqlxError>
where
diff --git a/src-tauri/src/database/models/wireguard_keys.rs b/src-tauri/src/database/models/wireguard_keys.rs
index 38a27273..dce89689 100644
--- a/src-tauri/src/database/models/wireguard_keys.rs
+++ b/src-tauri/src/database/models/wireguard_keys.rs
@@ -5,7 +5,6 @@ use x25519_dalek::{PublicKey, StaticSecret};
use super::{Id, NoId};
// User key pair
-#[derive(Debug)]
pub struct WireguardKeys {
pub id: I,
pub instance_id: Id,
diff --git a/src-tauri/src/enterprise/provisioning/mod.rs b/src-tauri/src/enterprise/provisioning/mod.rs
index 377a9b1e..a6ae327d 100644
--- a/src-tauri/src/enterprise/provisioning/mod.rs
+++ b/src-tauri/src/enterprise/provisioning/mod.rs
@@ -34,7 +34,10 @@ impl ProvisioningConfig {
let file_content = match fs::read_to_string(path) {
Ok(content) => content,
Err(err) => {
- warn!("Failed to open provisioning configuration file at {path:?}. Error details: {err}");
+ warn!(
+ "Failed to open provisioning configuration file at {path:?}. Error details: \
+ {err}"
+ );
return None;
}
};
@@ -45,7 +48,10 @@ impl ProvisioningConfig {
match serde_json::from_str::(file_content) {
Ok(config) => Some(config),
Err(err) => {
- warn!("Failed to parse provisioning configuration file at {path:?}. Error details: {err}");
+ warn!(
+ "Failed to parse provisioning configuration file at {path:?}. Error details: \
+ {err}"
+ );
None
}
}
@@ -80,13 +86,16 @@ pub async fn handle_client_initialization(app_handle: &AppHandle) -> Option {
- debug!("Provisioning config not found in {data_dir:?}. Proceeding with normal startup.")
+ debug!(
+ "Provisioning config not found in {data_dir:?}. Proceeding with normal \
+ startup."
+ );
}
}
}
}
Err(err) => {
- error!("Failed to verify if the client has already been initialized: {err}")
+ error!("Failed to verify if the client has already been initialized: {err}");
}
}
diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs
index a34c4330..176710cb 100644
--- a/src-tauri/src/error.rs
+++ b/src-tauri/src/error.rs
@@ -42,8 +42,6 @@ pub enum Error {
NoToken,
#[error("Failed to lock app state member.")]
StateLockFail,
- #[error("Failed to acquire lock on mutex. {0}")]
- PoisonError(String),
#[error("Failed to convert value. {0}")]
ConversionError(String),
#[error("JSON error: {0}")]
diff --git a/src-tauri/src/export.rs b/src-tauri/src/export.rs
new file mode 100644
index 00000000..ec89fd35
--- /dev/null
+++ b/src-tauri/src/export.rs
@@ -0,0 +1,135 @@
+//! Structures used for interchangeability with the Swift code.
+
+use std::{net::IpAddr, str::FromStr};
+
+use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask};
+use serde::Serialize;
+use sqlx::SqliteExecutor;
+use swift_rs::{swift, SRObject, SRString};
+
+#[repr(C)]
+// Should match the declaration in Swift.
+pub(crate) struct Stats {
+ pub(crate) tx_bytes: u64,
+ pub(crate) rx_bytes: u64,
+}
+
+swift!(pub(crate) fn start_tunnel(json: &SRString) -> bool);
+swift!(pub(crate) fn stop_tunnel(name: &SRString) -> bool);
+swift!(pub(crate) fn tunnel_stats(name: &SRString) -> Option>);
+
+use crate::{
+ database::models::{location::Location, wireguard_keys::WireguardKeys, Id},
+ error::Error,
+ utils::{DEFAULT_ROUTE_IPV4, DEFAULT_ROUTE_IPV6},
+};
+
+#[derive(Serialize)]
+pub(crate) struct TunnelConfiguration {
+ name: String,
+ #[serde(rename = "privateKey")]
+ private_key: String,
+ addresses: Vec,
+ #[serde(rename = "listenPort")]
+ listen_port: Option,
+ peers: Vec,
+ mtu: Option,
+ dns: Vec,
+ #[serde(rename = "dnsSearch")]
+ dns_search: Vec,
+}
+
+impl Location {
+ pub(crate) async fn tunnel_configurarion<'e, E>(
+ &self,
+ executor: E,
+ preshared_key: Option,
+ dns: Vec,
+ dns_search: Vec,
+ ) -> Result
+ where
+ E: SqliteExecutor<'e>,
+ {
+ debug!("Looking for WireGuard keys for location {self} instance");
+ let Some(keys) = WireguardKeys::find_by_instance_id(executor, self.instance_id).await?
+ else {
+ error!("No keys found for instance: {}", self.instance_id);
+ return Err(Error::InternalError(
+ "No keys found for instance".to_string(),
+ ));
+ };
+ debug!("WireGuard keys found for location {self} instance");
+
+ // prepare peer config
+ debug!("Decoding location {self} public key: {}.", self.pubkey);
+ let peer_key = Key::from_str(&self.pubkey)?;
+ debug!("Location {self} public key decoded: {peer_key}");
+ let mut peer = Peer::new(peer_key);
+
+ debug!("Parsing location {self} endpoint: {}", self.endpoint);
+ peer.set_endpoint(&self.endpoint)?;
+ peer.persistent_keepalive_interval = Some(25);
+ debug!("Parsed location {self} endpoint: {}", self.endpoint);
+
+ if let Some(psk) = preshared_key {
+ debug!("Decoding location {self} preshared key.");
+ let peer_psk = Key::from_str(&psk)?;
+ info!("Location {self} preshared key decoded.");
+ peer.preshared_key = Some(peer_psk);
+ }
+
+ debug!("Parsing location {self} allowed IPs: {}", self.allowed_ips);
+ let allowed_ips = if self.route_all_traffic {
+ debug!("Using all traffic routing for location {self}");
+ vec![DEFAULT_ROUTE_IPV4.into(), DEFAULT_ROUTE_IPV6.into()]
+ } else {
+ debug!(
+ "Using predefined location {self} traffic: {}",
+ self.allowed_ips
+ );
+ self.allowed_ips.split(',').map(str::to_string).collect()
+ };
+ for allowed_ip in &allowed_ips {
+ match IpAddrMask::from_str(allowed_ip) {
+ Ok(addr) => {
+ peer.allowed_ips.push(addr);
+ }
+ Err(err) => {
+ // Handle the error from IpAddrMask::from_str, if needed
+ error!(
+ "Error parsing IP address {allowed_ip} while setting up interface for \
+ location {self}, error details: {err}"
+ );
+ }
+ }
+ }
+ debug!(
+ "Parsed allowed IPs for location {self}: {:?}",
+ peer.allowed_ips
+ );
+
+ let addresses = self
+ .address
+ .split(',')
+ .map(str::trim)
+ .map(IpAddrMask::from_str)
+ .collect::>()
+ .map_err(|err| {
+ let msg = format!("Failed to parse IP addresses '{}': {err}", self.address);
+ error!("{msg}");
+ Error::InternalError(msg)
+ })?;
+ let interface_config = TunnelConfiguration {
+ name: self.name.clone(),
+ private_key: keys.prvkey,
+ addresses,
+ listen_port: Some(0),
+ peers: vec![peer],
+ mtu: None,
+ dns,
+ dns_search,
+ };
+
+ Ok(interface_config)
+ }
+}
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index d02dbc1b..16692304 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -23,62 +23,16 @@ pub mod database;
pub mod enterprise;
pub mod error;
pub mod events;
+#[cfg(target_os = "macos")]
+mod export;
pub mod log_watcher;
pub mod periodic;
+pub mod proto;
pub mod service;
pub mod tray;
pub mod utils;
pub mod wg_config;
-pub mod proto {
- use crate::database::models::{
- location::{Location, LocationMfaMode as MfaMode, ServiceLocationMode as SLocationMode},
- Id, NoId,
- };
-
- tonic::include_proto!("defguard.proxy");
-
- impl DeviceConfig {
- #[must_use]
- pub(crate) fn into_location(self, instance_id: Id) -> Location {
- let location_mfa_mode = match self.location_mfa_mode {
- Some(_location_mfa_mode) => self.location_mfa_mode().into(),
- None => {
- // handle legacy core response
- // DEPRECATED(1.5): superseeded by location_mfa_mode
- #[allow(deprecated)]
- if self.mfa_enabled {
- MfaMode::Internal
- } else {
- MfaMode::Disabled
- }
- }
- };
-
- let service_location_mode = match self.service_location_mode {
- Some(_service_location_mode) => self.service_location_mode().into(),
- None => SLocationMode::Disabled, // Default to disabled if not set
- };
-
- Location {
- id: NoId,
- instance_id,
- network_id: self.network_id,
- name: self.network_name,
- address: self.assigned_ip, // Transforming assigned_ip to address
- pubkey: self.pubkey,
- endpoint: self.endpoint,
- allowed_ips: self.allowed_ips,
- dns: self.dns,
- route_all_traffic: false,
- keepalive_interval: self.keepalive_interval.into(),
- location_mfa_mode,
- service_location_mode,
- }
- }
- }
-}
-
pub const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "-", env!("VERGEN_GIT_SHA"));
pub const MIN_CORE_VERSION: Version = Version::new(1, 5, 0);
pub const MIN_PROXY_VERSION: Version = Version::new(1, 5, 0);
diff --git a/src-tauri/src/periodic/mod.rs b/src-tauri/src/periodic/mod.rs
index 37daff37..ebbd46ef 100644
--- a/src-tauri/src/periodic/mod.rs
+++ b/src-tauri/src/periodic/mod.rs
@@ -22,7 +22,7 @@ pub async fn run_periodic_tasks(app_handle: &AppHandle) {
() = poll_config(app_handle.clone()) => {
error!("Config polling task has stopped unexpectedly");
}
- () = verify_active_connections(app_handle.clone()) => {
+ () = verify_active_connections(app_handle.clone()), if cfg!(not(target_os = "macos")) => {
error!("Active connection verification task has stopped unexpectedly");
}
() = purge_stats() => {
diff --git a/src-tauri/src/proto.rs b/src-tauri/src/proto.rs
new file mode 100644
index 00000000..ad041709
--- /dev/null
+++ b/src-tauri/src/proto.rs
@@ -0,0 +1,46 @@
+use crate::database::models::{
+ location::{Location, LocationMfaMode as MfaMode, ServiceLocationMode as SLocationMode},
+ Id, NoId,
+};
+
+tonic::include_proto!("defguard.proxy");
+
+impl DeviceConfig {
+ #[must_use]
+ pub(crate) fn into_location(self, instance_id: Id) -> Location {
+ let location_mfa_mode = match self.location_mfa_mode {
+ Some(_location_mfa_mode) => self.location_mfa_mode().into(),
+ None => {
+ // handle legacy core response
+ // DEPRECATED(1.5): superseeded by location_mfa_mode
+ #[allow(deprecated)]
+ if self.mfa_enabled {
+ MfaMode::Internal
+ } else {
+ MfaMode::Disabled
+ }
+ }
+ };
+
+ let service_location_mode = match self.service_location_mode {
+ Some(_service_location_mode) => self.service_location_mode().into(),
+ None => SLocationMode::Disabled, // Default to disabled if not set
+ };
+
+ Location {
+ id: NoId,
+ instance_id,
+ network_id: self.network_id,
+ name: self.network_name,
+ address: self.assigned_ip, // Transforming assigned_ip to address
+ pubkey: self.pubkey,
+ endpoint: self.endpoint,
+ allowed_ips: self.allowed_ips,
+ dns: self.dns,
+ route_all_traffic: false,
+ keepalive_interval: self.keepalive_interval.into(),
+ location_mfa_mode,
+ service_location_mode,
+ }
+ }
+}
diff --git a/src-tauri/src/service/config.rs b/src-tauri/src/service/config.rs
index 6277d19f..e2f8528b 100644
--- a/src-tauri/src/service/config.rs
+++ b/src-tauri/src/service/config.rs
@@ -1,8 +1,8 @@
use clap::Parser;
-#[cfg(target_os = "windows")]
+#[cfg(windows)]
pub const DEFAULT_LOG_DIR: &str = "/Logs/defguard-service";
-#[cfg(not(target_os = "windows"))]
+#[cfg(not(windows))]
pub const DEFAULT_LOG_DIR: &str = "/var/log/defguard-service";
#[derive(Debug, Parser, Clone)]
diff --git a/src-tauri/src/service/mod.rs b/src-tauri/src/service/mod.rs
index 859f8d0c..3a1d8c05 100644
--- a/src-tauri/src/service/mod.rs
+++ b/src-tauri/src/service/mod.rs
@@ -574,7 +574,7 @@ impl From for proto::InterfaceConfig {
.map(ToString::to_string)
.collect::>()
.join(","),
- port: config.port,
+ port: u32::from(config.port),
peers: config.peers.into_iter().map(Into::into).collect(),
}
}
@@ -591,7 +591,7 @@ impl From for InterfaceConfiguration {
name: config.name,
prvkey: config.prvkey,
addresses,
- port: config.port,
+ port: config.port as u16,
peers: config.peers.into_iter().map(Into::into).collect(),
mtu: None,
}
diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs
index bc6858ed..9132437c 100644
--- a/src-tauri/src/utils.rs
+++ b/src-tauri/src/utils.rs
@@ -1,9 +1,14 @@
+#[cfg(target_os = "macos")]
+use std::time::Duration;
use std::{env, path::Path, process::Command, str::FromStr};
use common::{find_free_tcp_port, get_interface_name};
use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, InterfaceConfiguration};
use sqlx::query;
+#[cfg(target_os = "macos")]
+use swift_rs::SRString;
use tauri::{AppHandle, Emitter, Manager};
+#[cfg(not(target_os = "macos"))]
use tonic::Code;
use tracing::Level;
#[cfg(windows)]
@@ -16,6 +21,8 @@ use windows_sys::Win32::Foundation::ERROR_SERVICE_DOES_NOT_EXIST;
#[cfg(windows)]
use crate::active_connections::find_connection;
+#[cfg(target_os = "macos")]
+use crate::export::{start_tunnel, stop_tunnel, tunnel_stats};
use crate::{
appstate::AppState,
commands::LocationInterfaceDetails,
@@ -43,90 +50,19 @@ use crate::{
pub(crate) static DEFAULT_ROUTE_IPV4: &str = "0.0.0.0/0";
pub(crate) static DEFAULT_ROUTE_IPV6: &str = "::/0";
-/// Setup client interface
+/// Setup client interface for `Instance`.
+#[cfg(not(target_os = "macos"))]
pub(crate) async fn setup_interface(
location: &Location,
- interface_name: String,
+ name: &str,
preshared_key: Option,
pool: &DbPool,
-) -> Result<(), Error> {
+) -> Result {
debug!("Setting up interface for location: {location}");
-
- debug!("Looking for WireGuard keys for location {location} instance");
- let Some(keys) = WireguardKeys::find_by_instance_id(pool, location.instance_id).await? else {
- error!("No keys found for instance: {}", location.instance_id);
- return Err(Error::InternalError(
- "No keys found for instance".to_string(),
- ));
- };
- debug!("WireGuard keys found for location {location} instance");
-
- // prepare peer config
- debug!(
- "Decoding location {location} public key: {}.",
- location.pubkey
- );
- let peer_key: Key = Key::from_str(&location.pubkey)?;
- debug!("Location {location} public key decoded: {peer_key}");
- let mut peer = Peer::new(peer_key);
-
- debug!(
- "Parsing location {location} endpoint: {}",
- location.endpoint
- );
- peer.set_endpoint(&location.endpoint)?;
- peer.persistent_keepalive_interval = Some(25);
- debug!("Parsed location {location} endpoint: {}", location.endpoint);
-
- if let Some(psk) = preshared_key {
- debug!("Decoding location {location} preshared key.");
- let peer_psk = Key::from_str(&psk)?;
- info!("Location {location} preshared key decoded.");
- peer.preshared_key = Some(peer_psk);
- }
-
- debug!(
- "Parsing location {location} allowed IPs: {}",
- location.allowed_ips
- );
- let allowed_ips = if location.route_all_traffic {
- debug!(
- "Using all traffic routing for location {location}: {DEFAULT_ROUTE_IPV4} \
- {DEFAULT_ROUTE_IPV6}"
- );
- vec![DEFAULT_ROUTE_IPV4.into(), DEFAULT_ROUTE_IPV6.into()]
- } else {
- debug!(
- "Using predefined location {location} traffic: {}",
- location.allowed_ips
- );
- location
- .allowed_ips
- .split(',')
- .map(str::to_string)
- .collect()
- };
- for allowed_ip in &allowed_ips {
- match IpAddrMask::from_str(allowed_ip) {
- Ok(addr) => {
- peer.allowed_ips.push(addr);
- }
- Err(err) => {
- // Handle the error from IpAddrMask::from_str, if needed
- error!(
- "Error parsing IP address {allowed_ip} while setting up interface for \
- location {location}, error details: {err}"
- );
- }
- }
- }
- debug!(
- "Parsed allowed IPs for location {location}: {:?}",
- peer.allowed_ips
- );
+ let interface_name = get_interface_name(name);
// request interface configuration
- debug!("Looking for a free port for interface {interface_name}...");
+ debug!("Looking for a free port for interface {interface_name}.");
let Some(port) = find_free_tcp_port() else {
let msg = format!(
"Couldn't find free port during interface {interface_name} setup for location \
@@ -136,29 +72,13 @@ pub(crate) async fn setup_interface(
return Err(Error::InternalError(msg));
};
debug!("Found free port: {port} for interface {interface_name}.");
- let addresses = location
- .address
- .split(',')
- .map(str::trim)
- .map(IpAddrMask::from_str)
- .collect::>()
- .map_err(|err| {
- let msg = format!("Failed to parse IP addresses '{}': {err}", location.address);
- error!("{msg}");
- Error::InternalError(msg)
- })?;
- let interface_config = InterfaceConfiguration {
- name: interface_name,
- prvkey: keys.prvkey,
- addresses,
- port: port.into(),
- peers: vec![peer.clone()],
- mtu: None,
- };
+
+ let interface_config = location
+ .interface_configurarion(pool, interface_name.clone(), preshared_key)
+ .await?;
debug!("Creating interface for location {location} with configuration {interface_config:?}");
let request = CreateInterfaceRequest {
config: Some(interface_config.clone().into()),
- allowed_ips,
dns: location.dns.clone(),
};
if let Err(error) = DAEMON_CLIENT.clone().create_interface(request).await {
@@ -188,10 +108,51 @@ pub(crate) async fn setup_interface(
name: {}.",
interface_config.name
);
- Ok(())
+ Ok(interface_name)
+ }
+}
+
+#[cfg(target_os = "macos")]
+pub(crate) async fn setup_interface(
+ location: &Location,
+ name: &str,
+ preshared_key: Option,
+ pool: &DbPool,
+) -> Result {
+ debug!("Setting up interface for location: {location}");
+ let interface_name = get_interface_name(name);
+
+ let (dns, dns_search) = location.dns();
+ let tunnel_config = location
+ .tunnel_configurarion(pool, preshared_key, dns, dns_search)
+ .await?;
+
+ unsafe {
+ let json: SRString = serde_json::to_string(&tunnel_config)
+ .unwrap()
+ .as_str()
+ .into();
+ let result = start_tunnel(&json);
+ error!("start_tunnel() returned {result:?}");
}
+ Ok(interface_name)
}
+#[cfg(target_os = "macos")]
+pub(crate) async fn stats_handler(_pool: DbPool, name: String, _connection_type: ConnectionType) {
+ const CHECK_INTERVAL: Duration = Duration::from_secs(5);
+ let mut interval = tokio::time::interval(CHECK_INTERVAL);
+
+ loop {
+ interval.tick().await;
+ // TODO: check all known localtions/tunnels, not just `name`.
+ if let Some(stats) = unsafe { tunnel_stats(&name.as_str().into()) } {
+ info!("Tunnel stats: {} {}", stats.tx_bytes, stats.rx_bytes);
+ }
+ }
+}
+
+#[cfg(not(target_os = "macos"))]
pub(crate) async fn stats_handler(
pool: DbPool,
interface_name: String,
@@ -333,21 +294,19 @@ pub fn load_log_targets() -> Vec {
// helper function to get log file directory for the defguard-service daemon
#[must_use]
pub fn get_service_log_dir() -> &'static Path {
- #[cfg(target_os = "windows")]
+ #[cfg(windows)]
let path = "/Logs/defguard-service";
- #[cfg(not(target_os = "windows"))]
+ #[cfg(not(windows))]
let path = "/var/log/defguard-service";
Path::new(path)
}
/// Setup client interface
-pub async fn setup_interface_tunnel(
- tunnel: &Tunnel,
- interface_name: String,
-) -> Result<(), Error> {
+pub async fn setup_interface_tunnel(tunnel: &Tunnel, name: &str) -> Result {
debug!("Setting up interface for tunnel {tunnel}");
+ let interface_name = get_interface_name(name);
// prepare peer config
debug!(
"Decoding tunnel {tunnel} public key: {}.",
@@ -379,10 +338,7 @@ pub async fn setup_interface_tunnel(
tunnel.allowed_ips
);
let allowed_ips = if tunnel.route_all_traffic {
- debug!(
- "Using all traffic routing for tunnel {tunnel}: {DEFAULT_ROUTE_IPV4} \
- {DEFAULT_ROUTE_IPV6}"
- );
+ debug!("Using all traffic routing for tunnel {tunnel}");
vec![DEFAULT_ROUTE_IPV4.into(), DEFAULT_ROUTE_IPV6.into()]
} else {
let msg = match &tunnel.allowed_ips {
@@ -411,7 +367,7 @@ pub async fn setup_interface_tunnel(
debug!("Parsed tunnel {tunnel} allowed IPs: {:?}", peer.allowed_ips);
// request interface configuration
- debug!("Looking for a free port for interface {interface_name}...");
+ debug!("Looking for a free port for interface {interface_name}.");
let Some(port) = find_free_tcp_port() else {
let msg = format!(
"Couldn't find free port for interface {interface_name} while setting up tunnel {tunnel}"
@@ -433,17 +389,16 @@ pub async fn setup_interface_tunnel(
Error::InternalError(msg)
})?;
let interface_config = InterfaceConfiguration {
- name: interface_name,
+ name: interface_name.clone(),
prvkey: tunnel.prvkey.clone(),
addresses,
- port: port.into(),
+ port,
peers: vec![peer.clone()],
mtu: None,
};
debug!("Creating interface {interface_config:?}");
let request = CreateInterfaceRequest {
config: Some(interface_config.clone().into()),
- allowed_ips,
dns: tunnel.dns.clone(),
};
if let Some(pre_up) = &tunnel.pre_up {
@@ -492,7 +447,7 @@ pub async fn setup_interface_tunnel(
"Created interface {} with config: {interface_config:?}",
interface_config.name
);
- Ok(())
+ Ok(interface_name)
}
}
@@ -618,8 +573,7 @@ pub(crate) async fn handle_connection_for_location(
) -> Result<(), Error> {
debug!("Setting up the connection for location {}", location.name);
let state = handle.state::();
- let interface_name = get_interface_name(&location.name);
- setup_interface(location, interface_name.clone(), preshared_key, &DB_POOL).await?;
+ let interface_name = setup_interface(location, &location.name, preshared_key, &DB_POOL).await?;
state
.add_connection(location.id, &interface_name, ConnectionType::Location)
.await;
@@ -650,8 +604,7 @@ pub(crate) async fn handle_connection_for_tunnel(
) -> Result<(), Error> {
debug!("Setting up the connection for tunnel: {}", tunnel.name);
let state = handle.state::();
- let interface_name = get_interface_name(&tunnel.name);
- setup_interface_tunnel(tunnel, interface_name.clone()).await?;
+ let interface_name = setup_interface_tunnel(tunnel, &tunnel.name).await?;
state
.add_connection(tunnel.id, &interface_name, ConnectionType::Tunnel)
.await;
@@ -720,32 +673,48 @@ pub(crate) async fn disconnect_interface(
);
return Err(Error::NotFound);
};
- let request = RemoveInterfaceRequest {
- interface_name,
- endpoint: location.endpoint.clone(),
- };
- debug!(
- "Sending request to the background service to remove interface {} for location \
- {}...",
- active_connection.interface_name, location.name
- );
- if let Err(error) = client.remove_interface(request).await {
- let msg = if error.code() == Code::Unavailable {
- format!(
- "Couldn't remove interface {}. Background service is unavailable. \
- Please make sure the service is running. Error: {error}.",
- active_connection.interface_name
- )
- } else {
- format!(
- "Failed to send a request to the background service to remove interface \
- {}. Error: {error}.",
- active_connection.interface_name
- )
+
+ #[cfg(target_os = "macos")]
+ {
+ let result = unsafe {
+ let name: SRString = location.name.as_str().into();
+ stop_tunnel(&name)
};
- error!("{msg}");
- return Err(Error::InternalError(msg));
+ error!("stop_tunnel() returned {result:?}");
+ if !result {
+ return Err(Error::InternalError("Error from Swift".into()));
+ }
}
+
+ #[cfg(not(target_os = "macos"))]
+ {
+ let request = RemoveInterfaceRequest {
+ interface_name,
+ endpoint: location.endpoint.clone(),
+ };
+ debug!(
+ "Sending request to the background service to remove interface {} for location \
+ {}...",
+ active_connection.interface_name, location.name
+ );
+ if let Err(error) = client.remove_interface(request).await {
+ let msg = if error.code() == Code::Unavailable {
+ format!(
+ "Couldn't remove interface {}. Background service is unavailable. \
+ Please make sure the service is running. Error: {error}.",
+ active_connection.interface_name
+ )
+ } else {
+ format!(
+ "Failed to send a request to the background service to remove interface \
+ {}. Error: {error}.",
+ active_connection.interface_name
+ )
+ };
+ error!("{msg}");
+ }
+ }
+
let connection: Connection = active_connection.into();
let connection = connection.save(&*DB_POOL).await?;
debug!(
@@ -854,7 +823,7 @@ pub async fn get_tunnel_or_location_name(id: Id, connection_type: ConnectionType
// Check if location/tunnel is connected and WireGuard Windows service is running.
// `id`: location or tunnel Id
// `name`: location or tunnel name
-#[cfg(target_os = "windows")]
+#[cfg(windows)]
async fn check_connection(
service_manager: &ServiceManager,
id: Id,
@@ -938,7 +907,7 @@ async fn check_connection(
// TODO: Move the connection handling to a seperate, common function,
// so `handle_connection_for_location` and `handle_connection_for_tunnel` are not
// partially duplicated here.
-#[cfg(target_os = "windows")]
+#[cfg(windows)]
pub async fn sync_connections(app_handle: &AppHandle) -> Result<(), Error> {
debug!("Synchronizing active connections with the systems' state...");
let all_locations = Location::all(&*DB_POOL, false).await?;
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 20b15b53..d3b64670 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -41,11 +41,13 @@
}
},
"macOS": {
- "entitlements": null,
- "exceptionDomain": "",
- "frameworks": [],
- "providerShortName": null,
- "signingIdentity": null
+ "bundleVersion": "@BUILD_NUMBER@",
+ "entitlements": "./Client.entitlements",
+ "files": {
+ "embedded.provisionprofile": "Defguard_Client_Mac_App_Store.provisionprofile",
+ "PlugIns/VPNExtension.appex": "../swift/extension/build/Release/VPNExtension.appex"
+ },
+ "minimumSystemVersion": "13.5"
},
"resources": [
"resources/icons/*"
diff --git a/src-tauri/tauri.linux.conf.json b/src-tauri/tauri.linux.conf.json
index 1ceacbf7..c946ac5d 100644
--- a/src-tauri/tauri.linux.conf.json
+++ b/src-tauri/tauri.linux.conf.json
@@ -1,5 +1,10 @@
{
+ "build": {
+ "features": [
+ "service"
+ ]
+ },
"bundle": {
"longDescription": "IMPORTANT: Reboot or Re-login Required\nOn initial install the user is added to the defguard group.\nA reboot or logging out and back in is required for group membership changes to take effect.\nThis is not required on subsequent updates."
}
-}
\ No newline at end of file
+}
diff --git a/src-tauri/tauri.macos.conf.json b/src-tauri/tauri.macos.conf.json
index bb2a7dfc..39e50dd0 100644
--- a/src-tauri/tauri.macos.conf.json
+++ b/src-tauri/tauri.macos.conf.json
@@ -1,11 +1,8 @@
{
- "bundle": {
- "externalBin": [
- "resources-macos/binaries/*"
- ],
- "resources": [
- "resources-macos/resources/*",
- "resources/icons/*"
- ]
+ "build": {
+ "beforeBundleCommand": {
+ "cwd": "../swift",
+ "script": "./build.sh"
+ }
}
}
diff --git a/swift/.gitignore b/swift/.gitignore
new file mode 100644
index 00000000..b996f867
--- /dev/null
+++ b/swift/.gitignore
@@ -0,0 +1,11 @@
+.DS_Store
+*/.build
+build
+/Packages
+xcuserdata/
+DerivedData/
+.swiftpm/config/registries.json
+.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
+.netrc
+Package.resolved
+extension/BoringTun
diff --git a/swift/boringtun b/swift/boringtun
new file mode 160000
index 00000000..f47e80a9
--- /dev/null
+++ b/swift/boringtun
@@ -0,0 +1 @@
+Subproject commit f47e80a96923733bb9ed2bd5590f882dfb1d9b95
diff --git a/swift/build.sh b/swift/build.sh
new file mode 100755
index 00000000..04112714
--- /dev/null
+++ b/swift/build.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+set -e
+
+DST="${PWD}/extension/BoringTun"
+CARGO="${HOME}/.cargo/bin/cargo"
+RUSTUP="${HOME}/.cargo/bin/rustup"
+
+export MACOSX_DEPLOYMENT_TARGET=13.5
+
+# Build BoringTun.
+
+pushd boringtun
+
+for TARGET in aarch64-apple-darwin x86_64-apple-darwin
+do
+ ${RUSTUP} target add "${TARGET}"
+ ${CARGO} build --lib --locked --release --target ${TARGET}
+done
+
+# Create universal library.
+
+mkdir -p target/universal/release
+lipo -create \
+ target/aarch64-apple-darwin/release/libboringtun.a \
+ target/x86_64-apple-darwin/release/libboringtun.a \
+ -output target/universal/release/libboringtun.a
+
+rm -f -r target/uniffi
+${CARGO} run --release --bin uniffi-bindgen -- \
+ --xcframework --headers --modulemap --swift-sources \
+ target/aarch64-apple-darwin/release/libboringtun.a target/uniffi
+
+# Install BoringTun framework.
+
+mkdir -p "${DST}"
+cp -c target/uniffi/boringtun.swift "${DST}/"
+rm -f -r "${DST}/boringtun.xcframework"
+xcodebuild -create-xcframework \
+ -library target/universal/release/libboringtun.a \
+ -headers target/uniffi \
+ -output ${DST}/boringtun.xcframework
+cp -c target/uniffi/boringtunFFI.h "${DST}/"
+
+popd
+
+# Build VPNExtension.
+
+# if [ "${TAURI_ENV_DEBUG}" = 'false' ]; then
+ CONFIG=Release
+# else
+# CONFIG=Debug
+# fi
+xcodebuild -project extension/VPNExtension.xcodeproj -target VPNExtension -configuration ${CONFIG} build
diff --git a/swift/extension/VPNExtension.xcodeproj/project.pbxproj b/swift/extension/VPNExtension.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..2a253c04
--- /dev/null
+++ b/swift/extension/VPNExtension.xcodeproj/project.pbxproj
@@ -0,0 +1,463 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 77;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 66CABCD82EA76D070057D1AF /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66CABCD72EA76D060057D1AF /* NetworkExtension.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 66CABCD42EA76D060057D1AF /* VPNExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = VPNExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+ 66CABCD72EA76D060057D1AF /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
+/* End PBXFileReference section */
+
+/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
+ 66CABCDE2EA76D070057D1AF /* Exceptions for "VPNExtension" folder in "VPNExtension" target */ = {
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+ membershipExceptions = (
+ Info.plist,
+ );
+ target = 66CABCD32EA76D060057D1AF /* VPNExtension */;
+ };
+/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
+
+/* Begin PBXFileSystemSynchronizedRootGroup section */
+ 660FE5002EA779E8006A7447 /* Defguard */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ name = Defguard;
+ path = ../plugin/Sources/Defguard;
+ sourceTree = SOURCE_ROOT;
+ };
+ 660FE50B2EA77C68006A7447 /* BoringTun */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ path = BoringTun;
+ sourceTree = "";
+ };
+ 66CABCD92EA76D070057D1AF /* VPNExtension */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ exceptions = (
+ 66CABCDE2EA76D070057D1AF /* Exceptions for "VPNExtension" folder in "VPNExtension" target */,
+ );
+ path = VPNExtension;
+ sourceTree = "";
+ };
+/* End PBXFileSystemSynchronizedRootGroup section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 66CABCD12EA76D060057D1AF /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 66CABCD82EA76D070057D1AF /* NetworkExtension.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 66CABCC92EA76CD80057D1AF = {
+ isa = PBXGroup;
+ children = (
+ 660FE50B2EA77C68006A7447 /* BoringTun */,
+ 660FE5002EA779E8006A7447 /* Defguard */,
+ 66CABCD92EA76D070057D1AF /* VPNExtension */,
+ 66CABCD62EA76D060057D1AF /* Frameworks */,
+ 66CABCD52EA76D060057D1AF /* Products */,
+ );
+ sourceTree = "";
+ };
+ 66CABCD52EA76D060057D1AF /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 66CABCD42EA76D060057D1AF /* VPNExtension.appex */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 66CABCD62EA76D060057D1AF /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 66CABCD72EA76D060057D1AF /* NetworkExtension.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 66CABCD32EA76D060057D1AF /* VPNExtension */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 66CABCDF2EA76D070057D1AF /* Build configuration list for PBXNativeTarget "VPNExtension" */;
+ buildPhases = (
+ 66CABCD02EA76D060057D1AF /* Sources */,
+ 66CABCD12EA76D060057D1AF /* Frameworks */,
+ 66CABCD22EA76D060057D1AF /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ fileSystemSynchronizedGroups = (
+ 660FE5002EA779E8006A7447 /* Defguard */,
+ 660FE50B2EA77C68006A7447 /* BoringTun */,
+ 66CABCD92EA76D070057D1AF /* VPNExtension */,
+ );
+ name = VPNExtension;
+ packageProductDependencies = (
+ );
+ productName = VPNExtension;
+ productReference = 66CABCD42EA76D060057D1AF /* VPNExtension.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 66CABCCA2EA76CD80057D1AF /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 2600;
+ LastUpgradeCheck = 2610;
+ TargetAttributes = {
+ 66CABCD32EA76D060057D1AF = {
+ CreatedOnToolsVersion = 26.0.1;
+ };
+ };
+ };
+ buildConfigurationList = 66CABCCD2EA76CD80057D1AF /* Build configuration list for PBXProject "VPNExtension" */;
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 66CABCC92EA76CD80057D1AF;
+ minimizedProjectReferenceProxies = 1;
+ preferredProjectObjectVersion = 77;
+ productRefGroup = 66CABCD52EA76D060057D1AF /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 66CABCD32EA76D060057D1AF /* VPNExtension */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 66CABCD22EA76D060057D1AF /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 66CABCD02EA76D060057D1AF /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 66CABCCE2EA76CD80057D1AF /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ DEAD_CODE_STRIPPING = YES;
+ DEVELOPMENT_TEAM = 82GZ7KN29J;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/BoringTun/boringtunFFI.h";
+ };
+ name = Debug;
+ };
+ 66CABCCF2EA76CD80057D1AF /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ DEAD_CODE_STRIPPING = YES;
+ DEVELOPMENT_TEAM = 82GZ7KN29J;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/BoringTun/boringtunFFI.h";
+ };
+ name = Release;
+ };
+ 66CABCE02EA76D070057D1AF /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_ENTITLEMENTS = VPNExtension/VPNExtension.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = "@BUILD_NUMBER@";
+ DEAD_CODE_STRIPPING = YES;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = 82GZ7KN29J;
+ ENABLE_APP_SANDBOX = YES;
+ ENABLE_HARDENED_RUNTIME = YES;
+ ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
+ ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
+ ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
+ ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
+ ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
+ ENABLE_RESOURCE_ACCESS_CAMERA = NO;
+ ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
+ ENABLE_RESOURCE_ACCESS_LOCATION = NO;
+ ENABLE_RESOURCE_ACCESS_PRINTING = NO;
+ ENABLE_RESOURCE_ACCESS_USB = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = VPNExtension/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = VPNExtension;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@executable_path/../../../../Frameworks",
+ );
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MACOSX_DEPLOYMENT_TARGET = 13.5;
+ MARKETING_VERSION = 1.6.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = net.defguard.VPNExtension;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ REGISTER_APP_GROUPS = YES;
+ SDKROOT = macosx;
+ SKIP_INSTALL = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ };
+ name = Debug;
+ };
+ 66CABCE12EA76D070057D1AF /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_ENTITLEMENTS = VPNExtension/VPNExtension.entitlements;
+ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
+ CODE_SIGN_STYLE = Manual;
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = "@BUILD_NUMBER@";
+ DEAD_CODE_STRIPPING = YES;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEVELOPMENT_TEAM = "";
+ "DEVELOPMENT_TEAM[sdk=macosx*]" = 82GZ7KN29J;
+ ENABLE_APP_SANDBOX = YES;
+ ENABLE_HARDENED_RUNTIME = YES;
+ ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
+ ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
+ ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
+ ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
+ ENABLE_RESOURCE_ACCESS_CAMERA = NO;
+ ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
+ ENABLE_RESOURCE_ACCESS_LOCATION = NO;
+ ENABLE_RESOURCE_ACCESS_PRINTING = NO;
+ ENABLE_RESOURCE_ACCESS_USB = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = VPNExtension/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = VPNExtension;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@executable_path/../../../../Frameworks",
+ );
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MACOSX_DEPLOYMENT_TARGET = 13.5;
+ MARKETING_VERSION = 1.6.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = net.defguard.VPNExtension;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Defguard VPNExtension Mac App Store";
+ REGISTER_APP_GROUPS = YES;
+ SDKROOT = macosx;
+ SKIP_INSTALL = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 66CABCCD2EA76CD80057D1AF /* Build configuration list for PBXProject "VPNExtension" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 66CABCCE2EA76CD80057D1AF /* Debug */,
+ 66CABCCF2EA76CD80057D1AF /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 66CABCDF2EA76D070057D1AF /* Build configuration list for PBXNativeTarget "VPNExtension" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 66CABCE02EA76D070057D1AF /* Debug */,
+ 66CABCE12EA76D070057D1AF /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 66CABCCA2EA76CD80057D1AF /* Project object */;
+}
diff --git a/swift/extension/VPNExtension.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/swift/extension/VPNExtension.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..919434a6
--- /dev/null
+++ b/swift/extension/VPNExtension.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/swift/extension/VPNExtension/Adapter.swift b/swift/extension/VPNExtension/Adapter.swift
new file mode 100644
index 00000000..f60864c7
--- /dev/null
+++ b/swift/extension/VPNExtension/Adapter.swift
@@ -0,0 +1,295 @@
+import Foundation
+import Network
+import NetworkExtension
+import os
+
+/// State of Adapter.
+enum State {
+ /// Tunnel is running.
+ case running
+ /// Tunnel is stopped.
+ case stopped
+ /// Tunnel is temporary unavaiable due to device being offline.
+ case dormant
+}
+
+@preconcurrency final class Adapter /*: Sendable*/ {
+ /// Packet tunnel provider.
+ private weak var packetTunnelProvider: NEPacketTunnelProvider?
+ /// BortingTun tunnel
+ private var tunnel: Tunnel?
+ /// UDP endpoint
+ private var endpoint: Network.NWEndpoint?
+ /// Server connection
+ private var connection: NWConnection?
+ /// Network routes monitor.
+ private var networkMonitor: NWPathMonitor?
+ /// Keep alive timer
+ private var keepAliveTimer: Timer?
+ /// Logging
+ private lazy var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "Adapter")
+ /// Adapter state.
+ private var state: State = .stopped
+
+ private let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
+
+ /// Designated initializer.
+ /// - Parameter packetTunnelProvider: an instance of `NEPacketTunnelProvider`. Internally stored
+ init(with packetTunnelProvider: NEPacketTunnelProvider) {
+ self.packetTunnelProvider = packetTunnelProvider
+ }
+
+ deinit {
+ self.stop()
+ }
+
+ func start(tunnelConfiguration: TunnelConfiguration) throws {
+ guard case .stopped = self.state else {
+ logger.error("Invalid state")
+ // TODO: throw invalid state
+ return
+ }
+
+ if tunnel != nil {
+ logger.info("Cleaning exiting Tunnel")
+ tunnel = nil
+ connection = nil
+ }
+
+ let networkMonitor = NWPathMonitor()
+ networkMonitor.pathUpdateHandler = { [weak self] path in
+ self?.networkPathUpdate(path: path)
+ }
+ networkMonitor.start(queue: .main)
+ self.networkMonitor = networkMonitor
+
+ logger.info("Initializing Tunnel")
+ tunnel = try Tunnel.init(
+ privateKey: tunnelConfiguration.privateKey,
+ serverPublicKey: tunnelConfiguration.peers[0].publicKey,
+ presharedKey: tunnelConfiguration.peers[0].preSharedKey,
+ keepAlive: tunnelConfiguration.peers[0].persistentKeepAlive,
+ index: 0
+ )
+
+ logger.info("Connecting to endpoint")
+ guard let endpoint = tunnelConfiguration.peers[0].endpoint else {
+ logger.error("Endpoint is nil")
+ return
+ }
+ self.endpoint = endpoint.asNWEndpoint()
+ initEndpoint()
+
+ logger.info("Sniffing packets")
+ readPackets()
+
+ state = .running
+
+ // Test notifications
+ // let notificationName = CFNotificationName("net.defguard.client.start" as CFString)
+ // CFNotificationCenterPostNotification(notificationCenter, notificationName, nil, nil, false)
+ }
+
+ func stop() {
+ logger.info("Stopping Adapter")
+ connection?.cancel()
+ connection = nil
+ tunnel = nil
+ keepAliveTimer?.invalidate()
+ keepAliveTimer = nil
+ // Cancel network monitor
+ networkMonitor?.cancel()
+ networkMonitor = nil
+
+ state = .stopped
+ logger.info("Tunnel stopped")
+
+ // Test notifications
+ // let notificationName = CFNotificationName("net.defguard.client.stop" as CFString)
+ // CFNotificationCenterPostNotification(notificationCenter, notificationName, nil, nil, false)
+ }
+
+ // Obtain tunnel statistics.
+ func stats() -> Stats? {
+ if let stats = tunnel?.stats() {
+ return Stats(txBytes: stats.txBytes, rxBytes: stats.rxBytes)
+ }
+ return nil
+ }
+
+ private func handleTunnelResult(_ result: TunnelResult) {
+ switch result {
+ case .done:
+ // Nothing to do.
+ break
+ case .err(let error):
+ logger.error("Tunnel error \(error, privacy: .public)")
+ switch error {
+ case .InvalidAeadTag:
+ logger.error("Invalid pre-shared key; stopping tunnel")
+ // The correct way is to call the packet tunnel provider, if there is one.
+ if let provider = packetTunnelProvider {
+ provider.cancelTunnelWithError(error)
+ } else {
+ stop()
+ }
+ case .ConnectionExpired:
+ logger.error("Connecion has expired; re-connecting")
+ packetTunnelProvider?.reasserting = true
+ initEndpoint()
+ packetTunnelProvider?.reasserting = false
+ default:
+ break
+ }
+ case .writeToNetwork(let data):
+ sendToEndpoint(data: data)
+ case .writeToTunnelV4(let data):
+ packetTunnelProvider?.packetFlow.writePacketObjects([
+ NEPacket(data: data, protocolFamily: sa_family_t(AF_INET))
+ ])
+ case .writeToTunnelV6(let data):
+ packetTunnelProvider?.packetFlow.writePacketObjects([
+ NEPacket(data: data, protocolFamily: sa_family_t(AF_INET6))
+ ])
+ }
+ }
+
+ /// Initialise UDP connection to endpoint.
+ private func initEndpoint() {
+ guard let endpoint = endpoint else { return }
+
+ logger.info("Init Endpoint")
+ // Cancel previous connection
+ connection?.cancel()
+ connection = nil
+
+ let params = NWParameters.udp
+ params.allowLocalEndpointReuse = true
+ let connection = NWConnection.init(to: endpoint, using: params)
+ connection.stateUpdateHandler = { [weak self] state in
+ self?.endpointStateChange(state: state)
+ }
+
+ connection.start(queue: .main)
+ self.connection = connection
+ }
+
+ /// Setup UDP connection to endpoint. This method should be called when UDP connection is ready to send and receive.
+ private func setupEndpoint() {
+ logger.info("Setup endpoint")
+
+ // Send initial handshake packet
+ if let tunnel = self.tunnel {
+ handleTunnelResult(tunnel.forceHandshake())
+ }
+ logger.info("Receiving UDP from endpoint")
+ logger.debug(
+ "NWConnection path \(String(describing: self.connection?.currentPath), privacy: .public)"
+ )
+ receive()
+
+ // Use Timer to send keep-alive packets.
+ keepAliveTimer?.invalidate()
+ logger.info("Creating keep-alive timer")
+ let timer = Timer(timeInterval: 0.25, repeats: true) { [weak self] timer in
+ guard let self = self, let tunnel = self.tunnel else { return }
+ self.handleTunnelResult(tunnel.tick())
+ }
+ keepAliveTimer = timer
+ RunLoop.main.add(timer, forMode: .common)
+ }
+
+ /// Send packets to UDP endpoint.
+ private func sendToEndpoint(data: Data) {
+ guard let connection = connection else { return }
+ if connection.state == .ready {
+ connection.send(
+ content: data,
+ completion: .contentProcessed { error in
+ if let error = error {
+ self.logger.error("UDP connection send error: \(error, privacy: .public)")
+ }
+ })
+ } else {
+ logger.warning("UDP connection not ready to send")
+ }
+ }
+
+ /// Handle UDP packets from the endpoint.
+ private func receive() {
+ connection?.receiveMessage { [weak self] data, context, isComplete, error in
+ guard let self = self else { return }
+ if let data = data, let tunnel = self.tunnel {
+ self.handleTunnelResult(tunnel.read(src: data))
+ }
+ if error == nil {
+ // continue receiving
+ self.receive()
+ } else {
+ logger.error("receive() error: \(error)")
+ }
+ }
+ }
+
+ /// Read tunnel packets.
+ private func readPackets() {
+ guard let tunnel = self.tunnel else { return }
+
+ // Packets received to the tunnel's virtual interface.
+ packetTunnelProvider?.packetFlow.readPacketObjects { packets in
+ for packet in packets {
+ self.handleTunnelResult(tunnel.write(src: packet.data))
+ }
+ // continue reading
+ self.readPackets()
+ }
+ }
+
+ /// Handle UDP connection state changes.
+ private func endpointStateChange(state: NWConnection.State) {
+ logger.debug("UDP connection state: \(String(describing: state), privacy: .public)")
+ switch state {
+ case .ready:
+ setupEndpoint()
+ //case .waiting(let error):
+ // switch error {
+ // case .posix(_):
+ // connection?.restart()
+ // default:
+ // self.stop()
+ // }
+ case .failed(let error):
+ logger.error("Failed to establish endpoint connection: \(error)")
+ // The correct way is to call the packet tunnel provider, if there is one.
+ if let provider = packetTunnelProvider {
+ provider.cancelTunnelWithError(error)
+ } else {
+ stop()
+ }
+ default:
+ break
+ }
+ }
+
+ /// Handle network path updates.
+ private func networkPathUpdate(path: Network.NWPath) {
+ logger
+ .debug(
+ "Network path status \(String(describing: path.status), privacy: .public); interfaces \(path.availableInterfaces, privacy: .public)"
+ )
+ if path.status == .unsatisfied {
+ if state == .running {
+ logger.warning("Unsatisfied network path: going dormant")
+ connection?.cancel()
+ connection = nil
+ state = .dormant
+ }
+ } else {
+ if state == .dormant {
+ logger.warning("Satisfied network path: going running")
+ initEndpoint()
+ state = .running
+ }
+ }
+ }
+}
diff --git a/swift/extension/VPNExtension/Info.plist b/swift/extension/VPNExtension/Info.plist
new file mode 100644
index 00000000..3059459e
--- /dev/null
+++ b/swift/extension/VPNExtension/Info.plist
@@ -0,0 +1,13 @@
+
+
+
+
+ NSExtension
+
+ NSExtensionPointIdentifier
+ com.apple.networkextension.packet-tunnel
+ NSExtensionPrincipalClass
+ $(PRODUCT_MODULE_NAME).PacketTunnelProvider
+
+
+
diff --git a/swift/extension/VPNExtension/PacketTunnelProvider.swift b/swift/extension/VPNExtension/PacketTunnelProvider.swift
new file mode 100644
index 00000000..addccb60
--- /dev/null
+++ b/swift/extension/VPNExtension/PacketTunnelProvider.swift
@@ -0,0 +1,79 @@
+import NetworkExtension
+import os
+
+enum WireGuardTunnelError: Error {
+ case invalidTunnelConfiguration
+}
+
+class PacketTunnelProvider: NEPacketTunnelProvider {
+ /// Logging
+ private var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PacketTunnelProvider")
+
+ private lazy var adapter: Adapter = {
+ return Adapter(with: self)
+ }()
+
+ override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
+ logger.debug("\(#function)")
+ if let options = options {
+ logger.log("Options: \(options)")
+ }
+
+ guard let protocolConfig = self.protocolConfiguration as? NETunnelProviderProtocol,
+ let providerConfig = protocolConfig.providerConfiguration,
+ let tunnelConfig = try? TunnelConfiguration.from(dictionary: providerConfig) else {
+ completionHandler(WireGuardTunnelError.invalidTunnelConfiguration)
+ return
+ }
+
+ let networkSettings = tunnelConfig.asNetworkSettings()
+ self.setTunnelNetworkSettings(networkSettings) { error in
+ if error != nil {
+ self.logger.error("Set tunnel network settings returned \(error)")
+ }
+ completionHandler(error)
+ return
+ }
+
+ do {
+ try adapter.start(tunnelConfiguration: tunnelConfig)
+ } catch {
+ logger.error("Failed to start tunnel")
+ completionHandler(error)
+ }
+ logger.info("Tunnel started")
+
+ completionHandler(nil)
+ }
+
+ override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
+ logger.debug("\(#function)")
+ adapter.stop()
+ logger.info("Tunnel stopped")
+ completionHandler()
+ }
+
+ override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
+ logger.debug("\(#function)")
+ // TODO: messageData should contain a valid message.
+ if let handler = completionHandler {
+ if let stats = adapter.stats() {
+ let data = try? JSONEncoder().encode(stats)
+ handler(data)
+ } else {
+ handler(nil)
+ }
+ }
+ }
+
+ override func sleep(completionHandler: @escaping () -> Void) {
+ logger.debug("\(#function)")
+ // Add code here to get ready to sleep.
+ completionHandler()
+ }
+
+ override func wake() {
+ logger.debug("\(#function)")
+ // Add code here to wake up.
+ }
+}
diff --git a/swift/extension/VPNExtension/VPNExtension.entitlements b/swift/extension/VPNExtension/VPNExtension.entitlements
new file mode 100644
index 00000000..dbbd0259
--- /dev/null
+++ b/swift/extension/VPNExtension/VPNExtension.entitlements
@@ -0,0 +1,16 @@
+
+
+
+
+ com.apple.developer.networking.networkextension
+
+ packet-tunnel-provider
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.network.client
+
+ com.apple.security.network.server
+
+
+
diff --git a/swift/plugin/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/swift/plugin/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..919434a6
--- /dev/null
+++ b/swift/plugin/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/swift/plugin/Package.swift b/swift/plugin/Package.swift
new file mode 100644
index 00000000..4e027349
--- /dev/null
+++ b/swift/plugin/Package.swift
@@ -0,0 +1,33 @@
+// swift-tools-version:5.3
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "defguard-vpn-plugin",
+ platforms: [
+ .macOS("13.5"),
+ .iOS("15.6"),
+ ],
+ products: [
+ // Products define the executables and libraries a package produces, and make them visible to other packages.
+ .library(
+ name: "defguard-vpn-plugin",
+ type: .static,
+ targets: ["defguard-vpn-plugin"])
+ ],
+ dependencies: [
+ .package(url: "https://github.com/Brendonovich/swift-rs", from: "1.0.7")
+ ],
+ targets: [
+ .target(
+ name: "defguard-vpn-plugin",
+ dependencies: [
+ .product(
+ name: "SwiftRs",
+ package: "swift-rs"
+ )
+ ],
+ path: "Sources")
+ ]
+)
diff --git a/swift/plugin/Sources/Defguard/Decodabe+Encodable.swift b/swift/plugin/Sources/Defguard/Decodabe+Encodable.swift
new file mode 100644
index 00000000..d3f8a8f0
--- /dev/null
+++ b/swift/plugin/Sources/Defguard/Decodabe+Encodable.swift
@@ -0,0 +1,22 @@
+import Foundation
+
+extension Decodable {
+ static func from(dictionary: [String: Any]) throws -> Self {
+ let data = try JSONSerialization.data(withJSONObject: dictionary)
+ let decoder = JSONDecoder()
+ return try decoder.decode(Self.self, from: data)
+ }
+}
+
+extension Encodable {
+ func toDictionary() throws -> [String: Any] {
+ let data = try JSONEncoder().encode(self)
+ let jsonObject = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
+ guard let dictionary = jsonObject as? [String: Any] else {
+ throw NSError(
+ domain: "EncodingError", code: 0,
+ userInfo: [NSLocalizedDescriptionKey: "Failed to convert to dictionary"])
+ }
+ return dictionary
+ }
+}
diff --git a/swift/plugin/Sources/Defguard/Endpoint.swift b/swift/plugin/Sources/Defguard/Endpoint.swift
new file mode 100644
index 00000000..64f50a40
--- /dev/null
+++ b/swift/plugin/Sources/Defguard/Endpoint.swift
@@ -0,0 +1,83 @@
+import Foundation
+import Network
+
+struct Endpoint: Codable, CustomStringConvertible {
+ let host: NWEndpoint.Host
+ let port: NWEndpoint.Port
+
+ init(host: NWEndpoint.Host, port: NWEndpoint.Port) {
+ self.host = host
+ self.port = port
+ }
+
+ /// Custom initializer from String. Assume format "host:port".
+ init?(from string: String) {
+ let trimmedEndpoint = string.trimmingCharacters(in: .whitespaces)
+ var endpointHost = trimmedEndpoint
+
+ // Extract host, supporting IPv4, IPv6, and domains
+ if trimmedEndpoint.hasPrefix("[") { // IPv6 with port, e.g. [fd00::1]:51820
+ if let closing = trimmedEndpoint.firstIndex(of: "]") {
+ endpointHost = String(
+ trimmedEndpoint[
+ trimmedEndpoint.index(after: trimmedEndpoint.startIndex).. 1 {
+ endpointHost = parts.dropLast().joined(separator: ":")
+ }
+ }
+
+ let endpointPort: Network.NWEndpoint.Port
+ if let portPart = trimmedEndpoint.split(separator: ":").last, let port = Int(portPart),
+ let nwPort = NWEndpoint.Port(rawValue: UInt16(port))
+ {
+ endpointPort = nwPort
+ } else {
+ return nil
+ }
+
+ self.host = NWEndpoint.Host(endpointHost)
+ self.port = endpointPort
+ }
+
+ /// A textual representation of this instance. Required for `CustomStringConvertible`.
+ var description: String {
+ "Endpoint(\(host):\(port))"
+ }
+
+ var hostString: String {
+ "\(host)"
+ }
+
+ func toString() -> String {
+ "\(host):\(port)"
+ }
+
+ // Encode to a single string "host:port", to smoothly encode into JSON.
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.singleValueContainer()
+ try container.encode(self.toString())
+ }
+
+ // Decode from a single string "host:port", to smoothly decode from JSON.
+ init(from decoder: Decoder) throws {
+ let container = try decoder.singleValueContainer()
+ let value = try container.decode(String.self)
+ guard let endpoint = Endpoint(from: value) else {
+ throw
+ DecodingError
+ .dataCorrupted(
+ DecodingError.Context(
+ codingPath: decoder.codingPath,
+ debugDescription: "Not in host:port format")
+ )
+ }
+ self = endpoint
+ }
+
+ func asNWEndpoint() -> NWEndpoint {
+ NWEndpoint.hostPort(host: host, port: port)
+ }
+}
diff --git a/swift/plugin/Sources/Defguard/IpAddrMask.swift b/swift/plugin/Sources/Defguard/IpAddrMask.swift
new file mode 100644
index 00000000..afb347c9
--- /dev/null
+++ b/swift/plugin/Sources/Defguard/IpAddrMask.swift
@@ -0,0 +1,109 @@
+import Foundation
+import Network
+
+struct IpAddrMask: Codable, Equatable {
+ let address: IPAddress
+ let cidr: UInt8
+
+ init(address: IPAddress, cidr: UInt8) {
+ self.address = address
+ self.cidr = cidr
+ }
+
+ init?(fromString string: String) {
+ let parts = string.split(
+ separator: "/",
+ maxSplits: 1,
+ )
+ if let ipv4 = IPv4Address(String(parts[0])) {
+ address = ipv4
+ } else if let ipv6 = IPv6Address(String(parts[0])) {
+ address = ipv6
+ } else {
+ return nil
+ }
+ if parts.count > 1 {
+ cidr = UInt8(parts[1]) ?? 0
+ } else {
+ cidr = 0
+ }
+ }
+
+ var stringRepresentation: String {
+ return "\(address)/\(cidr)"
+ }
+
+ enum CodingKeys: String, CodingKey {
+ case address
+ case cidr
+ }
+
+ /// Conform to `Encodable`.
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode("\(address)", forKey: .address)
+ try container.encode(cidr, forKey: .cidr)
+ }
+
+ /// Conform to `Decodable`.
+ init(from decoder: Decoder) throws {
+ let values = try decoder.container(keyedBy: CodingKeys.self)
+
+ let address_string = try values.decode(String.self, forKey: .address)
+ if let ipv4 = IPv4Address(address_string) {
+ address = ipv4
+ } else if let ipv6 = IPv6Address(address_string) {
+ address = ipv6
+ } else {
+ throw
+ DecodingError
+ .dataCorrupted(
+ DecodingError.Context(
+ codingPath: decoder.codingPath,
+ debugDescription: "Unable to decode IP address"
+ ))
+ }
+
+ cidr = try values.decode(UInt8.self, forKey: .cidr)
+ }
+
+ /// Conform to `Equatable`.
+ static func == (lhs: Self, rhs: Self) -> Bool {
+ return lhs.address.rawValue == rhs.address.rawValue && lhs.cidr == rhs.cidr
+ }
+
+ func mask() -> IPAddress {
+ if address is IPv4Address {
+ var bytes = Data(count: 4)
+ let mask = cidr == 0 ? UInt32(0) : ~UInt32(0) << (32 - cidr)
+ for i in 0...3 {
+ bytes[i] = UInt8(truncatingIfNeeded: mask >> (24 - i * 8))
+ }
+ return IPv4Address(bytes)!
+ }
+ // Note: UInt128 is available since iOS 18. Use UInt64 implementation.
+ if address is IPv6Address {
+ var bytes = Data(count: 16)
+ let (mask_upper, mask_lower) =
+ if cidr < 64 {
+ (
+ cidr == 0 ? UInt64.min : UInt64.max << (64 - cidr),
+ UInt64.min
+ )
+ } else {
+ (
+ UInt64.max,
+ (cidr - 64) == 0 ? UInt64.min : UInt64.max << (128 - cidr)
+ )
+ }
+ for i in 0...7 {
+ bytes[i] = UInt8(truncatingIfNeeded: mask_upper >> (56 - i * 8))
+ }
+ for i in 8...15 {
+ bytes[i] = UInt8(truncatingIfNeeded: mask_lower >> (56 - (i - 8) * 8))
+ }
+ return IPv6Address(bytes)!
+ }
+ fatalError()
+ }
+}
diff --git a/swift/plugin/Sources/Defguard/Peer.swift b/swift/plugin/Sources/Defguard/Peer.swift
new file mode 100644
index 00000000..f405c31c
--- /dev/null
+++ b/swift/plugin/Sources/Defguard/Peer.swift
@@ -0,0 +1,43 @@
+import Foundation
+
+final class Peer: Codable {
+ var publicKey: String
+ var preSharedKey: String?
+ var endpoint: Endpoint?
+ var lastHandshake: Date?
+ var txBytes: UInt64 = 0
+ var rxBytes: UInt64 = 0
+ var persistentKeepAlive: UInt16?
+ var allowedIPs = [IpAddrMask]()
+
+ init(
+ publicKey: String, preSharedKey: String? = nil, endpoint: Endpoint? = nil,
+ lastHandshake: Date? = nil, txBytes: UInt64 = 0, rxBytes: UInt64 = 0,
+ persistentKeepAlive: UInt16? = nil, allowedIPs: [IpAddrMask] = [IpAddrMask]()
+ ) {
+ self.publicKey = publicKey
+ self.preSharedKey = preSharedKey
+ self.endpoint = endpoint
+ self.lastHandshake = lastHandshake
+ self.txBytes = txBytes
+ self.rxBytes = rxBytes
+ self.persistentKeepAlive = persistentKeepAlive
+ self.allowedIPs = allowedIPs
+ }
+
+ init(publicKey: String) {
+ self.publicKey = publicKey
+ }
+
+ // Use snake_case to match Rust.
+ enum CodingKeys: String, CodingKey {
+ case publicKey = "public_key"
+ case preSharedKey = "preshared_key"
+ case endpoint
+ case lastHandshake = "last_handshake"
+ case txBytes = "tx_bytes"
+ case rxBytes = "rx_bytes"
+ case persistentKeepAlive = "persistent_keepalive_interval"
+ case allowedIPs = "allowed_ips"
+ }
+}
diff --git a/swift/plugin/Sources/Defguard/Stats.swift b/swift/plugin/Sources/Defguard/Stats.swift
new file mode 100644
index 00000000..b7f97569
--- /dev/null
+++ b/swift/plugin/Sources/Defguard/Stats.swift
@@ -0,0 +1,11 @@
+import ObjectiveC
+
+public class Stats: NSObject, Codable {
+ var txBytes: UInt64
+ var rxBytes: UInt64
+
+ init(txBytes: UInt64, rxBytes: UInt64) {
+ self.txBytes = txBytes
+ self.rxBytes = rxBytes
+ }
+}
diff --git a/swift/plugin/Sources/Defguard/TunnelConfiguration.swift b/swift/plugin/Sources/Defguard/TunnelConfiguration.swift
new file mode 100644
index 00000000..a19c1e65
--- /dev/null
+++ b/swift/plugin/Sources/Defguard/TunnelConfiguration.swift
@@ -0,0 +1,117 @@
+import Foundation
+import NetworkExtension
+
+final class TunnelConfiguration: Codable {
+ var name: String
+ var privateKey: String
+ var addresses: [IpAddrMask] = []
+ var listenPort: UInt16?
+ var peers: [Peer] = []
+ var mtu: UInt32?
+ var dns: [String] = []
+ var dnsSearch: [String] = []
+
+ init(name: String, privateKey: String, peers: [Peer]) {
+ self.name = name
+ self.privateKey = privateKey
+ self.peers = peers
+
+ let peerPublicKeysArray = peers.map { $0.publicKey }
+ let peerPublicKeysSet = Set(peerPublicKeysArray)
+ if peerPublicKeysArray.count != peerPublicKeysSet.count {
+ fatalError("Two or more peers cannot have the same public key")
+ }
+ }
+
+ /// Only encode these properties.
+ enum CodingKeys: String, CodingKey {
+ case name
+ case privateKey
+ case addresses
+ case listenPort
+ case peers
+ case mtu
+ case dns
+ case dnsSearch
+ }
+
+ func asNetworkSettings() -> NEPacketTunnelNetworkSettings {
+ // Keep 127.0.0.1 as remote address for WireGuard.
+ let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
+
+ let (ipv4IncludedRoutes, ipv6IncludedRoutes) = routes()
+
+ // IPv4 addresses
+ let addrs_v4 = addresses.filter { $0.address is IPv4Address }
+ .map { String(describing: $0.address) }
+ let masks_v4 = addresses.filter { $0.address is IPv4Address }
+ .map { String(describing: $0.mask()) }
+ let ipv4Settings = NEIPv4Settings(addresses: addrs_v4, subnetMasks: masks_v4)
+ ipv4Settings.includedRoutes = ipv4IncludedRoutes
+ networkSettings.ipv4Settings = ipv4Settings
+
+ // IPv6 addresses
+ let addrs_v6 = addresses.filter { $0.address is IPv6Address }
+ .map { String(describing: $0.address) }
+ let masks_v6 = addresses.filter { $0.address is IPv6Address }
+ .map { NSNumber(value: $0.cidr) }
+ let ipv6Settings = NEIPv6Settings(addresses: addrs_v6, networkPrefixLengths: masks_v6)
+ ipv6Settings.includedRoutes = ipv6IncludedRoutes
+ networkSettings.ipv6Settings = ipv6Settings
+
+ networkSettings.mtu = mtu as NSNumber?
+ networkSettings.tunnelOverheadBytes = 80
+
+ let dnsServers = dns
+ let dnsSettings = NEDNSSettings(servers: dnsServers)
+ dnsSettings.searchDomains = dnsSearch
+ networkSettings.dnsSettings = dnsSettings
+
+ return networkSettings
+ }
+
+ /// Return array of routes for IPv4 and IPv6.
+ func routes() -> ([NEIPv4Route], [NEIPv6Route]) {
+ var ipv4IncludedRoutes = [NEIPv4Route]()
+ var ipv6IncludedRoutes = [NEIPv6Route]()
+
+ // Routes to interface addresses.
+ for addr_mask in addresses {
+ if addr_mask.address is IPv4Address {
+ let route = NEIPv4Route(destinationAddress: "\(addr_mask.address)",
+ subnetMask: "\(addr_mask.mask())")
+ route.gatewayAddress = "\(addr_mask.address)"
+ ipv4IncludedRoutes.append(route)
+ } else if addr_mask.address is IPv6Address {
+ let route = NEIPv6Route(
+ destinationAddress: "\(addr_mask.address)",
+ networkPrefixLength: NSNumber(value: addr_mask.cidr)
+ )
+ route.gatewayAddress = "\(addr_mask.address)"
+ ipv6IncludedRoutes.append(route)
+ }
+ }
+
+ // Routes to peer's allowed IPs.
+ for peer in peers {
+ for addr_mask in peer.allowedIPs {
+ if addr_mask.address is IPv4Address {
+ ipv4IncludedRoutes.append(
+ NEIPv4Route(destinationAddress: "\(addr_mask.address)",
+ subnetMask: "\(addr_mask.mask())"))
+ } else if addr_mask.address is IPv6Address {
+ ipv6IncludedRoutes.append(
+ NEIPv6Route(destinationAddress: "\(addr_mask.address)",
+ networkPrefixLength: NSNumber(value: addr_mask.cidr)))
+ }
+ }
+ }
+
+ return (ipv4IncludedRoutes, ipv6IncludedRoutes)
+ }
+
+ /// Client connection expects one peer, so check for that.
+ func isValidForClientConnection() -> Bool {
+ return peers.count == 1
+ }
+}
diff --git a/swift/plugin/Sources/Shared.swift b/swift/plugin/Sources/Shared.swift
new file mode 100644
index 00000000..35eaaacc
--- /dev/null
+++ b/swift/plugin/Sources/Shared.swift
@@ -0,0 +1,70 @@
+//
+// THIS IS A SIMPLE TEMPORARY SOLUTION TO SHARE SOME TYPES BETWEEN THE POD AND VPNEXTENSION
+// WE SHOULD PROBABLY COME UP WITH A BETTER SOLUTION IN THE FUTURE
+//
+
+import Foundation
+
+let suiteName = "group.net.defguard.mobile"
+
+public enum TunnelTraffic: String, Codable {
+ case All = "all"
+ case Predefined = "predefined"
+}
+
+public struct TunnelStartData: Codable {
+ public var publicKey: String
+ public var privateKey: String
+ public var address: String
+ public var dns: String?
+ public var endpoint: String
+ public var allowedIps: String
+ public var keepalive: Int
+ public var presharedKey: String?
+ public var traffic: TunnelTraffic
+ public var locationName: String
+ public var locationId: Int
+ public var instanceId: Int
+
+ public init(
+ publicKey: String, privateKey: String, address: String, dns: String? = nil,
+ endpoint: String, allowedIps: String, keepalive: Int, presharedKey: String? = nil,
+ traffic: TunnelTraffic, locationName: String, locationId: Int, instanceId: Int
+ ) {
+ self.publicKey = publicKey
+ self.privateKey = privateKey
+ self.address = address
+ self.dns = dns
+ self.endpoint = endpoint
+ self.allowedIps = allowedIps
+ self.keepalive = keepalive
+ self.presharedKey = presharedKey
+ self.traffic = traffic
+ self.locationName = locationName
+ self.locationId = locationId
+ self.instanceId = instanceId
+ }
+}
+
+public struct ActiveTunnelData: Codable {
+ var locationId: Int
+ var instanceId: Int
+ var traffic: TunnelTraffic
+
+ init(fromConfig: TunnelStartData) {
+ self.locationId = fromConfig.locationId
+ self.instanceId = fromConfig.instanceId
+ self.traffic = fromConfig.traffic
+ }
+}
+
+public enum WireguardEvent: String {
+ case tunnelUp = "tunnel_up"
+ case tunnelDown = "tunnel_down"
+ case tunnelWaiting = "tunnel_waiting"
+ case MFASessionExpired = "mfa_session_expired"
+}
+
+public enum TunnelStopError: String {
+ case mfaSessionExpired = "mfa_session_expired"
+}
diff --git a/swift/plugin/Sources/VPNError.swift b/swift/plugin/Sources/VPNError.swift
new file mode 100644
index 00000000..213ed1af
--- /dev/null
+++ b/swift/plugin/Sources/VPNError.swift
@@ -0,0 +1,39 @@
+import Foundation
+
+enum VPNError: Error, LocalizedError {
+ case invalidArguments(String)
+ case noManager(String = "No VPN manager available")
+ case configurationError(Error)
+ case timeoutError(String)
+ case saveError(Error)
+ case startError(Error)
+ case stopError(Error)
+ case invalidConfig
+
+ var errorDescription: String? {
+ switch self {
+ case .invalidArguments(let msg): return "Invalid arguments: \(msg)"
+ case .noManager(let msg): return "\(msg)"
+ case .configurationError(let error):
+ return "Configuration parsing error: \(error.localizedDescription)"
+ case .timeoutError(let msg): return "Timeout: \(msg)"
+ case .saveError(let error): return "Save error: \(error.localizedDescription)"
+ case .startError(let error): return "Start error: \(error.localizedDescription)"
+ case .stopError(let error): return "Stop error: \(error.localizedDescription)"
+ case .invalidConfig: return "Invalid configuration for client connection"
+ }
+ }
+
+// private var code: String {
+// switch self {
+// case .invalidArguments: return "INVALID_ARGUMENTS"
+// case .noManager: return "NO_MANAGER"
+// case .configurationError: return "CONFIG_ERROR"
+// case .timeoutError: return "TIMEOUT_ERROR"
+// case .saveError: return "SAVE_ERROR"
+// case .startError: return "START_ERROR"
+// case .stopError: return "STOP_ERROR"
+// case .invalidConfig: return "INVALID_CONFIG"
+// }
+// }
+}
diff --git a/swift/plugin/Sources/VPNManager.swift b/swift/plugin/Sources/VPNManager.swift
new file mode 100644
index 00000000..e6627337
--- /dev/null
+++ b/swift/plugin/Sources/VPNManager.swift
@@ -0,0 +1,173 @@
+import NetworkExtension
+import os
+
+public enum VPNManagerError: Error {
+ case providerManagerNotSet
+}
+
+/// Define protocol so `VPNManager` can be mocked for testing in `MockVPNManager`.
+public protocol VPNManagement {
+ var providerManager: NETunnelProviderManager? { get }
+ var connectionStatus: NEVPNStatus? { get }
+
+ func loadProviderManager(
+ name: String,
+ completion: @escaping (NETunnelProviderManager?) -> Void
+ )
+ func saveProviderManager(
+ _ manager: NETunnelProviderManager,
+ completion: @escaping (Error?) -> Void
+ )
+ func startTunnel() throws
+ func stopTunnel() throws
+ func handleVPNConfigurationChange()
+}
+
+public class VPNManager: VPNManagement {
+ static let shared = VPNManager()
+ private var logger = Logger(subsystem: appId, category: "WireguardPlugin.VPNManager")
+
+ public private(set) var providerManager: NETunnelProviderManager?
+
+ public var connectionStatus: NEVPNStatus? {
+ providerManager?.connection.status
+ }
+
+ func managerForConfig(
+ _ config: TunnelConfiguration,
+ completion: @escaping (NETunnelProviderManager?) -> Void
+ ) {
+ NETunnelProviderManager.loadAllFromPreferences { managers, error in
+ guard let managers = managers else {
+ self.logger.info("No tunnel managers in user's settings")
+ return
+ }
+ guard error == nil else {
+ self.logger.warning(
+ "Error loading tunnel managers: \(error, privacy: .public)")
+ self.providerManager = nil
+ completion(nil)
+ return
+ }
+ self.logger.info("Loaded \(managers.count, privacy: .public) tunnel managers.")
+
+ // Find the right protocol manager.
+ self.providerManager = nil
+ for manager in managers {
+ if manager.localizedDescription != config.name {
+ continue
+ }
+ guard
+ let tunnelProtocol = manager.protocolConfiguration as? NETunnelProviderProtocol
+ else {
+ continue
+ }
+ // Sometimes all managers from all apps come through, so filter by bundle ID.
+ if tunnelProtocol.providerBundleIdentifier == pluginAppId {
+ self.providerManager = manager
+ break
+ }
+ }
+ if self.providerManager == nil {
+ self.logger.log("No VPN manager found")
+ } else {
+ self.logger.log(
+ "Loaded provider manager: \(String(describing: self.providerManager!.localizedDescription), privacy: .public)"
+ )
+ }
+ completion(self.providerManager)
+ }
+ }
+
+ /// Loads named provider manager from the system preferences.
+ public func loadProviderManager(
+ name: String,
+ completion: @escaping (NETunnelProviderManager?) -> Void
+ ) {
+ NETunnelProviderManager.loadAllFromPreferences { managers, error in
+ guard let managers = managers else {
+ self.logger.info("No tunnel managers in user's settings")
+ return
+ }
+ guard error == nil else {
+ self.logger.warning(
+ "Error loading tunnel managers: \(error, privacy: .public)")
+ self.providerManager = nil
+ completion(nil)
+ return
+ }
+ self.logger.info("Loaded \(managers.count, privacy: .public) tunnel managers.")
+
+ // Find the right protocol manager.
+ self.providerManager = nil
+ for manager in managers {
+ if manager.localizedDescription != name {
+ continue
+ }
+ guard
+ let tunnelProtocol = manager.protocolConfiguration as? NETunnelProviderProtocol
+ else {
+ continue
+ }
+ // Sometimes all managers from all apps come through, so filter by bundle ID.
+ if tunnelProtocol.providerBundleIdentifier == pluginAppId {
+ self.providerManager = manager
+ break
+ }
+ }
+ if self.providerManager == nil {
+ self.logger.log("No VPN manager found")
+ } else {
+ self.logger.log(
+ "Loaded provider manager: \(String(describing: self.providerManager!.localizedDescription), privacy: .public)"
+ )
+ }
+ completion(self.providerManager)
+ }
+ }
+
+ /// Save the provider manager to system preferences.
+ public func saveProviderManager(
+ _ manager: NETunnelProviderManager,
+ completion: @escaping (Error?) -> Void
+ ) {
+ manager.saveToPreferences { error in
+ if let error = error {
+ self.logger.log("Failed to save provider manager: \(error, privacy: .public)")
+ completion(error)
+ } else {
+ self.providerManager = manager
+ completion(nil)
+ }
+ }
+ }
+
+ public func handleVPNConfigurationChange() {
+ logger.log("VPN configuration changed, updating provider manager")
+ // loadProviderManager { providerManager in
+ // guard let providerManager = providerManager else {
+ // self.logger.log("No VPN manager found after configuration change")
+ // return
+ // }
+ // self.providerManager = providerManager
+ // }
+ }
+
+ public func startTunnel() throws {
+ guard let providerManager = providerManager else {
+ throw VPNManagerError.providerManagerNotSet
+ }
+
+ try providerManager.connection.startVPNTunnel()
+ logger.log("VPN tunnel started successfully")
+ }
+
+ public func stopTunnel() throws {
+ guard let providerManager = providerManager else {
+ throw VPNManagerError.providerManagerNotSet
+ }
+
+ providerManager.connection.stopVPNTunnel()
+ logger.log("VPN tunnel stopped successfully")
+ }
+}
diff --git a/swift/plugin/Sources/Wireguard.swift b/swift/plugin/Sources/Wireguard.swift
new file mode 100644
index 00000000..5788821a
--- /dev/null
+++ b/swift/plugin/Sources/Wireguard.swift
@@ -0,0 +1,199 @@
+// Functions to be called from Rust code.
+
+import NetworkExtension
+import SwiftRs
+import os
+
+let appId = Bundle.main.bundleIdentifier ?? "net.defguard"
+let pluginAppId = "\(appId).VPNExtension"
+let plugin = WireguardPlugin()
+let logger = Logger(subsystem: appId, category: "WireguardPlugin")
+
+/// From preferences load `NETunnelProviderManager` with a given `name.
+func managerForName(
+ _ name: String,
+ completion: @escaping (NETunnelProviderManager?) -> Void
+) {
+ var providerManager: NETunnelProviderManager?
+ NETunnelProviderManager.loadAllFromPreferences { managers, error in
+ guard let managers = managers else {
+ logger.info("No tunnel managers in user's settings")
+ return
+ }
+ guard error == nil else {
+ logger.warning(
+ "Error loading tunnel managers: \(error, privacy: .public)")
+ providerManager = nil
+ completion(nil)
+ return
+ }
+ logger.info("Loaded \(managers.count, privacy: .public) tunnel managers.")
+
+ // Find the right protocol manager.
+ providerManager = nil
+ for manager in managers {
+ // Obtain named configuration.
+ if manager.localizedDescription != name {
+ continue
+ }
+ guard let tunnelProtocol = manager.protocolConfiguration as? NETunnelProviderProtocol
+ else {
+ continue
+ }
+ // Sometimes all managers from all apps come through, so filter by bundle ID.
+ if tunnelProtocol.providerBundleIdentifier == pluginAppId {
+ providerManager = manager
+ break
+ }
+ }
+ if providerManager == nil {
+ logger.log("No VPN manager found")
+ } else {
+ logger.log(
+ "Loaded provider manager: \(String(describing: providerManager!.localizedDescription), privacy: .public)"
+ )
+ }
+ completion(providerManager)
+ }
+}
+
+@_cdecl("start_tunnel")
+public func startTunnel(json: SRString) -> Bool {
+ let decoder = JSONDecoder()
+ guard let json_data = json.toString().data(using: .utf8) else {
+ logger.error("Failed to convert JSON string to data")
+ return false
+ }
+ let config: TunnelConfiguration
+ do { config = try decoder.decode(TunnelConfiguration.self, from: json_data) } catch {
+ logger.error(
+ "Failed to decode tunnel configuration: \(error.localizedDescription, privacy: .public)"
+ )
+ return false
+ }
+
+ if !config.isValidForClientConnection() {
+ logger.error("Invalid tunnel configuration: \(json.toString(), privacy: .public)")
+ return false
+ }
+
+ logger.info("Saving tunnel with config: \(String(describing: config))")
+ saveConfig(config)
+
+ // MFA is not that fast to propagate pre-shared key, so wait a moment here.
+ Thread.sleep(forTimeInterval: 1)
+ // Note: this will re-load configuration from preferneces which is a desired effect.
+ startVPN(name: config.name)
+
+ return true
+}
+
+@_cdecl("stop_tunnel")
+public func stopTunnel(name: SRString) -> Bool {
+ // Blocking
+ let semaphore = DispatchSemaphore(value: 0)
+
+ managerForName(name.toString()) { manager in
+ if let providerManager = manager {
+ providerManager.connection.stopVPNTunnel()
+ logger.info("VPN stopped")
+ }
+ semaphore.signal()
+ }
+
+ semaphore.wait()
+ return true
+}
+
+@_cdecl("tunnel_stats")
+public func tunnelStats(name: SRString) -> Stats? {
+ // Blocking
+ let semaphore = DispatchSemaphore(value: 0)
+ var result: Stats? = nil
+
+ managerForName(name.toString()) { manager in
+ if let providerManager = manager as NETunnelProviderManager? {
+ let session = providerManager.connection as! NETunnelProviderSession
+ do {
+ // TODO: data should contain a valid message.
+ let data = Data()
+ try session.sendProviderMessage(data) { response in
+ if let data = response {
+ let decoder = JSONDecoder()
+ result = try? decoder.decode(Stats.self, from: data)
+ }
+ semaphore.signal()
+ }
+ } catch {
+ logger.error("Failed to send message to tunnel extension \(error)")
+ semaphore.signal()
+ }
+ }
+ }
+
+ semaphore.wait()
+ return result
+}
+
+/// Save `TunnelConfiguration` to preferences.
+func saveConfig(_ config: TunnelConfiguration) {
+ // Blocking
+ let semaphore = DispatchSemaphore(value: 0)
+
+ managerForName(config.name) { manager in
+ let providerManager = manager ?? NETunnelProviderManager()
+ let tunnelProtocol = NETunnelProviderProtocol()
+ tunnelProtocol.providerBundleIdentifier = pluginAppId
+ // `serverAddress` must have a non-nil string value for the protocol configuration to be valid.
+ if let endpoint = config.peers[0].endpoint {
+ tunnelProtocol.serverAddress = endpoint.toString()
+ } else {
+ tunnelProtocol.serverAddress = ""
+ }
+ let configDict: [String: Any]
+ do {
+ configDict = try config.toDictionary()
+ } catch {
+ logger.log(
+ "Failed to convert config to dictionary: \(error.localizedDescription, privacy: .public)"
+ )
+ // TODO: signal failure
+ semaphore.signal()
+ return
+ }
+ tunnelProtocol.providerConfiguration = configDict
+ providerManager.protocolConfiguration = tunnelProtocol
+ providerManager.localizedDescription = config.name
+ providerManager.isEnabled = true
+
+ providerManager.saveToPreferences { error in
+ if let error = error {
+ logger.log("Failed to save provider manager: \(error, privacy: .public)")
+ // TODO: signal failure
+ } else {
+ logger.info("Config saved")
+ }
+
+ semaphore.signal()
+ }
+ }
+
+ semaphore.wait()
+}
+
+
+/// Start VPN tunnel for a given `name`.
+func startVPN(name: String) {
+ managerForName(name) { manager in
+ guard let providerManager = manager else {
+ logger.warning("Couldn't load \(name) configuration from preferences")
+ return
+ }
+ do {
+ try providerManager.connection.startVPNTunnel()
+ logger.info("VPN started")
+ } catch {
+ logger.error("Failed to start VPN: \(error, privacy: .public)")
+ }
+ }
+}
diff --git a/swift/plugin/Sources/WireguardPlugin.swift b/swift/plugin/Sources/WireguardPlugin.swift
new file mode 100644
index 00000000..357273b5
--- /dev/null
+++ b/swift/plugin/Sources/WireguardPlugin.swift
@@ -0,0 +1,485 @@
+import NetworkExtension
+import os
+
+// The timeout for waiting for the tunnel status to change (e.g. when connecting or disconnecting).
+let tunnelStatusTimeout: TimeInterval = 10.0
+
+public class WireguardPlugin: NSObject {
+ private var activeTunnelData: ActiveTunnelData?
+ private var connectionObserver: NSObjectProtocol?
+ private var configurationObserver: NSObjectProtocol?
+ private var vpnManager: VPNManagement
+ private var logger = Logger(
+ subsystem: appId,
+ category: "WireguardPlugin")
+
+ public init(vpnManager: VPNManagement? = nil) {
+ if let vpnManager = vpnManager {
+ self.logger.debug("Using provided VPN manager")
+ self.vpnManager = vpnManager
+ } else {
+ self.logger.debug("Creating new VPN manager instance")
+ self.vpnManager = VPNManager.shared
+ }
+ super.init()
+ }
+
+ /// Loads the active tunnel data from the system configuration.
+ private func getActiveTunnelData(completion: @escaping (ActiveTunnelData?) -> Void) {
+ guard let providerManager = vpnManager.providerManager else {
+ logger.log("No VPN manager found")
+ return
+ }
+
+ if let config = providerManager.protocolConfiguration
+ as? NETunnelProviderProtocol,
+ let configDict = config.providerConfiguration,
+ let activeTunnelData = try? ActiveTunnelData.from(
+ dictionary: configDict
+ )
+ {
+ completion(activeTunnelData)
+ } else {
+ logger.log("No active tunnel data available")
+ completion(nil)
+ }
+ }
+
+ /// Loads the possibly already existing VPN manager and sets up observers for VPN connection status changes if its present.
+ /// This is to ensure that the VPN status is observed and updated correctly when the app starts.
+ // private func setupVPNManager(
+ // completion: @escaping () -> Void
+ // ) {
+ // vpnManager.loadProviderManager { manager in
+ // if manager == nil {
+ // self.logger.log(
+ // "No provider manager found, the VPN status won't be observed until the VPN is started."
+ // )
+ // } else {
+ // self.logger.log(
+ // "VPN manager loaded successfully, the VPN status will be observed and updated.")
+ // }
+ // completion()
+ // }
+ // }
+
+ /// Sets up observers for VPN connection status changes.
+ private func setupVPNObservers() {
+ if connectionObserver != nil {
+ logger.log("VPN observers already set up, removing it first")
+ removeVPNObservers()
+ }
+ guard let providerManager = vpnManager.providerManager else {
+ logger.log("No provider manager found, cannot set up VPN observers")
+ return
+ }
+ connectionObserver = NotificationCenter.default.addObserver(
+ forName: .NEVPNStatusDidChange,
+ object: providerManager.connection,
+ queue: .main,
+ using: { notification in
+ self.handleVPNStatusChange()
+ }
+ )
+ configurationObserver = NotificationCenter.default.addObserver(
+ forName: .NEVPNConfigurationChange,
+ object: nil,
+ queue: .main,
+ using: { notification in
+ self.vpnManager.handleVPNConfigurationChange()
+ self.handleVPNStatusChange()
+ }
+ )
+ }
+
+ private func removeVPNObservers() {
+ if let observer = connectionObserver {
+ NotificationCenter.default.removeObserver(observer)
+ connectionObserver = nil
+ }
+ if let observer = configurationObserver {
+ NotificationCenter.default.removeObserver(observer)
+ configurationObserver = nil
+ }
+ }
+
+ deinit {
+ removeVPNObservers()
+ }
+
+ /// Updates the UI status of the VPN connection. Used when the status changes asynchronously.
+ private func handleVPNStatusChange() {
+ guard let vpnStatus = vpnManager.connectionStatus else {
+ logger.log("Failed to get VPN status, the provider manager has not been loaded yet.")
+ return
+ }
+
+ switch vpnStatus {
+ case .connected:
+ logger.log("Detected that the VPN has connected, emitting event.")
+ let encoder = JSONEncoder()
+ encoder.keyEncodingStrategy = .convertToSnakeCase
+ if let activeTunnelData = activeTunnelData {
+ guard let data = try? encoder.encode(activeTunnelData),
+ let dataString = String(data: data, encoding: .utf8)
+ else {
+ logger.log("Failed to encode active tunnel data")
+ return
+ }
+ self.activeTunnelData = activeTunnelData
+ // self.emitEvent(
+ // event: WireguardEvent.tunnelUp,
+ // data: dataString
+ // )
+ } else {
+ getActiveTunnelData { activeTunnelData in
+ guard let activeTunnelData = activeTunnelData else {
+ self.logger.log("No active tunnel data available")
+ // self.emitEvent(
+ // event: WireguardEvent.tunnelDown,
+ // data: nil
+ // )
+ return
+ }
+ guard let data = try? encoder.encode(activeTunnelData),
+ let dataString = String(data: data, encoding: .utf8)
+ else {
+ self.logger.log("Failed to encode active tunnel data")
+ return
+ }
+ self.activeTunnelData = activeTunnelData
+ // self.emitEvent(
+ // event: WireguardEvent.tunnelUp,
+ // data: dataString
+ // )
+ }
+ }
+ setupVPNObservers()
+ case .disconnected, .invalid:
+ logger.log(
+ "Detected that the system VPN status is disconnected. Emitting event if our state differs"
+ )
+ // no point in emitting this event if we already agree that the tunnel is down
+ if activeTunnelData != nil {
+ if let lastError = getLastTunnelError() {
+ logger.log(
+ "Detected that the tunnel stopped due to the following error: \(lastError.rawValue, privacy: .public)"
+ )
+ if lastError == .mfaSessionExpired {
+ logger.log(
+ "Detected that the tunnel stopped due to MFA session expiration, emitting event."
+ )
+ // emitEvent(event: WireguardEvent.MFASessionExpired, data: nil)
+ } else {
+ logger.warning(
+ "Detected that the tunnel stopped due to an unknown error: \(lastError.rawValue, privacy: .public)"
+ )
+ // emitEvent(event: WireguardEvent.tunnelDown, data: nil)
+ }
+ resetLastTunnelError()
+ } else {
+ // emitEvent(event: WireguardEvent.tunnelDown, data: nil)
+ }
+
+ activeTunnelData = nil
+
+ logger.log(
+ "Our state differed, emitted event to inform the frontend about stopped tunnel."
+ )
+ } else {
+ logger.log("Our state did not differ, no event emitted.")
+ }
+ case .connecting:
+ logger.log(
+ "Detected that VPN is connecting, ignoring it since it is a temporary state we don't handle."
+ )
+ case .disconnecting:
+ logger.log(
+ "Detected that VPN is disconnecting, ignoring it since it is a temporary state we don't handle."
+ )
+ case .reasserting:
+ logger.log(
+ "Detected that VPN is reasserting, ignoring it since it is a temporary state we don't handle."
+ )
+ @unknown default:
+ logger.log(
+ "Detected unknown VPN status: \(vpnStatus.rawValue, privacy: .public), ignoring it since it is a state we don't handle."
+ )
+ }
+ }
+
+ private func getLastTunnelError() -> TunnelStopError? {
+ let defaults = UserDefaults(suiteName: suiteName)
+ guard let lastError = defaults?.string(forKey: "lastTunnelError") else {
+ logger.log("No last tunnel error found in user defaults")
+ return nil
+ }
+ logger.log("Last tunnel error found: \(lastError, privacy: .public)")
+ if let error = TunnelStopError(rawValue: lastError) {
+ return error
+ } else {
+ logger.error(
+ "Last tunnel error is not a valid TunnelStopError: \(lastError, privacy: .public)")
+ return nil
+ }
+ }
+
+ private func resetLastTunnelError() {
+ let defaults = UserDefaults(suiteName: suiteName)
+ defaults?.removeObject(forKey: "lastTunnelError")
+ }
+
+ // private func saveConfig(config: TunnelConfiguration, result: @escaping (VPNError?) -> Void) {
+ // logger.info("Saving tunnel config: \(String(describing: config))")
+ //
+ // vpnManager.saveProviderManager(providerManager) { saveError in
+ // if let saveError = saveError {
+ // self.logger.log("Failed to save preferences: \(saveError, privacy: .public)")
+ // result(
+ // VPNError.saveError(
+ // saveError
+ // )
+ // )
+ // return
+ // }
+ // }
+ // }
+
+ func startTunnel(
+ config: TunnelConfiguration,
+ completion: @escaping (VPNError?) -> Void
+ ) {
+ logger.log("Starting tunnel with config: \(String(describing: config))")
+
+ if !config.isValidForClientConnection() {
+ completion(VPNError.invalidConfig)
+ }
+
+ vpnManager.loadProviderManager(name: config.name) { manager in
+ let providerManager = manager ?? NETunnelProviderManager()
+ let tunnelProtocol = NETunnelProviderProtocol()
+ tunnelProtocol.providerBundleIdentifier = pluginAppId
+ // `serverAddress` must have a non-nil string value for the protocol configuration to be valid.
+ if let endpoint = config.peers[0].endpoint {
+ tunnelProtocol.serverAddress = endpoint.toString()
+ } else {
+ tunnelProtocol.serverAddress = ""
+ }
+ let configDict: [String: Any]
+ do {
+ configDict = try config.toDictionary()
+ } catch {
+ self.logger.log(
+ "Failed to convert config to dictionary: \(error.localizedDescription, privacy: .public)"
+ )
+ completion(
+ VPNError.configurationError(error)
+ )
+ return
+ }
+ tunnelProtocol.providerConfiguration = configDict
+ providerManager.protocolConfiguration = tunnelProtocol
+ providerManager.localizedDescription = config.name
+ providerManager.isEnabled = true
+
+ if let status = self.vpnManager.connectionStatus {
+ if status == .connected || status == .connecting {
+ do {
+ try self.vpnManager.stopTunnel()
+ } catch {
+ self.logger.log("Failed to stop VPN tunnel: \(error, privacy: .public)")
+ completion(
+ VPNError.stopError(
+ error
+ )
+ )
+ return
+ }
+ self.logger.log("Stopped running VPN tunnel to update config")
+ self.waitForTunnelStatus(
+ desiredStatuses: [.disconnected, .invalid]
+ ) { status in
+ if let status = status {
+ self.logger.log("Timeout waiting for tunnel to disconnect")
+ completion(
+ VPNError.timeoutError(
+ "The tunnel disconnection has failed to complete in a specified amount of time (\(tunnelStatusTimeout) seconds). Please check your configuration and try again. Current status: \(status.rawValue)"
+ )
+ )
+ return
+ }
+ self.saveAndStartTunnel(
+ providerManager: providerManager,
+ config: config,
+ result: completion
+ )
+ return
+ }
+ }
+ }
+ self.saveAndStartTunnel(
+ providerManager: providerManager,
+ config: config,
+ result: completion
+ )
+ }
+ }
+
+ /// Waits for the VPN connection to reach one of the desired statuses.
+ /// If it does not reach the desired status within the timeout,
+ /// it returns the current status.
+ private func waitForTunnelStatus(
+ desiredStatuses: [NEVPNStatus],
+ completion: @escaping (NEVPNStatus?) -> Void
+ ) {
+ let checkInterval = 0.2
+ var elapsedTime = 0.0
+ logger.log(
+ "Waiting for VPN status to change to one of: \(desiredStatuses.map { $0.rawValue })"
+ )
+ func check() {
+ guard let status = vpnManager.connectionStatus else {
+ self.logger.log("No VPN connection status available")
+ completion(nil)
+ return
+ }
+ self.logger.log("Checking VPN status: \(status.rawValue, privacy: .public)")
+ if desiredStatuses.contains(status) {
+ self.logger.log(
+ "Desired VPN status reached: \(status.rawValue, privacy: .public)"
+ )
+ completion(nil)
+ } else {
+ elapsedTime += checkInterval
+ if elapsedTime >= tunnelStatusTimeout {
+ completion(status)
+ } else {
+ DispatchQueue.main.asyncAfter(
+ deadline: .now() + checkInterval
+ ) {
+ check()
+ }
+ }
+ }
+ }
+ check()
+ }
+
+ private func saveAndStartTunnel(
+ providerManager: NETunnelProviderManager,
+ config: TunnelConfiguration,
+ result: @escaping (VPNError?) -> Void
+ ) {
+ vpnManager.saveProviderManager(providerManager) { saveError in
+ if let saveError = saveError {
+ self.logger.log("Failed to save preferences: \(saveError, privacy: .public)")
+ result(
+ VPNError.saveError(
+ saveError
+ )
+ )
+ return
+ }
+ self.startVPNTunnel(
+ config: config,
+ result: result
+ )
+ }
+ }
+
+ private func closeTunnel(result: @escaping (VPNError?) -> Void) {
+ logger.log("Stopping tunnel")
+
+ guard let status = vpnManager.connectionStatus else {
+ logger.log("No VPN connection status available")
+ result(
+ VPNError.noManager(
+ "No VPN connection status available. The tunnel may not be running."
+ )
+ )
+ // emitEvent(event: WireguardEvent.tunnelDown, data: nil)
+ return
+ }
+
+ if status == .connected || status == .connecting {
+ removeVPNObservers()
+ do {
+ try vpnManager.stopTunnel()
+ } catch {
+ logger.log("Failed to stop VPN tunnel: \(error, privacy: .public)")
+ result(
+ VPNError.stopError(error)
+ )
+ return
+ }
+
+ waitForTunnelStatus(desiredStatuses: [.disconnected, .invalid]) { status in
+ if let status = status {
+ self.logger.log(
+ "Timeout waiting for tunnel to disconnect: \(status.rawValue, privacy: .public)"
+ )
+ result(
+ VPNError.timeoutError(
+ "The tunnel disconnection has failed to complete in a specified amount of time (\(tunnelStatusTimeout) seconds). Please check your configuration and try again."
+ )
+ )
+ return
+ }
+ self.handleVPNStatusChange()
+ self.logger.log("VPN tunnel stopped")
+ result(nil)
+ }
+ } else {
+ logger.log("VPN tunnel is not running")
+ // Emit event just to update the UI if its broken
+ // emitEvent(event: WireguardEvent.tunnelDown, data: nil)
+ result(nil)
+ }
+ }
+
+ // private func emitEvent(event: WireguardEvent, data: String?) {
+ // logger.log(
+ // "Emitting event: \(event.rawValue, privacy: .public), data: \(String(describing: data), privacy: .public)"
+ // )
+ // guard let eventSink = eventSink else {
+ // logger.log("No event sink available, cannot emit event")
+ // return
+ // }
+ // let event: [String: Any?] = [
+ // "event": event.rawValue,
+ // "data": data,
+ // ]
+ // eventSink(event)
+ // }
+
+ private func startVPNTunnel(
+ config: TunnelConfiguration,
+ result: @escaping (VPNError?) -> Void
+ ) {
+ do {
+ try vpnManager.startTunnel()
+ // This is done because the frontend expects a blocking action to display a loading indicator.
+ waitForTunnelStatus(desiredStatuses: [.connected]) { status in
+ if status != nil {
+ self.logger.log("Timeout waiting for tunnel to connect.")
+ result(
+ VPNError.timeoutError(
+ "The tunnel connection has failed to be established in a specified amount of time. Please check your configuration and try again."
+ )
+ )
+ return
+ }
+ self.handleVPNStatusChange()
+ self.logger.log("VPN tunnel started successfully")
+ result(nil)
+ }
+ } catch {
+ logger.error("Failed to start VPN: \(error, privacy: .public)")
+ result(
+ VPNError.startError(
+ error,
+ )
+ )
+ }
+ }
+}